リモート開発メインのソフトウェア開発企業のエンジニアブログです

AWS CDK の権限周りについて考えてみた

AWS CDK でデプロイ等の業務を行う際に必要なアクセス権限を調べた所、よく考えられていてセキュリティリスクを抑える様に設計されている反面、デフォルトでは最悪 AdministratorAccess にエスカレーションされるリスクが残っており、対応策を考える必要があったのでそれについて共有します。

今回使用する CDK のバージョンは執筆時点で最新の 2.95.1 とします。また CDK 自体や IAM, CloudFormation の基礎的な知識を持つ方を対象読者としている為、これらの基礎的な部分の説明は割愛しています。

免責

本記事は一般的なセキュリティに関する情報を提供する目的で執筆されています。投稿者は AWS やセキュリティのスペシャリストでないので、掲載内容の正確性や適用性を保証するものではありません。本記事の内容を実践される際は、専門家の意見を取り入れることを強く推奨すると共に、本記事の情報に基づく損害や問題に対しての責任は負いかねますので予めご了承下さい。

AWS CDK を実行するのに必要な権限について

CDK を使った業務に必要な権限は一番最初に実行する cdk bootstrap コマンドによって作られます。

cdk bootstrap

上記の様に引数なしで実行すると、東京リージョンの場合次の様な IAM Role が作られます:

  • cdk-hnb659fds-cfn-exec-role-00000000000-ap-northeast-1
  • cdk-hnb659fds-deploy-role-00000000000-ap-northeast-1
  • cdk-hnb659fds-file-publishing-role-00000000000-ap-northeast-1
  • cdk-hnb659fds-image-publishing-role-00000000000-ap-northeast-1
  • cdk-hnb659fds-lookup-role-00000000000-ap-northeast-1

📝 hnb659fds と言う値はデフォルトで使われる (後述するオプションで変更可能. またこの値に意味はない模様) 物となっている

これらの role は、cdk deploy や cdk diff などの AWS API を扱うコマンドを実行する時に適宜使われます。

例えば cdk deploy を実行すると、 synthesise された CloudFormation テンプレートを S3 バケットに Put する際は cdk-hnb659fds-file-publishing-role-00000000000-ap-northeast-1 を、その後 CloudFormation にスタックを作成したりする際は cdk-hnb659fds-deploy-role-00000000000-ap-northeast-1 が使われます。他にも cdk diff の際は読み込み権限のみを持つ cdk-hnb659fds-lookup-role-00000000000-ap-northeast-1 が使用される等、必要に応じて最適な role が使われる様になっています。

この事から、 CDK を実行する環境では以下の権限だけあれば十分です:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::*:role/cdk-hnb659fds-*"
    }
  ]
}

ちなみに実行時にこれらの role に AssumeRole する権限がなくても、現在の認証情報の権限がフォールバックで使われるので、同等の権限が認可されていれば業務は続行されます。

deploy-role のセキュリティリスク

さて、この中でも特に注目したい role が cdk-hnb659fds-deploy-role-00000000000-ap-northeast-1 です。この role は非常に強力で、 CloudFormation テンプレートを S3 バケットにアップロードしたり、スタックを作成する権限の他、特筆すべき以下の権限を持っています:

{
  "Action": "iam:PassRole",
  "Resource": "arn:aws:iam::00000000000:role/cdk-hnb659fds-cfn-exec-role-00000000000-ap-northeast-1",
  "Effect": "Allow"
}

対象となっているのは冒頭で紹介した role の1つですが、デフォルトでは AdministratorAccess ポリシーが付与されています。この role は CloudFormation しか AssumeRole できませんが、 deploy-role はテンプレートのアップロードとスタックの作成が可能な為、 CloudFormation 経由とは言え実質的にこの AWS アカウントの AdministratorAccess を有する事になります。

既に冒頭で話した通り、 CDK の bootstrap によって作られる role の名前はデフォルトでは一意に決まる為、攻撃者がある AWS アカウントの認証情報を奪取した際にはひとまず cdk-hnb659fds-deploy-role-${AccountId}-#{Region} を AssumeRole できるか試す可能性があります。

セキュリティリスクを低減する目的で CDK のデプロイ業務を行う環境では先ほどの様に権限を絞った訳ですが、この手順で実質的に AdministratorAccess へエスカレーションできる状況は望ましいとは言えません。

これに関する対応策をいくつか考えて見たので次節で紹介します。尚、シナリオとしては一番最初に紹介した CDK 関連の role に AssumeRole ができるだけの限定的な権限が認可された認証情報が漏れた場合を想定しています。

Moba Pro

[手軽] Qualifier を変える

Qualifier は role 等の bootstrap 時に作られる AWS リソースの名前付与されている hnb659fds と言う値の事です。既に言及した通りこの値はランダムな文字列ですがデフォルトでは全プロジェクト共通でこの値が使われていて攻撃者としては推測がしやすい状態になっている為、この値は変えた方が良さそうです。

この値を変える方法として、最初に実行する cdk bootstrap コマンドで次の様に --qualifier オプションを付ける手法があります:

cdk bootstrap --qualifier my-project

こうすると、作られる role 等の名前は cdk-my-project-deploy-role-000000000000-ap-northeast-1 の様になります。

またこの他に後述する bootstrap テンプレートの変更によっても指定が可能になります。

尚、 Qualifier を変更した場合は cdk.json で指定する必要があります:

{
  ...  
  "@aws-cdk/core:bootstrapQualifier": "my-project",
  ...  
}

また既に CDK でスタックを作っている場合は、全てのスタックのパラメーターとテンプレートの更新が必要なので少々面倒です。

[複雑] cfn-exec-role の権限を絞る

CloudFormation が使うこの role はデフォルトでは AdministratorAccess が付与されていますが、これも Qualifier 同様 cdk bootstrap コマンドの --cloudformation-execution-policies オプションで変更が可能です:

cdk bootstrap --cloudformation-execution-policies "arn:aws:iam::aws:policy/PowerUserAccess"

PowerUserAccess にしておくとひとまず IAM 関連の操作は防げますが、当然ながら CDK でも IAM 関連のリソースは作れなくなるので、必要な IAM Role や Policy は CDK 管理外とする必要があり、そうなると少しメリットを享受しづらいかもしれません。

また IAM が使えなくても攻撃者がやりがちな計算資源を大量に使われる、と言った事は防げなさそうです。

一方でユーザー定義のカスタム policy を付与しておけば都度調整できるので、デプロイの度に AdministratorAccess 相当のユーザーがこれを調整すると言う手法も考えられますが業務としては煩雑になりすぎる気がします。

[手軽] deploy-role への AssumeRole を制限する

Qualifier と同様にコスパが良い手法として、 deploy-role への AssumeRole に制限を掛ける手法があります。
IAM Role の Trusted relationship では次の様にして IP アドレスによるアクセス制限をする事ができます:

{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::00000000000:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": ["<IP アドレス1>", "<IP アドレス2>"]
        }
      }
    }
  ]
}

CDK を操作する環境に応じて都度 IP アドレスを調整しておけば良さそうです。場合によっては管理が大変になるので必ずしもコスパがいいとは限りませんが、管理し切れる規模感であればそれなりに効果を発揮できそうです。

これにより、万が一 CDK 実行者の認証情報が漏れても第三者は deploy-role に AssumeRole できないので問題ありません。

設定する方法ですが、一度 bootstrap を実行した後に手動で設定する事もできますが、実は bootstrap 自体の CloudFormation テンプレートをカスタマイズする事も可能なので以下の手順で行う事ができます。

まずは以下のコマンドでオリジナルのテンプレートを出力します:

cdk bootstrap --show-template > bootstrap.yaml

今回は制限する IP アドレスを各 role の Trusted relationships に追加したいので、以下の様に修正します:

# ...
Parameters:
  # ...
  Qualifier:
    Description: An identifier to distinguish multiple bootstrap stacks in the same environment
    Default: my-qualifier
    Type: String
    AllowedPattern: "[A-Za-z0-9_-]{1,10}"
    ConstraintDescription: Qualifier must be an alphanumeric identifier of at most 10 characters
  DeploymentAllowedIpAddresses:
    Type: AWS::SSM::Parameter::Value<List<String>>
    Default: "/cdk-bootstrap/my-project/deployment-allowed-ip-addresses"
Resources:
  # ...
  DeploymentActionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              AWS:
                Ref: AWS::AccountId
            Condition:
              IpAddress:
                "aws:SourceIp": !Ref DeploymentAllowedIpAddresses
          # ...
  # ...

ここでは次の様な変更を加えています:

  • Qualifier パラメーターのデフォルト値を my-project に変更
  • DeploymentAllowedIpAddresses と言うパラメーターを追加してここで制限する IP アドレスを管理
    • /cdk-bootstrap/my-project/deployment-allowed-ip-addresses と言う SSM Parameter (StringList) は予め手動で作っておき、この中で IP アドレスのリストを管理

またこの例では deploy-role だけに IP 制限を掛けていますが、他の role にも必要に応じて掛けて下さい。今回は一番重要な deploy-role 以外は割愛します。

この状態で以下のコマンドで bootstrap します:

cdk bootstrap --template ./bootstrap.yaml

これで deploy-role に AssumeRole できる環境の制限を掛ける事ができました。

[番外] IAM User をそもそも使わない

これは本件とは直接関係ありませんが、 CDK 実行者の認証に IAM User を使わない事をお勧めします。理由としては認証情報の有効期間が長く、一度漏れるとそれだけ長い期間セキュリティリスクに晒される為です。最近では Identity Center によるユーザーの管理を行うのがベストプラクティスとされています。

まとめ

  • CDK のデフォルトの bootstrap で作られる role は比較的簡単に (実質) AdministratorAccess へのエスカレーションが可能
  • その理由として、作られる role の名前が推測可能な事が要因の1つなので Qualifier を変える事で推測を難しくした
  • これだけでそれなりに効果があるが、他の経路から role 名がバレてしまうと再びリスクに晒される為、この role に対する IP アドレスによる制限を掛ける事により、より堅牢な設計にした
  • そもそも認証情報の管理に IAM User を使う事を避け、 Identity Center で管理するのがおすすめ

← 前の投稿

Google Chromeの拡張機能が存外簡単に作れた話

次の投稿 →

MSYS2 で Windows 上に *NIX ぽい環境を構築する

コメントを残す