AWSの認証情報なしでGitHub Actionsからアクセスする with Terraform

みなさん明けましておめでとうございます。 この記事ではAWSのシークレットを用いずにGitHub Actions上からAWSに対してアクセスする方法や、その設定をTerraformで書く方法を紹介します。

読者としてはAWSやTerraform、GitHub Actionsをある程度使ったことがある人を想定しています。

概要

AWSにアクセスする際にはアクセスキーなどの認証情報を指定するのが最も簡単な方法です。 しかし、それらの認証情報を安全に管理するのは意外と面倒なのも事実です。

AWSにアクセスする際にはアクセスキーを使用する以外にも、外部のOIDC Providerと呼ばれる認証用のサーバーを使用する方法があります。 OIDC1とはOpen ID Connectの略で、簡単に説明すると、サーバー側でクライアント側からの認証情報を受け取り、それが正当なものであるかを確認できる仕組みです。
今回は、GitHubが提供するOIDC Providerを使用してAWSに対してアクセスします。

ちなみに、内部的にはGitHub Actionsの内部で取得できるトークンを使ってAssume RoleのAPIを使って一時的な認証情報を取得しているようです。

今回の構成によって行われる処理の流れは以下のようになります。

  1. GitHub ActionsのWorkflow内でGitHubのOIDC Providerが発行するトークンを取得する。
  2. 1.で取得したトークンを使ってAWSのAssume RoleのAPI2にリクエストを送る (GitHub Actions内で)
  3. AWS側で、送られてきたトークンを検証する (詳細は後述)
  4. AWS側で、送られてきたトークンが有効であれば一時的な認証情報を含んだレスポンスを返す
  5. GitHub Actions側では送られてきた認証情報を使用できるよう設定する
  6. GitHub Actions内でAWSのAPIを使用する

これらの処理を行うために、この記事では大きく4つのステップに分けて手順を説明していきます。

  1. GitHubの提供するOIDC ProviderをIAMで設定する。
  2. IAMのRoleやPolicyを作成する。
  3. GitHubのRepositoryに対してSecretを設定する。
  4. AWSを使用するGitHub Actionsのワークフローを作成する。

事前にGitHub Actionsを実行するRepositoryは作成済みとします。 この記事では執筆時点で最新のTerraform Provider (AWS と GitHub)を使用しています。

この記事ではIAM周りの設定を操作するため、間違った設定をしてしまった場合セキュリティ上の問題が発生する可能性があります。 この記事に書いてあることを試す際には充分ご注意ください。

OIDC Providerの設定

まず初めに、OIDC Providerの設定を追加します。 先述の通り、OIDC Providerから発行されたトークンをAWS側が検証してその有効性を確認します。
(上に書いた処理の流れの3)

OIDC Providerの発行するトークンには署名が含まれており、その署名の有効性を検証しています。 ちなみに、そのトークンの検証に使用するための公開鍵の情報などは特定のエンドポイントでアクセスできるように仕様で定められています3
例えば、今回使用するGitHub ActionsのOIDC Providerは https://token.actions.githubusercontent.com というエンドポイントがベースとなっていますが、その設定は https://token.actions.githubusercontent.com/.well-known/openid-configuration の URL から参照することができます。

コンソールからのOIDC Providerを追加する方法はAWSのドキュメント4にも書かれています。
ざっと説明すると、IAMのページのサイドバーからIdentity Providersのページを選択して、そのページの右上にある Add provider のボタンを押して追加するフォームを表示して、そこに必要情報を入れることで作成することができます。
(これ以上の詳細はAWSのドキュメントを見てください。)

では、その設定をTerraformで書いてみましょう。

data "tls_certificate" "provider_cert" {
  url = "https://token.actions.githubusercontent.com"
}

resource "aws_iam_openid_connect_provider" "github_oidc" {
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = [data.tls_certificate.provider_cert.certificates[0].sha1_fingerprint]
  url             = data.tls_certificate.provider_cert.url // https://token.actions.githubusercontent.com
}

データソースとリソースが1つずつあるのがわかると思います。 IAMにOIDC Providerの設定を追加する際にはOIDC Providerの使用するサーバー証明書の情報を指定する必要があります5。 上のコードに含まれるデータソースはその証明書のハッシュを取得するためのものです。

これを apply すると、AWSにOIDC Providerの設定が追加されてコンソールにも現れるはずです。

IAMのRole、Policyの作成

OIDC Providerの設定の次は、それによって認証されたクライアントがどの様な権限を持っているかを設定するための、RoleやPolicy の設定をします。

Policyの設定

この記事をここまで読んできた方はIAMのPolicyを1度くらいは書いたことがあると思いますので、詳細な説明は省きます。
以下の例では特定のs3 bucketに対するアクセスを許可していますが、他にも必要な権限を与えることができます。

resource "aws_iam_policy" "s3_access_policy" {
  name = "s3-access-policy"

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "s3:*"
      ],
      "Effect": "Allow",
      "Resource": "${aws_s3_bucket.foo.arn}"
    }
  ]
}
EOF
}

IAM Roleの設定

次に、OIDCを使って認証されるIAM Roleを設定します。 ここでは、作成したOIDC Providerによって認証された、特定のRepositoryのmasterブランチ上のWorkflowからのアクセスであることを条件にして、このRoleに割り当てられた権限を与えるようにします。

resource "aws_iam_role" "github_actions_role" {
  name = "github-actions-role"

  assume_role_policy = jsonencode(
    {
      Version = "2012-10-17"
      Statement = [
        {
          Effect = "Allow"
          Principal = {
            Federated = "${aws_iam_openid_connect_provider.github_oidc.arn}" // OIDC Provider の設定
          }
          Action = "sts:AssumeRoleWithWebIdentity",
          // アクセス元のRepositoryの限定
          Condition = {
            StringEquals = {
              "token.actions.githubusercontent.com:sub" = "repo:owner/repo:ref:refs/heads/master"
            }
          }
        }
      ]
  })
}

これがIAM Roleを作成するためのTerraformのコードです。 コード中のコメントにもある通り、今回作成したOIDC Provider経由で認証するように設定されていて、アクセス元も制限されています。

RoleとPolicyの関連付け

TerraformではIAM RoleとPolicyを作った後に、それらを関連付ける必要があります。 そのために、aws_iam_role_policy_attachmentのリソースを作成する必要があります。

resource "aws_iam_role_policy_attachment" "s3_policy_attatch" {
  policy_arn = aws_iam_policy.s3_access_policy.arn
  role       = aws_iam_role.github_actions_role.name
}

このコードはそのaws_iam_role_policy_attachmentを作成するためのコードです。 これは非常にシンプルで、関連づけるPolicyとRoleを指定しています。

以上で、AWS側の設定は完了となります。

GitHub Actions Secretの追加

先述の通り、GitHub ActionsでAWSにアクセスする際にはGitHub Actionsの提供するOIDC Providerからトークンを取得した後にそのトークンを使用してAssume RoleのAPIにリクエストを送って一時的な認証情報を取得します。

その際にはトークン以外にもどのRoleとして振る舞うかに関する情報が必要となります。 具体的には先程作成したIAM Roleのarnを指定する必要があります。

GitHub用のTerraform Provider6を使うことでGitHub ActionsのSecretをTerraformから作成することができ、今回はこれを使用します。

Terraformのコードは以下になります。

resource "github_actions_secret" "repo_role_arn" {
  repository      = "repo"
  secret_name     = "AWS_ROLE_ARN"
  plaintext_value = aws_iam_role.github_actions_role.arn
}

これもまた非常にシンプルなリソースで、リポジトリ名、シークレット名、シークレットの中身を指定するだけです。

ちなみに、このリソースを上記のコードの様にplaintext_valueを指定して使用するとtfstateのファイル内にシークレットが平文の状態で保存されてしまいます7。 今回の使用目的ではIAM RoleのARN自体は認証情報などとは異なりtfstateに保存されること自体は問題ないと判断した上でこのリソースを使用しています。 しかし、使い方によっては意図せずtfstate内に機密情報が入ってしまうリスクもありますので使用される際には充分注意してください。

ここまででTerraformは終わりです、お疲れ様でした。

GitHub Actionsの記述

最後に、GitHub ActionsのWorkflowを記述していきます。

AWS公式のAction8を使えば簡単にAWSの認証情報を取得して設定することができます。

GitHub Actionsの yaml は以下のようになります。

name: CI
on:
  push:
    branches: [ master ]
permissions:
  id-token: write
  contents: read 
jobs:
  build:
    runs-on: ubuntu-20.04
    steps:
    - uses: actions/checkout@v2
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        role-to-assume: ${{ secrets.AWS_ROLE_ARN }} # 先程作成したGitHub Actions SecretからRoleのARNを取得する
        aws-region: ap-northeast-1
    # ここからAWSにアクセスできるようになる

aws-actions/configure-aws-credentials@v1を使用することでAWSの認証情報を取得してきています。 コメントにもある通り、assume-to-roleに先程作成したSecretの値を入れることで作成したIAM Roleが使えるようにしています。

ちなみにネット上で同じことをしようとしている記事ではconfigure-aws-credentialsのバージョンをv1ではなくmasterと指定しているものもあります。 これは、その記事が執筆された時点ではまだmasterではOIDCを使用した認証情報の取得機能が入っていたが、v1ではそれが使えなかったためと考えられますが、この記事を書いている時点ではv1でも使えるようになっています。

上記のyamlの最後に、本来GitHub Actionsを使ってやりたかったことをやるコードを書けば、AWSのAPIを使って色々なことができるようになるはずです!!

まとめ

この記事ではGitHub Actionsの実行時にAWSへ認証情報をあらかじめ保持することなくAWSのAPIを使用する方法を紹介しました。 認証情報の管理に頭を悩ませている方はぜひ参考にしてみてください!

Reference

この記事を書くにあたっては脚注にあるサイト以外にも以下のサイトを参考にしました