NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

注目のタグ

    SSE-KMSで暗号化したS3に対して、ファイルアップロードする際に必要な権限について整理する

    本記事は  AWSアワード記念!夏のアドベントカレンダー  12日目の記事です。
    🎆🏆  11日目  ▶▶ 本記事 ▶▶  13日目  🏆🎆

    はじめに

    こんにちは、西本です。AWSアワード記念!夏のアドベントカレンダー 12日目を担当させていただきます。

    2024 Japan AWS All Certifications Engineersに選出

    昨年に引き続き、「2024 Japan AWS All Certifications Engineers」に選出いただきました。受賞者数も激増、試験も新しいものが増えてきていますが、引き続き頑張っていきたいと思います。

    今回のテーマ

    AWS Key Management Service(以降、KMS)は使われていますでしょうか?多くのシステムで利用しているサービスかと思います。 例えば、「Amazon S3の暗号化に使用している」、「AWS Systems Manager Parameter Storeのパラメータの暗号化に使用している」など用途は様々かと思います。 今回はSSE-KMSで暗号化したAmazon S3(以降、S3)に対するファイルアップロード時に必要な権限について、動作検証しながら見ていきたいと思います。

    KMSの仕組み

    多くのブログ等で紹介されているのでご存じの方が多いかと思いますが、KMSを利用した暗号化の仕組みについて触れておきたいと思います。 KMSではエンベロープ暗号化と呼ばれる手法が用いられており、ざっくりなフローは下記の通りです。

    1. データキーを生成

      図:データキーの生成

    2. データキーでデータを暗号化

      図:データの暗号化

    3. データキーでデータを復号

      図:データの復号

    コンソールから作成した下記のKMSキーがデータを直接暗号化しているキーではないということを、ここではまず理解いただければと思います。

    図:KMSコンソール画面
    上記のキーのことを「AWS KMS key」あるいは「KMS キー」と呼びます。過去にはカスタマーマスターキー(CMK)と呼ばれていたのでこちらの呼び方のほうがピンと来る方もいらっしゃるかもしれません。AWS KMSではこのKMSキーを使用してデータキーを生成し、そのデータキーを使ってデータを暗号化・復号する仕組みに基本的にはなっています(例外があります)。ではここから本題に入って、SSE-KMSで暗号化されたS3バケットにファイルをアップロードする際にどういった権限が必要なのか見ていきたいと思います。

    SSE-KMSで暗号化されたS3バケットにファイルをアップロードする際に必要な権限

    実際にS3を作成したKMSキーで暗号化して、動作確認をしてみたいと思います。今回使用するIAMロール(AwsAward2024BlogRole)にはAdmin権限を付与し、キーポリシーで権限の制御をしていきます。 まず、暗号化に必要な権限を考えた際にぱっと思い浮かぶのは「Encrypt」権限かなと思います(訳すと暗号化ですし。。)。なのでEncrypt権限のみ付与してS3へのファイルアップロードを試してみます。

    ・キーポリシー

    {
        "Version": "2012-10-17",
        "Id": "key-consolepolicy-3",
        "Statement": [
            {
                "Sid": "Allow access for Key Administrators",
                "Effect": "Allow",
                "Principal": {
                    "AWS": "arn:aws:iam::123456789012:role/管理用ロール"
                },
                "Action": [
                    "kms:Create*",
                    "kms:Describe*",
                    "kms:Enable*",
                    "kms:List*",
                    "kms:Put*",
                    "kms:Update*",
                    "kms:Revoke*",
                    "kms:Disable*",
                    "kms:Get*",
                    "kms:Delete*",
                    "kms:TagResource",
                    "kms:UntagResource",
                    "kms:ScheduleKeyDeletion",
                    "kms:CancelKeyDeletion",
                    "kms:RotateKeyOnDemand"
                ],
                "Resource": "*"
            },
            {
                "Sid": "Allow for AwsAward2024BlogRole",
                "Effect": "Allow",
                "Principal": {
                    "AWS": "arn:aws:iam::123456789012:role/AwsAward2024BlogRole"
                },
                "Action": "kms:Encrypt",
                "Resource": "*"
            }
        ]
    }
    

    ・実行結果

    [cloudshell-user@ip-10-134-25-112 ~]$ aws s3 cp blog-test.txt s3://bucketname/
    upload failed: ./blog-test.txt to s3://bucketname/blog-test.txt An error occurred (AccessDenied) when calling the PutObject operation: User: arn:aws:sts::123456789012:assumed-role/AwsAward2024BlogRole/xxxxx is not authorized to perform: kms:GenerateDataKey on resource: arn:aws:kms:ap-northeast-1:123456789012:key/xxxx-xxxx because no resource-based policy allows the kms:GenerateDataKey action

    エラーになります。「Encrypt」って暗号化権限じゃん。。と最初は思いましたが「GenerateDataKey」がどうやら必要のようです。KMSの仕組みの中で紹介した通り、データキーを使って暗号化の処理をすることからもこれが必要ということがわかります。 なので下記の通りキーポリシーを変更して再度実施してみます。

    ・キーポリシー(変更箇所のみ抜粋)

            {
                "Sid": "Allow for AwsAward2024BlogRole",
                "Effect": "Allow",
                "Principal": {
                    "AWS": "arn:aws:iam::123456789012:role/AwsAward2024BlogRole"
                },
                "Action": [
                    "kms:Encrypt",
                    "kms:GenerateDataKey"
                    ],
                "Resource": "*"
            }
    

    ・実行結果

    [cloudshell-user@ip-10-134-25-112 ~]$ aws s3 cp blog-test.txt s3://bucketname/
    upload: ./blog-test.txt to s3://bucketname/blog-test.txt

    成功しました。これで少なくとも「GenerateDataKey」の権限が必須であることが分かったかと思います。では「Encrypt」権限は必要なのか確認してみます。

    ・キーポリシー(変更箇所のみ抜粋)

            {
                "Sid": "Allow for AwsAward2024BlogRole",
                "Effect": "Allow",
                "Principal": {
                    "AWS": "arn:aws:iam::123456789012:role/AwsAward2024BlogRole"
                },
                "Action": "kms:GenerateDataKey",
                "Resource": "*"
            }
    

    ・実行結果

    [cloudshell-user@ip-10-134-25-112 ~]$ aws s3 cp blog-test.txt s3://awsaward2024blogbucket/
    upload: ./blog-test.txt to s3://bucketname/blog-test.txt

    こちらも成功しました。どうもKMSで暗号化されたS3にファイルをアップロードする際に必要な暗号化権限はあくまで「GenerateDataKey」のようです(※十分かどうかはかならず検証すること)

    この時「Encrypt」権限っていつ使うんだろうと疑問に思いました。

    「Encrypt」っていつ必要な権限なのか

    データを暗号化しているのはあくまでデータキーとは言え、データキーを暗号化しているのはKMSキーになります。 「GenerateDataKey」権限にデータキーの暗号化権限も内包されているとしたら「Encrypt」っていつ使うの。。と思い調べてみました。 まず、Encrypt APIについて調べてみます。

    Encrypts plaintext of up to 4,096 bytes using a KMS key. You can use a symmetric or asymmetric KMS key with a KeyUsage of ENCRYPT_DECRYPT. You can use this operation to encrypt small amounts of arbitrary data, such as a personal identifier or database password, or other sensitive information. You don't need to use the Encrypt operation to encrypt a data key. The GenerateDataKey and GenerateDataKeyPair operations return a plaintext data key and an encrypted copy of that data key.

    しっかり書いてますね。。用途としてはそこまで多くは無いように思いますが、データキーではなく直接KMSキーで暗号化したいような場合に使用するAPIのようです。また暗号化対象のデータの上限サイズもあるようですね。 Encrypt権限を付与していない状態で、Encrypt APIを呼んでみます。

    ・実行結果

    [cloudshell-user@ip-10-134-25-112 ~]$ ls -la | grep blog-test.txt 
    -rw-r--r--. 1 cloudshell-user cloudshell-user        5 Jul 20 13:17 blog-test.txt
    
    [cloudshell-user@ip-10-134-25-112 ~]$ aws kms encrypt --key-id xxxx-xxxx --plaintext fileb://blog-test.txt
    An error occurred (AccessDeniedException) when calling the Encrypt operation: User: arn:aws:sts::123456789012:assumed-role/AwsAward2024BlogRole/xxxx is not authorized to perform: kms:Encrypt on resource: arn:aws:kms:ap-northeast-1:123456789012:key/xxxx-xxxx because no resource-based policy allows the kms:Encrypt action

    想定通りのエラーですね。では「Encrypt」権限を付与したうえで再度試してみます。

    ・キーポリシー(変更箇所のみ抜粋)

            {
                "Sid": "Allow for AwsAward2024BlogRole",
                "Effect": "Allow",
                "Principal": {
                    "AWS": "arn:aws:iam::123456789012:role/AwsAward2024BlogRole"
                },
                "Action": "kms:Encrypt",
                "Resource": "*"
            }
    

    ・実行結果

    [cloudshell-user@ip-10-134-25-112 ~]$ aws kms encrypt --key-id xxxx-xxxx --plaintext fileb://blog-test.txt
    {
        "CiphertextBlob": "AQICxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "KeyId": "arn:aws:kms:ap-northeast-1:123456789012:key/xxxx-xxxx",
        "EncryptionAlgorithm": "SYMMETRIC_DEFAULT"
    }

    正常に動作しましたね。この時には「GenerateDataKey」の権限は付与していないことからも、データキーは特に生成されていません。

    また、試しに5120bytesのファイルを暗号化してみようと思います。

    ・実行結果

    [cloudshell-user@ip-10-134-25-112 ~]$ ls -la | grep blog-test-5KB.txt 
    -rw-r--r--. 1 cloudshell-user cloudshell-user     5120 Jul 20 13:55 blog-test-5KB.txt
    
    [cloudshell-user@ip-10-134-25-112 ~]$ aws kms encrypt --key-id xxxx-xxxx --plaintext fileb://blog-test-5KB.txt
    An error occurred (ValidationException) when calling the Encrypt operation: 1 validation error detected: Value at 'plaintext' failed to satisfy constraint: Member must have length less than or equal to 4096

    しっかりエラーになりましたね。ようやく「Encrypt」と「GenerateDataKey」が使われる場面の違いについて理解できました。

    マルチパートアップロード時には「Decrypt」権限が必要

    本題と少しそれてしまったのですが、ここまででSSE-KMSで暗号化したS3に対してファイルをアップロードする際には「GenerateDataKey」があればアップロードはできましたと紹介しました。 ただそれだけでは不十分な場面があるんです。

    最初の検証ではかなり小さいサイズのファイルをS3にアップロードしていました。ここでは150MB程度のファイルをアップロードしてみます。

    ・実行結果

    [cloudshell-user@ip-10-134-13-5 ~]$ ls -la | grep blog-test-150MB.txt
    -rw-r--r--. 1 cloudshell-user cloudshell-user 157286400 Jul 21 09:12 blog-test-150MB.txt
    
    [cloudshell-user@ip-10-134-13-5 ~]$ aws s3 cp blog-test-150MB.txt s3://bucketname
    upload failed: ./blog-test-150MB.txt to s3://bucketname/blog-test-150MB.txt An error occurred (AccessDenied) when calling the UploadPart operation: User: arn:aws:sts::123456789012:assumed-role/AwsAward2024BlogRole/xxxx is not authorized to perform: kms:Decrypt on resource: arn:aws:kms:ap-northeast-1:123456789012:key/xxxx-xxxx because no resource-based policy allows the kms:Decrypt action

    エラーになり、なぜか「Decrypt」アクションが必要と。。 ポイントは下記の部分です。

    An error occurred (AccessDenied) when calling the UploadPart operation

    アップロードする際のファイルサイズが大きいことで、「マルチパートアップロード」処理が走っています。SSE-KMSで暗号化されたS3に対して、マルチパートアップロードでファイルをアップロードする際には実は「Decrypt」権限が必要です。

    「Decrypt」といえばどうしても復号する際に必要な権限のイメージが強いため、ファイルをアップロードする際(=暗号化する際)には必要ないと判断してしまうことも多いかと思います。 特に、ファイルアップロード観点のみのテストだと、この権限の付与が漏れてしまい想定外のエラーが発生するといったことにもなります。SSE-KMSで暗号化されたS3に対してファイルアップロードをする必要があるリソースには「GenerateDataKey」「Decrypt」権限の両方を付けておくのが基本的な設計になります。 CloudWatch LogsからS3へのファイルのアーカイブ機能、その他にもS3にファイルをAWSのサービス内の機能からアップロードするような機能も多くあるかと思います。こういった機能でもマルチパートアップロード処理が使われる可能性はあるため、権限には気を付けましょう。

    最後に

    SSE-KMSで暗号化したS3にファイルをアップロードする際に必要な権限と「Encrypt」権限はいつ使うのかについて紹介してみました。特にマルチパートアップロードには「Decrypt」が必要というのは気づきにくいポイントかなと思います。 最小権限を意識しつつ、本当に必要な権限についてしっかり洗い出すことを意識して今後もシステム設計をしていきたいと思います。

    執筆者:西本 悠馬 クラウドエンジニア AWSをメインに触っています