tech.guitarrapc.cóm

Technical updates

GitHub Actions が OpenId Connect に対応したので AWS OIDC Provider と連携する

前々から言われていた GitHub Actions で OpenID Connect 経由で、各種Cloud Provider の認証を得るのが GA しました。 めでたい。

github.blog

これにより、aws-actions/configure-aws-credentials のみで認証が組めるようになったので見てみましょう。

github.com

tl;dr;

  • GitHub Actions でAWS操作をするために、IAM User が不要になるので神。
  • セルフホストな GitHub Actions を EC2 で動かしているなら、素直に Instance Role を使うので十分というのもある。(何か事情があれば OpenId Connector でもいい)
  • AWS アカウントで OpenId Connect Provider は 一意です。1 AWSアカウントで複数環境を持っている & 環境ごとに IAM Role を持っている場合は、環境ごとに OpenId Conect Provider を作ろうとして失敗しないように。
  • 並列度高く認証を取得しようとすると失敗することが多いので注意

動作例

設定がただしければ、必要な IAM Role Arn を role-to-assume に指定するだけで、その Role 権限で 操作ができるようになります。

これで

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@master
        with:
          aws-region: ap-northeast-1
          role-to-assume: "作ったIAM Role の Arn を入れる"
          role-session-name: "適当な名前"

こうなる。

f:id:guitarrapc_tech:20211105022035p:plain
GitHub OpenId Conector と AWS OIDC Provider でAWS の操作ができる

基本

GitHubから公式にドキュメントが公開されたのでこれに従いましょう。先日までuses: aws-actions/configure-aws-credentials@v1 になってたせいでドキュメントが嘘つきでしたが @master に修正されています。

docs.github.com

なお、https://github.com/aws-actions/configure-aws-credentials は、uses: aws-actions/configure-aws-credentials@v1 になっているので動きません。uses: aws-actions/configure-aws-credentials@master に読み替えましょう。

Terraform で AWSを用意する。

用意する必要があるのは、OIDC Provider と IAM Role です。

  • OIDC Provider は、GitHub OIDC と信頼関係を結ぶのに必要です。
  • IAM Role は OIDC Provider 経由で GitHub OIDC でリクエストされたときに、リクエスト元のリポジトリオーナー/リポジトリ名:ブランチ名 を検証し条件にマッチしたらそのRoleを Assume して利用できるようにします。つまり、このRoleに、リポジトリの制約と必要な IAM Policyを振ればok。

全体を示します。

gist.github.com

OIDC Provider を用意する

値はすべて固定なので単純です。terraform ならこうなります。

// oidc provider
resource "aws_iam_openid_connect_provider" "main" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = ["a031c46782e6e6c662c2c87c76da9aa62ccabd8e"]
}

client_id_list がGA前と後で変わっています。古いバージョンではここで、リクエスト元のリポジトリURL を指定していましたが、今は sts.amazonaws.com といつものになりました。標準に沿ってくれてよかった。

GitHub の Endpoint が確認できますね。

https://token.actions.githubusercontent.com/.well-known/openid-configuration

{"issuer":"https://token.actions.githubusercontent.com","jwks_uri":"https://token.actions.githubusercontent.com/.well-known/jwks","subject_types_supported":["public","pairwise"],"response_types_supported":["id_token"],"claims_supported":["sub","aud","exp","iat","iss","jti","nbf","ref","repository","repository_owner","run_id","run_number","run_attempt","actor","workflow","head_ref","base_ref","event_name","ref_type","environment","job_workflow_ref"],"id_token_signing_alg_values_supported":["RS256"],"scopes_supported":["openid"]}

IAM Role を用意する

順番に解説します。

重要なのは、Assume Role です。 Assume Role で OIDC Provider からのリクエストを検証しています。

  • principal は、認証を委譲されて受けるので type: "Federated" + 先ほど作った OIDC Provider の arn を指定 します。
  • condition で、リクエスト元が repo:<GitHubOwner>/<Repositry>:Branch の条件とマッチするか検証します。

OIDC 経由で、許可した「リポジトリ、かつブランチ」だった場合に IAM Role をsts:AssumeRoleWithWebIdentity としてAssume できるようにします。

さて、複数のリポジトリで同じロールを使いたいケースはどうすればいいでしょうか?

IAM Policy の condition は、ワイルドカード一致するかを StringLike で検証できますが、これは value が1要素 (=単一リポジトリ) のときにしか機能しません。 value が複数要素(=複数リポジトリ)でもワイルドカードで一致するかを見る場合は、ForAnyValue: + StringLike を用います。

今回は 分岐させましたが、別に ForAnyValue で初めから書いてもいいでしょう。

data "aws_iam_policy_document" "github_oid_assume_role_policy" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRoleWithWebIdentity"]
    principals {
      type        = "Federated"
      identifiers = [aws_iam_openid_connect_provider.main.arn]
    }
    # aud があるとはじかれてるので、aud の値がおかしいっぽい。 aws-actions/configure-aws-credentials の仕組みからすると、sts.amazonaws.com が来るはず。 <- github.com/aws-actions/configure-aws-credentials ではなしになってる :(
    # condition {
    #   test     = "StringEquals"
    #   variable = "token.actions.githubusercontent.com:aud"
    #   values   = ["https://github.com/${var.github_owner}"]
    # }
    condition {
      test     = length(var.github_oidc_repo_names) == 1 ? "StringLike" : "ForAnyValue:StringLike"
      variable = "token.actions.githubusercontent.com:sub"
      values   = [for item in var.github_oidc_repo_names : "repo:${var.github_owner}/${item}:*"]
    }
  }
}

AssumeRole ができてしまえば IAM Roleを作るだけです。今回は、aws sts get-caller-identity を実行できるようにしてみましょう。

特にいうことはないですね。終わり。

data "aws_iam_policy_document" "github_actions" {
  // allow running `aws sts get-caller-identity`
  statement {
    effect    = "Allow"
    actions   = ["sts:GetCallerIdentity"]
    resources = ["*"]
  }
}

resource "aws_iam_policy" "github_actions" {
  name        = "githubactions_policy"
  path        = "/"
  description = "Policy for GitHubActions"
  policy      = data.aws_iam_policy_document.github_actions.json
}
resource "aws_iam_role" "test_role" {
  name               = "githubactions-oidc-role"
  path               = "/"
  assume_role_policy = data.aws_iam_policy_document.github_oid_assume_role_policy.json
  policy_arns = [
    aws_iam_policy.github_actions.arn
  ]
}

リソースを作成

さて、これで AWS にリソースを作るとこんな感じになります。

今回は私は、guitarrapc/githubactions-lab と guitarrapc/infrastructure の 2リポジトリから GitHub Actions 経由で認証を受けられるようにしました。 IAM Role の Trust relationships > Conditions で2リポジトリが ForAnyValue:StringLike で指定されているのがわかりますね。

f:id:guitarrapc_tech:20211105021103p:plain
IAM > Identity providers > token.actions.githubusercontent.com

f:id:guitarrapc_tech:20211105021210p:plain
IAM > Roles > githubactions-oidc-role

ここまでのどれかにミスがあると Assume Role されません。 うまくいかない場合は何度も見直すことになるでしょう。

GitHub Actions の構成

Workflow を用意します。 キーポイントは3つです。

  • permissions で、id-token: write で書き込み権限が必要です。 permissions: write-all でもいいのですが、現状では permissions を指定しないとうごかないので注意です。
  • role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}secrets.AWS_ROLE_TO_ASSUME は、GitHub Secret に AWS_ROLE_TO_ASSUME という名前で先ほど作った IAM Role の Arn を仕込んであります。Arnのフォーマットは、今回の例なら arn:aws:iam::xxxxxxxxxxxxx:role/githubactions-oidc-role というフォーマットになります。
  • role-session-name で、CloudTrail イベントにユーザー名が出るのでいい感じの名前にしましょう。
  • uses: aws-actions/configure-aws-credentials@master を指定します。 uses: aws-actions/configure-aws-credentials@v1 でないので注意してください。

github.com

name: aws oidc credential

on:
  workflow_dispatch:
  push:
    branches: ["main"]

# allow use id-token
permissions:
  id-token: write # required!
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Configure AWS Credentials
        # must use "master", not "v1". v1 is not yet released to use latest role-to-assume.
        # Error: Credentials could not be loaded, please check your action inputs: Could not load credentials from any providers
        uses: aws-actions/configure-aws-credentials@master
        with:
          aws-region: ap-northeast-1
          role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
          role-session-name: GitHubActions-${{ github.run_id }}
          role-duration-seconds: 900 # minimum: 900sec, maximum: iam role session duration
      - name: get-caller-identity is allowed to run on role.
        run: aws sts get-caller-identity

実行してみると、冒頭のスクショのように成功するはずです。

$ aws sts get-caller-identity
{
    "UserId": "AROASJXUOK5UM7XZKRYTB:GitHubActions-1420393244",
    "Account": "***",
    "Arn": "arn:aws:sts::***:assumed-role/githubactions-oidc-role/GitHubActions-1420393244"
}

うまくできましたか? おめでとうございます!

ダメでした? 私も失敗を繰り返したので、経験したエラーと対処をFAQに載せておきます。

FAQ

GitHub Actionsで実行してみたら動作しない。

エラーメッセージ Credentials could not be loaded, please check your action inputs: Could not load credentials from any providers

この場合、AWS OIDC Provider の設定がおかしい or GitHub Actions の permissions が抜けています。

  • (AWSの設定ミス) OIDC Provider の client_id_list に "sts.amazonaws.com" ではなく、リポジトリのURL を指定している。(古いブログが記事がそうなっている)
  • (GitHub Actions Workflowの設定ミス) Workflow で、permissions がない。デフォルトの GitHub Actionsは write-all のはずですが、まだ id-tokens は含まれていないようです。明示的に permissions: write-all を指定するか、id-token: write を指定しましょう。

GitHub Actionsで実行してみたら動作しない。

エラーメッセージ Not authorized to perform sts:AssumeRoleWithWebIdentity

このケースは、OIDC Provider は問題なく、AssumeRole の設定にミスがあります。

  • (AWSの設定ミス) AssumeRole の Action が "Action": "sts:AssumeRoleWithWebIdentity" になっていない。
  • (AWSの設定ミス) 複数リポジトリなのに ForAnyValue:StringLike ではなく StringLike で判定している。
  • (AWSの設定ミス) AWS の IAM Role の Assume Policy が設定しようとしている GitHub Owner/Repository:ブランチ と内容と一致していない。

過去にある類似記事との差分

GA前の内容で、結構ずれています。GA前の記事の内容でやると失敗するので注意です。(aws-actions/configure-aws-credentials が v1 で OIDC 変更を反映すればいける)

dev.classmethod.jp

zenn.dev

github.com