🔓

GitHub Apps のトークンを使ってプライベートリポジトリにアクセスする

2022/12/20に公開

はじめに

こんにちは。
DeNA 23新卒内定者 Advent Calendar 2022 20日目 🎄 を担当させていただきます FarStep です。

https://qiita.com/advent-calendar/2022/dena-23-shinsotsu

DeNA 23新卒内定者 Advent Calendar 2022 では、本記事の公開日(2022/12/20)以降もさまざまなジャンルの技術に関する記事が更新されるのでぜひご覧ください!

本記事の目標

本記事の目標は、GitHub Apps で生成したトークンを使って、GitHub 上のプライベートリポジトリへの認証ができる です。
「プライベートリポジトリへの認証ができるようになる」ということは、例えば GitHub Actions を使って CI を構築する際に、「プライベートリポジトリに対して自由に操作ができるようになる」ということです。

想定する状況

あるレポジトリで Go 言語 を使った チーム開発 を行なっているとします。
そのレポジトリで、「最新イメージを ECR に push する」という CI を GitHub Actions を用いて構築する場合を想定します。
CI の流れは下記の通りです(一部省略しています)。

  1. OIDC を用いて AWS 認証を行う
  2. ECR にログインする
  3. Dockerfile 内の go mod download を実行する ← 本記事のメインテーマ
  4. Dockerfile 内の go build を実行する
  5. ECR へ最新イメージを push する

「3. Dockerfile 内の go mod download を実行する」というステップで、別のプライベートリポジトリを参照しています。チーム開発をする場合、全てのリポジトリは Organization 内にあるかと思いますので、図解すると下記のような感じですね。

つまり、

  • go.mod
  • Dockerfile
  • .github/workflows/build.yml

に下記のようなコードが記述されているということです。

go.mod
module github.com/organization/my-repository

go 1.18

require (
	// プライベートリポジトリを参照している
	github.com/organization/another-repository v0.0.0-xxxxx
)

require (
	github.com/xxxxx/xxxxx v0.0.0 // indirect
	github.com/xxxxx/xxxxx v0.0.0 // indirect
	github.com/xxxxx/xxxxx v0.0.0 // indirect
)

最初の require で、プライベートリポジトリを参照していますね。
github.com/organization/another-repositoryorganization には Organization の名前、another-repository には Organization 内の参照したいプライベートリポジトリの名前が入ります。

Dockerfile
FROM golang:1.18

WORKDIR /go/src/app

COPY . .

# ダウンロードするモジュールの中には、プライベートリポジトリのモジュールも含まれる
RUN go mod download

RUN go build ./cmd/main.go

EXPOSE 8080

CMD ["/go/src/app/main"]

go mod download を実行してダウンロードするモジュールには、github.com/organization/another-repository というプライベートなモジュールも含まれます。

.github/workflows/build.yml
name: "CI"

on:
  push:
    branches:
      - develop

permissions:
  id-token: write
  contents: read

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - uses: actions/cache@v2
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-buildx-

      - name: Assume Role
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ secrets.DEV_GITHUB_ACTIONS_OIDC_ARN }}
          aws-region: ap-northeast-1

      - name: Login to ECR
        uses: docker/login-action@v1
        with:
          registry: ${{ secrets.ECR_REGISTRY }}

      - name: Build and push
        uses: docker/build-push-action@v3
        with:
          push: true
          tags: ${{ secrets.ECR_REGISTRY }}/${{ secrets.ECR_REPOSITORY }}:${{ github.sha }}
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max

      - name: Move cache
        run: |
          rm -rf /tmp/.buildx-cache
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

name: Build and push のステップで、Dockerfile に記述された手順通りにビルドを行っています。

解決したい問題

さて、先ほど示した CI には一つ問題があります。
それは、プライベートリポジトリへの認証がされていない という点です。
CI の中でビルドを実行する際に、プライベートリポジトリのモジュールをダウンロードしているため、プライベートリポジトリへのアクセスが許可されていないと CI が失敗します。

具体的には GitHub Actions のログに、下記のようなエラーが吐かれます。

エラー①
remote: Repository not found.
fatal: Authentication failed for 'https://github.com/organization/private-repository/'
エラー②
fatal: could not read Username for 'https://github.com': terminal prompts disabled

問題の解決方法

前節での問題を解決する方法として、GitHub Apps で生成したトークンを使ったプライベートリポジトリへの認証 をご紹介します。

GitHub Apps とは

GitHub Apps は、Organization や個人アカウントに直接インストールでき、特定のリポジトリへのアクセス権を付与することが可能です。GitHub Apps の主な特長は次の三つです。

  1. アクセス権限を細かく設定することができる。
  2. インストール単位が User/Organization の保持するリポジトリ単位になる。
  3. リポジトリにおけるイベントの発生も受け取ることができる(Webhook を備える)。

また GitHub Apps は、パーソナルアクセストークン(personal access token)と異なり、個人アカウントに紐づかないので会社組織等で使う際の管理に向いています

パーソナルアクセストークンを採用してしまうと、ユーザーに紐付いたアクセスキーが発行されてしまうため、設定したユーザーが退職したり、異動したりしてアカウントが停止されたりするとアクセスキーも無効になり、認証エラーになってしまいます。

https://eng-blog.iij.ad.jp/archives/2242

GitHub Apps の作成

それではトークンを発行する GitHub Apps を作成しましょう。

https://github.com/{organization} にアクセスしてください。
{organization} には、Organization の名前が入ります。

「Settings」タブをクリックしたあと、下までスクロールしてください。

サイドバーに「GitHub Apps」というメニューがありますので、クリックしてください。
すると、https://github.com/organizations/{organization}/settings/apps に遷移します。

上記のページで「New GitHub App」をクリックしてください。
すると、https://github.com/organizations/{organization}/settings/apps/new に遷移します。

上記フォームに必須項目を入力してください。
今回、Webhook の設定は行いませんので、Webhook の Active のチェックは外してください。

項目
GitHub App name 任意の名前(例. my-github-app)
Homepage URL 任意のURL(例. https://example.com
Webhook の Active チェックを外す

フォームへの入力が完了しましたら、いよいよ権限の設定です。
「Repository permissions」をクリックすると、アコーディオンメニューが開きます。

今回構築する CI では、go mod download 実行時にリポジトリの中身を参照するだけですので、「Repository permissions」の「Contents」を「Access: Read-only」に変更してください。「Contents」を「Access: Read-only」に変更すると、自動的に「Metadata」も「Access: Read-only」に変更されます。

権限の設定が完了したら、「Create GitHub App」をクリックしてください。

作成が完了すると、下記のような画面に遷移します。

現在表示されている「App ID」の値を後ほど使います。

「App ID」に加えて「private key」を取得する必要があります。
下にスクロールして「Generate a private key」をクリックしてください。

すると、my-github-app.2022-12-15.private-key.pem というファイルがダウンロードされるはずです(ファイル名は GitHub App の名前、作成日時によって異なります)。
このファイルに記述されている秘密鍵を後ほど使います。

これで、GitHub Apps の作成が完了です 🎉

GitHub Apps のインストール

次に、作成した GitHub Apps を Organization 内のリポジトリにインストールしましょう。
下記画面のサイドバーの「Install App」メニューをクリックしてください。

インストール先の Organization を選択して「Install」をクリックしてください。

Organization 内には、複数のレポジトリが存在するかと思います。
今回は、CI を構築しているリポジトリ・CI で参照するもう一つのレポジトリのみに GitHub Apps のインストールします
GitHub Apps をインストールするリポジトリは最小限にとどめておきましょう。

これで、GitHub Apps のインストールが完了です 🎉

GitHub Apps によるトークンの生成

続いて、GitHub Actions のワークフロー内で GitHub Apps によるトークンの生成を行いましょう。
最初に、GitHub Apps 作成時に取得した「App ID」と「private key」を CI を構築するレポジトリの Secrets に登録します。

CI を構築するレポジトリの「Settings」に移動した後、サイドバーの「Secrets」の「Actions」をクリックしてください。

「New repository secret」をクリックして、「App ID」と「private key」を登録してください。

キーの名前は、「APP_ID」・「PRIVATE_KEY」としましょう。
下記のように、「APP_ID」・「PRIVATE_KEY」が追加されていれば OK です。

これで、GitHub Apps によるトークンの生成の準備が整いました。
次に、.github/workflows/build.yml を編集します。

.github/workflows/build.yml
name: "CI"

on:
  push:
    branches:
      - develop

permissions:
  id-token: write
  contents: read

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - uses: actions/cache@v2
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-buildx-

+     - name: Generate token
+       id: generate_token
+       uses: tibdex/github-app-token@v1
+       with:
+         app_id: ${{ secrets.APP_ID }}
+         private_key: ${{ secrets.PRIVATE_KEY }}

      - name: Assume Role
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ secrets.DEV_GITHUB_ACTIONS_OIDC_ARN }}
          aws-region: ap-northeast-1

      - name: Login to ECR
        uses: docker/login-action@v1
        with:
          registry: ${{ secrets.ECR_REGISTRY }}

      - name: Build and push
        uses: docker/build-push-action@v3
        with:
+         build-args: |
+           "TOKEN=${{ steps.generate_token.outputs.token }}"
          push: true
          tags: ${{ secrets.ECR_REGISTRY }}/${{ secrets.ECR_REPOSITORY }}:${{ github.sha }}
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max

      - name: Move cache
        run: |
          rm -rf /tmp/.buildx-cache
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

name: Generate token というステップを追加しました。
GitHub Apps によるトークンの生成には、tibdex/github-app-token を使用しています。
app_idprivate_key に、先ほど Secrets に登録した値を渡しています。

https://github.com/tibdex/github-app-token

また name: Build and push のステップでは、生成したトークンをビルド時に使用可能な ARG に渡しています。

これで、Dockerfile 内で GitHub Apps が生成したトークンを使えるようになりました 🎉

トークンによる認証

それでは最後に、生成したトークンを使ってプライベートリポジトリへの認証を行いましょう。
Dockerfile を編集します。

Dockerfile
FROM golang:1.18

+ ARG TOKEN

WORKDIR /go/src/app

COPY . .

+ RUN export GOPRIVATE=github.com/organization

+ RUN git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "https://github.com/"

RUN go mod download

RUN go build ./cmd/main.go

EXPOSE 8080

CMD ["/go/src/app/main"]

追加したコードについて説明します。

プライベートなモジュールを取得するときに checksum の検証ができないため、下記コードで GOPRIVATE という環境変数を設定しています。organization は、Organization の名前にしてください。

RUN export GOPRIVATE=github.com/organization

そしてトークンを利用するように、下記コードで git config の設定を行なっています。
https://github.com/ という URL を使用する際に別 URL に置き換えるための設定です。これで、常にトークンを使用して GitHub にアクセスするようになりました。

ARG TOKEN
RUN git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "https://github.com/"

以上で、トークンによるプライベートリポジトリへの認証が完了です 🎉
go mod download が成功するため、CI は正常に動くようになります。

おわりに

いかがだったでしょうか。
GitHub Apps で生成したトークンを使った認証について理解できたでしょうか。
本記事で紹介した状況は一例です。GitHub Apps の権限を変更することで、様々な GitHub のサービスに対して認証を行うことが可能です。
是非、GitHub Apps を活用してみてください。

参考文献

https://dev.classmethod.jp/articles/github-actions-docker-build-ecr/

https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps

Discussion