暗号化されたEBSボリュームを作成できずハマった話

2020.02.04

こんにちは。AWS事業本部の中川です。

暗号化されたEBSボリュームを作成できない事象に遭遇しました。
権限を絞りすぎたことで、リソース作成時に権限不足である旨のエラーが表示され失敗するケースはよくあるかと思います。 しかし、今回遭遇した事象は、作成時にエラーは表示されていないのにリソースが作成されないというもので、特殊なケースでした。
エラーが表示されず原因特定にハマったので、備忘録として残します。

前提

以下のようなポリシーが付与されたIAMユーザーを使用しています。(実際にはReadOnlyAccessのポリシーが付与されていたり、他にConditionがあります。)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateVolume"
            ],
            "Resource": [
                "arn:aws:ec2:*:*:volume/*"
            ]
        },
        {
            "Effect": "Deny",
            "Action": "*",
            "Resource": "*",
            "Condition": {
                "NotIpAddress": {
                    "aws:SourceIp": [
                        "x.x.x.x"
                    ]
                }
            }
        }
    ]
}

事象

暗号化のチェックボックスを有効にして、カスタマーマスターキー(以下CMK)はデフォルトを選択し、ボリュームを作成します。

「ボリュームは正常に作成されました」と表示され、EBS ボリュームが作成されたように見えます。ボリュームIDをクリックして、一覧の画面に戻ります。

しかし、一覧でボリュームIDと一致するボリュームが表示されません。右上の更新ボタンをクリックしても表示されず、ボリュームは作成されていないことがわかります。

原因

aws:SourceIpで接続IPアドレスの制限をしながら、EBSの暗号化(EBSからKMSの呼び出し)をしていたことが原因でした。

aws:SourceIpのドキュメントを見ると以下の記述があります。

aws:SourceIp 条件キーをポリシーで使用して、プリンシパルが指定された IP 範囲内からのみリクエストを行うことを許可できます。ただし、このポリシーは、ユーザーに代わって呼び出しを行う AWS のサービスへのアクセスを拒否します。たとえば、AWS CloudFormation がサービスロールを使用して Amazon EC2 を呼び出してインスタンスを停止するとします。この場合、ターゲットサービス (Amazon EC2) が呼び出し元のサービス (AWS CloudFormation) の IP アドレスを認識するため、リクエストは拒否されます。

aws:SourceIpは、「ユーザーに代わって呼び出しを行うAWSサービスへのアクセスを拒否する」ことが仕様のようです。 EBSボリュームの暗号化では、EBSがKMSにリクエストを送信して、暗号化で使用するをCMKを指定しています。 つまり、EBSの暗号化でKMSを呼び出すリクエストは、aws:SourceIpによって拒否されていたことがわかりました。

回避策

本事象の回避策は2つあります。

  1. aws:SourceIpを一時的に使用しないようにする(または条件から除いてしまう)
  2. kms:ViaServiceを使用して、CMKを呼び出していないときを条件として追加する

2.は呼び出されるAWSサービスがKMSの場合でのみ有効な回避策です。2.を使う場合、前提のポリシーを以下のように変更します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateVolume"
            ],
            "Resource": [
                "arn:aws:ec2:*:*:volume/*"
            ]
        },
        {
            "Effect": "Deny",
            "Action": "*",
            "Resource": "*",
            "Condition": {
                "Null": {
                    "kms:ViaService": "true"
                },
                "NotIpAddress": {
                    "aws:SourceIp": [
                        "x.x.x.x"
                    ]
                }
            }
        }
    ]
}

kms:ViaServiceは、CMKの使用を指定のAWSサービスからのリクエストに制限する条件キーです。
Nullは、条件キーの存在を確認する条件演算子です。Nullで{"kms:ViaService": "true"}と指定すると、「kms:ViaServiceが存在しない(≒CMKを呼び出していない)」という条件になります。
これを接続元IPアドレスを制限する条件に追加することで、「CMKを呼び出していない かつ 接続元IPを制限する」という条件になります。

さいごに

aws:SourceIpで接続元IPアドレスを制限すると、ユーザーに代わって呼び出しを行うAWSのサービスへのアクセスは拒否されることがわかりました。 KMS以外のサービスを呼び出す際は、aws:SourceIpを条件から外さなくてはならないので、接続元IPを制限をする際には気をつける必要があると思いました。

この記事がどなたかのお役に立てたら幸いです。

参考