やりたいこと
githubactionsを使用して、特定のブランチにPRが作成された場合にterraform planを実行するワークフローを構築していきます!
terraform plan
実行時にはAWSに対しての認証が必要になりますが、今回はAWSのOpenID Connectを使用していきます。
これにより以下のようなメリットがあります。
- Actionsで使用するキー情報をSecretで管理せずに済む
- Actionsで使用するIAM Userを作成する必要がない
特にIAM Userの認証情報を使用してActionsを実施する従来のパターンで度々起こっていた、IAM Userのキー情報が流出する等のインシデントを防げる点が大きいかと思います。
また今回は構築時にハマったポイントや、改善点として実施してみて良かったことも記載していきますので参考になりますと幸いです!
全体の構成
今回の構成ではAWSでGithubActions用のIDプロバイダーを構築していきます。
これによりterrraform plan,applyといったAWS側への認証、認可が必要なGithubActioinsを実行する事ができます。
具体的には全体構成図の3,4
の部分で、Github側のIDプロバイダーとAWS側に作成したIDプロバイダーで認証を行い、
AWS側のIDプロバイダーに割り当てたIAM Roleの許可ポリシーの範囲でActionsを実行できます。
GitHub Actions用のOIDCを構築する
AWS側で構築していきます。
githubactions用のIDプロバイダの設定値として以下は固定値です。
公式の解説がわかりやすかったので参考にしてください。
作成したIDプロバイダに割り当てるIAM Roleの信頼ポリシーは環境によって変動します。
以下ではワイルドカードを使用していますが、ブランチ単位でも設定できます。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::${IDプロバイダのARN}:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:${githubの組織名}/&{リポジトリ名}:*"
}
}
}
]
}
上記の信頼ポリシーでaud
,sub
の部分について少し解説します。
- aud(Audience)
- トークンがどのサービスを対象に発行されているかチェックする。全体構成でいうと
2
の部分でAWSのIDプロバイダーに対してのトークンが発行されるのでここは基本固定値になります。
- トークンがどのサービスを対象に発行されているかチェックする。全体構成でいうと
- sub(Subject)
- トークンがどのリソースに関連付けられているかを表します。今回はGithubの組織名、リポジトリ、ブランチを指定しています。
AWSのIAM Role側から見ると、githubのIDプロバイダーで発行されたトークンがどのリソース(sub)と関連付けられているか、何を対象(aud)にしているのかを確認しています。
Workflows
以下の内容で実施しました。
Actionsの概要については前回の記事を参考にしていただけますと幸いです。
name: 'Terraform test'
on:
pull_request:
branches:
- main
jobs:
Plan:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup AWS
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Get Terraform version
id: terraform-version
run: |
echo "value=$(cat .terraform-version)" >> $GITHUB_OUTPUT
- name: setup terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ steps.terraform-version.outputs.value }}
- name: Terraform Format
id: fmt
run: terraform fmt -check
- name: Terraform Init
run: terraform init
- name: Terraform Plan
working-directory: .
run: terraform plan
continue-on-error: true
上記の - name: Setup AWS
で指定しているsecretの概要は以下です。
- ${{ secrets.AWS_ROLE_ARN }}
- AWSで作成したIDプロバイダーに割り当てたRoleのARNをSecretに保存して指定
- ${{ secrets.AWS_REGION }}
- Actionsを実行するregionをSecretに保存して指定
実施
ここまで準備したらあとはmainブランチに対してPRを作成すればactionnsが実行されます。
ハマったこと
実際に上記planを実行できるようになるまでにハマったポイントがあるのでご紹介します。
まず、筆者の環境ではIdentity Centerを使用したSSOログインを使用しており、.aws/config
でprofileを指定しています。
そしてワークフローが実行される前に、terraformの挙動をローカルで確認したかったので、profileを以下のように指定してplan内容に問題がないかローカルで確認していました。
provider "aws" {
region = var.region
profile = var.profile
}
variable "region" {
default = "ap-northeast-1"
}
variable "profile" {}
profile = "your-profile"
tfvarsに記載したクレデンシャル情報はgithub上に載せたくなかったので.gitignore
で弾いています。
そしてplan内容に問題がないことを確認しからpush⇢PR作成⇢Actions実行までいったのですが、以下のようにterraform plan
実行されず先に進みませんでした。
当初の予想では、Actions実行の際に使用する認証情報には、Secret
に保存したIAM Roleが使用されるので、provider.tfでprofileを記載したままでも問題ないかと思っていたのですが、providerで指定したprofileを使用しようとする挙動があり、うまくいきませんでした。
provider "aws" {
region = var.region
profile = var.profile
}
解決策
そこで一旦provider.tf
からprofile,regionの情報を以下のように削除したところ正常にActionsが実行されました。
provider "aws" {
}
成功したActionsと、先に進まなかったActionsともにIDプロバイダー間の認証は成功していたので、おそらくprofile
を明示的に指定した事が原因と思われます。
direnvを使ってみる
よし、これでOK!と思ったのですが、こうするとローカルでplanを実行したい時にまたprofile情報を書いて、pushするときは消して、、のようにかなり面倒だと感じました。
そこでなんとかprofile
情報をコード上に記載しなくてもローカルで読み取れないか考えました。
profile
を環境変数で設定すれば解決できそうですが、そうすると他ディレクトリのprofile
でも同じ値が読み込まれるので問題が起こりそうです。
そこで今回はdirenv
を使用しました。
以下の記事がわかりやすかったので参考にしてください。
https://qiita.com/sasshi_i/items/609044aa106cdcb43a89
簡単に解説すると、terraformコマンドを実行するディレクトリの配下に.envrc
ファイルを用意してそこにprofile
情報を記載して読み込ませるようにします。
export AWS_PROFILE="your-profile"
export AWS_DEFAULT_REGION="ap-northeast-1"
export AWS_SDK_LOAD_CONFIG=1
そうすることでterrafoprm plan
を実行する際に、ローカルでは.envrc
ファイルが読み込まれ、ActionsではSecretに保存したIAM Roleを読み込むようにできました。
終わりに
多分筆者がつまずいた部分は、多くの方は意識しなくてもなんとなくそうなんだろうなで回避できるんだろうなあと思いつつ、そこからどこに原因があるのか検証したり、改善点を見つけることの大切さを知りました。。
この記事が誰かの参考になれば幸いです!
また記事内で誤っている部分があればコメントにて指摘していただけますと幸いです。