DevinのAWSエンジニア化計画 権限設計編

DevinのAWSエンジニア化計画 権限設計編

DevinをAWSエンジニアにしてみたいので最低限の権限の与え方について試してみました Devin調査ブログ3個目
Clock Icon2025.02.07

はじめに

本記事ではDevinに安全にAWS環境を使ってもらうには、どのような環境を与えるべきか考えてみました。

他の記事で試しているともしかするとDevinが思ってたより色々できるのでは?という感覚があったので、今回実際にAWS環境に触ってもらおうと思い記事を書きました。ただ無尽蔵にAWSのAdministrator相当権限を与えると、恐ろしい課金が発生する可能性があるので攻めすぎず守りすぎずで権限設計を考えました。

コンソールからの操作はDevinとの親和性が低いかもしれないと考えたので、今回はIaC(CDK)を使ってAWS環境のリソースを操作することを想定します。

権限設計について

最小権限の原則に則って可能な限り権限を制限したいですが、現実問題権限をAction単位で個別に有効化しているとDevinに手放しでタスクを頼んでいくのが難しくなります。人間がゲートキーパーのように振る舞うセキュリティでは、Devinの真の力は発揮できないかもしれません。今回はガードレールのように、特定の作業だけを禁止して可能な限り権限を与えるような権限設計を考えてみます。今後この考えが正しかったのかは検証していきます。

リスクの洗い出し

ガードレールとして最低限以下のリスクを想定します。

  • セキュリティ面
    • 権限の漏洩
      • アクセスキーを自分で作成し、インターネット上に公開
      • 渡したアクセスキーをインターネットに公開
    • 権限昇格
      • IAMユーザを作成し権限昇格
      • 自分にアタッチされているポリシーを変更し権限昇格
      • CDK Bootstrapの権限を操作し権限昇格
  • コスト面
    • 高額課金となるサービスを起動(RI/SP、Shield Advancedなど)
    • 課金額の高いインスタンスタイプのEC2を起動
    • マーケットプレイスのサブスクライブ

上記以外にも無限にリスクは考えられますが、他のリスクを受容する方向で考えます。

権限設計ポイントの整理

最低限上記のリスクに対応するために、権限の範囲で制限出来る部分はIAMで実装し、IAMで実現が難しい部分はプロンプトで指示します。
以下の部分はIAMで実装可能なのでIAMで対応します。

  • アクセスキーを自分で作成
  • IAMユーザを作成し権限昇格
  • 自分にアタッチされているポリシーを変更し権限昇格
  • CDK Bootstrapの権限を操作し権限昇格
  • 高額課金となるサービスを起動(RI/SP、Shield Advancedなど)
  • 課金額の高いインスタンスタイプのEC2を起動
  • マーケットプレイスのサブスクライブ

インターネット上へのシークレット公開については、GitHubリポジトリをプライベートに設定し、シークレットをインターネット上に公開しないよう命令する2段階で防ぎます。

権限設計の実装

可能な限り単純にアクセスキーに権限を与えないため、以下のように権限を分離していきます

devin-iam-permission-overview.png

  • IAMユーザ(アクセスキー)の持つ権限
    • DeploymentActionRoleへAssmeRoleできる権限(AWSリソースの作成/更新/削除操作が可能)
    • ReadOnlyRoleへAssmeRoleできる権限(AWSリソースの読取操作が可能)
  • DeploymentActionRole
    • CDKが生成するデフォルトの設定を利用
  • ReadOnlyRole
  • CloudFormation実行時の権限(CloudFormationExecutionRole)
    • 一般的なAWSリソース作成権限
    • PermissionsBoundaryで危険な操作を禁止

IAMユーザには最低限CloudFormationの実行だけを許可して、CDK経由での実行のみ許可。CDKからのリソース操作は基本Admin相当権限で許可し、危険な操作のみをPermissionsBoundaryで禁止する方式で考えます。

CDKが作成するロールの詳細については以下のブログをご参照ください。

https://dev.classmethod.jp/articles/cdk-minimum-deploy-policy/

権限設計の実装

ここからはいよいよ実装に入っていきます。以下のような工程で進めていきます。

  • IAMユーザ作成やMFAの設定
  • 閲覧用ロールの作成
  • CDKのBootstrapのPermissionsBoundaryをカスタム
  • Devinにシークレットを登録
  • AWSリソースへのアクセス方法Knowledgeに記載

上記の設定完了後、AWSリソースの読取操作と更新操作が可能か簡単な手順でDevinに依頼してみます。

IAMユーザやMFAの設定

IAMユーザ作成やMFAの設定をAWS CLIで行っていきます。事前に左記の作業を行える権限を付与して実施してください

# 0. 環境準備
export TESTUSERNAME="mfa-cli-********"
export PASSWORD="****************"

# 仮想MFAのシード値からOTPを取得するoathtoolコマンドをインストール
brew install oath-toolkit

# IAMユーザーの作成
aws iam create-user --user-name $TESTUSERNAME

# パスワードの設定
aws iam create-login-profile \
    --user-name $TESTUSERNAME \
    --password $PASSWORD \
    --password-reset-required

# 仮想MFAデバイスの作成とTESTUSERNAME_secret.txtに仮想MFAのシード値を保存
export MFA_RESPONSE=$(aws iam create-virtual-mfa-device \
    --virtual-mfa-device-name ${TESTUSERNAME}_mfa \
    --outfile ${TESTUSERNAME}_secret.txt \
    --bootstrap-method Base32StringSeed)

# シークレットキーを取得してoath-toolkitで2つのコードを生成
export SECRET_KEY=$(cat ${TESTUSERNAME}_secret.txt)

# 1,2つ目のコードを生成
export CODE1=$(oathtool --totp -b "$SECRET_KEY") \
    && sleep 30 \
    && export CODE2=$(oathtool --totp -b "$SECRET_KEY")

# MFAデバイスの有効化
aws iam enable-mfa-device \
    --user-name $TESTUSERNAME \
    --serial-number $MFA_ARN \
    --authentication-code1 $CODE1 \
    --authentication-code2 $CODE2

# アクセスキー/シークレットキーの作成
aws iam create-access-key \
    --user-name $TESTUSERNAME

上記でIAMユーザとMFAの事前設定は終了です。

閲覧用ロールの設定

Devin側からAWSリソースの状況を見れるようにするため、閲覧用ロールを作成しIAMユーザと紐づけます。またついでにCDKがBootstrapで生成するDeploymentActionRoleなどCDK Bootstrapで作成されるロール(iam:ResourceTag/aws-cdk:bootstrap-roleのタグを持つ)に紐づけます。

# trust-policy.json として保存
# アカウントIDとTESTUSERNAMEは適宜変更
cat << EOF > trust-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::123456789012:user/TESTUSERNAME"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
EOF

# ロールの作成
aws iam create-role \
    --role-name ReadOnlyRole \
    --assume-role-policy-document file://trust-policy.json

# ReadOnlyAccessポリシーの付与
aws iam attach-role-policy \
    --role-name ReadOnlyRole \
    --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess

# IAMユーザからスイッチできるよう権限を付与
cat << EOF > assume-role-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": [
                "arn:aws:iam::123456789012:role/ReadOnlyRole"
            ],
            "Condition": {
                "Bool": {
                    "aws:MultiFactorAuthPresent": "true"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "*",
            "Condition": {
                "ForAnyValue:StringEquals": {
                    "iam:ResourceTag/aws-cdk:bootstrap-role": [
                        "image-publishing",
                        "file-publishing",
                        "deploy",
                        "lookup"
                    ]
                },
                "Bool": {
                    "aws:MultiFactorAuthPresent": "true"
                }
            }
        }
    ]
}
EOF

# カスタムポリシーの作成
aws iam create-policy \
    --policy-name AssumeRolePolicy \
    --policy-document file://assume-role-policy.json

# IAMユーザにアタッチ
aws iam attach-user-policy \
    --user-name $TESTUSERNAME \
    --policy-arn arn:aws:iam::123456789012:policy/AssumeRolePolicy

ここからは先程取得したアクセスキー/シークレットキーを使って作業します。

# アクセスキー/シークレットキーをセット
export AWS_ACCESS_KEY_ID="****"
export AWS_SECRET_ACCESS_KEY="****"

# ReadOnlyRoleにAssumeRoleできるかテスト
export CREDENTIALS=$(aws sts assume-role \
    --role-arn arn:aws:iam::123456789012:role/ReadOnlyRole \
    --role-session-name MyReadOnlySession)

export AWS_ACCESS_KEY_ID=$(echo $CREDENTIALS | jq -r '.Credentials.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.Credentials.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo $CREDENTIALS | jq -r '.Credentials.SessionToken')

# 権限の確認
aws s3 ls

# DeploymentActionRoleにAssumeRoleできるかテスト
# 再度元のアクセスキーとシークレットキーをセット。AWS_SESSION_TOKENは削除
export AWS_ACCESS_KEY_ID="****"
export AWS_SECRET_ACCESS_KEY="****"
unset AWS_SESSION_TOKEN

export AWS_ACCESS_KEY_ID=$(echo $CREDENTIALS | jq -r '.Credentials.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.Credentials.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo $CREDENTIALS | jq -r '.Credentials.SessionToken')

# 権限の確認
aws cloudformation describe-stacks --stack-name CDKToolkit

CDKのBootstrapのPermissionsBoundaryをカスタム

参考

https://zenn.dev/yumemi_inc/articles/cdk-with-permissions-boundary

全体概要図の以下の赤枠の部分のロールの権限で、実際にCloudFormationがデプロイできるAWSリソースのが決まります。なのでこちらのロールに対して、PermissionsBoundaryで制限をかけます。

devin-iam-permission-overview-focus-deployrole.png

具体的には以下の内容をポリシー化してセットします。

  • アクセスキーを自分で作成
  • IAMユーザを作成し権限昇格
  • 自分にアタッチされているポリシーを変更し権限昇格
  • CDK Bootstrapの権限を操作し権限昇格
  • 高額課金となるサービスを起動(RI/SP、Shield Advancedなど)
  • 課金額の高いインスタンスタイプのEC2を起動
  • マーケットプレイスのサブスクライブ
cat << EOF > cdk-permissions-boundary-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AdministratorAccess",
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        },
        {
            "Sid": "DenyIAMUserSelfManagement",
            "Effect": "Deny",
            "Action": [
                "iam:CreateAccessKey",
                "iam:DeleteAccessKey",
                "iam:UpdateAccessKey",
                "iam:CreateLoginProfile",
                "iam:DeleteLoginProfile",
                "iam:UpdateLoginProfile",
                "iam:ChangePassword"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Sid": "DenyIAMUserAndPolicyManagement",
            "Effect": "Deny",
            "Action": [
                "iam:CreateUser",
                "iam:DeleteUser",
                "iam:AttachUserPolicy",
                "iam:DetachUserPolicy",
                "iam:PutUserPolicy",
                "iam:DeleteUserPolicy"
            ],
            "Resource": "*"
        },
        {
            "Sid": "DenyCDKBootstrapModification",
            "Effect": "Deny",
            "Action": [
                "iam:*"
            ],
            "Resource": [
                "arn:aws:iam::*:role/cdk-*",
                "arn:aws:iam::*:policy/cdk-*"
            ]
        },
        {
            "Sid": "DenyExpensiveServices",
            "Effect": "Deny",
            "Action": [
                "savingsplans:*",
                "ec2:PurchaseReservedInstancesOffering",
                "ec2:PurchaseScheduledInstances",
                "ec2:CreateCapacityReservation",
                "elasticache:PurchaseReservedCacheNodesOffering",
                "es:PurchaseReservedInstanceOffering",
                "redshift:PurchaseReservedNodeOffering",
                "rds:PurchaseReservedDBInstancesOffering",
                "memorydb:PurchaseReservedNodesOffering",
                "dynamodb:PurchaseReservedCapacityOfferings",
                "codebuild:CreateFleet",
                "medialive:PurchaseOffering",
                "cloudfront:CreateSavingsPlan",
                "shield:*",
                "aws-marketplace:Subscribe",
                "aws-marketplace:Unsubscribe"
            ],
            "Resource": "*"
        },
        {
            "Sid": "DenyExpensiveEC2Instances",
            "Effect": "Deny",
            "Action": [
                "ec2:RunInstances"
            ],
            "Resource": "arn:aws:ec2:*:*:instance/*",
            "Condition": {
                "StringNotLike": {
                    "ec2:InstanceType": [
                        "t3.nano",
                        "t3.micro",
                        "t3.small",
                        "t3a.nano",
                        "t3a.micro",
                        "t3a.small"
                    ]
                }
            }
        }
    ]
}
EOF

# ポリシーとして作成
aws iam create-policy \
    --policy-name CdkPermissionsBoundaryPolicy \
    --policy-document file://cdk-permissions-boundary-policy.json

# cdk bootstrapのオプションでPermissionsBoundaryをCloudFormationExecutionRoleに付与
# (CDKプロジェクトを展開しているディレクトリで行う)
npx cdk bootstrap aws://123456789012/ap-northeast-1 --custom-permissions-boundary CdkPermissionsBoundaryPolicy
npx cdk bootstrap aws://123456789012/us-east-1 --custom-permissions-boundary CdkPermissionsBoundaryPolicy

Devinにシークレットを登録

権限周りの準備はできたので、次にDevinにシークレットとなる以下を登録します。

  • アクセスキー
  • シークレットキー
  • 仮想MFAデバイスのArn
  • OTPのシード値

Devinとしてはシークレットを保存する機能があります。アクセスキー/シークレットキーなどのクレデンシャル情報はここに保存し、AWS環境にアクセスが必要な作業のときはこちらのクレデンシャルを使ってもらうよう今後指示します。詳細は以下を参照してください。

https://docs.devin.ai/onboard-devin/secrets

アクセスキー/シークレットキー/仮想MFAデバイスのArnは、Key-Valueで保存。OTPのシード値は、TOTPに保存します。

スクリーンショット 2025-02-05 23.11.58.png

スクリーンショット 2025-02-05 23.14.26.png

OTPはQRコードを読み込ませるか、以下のようにOTPのシード値をsecretにセットして記入すると保存できます。

otpauth://totp/Amazon%20Web%20Services:{IAM Usename}@{Account ID}?secret={Secret Value}

AWSリソースへのアクセス方法Knowledgeに記載

今回は以下のようなプロンプトを英訳して記載しておきます。

  • AWSアカウントにアクセスしたい場合はDevinシークレットのAWS_CREDENTIALSとAWS_MFA_SEEDを使ってください
  • アクセスキー/シークレットキーはそのまま使わず、aws sts get-session-tokenでserial-numberとOTPを使って一時的なアクセスキー/シークレットキー/セッショントークンを取得してアクセスしてください
  • AWSアカウントでリソース作成/更新/削除をする場合はCDKを使ってください
  • AWSアカウントでリソースを確認する場合は、aws sts assumeroleでReadOnlyRoleにAssumeしてください
  • AWSリソースを作成/更新する場合、利用料金が1ヶ月で10ドル以上にならないか注意してください
    • もし判断に迷う場合はユーザに相談してください
  • Devin Secretsに登録している内容はインターネットにアップロードしないでください
- When accessing AWS accounts, please use AWS_CREDENTIALS and AWS_MFA_SEED from Devin secrets
- Instead of using access keys/secret keys directly, obtain temporary access key/secret key/session token using aws sts get-session-token with serial-number and OTP
- When creating/updating/deleting resources in AWS accounts, please use CDK
- When checking resources in AWS accounts, assume the ReadOnlyRole using aws sts assumerole
- When creating/updating AWS resources, be careful not to exceed $10 in monthly usage fees
  - If unsure about the cost estimation, please consult with the user
- Do not upload any content stored in Devin Secrets to the internet

※Devin's Workspaceの設定によって、Devinのセッション立ち上げ時に、特定のコマンドを実行させることもできます。ただタスクによってAWSアカウントにアクセスするかは分からないので、今回はKnowledgeとしています。今後うまく動作しない場合はWorkspaceでのコマンドで明示的に指定してみます。

DevinからAWSアカウントへアクセス(Read編)

いよいよAWSアカウントに接続してみます。以下の簡単な指示で動くか確認してみます。

AWSアカウント123456789012にReadOnlyRoleで接続して、S3バケットの一覧を取得してください

すると色々試行錯誤しつつ、時にはOTPをジェネレートするサイトまで使って接続を試みます。

スクリーンショット 2025-02-06 0.05.54.png

ここで接続に詰まるとAWS_MFA_SEEDを使ってよいか確認が入りました。Knowledgeで許可していても忘れているみたいです。なのでOKと返します。

スクリーンショット 2025-02-06 0.08.32.png

すると、自然にaws sts get-session-tokenにMFAのOTPをセットして一時認証情報を使い、AWS環境のS3の情報を取得してサマリまで作ってくれました!人間がやるようなIAMユーザ/ロールによる接続プロセスもDevinが自然と行ってくれます。

スクリーンショット 2025-02-06 0.11.25.png

DevinからAWSアカウントへアクセス(Write編)

今度はCDKを使ってリソース作成ができるか確認してみます。

- packages/iacでcdk deployを実行してAWSアカウント123456789012にデプロイしてください
- デプロイ前にbin/parameters.tsのcommonParameter.projectNameの値はicasu-cdk-serverless-api-hogeに書き換えてください
- OTPにはDevin secretsのAWS_MFA_SEEDを使ってください

上記を伝えたところ、伝え忘れたノウハウ(環境変数DEV_AWS_ACCOUNT_IDにアカウントIDをセットすること)を勝手に理解してコマンドを組み立てて無事CDKを使用したAWS環境へのデプロイが成功しました!

スクリーンショット 2025-02-06 0.41.55.png

実際にAWS環境にアクセスしても、Cfnのスタックが作成されてることが確認できます。

スクリーンショット 2025-02-06 0.43.12.png

これである程度制限をかけて、AWSアカウントをDevinに使わせることが出来ました。

所感

どこまで制限をかけるべきか悩んだ結果、かなり書くのに時間がかかりました。ある程度Devinを自由に開発させたいと考える会社なら、今回の制限が複雑でもありつつ、Devin側操作の許容度も高いものなので良いのではないかと思っています。気になる場合は、DevinがAWSにデプロイする前にユーザ側へ承認をもらうようにDevin Knowledgeに設定しても良いかもしれません。

今後AIエンジニアの可能性が広がる中、どこまで制限を渡すかが重要になるかと考えています。もし自分たちでこういう制限を考えてるぜ。という方いれば是非ブログやXなどで交流できると嬉しいです!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.