From f6e4f55eae4b09e6b6eb7f2c89203f3ac6476aad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20Villar?= Date: Thu, 5 Nov 2026 11:21:00 -0600 Subject: [PATCH 1/2] Test Develocity injection with workload identity instead of an access key The Develocity injection integration tests no longer use the long-lived DV_SOLUTIONS_ACCESS_KEY. The jobs that publish Build Scans pass a GitHub OIDC token as the access key, which setup-gradle exchanges for a short-lived token like any access key. The short-lived token failure job uses a fake access key. --- .../actions/develocity-oidc-token/action.yml | 35 ++++++++++++++ .github/workflows/ci-integ-test-full.yml | 1 + .github/workflows/ci-integ-test.yml | 1 + .../integ-test-inject-develocity.yml | 47 +++++++++++++++---- .github/workflows/suite-integ-test-other.yml | 5 +- 5 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 .github/actions/develocity-oidc-token/action.yml diff --git a/.github/actions/develocity-oidc-token/action.yml b/.github/actions/develocity-oidc-token/action.yml new file mode 100644 index 00000000..fa2d93f3 --- /dev/null +++ b/.github/actions/develocity-oidc-token/action.yml @@ -0,0 +1,35 @@ +name: 'Get GitHub OIDC token for Develocity' +description: 'Mint a GitHub OIDC token for solutions.develocity.cloud, in the `host=token` access key form' + +inputs: + access-key-env: + description: 'Name of an env var to also export the access key to, for the following steps' + required: false + default: '' + +outputs: + access-key: + description: 'The OIDC token, as `solutions.develocity.cloud=`' + value: ${{ steps.oidc.outputs.access-key }} + +runs: + using: "composite" + steps: + - name: Get GitHub OIDC token + id: oidc + shell: bash + env: + ACCESS_KEY_ENV: ${{ inputs.access-key-env }} + run: | + if [ -z "$ACTIONS_ID_TOKEN_REQUEST_URL" ]; then + echo "::error::No GitHub OIDC token available, the job needs the 'id-token: write' permission" + exit 1 + fi + token=$(curl -sSfG -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ + --data-urlencode "audience=https://solutions.develocity.cloud" \ + "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r .value) + echo "::add-mask::$token" + echo "access-key=solutions.develocity.cloud=$token" >> "$GITHUB_OUTPUT" + if [ -n "$ACCESS_KEY_ENV" ]; then + echo "$ACCESS_KEY_ENV=solutions.develocity.cloud=$token" >> "$GITHUB_ENV" + fi diff --git a/.github/workflows/ci-integ-test-full.yml b/.github/workflows/ci-integ-test-full.yml index 0d49750a..18f242e0 100644 --- a/.github/workflows/ci-integ-test-full.yml +++ b/.github/workflows/ci-integ-test-full.yml @@ -37,6 +37,7 @@ jobs: permissions: contents: write actions: read + id-token: write needs: caching-integ-tests uses: ./.github/workflows/suite-integ-test-other.yml concurrency: diff --git a/.github/workflows/ci-integ-test.yml b/.github/workflows/ci-integ-test.yml index 6f37d384..5380a2b0 100644 --- a/.github/workflows/ci-integ-test.yml +++ b/.github/workflows/ci-integ-test.yml @@ -48,6 +48,7 @@ jobs: permissions: contents: write actions: read + id-token: write needs: caching-integ-tests uses: ./.github/workflows/suite-integ-test-other.yml concurrency: diff --git a/.github/workflows/integ-test-inject-develocity.yml b/.github/workflows/integ-test-inject-develocity.yml index 3c1eef42..eb70e209 100644 --- a/.github/workflows/integ-test-inject-develocity.yml +++ b/.github/workflows/integ-test-inject-develocity.yml @@ -12,9 +12,6 @@ on: skip-dist: type: boolean default: false - secrets: - DEVELOCITY_ACCESS_KEY: - required: true env: SKIP_DIST: ${{ inputs.skip-dist }} @@ -46,12 +43,14 @@ jobs: - plugin-version: '4.6.1' accessKeyEnv: DEVELOCITY_ACCESS_KEY runs-on: ${{ matrix.os }} + permissions: + contents: read + id-token: write env: DEVELOCITY_INJECTION_ENABLED: true DEVELOCITY_INJECTION_URL: https://solutions.develocity.cloud DEVELOCITY_INJECTION_DEVELOCITY_PLUGIN_VERSION: ${{ matrix.plugin-version }} DEVELOCITY_INJECTION_CCUD_PLUGIN_VERSION: '2.2.1' - ${{matrix.accessKeyEnv}}: ${{ secrets.DEVELOCITY_ACCESS_KEY }} steps: - name: Checkout sources @@ -60,6 +59,12 @@ jobs: uses: ./.github/actions/init-integ-test with: java-version: ${{ matrix.java-version }} + # The OIDC token is set as the access key env var, setup-gradle exchanges it like a long-lived access key + - name: Get GitHub OIDC token for Develocity + id: develocity-oidc + uses: ./.github/actions/develocity-oidc-token + with: + access-key-env: ${{ matrix.accessKeyEnv }} - name: Setup Gradle id: setup-gradle uses: ./setup-gradle @@ -80,6 +85,11 @@ jobs: run: "[ ${#DEVELOCITY_ACCESS_KEY} -gt 500 ] || (echo 'DEVELOCITY_ACCESS_KEY does not look like a short lived token'; exit 1)" - name: Check short lived token (GRADLE_ENTERPRISE_ACCESS_KEY) run: "[ ${#GRADLE_ENTERPRISE_ACCESS_KEY} -gt 500 ] || (echo 'GRADLE_ENTERPRISE_ACCESS_KEY does not look like a short lived token'; exit 1)" + # The OIDC token is also longer than 500 chars, so check it was replaced by the exchanged token + - name: Check access key was exchanged + env: + OIDC_ACCESS_KEY: ${{ steps.develocity-oidc.outputs.access-key }} + run: "[ \"${DEVELOCITY_ACCESS_KEY}\" != \"${OIDC_ACCESS_KEY}\" ] && [ \"${GRADLE_ENTERPRISE_ACCESS_KEY}\" != \"${OIDC_ACCESS_KEY}\" ] || (echo 'Access key was not exchanged for a short lived token'; exit 1)" inject-develocity-with-access-key: env: @@ -104,6 +114,9 @@ jobs: - gradle: '5.6.4' java-version: 11 runs-on: ${{ matrix.os }} + permissions: + contents: read + id-token: write steps: - name: Checkout sources uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -111,13 +124,16 @@ jobs: uses: ./.github/actions/init-integ-test with: java-version: ${{ matrix.java-version }} + - name: Get GitHub OIDC token for Develocity + id: develocity-oidc + uses: ./.github/actions/develocity-oidc-token - name: Setup Gradle id: setup-gradle uses: ./setup-gradle with: cache-read-only: false # For testing, allow writing cache entries on non-default branches gradle-version: ${{ matrix.gradle }} - develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + develocity-access-key: ${{ steps.develocity-oidc.outputs.access-key }} develocity-token-expiry: 1 - name: Run Gradle build id: gradle @@ -127,6 +143,11 @@ jobs: run: "[ ${#DEVELOCITY_ACCESS_KEY} -gt 500 ] || (echo 'DEVELOCITY_ACCESS_KEY does not look like a short lived token'; exit 1)" - name: Check short lived token (GRADLE_ENTERPRISE_ACCESS_KEY) run: "[ ${#GRADLE_ENTERPRISE_ACCESS_KEY} -gt 500 ] || (echo 'GRADLE_ENTERPRISE_ACCESS_KEY does not look like a short lived token'; exit 1)" + # The OIDC token is also longer than 500 chars, so check it was replaced by the exchanged token + - name: Check access key was exchanged + env: + OIDC_ACCESS_KEY: ${{ steps.develocity-oidc.outputs.access-key }} + run: "[ \"${DEVELOCITY_ACCESS_KEY}\" != \"${OIDC_ACCESS_KEY}\" ] && [ \"${GRADLE_ENTERPRISE_ACCESS_KEY}\" != \"${OIDC_ACCESS_KEY}\" ] || (echo 'Access key was not exchanged for a short lived token'; exit 1)" - name: Check Build Scan url if: ${{ !steps.gradle.outputs.build-scan-url }} uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -140,9 +161,10 @@ jobs: DEVELOCITY_INJECTION_URL: 'https://localhost:3333/' DEVELOCITY_INJECTION_DEVELOCITY_PLUGIN_VERSION: ${{ matrix.plugin-version }} DEVELOCITY_INJECTION_CCUD_PLUGIN_VERSION: '2.1' + # Fake access key, so the short-lived token request fails # Access key also set as an env var, we want to check it does not leak - GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} - DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + GRADLE_ENTERPRISE_ACCESS_KEY: 'solutions.develocity.cloud=foo' + DEVELOCITY_ACCESS_KEY: 'solutions.develocity.cloud=foo' strategy: fail-fast: false matrix: @@ -173,7 +195,7 @@ jobs: with: gradle-version: ${{ matrix.gradle }} cache-read-only: false # For testing, allow writing cache entries on non-default branches - develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + develocity-access-key: 'solutions.develocity.cloud=foo' - name: Run Gradle build id: gradle working-directory: .github/workflow-samples/no-ge @@ -184,8 +206,9 @@ jobs: run: "[ \"${GRADLE_ENTERPRISE_ACCESS_KEY}\" != \"\" ] || (echo 'GRADLE_ENTERPRISE_ACCESS_KEY is still supported in v3!'; exit 1)" inject-develocity-with-access-key-from-input-actions: - env: - DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + permissions: + contents: read + id-token: write strategy: fail-fast: false matrix: @@ -210,6 +233,10 @@ jobs: uses: ./.github/actions/init-integ-test with: java-version: ${{ matrix.java-version }} + - name: Get GitHub OIDC token for Develocity + uses: ./.github/actions/develocity-oidc-token + with: + access-key-env: DEVELOCITY_ACCESS_KEY - name: Setup Gradle id: setup-gradle uses: ./setup-gradle diff --git a/.github/workflows/suite-integ-test-other.yml b/.github/workflows/suite-integ-test-other.yml index 6d656c83..4e94ebcf 100644 --- a/.github/workflows/suite-integ-test-other.yml +++ b/.github/workflows/suite-integ-test-other.yml @@ -22,11 +22,12 @@ jobs: develocity-injection: if: ${{ ! github.event.pull_request.head.repo.fork }} + permissions: + contents: read + id-token: write uses: ./.github/workflows/integ-test-inject-develocity.yml with: skip-dist: ${{ inputs.skip-dist }} - secrets: - DEVELOCITY_ACCESS_KEY: ${{ secrets.DV_SOLUTIONS_ACCESS_KEY }} gradle-support-status: permissions: From 96e7f85c081bab6981e97fb6b1cef229a82f3b19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20Villar?= Date: Thu, 5 Nov 2026 11:28:41 -0600 Subject: [PATCH 2/2] Inline the OIDC token step and keep the existing assertions Use the same pre-step as #1109 instead of a composite action, drop the extra exchange check and the fake access key. --- .../actions/develocity-oidc-token/action.yml | 35 --------- .../integ-test-inject-develocity.yml | 77 +++++++++++++------ 2 files changed, 54 insertions(+), 58 deletions(-) delete mode 100644 .github/actions/develocity-oidc-token/action.yml diff --git a/.github/actions/develocity-oidc-token/action.yml b/.github/actions/develocity-oidc-token/action.yml deleted file mode 100644 index fa2d93f3..00000000 --- a/.github/actions/develocity-oidc-token/action.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: 'Get GitHub OIDC token for Develocity' -description: 'Mint a GitHub OIDC token for solutions.develocity.cloud, in the `host=token` access key form' - -inputs: - access-key-env: - description: 'Name of an env var to also export the access key to, for the following steps' - required: false - default: '' - -outputs: - access-key: - description: 'The OIDC token, as `solutions.develocity.cloud=`' - value: ${{ steps.oidc.outputs.access-key }} - -runs: - using: "composite" - steps: - - name: Get GitHub OIDC token - id: oidc - shell: bash - env: - ACCESS_KEY_ENV: ${{ inputs.access-key-env }} - run: | - if [ -z "$ACTIONS_ID_TOKEN_REQUEST_URL" ]; then - echo "::error::No GitHub OIDC token available, the job needs the 'id-token: write' permission" - exit 1 - fi - token=$(curl -sSfG -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ - --data-urlencode "audience=https://solutions.develocity.cloud" \ - "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r .value) - echo "::add-mask::$token" - echo "access-key=solutions.develocity.cloud=$token" >> "$GITHUB_OUTPUT" - if [ -n "$ACCESS_KEY_ENV" ]; then - echo "$ACCESS_KEY_ENV=solutions.develocity.cloud=$token" >> "$GITHUB_ENV" - fi diff --git a/.github/workflows/integ-test-inject-develocity.yml b/.github/workflows/integ-test-inject-develocity.yml index eb70e209..1e36894c 100644 --- a/.github/workflows/integ-test-inject-develocity.yml +++ b/.github/workflows/integ-test-inject-develocity.yml @@ -59,12 +59,19 @@ jobs: uses: ./.github/actions/init-integ-test with: java-version: ${{ matrix.java-version }} - # The OIDC token is set as the access key env var, setup-gradle exchanges it like a long-lived access key - name: Get GitHub OIDC token for Develocity id: develocity-oidc - uses: ./.github/actions/develocity-oidc-token - with: - access-key-env: ${{ matrix.accessKeyEnv }} + shell: bash + run: | + if [ -z "$ACTIONS_ID_TOKEN_REQUEST_URL" ]; then + echo "::warning::No GitHub OIDC token available (e.g. PR from a fork), Develocity will not be authenticated" + exit 0 + fi + token=$(curl -sSfG -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ + --data-urlencode "audience=https://solutions.develocity.cloud" \ + "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r .value) + echo "::add-mask::$token" + echo "${{ matrix.accessKeyEnv }}=solutions.develocity.cloud=$token" >> "$GITHUB_ENV" - name: Setup Gradle id: setup-gradle uses: ./setup-gradle @@ -85,11 +92,6 @@ jobs: run: "[ ${#DEVELOCITY_ACCESS_KEY} -gt 500 ] || (echo 'DEVELOCITY_ACCESS_KEY does not look like a short lived token'; exit 1)" - name: Check short lived token (GRADLE_ENTERPRISE_ACCESS_KEY) run: "[ ${#GRADLE_ENTERPRISE_ACCESS_KEY} -gt 500 ] || (echo 'GRADLE_ENTERPRISE_ACCESS_KEY does not look like a short lived token'; exit 1)" - # The OIDC token is also longer than 500 chars, so check it was replaced by the exchanged token - - name: Check access key was exchanged - env: - OIDC_ACCESS_KEY: ${{ steps.develocity-oidc.outputs.access-key }} - run: "[ \"${DEVELOCITY_ACCESS_KEY}\" != \"${OIDC_ACCESS_KEY}\" ] && [ \"${GRADLE_ENTERPRISE_ACCESS_KEY}\" != \"${OIDC_ACCESS_KEY}\" ] || (echo 'Access key was not exchanged for a short lived token'; exit 1)" inject-develocity-with-access-key: env: @@ -126,7 +128,17 @@ jobs: java-version: ${{ matrix.java-version }} - name: Get GitHub OIDC token for Develocity id: develocity-oidc - uses: ./.github/actions/develocity-oidc-token + shell: bash + run: | + if [ -z "$ACTIONS_ID_TOKEN_REQUEST_URL" ]; then + echo "::warning::No GitHub OIDC token available (e.g. PR from a fork), Develocity will not be authenticated" + exit 0 + fi + token=$(curl -sSfG -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ + --data-urlencode "audience=https://solutions.develocity.cloud" \ + "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r .value) + echo "::add-mask::$token" + echo "access-key=solutions.develocity.cloud=$token" >> "$GITHUB_OUTPUT" - name: Setup Gradle id: setup-gradle uses: ./setup-gradle @@ -143,11 +155,6 @@ jobs: run: "[ ${#DEVELOCITY_ACCESS_KEY} -gt 500 ] || (echo 'DEVELOCITY_ACCESS_KEY does not look like a short lived token'; exit 1)" - name: Check short lived token (GRADLE_ENTERPRISE_ACCESS_KEY) run: "[ ${#GRADLE_ENTERPRISE_ACCESS_KEY} -gt 500 ] || (echo 'GRADLE_ENTERPRISE_ACCESS_KEY does not look like a short lived token'; exit 1)" - # The OIDC token is also longer than 500 chars, so check it was replaced by the exchanged token - - name: Check access key was exchanged - env: - OIDC_ACCESS_KEY: ${{ steps.develocity-oidc.outputs.access-key }} - run: "[ \"${DEVELOCITY_ACCESS_KEY}\" != \"${OIDC_ACCESS_KEY}\" ] && [ \"${GRADLE_ENTERPRISE_ACCESS_KEY}\" != \"${OIDC_ACCESS_KEY}\" ] || (echo 'Access key was not exchanged for a short lived token'; exit 1)" - name: Check Build Scan url if: ${{ !steps.gradle.outputs.build-scan-url }} uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -161,10 +168,6 @@ jobs: DEVELOCITY_INJECTION_URL: 'https://localhost:3333/' DEVELOCITY_INJECTION_DEVELOCITY_PLUGIN_VERSION: ${{ matrix.plugin-version }} DEVELOCITY_INJECTION_CCUD_PLUGIN_VERSION: '2.1' - # Fake access key, so the short-lived token request fails - # Access key also set as an env var, we want to check it does not leak - GRADLE_ENTERPRISE_ACCESS_KEY: 'solutions.develocity.cloud=foo' - DEVELOCITY_ACCESS_KEY: 'solutions.develocity.cloud=foo' strategy: fail-fast: false matrix: @@ -182,6 +185,9 @@ jobs: - gradle: '5.6.4' java-version: 11 runs-on: ubuntu-latest + permissions: + contents: read + id-token: write steps: - name: Checkout sources uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -189,13 +195,29 @@ jobs: uses: ./.github/actions/init-integ-test with: java-version: ${{ matrix.java-version }} + # Access key also set as an env var, we want to check it does not leak + - name: Get GitHub OIDC token for Develocity + id: develocity-oidc + shell: bash + run: | + if [ -z "$ACTIONS_ID_TOKEN_REQUEST_URL" ]; then + echo "::warning::No GitHub OIDC token available (e.g. PR from a fork), Develocity will not be authenticated" + exit 0 + fi + token=$(curl -sSfG -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ + --data-urlencode "audience=https://solutions.develocity.cloud" \ + "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r .value) + echo "::add-mask::$token" + echo "access-key=solutions.develocity.cloud=$token" >> "$GITHUB_OUTPUT" + echo "GRADLE_ENTERPRISE_ACCESS_KEY=solutions.develocity.cloud=$token" >> "$GITHUB_ENV" + echo "DEVELOCITY_ACCESS_KEY=solutions.develocity.cloud=$token" >> "$GITHUB_ENV" - name: Setup Gradle id: setup-gradle uses: ./setup-gradle with: gradle-version: ${{ matrix.gradle }} cache-read-only: false # For testing, allow writing cache entries on non-default branches - develocity-access-key: 'solutions.develocity.cloud=foo' + develocity-access-key: ${{ steps.develocity-oidc.outputs.access-key }} - name: Run Gradle build id: gradle working-directory: .github/workflow-samples/no-ge @@ -234,9 +256,18 @@ jobs: with: java-version: ${{ matrix.java-version }} - name: Get GitHub OIDC token for Develocity - uses: ./.github/actions/develocity-oidc-token - with: - access-key-env: DEVELOCITY_ACCESS_KEY + id: develocity-oidc + shell: bash + run: | + if [ -z "$ACTIONS_ID_TOKEN_REQUEST_URL" ]; then + echo "::warning::No GitHub OIDC token available (e.g. PR from a fork), Develocity will not be authenticated" + exit 0 + fi + token=$(curl -sSfG -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ + --data-urlencode "audience=https://solutions.develocity.cloud" \ + "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r .value) + echo "::add-mask::$token" + echo "DEVELOCITY_ACCESS_KEY=solutions.develocity.cloud=$token" >> "$GITHUB_ENV" - name: Setup Gradle id: setup-gradle uses: ./setup-gradle