前置き
以下のような AWS::Serverless::Function
のリソースを作成する SAM(CloudFormation)テンプレートがありました。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
Sample SAM Template
Resources:
NuxtServerFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub "${AWS::StackName}-nuxt-server"
CodeUri: nuxt-app/.output/server/
Handler: index.handler
Description: Nuxt4 Server application
MemorySize: 1024
FunctionUrlConfig:
AuthType: NONE
Cors:
AllowMethods: ["*"]
AllowOrigins: ["*"]
AllowHeaders: ["*"]
AWS::Serverless::Function
は AWS SAM で利用可能なサーバーレスアプリケーションを作るのに特化したリソースで Lambda 関数に紐づく実行ロールや API Gateway の設定を含めた構築が可能となっています。Lambda 関数に紐づく実行ロールについて自動生成されるロールの名称は以下のような規則に基づいたものとなっています。
<スタック名>-<AWS::Serverless::Function のリソース名>Role-<ランダムな12桁の文字列>
↓
hogefuga-stack-stg-NuxtServerFunctionRole-abcd1234efgh
IAM ロールの名称には最大64文字までという制限があります。
今回は AWS SAM を使ったアプリのデプロイを GitHub Actions から実行しています。
あらかじめ AWS アカウント側に IAM OIDC ID プロバイダー を構築し、GitHub Actions と AWS の間に信頼関係を作っておくことでワークフロー実行時に一時的なクレデンシャルを使った認証を可能としています。
デプロイする際のロールには PowerUserAccess という強力なマネージドポリシーと以下のような一部の iam の操作を許可するポリシーを付与しています。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"iam:CreateRole",
"iam:DeleteRole",
"iam:AttachRolePolicy",
"iam:DetachRolePolicy",
"iam:PutRolePolicy",
"iam:DeleteRolePolicy",
"iam:GetRole",
"iam:PassRole",
"iam:TagRole",
"iam:UntagRole"
],
"Resource": [
"arn:aws:iam::<アカウントID>:role/<アプリ名>-stg-*",
"arn:aws:iam::<アカウントID>:role/<アプリ名>-prod-*"
],
"Effect": "Allow"
}
]
}
Resource の記述によって iam について前方一致でAWSの操作可能なリソースの範囲を制限するといった施策を取っています。
何が問題なのか
こちらスタック名(アプリ名)にそこそこ長い名称を指定した際にスタック名の一部が欠落し、結果としてデプロイに失敗するという問題が発生してしまいました。。
例)
スタック名:hogehoge-fugafuga-abcdefg-stg
AWS::Serverless::Function のリソース名:NuxtServerFunction
ランダム文字列:abcd1234efgh
これらを連結すると hogehoge-fugafuga-abcdefg-stg-NuxtServerFunctionRole-abcd1234efgh という 65 文字の文字列になりますが、IAMロールには前述の通りで最大で 64 文字の制限があります。
こちら実際にはプレフィックス部分である hogehoge-fugafuga-abcdefg-stg の一部が取り除かれた hogehoge-fugafuga-abcdefg-st-NuxtServerFunctionRole-abcd1234efgh といったロールを生成しようとしていました。
前置きの中で話として挙げたデプロイ用ロールは <アプリ名>-stg-* に合致するリソースしか操作できないため、この切り詰めで名前が一致せず、以下のような iam:CreateRole
権限エラーが発生しました。
Resource handler returned message: "Encountered a permissions error performing a tagging operation, please add required tag permissions.
See https://repost.aws/knowledge-center/cloudformation-tagging-permission-error for how to resolve.
Resource handler returned message: "User: arn:aws:sts::123456789012:assumed-role/deploy-role-for-<アプリ名>/GitHubActions is not authorized to perform: iam:CreateRole on resource: arn:aws:iam::123456789012:role/<アプリ名>-st-NuxtServerFunctionRole-7MYDjp6EaYMm because no identity-based policy allows the iam:CreateRole action (Service: Iam, Status Code: 403, Request ID: 4fc3ffde-0b9f-4462-b26b-2fd0d364d98c) (SDK Attempt Count: 1)"" (RequestToken: z9x8y7w6-v5u4-3210-tsrq-0987654321po, HandlerErrorCode: UnauthorizedTaggingOperation)
ということで SAM を使う場合でも IAM ロール名には余裕を持たせるべく、必要に応じて 独自にロールを自作する方針 を取ることにしました。
実行ロールについて自作する
以下のような感じで AWS::IAM::Role
リソースを追加し、その ARN を AWS::Serverless::Function
リソースの Role
プロパティで渡すことで IAM ロールの名称について独自のものを設定することができました。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
Sample SAM Template
Resources:
NuxtServerFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub "${AWS::StackName}-nuxt-server"
+ Role: !GetAtt NuxtServerFunctionRole.Arn
CodeUri: nuxt-app/.output/server/
Handler: index.handler
Description: Nuxt4 Server application
MemorySize: 1024
FunctionUrlConfig:
AuthType: NONE
Cors:
AllowMethods: ["*"]
AllowOrigins: ["*"]
AllowHeaders: ["*"]
+ NuxtServerFunctionRole:
+ Type: AWS::IAM::Role
+ Properties:
+ # ロール名を設定(スタック名だけだとわかりにくいので Lambda 関数の実行ロールであることがわかるようなサフィックスを付与)
+ RoleName: !Sub "${AWS::StackName}-nuxt-server-role"
+ # 以下の信頼ポリシーと許可ポリシーは AWS::Serverless::Function でポリシーなど特に追記せずに作成した場合のデフォルトと同じ
+ AssumeRolePolicyDocument:
+ Version: '2012-10-17'
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service: lambda.amazonaws.com
+ Action: sts:AssumeRole
+ ManagedPolicyArns:
+ - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
↓ 修正前
↓ 修正後(短くなった!)
意図したロール名での生成が行えてることを確認できました。
今回はサフィックスとして -nuxt-server-role
という17文字を付与しているのでスタック名には 64 - 17 で 47 文字まで設定できるようになったものと思われます。
備考
capabilities に関するエラーー
IAM ロールを自作する過程で、capabilities に関するエラーが発生しました。
Error: Failed to create changeset for the stack: <アプリ名>-stg, ex: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: Requires capabilities : [CAPABILITY_NAMED_IAM]
原因は、samconfig.toml の CI 用デプロイ設定で capabilities = "CAPABILITY_IAM"
としていたためです。
AWS::Serverless::Function
を使っている場合はこれでも問題なかったみたいですが、IAM ロールを独自に作る場合には CAPABILITY_NAMED_IAM
を明示する必要がありました。
~~~ 省略 ~~~
# GitHub Actions 用デプロイ設定
[ci.deploy.parameters]
-capabilities = "CAPABILITY_IAM"
+capabilities = "CAPABILITY_NAMED_IAM"
confirm_changeset = false
fail_on_empty_changeset = false
resolve_s3 = true
GitHub Actions からのデプロイの中で以下のように --config-env ci
を指定して ci.deploy.parameters
の設定を読み込んでいます。
- name: 🌐 メインスタックをデプロイ(CloudFront + Lambda + S3 + etc など)
run: |
sam build
sam deploy \
--config-env ci \
--stack-name ${{ env.STACK_NAME }} \
--region ${{ env.AWS_REGION }} \
--no-progressbar
スタック名の生成について
GitHub Actions ではリポジトリ名と環境名を組み合わせてスタック名を生成しています。
- name: 🔧 スタック環境変数を設定
run: |
REPO_NAME="${{ github.event.repository.name }}"
ENVIRONMENT=$([[ "$GITHUB_REF" == "refs/heads/prod" ]] && echo "prod" || echo "stg")
STACK_NAME=$(echo "${REPO_NAME}-${ENVIRONMENT}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g')
echo "REPO_NAME=${REPO_NAME}" >> $GITHUB_ENV
echo "ENVIRONMENT=${ENVIRONMENT}" >> $GITHUB_ENV
echo "STACK_NAME=${STACK_NAME}" >> $GITHUB_ENV
echo "✅ スタック名を設定: ${STACK_NAME}"
- 後続のステップで
${{ env.~~ }}
で参照可能とするため$GITHUB_ENV
に格納しています。
まとめ
今回の件は私の SAM テンプレート内の AWS::Serverless::Function
リソースの名称が長かったことや、スタック名の長さなどについてこれと言ってチェックしていなかったことが原因で引き起こされたそこそこレアな症状な気がします。
一応、他のリソースなども可能な範囲で小文字のケバブケースで定義しているので今回の修正によって IAM の実行ロールについても統一感が出たのはメリットと言えるかも?
参考リンク
↑ 記事内でさらっと触れた GitHub Actions からの AWS リソースの操作を行う件について日本語で読みやすかったページ