1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【AWS】Cognitoで認証したユーザーごとに専用のCMKを構築する方法 ~CLIで検証してみた~

Posted at

はじめに

本記事では、Cognito で認証したユーザーに対して、専用の CMK を構築し、他のユーザーが触れないようにアクセス制御する方法をご紹介します。

珍しい要件であり、同様の記事がみつからなかったため、備忘として記事化しました。

実現したいこと

実現したいことは、以下の通りです。

  • Cognito ユーザー1毎に専用の CMK を構築
    (100 ユーザーいれば、100 の CMK ができるイメージ)
  • 各 CMK に複数の Cognito ユーザーがアクセスすることは不可

以下のイメージ図をご覧ください。

イメージ図

注意点として、ID プールに紐づけられる認証済みユーザー用の IAM ロールは1つのみです。
上記構成では、全 Cognito ユーザーで IAM ロールを共有するため、IAM ロールで CMK のアクセス先制御を行うのは難しそうです。

結論

キーポリシーの Condition 句で、cognito-identity.amazonaws.com:sub を使用することで、上記を実現可能です。以下をご参照ください。

キーポリシー
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Enable IAM policies",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::${ACCOUNT_ID}:root"
      },
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "Allow a specific cognito user to use the key",
      "Effect": "Allow",
      "Principal": "*",
      "Action": [
        "kms:Describe*",
        "kms:Get*",
        "kms:List*"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:sub": "${IDENTITY_ID}",
          "aws:userid": "${AUTH_ROLE_ID}:CognitoIdentityCredentials"
        }
      }
    }
  ]
}

cognito-identity.amazonaws.com:sub で IdentityId を指定することにより、特定の Cognito ユーザーのみに CMK アクセスを制限することが可能です。2
アクセス元の IAM ロールは限定されているので、aws:userid では認証済みユーザー用の IAM ロール ID を指定しています。

なお、キーポリシーで KMS 権限を付与しているため、IAM ロールに KMS 権限は不要です。Sid「Enable IAM policies」ではデフォルトのキーポリシーを使用していますが、必要に応じて権限を絞ってください。3

構築方法

前提

事前に、Cognito ユーザープール・ID プール作成済であることを前提とします。
Amplify でのCognito関連構築方法に関しては、以下をご参照ください。
amplify add authで簡単に認証機能を実装できます。

以下、Admin 相当の権限を保有する Cloud9 のターミナルより、AWS CLI で Cognito ユーザと専用の CMK を作成します。4

  • OS : Amazon Linux 2
  • AWS CLI : v2.10.1

共通値の設定

まずは、アカウント・リージョン情報や事前作成済みの Cognito に関する値を設定します。

# 共通値
$ ACCOUNT_ID=012345678901
$ REGION=ap-northeast-1
$ USER_POOL_ID=${REGION}_123xyABCD
$ CLIENT_ID=abcdefghijklmnop1234567890
$ IDENTITY_POOL_ID=${REGION}:11111111-1111-1111-1111-111111111111
$ COGNITO_USER_POOL=cognito-idp.${REGION}.amazonaws.com/${USER_POOL_ID}
$ AUTH_ROLE_NAME=amplify-react-dev-01234-authRole

Cognito ユーザー作成

次に、テスト用の Cognito ユーザーとして、「test_user1」を作成します。

# ユーザー情報
$ USER_NAME=test_user1
$ PASSWORD="Password@1"

# 「test_user1」を作成
$ aws cognito-idp admin-create-user \
  --user-pool-id ${USER_POOL_ID} \
  --username ${USER_NAME} \
  --user-attributes Name=email,Value="test@sample.com" Name=email_verified,Value=true \
  --message-action SUPPRESS > /dev/null
$ aws cognito-idp admin-set-user-password \
  --user-pool-id ${USER_POOL_ID} \
  --username ${USER_NAME} \
  --password ${PASSWORD} \
  --permanent

CMKの作成

専用 CMK 作成の下準備として、test_user1 の IdentityId を取得します。

# トークン取得(IdentityId取得に必要)
$ ID_TOKEN=$(aws cognito-idp admin-initiate-auth \
  --user-pool-id ${USER_POOL_ID} \
  --client-id ${CLIENT_ID} \
  --auth-flow ADMIN_NO_SRP_AUTH \
  --auth-parameters "USERNAME=${USER_NAME},PASSWORD=${PASSWORD}" \
  --query "AuthenticationResult.IdToken" \
  --output text)

# IdentityId取得
$ IDENTITY_ID=$(aws cognito-identity get-id \
  --identity-pool-id ${IDENTITY_POOL_ID} \
  --logins "${COGNITO_USER_POOL}=${ID_TOKEN}" \
  --query "IdentityId" \
  --output text) && echo ${IDENTITY_ID}
(実行結果) IDENTITY_ID
ap-northeast-1:1234abcd-a123-b234-c345-222222222222

次に、キーポリシーで許可する IAM ロールを絞るため、Cognito 認証後のロール ID を取得します。

# Cognito 認証済みユーザー用の IAM ロール IDを取得
$ AUTH_ROLE_ID=$(aws iam get-role \
  --role-name ${AUTH_ROLE_NAME} \
  --query "Role.RoleId" \
  --output text) && echo ${AUTH_ROLE_ID}
(実行結果) AUTH_ROLE_ID
AROA1234567890EXAMPLE

上記で CMK 作成のための下準備は完了です。
以下コマンドで、test_user1 専用の CMK を作成します。前述の通りに キーポリシーを指定します。

# CMK・キーポリシーを作成
$ KEY_ID=$(aws kms create-key \
  --policy \
'{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Enable IAM policies",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::'${ACCOUNT_ID}':root"
      },
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "Allow a specific cognito user to use the key",
      "Effect": "Allow",
      "Principal": "*",
      "Action": [
        "kms:Describe*",
        "kms:Get*",
        "kms:List*"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:sub": "'${IDENTITY_ID}'",
          "aws:userid": "'${AUTH_ROLE_ID}':CognitoIdentityCredentials"
        }
      }
    }
  ]
}' \
  --query "KeyMetadata.KeyId" \
  --output text) && echo ${KEY_ID}
(実行結果) KEY_ID
b8a9477d-836c-491f-857e-07937918959b

以上で、Cognito ユーザと専用の CMK 作成は完了です。

アクセステスト

想定通りの制御ができているか、以下2種のテストをします。

  1. 正常系:
    Cognito ユーザー「test_user1」が、専用の CMK にアクセスできることの確認
  2. 異常系:
    他の Cognito ユーザーが、test_user1 専用の CMK にアクセスできないことの確認

1. 正常系

test_user1 が専用の CMK にアクセスできることを確認します。
まずは、test_user1 のクレデンシャルを取得・設定します。
※トークンなどは、構築時に取得したコマンドと同じなので省略

# クレデンシャル取得
$ CREDENTIALS=$(aws cognito-identity get-credentials-for-identity \
  --identity-id ${IDENTITY_ID} \
  --logins "${COGNITO_USER_POOL}=${ID_TOKEN}")

# クレデンシャルを設定
$ export AWS_ACCESS_KEY_ID=`echo "${CREDENTIALS}" | jq -r ".Credentials.AccessKeyId"`
$ export AWS_SECRET_ACCESS_KEY=`echo "${CREDENTIALS}" | jq -r ".Credentials.SecretKey"`
$ export AWS_SECURITY_TOKEN=`echo "${CREDENTIALS}" | jq -r ".Credentials.SessionToken"`
$ aws sts get-caller-identity
(実行結果)
{
  "UserId": "AROA1234567890EXAMPLE:CognitoIdentityCredentials",
  "Account": "012345678901",
  "Arn": "arn:aws:sts::012345678901:assumed-role/amplify-react-dev-01234-authRole/CognitoIdentityCredentials"
}

上記クレデンシャルで、test_user1 専用の CMK にアクセスしてみます。

# CMKの情報を取得
$ aws kms describe-key --key-id ${KEY_ID}
(実行結果)
{
  "KeyMetadata": {
    "AWSAccountId": "012345678901",
    "KeyId": "b8a9477d-836c-491f-857e-07937918959b",
    "Arn": "arn:aws:kms:ap-northeast-1:012345678901:key/b8a9477d-836c-491f-857e-07937918959b",
    "CreationDate": "2023-03-13T10:14:42.260000+00:00",
    "Enabled": true,
    "Description": "",
    "KeyUsage": "ENCRYPT_DECRYPT",
    "KeyState": "Enabled",
    "Origin": "AWS_KMS",
    "KeyManager": "CUSTOMER",
    "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT",
    "KeySpec": "SYMMETRIC_DEFAULT",
    "EncryptionAlgorithms": [
      "SYMMETRIC_DEFAULT"
    ],
    "MultiRegion": false
  }
}

無事にアクセスすることができました。

2. 異常系

他の Cognito ユーザーを作って、前述の CMK にアクセスできないことを確認してみます。
まずは、「test_user2」を作成します。

# クレデンシャルのリセット
$ export -n AWS_ACCESS_KEY_ID
$ export -n AWS_SECRET_ACCESS_KEY
$ export -n AWS_SECURITY_TOKEN

# 他ユーザー情報を定義
$ USER_NAME=test_user2
$ PASSWORD="Password@2"

# 他ユーザー「test_user2」を作成
$ aws cognito-idp admin-create-user \
  --user-pool-id ${USER_POOL_ID} \
  --username ${USER_NAME} \
  --user-attributes Name=email,Value="test2@sample.com" Name=email_verified,Value=true \
  --message-action SUPPRESS > /dev/null
$ aws cognito-idp admin-set-user-password \
  --user-pool-id ${USER_POOL_ID} \
  --username ${USER_NAME} \
  --password ${PASSWORD} \
  --permanent

次に、test_user2 のクレデンシャルを取得・設定します。

# 認証・認可
$ ID_TOKEN=$(aws cognito-idp admin-initiate-auth \
  --user-pool-id ${USER_POOL_ID} \
  --client-id ${CLIENT_ID} \
  --auth-flow ADMIN_NO_SRP_AUTH \
  --auth-parameters "USERNAME=${USER_NAME},PASSWORD=${PASSWORD}" \
  --query "AuthenticationResult.IdToken" \
  --output text)
$ IDENTITY_ID=$(aws cognito-identity get-id \
  --identity-pool-id ${IDENTITY_POOL_ID} \
  --logins "${COGNITO_USER_POOL}=${ID_TOKEN}" \
  --query "IdentityId" \
  --output text)
$ CREDENTIALS=$(aws cognito-identity get-credentials-for-identity \
  --identity-id ${IDENTITY_ID} \
  --logins "${COGNITO_USER_POOL}=${ID_TOKEN}")

# クレデンシャルを設定
$ export AWS_ACCESS_KEY_ID=`echo "${CREDENTIALS}" | jq -r ".Credentials.AccessKeyId"`
$ export AWS_SECRET_ACCESS_KEY=`echo "${CREDENTIALS}" | jq -r ".Credentials.SecretKey"`
$ export AWS_SECURITY_TOKEN=`echo "${CREDENTIALS}" | jq -r ".Credentials.SessionToken"`
$ aws sts get-caller-identity
(実行結果)
{
  "UserId": "AROA1234567890EXAMPLE:CognitoIdentityCredentials",
  "Account": "012345678901",
  "Arn": "arn:aws:sts::012345678901:assumed-role/amplify-react-dev-01234-authRole/CognitoIdentityCredentials"
}

get-caller-identity の実行結果は、test_user1 と同じです。この点については後ほど触れます。
上記クレデンシャルで、test_user1 専用の CMK にアクセスしてみます。

# CMKの情報を取得
$ aws kms describe-key --key-id ${KEY_ID}
(実行結果)
An error occurred (AccessDeniedException) when calling the DescribeKey operation: User: arn:aws:sts::012345678901:assumed-role/amplify-react-dev-01234-authRole/CognitoIdentityCredentials is not authorized to perform: kms:DescribeKey on resource: arn:aws:kms:ap-northeast-1:012345678901:key/b8a9477d-836c-491f-857e-07937918959b because no identity-based policy allows the kms:DescribeKey action

想定通り、アクセス拒否されました。

考察

test_user1 と test_user2 で get-caller-identity の結果が同じなのに、CMK のアクセス結果が異なるのはなぜでしょうか。
まずは、test_user1 のリクエストに関する CloudTrail ログをみてみます。

(抜粋表示) test_user1 の CloudTrail ログ
{
  "eventVersion": "1.08",
  "userIdentity": {
    "type": "AssumedRole",
    "principalId": "AROA1234567890EXAMPLE:CognitoIdentityCredentials",
    "sessionContext": {
      "sessionIssuer": {
        "arn": "arn:aws:iam::012345678901:role/amplify-react-dev-01234-authRole"
      },
      "webIdFederationData": {
        "federatedProvider": "cognito-identity.amazonaws.com",
        "attributes": {
          "cognito-identity.amazonaws.com:aud": "ap-northeast-1:11111111-1111-1111-1111-111111111111",
          "cognito-identity.amazonaws.com:sub": "ap-northeast-1:1234abcd-a123-b234-c345-222222222222"
        }
      }
    }
  },
  "eventSource": "kms.amazonaws.com",
  "eventName": "DescribeKey"
}

上記の通り、Cognito 経由のリクエストには、WebIdFederationData が含まれています。
次に、AccessDenied されたtest_user2 のリクエストに関する CloudTrail ログをみてみます。

(抜粋表示) test_user2 の CloudTrail ログ
{
  "eventVersion": "1.08",
  "userIdentity": {
    "type": "AssumedRole",
    "principalId": "AROA1234567890EXAMPLE:CognitoIdentityCredentials",
    "sessionContext": {
      "sessionIssuer": {
        "arn": "arn:aws:iam::012345678901:role/amplify-react-dev-01234-authRole"
      },
      "webIdFederationData": {
        "federatedProvider": "cognito-identity.amazonaws.com",
        "attributes": {
          "cognito-identity.amazonaws.com:aud": "ap-northeast-1:11111111-1111-1111-1111-111111111111",
-         "cognito-identity.amazonaws.com:sub": "ap-northeast-1:1234abcd-a123-b234-c345-222222222222"
+         "cognito-identity.amazonaws.com:sub": "ap-northeast-1:9876abcd-x000-y111-z222-333333333333"
        }
      }
    }
  },
  "eventSource": "kms.amazonaws.com",
  "eventName": "DescribeKey",
+ "errorCode": "AccessDenied"
}

test_user1 と test_user2 の各リクエストで、cognito-identity.amazonaws.com:sub の値が異なっています。
この値は、Cognito ID プールに紐づく IdentityId であり、キーポリシーでリクエスト元の Cognito ユーザーを識別することができます。

ちなみに、cognito-identity.amazonaws.com:aud は、Cognito ID プール自体の ID になります。
この値は、認証済みユーザー用の IAM ロールにおける信頼ポリシーでも使用されています。
イメージ図の通り、ID プールと IAM ロールがきちんと紐づいている証ですね。

信頼ポリシー例
amplify-react-dev-01234-authRole
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "cognito-identity.amazonaws.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:aud": "ap-northeast-1:11111111-1111-1111-1111-111111111111"
        },
        "ForAnyValue:StringLike": {
          "cognito-identity.amazonaws.com:amr": "authenticated"
        }
      }
    }
  ]
}

おわりに

本記事では、Cognito で認証したユーザーに対して、キーポリシーを使って専用 CMK へアクセス許可する方法をご紹介しました。

CLI では Cognito の挙動が分かりやすく、本記事の執筆を通じて、筆者自身も知識の整理ができました。本記事がどなたかのお役に立てれば、幸いです。

参考情報

  1. 本記事ではCognito ユーザープールで認証したユーザーのことを、Cognito ユーザーと呼んでいます。

  2. 記事用に Action 句で幅広い権限を付与していますが、実開発では権限を絞っています。

  3. IAM ポリシーが AWS KMS の KMS キーへのアクセスをユーザーまたはロールに許可しないようにするにはどうすればよいですか?

  4. 各入力値・実行結果は、記事用に仮値へ変換していますので、ご留意ください。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?