Fargate上に設営するRedashのデプロイ用IAMロールをcdkで作成してみた

GitHub ActionsでCICD用のIAMロールを作成する際に、CloudFormationテンプレートを直接書きたくなかったがゆえに、既存のcdkスタックにて混ぜてなんとかしようと頑張ってみました。
2021.06.10

RedashのCICD用にIAMロールを作っていました。後々のメンテナンスを面倒にしないことが一番の課題です。

RedashのDeploy用IAMロール及びIAMユーザ作成にYAMLのCloudFormationテンプレートを使う方をよく見ますが、正直YAMLでは書きたくないなーというのが本音でした。インデントで関係が見やすいとしても依存等は追いかけ難い。個人的には「cdkでもいいんじゃない?」と思いますが、やってる人が少なくてゼロから検証するのも確かに手間です。

ただ、誰もやってなさそうなので「やってみた」系ネタとしては有用かなと思い、cdkでやってみました。

今回の前提

IAMロール作成用スタックを環境作成用スタックに含めた上で、IAMロール作成用スタックのみを指定実行します。

既にIAMユーザは存在しており、Arnで指定します。ポイントはロール作成に用いるclass。ロールを引き受けつつセッションタグを受け渡すため、以下のActionを設定する必要があります。

  • sts:AssumeRole
  • sts:TagSession

複数のActionをAssumeRoleActionに割り当てる手段

classにRoleを用いた場合、assumedByに渡すIPrincipalは要素のassumeRoleActionがstringのみ受け付けており、複数Actionは配列扱いとなるため適用できません。IPrincipal@aws-cdk/aws-iamのClassで実装されており、assumeRoleActionを引数として受け付けるClass全てで該当しています。ではどうすべきか。

Actionに2つ割り当てた例として以下の記事があります。

What I ended up with, is to use a low-level construct CfnRole.

とあるように、CfnRoleを使い、assumeRolePolicyDocumentに渡す形にて実装例を以下に挙げます。

CfnRoleを用いた実装

ManagedPolicyは枠に上限があるため、アスタリスクでActionが指定されていたり、Action数が少ないものについてはPolicyDocumentとして纏めて追加しています。

import { CfnOutput, Construct, Stack, StackProps } from '@aws-cdk/core';
import { ManagedPolicy, CfnRole, Effect, PolicyDocument, PolicyStatement } from'@aws-cdk/aws-iam';

export class RedashDeployRoleStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);

    const deployRole = new CfnRole(this, 'RedashDeployRoleStack', {
      roleName: `redash-deploy`,
      assumeRolePolicyDocument: {
        'Statement': [{
            'Effect': Effect.ALLOW,
            'Action': ['sts:AssumeRole', 'sts:TagSession'],
            'Principal': {
              'AWS': `arn:aws:iam::${this.account}:user/redash-user`,
            }
        }]
      },
      managedPolicyArns: [
        ManagedPolicy.fromAwsManagedPolicyName('AmazonECS_FullAccess').managedPolicyArn,
        ManagedPolicy.fromAwsManagedPolicyName('AmazonRDSFullAccess').managedPolicyArn,
        ManagedPolicy.fromAwsManagedPolicyName('AmazonElastiCacheFullAccess').managedPolicyArn,
        ManagedPolicy.fromAwsManagedPolicyName('AmazonRoute53FullAccess').managedPolicyArn
      ],
      maxSessionDuration: 60 * 60 * 12,
      // AmazonVPCFullAccess
      // AWSCloudFormationFullAccess
      // IAMFullAccess
      // AmazonSSMReadOnlyAccess
      policies: [{
        policyName: `RedashDeployPolicy`,
        policyDocument: new PolicyDocument({
          statements: [
            new PolicyStatement({
              actions: [
                "cloudformation:*",
                "iam:*",
                "ec2:*",
                "ssm:Describe*",
                "ssm:Get*",
                "ssm:List*"
              ],
              resources: ["*"]
            }),
          ]
        }).toJSON()
      }]
    });

    // output
    new CfnOutput(this, 'RedashDeployRole', {
      value: deployRole.attrArn,
      description: 'RoleArn for Deploy'
    });
  }
}

Deploy用にcdk.tsへ追記します。

import { App } from '@aws-cdk/core';
import { RedashDeployRoleStack } from '../lib/deploy-role';
const env = {account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION}
const app = new App({
  context: {
    version: process.env.VERSION
  }
});
const roleStack = new RedashDeployRoleStack(app, `redash-deploy-role`, {env});

手軽に実行できるよう、バッチ化します。

#!/bin/sh
npm run build
npm run cdk -- deploy --require-approval never --context redash-deploy-role

実行後、ログに出力されるArnをGitHub上でSecretsに指定し、aws-actions/configure-aws-credentialsrole-to-assumeに渡します。

Outputs:
redash-deploy-role.RedashDeployRole = arn:aws:iam::000000000000:role/redash-deploy
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-northeast-1
        role-to-assume: ${{ secrets.AWS_ROLE_FOR_REDASH }}
        role-duration-seconds: 3600

あとがき

ActionsのWorkflowを動作させるために必要なため、実行する場所は手元の作業環境に限定されます。ActionsのWorkflow上ではnochangesで終わるはずですが、万が一意図しない変更が発生していた場合は不要な修正が含まれていないか差分を確認しましょう。