«前の日記(2014-07-28 (Mon)) 最新 次の日記(2014-09-04 (Thu))» 編集

雑記帳


2014-07-31 (Thu) [長年日記]

[AWS] Amazon S3に別アカウントから書き込みを許可する場合の設定

あるバケットに、別のアカウントから読み書きできるようにバケットポリシーを定義したい。普通に考えると、下記のような感じになるだろう。

{
  "Version": "2012-10-17",
  "Id": "Policy1406782030997",
  "Statement": [
    {
      "Sid": "Stmt1406782027567",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:ListBucket",
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:s3:::s3-acl-test",
        "arn:aws:s3:::s3-acl-test/*"
      ],
      "Principal": {
        "AWS": [
          "024665792058"
        ]
      }
    }
  ]
}

このように設定されたバケットに、許可した別アカウントから書き込みをする。

% aws --profile s3test s3api put-object --bucket s3-acl-test --key file --body file
{
    "ETag": "\"8a9c538c7f848d97d9d45736c4f709f3\""
}

書き込んだオブジェクトを取得してみる。中身はどうでもいいので、/dev/nullに捨てる。

% aws --profile s3test s3api get-object --bucket s3-acl-test --key file /dev/null
{
    "AcceptRanges": "bytes",
    "ContentType": "binary/octet-stream",
    "LastModified": "Thu, 31 Jul 2014 04:56:08 GMT",
    "ContentLength": "14",
    "ETag": "\"8a9c538c7f848d97d9d45736c4f709f3\""
}

さて、これをバケットを所有しているアカウントで取得してみよう。

% aws --profile default s3api get-object --bucket s3-acl-test --key file /dev/null

A client error (AccessDenied) occurred when calling the GetObject operation: Access Denied

アクセス出来ない。これは、ACLが下記のように、書き込んだユーザのみ権限が付与されているからである。

% aws --profile s3test s3api get-object-acl --bucket s3-acl-test --key file
{
    "Owner": {
        "DisplayName": "s3test",
        "ID": "4682c00752c7f406f24e7dda04700a324b2179670ee5ab2a610352226a47dc73"
    },
    "Grants": [
        {
            "Grantee": {
                "DisplayName": "s3test",
                "ID": "4682c00752c7f406f24e7dda04700a324b2179670ee5ab2a610352226a47dc73"
            },
            "Permission": "FULL_CONTROL"
        }
    ]
}

バケットの所有者は、このオブジェクトを誰でも読み取り可能にしたい。バケットポリシーを下記のものに変更する。

{
  "Version":"2012-10-17",
  "Id": "Policy1406782030997",
  "Statement": [
    {
      "Sid": "Stmt1406782027567",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:s3:::s3-acl-test",
        "arn:aws:s3:::s3-acl-test/*"
      ],
      "Principal": {
        "AWS": [
          "024665792058"
        ]
      }
    },
    {
      "Sid": "Stmt1406782027568",
      "Action": [
        "s3:GetObject"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:s3:::s3-acl-test",
        "arn:aws:s3:::s3-acl-test/*"
      ],
      "Principal": {
        "AWS": [
          "*"
        ]
    }
  ]
}

自分でオブジェクトを作成して、テストしてみる。

% aws --profile default s3api put-object --bucket s3-acl-test --key file2 --body file
{
    "ETag": "\"8a9c538c7f848d97d9d45736c4f709f3\""
}

curlで取得してみる。

% curl http://s3-acl-test.s3.amazonaws.com/file2
Hello, World.
% curl --head http://s3-acl-test.s3.amazonaws.com/file2
HTTP/1.1 200 OK
x-amz-id-2: 8Bb1L6uwe497S7CEHzFTtx4qI2rye2c+ilZoALtDqJ9xzk+LXVvZmcbk7/HB+yy/
x-amz-request-id: 1696D56CCBF01BE5
Date: Thu, 31 Jul 2014 05:08:17 GMT
Last-Modified: Thu, 31 Jul 2014 05:06:04 GMT
ETag: "8a9c538c7f848d97d9d45736c4f709f3"
Accept-Ranges: bytes
Content-Type: binary/octet-stream
Content-Length: 14
Server: AmazonS3

問題なく取得できる。

他のアカウントが作成したオブジェクトも同様に取得してみる。

% curl http://s3-acl-test.s3.amazonaws.com/file
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>8A2F7FE7C668B21C</RequestId><HostId>ZMgwnn6fAvDCB3bP8pLIXu7Ar3zQ0jeMfTXP6Wn1fW9y9dKPsgkBaMc8l+esYiQ+</HostId></Error>
% curl --head http://s3-acl-test.s3.amazonaws.com/file
HTTP/1.1 403 Forbidden
x-amz-request-id: 7FF68FDDDC40A0A7
x-amz-id-2: 4RN6yGv2TUrEXSwvaTGsaOANf5rEW0j82eOyh4hPdOGuMbRLtZ4+Kuw45BtSzZOckPMVIGUbahBY+8MXv0YM1A==
Content-Type: application/xml
Transfer-Encoding: chunked
Date: Thu, 31 Jul 2014 05:12:23 GMT
Server: AmazonS3

エラーになってしまった。これはどういう事だろうか。バケットポリシーが適用されていないように見える。

実は、バケットポリシーは、バケットの所有者とオブジェクトの所有者が同じでないと、ポリシーが適用されないという仕様になっている*1

ということで、バケットの所有者で改めてオブジェクトを作成しなおしてみる。オブジェクトに権限がなくてもオブジェクトを作成・削除する権限があるので、普通にputすればよい。

% aws --profile default s3api put-object --bucket s3-acl-test --key file --body file
{
    "ETag": "\"8a9c538c7f848d97d9d45736c4f709f3\""
}

所有者が変更されている。

% aws --profile default s3api get-object-acl --bucket s3-acl-test --key file
{
    "Owner": {
        "DisplayName": "default",
        "ID": "2b70304c677e207198b076a1436d3f0fdf8c4cd34a1d6a77c95924268e52dc27"
    },
    "Grants": [
        {
            "Grantee": {
                "DisplayName": "default",
                "ID": "2b70304c677e207198b076a1436d3f0fdf8c4cd34a1d6a77c95924268e52dc27"
            },
            "Permission": "FULL_CONTROL"
        }
    ]
}

この状態であれば、バケットポリシーが適用されるので、匿名ユーザでもGETできるようになる。

% curl http://s3-acl-test.s3.amazonaws.com/file
Hello, World.
% curl --head http://s3-acl-test.s3.amazonaws.com/file
HTTP/1.1 200 OK
x-amz-id-2: /Hy86Fcmqf6t5ICqdycw4GvqI9DX12cPtTfE6OOXYJZ0JRMZPK6WTc2vOU/+Ot88
x-amz-request-id: 3388276F03F90125
Date: Thu, 31 Jul 2014 11:07:17 GMT
Last-Modified: Thu, 31 Jul 2014 10:56:18 GMT
ETag: "8a9c538c7f848d97d9d45736c4f709f3"
Accept-Ranges: bytes
Content-Type: binary/octet-stream
Content-Length: 14
Server: AmazonS3

問題が起こらないようにするには

ということで、他のアカウントのユーザに自分のバケットを開放する際には、気をつけなければいけないことがわかった。では、誰が書き込んだとしても、匿名ユーザからのアクセスを許可したい場合には、どうすればいいのか。

下記のようなバケットポリシーを設定すればよい。

{
  "Version": "2012-10-17",
  "Id": "Policy1406782030997",
  "Statement": [
    {
      "Sid": "Stmt1406782027567",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:s3:::s3-acl-test",
        "arn:aws:s3:::s3-acl-test/*"
      ],
      "Principal": {
        "AWS": [
          "024665792058"
        ]
      }
    },
    {
      "Sid": "Stmt1406782027569",
      "Effect": "Deny",
      "Principal": {
        "AWS": [
          "024665792058"
        ]
      },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::s3-acl-test/*",
      "Condition": {
        "StringNotEquals": {
          "s3:x-amz-grant-read": [
            "uri=http://acs.amazonaws.com/groups/global/AllUsers"
          ]
        }
      }
    },
    {
      "Sid": "Stmt1406782027568",
      "Action": [
        "s3:GetObject"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:s3:::s3-acl-test",
        "arn:aws:s3:::s3-acl-test/*"
      ],
      "Principal": {
        "AWS": [
          "*"
        ]
      }
    }
  ]
}

追加したポリシー(真ん中のブロック)は、PutObjectを実行する際には、AllUsersに対して読み込みを許可する権限が付与されていなければ拒否する、というものである*2。つまり、書き込むオブジェクトの権限を強制するもの、ということになる。

それでは、このバケットポリシーが設定されている状態で、先ほどと同じように、オブジェクトを作成してみる。

% aws --profile s3test s3api put-object --bucket s3-acl-test --key file3

A client error (AccessDenied) occurred when calling the PutObject operation: Access Denied

エラーとなった。すべてのユーザーがREADできるようにACLを付与するように変更してみる。

% aws --profile s3test s3api put-object --bucket s3-acl-test --key file3 --body file --grant-read "uri=http://acs.amazonaws.com/groups/global/AllUsers"
{
    "ETag": "\"8a9c538c7f848d97d9d45736c4f709f3\""
}

今度は書き込めたので、ACLを確認する。

% aws --profile s3test s3api get-object-acl --bucket s3-acl-test --key file3
{
    "Owner": {
        "DisplayName": "s3test",
        "ID": "4682c00752c7f406f24e7dda04700a324b2179670ee5ab2a610352226a47dc73"
    },
    "Grants": [
        {
            "Grantee": {
                "URI": "http://acs.amazonaws.com/groups/global/AllUsers"
            },
            "Permission": "READ"
        }
    ]
}

curlでGETしてみる。

% curl http://s3-acl-test.s3.amazonaws.com/file3
Hello, World.
% curl --head http://s3-acl-test.s3.amazonaws.com/file3
HTTP/1.1 200 OK
x-amz-id-2: sZY8vgy+haVfV8Rji1w8bePa8kjFkyxms6mMZvu18vHgIStEB4RUcS7Y+stiHrra
x-amz-request-id: C58EB280DFE46C5D
Date: Thu, 31 Jul 2014 11:45:07 GMT
Last-Modified: Thu, 31 Jul 2014 11:44:39 GMT
ETag: "8a9c538c7f848d97d9d45736c4f709f3"
Accept-Ranges: bytes
Content-Type: binary/octet-stream
Content-Length: 14
Server: AmazonS3

今度は取得できるようになった。

今回はACLにて権限を設定したが、実際はバケットポリシーで制限をしたい場合の方が多いのではないかと思う。その場合は、バケットとオブジェクトの所有者をあわせなければならない。どうやら、オブジェクトの所有者を変更するには、そのユーザでオブジェクトを作成するしか方法がないようである。従って、次のようにすれば良い。

  • バケットの所有者にフルコントロールを与える権限を付与しないとPUTできないようにする(前述のバケットポリシーの応用)
  • バケットの所有者が、書き込まれたオブジェクトを同じ場所にコピーする(コピー元とコピー先を同じにする)。

こうすることで、バケットの所有者とオブジェクトの所有者を同じにする事ができる。書き込まれたオブジェクト毎に実行しなければならないので、書き込まれたことの通知を受けて処理するようにする必要があるなど、実現するにはハードルが高いように思える。そもそも同じアカウントで書き込むのが手っ取り早いだろう。

*1 これを知らなかったせいで結構ハマったのであった…

*2 AWSのドキュメントを参考にした。http://docs.aws.amazon.com/AmazonS3/latest/dev/AccessPolicyLanguage_UseCases_s3_a.html