Amazon Web Services ブログ

IAM アクセス許可境界によるセキュアな CDK アプリケーションのデプロイ

AWS Cloud Development Kit (CDK) はクラウドリソースの作成に一般的なプログラミング言語を使えるようにすることで、クラウド上での開発を加速します。この速度の利点を生かすためには、アクセス許可やセキュリティ制御が開発速度を低下させないような環境が必要です。しかし厳格に管理された環境では、そうしたことが必ずしも保証されているわけではありません。一方で懸念されるのは、開発者が AWS Identity and Access Management (IAM) エンティティ (ユーザーやロールなど) を作成する権限を持つ場合です。この場合、権限の昇格が可能になってしまい、IAM エンティティの作成者である開発者よりも広範なアクセス許可を持つエンティティが作成できてしまうおそれがあります。このような課題は一般的に、IAM エンティティのアクセス許可境界を使うことで解決できます。本ブログではこのアクセス許可境界を CDK アプリケーション開発に適用する方法について説明し、セキュリティを確保しながらスピーディな開発を実現します。

カスタムアクセス許可境界を CDK アプリケーションのデプロイに適用する

CDK アプリケーションをデプロイする際、ユーザーに代わって操作を実行するための AWS CloudFormation サービスロールが利用されます。この IAM ロールは、ブートストラップフェーズAWS CDK コマンドラインインターフェース (CLI) によって作成されます。このロールは、開発者に代わって CloudFormation がデプロイを実行するために必要な操作を許可する必要がありますが、組織のコンプライアンスやセキュリティの目標を損なわないようにもする必要があります。開発者が IAM エンティティを作成し、そこにアクセス許可を割り当てる必要がある場合、この作業は複雑な結果を生むことがあります。開発者が割り当てたアクセス許可が、開発者が持つ既存のアクセス許可を超えることで権限昇格がなされる可能性があるためです。これらのエンティティの作成を許可しないことは、この問題を解決する 1 つの方法です。しかし、その場合は、開発者が管理者に新規のリソースを都度要求しなければならず、スピーディな開発に対して大きな障害となります。セキュリティを重視した環境では、スタック内の各 AWS Lambda 関数など、個別のユースケースごとに個別の IAM ロールを作成することが考えられるため、この問題はより困難になります。この方法ではなく、IAM アクセス許可境界を使えば、次の 2 つのメリットが得られます。1 つ目は、すべてのアクションがユーザーの持つアクセス許可とアクセス許可境界の重なり合う部分に収まることが保証されます。2 つ目は、作成される IAM エンティティにも同じアクセス許可境界が適用されることが保証されます。これにより、開発者による IAM エンティティの作成を制限することなく、権限昇格の経路をブロックできます。最新バージョンの AWS CLI では、ブートストラップコマンドの実行時に自動的にこれらのアクセス許可境界をサービスロールに適用できるだけでなく、CDK スタックで作成された IAM エンティティにもアクセス許可境界を設定できます。

CDK でアクセス許可境界を使用するには、まず IAM ポリシーを作成し、それをアクセス許可境界として使用します。このポリシーでは、CDK アプリケーションがデプロイ時と運用時に開発者に代わって実行できるアクションセットの上限を定義する必要があります。この手順は通常、アカウントのセキュリティを担当する管理者が実行し、適切なアクセス許可境界とコントロールを設定します。作成後、このポリシーの名前を bootstrap コマンドに指定します。以下の例では、IAM ポリシー「developer-policy」を使用して、コマンドの利用方法を説明しています。

cdk bootstrap --custom-permissions-boundary developer-policy

このコマンドを実行すると、新しい bootstrap スタックが作成され (または既存のスタックが更新されて)、実行ロールにこのアクセス許可境界が適用されます。次に、作成されるすべての IAM エンティティにも同じアクセス許可境界が適用されるようにできます。これは、CDK コンテキスト変数を使用するか、それらのリソースの permissionBoundary 属性を設定することで実現できます。この機能の詳細を説明するため、実際のユースケースを想定したシナリオとして、開発者の AWS Config サービスの利用を制限する方法を例として説明します。

AWS CDK CLI のインストールまたはアップグレード

最初に、AWS CDK CLI ツールの最新バージョンがインストールされていることを確認してください。ドキュメントの指示に従ってこれを完了してください。この新機能を利用するには、バージョン 2.54.0 以上が必要です。インストールされているバージョンを確認するには、次のコマンドを実行してください。

cdk --version

ポリシーの作成

最初に、新しい IAM ポリシーを作成しましょう。以下は、このサンプルで使用するアクセス許可ポリシーを作成する CloudFormation テンプレートです。この場合は AWS CLI で直接デプロイできますが、CloudFormation Stack Sets のようなメカニズムを使えば、自動化して大規模に実行することも可能です。このテンプレートには、次のポリシーステートメントが含まれています。

  1. デフォルトですべてのアクションを許可します。これにより、選択したアクションを特定して拒否できます。ただし、独自のポリシーを作成する際は、アクション許可/拒否のアプローチを慎重に検討する必要があります。
  2. 「developer-policy」のアクセス許可境界が使用されていない場合、ユーザーまたはロールの作成を拒否します。また、既存のエンティティへのアクセス許可境界の割り当ても、「developer-policy」のみを許可するように制限します。これにより、ポリシーを超えた権限を持つエンティティの作成や変更を防ぐことができます。
  3. そのポリシー自体の変更を拒否します。これにより、開発者がその境界内で操作できる範囲を変更できなくなります。
  4. ユーザーやロールからアクセス許可境界を削除する機能を拒否します。
  5. AWS Config サービスに対するすべてのアクションを拒否します。

項目 2、3、4 は、アクセス許可境界が正常に機能するための下準備です。アクセス許可境界が削除、改ざん、回避されることを防ぐ制御機構です。このポリシーの中心は、項目 1 と 5 であり、ここでは特定のアクションを拒否する (許可リストアプローチではなく、拒否リストを用いたアプローチ) 以外のすべてを許可しています。

Resources:
  PermissionsBoundary:
    Type: AWS::IAM::ManagedPolicy 
    Properties:
      PolicyDocument:
        Statement:
          # ----- Begin base policy ---------------
          # If permission boundaries do not have an explicit allow 
          # then the effect is deny 
          - Sid: ExplicitAllowAll 
            Action: "*"
            Effect: Allow 
            Resource: "*"
          # Default permissions to prevent privilege escalation 
          - Sid: DenyAccessIfRequiredPermBoundaryIsNotBeingApplied 
            Action:
              - iam:CreateUser 
              - iam:CreateRole 
              - iam:PutRolePermissionsBoundary 
              - iam:PutUserPermissionsBoundary 
            Condition:
              StringNotEquals:
                iam:PermissionsBoundary:
                  Fn::Sub: arn:${ AWS::Partition }:iam::${ AWS::AccountId }:policy/developer-policy 
            Effect: Deny 
            Resource: "*"
          - Sid: DenyPermBoundaryIAMPolicyAlteration 
            Action:
              - iam:CreatePolicyVersion 
              - iam:DeletePolicy 
              - iam:DeletePolicyVersion 
              - iam:SetDefaultPolicyVersion 
            Effect: Deny 
            Resource:
              Fn::Sub: arn:${ AWS::Partition }:iam::${ AWS::AccountId }:policy/developer-policy 
          - Sid: DenyRemovalOfPermBoundaryFromAnyUserOrRole 
            Action: 
              - iam:DeleteUserPermissionsBoundary 
              - iam:DeleteRolePermissionsBoundary 
            Effect: Deny 
            Resource: "*"
          # ----- End base policy ---------------
          # -- Begin Custom Organization Policy --
          - Sid: DenyModifyingConfig 
            Effect: Deny 
            Action: config:*
            Resource: "*"
          # -- End Custom Organization Policy --
        Version: "2012-10-17"
      Description: "Bootstrap Permission Boundary"
      ManagedPolicyName: developer-policy 
      Path: /

上の内容を developer-policy.yaml としてローカルに保存し、AWS CLI の CloudFormation コマンドでデプロイできます。

aws cloudformation create-stack --stack-name DeveloperPolicy \ 
        --template-body file://developer-policy.yaml \ 
        --capabilities CAPABILITY_NAMED_IAM

ポリシーをテストするためのスタックの作成

始めるにあたり、アクセス許可境界の動作をテストして観察するために使用する新しい CDK アプリケーションを作成します。次のコマンドを実行して、TypeScript CDK アプリケーションを含む新しいディレクトリを作成してください。

mkdir DevUsers && cd DevUsers 
cdk init --language typescript

まず、cdk bootstrap コマンドを使用して CDK ブートストラップスタックをデプロイする必要があります。最初はアクセス許可境界を適用しないでください。後で追加し、デプロイの動作がどのように変化するかを確認します。bootstrap コマンドは --cloudformation-execution-policies 引数を使用していないため、デフォルトで arn:aws:iam::aws:policy/AdministratorAccess が適用されます。つまり、アクセス許可境界が適用されるまで、CloudFormation がアカウントに対する完全なアクセス許可を持つことになります。

cdk bootstrap

コマンドが実行されたら、アクセス許可境界が適用される前に問題なくこれが機能することを確認するために、アプリケーションに AWS Config Rule を作成してください。ファイル lib/dev_users-stack.ts を開き、その内容を以下のサンプルのように編集してください。


 import * as cdk from 'aws-cdk-lib';
 import { ManagedRule, ManagedRuleIdentifiers } from 'aws-cdk-lib/aws-config';
 import { Construct } from "constructs";

 export class DevUsersStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props ?: cdk.StackProps) {
    super(scope, id, props);

    new ManagedRule(this, 'AccessKeysRotated', {
      configRuleName: 'access-keys-policy',
      identifier: ManagedRuleIdentifiers.ACCESS_KEYS_ROTATED,
      inputParameters: {
        maxAccessKeyAge: 60, // default is 90 days 
      },
    });
  }
}

次に、cdk deploy コマンドを使って CDK CLI でデプロイできます。この操作は成功するはずです (重要な項目の概要を示すために、一部の出力は省略されています)。

❯ cdk deploy 
✨  Synthesis time: 3.05s 
✅  DevUsersStack 
✨  Deployment time: 23.17s 

 Stack ARN:
 arn:aws:cloudformation:ap-southeast-2:123456789012:stack/DevUsersStack/704a7710-7c11-11ed-b606-06d79634f8d4 

✨  Total time: 26.21s

アクセス許可境界をデプロイする前に、cdk destroy コマンドを使ってこのスタックを削除してください。

❯ cdk destroy 
 Are you sure you want to delete: DevUsersStack (y/n)? y 
 DevUsersStack: destroying... [1/1] 
✅ DevUsersStack: destroyed

CDK テストアプリケーションでアクセス許可境界を使用する

作成したアクセス許可境界を適用し、同じスタックのデプロイに与える影響を確認してみましょう。アクセス許可境界を設定するためにブートストラップスタックを更新するには、新しい custom-permissions-boundary パラメータを指定して cdk bootstrap コマンドを再実行します。

cdk bootstrap --custom-permissions-boundary developer-policy

このコマンドが実行された後、CloudFormation の実行ロールは、config:* の拒否ルールに基づいて同じアプリケーションのデプロイが失敗するように、そのポリシーをアクセス許可境界として使用するように更新されます。この内容を確認するためには、再度 cdk deploy を実行し、エラーメッセージを確認してください。

❌ Deployment failed: Error: Stack Deployments Failed: Error: The stack 
 named DevUsersStack failed creation, it may need to be manually deleted 
 from the AWS console: 
  ROLLBACK_COMPLETE: 
    User: arn:aws:sts::123456789012:assumed-role/cdk-hnb659fds-cfn-exec-role-123456789012-ap-southeast-2/AWSCloudFormation 
    is not authorized to perform: config:PutConfigRule on resource: access-keys-policy with an explicit deny in a 
    permissions boundary

これは、アクセス許可境界によってアクションが意図通りに拒否されたことを示しています。

IAM エンティティへのアクセス許可境界の自動適用

次に、CDK アプリケーションによって作成された IAM エンティティにアクセス許可境界を拡張する方法を見ていきましょう。ここで懸念されるのは、新しい IAM エンティティを作成する開発者が、自分自身が持っているよりも強力なアクセス許可をそのエンティティに割り当てる可能性があることです。アクセス許可境界が付与されたエンティティのみを作成できるよう制限することでこの事態を予防します。この動作はスタックを変更し、アクセス許可境界を含まない IAM ロールを使用する Lambda 関数をデプロイすることで検証できます。lib/dev_users-stack.ts ファイルを再度開き、以下のサンプルを反映するように編集してください。

import * as cdk from 'aws-cdk-lib';
 import { PolicyStatement } from "aws-cdk-lib/aws-iam";
 import {
  AwsCustomResource,
  AwsCustomResourcePolicy,
  PhysicalResourceId,
} from "aws-cdk-lib/custom-resources";
 import { Construct } from "constructs";

 export class DevUsersStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props ?: cdk.StackProps) {
    super(scope, id, props);

    new AwsCustomResource(this, "Resource", {
      onUpdate: {
        service: "ConfigService",
        action: "putConfigRule",
        parameters: {
          ConfigRule: {
            ConfigRuleName: "SampleRule",
            Source: {
              Owner: "AWS",
              SourceIdentifier: "ACCESS_KEYS_ROTATED",
            },
            InputParameters: '{"maxAccessKeyAge":"60"}',
          },
        },
        physicalResourceId: PhysicalResourceId.of("SampleConfigRule"),
      },
      policy: AwsCustomResourcePolicy.fromStatements([ 
        new PolicyStatement({
          actions: ["config:*"],
          resources: ["*"],
        }),
      ]),
    });
  }
}

ここで AwsCustomResource を使用して、新しい Config ルールを作成する Lambda 関数をプロビジョニングしています。これは先ほどのスタックと同じ結果ですが、この場合はルールの作成が CDK コンストラクトによって作成される新しい IAM ロールによって行われます。このままデプロイを試みると失敗します。この動作を確認するには、cdk deploy を実行してください。

❌ Deployment failed: Error: Stack Deployments Failed: Error: The stack named 
 DevUsersStack failed creation, it may need to be manually deleted from the AWS 
 console: 
  ROLLBACK_COMPLETE: 
    API: iam:CreateRole User: arn:aws:sts::123456789012:assumed-
 role/cdk-hnb659fds-cfn-exec-role-123456789012-ap-southeast-2/AWSCloudFormation 
    is not authorized to perform: iam:CreateRole on resource:
 arn:aws:iam::123456789012:role/DevUsersStack-
 AWS679f53fac002430cb0da5b7982bd2287S-1EAD7M62914OZ 
    with an explicit deny in a permissions boundary

このエラーメッセージは、アクセス許可境界が適用されていないために、iam:CreateRole の呼び出しに失敗したため、スタックをデプロイできなかったことを説明しています。CDK では cdk.json ファイル内の CDK コンテキスト変数 core:permissionsBoundary を介して、作成されたすべての IAM エンティティにデフォルトのアクセス許可境界を設定する簡単な方法が提供されています。

{
  "context": {
     "@aws-cdk/core:permissionsBoundary": {
       "name": "developer-policy"
     }
  }
}

この context を利用したアプローチによって、インポートした IAM エンティティを作成するコンストラクト (Construct Hub で見つけたものや、デフォルトの IAM ロールを作成する既存のコンストラクトなど) に対してもアクセス許可境界が適用されるようになります。このアプローチが適さない場合、別の方法として特定のロールにアクセス許可境界を設定することもできます。

cdk.json ファイルを変更して、再度 CDK デプロイを実行します。今度はカスタムリソースが CloudFormation 実行ロールではなくアクセス許可境界が付与された IAM ロールを使用して Config ルールの作成を試みます。同様に、このアクセス許可境界により Lambda 関数が許可されていないアクションの実行から保護されます。これを確認するために、再度 cdk deploy を実行してください。CloudFormation のデプロイ更新では、今回はロール作成が成功し、新しいエラーメッセージが生成されていることがわかります。

❌ Deployment failed: Error: Stack Deployments Failed: Error: The stack named 
 DevUsersStack failed creation, it may need to be manually deleted from the AWS 
 console:
  ROLLBACK_COMPLETE: 
    Received response status [FAILED] from custom resource. Message returned: User:
    arn:aws:sts::123456789012:assumed-role/DevUsersStack-
 AWS679f53fac002430cb0da5b7982bd2287S-84VFVA7OGC9N/DevUsersStack-
 AWS679f53fac002430cb0da5b7982bd22872-MBnArBmaaLJp 
    is not authorized to perform: config:PutConfigRule on resource: SampleRule with an explicit deny in a permissions boundary

このエラーメッセージから、参照されているユーザーが DevUsersStack-AWS679f53fac002430cb0da5b7982bd2287S-84VFVA7OGC9N であり、CloudFormation の実行ロールではないことがわかります。カスタム Lambda 関数リソースで使用されているロールで Config ルールの作成を試みると、同様にアクセス許可境界により拒否されます。ここから、アクセス許可境界が CDK アプリ内で作成されるすべての IAM エンティティに一貫して適用される様子がわかります。これにより、開発者が行う作業すべてに対して、管理コントロールを最小限のオーバーヘッドで一貫して適用することが可能になります。

クリーンアップ

クリーンアップでは CDK ブートストラップスタックを削除するか、スタックからアクセス許可境界を削除するかを選択できます。削除する場合は、次の AWS CLI コマンドで CloudFormation から CDKToolkit スタックを削除します。

aws cloudformation delete-stack --stack-name CDKToolkit

ブートストラップスタックを保持したい場合は、次の手順に従ってアクセス許可境界を削除できます。

  1. AWS コンソールの CloudFormation ページに移動し、CDKToolkit スタックを選択します。
  2. 「更新」ボタンを選択します。「現在のテンプレートの使用」を選択し、次に「次へ」をクリックします。
  3. パラメータページで、値「developer-policy」が設定されている InputPermissionsBoundary を見つけ、このインプットのテキストを削除して空欄にします。「次へ」をクリックし、次のページでも「次へ」をクリックします。
  4. 最終ページで下までスクロールし、CloudFormation がカスタム名の IAM リソースを作成する可能性があることを認める確認ボックスにチェックを入れ、「送信」を選択します。

アクセス許可境界はもう使用されていないので、最後のステップとして、アクセス許可境界の作成に利用したスタックを削除できます。

aws cloudformation delete-stack --stack-name DeveloperPolicy

おわりに

IAM のアクセス許可境界をどのようにシンプルに CDK 開発に統合できるかがわかりました。開発者が必要なコントロールを与えつつ、管理者はセキュリティを組織の要件に沿った方法で管理できるようになります。

この点を踏まえた上で、アクセス許可境界の使用をさらに拡張するための次のステップがあります。GitHub の CDK Security and Safety Dev Guide では、これらのアプローチと、デプロイ時の権限アプローチの考え方を説明しています。開発者とセキュリティ管理者はこれを確認し、セキュリティ目標に合った適切な権限ポリシーのアプローチを開発することをお勧めします。

さらに、アクセス許可境界の概念を、各ステージに個別のアクセス許可境界名を適用するマルチアカウントモデルに適用できます。これにより、下位レベルの環境 (開発環境やベータ環境など) にはトラブルシューティングやその他の開発者固有のアクションに適した緩やかなアクセス許可境界を設定しつつ、上位レベルの環境 (ガンマまたは本番環境など) にはセキュリティリスクを適切に管理するために制限の厳しいアクセス許可境界を設定できるようになります。このメカニズムを実装する方法は、CDK Security and Safety Dev Guide でも紹介されています。

本記事は、Secure CDK deployments with IAM permission boundaries を翻訳したものです。翻訳は Solutions Architect の 山崎 宏紀が担当しました。