S3のアクセスコントロールが多すぎて訳が解らないので整理してみる

462件のシェア(そこそこ話題の記事)

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Amazon S3のアクセスコントロールがわけわからない

いくつもあってわけがわかりませんね。
それぞれ何が出来て何ができないのか、どうゆう時にどれを使うのか、組み合わせて使うとどうなるのか、いい機会なので整理してみたいと思います。

ACL

これはおそらくいちばん古くからある機能です。
permissions
ManagementConsoleではPermissionsというところで設定ができます。
ACLの設定はバケット、もしくはオブジェクト単位で設定する事が出来るのが特徴になっています。
APIを見てみても、putBucketACLとputObjectACLとあることから、Bucket単位でのACLとObject単位でのACLがあることが分かります。 別のアカウントにまたがって設定することも出来ます。

BucketPolicy

S3のBucketに対して、PolicyDocumentを使ってアクセスコントロールを設定出来ます。

{
  "Id": "bucketpolicy-${oacl}-${acl}-${EFFECT}",
  "Statement": [
    {
      "Sid": "Stmt${UNIQ}-1",
      "Action": [
        "s3:GetObject"
      ],
      "Effect": "${EFFECT}",
      "Resource": "arn:aws:s3:::${BUCKET}/*",
      "Principal": {
        "AWS": [
          "${TARGET_PRINCIPAL}"
        ]
      }
    },
    {
      "Sid": "Stmt${UNIQ}-2",
      "Action": [
        "s3:ListBucket"
      ],
      "Effect": "${EFFECT}",
      "Resource": "arn:aws:s3:::${BUCKET}",
      "Principal": {
        "AWS": [
          "${TARGET_PRINCIPAL}"
        ]
      }
    }
  ]
} 

こんな感じで、Action, Effect, Principal, Resourceと、これらを組み合わせて柔軟なアクセスコントロールが実現出来ます。
この一個のファイルだけを公開したいとかそういった用途に使うのには向いていないように思います。ObjectACLを使いましょう。
個人的な感覚ですが、bucketACLはBucketPolicyに置き換えたほうが良いなと思っています。

ACLとBucketPolicyどちらが強いのか

気になってしまったので、ACLとBucketPolicyどちらが強いのかを検証したいと思います。
検証の方法は簡単です。 ACLは許可するか、何もしないかの2値です。BucketPolicyは許可する、拒絶する、何もしないの3値です。
ACLはBucketとObjectがありますので、この3つの設定を全通りの組み合わせでやってみます。

BucketACL ObjcetACL BucketPolicy 結果
許可 許可 拒否 失敗
許可 未設定 拒否 失敗
許可 許可 許可 成功
許可 未設定 許可 成功
許可 許可 未設定 成功
許可 未設定 未設定 失敗
未設定 許可 拒否 失敗
未設定 未設定 拒否 失敗
未設定 許可 許可 成功
未設定 未設定 許可 成功
未設定 許可 未設定 成功
未設定 未設定 未設定 失敗

この結果からもわかるように、[Bucket|Object]ACLよりもBucketPolicyの方が強くなります。 BucketPolicyで設定されていない場合は、ObjcetACL , BucketACLによって制御されています。
これについては、公式にドキュメントでここここにも書いて有ります。
さらにBucketPolicyではIPアドレスで制御やRefererで制御も出来ます。
直リン禁止とか出来たりします。

IAM(IAMRole)

IAMではPolicyDocumentを使用してアクセスをコントロールします。
IAMとBucketPolicyの違いはユーザ(クライアント)側に紐付いて権限が与えられるということです。
また、現在はPolicyに変数を利用する事が出来るため、ユーザ名に応じたアクセスのコントロールが可能になっています。
例えば、こんな感じです。
policyDocumentに

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Effect": "Allow",
            "Resource": ["arn:aws:s3:::BUCKET/${aws:username}/*"]
        }
    ]
}

と記述されていたら、
IAMuser名: akeri の時には s3://BUCKET/akeri/ で始まるオブジェクトにアクセス出来る事になります。
BucketPolicy同様にconditionによって、条件を指定する事も出来ます。
IPアドレスで制御したり、 @see AWS Identity and Access Management

で、IAMとBucketPolicyはどっちが強いの?

先ほどACLとBucketPolicyを試してみたら、BucketPolicyが強かったのですが、今度はIAMとBucketPolicyどっちが強いのかを試してみたいと思います。
さっきと同様に総当りで調べてみます。 今回ACLは何も設定していない状態です。

BucketPolicy IAM 結果
拒否 拒否 失敗
拒否 許可 失敗
許可 拒否 失敗
許可 許可 成功
未設定 拒否 失敗
未設定 許可 成功

結果はこんな感じです。
とりあえず、IAMで許可されていないと確実に失敗します。
当たり前といえば当たり前な気がします。
しかし、IAMで許可されていたとしてもBucketPolicyで拒否されているとアクセス出来ませんでした。 とりあえず、ここから わかることは拒否は絶対ということです。
IAMで拒否されていてもBucketPolicyで拒否されていても、失敗します。
成功するのは、IAMで許可されているかつBucketPolicyで拒否されていない(Allowもしくは未設定)の場合でした。
さっきのACLとBucketPolicyではわかりませんでしたが、BucketPolicyが優先されているわけではなくて、Denyが優先されているようです。

まとめ

上記の結果から、S3のACLに関するルールがなんとなく見えてきました。
ACLの基本として、どこかしらでDenyとなっていたら、ほかでAllowだろうとDenyになってしまいます。
Denyに対して、Allowで上書きは出来ません。
それとは逆にBucketPolicyやACLでAllowを設定していても、IAMでDenyであった場合は当然アクセス出来ません。
ここから考えるベストプラクティスというのはパットは出てきませんが、ACLはあるオブジェクト一つだけを公開したい時や、publicにする時に使う以外では使わないのかもしれません。
まとめるとタイトルで言ったものの、あまりまとめられてる感じがしないですね。ごめんなさい。

バケットとして不変的なアクセスコントロールはBucketPolicyで定義し、IAMはユーザ毎に変更になるようなものを設定するのが良いのではと思います。この時にBucketPolicyでDenyを付けてしまうと、IAMでいくら設定してもDenyなので、今の段階で私の考えられるベターな方法は、まずは必要なところだけBucketPolicyで公開します。その公開している範囲ないで更に限定的に拒否したいところにはDenyで上書いてあげる方法が良さそうです。
さらにIAMでは変数が使えるので、グループのPolicyで一括設定もできるので、複数のユーザがいて、あるルールに基づいてアクセスをコントロールしたい場合にはIAMを使いたいですね。

用途をまとめるとこんな感じかなと思います。◯はいいじゃんで、☓は出来ない。△はやってやれないことは無いけど、めんどくさすぎてやりたくない位なゆるい判断基準で付けてます。

ユースケース [Bucket|Object]ACL BucketPolicy IAM
一個のObjectを公開したい
バケット全体を公開したい
特定のユーザにだけ公開したい
IPアドレス制限をかけたい
Bucketの中のPrefixを指定して公開したい
prefixがユーザ毎に可変

おまけ

今回調査に使ったシェルスクリプト載せておきます。
権限毎にバケット何個も作って、オブジェクトも作って、アクセスしてみて、終わったら削除してという感じのものです。

#!/bin/bash

UNIQ=$(date +'%Y%m%d%I%M%S')
alias aws='aws --region ap-northeast-1'
BUCKET_PREFIX=akeri-acl-test-$UNIQ-
TARGET_PRINCIPAL="arn:aws:iam::123456789012:user/s3acltest"
TARGET_MAIL="example@example.com"
# create Bucket Policy Document
UPLOADFILE=akeridayo.txt
IAM_USERNAME=s3acltest
echo "akeridayo" > $UPLOADFILE
echo "created upload file"
echo "<table>" > result.html


# bucket create
# 0 deny
# 1 allow
# 2 none
for acl in `seq 2 2`
do
  for policy in `seq 0 2`
  do
    BUCKET=${BUCKET_PREFIX}${acl}${policy}
    # create bucket
    aws s3api create-bucket --bucket $BUCKET
    if [ $acl -eq 1 ];
    then
      aws s3api put-bucket-acl --bucket $BUCKET --grant-read emailaddress="$TARGET_MAIL"
      echo "putted bucket acl"
    fi

    echo "created bucket $BUCKET"   
    # set Bucket Policy
    if [ $policy -ne 2 ];
    then
    EFFECT="Allow"
      if [ $policy -eq 0 ];
      then
        EFFECT="Deny"
      fi
#========================================= policy.json
    cat << _EOT_ > bucketPolicy-${UNIQ}-${acl}-${EFFECT}.json
{
  "Id": "bucketpolicy-${oacl}-${acl}-${EFFECT}",  "Statement": [ {
      "Sid": "Stmt${UNIQ}-1",
      "Action": ["s3:GetObject"],
      "Effect": "${EFFECT}",
      "Resource": "arn:aws:s3:::${BUCKET}/*",
      "Principal": {"AWS": ["${TARGET_PRINCIPAL}" ]}},
    {
      "Sid": "Stmt${UNIQ}-2",
      "Action": ["s3:ListBucket"],
      "Effect": "${EFFECT}",
      "Resource": "arn:aws:s3:::${BUCKET}",
      "Principal": {"AWS": ["${TARGET_PRINCIPAL}"]}
    }
  ]
} 
_EOT_
#==========================================================
      # set Bucket ACL Allow
      aws s3api put-bucket-policy --bucket $BUCKET --policy file://$(pwd)/bucketPolicy-${UNIQ}-${acl}-${EFFECT}.json
      echo "putted bucket policy"
      rm bucketPolicy-${UNIQ}-${acl}-${EFFECT}.json
    fi
    # set Bucket Policy END

#___Object ACL Loop Start
    for oacl in `seq 2 2`
    do
      key=${acl}${oacl}
      aws s3api put-object --bucket $BUCKET --key $key --body $UPLOADFILE 
      echo "file uploaded "
      if [ $oacl -eq 1 ];
      then
        aws s3api put-object-acl --bucket $BUCKET --key $key --grant-read emailaddress="$TARGET_MAIL",emailaddress="magcot.com@gmail.com"
        echo "putted object acl "
      fi

      aws s3api get-object --profile iamuser  --bucket $BUCKET --key $key /dev/null
      if [ $? -eq 0 ];
      then
        resultOther="成功"
      else
        resultOther="失敗"
      fi

      echo "getted Object  other account $resultOther"

      for iam in `seq 0 1`
      do
        IAMEFFECT="Allow"
        if [ $iam -eq 0 ];
        then
          IAMEFFECT="Deny"
        fi
        POLICYNAME=POLICY-${UNIQ}-${acl}-${oacl}-${policy}-${iam}
        POLICYFILE=file://$(pwd)/$POLICYNAME.json
        cat << _EOT_ > $POLICYNAME.json
{"Statement": [
    {
      "Sid": "Stmt${UNIQ}2",
      "Action": ["s3:GetObject"],
      "Effect": "${IAMEFFECT}",
      "Resource": "arn:aws:s3:::${BUCKET}/*"
}]}
_EOT_
        echo "created policy document"
        aws iam put-user-policy --policy-name $POLICYNAME --user-name $IAM_USERNAME --policy-document $POLICYFILE
        if [ $? -eq 0 ];
        then
          echo "put user policy OK"
        fi
        echo "putted user policy"
        rm $POLICYNAME.json
        sleep 5
        aws s3api get-object --profile iamuser --bucket $BUCKET --key $key out
        if [ $? -eq 0 ];
        then
          resultIAM="成功"
        else
          resultIAM="失敗"
        fi
        echo "get object IAM User $resultIAM"
        aws iam delete-user-policy --policy-name $POLICYNAME --user-name $IAM_USERNAME
        #output html 
      cat << _EOT_ >> result.html
  <tr>
    <td>${acl}</td>
    <td>${oacl}</td>
    <td>${policy}</td>
    <td>${resultOther}</td>
    <td>${iam}</td>
    <td>${resultIAM}</td>
  </tr>
_EOT_
      done
      aws s3api delete-object --bucket $BUCKET --key $key
    done
    aws s3api delete-bucket --bucket $BUCKET
  done
done

echo "</table>" >> result.html

cat result.html |perl -pe "s/>1</>許可</g" |perl -pe "s/>2</>未設定</g" |perl -pe "s/>0</>拒否</g" > index.html
rm result.html