[S3] 特定のパスだけ制限したい!ポリシーの注意点について

2024.06.21

はじめに

こんにちは、さすけです!今回は、S3 バケットのアクセス権限のお話です。
特定のパス(バケット内の特定のフォルダ)に対しての権限を IAM ポリシーで制御する際に s3:ListBucket と s3:GetObject では、ポリシーの書き方に少し違いがあったので、書き留めておきたいと思います!

やりたいこと

このブログ内では、あるバケット内に存在する特定のフォルダの参照および取得を制限することを目標とします。それぞれ以下の通りに作成しました。
バケット名:list-deny
フォルダ名:testdata
list-deny 配下の testdata フォルダにあるオブジェクトは参照、取得ができないように制限し、それ以外のバケットに対しては参照、取得ともに許可するようにポリシーを設定します。

間違っていたポリシーについて

最初に私が考えたポリシーは以下の通りです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllAllow01",
            "Action": [
                "s3:ListBucket",
                "s3:GetObject",
                "s3:ListAllMyBuckets"
            ],
            "Effect": "Allow",
            "Resource": "*"
        },
        {
            "Sid": "ListGetDeny01",
            "Action": [
                "s3:ListBucket",
                "s3:GetObject"
            ],
            "Effect": "Deny",
            "Resource": "arn:aws:s3:::list-deny",
            "Condition": {
                "StringLike": {
                    "s3:prefix": "testdata/*"
                }
            }
        }
    ]
}

AllAllow01 で自身の全バケットに対して参照、取得する権限を付与し ListGetDeny01 で list-deny 配下に存在する testdata フォルダへの参照、取得を拒否しようとして設定しました。

検証

さて、上記のポリシーでの挙動を確認していきましょう。
まずは念の為 list-deny バケット以外のバケットを参照、オブジェクトの取得ができるのか確認してみます。今回は検証用にtest-allow-01というバケットを作成してあるのでそちらで確認してみます。
すると下記画像のように、問題なくバケット内を参照することができました。

ダウンロードも可能となっており、ボタンを押すと下記画像のように問題なくダウンロードできていることがわかります。

次に list-deny バケットを確認してみましょう。
こちらも以下の画像の通り、バケット内を参照できるようになっています。

ただし、testdata フォルダに入ると以下の画像のように「権限が足りないぞ!」と怒られました。

参照ができないことでダウンロードボタンも押せないようになっていて、一見すると問題なく参照、取得ともに拒否されているように見えます。
しかし、このポリシーだと抜け道が存在してしまいます。それは CLI からはダウンロードができてしまうことです。

CLI からダウンロード

それでは実際に CLI からダウンロードしてみましょう。
まず、認証情報を登録します。

aws configure

オブジェクトを保存するフォルダを作成して、ディレクトリを移動しておきます。今回はtestというフォルダを作成しておきました。
その後、下記コマンドを実行してダウンロードを試してみます。

aws s3 cp s3://list-deny/testdata/test01.jpg ./

すると以下のように結果が返ってきました。

download: s3://list-deny/testdata/test01.jpg to ./test01.jpg

そうです、ダウンロードできちゃったんですよ。。ダウンロードできたので当然ですが、test フォルダを確認すると該当の画像が保存されていました。
念の為、以下のコマンドを実行してオブジェクトの一覧表示ができないようになっているのか CLI からも確認してみます。

aws s3 ls s3://list-deny/testdata/

すると

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied

というエラーが返ってきました。こちらはしっかりと拒否ポリシーが反映されていることが確認できましたね。

原因

さて、JSON で記述されたポリシーを見る限りだと s3:GetObject も Deny しているのに、どうしてこのようなことが起きるのでしょうか。。
原因は以下の部分にあります。

"Resource": "arn:aws:s3:::list-deny",
"Condition": {
    "StringLike": {
        "s3:prefix": "testdata/*"
    }
}

実はこの s3:prefix を使用して、フォルダ(パス)を指定する方法は s3:GetObject では使用できないんです!
s3:prefix を使用した方法が反映されるのは s3:ListBucket のみで s3:GetObject をはじめとする他の S3 アクションに対して使用しても適用されないんです。。
「じゃあ s3:ListBucket も Resource を使用して指定すればいいんじゃない?」と思いますよね!
私もそう思って試してみましたが、それだと今度は s3:ListBucket に適用されなくなってしまうんです。。

解決策

お待たせしました!いよいよ解決策です!
これは至って単純で、s3:ListBucket と s3:GetObject を分けて書くだけです。

{
    "Sid": "ListDeny01",
    "Action": "s3:ListBucket",
    "Effect": "Deny",
    "Resource": "arn:aws:s3:::list-deny",
    "Condition": {
        "StringLike": {
            "s3:prefix": "testdata/*"
        }
    }
},
{
    "Sid": "GetDeny01",
    "Action": "s3:GetObject",
    "Effect": "Deny",
    "Resource": "arn:aws:s3:::list-deny/testdata/*"
}

それぞれの指定方法が違う以上、どうしても同時に記述することはできません。なので、上記ポリシーのように ListDeny01 と GetDeny01 に分けて対応しました。
次のセクションで、適用されているのか挙動を確認してみましょう。

改善後のポリシーで再検証

ようやくポリシーが出来上がったので、書き直して再検証していきましょう。
改善後のポリシーは以下の通りです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllAllow01",
            "Action": [
                "s3:ListBucket",
                "s3:GetObject",
                "s3:ListAllMyBuckets"
            ],
            "Effect": "Allow",
            "Resource": "*"
        },
        {
            "Sid": "ListDeny01",
            "Action": "s3:ListBucket",
            "Effect": "Deny",
            "Resource": "arn:aws:s3:::list-deny",
            "Condition": {
                "StringLike": {
                    "s3:prefix": "testdata/*"
                }
            }
        },
        {
            "Sid": "GetDeny01",
            "Action": "s3:GetObject",
            "Effect": "Deny",
            "Resource": "arn:aws:s3:::list-deny/testdata/*"
        }
    ]
}

こちらのポリシーを適用した上で、もう一度 CLI コマンドを実行してみます。下記コマンドを実行してみます。
なお、CLI からダウンロード を検証した際に、認証情報の登録は完了しているので、再検証では不要です。

aws s3 cp s3://list-deny/testdata/test01.jpg ./

実行結果は以下の通りです。

fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden

この通り、CLI からもオブジェクトの取得が拒否されてエラーが返ってくるようになりました。

まとめ

今回は、IAM ポリシーで S3 アクションを制限する際の仕様について新しく学びになったことをまとめてみました。
これまでテキストベースの試験対策はしてきましたが、実際に AWS を触る機会がほとんどなく、このようなアクションごとの細かいポリシーの書き方については、SAA や SOA でも問われなかったため良い学びになりました(正しくポリシーを設定できることは大事だし!!)。
私と同じように、AWS 学習し始めて間もない方々の助けになっていたら嬉しいです!

参考文献

アノテーション株式会社

アノテーション株式会社は、クラスメソッド社のグループ企業として「オペレーション・エクセレンス」を担える企業を目指してチャレンジを続けています。「らしく働く、らしく生きる」のスローガンを掲げ、様々な背景をもつ多様なメンバーが自由度の高い働き方を通してお客様へサービスを提供し続けてきました。現在当社では一緒に会社を盛り上げていただけるメンバーを募集中です。少しでもご興味あれば、アノテーション株式会社WEBサイトをご覧ください。