ようへいの日々精進XP

よかろうもん

goofys で S3 バケットをマウントするにあたって, 最低限必要な IAM Policy を調べた

tl;dr

Amazon S3 をマウントしてファイルシステムのように扱うことが出来るツールとして, いにしへの昔から s3fs というツールが有名ですが, その s3fs を凌駕するパフォーマンスを備える goofys というツールを検証する機会がありました.

github.com

そもそも, Amazon S3ファイルシステムのように扱って良いものかどうかについての議論は別の機会にするとして, 簡単な検証ではありましたが, Go でコンパイルされたシングルバイナリをダウンロードしてきて, 以下のようにコマンド一発で S3 バケットが簡単にマウントしてファイルの読み書きが出来るのは気持ち良かったです.

$ goofys your.s3.bucket.com your-mount-point

尚, goofys の詳細については, 上記の Github リポジトリの README や, 各種検証記事を参照して頂ければと思います.

今回, この記事では, goofys を利用するにあたり, S3 バケットにどのようなポリシーを付与すれば, 正常にマウント出来るのか調べた内容をメモしています. 例によって, 本記事の内容は, 自分の手元の環境で調べた内容となり, goofys オフィシャルの見解 (が有った場合) とは異なる場合がありますのでご容赦下さい.

全て許可ならなんでも出来る (by アンダコレ猪木

今回の構成

以下のような構成で確認しました.

S3 バケット goofys.inokara.debug を EC2 上の goofys にマウントしたいと思います. 尚, goofys のインストールについては, ビルドされたバイナリ をパスが通ったディレクトリに保存しています.

シンプルに

やりたいのであれば, マネジメントポリシーの AmazonS3FullAccess を付与しておきましょう. 特に何も問題なくバケットをマウント出来ますし, ファイル (オブジェクト) の操作が可能です. 以下のように goofys を起動する際に --debug_s3 オプションと -f (フォアグラウンドで起動) を付与します. そうすることで, goofys が S3 とどのような通信を行っているかをリアルタイムに確認することが可能です.

$ goofys-latest --debug_s3 -f goofys.inokara.debug goofys

起動すると, 以下のように出力されます.

$ goofys-latest --debug_s3 -f goofys.inokara.debug goofys
2019/03/03 06:47:08.432202 s3.DEBUG HEAD https://s3.amazonaws.com/goofys.inokara.debug = 301 [ap-northeast-1]
2019/03/03 06:47:08.432276 s3.INFO Switching from region 'us-east-1' to 'ap-northeast-1'
2019/03/03 06:47:08.434407 s3.DEBUG DEBUG: Request ec2metadata/GetMetadata Details:
---[ REQUEST POST-SIGN ]-----------------------------
GET /latest/meta-data/iam/security-credentials HTTP/1.1
Host: 169.254.169.254
User-Agent: aws-sdk-go/1.8.25 (go1.9.2; linux; amd64)
Accept-Encoding: gzip

... () ...

-----------------------------------------------------
2019/03/03 06:47:08.437554 s3.DEBUG DEBUG: Request s3/HeadObject Details:
---[ REQUEST POST-SIGN ]-----------------------------
HEAD /goofys.inokara.debug/uirkrag88e0lkubvsqlv5nt33pyi5z4b HTTP/1.1
Host: s3-ap-northeast-1.amazonaws.com
User-Agent: aws-sdk-go/1.8.25 (go1.9.2; linux; amd64)
Authorization: AWS4-HMAC-SHA256 Credential=XXXXXXXXXXXXXXXXXXXXXX/20190303/ap-northeast-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token,
Signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Amz-Content-Sha256: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Amz-Date: 20190303T064708Z
X-Amz-Security-Token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

-----------------------------------------------------
2019/03/03 06:47:08.470979 s3.DEBUG DEBUG: Response s3/HeadObject Details:
---[ RESPONSE ]--------------------------------------
HTTP/1.1 404 Not Found
Transfer-Encoding: chunked
Content-Type: application/xml
Date: Sun, 03 Mar 2019 06:47:07 GMT
Server: AmazonS3
X-Amz-Id-2: QMaYNxI2kCk5NAXL+h44hlTwwVuQX0LI3i+/PZG29w3Z2z6zbbE/fuBeXIG9IDcVl+pFfJC+Qvg=
X-Amz-Request-Id: 68D120986D39ADA3


... () ...

-----------------------------------------------------
2019/03/03 06:47:08.473279 main.INFO File system has been successfully mounted.
2019/03/03 06:47:08.492164 s3.DEBUG DEBUG: Response s3/ListMultipartUploads Details:
---[ RESPONSE ]--------------------------------------
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/xml
Date: Sun, 03 Mar 2019 06:47:09 GMT
Server: AmazonS3
X-Amz-Id-2: 12MVKsLN/9gPBD5rhOLRvQ3dSfOz+1ieFos7eq77lBXo9YZnC+14ynqzz/+CZ5xZhsb3peEjGPM=
X-Amz-Request-Id: F5CC8A70E5CB4E8B


-----------------------------------------------------
2019/03/03 06:47:08.492207 s3.DEBUG {
  Bucket: "goofys.inokara.debug",
  IsTruncated: false,
  KeyMarker: "",
  MaxUploads: 1000,
  NextKeyMarker: "",
  NextUploadIdMarker: "",
  UploadIdMarker: ""
}

マウントされるだけで随分いろいろなやりとりが行われていることが判ります. S3 だけではなく, EC2 のメタデータへのアクセスも行われていることも判ります. さらに興味深いのは, S3 に対して, 最初に HeadObject リクエストを送信している点です. 引き続き, マウントしたディレクトリに移動して ls コマンドを叩いてみたところ, 以下のように出力されました.

---[ REQUEST POST-SIGN ]-----------------------------
GET /goofys.inokara.debug?delimiter=%2F&prefix= HTTP/1.1
Host: s3-ap-northeast-1.amazonaws.com
User-Agent: aws-sdk-go/1.8.25 (go1.9.2; linux; amd64)
Accept-Encoding: identity
Authorization: AWS4-HMAC-SHA256 Credential=XXXXXXXXXXXXXXXXXXXXXX/20190303/ap-northeast-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token, Signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Amz-Content-Sha256: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Amz-Date: 20190303T085410Z
X-Amz-Security-Token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


-----------------------------------------------------
2019/03/03 08:54:10.553495 s3.DEBUG DEBUG: Response s3/ListObjects Details:
---[ RESPONSE ]--------------------------------------
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/xml
Date: Sun, 03 Mar 2019 08:54:11 GMT
Server: AmazonS3
X-Amz-Bucket-Region: ap-northeast-1
X-Amz-Id-2: PdxicsoYsHk8URTDbhw87NEJxPrXZ8yTOY2KKdQkWKZjplVDMa81xzrjWnF48Onz7IXRR1DL4/4=
X-Amz-Request-Id: 3BA625E381F3D35C


-----------------------------------------------------
2019/03/03 08:54:10.553700 s3.DEBUG {
  Contents: [{
      ETag: "\"d41d8cd98f00b204e9800998ecf8427e\"",
      Key: "test.txt",
      LastModified: 2019-03-03 06:43:16 +0000 UTC,
      Owner: {
        DisplayName: "inokara",
        ID: "8699d12cece0e8306cc7c614f7f169f4b61da044533a29f0b8431b781fcd9b93"
      },
      Size: 0,
      StorageClass: "STANDARD"
    }],
  Delimiter: "/",
  IsTruncated: false,
  Marker: "",
  MaxKeys: 1000,
  Name: "goofys.inokara.debug",
  Prefix: ""
}

ListObject リクエストが送信されていることが判ります. ここで疑問に湧くのが, ls コマンドが実行すると ListObjects がコールされる点. この関連付けってどこで行われているだろうと気になります. ちゃんと調べきれていませんが, このあたりで FUSE が暗躍していて, lsシステムコールFUSE がフックして ListObjects を叩いているのかなと妄想しております.

さて

試すだけなら

goofys を試すだけなら, 先述のようにマウントする EC2 に AmazonS3FullAccess を与えておけば良いと思います. しかし, 実際に業務等で運用する場合, 出来るだけ大きな権限を与えたくないというのが定石だと思います. ということで, goofys を利用するにあたって, 最低限必要なポリシーを探ってみたいと思います.

仮想要件

まず, 権限無し

まずは, 先述の環境の S3 にアクセスする為のポリシーを削除してみます. この状態ではマウントに失敗する想定ですが, goofys を実行すると以下のように出力されました.

$ goofys-latest --debug_s3 -f goofys.inokara.debug goofys

... () ...

-----------------------------------------------------
2019/03/03 10:45:15.307392 s3.DEBUG DEBUG: Response s3/HeadObject Details:
---[ RESPONSE ]--------------------------------------
HTTP/1.1 403 Forbidden
Transfer-Encoding: chunked
Content-Type: application/xml
Date: Sun, 03 Mar 2019 10:45:14 GMT
Server: AmazonS3
X-Amz-Id-2: KQBkujVF1zNsd63FYXJTJ9pI3U9PIrKPDJTkDoEIMak+gtYVfUi67DPyjb5DeBl2zEuHbX/7mJg=
X-Amz-Request-Id: CF02690385F33272



-----------------------------------------------------
2019/03/03 10:45:15.307511 s3.DEBUG DEBUG: Validate Response s3/HeadObject failed, not retrying, error Forbidden: Forbidden
        status code: 403, request id: CF02690385F33272, host id: KQBkujVF1zNsd63FYXJTJ9pI3U9PIrKPDJTkDoEIMak+gtYVfUi67DPyjb5DeBl2zEuHbX/7mJg=
2019/03/03 10:45:15.307584 main.ERROR Unable to access 'goofys.inokara.debug': permission denied
2019/03/03 10:45:15.307627 main.FATAL Mounting file system: Mount: initialization failed

想定通りマウントに失敗しました.

ListBucket ポリシー

docs.aws.amazon.com

上記のドキュメントを参考にして, ListBucket ポリシーを付与してみます.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::goofys.inokara.debug"
            ]
        }
    ]
}

この状態で goofys でバケットをマウントしてみると, 以下のように出力されました.

$ goofys-latest --debug_s3 -f goofys.inokara.debug goofys

... () ...

-----------------------------------------------------
2019/03/03 10:57:19.169593 main.INFO File system has been successfully mounted.
2019/03/03 10:57:19.172542 s3.DEBUG DEBUG: Response s3/ListMultipartUploads Details:
---[ RESPONSE ]--------------------------------------
HTTP/1.1 403 Forbidden
Transfer-Encoding: chunked
Content-Type: application/xml
Date: Sun, 03 Mar 2019 10:57:18 GMT
Server: AmazonS3
X-Amz-Id-2: 7/6sgwcEaNd4gQxmfToLH3F4jxDOb4vUzVQ5tiXmn712zvRoPH/nrWspmiRaDcvJyCTALwpQhPs=
X-Amz-Request-Id: CDC72FCAF1F770F4


-----------------------------------------------------
2019/03/03 10:57:19.172565 s3.DEBUG DEBUG: Validate Response s3/ListMultipartUploads failed, not retrying, error AccessDenied: Access Denied
        status code: 403, request id: CDC72FCAF1F770F4, host id: 7/6sgwcEaNd4gQxmfToLH3F4jxDOb4vUzVQ5tiXmn712zvRoPH/nrWspmiRaDcvJyCTALwpQhPs=

ほう, ListMultipartUploads がコールされていますが, これが AccessDenied になっています. ちなみに, HeadObject というポリシーは存在しておりませんが, ListBucket ポリシーを付与することで, オブジェクトの一覧を取得することが出来るようなので ListBucket が付与されていれば, 透過的に HeadObject をコール可能になっていると想定されます.

ListBucketMultipartUploads ポリシー

先述の通り, ListMultipartUploads をコールした際に AccessDenied となっていたので, ポリシーに ListBucketMultipartUploads を追加してみたいと思います.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:ListBucketMultipartUploads"
            ],
            "Resource": [
                "arn:aws:s3:::goofys.inokara.debug"
            ]
        }
    ]
}

このポリシーの状態で, 改めて goofys を使ってバケットをマウントをしてみると...以下のように出力されました.

$ goofys-latest --debug_s3 -f goofys.inokara.debug goofys

... () ...

-----------------------------------------------------
2019/03/03 11:09:55.387597 main.INFO File system has been successfully mounted.
2019/03/03 11:09:55.410434 s3.DEBUG DEBUG: Response s3/ListMultipartUploads Details:
---[ RESPONSE ]--------------------------------------
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/xml
Date: Sun, 03 Mar 2019 11:09:56 GMT
Server: AmazonS3
X-Amz-Id-2: WcyAjJdK76wsGygYDxNYn6ZYTj3LZqkxQWth+yTKf7kprvZivMw74FQ3X+4ahzw65Q5ZriHPM2Q=
X-Amz-Request-Id: 3E897110C2CC4E5E


-----------------------------------------------------
2019/03/03 11:09:55.410472 s3.DEBUG {
  Bucket: "goofys.inokara.debug",
  IsTruncated: false,
  KeyMarker: "",
  MaxUploads: 1000,
  NextKeyMarker: "",
  NextUploadIdMarker: "",
  UploadIdMarker: ""
}

特にエラー無くマウントされたようです. この状態で ls コマンドを実行してみると以下のように出力されました.

... () ...

-----------------------------------------------------
2019/03/03 11:13:29.457766 s3.DEBUG DEBUG: Response s3/ListObjects Details:
---[ RESPONSE ]--------------------------------------
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/xml
Date: Sun, 03 Mar 2019 11:13:30 GMT
Server: AmazonS3
X-Amz-Bucket-Region: ap-northeast-1
X-Amz-Id-2: +uEEujy8wlHA/dtZ4n/TpE3lZQWGZCH1cVQPj9F8WGSjl1kinRvK3yCvbvDXwc384PYHLVbrrzA=
X-Amz-Request-Id: 7B7D4B43444404D8


-----------------------------------------------------
2019/03/03 11:13:29.457991 s3.DEBUG {
  Contents: [{
      ETag: "\"d41d8cd98f00b204e9800998ecf8427e\"",
      Key: "test.txt",
      LastModified: 2019-03-03 06:43:16 +0000 UTC,
      Size: 0,
      StorageClass: "STANDARD"
    }],
  Delimiter: "/",
  IsTruncated: false,
  Marker: "",
  MaxKeys: 1000,
  Name: "goofys.inokara.debug",
  Prefix: ""
}

ファイルの閲覧と作成

files というキー以下でのみファイルの作成が出来るようにします. 以下のようにポリシーを更新しました.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:ListBucketMultipartUploads"
            ],
            "Resource": [
                "arn:aws:s3:::goofys.inokara.debug"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::goofys.inokara.debug/files/*"
            ]
        }
    ]
}

まずは, files パス以外でファイルを作成してみると, 以下のように出力されました.

... () ...

-----------------------------------------------------
2019/03/03 11:20:23.091167 s3.DEBUG DEBUG: Response s3/PutObject Details:
---[ RESPONSE ]--------------------------------------
HTTP/1.1 403 Forbidden
Transfer-Encoding: chunked
Content-Type: application/xml
Date: Sun, 03 Mar 2019 11:20:22 GMT
Server: AmazonS3
X-Amz-Id-2: pehJsUi+UF2hUwPkQJkwSnvYJuVZTxK9EBQDSvsWumIKsZlKYXOjeEK+5Yajvyv0HlKTVFnmOCo=
X-Amz-Request-Id: 664CA4D73C306D63


-----------------------------------------------------
2019/03/03 11:20:23.091194 s3.DEBUG DEBUG: Validate Response s3/PutObject failed, not retrying, error AccessDenied: Access Denied
        status code: 403, request id: 664CA4D73C306D63, host id: pehJsUi+UF2hUwPkQJkwSnvYJuVZTxK9EBQDSvsWumIKsZlKYXOjeEK+5Yajvyv0HlKTVFnmOCo=

引き続き, files パス以下でファイルを作成してみると, 以下のように出力されました.

-----------------------------------------------------
2019/03/03 11:24:16.005394 s3.DEBUG DEBUG: Response s3/PutObject Details:
---[ RESPONSE ]--------------------------------------
HTTP/1.1 200 OK
Content-Length: 0
Date: Sun, 03 Mar 2019 11:24:16 GMT
Etag: "d41d8cd98f00b204e9800998ecf8427e"
Server: AmazonS3
X-Amz-Id-2: 0XgeKsUv8e/Gb49KJ7mGO7samq3zfkVZgGKrimx82qZ7WzaG6qyNxiXmlEPHunjuRRR0yYi+b+w=
X-Amz-Request-Id: F6CA71FDA3007423


-----------------------------------------------------

HTTP のレスポンスで 200 が返却されているので, 正常にファイルが作成されているようです. 念の為, 確認してみます.

$ pwd
/home/ec2-user/goofys/files
$ ls -l
合計 0
-rw-r--r-- 1 ec2-user ec2-user 0  33 11:20 1.txt
-rw-r--r-- 1 ec2-user ec2-user 0  33 11:24 2.txt

goofys 側では以下のように出力されました.

... () ...

-----------------------------------------------------
2019/03/03 11:25:57.822293 s3.DEBUG DEBUG: Response s3/ListObjects Details:
---[ RESPONSE ]--------------------------------------
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/xml
Date: Sun, 03 Mar 2019 11:25:58 GMT
Server: AmazonS3
X-Amz-Bucket-Region: ap-northeast-1
X-Amz-Id-2: VknicuS9iz1q60hmiQ1OmqceX2Xxj3DzST0jTtbTYajVI0UcK4i16STxBPELd0ACDgd6hDhZc24=
X-Amz-Request-Id: FD2F29B8186C04A2


-----------------------------------------------------
2019/03/03 11:25:57.822503 s3.DEBUG {
  Contents: [{
      ETag: "\"d41d8cd98f00b204e9800998ecf8427e\"",
      Key: "files/",
      LastModified: 2019-03-03 11:19:52 +0000 UTC,
      Size: 0,
      StorageClass: "STANDARD"
    },{
      ETag: "\"d41d8cd98f00b204e9800998ecf8427e\"",
      Key: "files/1.txt",
      LastModified: 2019-03-03 11:20:16 +0000 UTC,
      Size: 0,
      StorageClass: "STANDARD"
    },{
      ETag: "\"d41d8cd98f00b204e9800998ecf8427e\"",
      Key: "files/2.txt",
      LastModified: 2019-03-03 11:24:16 +0000 UTC,
      Size: 0,
      StorageClass: "STANDARD"
    }],
  Delimiter: "/",
  IsTruncated: false,
  Marker: "",
  MaxKeys: 1000,
  Name: "goofys.inokara.debug",
  Prefix: "files/"
}

作成したファイルを /tmp/ 以下にコピーしてみます.

$ pwd
/home/ec2-user/goofys/files
$ cp 1.txt /tmp/
$ ls -l /tmp/1.txt
-rw-r--r-- 1 ec2-user ec2-user 5  33 12:26 /tmp/1.txt

goofys 側では以下のように出力されました.

$ goofys-latest --debug_s3 -f goofys.inokara.debug goofys 2>&1 | grep "s3.DEBUG DEBUG"

... () ...

2019/03/03 12:32:51.268230 s3.DEBUG DEBUG: Request s3/ListObjects Details:
2019/03/03 12:32:51.268500 s3.DEBUG DEBUG: Request s3/HeadObject Details:
2019/03/03 12:32:51.268595 s3.DEBUG DEBUG: Request s3/HeadObject Details:
2019/03/03 12:32:51.289604 s3.DEBUG DEBUG: Response s3/HeadObject Details:
2019/03/03 12:32:51.289755 s3.DEBUG DEBUG: Validate Response s3/HeadObject failed, not retrying, error NotFound: Not Found
2019/03/03 12:32:51.297684 s3.DEBUG DEBUG: Response s3/HeadObject Details:
2019/03/03 12:32:51.298363 s3.DEBUG DEBUG: Response s3/ListObjects Details:
2019/03/03 12:32:51.299038 s3.DEBUG DEBUG: Request s3/GetObject Details:
2019/03/03 12:32:51.308361 s3.DEBUG DEBUG: Response s3/GetObject Details:

ファイルのコピーにあたっては, ListObjectsGetObject がコールされることが判ります.

ということで...

goofys で S3 バケットをマウントする際のステップ

以下のようなステップとなるようです.

  1. S3 バケット内の存在していないオブジェクトに対して, HeadObject をコールしてバケットにアクセス出来ることをチェックする (このあたり)
  2. バケットにアクセス出来た時点でファイルシステムとしてマウントは完了する
  3. その後, ListMultipartUploads が可能であるかチェックする (このあたり)

この 3 つのステップが完了することでファイルシステムで利用可能な状態になると思われます.

最低限必要なポリシー

goofys を利用するにあたって, 最低限必要な IAM Policy は以下のようになると思います.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:ListBucketMultipartUploads"
            ],
            "Resource": [
                "arn:aws:s3:::goofys.inokara.debug"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::goofys.inokara.debug/files/*"
            ]
        }
    ]
}

オブジェクト全体に対してファイルの作成や取得を許可する場合には以下の通り.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:ListBucketMultipartUploads",
                "s3:PutObject",
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::goofys.inokara.debug"
            ]
        }
    ]
}

以上, 長々と失礼いたしました. 素敵なファイルシステムライフをお過ごし下さい.