この投稿では、プライベートリポジトリのGoモジュールを別のリポジトリでgo getしたりgo mod downloadする方法を解説する。
基礎
プライベートリポジトリのGoモジュールを使うには次の仕組みを理解する必要がある:
GOPRIVATE- GitHubの認証方式
ローカルで開発するにも、Dockerfileを書くにも、GitHub Actionsのワークフローを構築するにも、これらの知識が大前提となる。
GOPRIVATE
GOPRIVATEはGoに「このモジュールは非公開」と伝える仕組み。
GOPRIVATEをセットするにはgo envコマンドを実行する:
# 特定組織配下をすべて指定する場合:
go env -w GOPRIVATE="github.com/your-org/*"
# 個別に細かく指定する場合:
go env -w GOPRIVATE="github.com/your-org/*",github.com/your-user/your-repo
GOPRIVATEがどうなっているかもgo envで確認できる:
go env GOPRIVATE
GORPIVATEをセットすると何が起きる?
-
proxy.golang.orgなどを経由せず、GitHubから直接取ってくる -
sum.golang.orgなどの公開チェックサムDBを使わなくなる
GOPRIVATEをセットしただけでは、プライベートリポジトリの中身は取れない。
GitHubの認証方式
GitHubの認証方式は、大きく分けて2つある。
- トークン認証: PAT, OAuth, GitHub Apps
- SSH秘密鍵認証: ~/.ssh, デプロイメントキー
Goがプライベートモジュールをダウンロードするには、どちらかの認証方式を設定する必要がある。
ローカル
macOSなどのローカル開発環境では、SSH秘密鍵認証を用いてプライベートリポジトリにアクセスするのがおすすめ。既にgit pushするためにSSHの設定がなされている可能性が高いため。
SSHでGitHubの認証ができるかは次のコマンドで確認できる:
ssh -T git@github.com
認証が通っているなら、go envでGOPRIVATEを設定するだけ。go getでモジュールが取得できれば設定成功。
go get github.com/your-org/your-repo
Dockerfile
Dockerfileにおける認証方法は、トークン認証がオススメ。理由は、GitHub Actionsなどのビルド環境でもDockerfileが使われることがあるため。トークン認証にそろえておけば、ローカルでdocker buildするときも、CIでするときも同じDockerfileが使える。
# syntax=docker/dockerfile:1
# [1]
FROM docker.io/golang:1.24 AS builder
# [2]
ENV GOPRIVATE=github.com/your-org/*
COPY go.mod go.mod
COPY go.sum go.sum
# [3]
RUN --mount=type=secret,id=GH_TOKEN,env=GH_TOKEN \
GIT_CONFIG_COUNT=1 \
GIT_CONFIG_KEY_0=url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf \
GIT_CONFIG_VALUE_0=https://github.com/ \
go mod download
[1]: --mount=type=secretを有効にするために必要
[2]: GOPRIVATEにプライベートモジュールを指定する
[3]: --mount=type=secretでGitHubトークンを安全に受け取れるようにする。また、GIT_CONFIG_*環境変数でinsteadOfをセットすることで、go mod にGH_TOKENを使わせる。
ARGに機密情報はアブナイ
docker build --build-arg GH_TOKEN=... のように ARG/ENVでトークンを渡すのは非推奨。ARG/ENVはビルド履歴・レイヤ・キャッシュや最終イメージ/メタデータ経由で漏れうるため。
BuildKitの「シークレット/SSHマウント」を使うのが正解。Docker公式も「ARG/ENVでシークレットを渡すのは不適切。秘密はシークレット or SSHマウントを使え」と明記している。
なぜgit configではなくGIT_CONFIG_*なのか?
次のようなgit configコマンドでinsteadOfを設定する方法がなぜダメなのか説明する。
RUN --mount=type=secret,id=GH_TOKEN,env=GH_TOKEN \
git config --global url."https://x-access-token:$GH_TOKEN@github.com/".insteadOf "https://github.com/" && \
go mod download
git config --global ... はファイル書き込みが発生するのでレイヤにGH_TOKENが記録される可能性がある。もちろん、同一RUN内で削除すれば最終レイヤには残らないが、そもそもディスクに書かないほうが安全。
ローカルでdocker buildするには?
このDockerfileをローカルでビルドしてみるには、次のコマンドを使う。
GH_TOKEN=$(gh auth token) \
docker buildx build --secret id=GH_TOKEN,env=GH_TOKEN .
大事なのは、--secretでGitHubの認証トークンの渡し方を指定すること。また、GH_TOKENには、ghコマンドの認証トークンを代入する方法が安全かつ手軽だ。
idの値GH_TOKENはDockerfileのidの値GH_TOKENと同じにする。envの値GH_TOKENはdockerコマンドを実行するシェルの環境変数名と同じにする。この例では、全部GH_TOKENになっているが、次の例のように全部ばらばらでもいい:
# Shell
export A="hello world" # 環境変数Aに、値をセットする
docker buildx build --secret id=B,env=A # Secret Bに、環境変数Aの値をセットする
# Dockerfile
RUN --mount=type=secret,id=B,env=C \ # 環境変数Cに、Secret Bの値をセットする
echo $C # 環境変数Cを表示する
#=> "hello world"
GitHub Actions
GitHub Actionsでの認証方式はいくつかある。
- GitHub App Installation Access Token (オススメ)
- Personal access token (PAT)
- デプロイメントキー
この中でおすすめなのはGitHub App Installation Access Tokenだ。Personal access tokenは属人的なのでチーム開発には不向き。デプロイメントキーはリポジトリごとに設定しないといけず面倒、かつ、有効期限がないためリスクが高い。GitHub App Installation Access Tokenは、発行時にscopeやrepositoriesを指定して利用可能リソース範囲を絞れたり、有効期限が1時間と短くセキュア。
GitHub Appsの作成とインストール
GitHub App Installation Access Tokenを使うには、GitHub Appsを作成し、それを対象のorganizationにインストールする必要がある。go getするには、少くともContents:Readの権限をGitHub Appに与える。
また、GitHub Actionsで、GitHub Appの秘密鍵を使えるように、ActionsのSecretに秘密鍵を登録しておく必要もある。
ワークフローでアクセストークンを使う
GitHub Actionsのワークフローを組むときの大事なポイント:
-
actions/create-github-app-tokenでGitHub Apps Installation Access Tokenを取得する - それを
docker/build-push-actionにsecretsとして渡す
以下がワークフローの全容:
name: Push container image
on:
push:
branches:
- main
tags:
- v*.*.*
env:
REGISTRY: ghcr.io
# "ghcr.io/your-org/your-repo" のような値が代入される
IMAGE_PATH: ghcr.io/${{ github.repository }}
jobs:
push-to-registry:
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
# GitHub App Installation Access Tokenを取得するステップ
- name: Create GitHub App Token
uses: actions/create-github-app-token@bf559f85448f9380bcfa2899dbdc01eb5b37be3a # v3.0.0-beta.2
# 別ステップで参照できるようにidを宣言する
id: app-token
with:
# 環境変数からApp IDをセット
app-id: ${{ secrets.GITHUB_ACTIONS_APP_ID }}
# 環境変数から秘密鍵をセット
private-key: ${{ secrets.GITHUB_ACTIONS_APP_PRIVATE_KEY }}
# 組織内の別のリポジトリを参照する場合は owner の指定が必須
owner: ${{ github.repository_owner }}
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Login to Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
id: meta
with:
images: |
${{ env.IMAGE_PATH }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=edge,enable={{is_default_branch}}
- name: Build and push Docker images
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.annotations }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
# Dockerfileのtype=secret,id=GH_TOKENにGitHub App Installation Access Tokenを渡す指定
secrets: |
GH_TOKEN=${{ steps.app-token.outputs.token }}
GITHUB_TOKENはなぜ使えない?
GITHUB_TOKENは自リポジトリのみが参照可能です。プライベートな他リポジトリを参照する権限がついていないので、GITHUB_TOKENは役に立ちません。
トラブルシューティング
go mod downloadで「Invalid username or token」
Invalid username or token. Password authentication is not supported for Git operations.
このようなエラーがdocker/build-push-actionで起きたのなら、GH_TOKENをDockerfileに上手く渡せていないかもしれない。次のステップをDockerfileに足し、本当にGH_TOKENが渡ってきているか確かめよう。
RUN --mount=type=secret,id=GH_TOKEN,env=GH_TOKEN \
test -n "${GH_TOKEN}" && echo "GH_TOKEN=present" || echo "GH_TOKEN=empty"; \
echo "GH_TOKEN length: $(echo -n "${GH_TOKEN}" | wc -c)"
go mod downloadで「Repository not found」
これがローカルでは起きずに、GitHub Actionsだけで起きるなら、考えられる原因は次のとおり:
-
create-github-app-tokenのownerが指定されていない。ownerを指定しないと、発行されるトークンには、GitHub Actionsが実行されたリポジトリのアクセス権限しかつかない。 -
create-github-app-tokenのrepositoriesに対象リポジトリがリストアップされていない。repositories自体がない場合は、GitHub Appをインストールした全リポジトリが自動的にセットされるので、repositoriesを一旦コメントアウトするのもあり。
- name: Create GitHub App Token
uses: actions/create-github-app-token@bf559f85448f9380bcfa2899dbdc01eb5b37be3a
id: app-token
with:
app-id: ${{ secrets.GITHUB_ACTIONS_APP_ID }}
private-key: ${{ secrets.GITHUB_ACTIONS_APP_PRIVATE_KEY }}
owner: your-org # ここが指定されていない
repositories: |
repo1
repo2
# ↑ここにリストアップされていない