🗝️

【失敗談】KMSキーで「最小権限」に絞ろうと試みるも無惨に諦めた話

に公開

レバテック開発部の松浪です。

先日、EBSボリュームに使用する暗号化キーについて、AWSマネージドキーからカスタマーマネージドキー(KMSキー)への移行を試みました。
目的は権限を絞るためです。AWSマネージドキーは "Principal": {"AWS": "*"} という汎用性が高い反面、AWSアカウント内の誰からでも利用可能な広い権限設定になっています。

KMSキーへ移行する過程で色々なエラーに遭遇しました。そして、移行を断念しました。
 
 

 
  
...で、終わったらブログにならないので、この記事では私が遭遇した3つのエラーと移行を断念した理由を解説します。

はじめに

  • この記事は「キー移行の成功手順」を解説するものではありません 🙇
  • 代わりにKMSキー作成の過程で遭遇した具体的なエラー、そのエラー理由と解消方法を解説します 🙇
  • そして、最終的にKMSキーへ移行しなかった理由を共有する記事です 🙇

なお、インフラのコード管理(IaC)には terraform を利用しています。
https://developer.hashicorp.com/terraform

KMSって何?という方は公式ドキュメントをご覧ください。
https://aws.amazon.com/jp/kms/

エラーその1: MalformedPolicyDocumentException

terraform plan は正常に結果を出力してくれるのに、いざ terraform apply を実行するとこのエラーに遭遇しました。

エラーログ

Error: creating KMS Key: operation error KMS: CreateKey, https response error StatusCode: 400, RequestID: f64ee3a1-1f12-43bb-8211-facd951d9ac9, MalformedPolicyDocumentException: The new key policy will not allow you to update the key policy in the future.

原因

kms:PutKeyPolicy が不足していたため、発生していました。

KMSでは、新しいキーポリシーが作成された後も、将来にわたって更新できる必要があります。
これは自分自身がアクセスできないキーを作成してしまうのを防ぐためのAWSに備わっている対策(仕様)です。

https://docs.aws.amazon.com/ja_jp/kms/latest/APIReference/API_PutKeyPolicy.html#API_PutKeyPolicy_RequestParameters

The key policy must allow the calling principal to make a subsequent PutKeyPolicy request on the KMS key.

と記載があり、 PutKeyPolicy が必要なのがわかります。

エラーログには Error: creating KMS Key: operation error KMS: CreateKey, 〜 とあるので、kms:CreateKey が不足していると誤認しないように注意してください。
kms:CreateKey をキーポリシーに含める必要はありません。

対処

キーポリシーの許可するActionに kms:PutKeyPolicy を追加してエラーを回避できました。

example.tf
resource "aws_kms_key_policy" "example" {
  policy = jsonencode({
    Statement = [
      {
        Action = [
          ...(略),
          "kms:PutKeyPolicy" ## これが必要
        ],
        :

エラーその2: no resource-based policy allows the kms:CreateAlias action

キーが無事に生成されたのに、そのキーに別名(エイリアス)を付けようとしたところでエラーが発生しました。

エラーログ

Error: creating KMS Alias (alias/ebs-default-encryption): operation error KMS: CreateAlias, https response error StatusCode: 400, RequestID: 12345-..., api error AccessDeniedException: User: arn:aws:sts::(account_id):assumed-role/GitHubActions is not authorized to perform: kms:CreateAlias on resource: arn:aws:kms:***:(account_id):key/abcde-... because no resource-based policy allows the kms:CreateAlias action

原因

エイリアスより前にキーポリシーを先に作成されていたため、発生していました。

KMSキー、キーポリシー、エイリアスの3つを同時に作成する際、terraformは次の順番でリソースを作成していました。

  1. aws_kms_key を作成
  2. aws_kms_key_policy を作成
  3. aws_kms_alias を作成 (← ここでエラー)

KMSはキーを作成した直後は、 非常に寛容な「デフォルトキーポリシー」 を適用します。このデフォルトキーポリシーは、KMSに対するさまざまなアクションが許可されている状態です。

https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-default-allow-root-enable-iam

It gives the AWS account that owns the KMS key full access to the KMS key.

と記載があり、KMSに対してあらゆるActionが許容されているのがわかります。

今回、terraformはキーを作成した後、エイリアスを作成するよりも先に、私の定義したカスタムキーポリシーが適用されていました。
結果としてデフォルトキーポリシーは上書きされ、 kms:CreateAlias の許可がなくなってしまった、という訳です。

対処

depends_on を使って、リソース間の依存関係を定義することでエラーを回避できました。

https://developer.hashicorp.com/terraform/language/meta-arguments#depends_on

example.tf
# 1. KMSキー
resource "aws_kms_key" "example" {
  deletion_window_in_days = 30
}

# 2. キーポリシー
resource "aws_kms_key_policy" "example" {
  key_id = aws_kms_key.example.id
  policy = jsonencode({...})

  depends_on = [aws_kms_alias.example] # 3. を作成後、2. を作成する
}

# 3. エイリアス
resource "aws_kms_alias" "example" {
  name          = "alias/ebs-default-encryption"
  target_key_id = aws_kms_key.example.key_id
}

エラーその3: no resource-based policy allows the kms:GetKeyPolicy action

キーが無事に生成されて、エイリアスも付けて、いざAWSコンソール上で確認しようとするもキーにアクセスできず、次のエラーが発生しました。

エラーログ

Error: reading KMS Key (abcde-...) policy: operation error KMS: GetKeyPolicy, https response error StatusCode: 400, RequestID: 12345-..., api error AccessDeniedException: User: arn:aws:sts::(account_id):assumed-role/GitHubActions is not authorized to perform: kms:GetKeyPolicy on resource: arn:aws:kms:***:(account_id):key/abcde-... because no resource-based policy allows the kms:GetKeyPolicy action

原因

kms:GetKeyPolicy が不足していたため、発生していました。

このエラーが厄介なのは、キーの作成やポリシーの適用の時点ではエラーを返さないことです。
「作成はできるけど、二度と中身を見ることができない」という状態に陥りました。

kms:PutKeyPolicy が不足していると、AWSが親切にキーを生成する前にエラーを返してくれます。(エラー1のケース)
しかし、kms:GetKeyPolicy にはそのような事前チェックがありません。

その結果、 terraform apply は成功するが、applyしたキーにアクセスできなくて詰みます。

対処

一度、AWSアカウントのルートユーザでキーポリシーを初期化していただき、
キーポリシーの許可するActionに kms:GetKeyPolicy を追加してエラーを回避できました。

example.tf
resource "aws_kms_key_policy" "example" {
  policy = jsonencode({
    Statement = [
      {
        Action = [
          ...(略),
          "kms:PutKeyPolicy",
          "kms:GetKeyPolicy" ## これが必要
        ],
        :

カスタマーマネージドキーに移行後...

3つのエラーを乗り越え、無事にEBSボリュームのデフォルト暗号化キーをカスタマーマネージドキーに移行しました。
めでたしめでたし。

...と思ったのも束の間、今度はEKSのバージョンアップがノード作成中のエラーで失敗してしまうのと報告を受けました。

https://aws.amazon.com/jp/eks/

原因

EKSに対して、KMSキーを使う権限が与えられていなかったため、発生していました。
これは完全に私の知識不足なのですが、EBSボリュームを作成する可能性があるケースは何も開発者からの操作だけではありません。

EC2のオートスケーリング、EKSのバージョンアップ、RDSをスナップショットから復元、AWS Backupでのバックアップからの復元、など。

つまり、権限を絞るということはどのAWSリソースに何のActionを許容するか事前にしっかり見極めないといけないということです。

戦略的(?)撤退

事前に影響範囲をすべて把握するのは困難で、障害が起きてから初めて気づくようでは運用面でもリスクが高すぎる、と判断してEBSボリュームに使用する暗号化キーの権限を絞ることは中断することしました。
触らぬ神に祟りなし。

では、どうすればよかったか?

AWSアカウント全体の1つの暗号化キーを使うのではなく、アプリケーションごとに暗号化キー(KMSキー)を用意して使うのが良いかなと思います。
そうすれば、権限を絞りやすくなり、万が一設定を誤っても影響はそのアプリケーション内に限定することができます。

おわりに

今回「最小権限の原則」をインフラに適用する際の難しさを改めて実感しました。
もし、KMSキーへ移行して適切な権限制御を実現したノウハウを持つ方がいらっしゃったら是非、弊社に来て助けてください。

レバテック開発部

Discussion