ちょっと話題の記事

S3バケットポリシーの具体例で学ぶAWSのPolicyドキュメント

AWSのアクセスを管理するPolicyドキュメントは柔軟に可否を設定できてとてもすごい!でも、柔軟がゆえに難しい!評価条件もよくわからない! 具体例があると理解の助けになると考えているので、自分の考えを整理するために具体例を作りました。具体例を見ながらPolicyドキュメントについて学んでいきましょう。
2019.08.19

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

AWSのアクセスを管理するPolicyドキュメントは柔軟に可否を設定できてとてもすごい! でも、柔軟がゆえに難しい!評価条件もよくわからない!

具体例があると理解の助けになると考えているので、自分の考えを整理するために具体例を作りました。 具体例を見ながらPolicyドキュメントについて学んでいきましょう。

Policyドキュメントとは?

AWSでアクセスを管理するために、基本的にはIAMポリシーとIAMロールを作成し、それをIAMユーザーやAWSリソースにアタッチする必要があります。

そのときに、IAMポリシーでアクセスする許可と拒否の条件をJSON形式で記述したものがPolicyドキュメントです。

Policyドキュメントの例

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        }
    ]
}

Policyドキュメントの詳細な仕様は、公式のリファレンスをご覧ください。

このPolicyドキュメントは、IAMポリシーに限らず、外部からのアクセスを管理するサービスで使われています。 たとえば、S3のバケットポリシーです。バケットポリシーを使うことで、S3はバケットやオブジェクトごとにアクセス管理できます。

前提知識

まず大前提として、AWSへのアクセスはデフォルトすべて拒否です。

細かいことをいうとさまざまなポリシーがあって、その評価順とかいう話もあるのですが、 今回は話を簡単にするため、バケットポリシー(リソースベースポリシー)の話だけに絞ります。

そのあたりの詳細を知りたい方は、AWSの公式ドキュメントを御覧ください。

何も許可していないIAMユーザーを新しく作って作業することで、 バケットポリシーで許可されている時のみ、アクセス可能な状況を作り出します。

下準備

S3バケットポリシーの具体例を出すために、S3バケットとIAMユーザーを作ります。

S3バケットの作成

権限のあるユーザーでS3バケットを作成します。そして、ファイルを1つ( hello_world.html )アップロードしておきます。

権限のあるユーザーでAWS CLIコマンドを実行すると、S3バケットにファイルが存在していることを確認できます。

$ export S3_BUCKET_NAME=<<BUCKET_NAME>>
$ aws s3 ls s3://${S3_BUCKET_NAME}
2019-08-16 15:36:06         13 hello_world.html

IAMユーザーの作成

次に何の権限もないIAMユーザー(ユーザー名: unauthorized-user )を作成します。 IAMポリシーとしては何の権限もありませんが、バケットポリシーで許可された操作はできます。

アクセスキーを作成して、CLIで操作できるようにしておきます。これで準備は完了です。

バケットポリシーがない場合の動作を確認してみる

まずはバケットポリシーがない状況で、CLIコマンドで aws s3 ls してみます。 IAMユーザーとしても許可されていないし、バケットポリシーとしても許可されていないので、当然のごとくアクセスは拒否されます。

$ export S3_BUCKET_NAME=<<BUCKET_NAME>>
$ aws s3 ls s3://${S3_BUCKET_NAME}
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied

Allowポリシーを設定してみる

S3バケットにアクセスできるように、バケットポリシーを設定します。 接続元のIPに応じて ListBucket を許可する設定を入れてみます。

※便宜上、S3バケット名を ${S3_BUCKET_NAME} に、接続元のグローバルIPを ${SOURCE_IP} に変更しています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::${S3_BUCKET_NAME}",
            "Condition": {
                "StringEquals": {
                    "aws:SourceIp": "${SOURCE_IP}"
                }
            }
        }
    ]
}

このバケットポリシーを設定することで、接続元のグローバルIPさえ一致していれば、このバケットに対して ListBucket の操作ができます。 aws s3 ls することで、アクセスが許可されていることがわかります。

$ export S3_BUCKET_NAME=<<BUCKET_NAME>>
$ aws s3 ls s3://${S3_BUCKET_NAME}
2019-08-16 15:36:06         13 hello_world.html

SourceIpに複数のIPアドレスを設定してみる

次に複数IPを条件に設定してみます。

※便宜上、S3バケット名を ${S3_BUCKET_NAME} に、接続元のグローバルIPを ${SOURCE_IP} に変更しています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::${S3_BUCKET_NAME}",
            "Condition": {
                "StringEquals": {
                    "aws:SourceIp": [
                        "${SOURCE_IP}",
                        "192.0.2.2"
                    ]
                }
            }
        }
    ]
}

このように単一のIPとの一致だけでなく、複数のIPと一致するかどうかの条件を設定できます。

ちなみに、ここで設定した複数のIPはAND条件で評価されるでしょうか?OR条件で評価されるでしょうか? 直感的に考えると、接続元のIPは1つで複数のIPと一致することはありえません。AND条件とはならないでしょう。

ここは直感どおりOR条件で評価されます。公式ドキュメントにも記載されています。

よって、このバケットポリシーを設定した場合、アクセスは許可されます。

$ export S3_BUCKET_NAME=<<BUCKET_NAME>>
$ aws s3 ls s3://${S3_BUCKET_NAME}
2019-08-16 15:36:06         13 hello_world.html

StringEqualsに複数条件を設定してみる

次にStringEqualsに複数条件を設定してみます。

今回は、aws:username を使用して、アクセスするユーザー名による制限を追加します。

 

Policyドキュメントで取得できるキー(例:aws:SourceIp)については、公式ドキュメントを御覧ください。

AWS グローバル条件コンテキストキー - AWS Identity and Access Management

※便宜上、S3バケット名を ${S3_BUCKET_NAME} に、接続元のグローバルIPを ${SOURCE_IP} に変更しています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::${S3_BUCKET_NAME}",
            "Condition": {
                "StringEquals": {
                    "aws:SourceIp": "${SOURCE_IP}",
                    "aws:username": "hogefuga"
                }
            }
        }
    ]
}

今回の場合、StringEqualsで複数設定した条件は、AND条件で評価されるでしょうか?OR条件で評価されるでしょうか?

StringEquals(条件演算子)に条件を複数設定した場合は、AND条件で評価されます。 このあたりについても、さきほどの公式ドキュメントに記載されています。

そのため username が一致していない場合、許可の条件を満たさず、アクセスが拒否されます。

$ export S3_BUCKET_NAME=<<BUCKET_NAME>>
$ aws s3 ls s3://${S3_BUCKET_NAME}
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied

両方の条件を満たすようにバケットポリシーを設定すれば、アクセスが許可されます。

※便宜上、S3バケット名を ${S3_BUCKET_NAME} に、接続元のグローバルIPを ${SOURCE_IP} に変更しています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::${S3_BUCKET_NAME}",
            "Condition": {
                "StringEquals": {
                    "aws:SourceIp": "${SOURCE_IP}",
                    "aws:username": "unauthorized-user"
                }
            }
        }
    ]
}
$ export S3_BUCKET_NAME=<<BUCKET_NAME>>
$ aws s3 ls s3://${S3_BUCKET_NAME}
2019-08-16 15:36:06         13 hello_world.html

それでは、逆にOR条件で接続を許可したい場合どうすればよいのでしょうか?

たとえば、今回のように「接続元のIPがある値、またはユーザー名が unauthorized-user である場合、接続を許可したい。」と、いった状況です。

これは複数のAllowポリシーを設定することで実現できます。Allowポリシーはそれぞれ独立して評価されるので、OR条件で評価されます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::${S3_BUCKET_NAME}",
            "Condition": {
                "StringEquals": {
                    "aws:SourceIp": "${SOURCE_IP}"
                }
            }
        },
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::${S3_BUCKET_NAME}",
            "Condition": {
                "StringEquals": {
                    "aws:username": "unauthorized-user"
                }
            }
        }
    ]
}
$ export S3_BUCKET_NAME=<<BUCKET_NAME>>
$ aws s3 ls s3://${S3_BUCKET_NAME}
2019-08-16 15:36:06         13 hello_world.html

条件演算子を複数にしてみる

Conditionの条件演算子に、StringEqualsだけを使ってきましたが、条件演算子が複数になった場合はどう評価されるのでしょうか。

今回は、StringNotEqualsを追加してみます。

Policyドキュメントで使用できる条件演算子(例:StringEquals)については、公式ドキュメントを御覧ください。

IAM JSON ポリシーエレメント: 条件演算子 - AWS Identity and Access Management

※便宜上、S3バケット名を ${S3_BUCKET_NAME} に、接続元のグローバルIPを ${SOURCE_IP} に変更しています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::${S3_BUCKET_NAME}",
            "Condition": {
                "StringEquals": {
                    "aws:SourceIp": "${SOURCE_IP}"
                },
                "StringNotEquals": {
                    "aws:username": "unauthorized-user"
                }
            }
        }
    ]
}

この場合も、AND条件で評価されます。 username が一致しており、NotEqualsを満たしていないため、この設定ではアクセスが拒否されます。

$ export S3_BUCKET_NAME=<<BUCKET_NAME>>
$ aws s3 ls s3://${S3_BUCKET_NAME}
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied

このあたりについても、さきほどの公式ドキュメントに記載されています。

Denyポリシーを設定してみる

さきほど、複数のAllowポリシーを設定した場合はOR条件で評価されると説明しました。 それでは、Denyポリシーを設定するとどうでしょうか?

※便宜上、S3バケット名を ${S3_BUCKET_NAME} に、接続元のグローバルIPを ${SOURCE_IP} に変更しています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::${S3_BUCKET_NAME}",
            "Condition": {
                "StringEquals": {
                    "aws:SourceIp": "${SOURCE_IP}"
                }
            }
        },
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::${S3_BUCKET_NAME}",
            "Condition": {
                "StringEquals": {
                    "aws:SourceIp": "${SOURCE_IP}"
                }
            }
        }
    ]
}

DenyとAllowに同じ条件を設定していますが、アクセスが拒否されます。 明示的なDenyは、明示的なAllowより優先されて評価されます。

$ export S3_BUCKET_NAME=<<BUCKET_NAME>>
$ aws s3 ls s3://${S3_BUCKET_NAME}
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied

このあたりについては公式ドキュメントのここに記載されています。

DenyポリシーのStringEqualsに複数条件を設定してみる

DenyポリシーにもStringEqualsで複数条件を設定してみます。 アクセスするユーザー名によって拒否する設定を追加します。

※便宜上、S3バケット名を ${S3_BUCKET_NAME} に、接続元のグローバルIPを ${SOURCE_IP} に変更しています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::${S3_BUCKET_NAME}",
            "Condition": {
                "StringEquals": {
                    "aws:SourceIp": "${SOURCE_IP}"
                }
            }
        },
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::${S3_BUCKET_NAME}",
            "Condition": {
                "StringEquals": {
                    "aws:SourceIp": "${SOURCE_IP}",
                    "aws:username": "hogefuga"
                }
            }
        }
    ]
}

StringEquals(条件演算子)で複数設定した条件は、Allowポリシーと同様にAND条件で評価されます。

この設定の場合、ユーザー名が一致していないため、Denyの条件を満たしていません。 そのため、Denyの条件は満たさず、Allowの条件を満たしているので、アクセスが許可されています。

$ export S3_BUCKET_NAME=<<BUCKET_NAME>>
$ aws s3 ls s3://${S3_BUCKET_NAME}
2019-08-16 15:36:06         13 hello_world.html

まとめ

Policyドキュメントの評価条件をまとめると、こんな感じになります。

正直、この図が書きたいためだけにこのブログを書きました。 Policyドキュメントを学んで適切にアクセス制限しましょう。