From 9bda8b5f248d2e020cec3503c3bcc78a188ea2b3 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Sat, 7 Nov 2026 15:27:33 +0000 Subject: [PATCH 1/5] Add shared Docker Hub credential lookup action --- .../actions/dockerhub-credentials/action.yml | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/actions/dockerhub-credentials/action.yml diff --git a/.github/actions/dockerhub-credentials/action.yml b/.github/actions/dockerhub-credentials/action.yml new file mode 100644 index 00000000..2833a898 --- /dev/null +++ b/.github/actions/dockerhub-credentials/action.yml @@ -0,0 +1,61 @@ +name: Docker Hub credentials from SSM +description: Read the shared registry credential without replacing the caller's AWS credentials. +inputs: + role-arn: + description: Optional role to assume for the read; omit to use existing AWS credentials. + required: false + default: '' + role-chaining: + description: Assume the role using existing AWS credentials rather than GitHub OIDC. + required: false + default: 'false' +outputs: + username: + value: ${{ steps.read.outputs.username }} + password: + value: ${{ steps.read.outputs.password }} + credentials: + description: JSON username/password pair for Ansible. + value: ${{ steps.read.outputs.credentials }} +runs: + using: composite + steps: + - uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4.3.1 + if: inputs.role-arn != '' + id: aws + with: + role-to-assume: ${{ inputs.role-arn }} + role-chaining: ${{ inputs.role-chaining }} + aws-region: us-east-1 + allowed-account-ids: '613957054632' + output-credentials: true + output-env-credentials: false + - id: read + shell: bash + env: + AWS_ACCESS_KEY_ID: ${{ steps.aws.outputs.aws-access-key-id || env.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ steps.aws.outputs.aws-secret-access-key || env.AWS_SECRET_ACCESS_KEY }} + AWS_SESSION_TOKEN: ${{ steps.aws.outputs.aws-session-token || env.AWS_SESSION_TOKEN }} + run: | + python3 - <<'PY' + import json, os, subprocess + raw = subprocess.check_output([ + "aws", "ssm", "get-parameter", "--name", "/kernel/dockerhub/credentials", + "--region", "us-east-1", "--with-decryption", "--output", "json", + ], text=True) + parameter = json.loads(raw)["Parameter"] + if parameter["Type"] != "SecureString": + raise ValueError("Docker Hub credentials must be stored as a SecureString") + credential = json.loads(parameter["Value"]) + for key in ("username", "password"): + value = credential.get(key) + if not isinstance(value, str) or not value or "\n" in value or "\r" in value: + raise ValueError("SSM must contain a nonempty, single-line username and password") + values = {key: credential[key] for key in ("username", "password")} + values["credentials"] = json.dumps(values) + for value in values.values(): + print("::add-mask::" + value.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")) + with open(os.environ["GITHUB_OUTPUT"], "a") as output: + for key, value in values.items(): + output.write(f"{key}={value}\n") + PY From 8ef30f9f1e4e2c33533b1b4fc75734b79103efa6 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Sat, 7 Nov 2026 16:05:31 +0000 Subject: [PATCH 2/5] Read Docker Hub credentials from SSM --- .github/workflows/chromium-headful-image.yaml | 17 +++++++++++------ .../workflows/chromium-headless-image.yaml | 17 +++++++++++------ .github/workflows/server-test.yaml | 19 ++++++++++++++++--- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/.github/workflows/chromium-headful-image.yaml b/.github/workflows/chromium-headful-image.yaml index 417126e6..cff51f5a 100644 --- a/.github/workflows/chromium-headful-image.yaml +++ b/.github/workflows/chromium-headful-image.yaml @@ -5,13 +5,12 @@ on: jobs: docker: - # Fork PRs get no secrets and now must not reach the self-hosted pool - # either: skipping here (rather than failing at registry login, as - # before) also skips the dependent e2e job via its needs chain. - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + # Credential-bearing builds run on trusted branch pushes, not PR merge refs. + if: github.repository == 'kernel/kernel-images' && github.event_name != 'pull_request' runs-on: [self-hosted, linux] permissions: contents: read + id-token: write steps: - name: Checkout uses: actions/checkout@v4 @@ -43,11 +42,17 @@ jobs: # that root-run CI from other repos clobbers to root-owned. echo "BUILDX_CONFIG=${HOME}/.docker/buildx-ci" >> "$GITHUB_ENV" + - name: Read Docker Hub credentials from SSM + id: dockerhub + uses: ./.github/actions/dockerhub-credentials + with: + role-arn: arn:aws:iam::613957054632:role/github-actions-kernel-images + - name: Login to Docker Hub uses: docker/login-action@v3 with: - username: ${{ vars.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + username: ${{ steps.dockerhub.outputs.username }} + password: ${{ steps.dockerhub.outputs.password }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/chromium-headless-image.yaml b/.github/workflows/chromium-headless-image.yaml index 26c6b64a..80008366 100644 --- a/.github/workflows/chromium-headless-image.yaml +++ b/.github/workflows/chromium-headless-image.yaml @@ -5,13 +5,12 @@ on: jobs: docker: - # Fork PRs get no secrets and now must not reach the self-hosted pool - # either: skipping here (rather than failing at registry login, as - # before) also skips the dependent e2e job via its needs chain. - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + # Credential-bearing builds run on trusted branch pushes, not PR merge refs. + if: github.repository == 'kernel/kernel-images' && github.event_name != 'pull_request' runs-on: [self-hosted, linux] permissions: contents: read + id-token: write steps: - name: Checkout uses: actions/checkout@v4 @@ -43,11 +42,17 @@ jobs: # that root-run CI from other repos clobbers to root-owned. echo "BUILDX_CONFIG=${HOME}/.docker/buildx-ci" >> "$GITHUB_ENV" + - name: Read Docker Hub credentials from SSM + id: dockerhub + uses: ./.github/actions/dockerhub-credentials + with: + role-arn: arn:aws:iam::613957054632:role/github-actions-kernel-images + - name: Login to Docker Hub uses: docker/login-action@v3 with: - username: ${{ vars.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + username: ${{ steps.dockerhub.outputs.username }} + password: ${{ steps.dockerhub.outputs.password }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/server-test.yaml b/.github/workflows/server-test.yaml index 0e92f023..facffc8e 100644 --- a/.github/workflows/server-test.yaml +++ b/.github/workflows/server-test.yaml @@ -2,7 +2,7 @@ name: Test for the server/ directory on: push: - branches: ["main"] + branches: ["**"] pull_request: branches: ["main"] workflow_dispatch: @@ -13,10 +13,16 @@ concurrency: jobs: build-headful: + permissions: + contents: read + id-token: write uses: ./.github/workflows/chromium-headful-image.yaml secrets: inherit build-headless: + permissions: + contents: read + id-token: write uses: ./.github/workflows/chromium-headless-image.yaml secrets: inherit @@ -67,6 +73,7 @@ jobs: needs: [build-headful, build-headless] permissions: contents: read + id-token: write steps: - name: Checkout code @@ -119,11 +126,17 @@ jobs: shell: bash run: echo "short_sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" + - name: Read Docker Hub credentials from SSM + id: dockerhub + uses: ./.github/actions/dockerhub-credentials + with: + role-arn: arn:aws:iam::613957054632:role/github-actions-kernel-images + - name: Login to Docker Hub uses: docker/login-action@v3 with: - username: ${{ vars.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + username: ${{ steps.dockerhub.outputs.username }} + password: ${{ steps.dockerhub.outputs.password }} - name: Pull e2e images shell: bash From 00132e625d389a25deaa4973abd1ba179f70219f Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Sat, 7 Nov 2026 16:22:44 +0000 Subject: [PATCH 3/5] Describe the credential action outputs --- .github/actions/dockerhub-credentials/action.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/actions/dockerhub-credentials/action.yml b/.github/actions/dockerhub-credentials/action.yml index 2833a898..f65075d2 100644 --- a/.github/actions/dockerhub-credentials/action.yml +++ b/.github/actions/dockerhub-credentials/action.yml @@ -11,8 +11,10 @@ inputs: default: 'false' outputs: username: + description: Docker Hub username. value: ${{ steps.read.outputs.username }} password: + description: Docker Hub access token. value: ${{ steps.read.outputs.password }} credentials: description: JSON username/password pair for Ansible. From 230a3816ef28b47af3c8ab3490064f2620735f2c Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Mon, 9 Nov 2026 16:04:47 +0000 Subject: [PATCH 4/5] Avoid session tagging for chained credential reads --- .github/actions/dockerhub-credentials/action.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/actions/dockerhub-credentials/action.yml b/.github/actions/dockerhub-credentials/action.yml index f65075d2..f08ecccd 100644 --- a/.github/actions/dockerhub-credentials/action.yml +++ b/.github/actions/dockerhub-credentials/action.yml @@ -22,12 +22,13 @@ outputs: runs: using: composite steps: - - uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4.3.1 + - uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6 if: inputs.role-arn != '' id: aws with: role-to-assume: ${{ inputs.role-arn }} role-chaining: ${{ inputs.role-chaining }} + role-skip-session-tagging: ${{ inputs.role-chaining }} aws-region: us-east-1 allowed-account-ids: '613957054632' output-credentials: true From 8dd3f5830b8c918d8aec25c3fee77aa77b16805d Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Wed, 11 Nov 2026 16:20:24 +0000 Subject: [PATCH 5/5] Use Docker Hub OIDC for public image CI --- .../actions/dockerhub-credentials/action.yml | 64 ------------------- .github/workflows/chromium-headful-image.yaml | 20 +++--- .../workflows/chromium-headless-image.yaml | 20 +++--- .github/workflows/server-test.yaml | 17 ++--- 4 files changed, 22 insertions(+), 99 deletions(-) delete mode 100644 .github/actions/dockerhub-credentials/action.yml diff --git a/.github/actions/dockerhub-credentials/action.yml b/.github/actions/dockerhub-credentials/action.yml deleted file mode 100644 index f08ecccd..00000000 --- a/.github/actions/dockerhub-credentials/action.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Docker Hub credentials from SSM -description: Read the shared registry credential without replacing the caller's AWS credentials. -inputs: - role-arn: - description: Optional role to assume for the read; omit to use existing AWS credentials. - required: false - default: '' - role-chaining: - description: Assume the role using existing AWS credentials rather than GitHub OIDC. - required: false - default: 'false' -outputs: - username: - description: Docker Hub username. - value: ${{ steps.read.outputs.username }} - password: - description: Docker Hub access token. - value: ${{ steps.read.outputs.password }} - credentials: - description: JSON username/password pair for Ansible. - value: ${{ steps.read.outputs.credentials }} -runs: - using: composite - steps: - - uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6 - if: inputs.role-arn != '' - id: aws - with: - role-to-assume: ${{ inputs.role-arn }} - role-chaining: ${{ inputs.role-chaining }} - role-skip-session-tagging: ${{ inputs.role-chaining }} - aws-region: us-east-1 - allowed-account-ids: '613957054632' - output-credentials: true - output-env-credentials: false - - id: read - shell: bash - env: - AWS_ACCESS_KEY_ID: ${{ steps.aws.outputs.aws-access-key-id || env.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ steps.aws.outputs.aws-secret-access-key || env.AWS_SECRET_ACCESS_KEY }} - AWS_SESSION_TOKEN: ${{ steps.aws.outputs.aws-session-token || env.AWS_SESSION_TOKEN }} - run: | - python3 - <<'PY' - import json, os, subprocess - raw = subprocess.check_output([ - "aws", "ssm", "get-parameter", "--name", "/kernel/dockerhub/credentials", - "--region", "us-east-1", "--with-decryption", "--output", "json", - ], text=True) - parameter = json.loads(raw)["Parameter"] - if parameter["Type"] != "SecureString": - raise ValueError("Docker Hub credentials must be stored as a SecureString") - credential = json.loads(parameter["Value"]) - for key in ("username", "password"): - value = credential.get(key) - if not isinstance(value, str) or not value or "\n" in value or "\r" in value: - raise ValueError("SSM must contain a nonempty, single-line username and password") - values = {key: credential[key] for key in ("username", "password")} - values["credentials"] = json.dumps(values) - for value in values.values(): - print("::add-mask::" + value.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")) - with open(os.environ["GITHUB_OUTPUT"], "a") as output: - for key, value in values.items(): - output.write(f"{key}={value}\n") - PY diff --git a/.github/workflows/chromium-headful-image.yaml b/.github/workflows/chromium-headful-image.yaml index cff51f5a..355dffea 100644 --- a/.github/workflows/chromium-headful-image.yaml +++ b/.github/workflows/chromium-headful-image.yaml @@ -5,9 +5,10 @@ on: jobs: docker: - # Credential-bearing builds run on trusted branch pushes, not PR merge refs. - if: github.repository == 'kernel/kernel-images' && github.event_name != 'pull_request' + # Fork PRs must not reach the self-hosted pool or the publishing identity. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository runs-on: [self-hosted, linux] + environment: dockerhub-publish permissions: contents: read id-token: write @@ -42,17 +43,12 @@ jobs: # that root-run CI from other repos clobbers to root-owned. echo "BUILDX_CONFIG=${HOME}/.docker/buildx-ci" >> "$GITHUB_ENV" - - name: Read Docker Hub credentials from SSM - id: dockerhub - uses: ./.github/actions/dockerhub-credentials + - name: Login to Docker Hub with OIDC + uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0 with: - role-arn: arn:aws:iam::613957054632:role/github-actions-kernel-images - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ steps.dockerhub.outputs.username }} - password: ${{ steps.dockerhub.outputs.password }} + username: onkernel + env: + DOCKERHUB_OIDC_CONNECTIONID: ${{ vars.DOCKERHUB_OIDC_PUSH_CONNECTION_ID }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/chromium-headless-image.yaml b/.github/workflows/chromium-headless-image.yaml index 80008366..2e00514f 100644 --- a/.github/workflows/chromium-headless-image.yaml +++ b/.github/workflows/chromium-headless-image.yaml @@ -5,9 +5,10 @@ on: jobs: docker: - # Credential-bearing builds run on trusted branch pushes, not PR merge refs. - if: github.repository == 'kernel/kernel-images' && github.event_name != 'pull_request' + # Fork PRs must not reach the self-hosted pool or the publishing identity. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository runs-on: [self-hosted, linux] + environment: dockerhub-publish permissions: contents: read id-token: write @@ -42,17 +43,12 @@ jobs: # that root-run CI from other repos clobbers to root-owned. echo "BUILDX_CONFIG=${HOME}/.docker/buildx-ci" >> "$GITHUB_ENV" - - name: Read Docker Hub credentials from SSM - id: dockerhub - uses: ./.github/actions/dockerhub-credentials + - name: Login to Docker Hub with OIDC + uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0 with: - role-arn: arn:aws:iam::613957054632:role/github-actions-kernel-images - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ steps.dockerhub.outputs.username }} - password: ${{ steps.dockerhub.outputs.password }} + username: onkernel + env: + DOCKERHUB_OIDC_CONNECTIONID: ${{ vars.DOCKERHUB_OIDC_PUSH_CONNECTION_ID }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/server-test.yaml b/.github/workflows/server-test.yaml index facffc8e..106b7628 100644 --- a/.github/workflows/server-test.yaml +++ b/.github/workflows/server-test.yaml @@ -2,7 +2,7 @@ name: Test for the server/ directory on: push: - branches: ["**"] + branches: ["main"] pull_request: branches: ["main"] workflow_dispatch: @@ -126,17 +126,12 @@ jobs: shell: bash run: echo "short_sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" - - name: Read Docker Hub credentials from SSM - id: dockerhub - uses: ./.github/actions/dockerhub-credentials - with: - role-arn: arn:aws:iam::613957054632:role/github-actions-kernel-images - - - name: Login to Docker Hub - uses: docker/login-action@v3 + - name: Login to Docker Hub with OIDC + uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0 with: - username: ${{ steps.dockerhub.outputs.username }} - password: ${{ steps.dockerhub.outputs.password }} + username: onkernel + env: + DOCKERHUB_OIDC_CONNECTIONID: ${{ vars.DOCKERHUB_OIDC_PULL_CONNECTION_ID }} - name: Pull e2e images shell: bash