Tips - 堅牢なサプライチェーンセキュリティ
1. はじめに
CI/CDパイプラインのセキュリティは、もはや「あったらいい」機能ではなく、必須要件となっています。GitHub Actionsは、Artifact Attestations(成果物証明)とOpenID Connect(OIDC)という2つの強力な機能を通じて、この課題に真正面から取り組んでいます。
本記事では、これらの機能を活用して、SLSA v1.0 Build Level 3に準拠した、検証可能なビルドプロセスを構築する方法を解説します。
2. Artifact Attestationsとは何か
Artifact Attestationsは、ソフトウェアの成果物(バイナリやコンテナイメージ)が「どこで」「どのように」ビルドされたかを暗号学的に証明する仕組みです。これにより、サプライチェーン攻撃のリスクを大幅に低減できます。
2.1 利用可能なプラン
- パブリックリポジトリ: すべてのGitHubプラン(Free、Pro、Team、Enterprise Cloud)
- プライベート/内部リポジトリ: GitHub Enterprise Cloudプランのみ
注意: レガシープラン(Bronze、Silver、Gold)では利用できません。
2.2 基本的な仕組み
3. ビルド証明の生成方法
3.1 バイナリファイルの場合
ワークフローに以下の権限を追加します。
permissions:
id-token: write
contents: read
attestations: write
ビルドステップの後に、証明生成ステップを追加します。
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v3
with:
subject-path: 'PATH/TO/ARTIFACT'
subject-pathには、証明を付与したいバイナリファイルのパスを指定します。
3.2 コンテナイメージの場合
権限設定にpackages: writeを追加します。
permissions:
id-token: write
contents: read
attestations: write
packages: write
イメージビルド後に証明を生成します。
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v3
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: 'sha256:fedcba0...'
push-to-registry: true
重要なポイント:
-
subject-nameには完全修飾イメージ名を指定(例:ghcr.io/yamada-taro/app) - タグは含めない
-
subject-digestにはSHA256ダイジェストを指定 -
docker/build-push-actionのdigest出力を利用可能
3.3 実践例: 完全なワークフロー
name: ビルドと証明の生成
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
attestations: write
packages: write
jobs:
build-and-attest:
runs-on: ubuntu-latest
steps:
- name: リポジトリのチェックアウト
uses: actions/checkout@v5
- name: Dockerイメージのビルド
id: build
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/yamada-taro/myapp:latest
- name: ビルド証明の生成
uses: actions/attest-build-provenance@v3
with:
subject-name: ghcr.io/yamada-taro/myapp
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
4. SBOM証明の生成
Software Bill of Materials(SBOM)の証明も生成できます。
4.1 バイナリのSBOM証明
- name: Generate SBOM attestation
uses: actions/attest-sbom@v2
with:
subject-path: 'PATH/TO/ARTIFACT'
sbom-path: 'PATH/TO/SBOM'
4.2 コンテナイメージのSBOM証明
- name: Generate SBOM attestation
uses: actions/attest-sbom@v2
with:
subject-name: ${{ env.REGISTRY }}/PATH/TO/IMAGE
subject-digest: 'sha256:fedcba0...'
sbom-path: 'sbom.json'
push-to-registry: true
SBOMファイルはJSON形式で、SPDXまたはCycloneDX形式に対応しています。
5. 証明の検証方法
5.1 GitHub CLIを使用した検証
バイナリの検証:
gh attestation verify PATH/TO/YOUR/BUILD/ARTIFACT-BINARY \
-R 組織名/リポジトリ名
コンテナイメージの検証:
docker login ghcr.io
gh attestation verify oci://ghcr.io/組織名/イメージ名:test \
-R 組織名/リポジトリ名
SBOM証明の検証(SPDX形式の例):
gh attestation verify PATH/TO/YOUR/BUILD/ARTIFACT-BINARY \
-R 組織名/リポジトリ名 \
--predicate-type https://spdx.dev/Document/v2.3 \
--format json \
--jq '.[].verificationResult.statement.predicate'
5.2 検証のフロー
6. OpenID Connect(OIDC)による認証
OIDCを使用すると、長期的なクレデンシャルをGitHubシークレットに保存せずに、クラウドプロバイダーにアクセスできます。
6.1 OIDCの基本原理
6.2 対応クラウドプロバイダー
| プロバイダー | 公式アクション | 特記事項 |
|---|---|---|
| AWS | aws-actions/configure-aws-credentials |
IAM条件キー推奨 |
| Azure | azure/login |
Workload Identity Federation |
| Google Cloud | google-github-actions/auth |
Workload Identity Pool |
| HashiCorp Vault | hashicorp/vault-action |
JWT認証方式 |
| JFrog | jfrog/setup-jfrog-cli |
OIDC Provider設定必要 |
| PyPI | pypa/gh-action-pypi-publish |
Trusted Publishing |
7. AWS での OIDC 設定
7.1 IAMにIDプロバイダーを追加
- プロバイダーURL:
https://token.actions.githubusercontent.com - Audience:
sts.amazonaws.com
7.2 ロールと信頼ポリシーの設定
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456123456:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:佐藤組織/佐藤リポジトリ:ref:refs/heads/main"
}
}
}
]
}
環境を使用する場合:
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:佐藤組織/佐藤リポジトリ:environment:prod"
}
}
ワイルドカードを使用した柔軟な設定:
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:佐藤組織/佐藤リポジトリ:*"
},
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
}
}
7.3 ワークフローの更新
name: AWSデプロイ例
on: [push]
env:
BUCKET_NAME: "マイバケット"
AWS_REGION: "ap-northeast-1"
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: リポジトリのチェックアウト
uses: actions/checkout@v5
- name: AWS認証情報の設定
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502
with:
role-to-assume: arn:aws:iam::123456123456:role/example-role
role-session-name: samplerolesession
aws-region: ${{ env.AWS_REGION }}
- name: S3へのファイルアップロード
run: |
aws s3 cp ./index.html s3://${{ env.BUCKET_NAME }}/
8. Azure での OIDC 設定
8.1 必要な設定手順
- Microsoft Entra ID(旧Azure AD)アプリケーションとサービスプリンシパルを作成
- Entra IDアプリケーションにフェデレーテッドクレデンシャルを追加
- Azure設定を保存するGitHubシークレットを作成
推奨設定:
- Audience:
api://AzureADTokenExchange
8.2 ワークフロー例
name: AzureログインとOIDC
on: [push]
permissions:
id-token: write
contents: read
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: 'Azure CLIログイン'
uses: azure/login@8c334a195cbb38e46038007b304988d888bf676a
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: 'azコマンドの実行'
run: |
az account show
az group list
9. Google Cloud Platform での OIDC 設定
9.1 設定手順
- 新しいIdentity Poolを作成
- マッピングと条件を設定
- 新しいプールをサービスアカウントに接続
重要な設定値:
- Issuer URL:
https://token.actions.githubusercontent.com - サービスアカウントに
roles/iam.workloadIdentityUserロールを割り当て
9.2 ワークフロー例
name: GCPサービス一覧取得
on:
pull_request:
branches:
- main
permissions:
id-token: write
jobs:
Get_OIDC_ID_token:
runs-on: ubuntu-latest
steps:
- id: 'auth'
name: 'GCPへの認証'
uses: 'google-github-actions/auth@f1e2d3c4b5a6f7e8d9c0b1a2c3d4e5f6a7b8c9d0'
with:
create_credentials_file: 'true'
workload_identity_provider: 'projects/example-project-id/locations/global/workloadIdentityPools/name-of-pool/providers/name-of-provider'
service_account: 'my-service-account@example-project.iam.gserviceaccount.com'
- id: 'gcloud'
name: 'gcloud'
run: |-
gcloud auth login --brief --cred-file="${{ steps.auth.outputs.credentials_file_path }}"
gcloud services list
10. HashiCorp Vault での OIDC 設定
10.1 Vault サーバーの設定
JWT認証方式を有効化:
vault auth enable jwt
設定の適用:
vault write auth/jwt/config \
bound_issuer="https://token.actions.githubusercontent.com" \
oidc_discovery_url="https://token.actions.githubusercontent.com"
ポリシーの設定:
vault policy write myproject-production - <<EOF
# 'secret/data/production/*' パスへの読み取り専用権限
path "secret/data/production/*" {
capabilities = [ "read" ]
}
EOF
ロールの設定:
vault write auth/jwt/role/myproject-production -<<EOF
{
"role_type": "jwt",
"user_claim": "actor",
"bound_claims": {
"repository": "田中組織/田中リポジトリ"
},
"policies": ["myproject-production"],
"ttl": "10m"
}
EOF
10.2 ワークフロー例
jobs:
retrieve-secret:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Vaultからシークレットを取得
uses: hashicorp/vault-action@9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b
with:
method: jwt
url: https://vault.example.com
namespace: admin # HCP VaultとVault Enterpriseのみ
role: myproject-production
secrets: secret/data/production/ci npmToken
- name: Vaultから取得したシークレットを使用
run: |
# ここで取得したシークレットを使用可能
10.3 トークンの手動取り消し
steps:
- name: Vaultからシークレットを取得
uses: hashicorp/vault-action@9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b
with:
exportToken: true
method: jwt
url: https://vault.example.com
role: myproject-production
secrets: secret/data/production/ci npmToken
- name: シークレットの使用
run: |
# シークレットを使用
- name: トークンの取り消し
if: always()
run: |
curl -X POST -sv -H "X-Vault-Token: ${{ env.VAULT_TOKEN }}" \
https://vault.example.com/v1/auth/token/revoke-self
11. JFrog での OIDC 設定
11.1 設定のポイント
JFrogプラットフォームでIdentity Mappingsを設定する際、Claims JSONを適切に設定することが重要です。
推奨設定例:
{
"iss": "https://token.actions.githubusercontent.com",
"repository": "高橋組織/高橋リポジトリ"
}
11.2 ワークフロー例
permissions:
id-token: write
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: JFrog CLIをOIDCでセットアップ
id: setup-jfrog-cli
uses: jfrog/setup-jfrog-cli@29fa5190a4123350e81e2a2e8d803b2a27fed15e
with:
JF_URL: ${{ env.JF_URL }}
oidc-provider-name: 'YOUR_PROVIDER_NAME'
oidc-audience: 'YOUR_AUDIENCE' # オプション
- name: 成果物のアップロード
run: jf rt upload "dist/*.zip" my-repo/
11.3 他のステップでOIDC認証情報を使用
setup-jfrog-cliアクションは、oidc-userとoidc-tokenを出力として提供します。
- name: Artifactory Dockerレジストリにサインイン
uses: docker/login-action@v3
with:
registry: ${{ env.JF_URL }}
username: ${{ steps.setup-jfrog-cli.outputs.oidc-user }}
password: ${{ steps.setup-jfrog-cli.outputs.oidc-token }}
12. PyPI での OIDC 設定(Trusted Publishing)
12.1 PyPIでの信頼設定
-
PyPIにサインインし、プロジェクトの設定ページに移動
-
GitHubリポジトリとワークフローの信頼関係を設定
- Owner:
伊藤組織 - Repository name:
伊藤リポジトリ - Workflow name:
release.yml - Environment name (オプション):
release
- Owner:
12.2 ワークフロー例
jobs:
release-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: リリース配布物のビルド
run: |
python -m pip install build
python -m build
- name: 配布物のアップロード
uses: actions/upload-artifact@v4
with:
name: release-dists
path: dist/
pypi-publish:
runs-on: ubuntu-latest
needs:
- release-build
permissions:
id-token: write
steps:
- name: リリース配布物の取得
uses: actions/download-artifact@v5
with:
name: release-dists
path: dist/
- name: PyPIへの公開
uses: pypa/gh-action-pypi-publish@3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f
重要: usernameとpasswordを指定する必要はありません。id-token: write権限のみで動作します。
13. Kubernetes Admission Controller による証明の強制
13.1 概要
Kubernetesクラスタ内で、GitHubの成果物証明を持つコンテナイメージのみをデプロイ可能にする仕組みです。
13.2 前提条件
- Kubernetesバージョン1.27以降
- Helm 3.0以降
- kubectl
-
push-to-registry: trueを設定したattest-build-provenanceアクションの使用
13.3 Policy Controllerのデプロイ
helm upgrade policy-controller --install --atomic \
--create-namespace --namespace artifact-attestations \
oci://ghcr.io/sigstore/helm-charts/policy-controller \
--version 0.10.5
13.4 TrustRootとClusterImagePolicyの追加
helm upgrade trust-policies --install --atomic \
--namespace artifact-attestations \
oci://ghcr.io/github/artifact-attestations-helm-charts/trust-policies \
--version v0.7.0 \
--set policy.enabled=true \
--set policy.organization=渡辺組織
13.5 名前空間でポリシーを有効化
ラベルの追加:
metadata:
labels:
policy.sigstore.dev/include: "true"
またはコマンドで:
kubectl label namespace マイ名前空間 policy.sigstore.dev/include=true
13.6 イメージのマッチング設定
特定のイメージのみに証明を強制する場合:
helm upgrade trust-policies --install --atomic \
--namespace artifact-attestations \
oci://ghcr.io/github/artifact-attestations-helm-charts/trust-policies \
--version v0.7.0 \
--set policy.enabled=true \
--set policy.organization=渡辺組織 \
--set-json 'policy.exemptImages=["index.docker.io/library/busybox**"]' \
--set-json 'policy.images=["ghcr.io/渡辺組織/**"]'
重要なポイント:
- Docker Hubのイメージも完全修飾名で指定(例:
index.docker.io/library/busybox**) -
**グロブを使用してすべてのバージョンに一致 -
policy.exemptImagesとpolicy.imagesの両方に一致する場合は拒否される
14. 再利用可能なワークフローとの組み合わせ
14.1 SLSA v1.0 Build Level 3 の達成
再利用可能なワークフローと成果物証明を組み合わせることで、SLSA v1.0 Build Level 3を達成できます。
14.2 必要な権限設定
呼び出しワークフローと再利用可能ワークフローの両方に必要:
permissions:
attestations: write
contents: read
id-token: write
コンテナイメージの場合は追加で:
permissions:
packages: write
14.3 JWTトークンの構造
再利用可能なワークフローで生成された証明のトークン例:
{
"jti": "example-id",
"sub": "repo:中村組織/中村リポジトリ:environment:prod",
"aud": "https://github.com/中村組織",
"ref": "refs/heads/main",
"sha": "example-sha",
"repository": "中村組織/中村リポジトリ",
"repository_owner": "中村組織",
"actor": "中村太郎",
"workflow": "example-workflow",
"job_workflow_ref": "中村組織/中村自動化/.github/workflows/oidc.yml@refs/heads/main",
"iss": "https://token.actions.githubusercontent.com"
}
job_workflow_refが再利用可能なワークフローを示します。
14.4 検証方法
14.4.1 特定リポジトリ内の再利用可能ワークフローのフィルタリング
gh attestation verify -o 中村組織 \
--signer-repo 中村組織/中村自動化 \
PATH/TO/ARTIFACT
14.4.2 特定の再利用可能なワークフローへの限定
gh attestation verify -o 中村組織 \
--signer-workflow 中村組織/中村自動化/.github/workflows/deployment.yml \
PATH/TO/ARTIFACT
14.5 クラウドプロバイダーでの信頼条件設定
14.5.1 リポジトリ内のすべての再利用可能ワークフローを信頼
Subject:
repo:組織名/*
Custom claim:
job_workflow_ref:組織名/リポジトリ名@*
14.5.2 特定のrefの特定ワークフローのみを信頼
Subject:
repo:組織名/*
Custom claim:
job_workflow_ref:組織名/リポジトリ名/.github/workflows/deployment.yml@10040c56a8c0253d69db7c1f26a0d227275512e2
15. オフライン環境での証明検証
インターネット接続がない環境でも、事前準備により証明の検証が可能です。
15.1 検証プロセス
15.2 証明バンドルのダウンロード
オンライン環境で実行:
gh attestation download PATH/TO/YOUR/BUILD/ARTIFACT-BINARY \
-R 組織名/リポジトリ名
出力例:
Wrote attestations to file sha256:ae57936def59bc4c75edd3a837d89bcefc6d3a5e31d55a6fa7a71624f92c3c3b.jsonl.
Any previous content has been overwritten
The trusted metadata is now available at sha256:ae57936def59bc4c75edd3a837d89bcefc6d3a5e31d55a6fa7a71624f92c3c3b.jsonl
15.3 信頼ルートのダウンロード
gh attestation trusted-root > trusted_root.jsonl
このファイルには、以下の鍵情報が含まれます:
- パブリックリポジトリ: Sigstore Public Good Instance
- プライベートリポジトリ: GitHub's Sigstore Instance
15.4 オフライン検証の実行
オフライン環境に以下を転送:
- GitHub CLI
- 検証対象の成果物
- バンドルファイル(
*.jsonl) - 信頼ルートファイル(
trusted_root.jsonl)
検証コマンド:
gh attestation verify PATH/TO/YOUR/BUILD/ARTIFACT-BINARY \
-R 組織名/リポジトリ名 \
--bundle sha256:ae57936def59bc4c75edd3a837d89bcefc6d3a5e31d55a6fa7a71624f92c3c3b.jsonl \
--custom-trusted-root trusted_root.jsonl
15.5 信頼ルートの更新戦略
ベストプラクティス:
- 新しい署名済み資材をインポートする際、毎回新しい
trusted_root.jsonlを生成 - 鍵ローテーションはSigstoreインスタンスが年に数回実行
- 古い信頼ルートでも、生成時点以前の署名は検証可能
- ただし鍵失効情報は取得不可
16. 証明のライフサイクル管理
16.1 証明を削除すべきケース
- 誤って作成された証明
- 存在しない成果物にリンクされた証明
- 信頼すべきでない成果物にリンクされた証明
削除により、検証プロセスを持つ消費者が該当成果物を使用できなくなります。
16.2 証明の検索方法
- リポジトリに移動
- 「Actions」タブをクリック
- サイドバーの「Management」セクションで「Attestations」をクリック
- 検索フィルターを使用
16.2.1 検索とフィルタリング
検索オプション:
- フリーテキスト検索: subject nameで検索(部分一致)
-
作成日フィルター:
created:<2025-04-03(演算子:>,<) -
述語タイプフィルター:
predicate:provenanceまたはpredicate:sbom
例:
created:>2025-01-01 predicate:provenance
16.3 証明の削除
- 削除したい証明のチェックボックスを選択(複数選択可能)
- 「Delete」をクリック
- 確認メッセージを読み、「Delete attestations」をクリック
注意: 削除前にバックアップのダウンロードを推奨します。
16.4 REST APIによる一括管理
大量の証明を管理する場合、REST APIが利用可能です。
エンドポイント例:
GET /repos/{owner}/{repo}/attestationsDELETE /repos/{owner}/{repo}/attestations/{attestation_id}
17. セキュリティのベストプラクティス
17.1 環境保護ルールの活用
jobs:
deploy:
environment: production
steps:
# デプロイ処理
環境に以下のルールを設定:
- デプロイ可能なブランチ/タグの制限
- 必須レビュアーの設定
- 待機タイマーの設定
17.2 最小権限の原則
必要な権限のみを付与:
permissions:
id-token: write # OIDCトークンの取得のみ
contents: read # リポジトリの読み取りのみ
attestations: write # 証明の書き込みのみ
17.3 信頼条件の厳格な設定
曖昧な条件を避ける:
❌ 悪い例:
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:*/*:*"
}
}
✅ 良い例:
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:sub": "repo:小林組織/小林リポジトリ:ref:refs/heads/main"
}
}
17.4 定期的な証明の整理
自動化スクリプト例:
name: 古い証明の削除
on:
schedule:
- cron: '0 0 * * 0' # 毎週日曜日
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: 90日以上前の証明を削除
run: |
# REST APIを使用した削除処理
17.5 監査とモニタリング
証明の使用状況を定期的に確認:
- どのリポジトリが証明を生成しているか
- 検証の成功/失敗率
- 異常なアクセスパターン
18. トラブルシューティング
18.1 よくある問題と解決方法
18.1.1 問題1: 証明の検証が失敗する
原因:
- 成果物が変更された
- 証明が削除された
- ネットワークの問題
解決方法:
# 詳細なエラー情報を確認
gh attestation verify PATH/TO/ARTIFACT -R 組織名/リポジトリ名 --format json
18.1.2 問題2: OIDCトークンの取得に失敗
原因:
- 権限設定が不正
- IDプロバイダーの設定ミス
解決方法:
# 権限を確認
permissions:
id-token: write # この権限が必須
18.1.3 問題3: Kubernetes Admission Controllerがイメージを拒否
原因:
- 証明が存在しない
- イメージ名が完全修飾名でない
- ポリシー設定のミスマッチ
解決方法:
# ポリシーの確認
kubectl get clusterimagepolicy -n artifact-attestations
# ログの確認
kubectl logs -n artifact-attestations deployment/policy-controller-webhook
19. まとめ
GitHub Actionsの成果物証明とOIDCは、現代的なCI/CDパイプラインにおけるセキュリティの基盤となる機能です。
19.1 主な利点
- 検証可能な信頼性: すべての成果物の出所を暗号学的に証明
- クレデンシャル管理の簡素化: 長期的なシークレット不要
- 標準への準拠: SLSA v1.0 Build Level 3に対応
- 柔軟な統合: 主要クラウドプロバイダーとの統合
- 自動化されたセキュリティ: Kubernetesでの自動検証
19.2 導入のステップ
- ワークフローに証明生成を追加
- OIDCでクラウドプロバイダーと統合
- 検証プロセスの確立
- 再利用可能なワークフローへの移行
- 継続的な監査と改善
これらの機能を適切に実装することで、ソフトウェアサプライチェーン全体のセキュリティを大幅に向上させることができます。技術的な複雑さはありますが、得られる安全性とコンプライアンスの価値は計り知れません。
まずは小規模なプロジェクトから始めて、徐々に組織全体へ展開していくことをお勧めします。