Y_Yamashitaのブログ

勉強したことのアウトプット・メモが中心。記事の内容は個人の見解であり、所属組織を代表するものではありません。

KMSカスタマー管理キーで暗号化したSecrets Managerからシークレット情報を取得する時の、復号に関する権限を整理する

最近、KMS回りの権限について勉強中です。
今回は、KMSカスタマー管理キーで暗号化したSecrets Managerからシークレット情報を取得する時の、復号(Decrypt)に関する権限を整理してみたいと思います。

前回のブログ(ECRの暗号化)を踏まえた想像

前回のブログでは、KMSカスタマー管理キーで暗号化したECRにイメージプッシュする時の、暗号化に関する権限を調査しました。

yuy-83.hatenablog.com

調査の結果、ECRリポジトリを作成する際に、IAMユーザーによってCreateGrantが実行されること、KMS側では、そのCreateGrantを許可する必要があることが分かりました。

ECRにイメージプッシュする時の暗号化の動作


これを踏まえると、Secrets Managerでも同様に、下図のような動作になるのかなと想像しました。

ECRと同様なら、こうなりそうなものだが。。


が、残念ながら、またも想像と実際の動作は違っておりました。。

実際の動作

Secrets Managerの場合は、シークレットを作成する際にCreateGrantは実行されませんでした。KMSキーへのアクセスは、シークレットをリクエストするプリンシパルの権限で行われます。(下図の例では、Lambdaに関連付けられたIAMロール)

ただし、一つややこしい点として、KMSキーへのアクセス自体はSecrets Managerが行います。下図の例では、Secrets ManagerがLambdaの代理として、Lambdaの権限を使ってKMSキーにアクセスします。Lambdaが直接KMSにアクセスすることはないため、KMS用のVPCエンドポイントは不要です。

Secrets Managerにアクセスするプリンシパルに、KMSの権限も必要

動作確認用の環境構築

上図の構成で実際に動作確認をするために、環境を構築します。

KMS

「secrets-manager-key」というエイリアスのカスタマー管理キーを作成します。暗号化設定はデフォルトです。
キーポリシーでは、IAMの権限を使用したキーアクセスと、「YuY」ユーザーによるキー管理のみ許可します。具体的なポリシーは以下です。

キーポリシー(クリックまたはタップで詳細表示)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Enable IAM User Permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<account-id>:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Sid": "Allow access for Key Administrators",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<account-id>:user/YuY"
            },
            "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": "*"
        }
    ]
}


Secrets Manager

Secrets Managerで、テスト用のシークレットを作成します。シークレットのキーと値は適当に設定します。先ほど作成したKMSカスタマー管理キーを使って暗号化します。リソースベースポリシーは今回設定しません。


Lambda関数

今回は、Lambda関数をVPC内のプライベートサブネットにデプロイし、VPCエンドポイント経由でSecrets Managerからシークレット値を取得します。

コード

Lambda関数のコードは以下です。

Lambda関数(クリックまたはタップで詳細表示)

import json
import boto3
from botocore.exceptions import ClientError

def lambda_handler(event, context):
    secret_name = "test-secret"
    region_name = "ap-northeast-1"

    # Create a Secrets Manager client
    session = boto3.session.Session()
    client = session.client(
        service_name='secretsmanager',
        region_name=region_name
    )

    try:
        get_secret_value_response = client.get_secret_value(
            SecretId=secret_name
        )
    except ClientError as e:
        # For a list of exceptions thrown, see
        # https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
        raise e

    secret = get_secret_value_response['SecretString']
    return {
        'statusCode': 200,
        'body': json.dumps(secret)
    }


IAMロール

Lambdaに紐づけるIAMロールには、以下のポリシーをアタッチします。

ポリシー名 タイプ 概要
AWSLambdaVPCAccessExecutionRole AWS管理 LambdaをVPCに配置するために必要な権限をまとめたポリシー
AWSLambdaBasicExecutionRole-xxxxxxxx カスタマー管理 Lambda関数作成時に自動で作成されるポリシー。CloudWatch Logsへのログ送付を許可する
GetSecretValuePolicy カスタマー管理 Secrets Managerのシークレットを取得する権限を持つポリシー
KMSDecryptPolicy カスタマー管理 KMSキーを使った復号を許可するポリシー

カスタマー管理ポリシーの具体的な中身は以下です。

■GetSecretValuePolicy

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

■KMSDecryptPolicy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "kms:Decrypt",
            "Resource": "arn:aws:kms:ap-northeast-1:<account-id>:key/<今回作成したsecrets-manager-keyのキーID>"
        }
    ]
}


その他

Lambda はVPCのプライベートサブネットに配置します。また、Secrets ManagerエンドポイントをVPCに配置します。どちらもセキュリティグループはdefaultにし、相互の通信を許可します。Secrets Managerエンドポイントのポリシーはフルアクセスにします。

LambdaのVPC設定

Secrets ManagerのVPCエンドポイント

動作確認

ここからは、実際にSecrets Managerからシークレット情報を取得しながら、各種ログを見ていきたいと思います。
Lambda関数でテストを実行します。テストは成功し、シークレットが取得できました。


続いて、CloudTrailでDecryptのログを確認します。

Decryptログ(クリックまたはタップで詳細表示)

{
    "eventVersion": "1.09",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "AROATO53XXXXXXXXXYAHN:GetSecretValueTest",
        "arn": "arn:aws:sts::<account-id>:assumed-role/GetSecretValueTest-role-8iun9wza/GetSecretValueTest",
        "accountId": "<account-id>",
        "accessKeyId": "ASIATO53XXXXXLYIAQGQ",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "AROATO53XXXXXXXXXYAHN",
                "arn": "arn:aws:iam::<account-id>:role/service-role/GetSecretValueTest-role-8iun9wza",
                "accountId": "<account-id>",
                "userName": "GetSecretValueTest-role-8iun9wza"
            },
            "attributes": {
                "creationDate": "2024-08-06T02:29:30Z",
                "mfaAuthenticated": "false"
            }
        },
        "invokedBy": "secretsmanager.amazonaws.com"
    },
    "eventTime": "2024-08-06T02:29:33Z",
    "eventSource": "kms.amazonaws.com",
    "eventName": "Decrypt",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "secretsmanager.amazonaws.com",
    "userAgent": "secretsmanager.amazonaws.com",
    "requestParameters": {
        "encryptionAlgorithm": "SYMMETRIC_DEFAULT",
        "encryptionContext": {
            "SecretARN": "arn:aws:secretsmanager:ap-northeast-1:<account-id>:secret:test-secret-0qvFls",
            "SecretVersionId": "396f5e59-37b0-470c-9001-80b4a56fc8ec"
        }
    },
    "responseElements": null,
    "requestID": "c2ddf09a-ec9b-46af-a6c7-aaddf7c25d26",
    "eventID": "86bbee00-4fe8-4460-ac98-a2cd4421bee8",
    "readOnly": true,
    "resources": [
        {
            "accountId": "<account-id>",
            "type": "AWS::KMS::Key",
            "ARN": "arn:aws:kms:ap-northeast-1:<account-id>:key/1a243d36-xxxx-xxxx-xxxx-ffdf6e79f7e1"
        }
    ],
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "<account-id>",
    "sharedEventID": "4088647a-8cab-45f4-a1b2-fe0bbb48f74c",
    "vpcEndpointId": "vpce-0e1bae87ff206ac9e",
    "vpcEndpointAccountId": "secretsmanager.amazonaws.com",
    "eventCategory": "Management"
}


6行目、13行目の "arn" や 15行目の "userName" を見ると、Lambdaに紐づけたIAMロールになっています。その一方で、22行目の "invokedBy": "secretsmanager.amazonaws.com" や 28行目の "sourceIPAddress": "secretsmanager.amazonaws.com" という記載から、Secrets Managerにより実行されていることが読み取れます。52行目に"vpcEndpointId" がありますが、自分で作ったVPCエンドポイントのIDではありません。53行目に "vpcEndpointAccountId": "secretsmanager.amazonaws.com" とあるので、Secrets ManagerからKMSにアクセスする過程で、内部的にAWS管理のVPCエンドポイントを使っているのかもしれません。

キーポリシーで、Lambdaに紐づけたIAMロールのDecryptを拒否してみる

続いて、キーポリシーを更新し、Lambdaに紐づけたIAMロールのDecryptを拒否してみます。

キーポリシー(クリックまたはタップで詳細表示)

{
    "Version": "2012-10-17",
    "Id": "key-consolepolicy-3",
    "Statement": [
        {
            "Sid": "Enable IAM User Permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<account-id>:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Sid": "Allow access for Key Administrators",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<account-id>:user/YuY"
            },
            "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": "Deny Decrypt Lambda Role",
            "Effect": "Deny",
            "Principal": {
                "AWS": "arn:aws:iam::<account-id>:role/service-role/GetSecretValueTest-role-8iun9wza"
            },
            "Action": "kms:Decrypt",
            "Resource": "*"
        }
    ]
}


ポリシー更新後にLambdaでテストを実行すると、今度はエラーとなりました。エラーメッセージを見ると、KMSへのアクセスが許可されないとハッキリ書かれています。


改めてCloudTrailログを確認します。まずはGetSecretValueから確認します。

GetSecretValue(クリックまたはタップで詳細表示)

{
    "eventVersion": "1.09",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "AROATO53XXXXXXXXXYAHN:GetSecretValueTest",
        "arn": "arn:aws:sts::<account-id>:assumed-role/GetSecretValueTest-role-8iun9wza/GetSecretValueTest",
        "accountId": "<account-id>",
        "accessKeyId": "ASIATO53XXXXXXXXLSPK",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "AROATO53XXXXXXXXXYAHN",
                "arn": "arn:aws:iam::<account-id>:role/service-role/GetSecretValueTest-role-8iun9wza",
                "accountId": "<account-id>",
                "userName": "GetSecretValueTest-role-8iun9wza"
            },
            "attributes": {
                "creationDate": "2024-08-06T02:44:24Z",
                "mfaAuthenticated": "false"
            }
        }
    },
    "eventTime": "2024-08-06T02:44:27Z",
    "eventSource": "secretsmanager.amazonaws.com",
    "eventName": "GetSecretValue",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "10.0.0.134",
    "userAgent": "Boto3/1.34.42 md/Botocore#1.34.42 ua/2.0 os/linux#5.10.219-229.866.amzn2.x86_64 md/arch#x86_64 lang/python#3.12.3 md/pyimpl#CPython exec-env/AWS_Lambda_python3.12 cfg/retry-mode#legacy Botocore/1.34.42",
    "errorCode": "AccessDenied",
    "errorMessage": "Access to KMS is not allowed",
    "requestParameters": {
        "secretId": "test-secret"
    },
    "responseElements": null,
    "requestID": "52075a59-6751-418b-ae54-187524137760",
    "eventID": "3248a5da-9378-4799-ab60-428a47a09fe6",
    "readOnly": true,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "<account-id>",
    "sharedEventID": "38247234-d5d5-4c90-9f84-69058c1a7f34",
    "vpcEndpointId": "vpce-021a62b9418a93630",
    "vpcEndpointAccountId": "<account-id>",
    "eventCategory": "Management",
    "tlsDetails": {
        "tlsVersion": "TLSv1.3",
        "cipherSuite": "TLS_AES_128_GCM_SHA256",
        "clientProvidedHostHeader": "secretsmanager.ap-northeast-1.amazonaws.com"
    }
}


29行目の "errorCode": "AccessDenied" 、30行目の "errorMessage": "Access to KMS is not allowed" で、KMSへのアクセスが許可されていないことが読み取れます。

Decrypt のログも見てみます。

Decrypt(クリックまたはタップで詳細表示)

{
    "eventVersion": "1.09",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "AROATO53XXXXXXXXXYAHN:GetSecretValueTest",
        "arn": "arn:aws:sts::<account-id>:assumed-role/GetSecretValueTest-role-8iun9wza/GetSecretValueTest",
        "accountId": "<account-id>",
        "accessKeyId": "ASIATO53XXXXXXXXUL4P",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "AROATO53XXXXXXXXXYAHN",
                "arn": "arn:aws:iam::<account-id>:role/service-role/GetSecretValueTest-role-8iun9wza",
                "accountId": "<account-id>",
                "userName": "GetSecretValueTest-role-8iun9wza"
            },
            "attributes": {
                "creationDate": "2024-08-06T02:44:24Z",
                "mfaAuthenticated": "false"
            }
        },
        "invokedBy": "secretsmanager.amazonaws.com"
    },
    "eventTime": "2024-08-06T02:44:27Z",
    "eventSource": "kms.amazonaws.com",
    "eventName": "Decrypt",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "secretsmanager.amazonaws.com",
    "userAgent": "secretsmanager.amazonaws.com",
    "errorCode": "AccessDenied",
    "errorMessage": "User: arn:aws:sts::<account-id>:assumed-role/GetSecretValueTest-role-8iun9wza/GetSecretValueTest is not authorized to perform: kms:Decrypt on resource: arn:aws:kms:ap-northeast-1:<account-id>:key/1a243d36-xxxx-xxxx-xxxx-ffdf6e79f7e1 with an explicit deny in a resource-based policy",
    "requestParameters": null,
    "responseElements": null,
    "requestID": "bdaf3e10-2084-4332-9b4b-250514c509c3",
    "eventID": "b443107b-f86a-4ac5-9409-4765d3fa6591",
    "readOnly": true,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "<account-id>",
    "sharedEventID": "b1526ab3-73e6-49c5-a866-0d4fdc38278b",
    "vpcEndpointId": "vpce-0e1bae87ff206ac9e",
    "vpcEndpointAccountId": "secretsmanager.amazonaws.com",
    "eventCategory": "Management"
}


30行目の "errorCode": "AccessDenied" はGetSecretValueと同じですが、31行目の "errorMessage" の内容が、より詳細になっています。「KMSキーのリソースベースポリシーで、Lambda用IAMロールのDecryptが許可されていない」と明記されています。

以上の内容から、「Decryptを実行しているのはSecrets Managerだが、権限についてはLambda用IAMロールの権限が使われている」ということが確認できました。

(応用)暗号化コンテキストを使って特定シークレット以外のDecryptを拒否する

Secrets Managerでも暗号化コンテキストが利用されます。詳細は以下公式ドキュメントで解説されています。

docs.aws.amazon.com

暗号化コンテキストの "SecretARN" を使うことで、"SecretARN" で指定したシークレット以外のDecryptを拒否することが可能です。具体的には、キーポリシーに以下の記載を加えます。(※SecretARNにワイルドカードを使う場合は、"StringNotEquals" ではなく "StringNotLike" を使用してください。)

キーポリシー(※該当部分のみ。クリックまたはタップで詳細表示)

{
  "Sid": "Deny access not for specified secret",
  "Effect": "Deny",
  "Principal": {
    "AWS": [
      "*"
    ]
  },
  "Action": "kms:Decrypt",
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "kms:EncryptionContext:SecretARN": "arn:aws:secretsmanager:<region>:<account-id>:secret:<secret-name>"
    }
  }
}


こうすることで、同じキーを他のリソースに使いまわされることを防ぐことが出来ます。今回はDecryptのみ拒否しましたが、Encrypt・GenerateDataKey・CreateGrant・ReEncrypt 等も合わせて拒否しておくと良いかと思います。

おわりに

というわけで、Secrets ManagerをKMSカスタマー管理キーで暗号化/復号する際の動作は、ECRとはまた異なるものでした。前回のブログにも書きましたが、暗号化/復号するサービスによって動作や権限が異なると混乱するので、出来れば全て同じ設定方法で制御できるとありがたいなあと思いました。。

今回のブログは以上となります。少しでも参考になることがあれば幸いです。