【AWS】Secrets Manager の Secrets をクロスアカウントで取得するための各リソース(Secrets、KMS、IAMロール、リソースポリシー)の設定方法

別アカウントの EC2 から Secrets Manager の値を直接取得する方法について解説します。

必要な設定は大きく以下の 3つのポリシーになります。

【Secretsを所有するアカウント】
1. Secrets Manager のリソースポリシー
2. KMS カスタマーマネージドキーのキーポリシー

【Secretsを利用するアカウント】
3. EC2 用 IAM ロールの IAM ポリシー

構成図

構成としては、下図のようにアカウントA(11111111111111)に Secrets があり、アカウントB(222222222222)がクロスアカウントで Secrets の値を取得します。

構成イメージ

区分アカウント
Secretsを所有する側アカウントA:111111111111
EC2から利用する側アカウントB:222222222222
リージョンap-northeast-1
利用側EC2のIAMロールApplicationRole
Secret名devSecretForEncryption
KMS エイリアス名alias/devKmsForEncryption

EC2 はアクセスキーをサーバー上に保存せず、IAM ロールから一時認証情報を取得して AWS API を呼び出します。IAM ロールを EC2 に割り当てる際の入れ物がインスタンスプロファイルです。

Secretsを所有する側(アカウントA)に必要なリソース

アカウントAには次のリソースが必要です。

  1. Secrets Manager Secret
  2. KMS カスタマーマネージドキー
  3. Secret に設定するリソースポリシー
  4. KMS キーに設定するキーポリシー

重要なのは、クロスアカウント参照では通常の AWS 管理キーである alias/aws/secretsmanager は使用できないことです。その理由は、AWS 管理キー(AWS マネージドキー)は、キーポリシーを編集できないため、クロスアカウントの IAM ロールを許可できないからです。

クロスアカウントで利用する Secret は、キーポリシーが変更可能な KMS カスタマーマネージドキーで暗号化する必要があります。

ちなみに KMS カスタマーマネージドキーは、キーポリシー、Grant、無効化、ローテーションなどを利用者が管理できます。

そもそも Secrets Manager の値は KMS で暗号化する必要がある

Secrets Manager に保存する値は必ず暗号化されます。暗号化しない状態で保存することはできません。

Secrets Manager は、AWS Key Management Service (AWS KMS) による暗号化を使用して、保存されているデータの機密性を保護します。AWS KMS は、多くの AWS サービスで使用されるキーの保存および暗号化サービスを提供します。Secrets Manager 内のすべてのシークレットは、一意のデータ キーで暗号化されます。各データ キーは KMS キーによって保護されます。アカウントの Secrets Manager AWS マネージド キーを使用したデフォルトの暗号化を使用することも、AWS KMS で独自のカスタマー マネージド キーを作成することもできます。カスタマー マネージド キーを使用すると、KMS キーのアクティビティに対するよりきめ細かな承認制御が可能になります。

実際は Secrets Manager の値は暗号化されている

AWS 管理画面から Secrets の値を閲覧すると当たり前ですが平文で人間が確認できます。しかし実際は、値は KMS で暗号化されて保存されています。

ブラウザ
  ↓ 「シークレットの値を取得」
Secrets Manager
  ↓ KMSへDecryptを依頼
KMS
  ↓ 復号したデータキーを返す
Secrets Manager
  ↓ データキーでSecretを復号
管理画面に平文を表示

Secrets Manager のリソースポリシー(アカウントA)

Secret 自身に、利用側アカウントBの EC2 用 IAM ロールを許可します。

Secret 側のリソースポリシー

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCrossAccountGetSecretValue",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::222222222222:role/ApplicationRole"
      },
      "Action": [
        "secretsmanager:GetSecretValue"
      ],
      "Resource": "*"
    }
  ]
}

Secret に直接アタッチするポリシーなので、Resource* で問題ありません。この * は、ポリシーをアタッチした当該 Secret を意味する形になります。

Secret の情報も取得する場合は、次の権限を追加できます。

"Action": [
  "secretsmanager:GetSecretValue",
  "secretsmanager:DescribeSecret"
]

ただし、Secretの値を取得するだけなら基本的には次で足ります。

secretsmanager:GetSecretValue

KMSキーのキーポリシー(アカウントA)

Secret を暗号化している KMS キーにも、利用側の ApplicationRole を許可します。Secrets Managerは、Secretを取得している利用者の権限を使って、利用者に代わって KMS 操作を行います。そのため、KMS キーポリシーや IAM ポリシーでは、利用側の ApplicationRolekms:Decrypt を許可します。

最小構成

{
  "Sid": "AllowCrossAccountSecretDecryption",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::222222222222:role/ApplicationRole"
  },
  "Action": [
    "kms:Decrypt",
    "kms:DescribeKey"
  ],
  "Resource": "*"
}

KMS キーポリシーの Statement です。KMS キーポリシーでは、Resource: "*" はそのポリシーが設定されている KMS キー自身を指します。

より安全なキーポリシー(アカウントA)

Secret の取得を経由した場合だけ KMS キーを使えるように、kms:ViaService を付けるのがおすすめです。

{
  "Sid": "AllowCrossAccountSecretDecryption",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::222222222222:role/ApplicationRole"
  },
  "Action": [
    "kms:Decrypt",
    "kms:DescribeKey"
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:ViaService": "secretsmanager.ap-northeast-1.amazonaws.com"
    }
  }
}

これにより、EC2 上のアプリケーションが KMS のDecrypt API を直接自由に呼ぶのではなく、Secrets Manager 経由の復号に限定できます。

条件kms:ViaServiceキーは、KMS キーの使用を、指定された AWS サービスからのリクエストに制限します。この条件キーは、フォワード アクセス セッションにのみ適用されます。各条件キーkms:ViaServiceには、1 つ以上のサービスを指定できます 。操作はKMS キー リソース操作、つまり特定の KMS キーに対して承認されている操作である必要があります。KMS キー リソース操作を識別するには、[アクションとリソース] テーブルで、操作の列KMS keyResourcesの値があるかどうかを確認します。

Secrets Manager 経由の操作だけに KMS キー利用を限定するため、kms:ViaService 条件を利用できます。

{
  "Sid": "AllowCrossAccountSpecificSecretDecryption",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::222222222222:role/ApplicationRole"
  },
  "Action": [
    "kms:Decrypt",
    "kms:DescribeKey"
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:ViaService": "secretsmanager.ap-northeast-1.amazonaws.com",
      "kms:EncryptionContext:SecretARN": "arn:aws:secretsmanager:ap-northeast-1:111111111111:secret:prod/secretForEncryption-AbCdEf"
    }
  }
}

(完全版)KMSキーのキーポリシー(アカウントA)

policy = jsonencode({
  Version = "2012-10-17"

  Statement = [
    {
      Sid    = "EnableIAMUserPermissions"
      Effect = "Allow"

      Principal = {
        AWS = "arn:aws:iam::111111111111:root"
      }

      Action   = "kms:*"
      Resource = "*"
    },
    {
      Sid    = "AllowKeyAdministration"
      Effect = "Allow"

      Principal = {
        AWS = "arn:aws:iam::111111111111:role/KmsAdministratorRole"
      }

      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    = "AllowCrossAccountSecretDecryption"
      Effect = "Allow"

      Principal = {
        AWS = "arn:aws:iam::222222222222:role/ApplicationRole"
      }

      Action = [
        "kms:Decrypt",
        "kms:DescribeKey"
      ]

      Resource = "*"

      Condition = {
        StringEquals = {
          "kms:ViaService" = "secretsmanager.ap-northeast-1.amazonaws.com"
        }

        StringLike = {
          "kms:EncryptionContext:SecretARN" = "arn:aws:secretsmanager:ap-northeast-1:111111111111:secret:devSecretForEncryption-*"
        }
      }
    }
  ]
})

Secretまで限定する構成(アカウントA)

同じ KMS キーで複数の Secret を暗号化している場合は、Encryption Context を使って、特定の Secret だけに限定できます。

{
  "Sid": "AllowCrossAccountSpecificSecretDecryption",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::222222222222:role/ApplicationRole"
  },
  "Action": [
    "kms:Decrypt",
    "kms:DescribeKey"
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:ViaService": "secretsmanager.ap-northeast-1.amazonaws.com",
      "kms:EncryptionContext:SecretARN": "arn:aws:secretsmanager:ap-northeast-1:111111111111:secret:devSecretForEncryption-AbCdEf"
    }
  }
}

Secrets ManagerがKMSを呼び出す際には、Encryption Contextとして次の情報が渡されます。

SecretARN
SecretVersionId

したがって、kms:EncryptionContext:SecretARN で特定の Secret に限定できます。

ただし、Secret ARN の末尾には Secrets Manager が付与するランダムな6文字が含まれるので、実際の完全なARNを設定してください。

利用する側(アカウントB)に必要なリソース

アカウントBには次のリソースが必要です。

  1. EC2 インスタンス
  2. EC2 用 IAM ロール
  3. IAM ロールの信頼ポリシー
  4. IAM ロールに付ける権限ポリシー
  5. インスタンスプロファイル
  6. 必要に応じて VPC エンドポイントまたはインターネットへの経路

EC2用IAMロールの信頼ポリシー(アカウントB)

EC2 が IAM ロールを利用できるようにします。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowEC2AssumeRole",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

ここで指定する Principal は、Secrets を所有するアカウントではなく、EC2 サービスです。

"Principal": {
  "Service": "ec2.amazonaws.com"
}

つまり、この信頼ポリシーは、この IAM ロールを EC2 インスタンスが使用してよいという設定です。

EC2用IAMロールの権限ポリシー(アカウントB)

利用側アカウントBの ApplicationRole に、次の IAM ポリシーを付けます。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowGetCrossAccountSecret",
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue"
      ],
      "Resource": "arn:aws:secretsmanager:ap-northeast-1:111111111111:secret:devSecretForEncryption-AbCdEf"
    },
    {
      "Sid": "AllowDecryptCrossAccountSecret",
      "Effect": "Allow",
      "Action": [
        "kms:Decrypt"
      ],
      "Resource": "arn:aws:kms:ap-northeast-1:111111111111:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
      "Condition": {
        "StringEquals": {
          "kms:ViaService": "secretsmanager.ap-northeast-1.amazonaws.com"
        }
      }
    }
  ]
}

ポイントは、利用側 IAM ロールにも次の両方が必要なことです。

secretsmanager:GetSecretValue
kms:Decrypt

Secrets 側のリソースポリシーと KMS キーポリシーだけでは不十分です。クロスアカウントの場合、利用側 IAM ロールのアイデンティティポリシーにも明示的な許可が必要です。

DescribeSecret も使用する場合

アプリケーションや AWS SDK の処理内容によっては、Secretのメタデータを確認するために DescribeSecret を使用する場合があります。例えば、Secrets Manager Agent や内部で利用される AWS SDK が、Secret の状態やバージョンなどのメタデータ確認のために DescribeSecret を呼ぶ可能性があります。

その場合は次のようにします。

{
  "Sid": "AllowReadCrossAccountSecret",
  "Effect": "Allow",
  "Action": [
    "secretsmanager:GetSecretValue",
    "secretsmanager:DescribeSecret"
  ],
  "Resource": "arn:aws:secretsmanager:ap-northeast-1:111111111111:secret:devSecretForEncryption-AbCdEf"
}

単純な GetSecretValue 呼び出しだけなら、DescribeSecret必須ではありません。

DescribeSecretは何をする権限か

GetSecretValue は、文字どおり Secret の値を取得します。

GetSecretValue
→ 実際のSecretStringやSecretBinaryを取得

一方、DescribeSecret は Secret の値ではなく、Secret の設定情報を取得します。

DescribeSecret
→ Secret名
→ ARN
→ 使用しているKMSキー
→ バージョン情報
→ ローテーション設定
→ タグなど

Secretの中身そのものは返しません。

インスタンスプロファイル

IAM ロールを EC2 にアタッチするには、インスタンスプロファイルが必要です。

関係は次のとおりです。

EC2
  └ インスタンスプロファイル
       └ IAMロール
            └ IAMポリシー

IAMコンソールから「AWSサービス・EC2用」としてロールを作成した場合、通常はインスタンスプロファイルも自動的に作成されます。

TerraformやAWS CLIで作る場合は、IAMロールとインスタンスプロファイルを別々に作成します。

Terraform で表す場合

Secretsを所有する側(アカウントA)

KMSキー

data "aws_caller_identity" "current" {}

locals {
  # KMSキーを管理する、Secrets所有側アカウントのIAMロール
  kms_administrator_role_arn = "arn:aws:iam::111111111111:role/KmsAdministratorRole"

  # 別アカウントのEC2に付与するIAMロール
  application_role_arn = "arn:aws:iam::222222222222:role/ApplicationRole"

  # Secrets ManagerのSecret ARN
  #
  # Secret ARNの末尾には、AWSによる6文字のランダム文字列が付与されるため、
  # Encryption ContextではStringLikeとワイルドカードを使用しています。
  secret_arn_pattern = "arn:aws:secretsmanager:ap-northeast-1:111111111111:secret:devSecretForEncryption-*"
}

resource "aws_kms_key" "dev_secrets" {
  description = "KMS key for devSecretForEncryption cross-account access"

  enable_key_rotation     = true
  deletion_window_in_days = 30

  policy = jsonencode({
    Version = "2012-10-17"

    Statement = [
      # ------------------------------------------------------------
      # 1. Secrets所有側アカウントのIAMポリシーを有効にする
      # ------------------------------------------------------------
      {
        Sid    = "EnableIAMUserPermissions"
        Effect = "Allow"

        Principal = {
          AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
        }

        Action = "kms:*"

        Resource = "*"
      },

      # ------------------------------------------------------------
      # 2. Secrets所有側のKMS管理者にキー管理を許可する
      # ------------------------------------------------------------
      {
        Sid    = "AllowKeyAdministration"
        Effect = "Allow"

        Principal = {
          AWS = local.kms_administrator_role_arn
        }

        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 = "*"
      },

      # ------------------------------------------------------------
      # 3. 利用側アカウントのEC2用IAMロールに復号を許可する
      # ------------------------------------------------------------
      {
        Sid    = "AllowCrossAccountSecretDecryption"
        Effect = "Allow"

        Principal = {
          AWS = local.application_role_arn
        }

        Action = [
          "kms:Decrypt",
          "kms:DescribeKey"
        ]

        Resource = "*"

        Condition = {
          StringEquals = {
            "kms:ViaService" = "secretsmanager.ap-northeast-1.amazonaws.com"
          }

          StringLike = {
            "kms:EncryptionContext:SecretARN" = local.secret_arn_pattern
          }
        }
      }
    ]
  })

  tags = {
    Name        = "devKmsForEncryption"
    Environment = "dev"
    Service     = "secrets-manager"
    Purpose     = "cross-account-secret-encryption"
  }
}

resource "aws_kms_alias" "dev_secrets" {
  name          = "alias/devKmsForEncryption"
  target_key_id = aws_kms_key.dev_secrets.key_id
}

Secret

resource "aws_secretsmanager_secret" "encryption_secret" {
  name       = "devSecretForEncryption"
  kms_key_id = aws_kms_key.secret_key.arn
}

Secretのリソースポリシー

resource "aws_secretsmanager_secret_policy" "cross_account" {
  secret_arn = aws_secretsmanager_secret.encryption_secret.arn

  block_public_policy = true

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "AllowCrossAccountGetSecretValue"
        Effect = "Allow"

        Principal = {
          AWS = "arn:aws:iam::222222222222:role/ApplicationRole"
        }

        Action = [
          "secretsmanager:GetSecretValue"
        ]

        Resource = "*"
      }
    ]
  })
}

block_public_policy = true を設定しておくと、誤って広範囲な公開ポリシーを設定するリスクを抑えられます。AWSも BlockPublicPolicy の利用を案内しているのでお勧めです。

Secretsを利用する側(アカウントB)

IAMロール

resource "aws_iam_role" "application_role" {
  name = "ApplicationRole"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "AllowEC2AssumeRole"
        Effect = "Allow"

        Principal = {
          Service = "ec2.amazonaws.com"
        }

        Action = "sts:AssumeRole"
      }
    ]
  })
}

IAMポリシー

resource "aws_iam_role_policy" "get_cross_account_secret" {
  name = "GetCrossAccountSecret"
  role = aws_iam_role.application_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "AllowGetCrossAccountSecret"
        Effect = "Allow"

        Action = [
          "secretsmanager:GetSecretValue"
        ]

        Resource = [
          "arn:aws:secretsmanager:ap-northeast-1:111111111111:secret:devSecretForEncryption-AbCdEf"
        ]
      },
      {
        Sid    = "AllowDecryptCrossAccountSecret"
        Effect = "Allow"

        Action = [
          "kms:Decrypt"
        ]

        Resource = [
          "arn:aws:kms:ap-northeast-1:111111111111:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
        ]

        Condition = {
          StringEquals = {
            "kms:ViaService" = "secretsmanager.ap-northeast-1.amazonaws.com"
          }
        }
      }
    ]
  })
}

インスタンスプロファイル

resource "aws_iam_instance_profile" "application_profile" {
  name = "ApplicationInstanceProfile"
  role = aws_iam_role.application_role.name
}

EC2への設定

resource "aws_instance" "application" {
  ami           = "ami-xxxxxxxxxxxxxxxxx"
  instance_type = "t3.micro"

  iam_instance_profile = aws_iam_instance_profile.application_profile.name
}

最終的に必要な許可関係

Secretのリソースポリシー
    利用側IAMロール → GetSecretValueを許可

KMSキーポリシー
    利用側IAMロール → Decryptを許可

利用側IAMロールのIAMポリシー
    対象Secret → GetSecretValueを許可
    対象KMSキー → Decryptを許可

つまり、利用側IAMロールから見ると、

Secret側から許可されている
かつ
KMS側から許可されている
かつ
自分自身のIAMポリシーでも許可されている
という3条件がそろって、初めてEC2からSecretを取得できます。

KMS カスタマーマネージドキーのエイリアスについて

KMSのカスタマーマネージドキーには、厳密には一般的なリソースのような 「名前」項目はありません

主に次の識別情報があります。

項目意味
キーID1234abcd-12ab-34cd-56ef-1234567890abAWSが自動生成する一意のID
キーARNarn:aws:kms:ap-northeast-1:111111111111:key/1234…AWS全体でキーを一意に示す完全な識別子
エイリアスalias/devKmsForEncryption人が分かりやすいように付ける任意の別名
DescriptionKMS key for dev Secrets Managerキーの用途を説明する文章
NameタグdevKmsForEncryption管理用タグ。KMS固有の名前ではない

エイリアスは、複雑なキーIDの代わりに使える、人間向けの名前です。

キーID
1234abcd-12ab-34cd-56ef-1234567890ab

エイリアス
alias/devKmsForEncryption

関係を図にすると次のようになります。

KMSキー本体
├─ キーID      :AWSが自動生成
├─ キーARN     :AWSが自動生成
├─ エイリアス  :利用者が設定する呼び名
├─ Description :利用者が設定する説明
└─ Nameタグ    :利用者が設定する管理用ラベル