1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitHub Actions でTerraform planを実行する

Posted at

やりたいこと

githubactionsを使用して、特定のブランチにPRが作成された場合にterraform planを実行するワークフローを構築していきます!
terraform plan 実行時にはAWSに対しての認証が必要になりますが、今回はAWSのOpenID Connectを使用していきます。
これにより以下のようなメリットがあります。

  • Actionsで使用するキー情報をSecretで管理せずに済む
  • Actionsで使用するIAM Userを作成する必要がない

特にIAM Userの認証情報を使用してActionsを実施する従来のパターンで度々起こっていた、IAM Userのキー情報が流出する等のインシデントを防げる点が大きいかと思います。
また今回は構築時にハマったポイントや、改善点として実施してみて良かったことも記載していきますので参考になりますと幸いです!

全体の構成

image.png

今回の構成では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プロバイダの設定値として以下は固定値です。
公式の解説がわかりやすかったので参考にしてください。
image.png

作成した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が実行されます。
image.png

ハマったこと

実際に上記planを実行できるようになるまでにハマったポイントがあるのでご紹介します。

まず、筆者の環境ではIdentity Centerを使用したSSOログインを使用しており、.aws/configでprofileを指定しています。
そしてワークフローが実行される前に、terraformの挙動をローカルで確認したかったので、profileを以下のように指定してplan内容に問題がないかローカルで確認していました。

provider.tf
provider "aws" {
  region  = var.region
  profile = var.profile
}
variables.tf
variable "region" {
  default = "ap-northeast-1"
}
variable "profile" {}

terraform.tfvars
profile = "your-profile"

tfvarsに記載したクレデンシャル情報はgithub上に載せたくなかったので.gitignoreで弾いています。
そしてplan内容に問題がないことを確認しからpush⇢PR作成⇢Actions実行までいったのですが、以下のようにterraform plan実行されず先に進みませんでした。

image.png

当初の予想では、Actions実行の際に使用する認証情報には、Secretに保存したIAM Roleが使用されるので、provider.tfでprofileを記載したままでも問題ないかと思っていたのですが、providerで指定したprofileを使用しようとする挙動があり、うまくいきませんでした。

provider.tf
provider "aws" {
  region  = var.region
  profile = var.profile
}

解決策

そこで一旦provider.tfからprofile,regionの情報を以下のように削除したところ正常にActionsが実行されました。

provider.tf
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情報を記載して読み込ませるようにします。

.envrc
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を読み込むようにできました。

終わりに

多分筆者がつまずいた部分は、多くの方は意識しなくてもなんとなくそうなんだろうなで回避できるんだろうなあと思いつつ、そこからどこに原因があるのか検証したり、改善点を見つけることの大切さを知りました。。
この記事が誰かの参考になれば幸いです!
また記事内で誤っている部分があればコメントにて指摘していただけますと幸いです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?