こんにちは、CX事業本部 IoT事業部の若槻です。
GitHub ActionsではOpenID Connect(OIDC)がサポートされたため、AWSなどと安全にキーのやり取りが可能となっています。
GitHub ActionsをAWSとOIDC連携する場合は、「ID Provider」と「IAM Role」をAWS上に作成する必要があるのですが、これらリソースをAWS CDKで作ってコードで管理するようにしてみました。
やってみた
以前作ったID Providerの確認、削除
以前にコンソールから手動で作成した同じくGitHubとのOIDC連携用のID Providerを取得します。
$ OPEN_ID_CONNECT_PROVIDER_ARN=<OPEN_ID_CONNECT_PROVIDER_ARN>
$ aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn ${OPEN_ID_CONNECT_PROVIDER_ARN}
{
"Url": "token.actions.githubusercontent.com",
"ClientIDList": [
"sts.amazonaws.com"
],
"ThumbprintList": [
"6938fxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
],
"CreateDate": "2022-02-28T14:20:17.398000+00:00",
"Tags": []
}
URL
およびClientIDList
をCDKで作るときにも指定して上げれば良さそうです。
ちなみに同じURLのID Providerは複数作成できないので、ここで削除しておきます。
$ aws iam delete-open-id-connect-provider \
--open-id-connect-provider-arn ${OPEN_ID_CONNECT_PROVIDER_ARN}
CDKコード
CDK Stackで次の2つのConstructを作成します。
- ID Provider
- IAM Role(およびInline Policy)
lib/aws-cdk-v2-project-stack.ts
import { Construct } from 'constructs';
import { Stack, StackProps, aws_iam } from 'aws-cdk-lib';
export interface AwsCdkV2ProjectStackProps extends StackProps {
principalFederatedSub: string;
}
export class AwsCdkV2ProjectStack extends Stack {
constructor(scope: Construct, id: string, props: AwsCdkV2ProjectStackProps) {
super(scope, id, props);
const accountId = Stack.of(this).account;
const region = Stack.of(this).region;
const gitHubIdProvider = new aws_iam.OpenIdConnectProvider(
this,
'GitHubIdProvider',
{
url: 'https://token.actions.githubusercontent.com',
clientIds: ['sts.amazonaws.com'],
}
);
const oidcDeployRole = new aws_iam.Role(this, 'GitHubOidcRole', {
roleName: 'github-oidc-role',
assumedBy: new aws_iam.FederatedPrincipal(
gitHubIdProvider.openIdConnectProviderArn,
{
StringLike: {
'token.actions.githubusercontent.com:sub':
props.principalFederatedSub,
},
},
'sts:AssumeRoleWithWebIdentity' //これを忘れるとStatementのActionが'sts:AssumeRole'となりOIDCでのAssumeRoleで使えなくなる。
),
});
const deployPolicy = new aws_iam.Policy(this, 'deployPolicy', {
policyName: 'deployPolicy',
statements: [
new aws_iam.PolicyStatement({
effect: aws_iam.Effect.ALLOW,
actions: [
's3:getBucketLocation',
's3:ListAllMyBuckets',
'cloudformation:CreateStack',
'cloudformation:CreateChangeSet',
'cloudformation:DeleteChangeSet',
'cloudformation:DescribeChangeSet',
'cloudformation:DescribeStacks',
'cloudformation:DescribeStackEvents',
'cloudformation:ExecuteChangeSet',
'cloudformation:GetTemplate',
],
resources: [
'arn:aws:s3:::*',
`arn:aws:cloudformation:${region}:${accountId}:stack/CDKToolkit/*`,
`arn:aws:cloudformation:${region}:${accountId}:stack/*/*`,
],
}),
new aws_iam.PolicyStatement({
effect: aws_iam.Effect.ALLOW,
actions: ['s3:PutObject', 's3:GetObject'],
resources: [`arn:aws:s3:::cdk-*-assets-${accountId}-${region}/*`],
}),
new aws_iam.PolicyStatement({
effect: aws_iam.Effect.ALLOW,
actions: ['ssm:GetParameter'],
resources: [
`arn:aws:ssm:${region}:${accountId}:parameter/cdk-bootstrap/*/version`,
],
}),
new aws_iam.PolicyStatement({
effect: aws_iam.Effect.ALLOW,
actions: ['iam:PassRole'],
resources: [
`arn:aws:iam::${accountId}:role/cdk-*-cfn-exec-role-${accountId}-${region}`,
],
}),
],
});
oidcDeployRole.attachInlinePolicy(deployPolicy);
}
}
CDK AppでprincipalFederatedSub
をConstructに注入します。ここではAccountはcm-rwakatsuki
、Repositoryはaws-cdk-v2-project
とし、どのブランチからも使用可能(*
)としています。必要に応じて書き換えてください。
bin/aws-cdk-v2-project.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { AwsCdkV2ProjectStack } from '../lib/aws-cdk-v2-project-stack';
const app = new cdk.App();
new AwsCdkV2ProjectStack(app, 'AwsCdkV2ProjectStack', {
env: { region: 'ap-northeast-1' },
principalFederatedSub:
'repo:cm-rwakatsuki/aws-cdk-v2-project:ref:refs/heads/*',
});
cdk deploy
でCDK Stackをデプロイします。
動作確認
GitHub側でActions SecretとしてIAM RoleのArnを登録します。
次のWorkflowで試してみます。OIDCにより取得したJWTを使用してAssumeRoleを行い、取得した一時クレデンシャルでCDK Deployを行っています。
.github/workflows/cicd.yaml
on:
push:
paths-ignore:
- '**/*.md'
jobs:
integration:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Cache CDK Dependency
uses: actions/cache@v3
id: cache_cdk_dependency_id
env:
cache-name: cache-cdk-dependency
with:
path: node_modules
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('package-lock.json') }}
restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}-
- name: Install CDK Dependency
if: ${{ steps.cache_cdk_dependency_id.outputs.cache-hit != 'true' }}
run: npm ci
- name: CDK Test
run: npm run test
deploy:
runs-on: ubuntu-latest
if: ${{ github.ref_name == 'main' }}
needs: integration
env:
AWS_OIDC_ROLE_ARN: ${{ secrets.AWS_OIDC_ROLE_ARN }}
AWS_REGION: ap-northeast-1
permissions:
id-token: write
contents: read
steps:
- name: debug
run: |
echo AWS_OIDC_ROLE_ARN: ${AWS_OIDC_ROLE_ARN/::*:/::XXXXXXXXXXXX:}
- name: Checkout
uses: actions/checkout@v3
- name: Assume Role
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: ${{ env.AWS_OIDC_ROLE_ARN }}
aws-region: ${{env.AWS_REGION}}
- name: Cache CDK Dependency
uses: actions/cache@v3
id: cache_cdk_dependency_id
env:
cache-name: cache-cdk-dependency
with:
path: node_modules
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('package-lock.json') }}
- name: Deploy
run: npm run deploy
Workflowを実行すると、OIDCによるAssumeRoleが行われ、CDK Deployを行うことができました!
注意点
ID ProviderのConstructクラスは2種類ある
今回ID Providerの作成に使用したのはOpenIdConnectProvider
というOIDC対応のID Providerを作成するConstructクラスです。
これと似ているものとしてSampProvider
というSAML2.0対応のID Providerを作成するものもあるので、取り違えないように注意。
同じURLのID Providerが既に作成されていないか注意する
デプロイしようとするID Providerと同じURLのものがすでに作成済みの場合は次のようなエラーとなります。よって冒頭で前回作成分の削除を行う必要がありました。
11:19:36 PM | CREATE_FAILED | Custom::AWSCDKOpenIdConnectProvider | IdProvider/Resource/Default
Received response status [FAILED] from custom resource. Message returned: EntityAlreadyExists: Provider with url https://token.
actions.githubusercontent.com already exists.
参考
- get-open-id-connect-provider — AWS CLI 2.5.8 Command Reference
- delete-open-id-connect-provider — AWS CLI 2.5.8 Command Reference
- class PolicyStatement · AWS CDK
以上