
DevinのAWSエンジニア化計画 権限設計編
はじめに
本記事では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段階で防ぎます。
権限設計の実装
可能な限り単純にアクセスキーに権限を与えないため、以下のように権限を分離していきます

- IAMユーザ(アクセスキー)の持つ権限
- DeploymentActionRoleへAssmeRoleできる権限(AWSリソースの作成/更新/削除操作が可能)
- ReadOnlyRoleへAssmeRoleできる権限(AWSリソースの読取操作が可能)
- DeploymentActionRole
- CDKが生成するデフォルトの設定を利用
- ReadOnlyRole
- ReadOnlyAccess相当の権限
- CloudFormation実行時の権限(CloudFormationExecutionRole)
- 一般的なAWSリソース作成権限
- PermissionsBoundaryで危険な操作を禁止
IAMユーザには最低限CloudFormationの実行だけを許可して、CDK経由での実行のみ許可。CDKからのリソース操作は基本Admin相当権限で許可し、危険な操作のみをPermissionsBoundaryで禁止する方式で考えます。
CDKが作成するロールの詳細については以下のブログをご参照ください。
権限設計の実装
ここからはいよいよ実装に入っていきます。以下のような工程で進めていきます。
- 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" ] }, { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "*", "Condition": { "ForAnyValue:StringEquals": { "iam:ResourceTag/aws-cdk:bootstrap-role": [ "image-publishing", "file-publishing", "deploy", "lookup" ] } } }, { "Sid": "DenyAssumeRoleIfNoMFA", "Effect": "Deny", "Action": "sts:AssumeRole", "Resource": "*", "Condition": { "BoolIfExists": { "aws:MultiFactorAuthPresent": "false" } } } ] } 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をカスタム
参考
全体概要図の以下の赤枠の部分のロールの権限で、実際にCloudFormationがデプロイできるAWSリソースのが決まります。なのでこちらのロールに対して、PermissionsBoundaryで制限をかけます。

具体的には以下の内容をポリシー化してセットします。
- アクセスキーを自分で作成
- 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環境にアクセスが必要な作業のときはこちらのクレデンシャルを使ってもらうよう今後指示します。詳細は以下を参照してください。
アクセスキー/シークレットキー/仮想MFAデバイスのArnは、Key-Valueで保存。OTPのシード値は、TOTPに保存します。


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をジェネレートするサイトまで使って接続を試みます。

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

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

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環境へのデプロイが成功しました!

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

これである程度制限をかけて、AWSアカウントをDevinに使わせることが出来ました。
所感
どこまで制限をかけるべきか悩んだ結果、かなり書くのに時間がかかりました。ある程度Devinを自由に開発させたいと考える会社なら、今回の制限が複雑でもありつつ、Devin側操作の許容度も高いものなので良いのではないかと思っています。気になる場合は、DevinがAWSにデプロイする前にユーザ側へ承認をもらうようにDevin Knowledgeに設定しても良いかもしれません。
今後AIエンジニアの可能性が広がる中、どこまで制限を渡すかが重要になるかと考えています。もし自分たちでこういう制限を考えてるぜ。という方いれば是非ブログやXなどで交流できると嬉しいです!










