この記事は ハンズラボ AdventCalendar2021 3日目の記事です。
#はじめに
今回は、こちらの記事のCDK化(Python)を行っていきます。
構成やcfnテンプレートなど詳細はこちらをご参照ください。
前提条件
- GitHub Actions OpenID Connect (OIDC) で多段 AssumeRole するを読み構成を理解していること
- CDKがインストールされ実行環境が準備されていること
実装
ではさっそくやっていきます。
初期設定
ディレクトリを作成し、cdk init
を実行してプロジェクトを立ち上げます。
$ mkdir github_oicd
$ cd github_oicd
$ cdk init app --language python
これで準備完了ですので、早速コーディングしていきます。
Provider作成
まずはProviderを作成していきます。
ここはドキュメント通りに値をセットするだけですので、簡単に実装できました。
ドキュメントはこちら
github_oidc_provider = iam.OpenIdConnectProvider(self,
client_ids=["sts.amazonaws.com"],
thumbprints=["a031c46782e6e6c662c2c87c76da9aa62ccabd8e"],
id="GithubOicdProvider",
url="https://token.actions.githubusercontent.com",
)
bastion_role(踏み台ロール)作成
ここから少し難易度が上がった印象です。
Principalに先ほど作成したProviderを設定する箇所でかなり苦戦しましたが、最後はドキュメントが助けてくれました。
(最初からドキュメントちゃんと読めという話ですが、、)
助けてくれたドキュメント達 ( Role, FederatedPrincipal, PolicyDocument, PolicyStatement )
GitHubOrgName = "example"
GitHubRepoName = "example"
conditions = {
"StringEquals" : {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike" : {
"token.actions.githubusercontent.com:sub" : f"repo:{GitHubOrgName}/{GitHubRepoName}:*"
}
}
bastion_role = iam.Role(self,
assumed_by=iam.FederatedPrincipal(
github_oidc_provider.open_id_connect_provider_arn,
conditions=conditions,
assume_role_action="sts:AssumeRoleWithWebIdentity"
),
path="/",
inline_policies={
"AssumeRolePolicy": iam.PolicyDocument(
statements=[
iam.PolicyStatement(
effect=iam.Effect.ALLOW,
actions=["sts:GetCallerIdentity"],
resources=['*']
),
iam.PolicyStatement(
effect=iam.Effect.ALLOW,
actions=[
"sts:AssumeRole",
"sts:TagSession"
],
resources=["*"]
)
]
)
},
id="BastionRole",
role_name="BastionRole",
)
deploy_role(デプロイ用ロール)作成
最後のロール作成です。
ここが一番難しかったです。
一度に複数のStatementを設定したかったのですが、想定している設定がなかなかできなかったので、最初に1つ設定して後付けしています。
新たに助けてくれたドキュメント達 ( ArnPrincipal, PrincipalWithConditions )
ExternalId = "example1234"
aws_principal = iam.ArnPrincipal(
arn=bastion_role.role_arn,
)
# add condition
aws_principal_with_condition = iam.PrincipalWithConditions(aws_principal, {
"StringEquals": {
"sts:ExternalId": ExternalId
}
})
github_actions_deploy_role = iam.Role(self,
assumed_by=aws_principal_with_condition,
id="GitHubActionsDeployRole",
path="/",
role_name="GitHubActionsDeployRole",
)
# add policy
github_actions_deploy_role.assume_role_policy.add_statements(iam.PolicyStatement(
actions=["sts:TagSession"],
principals=[
aws_principal.grant_principal,
],
))
この様に書くと以下の様なポリシーになります。
完璧です。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::{AccoutID}:role/BastionRole"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "example1234"
}
}
},
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::{AccoutID}:role/BastionRole"
},
"Action": "sts:TagSession"
}
]
}
##完成系
from aws_cdk import (
aws_iam as iam,
core,
)
class AwsCdkOicdStack(core.Stack):
def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
ExternalId = "example1234"
GitHubOrgName = "example"
GitHubRepoName = "example"
github_oidc_provider = iam.OpenIdConnectProvider(self,
client_ids=["sts.amazonaws.com"],
thumbprints=["a031c46782e6e6c662c2c87c76da9aa62ccabd8e"],
id="GithubOicdProvider",
url="https://token.actions.githubusercontent.com",
)
conditions = {
"StringEquals" : {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike" : {
"token.actions.githubusercontent.com:sub" : f"repo:{GitHubOrgName}/{GitHubRepoName}:*"
}
}
bastion_role = iam.Role(self,
assumed_by=iam.FederatedPrincipal(
github_oidc_provider.open_id_connect_provider_arn,
conditions=conditions,
assume_role_action="sts:AssumeRoleWithWebIdentity"
),
path="/",
inline_policies={
"AssumeRolePolicy": iam.PolicyDocument(
statements=[
iam.PolicyStatement(
effect=iam.Effect.ALLOW,
actions=["sts:GetCallerIdentity"],
resources=['*']
),
iam.PolicyStatement(
effect=iam.Effect.ALLOW,
actions=[
"sts:AssumeRole",
"sts:TagSession"
],
resources=["*"]
)
]
)
},
id="BastionRole",
role_name="BastionRole",
)
aws_principal = iam.ArnPrincipal(
arn=bastion_role.role_arn,
)
# add condition
aws_principal_with_condition = iam.PrincipalWithConditions(aws_principal, {
"StringEquals": {
"sts:ExternalId": ExternalId
}
})
github_actions_deploy_role = iam.Role(self,
assumed_by=aws_principal_with_condition,
id="GitHubActionsDeployRole",
path="/",
role_name="GitHubActionsDeployRole",
)
# add policy
github_actions_deploy_role.assume_role_policy.add_statements(iam.PolicyStatement(
actions=["sts:TagSession"],
principals=[
aws_principal.grant_principal,
],
))
デプロイ & 動作確認
では、実際にデプロイして動作確認してみます。
Github Actions Workflowはこちらを参照してください。
$ cdk deploy
無事、それぞれのroleでAWSアカウント情報を取得することができました。
所感
個人的にはcfnよりもコーディングが楽しかったです。
今回初めてCDKを使って構築してみましたが、普段使用している言語だったためか、かなりスムーズに構築できたと思います。
cfnを初めて触った時はひどいもんでした。
みなさんも気分転換に一度CDKで構築してみてください。楽しいはずです!
以上!