diff --git a/.github/actions/dockerhub-credentials/action.yml b/.github/actions/dockerhub-credentials/action.yml new file mode 100644 index 00000000..f65075d2 --- /dev/null +++ b/.github/actions/dockerhub-credentials/action.yml @@ -0,0 +1,63 @@ +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@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 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