diff --git a/.github/workflows/bake.yml b/.github/workflows/bake.yml index c3dd25a5..de835b2a 100644 --- a/.github/workflows/bake.yml +++ b/.github/workflows/bake.yml @@ -998,7 +998,29 @@ jobs: if: ${{ needs.registry-identities.outputs.aws-ecr-enabled == 'true' }} uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 with: - registry-auth: ${{ needs.registry-identities.outputs.aws-ecr-registry-auth }} + registry-auth: | + - registry: ${{ needs.registry-identities.outputs.aws-ecr-registry }} + - + name: Authenticate to Google Cloud + id: gcp-wif-auth + if: ${{ needs.registry-identities.outputs.gcp-wif-enabled == 'true' }} + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 + with: + token_format: access_token + workload_identity_provider: ${{ needs.registry-identities.outputs.gcp-wif-workload-identity-provider }} + service_account: ${{ needs.registry-identities.outputs.gcp-wif-service-account }} + project_id: ${{ needs.registry-identities.outputs.gcp-wif-project-id }} + create_credentials_file: false + export_environment_variables: false + - + name: Login to Google Artifact Registry + if: ${{ needs.registry-identities.outputs.gcp-wif-enabled == 'true' }} + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + with: + registry-auth: | + - registry: ${{ needs.registry-identities.outputs.gcp-wif-registry }} + username: oauth2accesstoken + password: ${{ steps.gcp-wif-auth.outputs.access_token }} - name: Login to registry if: ${{ inputs.push && inputs.output == 'image' && env.REGISTRY_AUTHS_PRESENT == 'true' }} @@ -1218,7 +1240,29 @@ jobs: if: ${{ inputs.push && inputs.output == 'image' && needs.registry-identities.outputs.aws-ecr-enabled == 'true' }} uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 with: - registry-auth: ${{ needs.registry-identities.outputs.aws-ecr-registry-auth }} + registry-auth: | + - registry: ${{ needs.registry-identities.outputs.aws-ecr-registry }} + - + name: Authenticate to Google Cloud + id: gcp-wif-auth + if: ${{ inputs.push && inputs.output == 'image' && needs.registry-identities.outputs.gcp-wif-enabled == 'true' }} + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 + with: + token_format: access_token + workload_identity_provider: ${{ needs.registry-identities.outputs.gcp-wif-workload-identity-provider }} + service_account: ${{ needs.registry-identities.outputs.gcp-wif-service-account }} + project_id: ${{ needs.registry-identities.outputs.gcp-wif-project-id }} + create_credentials_file: false + export_environment_variables: false + - + name: Login to Google Artifact Registry + if: ${{ inputs.push && inputs.output == 'image' && needs.registry-identities.outputs.gcp-wif-enabled == 'true' }} + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + with: + registry-auth: | + - registry: ${{ needs.registry-identities.outputs.gcp-wif-registry }} + username: oauth2accesstoken + password: ${{ steps.gcp-wif-auth.outputs.access_token }} - name: Login to registry if: ${{ inputs.push && inputs.output == 'image' && env.REGISTRY_AUTHS_PRESENT == 'true' }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d88edfec..7c6135d8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -864,7 +864,29 @@ jobs: if: ${{ needs.registry-identities.outputs.aws-ecr-enabled == 'true' }} uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 with: - registry-auth: ${{ needs.registry-identities.outputs.aws-ecr-registry-auth }} + registry-auth: | + - registry: ${{ needs.registry-identities.outputs.aws-ecr-registry }} + - + name: Authenticate to Google Cloud + id: gcp-wif-auth + if: ${{ needs.registry-identities.outputs.gcp-wif-enabled == 'true' }} + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 + with: + token_format: access_token + workload_identity_provider: ${{ needs.registry-identities.outputs.gcp-wif-workload-identity-provider }} + service_account: ${{ needs.registry-identities.outputs.gcp-wif-service-account }} + project_id: ${{ needs.registry-identities.outputs.gcp-wif-project-id }} + create_credentials_file: false + export_environment_variables: false + - + name: Login to Google Artifact Registry + if: ${{ needs.registry-identities.outputs.gcp-wif-enabled == 'true' }} + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + with: + registry-auth: | + - registry: ${{ needs.registry-identities.outputs.gcp-wif-registry }} + username: oauth2accesstoken + password: ${{ steps.gcp-wif-auth.outputs.access_token }} - name: Login to registry if: ${{ inputs.push && inputs.output == 'image' && env.REGISTRY_AUTHS_PRESENT == 'true' }} @@ -1080,7 +1102,29 @@ jobs: if: ${{ inputs.push && inputs.output == 'image' && needs.registry-identities.outputs.aws-ecr-enabled == 'true' }} uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 with: - registry-auth: ${{ needs.registry-identities.outputs.aws-ecr-registry-auth }} + registry-auth: | + - registry: ${{ needs.registry-identities.outputs.aws-ecr-registry }} + - + name: Authenticate to Google Cloud + id: gcp-wif-auth + if: ${{ inputs.push && inputs.output == 'image' && needs.registry-identities.outputs.gcp-wif-enabled == 'true' }} + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 + with: + token_format: access_token + workload_identity_provider: ${{ needs.registry-identities.outputs.gcp-wif-workload-identity-provider }} + service_account: ${{ needs.registry-identities.outputs.gcp-wif-service-account }} + project_id: ${{ needs.registry-identities.outputs.gcp-wif-project-id }} + create_credentials_file: false + export_environment_variables: false + - + name: Login to Google Artifact Registry + if: ${{ inputs.push && inputs.output == 'image' && needs.registry-identities.outputs.gcp-wif-enabled == 'true' }} + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + with: + registry-auth: | + - registry: ${{ needs.registry-identities.outputs.gcp-wif-registry }} + username: oauth2accesstoken + password: ${{ steps.gcp-wif-auth.outputs.access_token }} - name: Login to registry if: ${{ inputs.push && inputs.output == 'image' && env.REGISTRY_AUTHS_PRESENT == 'true' }} diff --git a/.github/workflows/setup-registry-identities.yml b/.github/workflows/setup-registry-identities.yml index c63a1965..bfd43404 100644 --- a/.github/workflows/setup-registry-identities.yml +++ b/.github/workflows/setup-registry-identities.yml @@ -1,4 +1,4 @@ -name: registry-identities +name: setup-registry-identities on: workflow_call: @@ -11,18 +11,33 @@ on: aws-ecr-enabled: description: "Whether an AWS ECR registry identity was configured" value: ${{ jobs.setup-registry-identities.outputs.aws-ecr-enabled }} + aws-ecr-registry: + description: "AWS ECR registry host to authenticate against" + value: ${{ jobs.setup-registry-identities.outputs.aws-ecr-registry }} aws-ecr-role-to-assume: description: "AWS IAM role ARN to assume for ECR authentication" value: ${{ jobs.setup-registry-identities.outputs.aws-ecr-role-to-assume }} aws-ecr-region: description: "AWS region to use for ECR authentication" value: ${{ jobs.setup-registry-identities.outputs.aws-ecr-region }} - aws-ecr-registry-auth: - description: "Registry auth YAML for Docker login" - value: ${{ jobs.setup-registry-identities.outputs.aws-ecr-registry-auth }} + gcp-wif-enabled: + description: "Whether a GCP Workload Identity Federation registry identity was configured" + value: ${{ jobs.setup-registry-identities.outputs.gcp-wif-enabled }} + gcp-wif-registry: + description: "Google Artifact Registry host to authenticate against" + value: ${{ jobs.setup-registry-identities.outputs.gcp-wif-registry }} + gcp-wif-workload-identity-provider: + description: "GCP Workload Identity Provider resource name" + value: ${{ jobs.setup-registry-identities.outputs.gcp-wif-workload-identity-provider }} + gcp-wif-service-account: + description: "GCP service account to impersonate" + value: ${{ jobs.setup-registry-identities.outputs.gcp-wif-service-account }} + gcp-wif-project-id: + description: "GCP project ID" + value: ${{ jobs.setup-registry-identities.outputs.gcp-wif-project-id }} env: - RUNTIME_MODULE: "@docker/github-builder-runtime@0.92.0" + RUNTIME_MODULE: "@docker/github-builder-runtime@0.93.0" RUNTIME_INSTALL_ARGS: | --loglevel=error --no-save @@ -39,9 +54,14 @@ jobs: runs-on: ubuntu-24.04 outputs: aws-ecr-enabled: ${{ steps.validate.outputs.aws-ecr-enabled }} + aws-ecr-registry: ${{ steps.validate.outputs.aws-ecr-registry }} aws-ecr-role-to-assume: ${{ steps.validate.outputs.aws-ecr-role-to-assume }} aws-ecr-region: ${{ steps.validate.outputs.aws-ecr-region }} - aws-ecr-registry-auth: ${{ steps.validate.outputs.aws-ecr-registry-auth }} + gcp-wif-enabled: ${{ steps.validate.outputs.gcp-wif-enabled }} + gcp-wif-registry: ${{ steps.validate.outputs.gcp-wif-registry }} + gcp-wif-workload-identity-provider: ${{ steps.validate.outputs.gcp-wif-workload-identity-provider }} + gcp-wif-service-account: ${{ steps.validate.outputs.gcp-wif-service-account }} + gcp-wif-project-id: ${{ steps.validate.outputs.gcp-wif-project-id }} steps: - name: Install dependencies @@ -83,9 +103,14 @@ jobs: script: | const setEmptyOutputs = () => { core.setOutput('aws-ecr-enabled', 'false'); + core.setOutput('aws-ecr-registry', ''); core.setOutput('aws-ecr-role-to-assume', ''); core.setOutput('aws-ecr-region', ''); - core.setOutput('aws-ecr-registry-auth', ''); + core.setOutput('gcp-wif-enabled', 'false'); + core.setOutput('gcp-wif-registry', ''); + core.setOutput('gcp-wif-workload-identity-provider', ''); + core.setOutput('gcp-wif-service-account', ''); + core.setOutput('gcp-wif-project-id', ''); }; const registryIdentities = core.getInput('registry-identities', {trimWhitespace: false}); @@ -135,13 +160,14 @@ jobs: } let awsEcr; + let gcpWif; entries.forEach((entry, index) => { const path = `registry-identities[${index}]`; ensureObject(entry, path); const type = requireString(entry, 'type', path); switch (type) { case 'aws-ecr': { - const allowedKeys = new Set(['type', 'aws-region', 'role-to-assume', 'registry']); + const allowedKeys = new Set(['type', 'registry', 'role-to-assume', 'region']); for (const key of Object.keys(entry)) { if (!allowedKeys.has(key)) { fail(`${path}.${key} is not supported for aws-ecr`); @@ -150,15 +176,28 @@ jobs: if (awsEcr) { fail('only one aws-ecr registry identity is supported'); } - const awsRegion = requireString(entry, 'aws-region', path); - const roleToAssume = requireString(entry, 'role-to-assume', path); - const registry = requireString(entry, 'registry', path); awsEcr = { - awsRegion, - roleToAssume, - registryAuth: yaml.dump([{ - registry - }], {lineWidth: -1}) + registry: requireString(entry, 'registry', path), + roleToAssume: requireString(entry, 'role-to-assume', path), + region: requireString(entry, 'region', path) + }; + break; + } + case 'gcp-wif': { + const allowedKeys = new Set(['type', 'registry', 'workload_identity_provider', 'service_account', 'project_id']); + for (const key of Object.keys(entry)) { + if (!allowedKeys.has(key)) { + fail(`${path}.${key} is not supported for gcp-wif`); + } + } + if (gcpWif) { + fail('only one gcp-wif registry identity is supported'); + } + gcpWif = { + registry: requireString(entry, 'registry', path), + workloadIdentityProvider: requireString(entry, 'workload_identity_provider', path), + serviceAccount: requireString(entry, 'service_account', path), + projectId: requireString(entry, 'project_id', path) }; break; } @@ -168,6 +207,11 @@ jobs: }); core.setOutput('aws-ecr-enabled', awsEcr ? 'true' : 'false'); + core.setOutput('aws-ecr-registry', awsEcr?.registry || ''); core.setOutput('aws-ecr-role-to-assume', awsEcr?.roleToAssume || ''); - core.setOutput('aws-ecr-region', awsEcr?.awsRegion || ''); - core.setOutput('aws-ecr-registry-auth', awsEcr?.registryAuth || ''); + core.setOutput('aws-ecr-region', awsEcr?.region || ''); + core.setOutput('gcp-wif-enabled', gcpWif ? 'true' : 'false'); + core.setOutput('gcp-wif-registry', gcpWif?.registry || ''); + core.setOutput('gcp-wif-workload-identity-provider', gcpWif?.workloadIdentityProvider || ''); + core.setOutput('gcp-wif-service-account', gcpWif?.serviceAccount || ''); + core.setOutput('gcp-wif-project-id', gcpWif?.projectId || ''); diff --git a/README.md b/README.md index 6301517a..c6557b17 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ ___ * [Notes](#notes) * [Signed GitHub Actions cache](#signed-github-actions-cache) * [Registry identities](#registry-identities) + * [AWS ECR](#aws-ecr) + * [Google Artifact Registry](#google-artifact-registry) * [Runner mapping](#runner-mapping) * [Metadata templates](#metadata-templates) @@ -412,9 +414,11 @@ secret. Provider-specific authentication steps are pinned in these reusable workflows; callers can only select supported provider types and pass identity configuration. -Amazon ECR private registry authentication is configured with `type: aws-ecr`. -Callers must grant `id-token: write` so the AWS credential step can assume the -role through GitHub OIDC: +#### AWS ECR + +Amazon ECR registry authentication is configured with `type: aws-ecr`. Callers +must grant `id-token: write` so the AWS credential step can assume the role +through GitHub OIDC: ```yaml jobs: @@ -430,15 +434,62 @@ jobs: 123456789100.dkr.ecr.us-east-1.amazonaws.com/sandbox/test-github-builder registry-identities: | - type: aws-ecr - aws-region: us-east-1 - role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role registry: 123456789100.dkr.ecr.us-east-1.amazonaws.com + role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role + region: us-east-1 ``` +| Name | Type | Required | Description | +|------------------|--------|----------|---------------------------------------------------------------------------------------------------------------------------------| +| `type` | String | Yes | Registry identity provider type. Must be `aws-ecr`. | +| `registry` | String | Yes | Registry server passed to `docker/login-action`, such as `public.ecr.aws` for public ECR or a private ECR host for private ECR. | +| `role-to-assume` | String | Yes | IAM role ARN assumed through GitHub OIDC. | +| `region` | String | Yes | AWS region passed to `aws-actions/configure-aws-credentials` when assuming the role. Use `us-east-1` for ECR Public. | + The `registry` value is required for AWS ECR. Use the registry server that `docker/login-action` should log in to, such as `public.ecr.aws` for public ECR or `123456789100.dkr.ecr.us-east-1.amazonaws.com` for private ECR. +#### Google Artifact Registry + +Google Artifact Registry authentication is configured with `type: gcp-wif`. +Callers must grant `id-token: write` so `google-github-actions/auth` can +exchange the GitHub OIDC token through GCP Workload Identity Federation: + +```yaml +jobs: + build: + uses: docker/github-builder/.github/workflows/build.yml@v1 + permissions: + contents: read # to fetch the repository content + id-token: write # for signing attestations, cache entries with GitHub OIDC and log in to Google Artifact Registry + with: + output: image + push: ${{ github.event_name != 'pull_request' }} + meta-images: | + us-docker.pkg.dev/my-project/sandbox/test-github-builder + registry-identities: | + - type: gcp-wif + registry: us-docker.pkg.dev + workload_identity_provider: projects/123456789/locations/global/workloadIdentityPools/github/providers/provider + service_account: builder@my-project.iam.gserviceaccount.com + project_id: my-project +``` + +| Name | Type | Required | Description | +|------------------------------|--------|----------|------------------------------------------------------------------------------------------------------------------------------------------------| +| `type` | String | Yes | Registry identity provider type. Must be `gcp-wif`. | +| `registry` | String | Yes | Google Artifact Registry host passed to `docker/login-action`, such as `us-docker.pkg.dev`. Do not use the full repository path. | +| `workload_identity_provider` | String | Yes | Workload Identity Provider resource name passed to `google-github-actions/auth`. | +| `service_account` | String | Yes | Service account email passed to `google-github-actions/auth` for Workload Identity Federation. | +| `project_id` | String | Yes | Google Cloud project ID passed to `google-github-actions/auth`. | + +The `registry` value is required for GCP WIF. Use the Artifact Registry host +that `docker/login-action` should log in to, such as `us-docker.pkg.dev`, not +the full repository path. The workflow requests an access token from +`google-github-actions/auth` and passes it directly to `docker/login-action` +with the `oauth2accesstoken` username inside the same job. + ### Runner mapping The `runner` input accepts either a single GitHub-hosted Linux runner label or a