AWS Transfer Family 接続時のPermission Denied等のエラーに対して、AthenaでS3 アクセスログを分析してみた

2024.02.06

はじめに

AWS Transfer Familyへの接続時に、Permission Deniedになる連絡を受けた際、S3のアクセスログからリクエストの詳細を調査してエラー解決することができますので、その手順をまとめました。

Transfer Familyでは、標準設定でCloudWatch Logsに以下のようにログを出力されます。

{
    "path": "/S3バケット名/test.py",
    "activity-type": "ERROR",
    "resource-arn": "arn:aws:transfer:ap-northeast-1:xxxx:server/s-aacfeafc8f2840b9a",
    "message": "Access denied",
    "bytes-in": "8405",
    "operation": "CLOSE",
    "session-id": "03bf7ddc20e33b8c"
}
{
    "activity-type": "ERROR",
    "resource-arn": "arn:aws:transfer:ap-northeast-1:xxxx:server/s-xxxxxxxxx",
    "message": "Access denied",
    "session-id": "03bf7ddc20e33b8c"
}

ただし、このログではエラーメッセージや対象のパスは確認できますが、ユーザー情報は記載されていません。そのため、ユーザー情報も含めて確認するためにはS3のアクセスログを見ることをおすすめします。

ちなみに、CloudTrailでも確認しましたが、エラーに関するログは取得されていませんでした。

事前準備

構成は下記の通りです。

Transfer Familyは、下記の仕様で作成済です。

  • エンドポイント:パブリック
  • プロトコル:SFTP
  • Transfer Familyのファイル転送先(ドメイン): S3バケット
  • ユーザー管理方法:サービスマネージド

ファイル転送先S3バケットとアクセスログ用のS3バケットを事前に構築しておきます。

手順

手順は下記の通りです。

  1. S3バケットのアクセスログを有効化
  2. Athenaで分析する

S3バケットのアクセスログを有効化

Transfer Family経由で転送するS3バケットのアクセスログを有効化します。保存先は別のS3バケットを指定します。

ドキュメントにも記載されている通り、アクセスログが有効になるまでには時間がかかるため注意が必要です。

バケットのログ記録ステータスの変更がログファイルの配信に反映されるまでには時間がかかります。例えば、バケットのログを有効にする場合、その後数時間に行われるリクエストは記録される場合もあれば、されない場合もあります。ログ記録の送信先バケットをバケット A からバケット B に変更すると、その後 1 時間は一部のログがバケット A に引き続き配信されたり、新しいターゲットバケット B に配信されたりします。引用

Athenaで分析

S3のアクセスログをAthenaで分析します。

本記事のAthenaのクエリは、AWSドキュメントを参考にしています。

まず、データベースを作成します。

CREATE DATABASE s3_access_logs_db

次に、データベースのテーブルスキーマを作成します。クエリ内のLOCATIONは、実際のS3バケット名に変更してください。

CREATE EXTERNAL TABLE `s3_access_logs_db.mybucket_logs`(
  `bucketowner` STRING, 
  `bucket_name` STRING, 
  `requestdatetime` STRING, 
  `remoteip` STRING, 
  `requester` STRING, 
  `requestid` STRING, 
  `operation` STRING, 
  `key` STRING, 
  `request_uri` STRING, 
  `httpstatus` STRING, 
  `errorcode` STRING, 
  `bytessent` BIGINT, 
  `objectsize` BIGINT, 
  `totaltime` STRING, 
  `turnaroundtime` STRING, 
  `referrer` STRING, 
  `useragent` STRING, 
  `versionid` STRING, 
  `hostid` STRING, 
  `sigv` STRING, 
  `ciphersuite` STRING, 
  `authtype` STRING, 
  `endpoint` STRING, 
  `tlsversion` STRING,
  `accesspointarn` STRING,
  `aclrequired` STRING)
ROW FORMAT SERDE 
  'org.apache.hadoop.hive.serde2.RegexSerDe' 
WITH SERDEPROPERTIES ( 
  'input.regex'='([^ ]*) ([^ ]*) \\[(.*?)\\] ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) (\"[^\"]*\"|-) (-|[0-9]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) (\"[^\"]*\"|-) ([^ ]*)(?: ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*))?.*$') 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.mapred.TextInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
  's3://DOC-EXAMPLE-BUCKET1-logs/prefix/'

ステータスが200以外のログを出力するクエリを実行します。

SELECT 
    bucket_name,
    requestdatetime,
    requester,
    httpstatus,
    errorcode,
    operation,
    key,
    request_uri,
    useragent
FROM 
    s3_access_logs_db.mybucket_logs 
WHERE 
    httpstatus <> '200'
ORDER BY 
    requestdatetime DESC;

クエリ結果から、Transfer-Family-RoleというIAMロールのtest1というユーザーがAccessDeniedを発生させていることがわかりました。

IAMロールがTransfer-Family-Roleかつ、AccessDeniedエラーのログのみを出力するクエリを実行します。

SELECT 
    bucket_name,
    requestdatetime,
    requester,
    httpstatus,
    errorcode,
    operation,
    key,
    request_uri,
    useragent
FROM 
    s3_access_logs_db.mybucket_logs 
WHERE 
    requester LIKE '%Transfer-Family-Role%'
    AND errorcode = 'AccessDenied'
ORDER BY 
    requestdatetime DESC;

ちなみに、下記のように時間範囲を指定することも可能です。

SELECT 
    bucket_name,
    requestdatetime,
    requester,
    httpstatus,
    errorcode,
    operation,
    key,
    request_uri,
    useragent
FROM 
    s3_access_logs_db.mybucket_logs 
WHERE 
    requester LIKE '%Transfer-Family-Role%'
    AND errorcode = 'AccessDenied'
    AND parse_datetime(RequestDateTime,'dd/MMM/yyyy:HH:mm:ss Z')    
    BETWEEN timestamp '2024-02-01 00:00:00'    
    AND timestamp '2024-02-01 02:00:00' 
ORDER BY 
    requestdatetime DESC;

結果から複数のエラーが確認できました。

赤枠部分からは、Transfer-Family-Roletest1というユーザーがtest.pyファイルをS3バケットにアップロードしようとした際、AccessDeniedが発生したことがわかります。

よって、赤枠のAccessDeniedエラーは、IAMロールTransfer-Family-Roles3:PutObjectの権限を許可することで解決できます。

Transfer Family経由のアクセスログ

ちなみに、「AWSマネジメントコンソール上からS3バケットにアクセスした場合」と「Transfer Family経由でS3バケットにアクセスした場合」では、S3のアクセスログのrequesteruseragentから前者もしくは後者と判断できます。

例えば、下記の違いがあります。

  • AWSマネジメントコンソール上からS3バケットにアクセスした時
    • requester:arn:aws:sts::xxx:assumed-role/スイッチ元IAMユーザ名/スイッチ先IAMロール名
    • useragent:S3Console/0.4, aws-internal/...省略
  • Transfer Family経由でS3バケットにアクセス時
    • requester:arn:aws:sts::xxx:assumed-role/Transfer Familyのユーザー用のIAMロール/Transfer Familyのユーザー名.セッションID@Transfer FamilyのサーバーID
    • useragent:aws-transfer, aws-internal/...省略

最後に

今回は、AWS Transfer Familyへの接続時に発生したPermission Denied エラーを、S3のアクセスログを分析することで解決する方法について解説しました。

アクセスログの有効化とAthenaでの分析のみで容易に調査が可能であることがお分かりいただけたかと思います。

どなたかの参考になれば幸いです。