GitHub Actions の OIDC 連携を AWS CDK で実現する方法
1. はじめに
GitHub Actions から AWS に OIDC (OpenID Connect) を使って安全にアクセスするために必要なリソースの定義を AWS CDK を利用して行ったので, その記録です.
今回は, 静的ファイルを S3 バケットにアップロードし, CloudFront で配信するユースケースを前提にして想定して定義を行いました.
※Github Actions の説明や AWS CDK の説明は本記事の対象外です
2. 作成したもの
2.1 ソースコード
作成したソースコードは以下で公開しています.
2.2 構成概略図
3. CDK で定義したリソースの解説
まずは, stack 定義全体から.
lib/stacks/aws-oidc-provider-for-github-stack.ts
import {
aws_iam as iam,
aws_s3 as s3,
aws_cloudfront as cloudfront,
aws_cloudfront_origins as cloudfront_origins,
RemovalPolicy,
Stack,
StackProps,
} from "aws-cdk-lib";
import { Construct } from "constructs";
export interface AwsOIDCProviderForGithubStackProps extends StackProps {
readonly githubAccountName: string;
readonly repositryName: string;
}
export class AwsOIDCProviderForGithubStack extends Stack {
constructor(
scope: Construct,
id: string,
props: AwsOIDCProviderForGithubStackProps
) {
super(scope, id, props);
const accountId = Stack.of(this).account;
const region = Stack.of(this).region;
const bucket = new s3.Bucket(this, "Bucket", {
bucketName: `${props.repositryName}-assets-${accountId}-${region}`,
removalPolicy: RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
const s3BucketOrigin =
cloudfront_origins.S3BucketOrigin.withOriginAccessControl(bucket);
new cloudfront.Distribution(this, "Distribution", {
defaultRootObject: "index.html",
defaultBehavior: {
origin: s3BucketOrigin,
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
});
const gitHubIdProvider = new iam.OpenIdConnectProvider(
this,
"GitHubIdProvider",
{
url: "https://token.actions.githubusercontent.com",
clientIds: ["sts.amazonaws.com"],
}
);
const oidcDeployRole = new iam.Role(this, "GitHubOidcRole", {
roleName: `github-${props.repositryName}-oidc-role`,
assumedBy: new iam.FederatedPrincipal(
gitHubIdProvider.openIdConnectProviderArn,
{
StringEquals: {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
},
StringLike: {
"token.actions.githubusercontent.com:sub": `repo:${props.githubAccountName}/${props.repositryName}:*`,
},
},
"sts:AssumeRoleWithWebIdentity"
),
});
const deployPolicy = new iam.Policy(this, "DeployPolicy", {
policyName: `github-${props.repositryName}-deploy-policy`,
// 必要に応じてポリシーを追加する
// 以下は Amazon S3 に静的コンテンツを配置するためのポリシー
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["s3:ListBucket", "s3:DeleteObject", "s3:PutObject"],
resources: [bucket.bucketArn, `${bucket.bucketArn}/*`],
}),
],
});
oidcDeployRole.attachInlinePolicy(deployPolicy);
new CfnOutput(this, "DeployRoleArn", { value: oidcDeployRole.roleArn });
new CfnOutput(this, "BucketName", { value: bucket.bucketName });
}
}
大きく以下 4 つのパートに分かれています
- 静的ファイルの配置先の定義
- IAM OIDC ID identity provider の定義
- 3.3 GitHub Action が使う IAM Role, Policy の定義
- GitHub Actions の設定に必要な値を出力
3.1 静的ファイルの配置先の定義
最初に静的ファイルの配置先の定義を行いました.
// GitHub Actions から書き込む S3 バケット
const bucket = new s3.Bucket(this, "Bucket", {
// ...
});
// S3 バケットに配置した静的ファイルを配信する CloudFront Distribution (とオリジン設定)
const s3BucketOrigin =
cloudfront_origins.S3BucketOrigin.withOriginAccessControl(bucket);
new cloudfront.Distribution(this, "Distribution", {
// ...
},
});
ちなみに L2 コンストラクトで OAC を定義できるようになったのは最近です(今回初めて使いました).
aws-cdk v2.156.0 リリースノート
3.2 IAM OIDC ID identity provider の定義
// GitHub 用の IAM エンティティを定義
// OpenIdConnectProvider は OpenID Connect (OIDC) 標準をサポートする外部アイデンティティプロバイダー (IdP) サービスを IAM エンティティとして扱う際に利用する
const gitHubIdProvider = new iam.OpenIdConnectProvider(
this,
"GitHubIdProvider",
{
url: "https://token.actions.githubusercontent.com",
clientIds: ["sts.amazonaws.com"],
}
);
3.3 GitHub Action が使う IAM Role, Policy の定義
const oidcDeployRole = new iam.Role(this, "GitHubOidcRole", {
// ...
// 信頼ポリシー の設定 (3.2 で作成した IAM OIDC ID identity provider がこの role を利用できるようにする)
assumedBy: new iam.FederatedPrincipal(
// principal (IAM OIDC ID identity provider) の指定
gitHubIdProvider.openIdConnectProviderArn,
// 条件の指定
{
// 指定したリポジトリの GitHub Actions のみこの IAM Role を引き受けることを許可する(AssumeRole を許可する).
StringEquals: {
// GitHub Actions から AWS にアクセスする際に 用いる Token の`aud` (audience) クレームが "sts.amazonaws.com" の場合のみこの IAM Role を引き受けることを許可する(AssumeRole を許可する).
// 「AWS STS で認証し、IAM ロールを引き受けるためのトークンである」ことを確認する.
// https://github.com/aws-actions/configure-aws-credentials#configure-aws-credentials-for-github-actions
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
},
// 指定したリポジトリの GitHub Actions のみこの IAM Role を引き受けることを許可する(AssumeRole を許可する).
StringLike: {
"token.actions.githubusercontent.com:sub": `repo:${props.githubAccountName}/${props.repositryName}:*`,
},
},
// WebIdentity に対してのみこの IAM Role を引き受けることを許可する(AssumeRole を許可する).
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html
"sts:AssumeRoleWithWebIdentity"
),
});
// Amazon S3 に静的コンテンツを配置するためのポリシーを追加する
const deployPolicy = new iam.Policy(this, "DeployPolicy", {
policyName: `github-${props.repositryName}-deploy-policy`,
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["s3:ListBucket", "s3:DeleteObject", "s3:PutObject"],
resources: [bucket.bucketArn, `${bucket.bucketArn}/*`],
}),
],
});
oidcDeployRole.attachInlinePolicy(deployPolicy);
3.4 GitHub Actions の設定に必要な値を出力
GitHub Actions に必要な値を出力します.
new CfnOutput(this, "DeployRoleArn", { value: oidcDeployRole.roleArn });
new CfnOutput(this, "BucketName", { value: bucket.bucketName });
cdk deploy
が完了すると以下のようにターミナルに出力されます
...
Outputs:
AwsOIDCProviderForGithubStack.BucketName = bucketname-123456789012-region
AwsOIDCProviderForGithubStack.DeployRoleArn = arn:aws:iam::123456789012:role/rolename
...
これらの値を 3.5 で使います.
3.5 GitHub Actions の job 定義
3.4 で出力した値を IAM Role の ARN をsecrets に追加します.
- AWS_OIDC_ROLE_ARN: {
AwsOIDCProviderForGithubStack.DeployRoleArn の値} - BUCKET_NAME: {AwsOIDCProviderForGithubStack.BucketName の値}
Settings > Security > Actions > Secrets > New repositry secret
job の定義は以下のようにしました.
ビルドジョブで作成したファイルをダウンロードし, S3 バケットへコピーしています.
jobs:
deploy:
runs-on: ubuntu-latest
needs: build
env:
AWS_OIDC_ROLE_ARN: ${{ secrets.AWS_OIDC_ROLE_ARN }}
BUCKET_NAME: ${{ secrets.BUCKET_NAME }}
AWS_REGION: ap-northeast-1
permissions:
id-token: write
contents: read
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Assume Role
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.AWS_OIDC_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Deploy to AWS
run: |
aws s3 cp ./dist "s3://${{ env.BUCKET_NAME }}" --recursive
timeout-minutes: 3
こちらのファイルは以下に配置しています.
GitHub virtual-hippo/walk-the-dog
4. 最後に
最後まで読んでくださりありがとうございました.
もし, 誤った記述を見つけた場合には指摘いただけると助かります.
参考
以下の Web ページを参考にしました
Discussion