AWS CDKで独自のIAMポリシーを作っていてデプロイ時に失敗する場合の解決方法
はじめに
CX事業本部IoT事業部の佐藤智樹です。
今回は自分がAWS CDKでIAMユーザやロールのために条件付けした独自のIAMポリシーを作っていて、AWS CDKのデプロイ時に失敗する場合の解決方法を紹介します。
AWS CDKでTypeScriptを使ってリソースを書いている場合は型補完である程度リソースを簡単に記述できますが、IAMポリシーに関してはうまく型補完が効かずに実行した際に失敗する場合があります。そこで実行時に失敗した後にどのようにコードを直していくのかを紹介します。AWS CDKをメインにコードを書いている方で、できればリソースを全てAWS CDKで書きたいという方には参考になるかと思います。
注意点として予めCloudTrailは有効化しておいてください。またCloudFormationでIAMポリシーを作成されている方にはほぼ既出の内容かもしれませんがご了承ください。
失敗して修正してみる
例えば以下のAWS CDKのコードでIAMポリシーを作成するとします。例はRetoolというSaaSからIAMロールにスイッチさせる際に、許可したIPからだけ接続させるものです。
export class HogeStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); ... new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: ["sts:AssumeRole"], resources: ["*"], conditions: [ { IpAddress: { "aws:SourceIp": [ // See: https://docs.retool.com/docs/connecting-your-database "13.80.4.170/32", ... ], }, }, ], }), ... }
このポリシーを作成しようとすると、cdk synth
コマンドでCloudFormationテンプレートの作成まではうまく行きます。しかし、cdk deploy
すると以下のようにエラーが発生します。
... 9:41:25 | CREATE_FAILED | AWS::IAM::Policy | hogeassumerolepolicy0XXXXXXX Syntax errors in policy. (Service: AmazonIdentityManagement; Status Code: 400; Error Code: MalformedPolicyDocument; Request ID: xxxxxxxx-xxxx-4a f2-8375-xxxxxxxxxxxx; Proxy: null) ...
こういう場合どうすれば良いでしょうか。AWSのドキュメントを確認すると以下のような情報がヒットします。
上記のリンクをみると以下の作業で問題点が把握できそうです。
- CloudTrailの確認
- IAMポリシーの検証
次節からは上記のステップで修正すべき部分を探します。
CloudTrailの確認
AWSコンソールにログインし、CloudTrailから「イベント履歴」を開きます。以下の画像のようにプルダウンメニューで「リソースタイプ」、検索に「AWS:IAM:Policy」を入力します。実行した期間に合わせて画面右側の時間を調整し検索をかけます。すると「PutRolePolicy」が上がってくるので確認してみます。(実行後すぐに確認すると記録されてないことがあるので注意して下さい)
イベントを開くと以下のようなJSONが表示されます。接続元のIPアドレスの一覧は省略しています。
{ "eventVersion": "1.08", ... "errorCode": "MalformedPolicyDocumentException", "errorMessage": "Syntax errors in policy.", "requestParameters": { "roleName": "hoge-role", "policyName": "hoge-policy", "policyDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Condition\":{\"0\":{\"IpAddress\":{\"aws:SourceIp\":[\"13.80.4.170/32\",...]}}},\"Action\":\"sts:AssumeRole\",\"Resource\":\"*\",\"Effect\":\"Allow\"}]}" } ... }
生成されたIAMポリシーがエラーになっていることがわかります。次はこのJSONを使ってIAMポリシーの検証を行います。その前にJSONをIAMポリシー検証で読める形に修正します。"policyDocument"の値をコピーして、任意のエディタ(今回はVSCodeを使用します)に貼り付けて「\」を削除します。そのまま拡張子jsonで保存すると以下のように整形されるのでこちらを使用します。
{ "Version": "2012-10-17", "Statement": [ { "Condition": { "0": { "IpAddress": { "aws:SourceIp": [ "13.80.4.170/32", ... ] } } }, "Action": "sts:AssumeRole", "Resource": "*", "Effect": "Allow" } ] }
IAMポリシーの検証
上記で作成したIAMポリシーのJSONを検証していきます。再びAWSコンソールに戻ってIAMの画面を開き、画面左のメニューから「ポリシー」を選択し「ポリシーを作成」ボタンを押下します。押下した後タブで「JSON」を選択するとポリシーをJSON形式で書きバリデーションできる画面が表示されるので先ほど作成したJSONを貼り付けます。すると以下のようにエラー項目が分かります。
検証結果を元にコードを修正
検証結果をもとにIAMポリシーのJSONを修正してみます。エラーを見ると条件演算士には"0"が入らないみたいです。conditionの定義を確認してみます。
上記のリンク先を見ると条件演算士には直接IpAddress
を指定するようです。項目名がconditions
と複数形なので配列かと勘違いしたのですが、直接指定が必要でした。JSONを修正すると以下のようにエラーも無くなりました。
上記のようなJSONを生成できるようにAWS CDKのコードも修正します。
export class HogeStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); ... new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: ["sts:AssumeRole"], resources: ["*"], conditions:{ IpAddress: { "aws:SourceIp": [ // See: https://docs.retool.com/docs/connecting-your-database "13.80.4.170/32", ... ], }, }, }), ... }
上記のように修正すると問題なくデプロイでき、IAMポリシーが生成されます。後はポリシー自体に正しく権限が与えられているか検証するだけです。
おまけ:なぜエラーなのに気づけないのか
なぜ上記のエラーはAWS CDKでは気づけないのでしょうか。今回問題だったaws-iam Construct
のPolicyStatement
のconditions
の型を確認してみます。
... /** * Conditions to add to the statement. * * @default - no condition * @stability stable */ readonly conditions?: { [key: string]: any; }; ...
conditionsの型が[key: string]: any
になっているため配列を許容する型になっています。これが原因で型上ではエラーが発生せずコーディング時は気づけないようです。ここからは推測ですが、Conditionの形式が条件演算士にプラスして、以下のグローバル条件キーも入り複雑なためany型で実装が止まっているのかもしれません。
おまけ2:JSONファイルを取り込む方針に変える
AWS CDKではポリシードキュメントを記述しやすいように機能がいくつかありますが、上記のようにデプロイするまで気づけない/実装方法が分からない場合があります。いっそ無理にAWS CDKのコードで表現せず以下の記事で触れられているようにPolicyDocument.fromJson()
を使用して、JSON形式のIAMポリシーを取り込むように書いた方が既存資産を有利に活かせて楽かもしれないです。
所感
どの程度需要があるか分からないですが、何回か悩んだ気がしたので記事にしました。泥臭いやり方なのでもっとスマートにチェックできるぜ!という方がいればTwitter(@tmk2154)でご連絡いただけるとありがたいです。 どなたかの参考になれば幸いです。