From 56998903734d5a4f7257af408eaaaef3707ab8ec Mon Sep 17 00:00:00 2001 From: Aurelien Poscia Date: Thu, 22 Oct 2026 19:04:08 +0200 Subject: [PATCH 1/4] SC-60373 Use WIF to authenticate SonarQube Cloud scans in build actions Co-Authored-By: Claude Sonnet 5.5 --- README.md | 42 +++++++++++++++++++++++++++++++++++ build-gradle/action.yml | 29 +++++++++++++++--------- build-gradle/build.sh | 4 +--- build-maven/action.yml | 29 +++++++++++++++--------- build-maven/build.sh | 4 +--- build-npm/action.yml | 29 +++++++++++++++--------- build-npm/build.sh | 4 +--- build-poetry/action.yml | 29 +++++++++++++++--------- build-poetry/build.sh | 4 +--- build-yarn/action.yml | 29 +++++++++++++++--------- build-yarn/build.sh | 4 +--- shared/common-functions.sh | 25 ++++++++++++++++++++- spec/shared-functions_spec.sh | 42 +++++++++++++++++++++++++++++++++++ 13 files changed, 208 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 3d7db2a5..44ea7ad0 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,28 @@ The selected URL also determines the matching Vault server and reader-token engi use the configured resolve URL instead of a hardcoded SaaS hostname. - To resolve from SaaS Repox instead, set `repox-url: https://repox.jfrog.io`. +## SonarQube Cloud authentication + +The `build-*` actions authenticate to SonarQube Cloud (`sonar-platform: sqc-eu` or `sqc-us`, and the matching platforms of +`run-shadow-scans`) with the GitHub OIDC token of the job, through +[`sonarqube-auth-action`](https://github.com/SonarSource/sonarqube-auth-action), instead of with Vault secrets. +The `next` platform still reads its URL and token from Vault. + +Requirements for each SonarQube Cloud platform that is analyzed: + +- The job has the `id-token: write` permission. +- The repository is bound to a SonarQube Cloud project on that platform through the GitHub integration, and an administrator of the + organization has enabled trusted scanning for GitHub Actions. +- The `sonar.organization` and `sonar.projectKey` of the build match the project bound to the repository: the token only grants + access to that project. +- The build, the tests and the analysis complete within 30 minutes. The token is requested right before the build and lasts 30 + minutes, which is the delay the analysis report must be uploaded within. + +The token exchange fails the job when the repository is not bound, or when the job has no OIDC token. This is the case for +pull requests from forks, as GitHub does not grant `id-token: write` to them. + +The secrets `development/kv/data/sonarcloud` and `development/kv/data/sonarqube-us` are not read anymore. + --- ## `get-build-number` @@ -339,6 +361,10 @@ Build and deploy a Maven project with SonarQube analysis and Artifactory deploym - `public-deployer` or `qa-deployer`: Artifactory role for deployment. - `development/kv/data/develocity`: Develocity access token (only fetched when `use-develocity: true`). +#### SonarQube Cloud Authentication + +Analysis on `sqc-eu` and `sqc-us` uses the GitHub OIDC token, see [SonarQube Cloud authentication](#sonarqube-cloud-authentication). + #### Other Dependencies - The Java and Maven tools must be pre-installed. Use of `mise` is recommended. @@ -577,6 +603,10 @@ Build, analyze, and publish a Python project using Poetry with SonarQube integra - `public-reader` or `private-reader`: Artifactory role for reading dependencies - `public-deployer` or `qa-deployer`: Artifactory role for deployment +#### SonarQube Cloud Authentication + +Analysis on `sqc-eu` and `sqc-us` uses the GitHub OIDC token, see [SonarQube Cloud authentication](#sonarqube-cloud-authentication). + #### Other Dependencies The Python and Poetry tools must be pre-installed. Use of `mise` is recommended. @@ -798,6 +828,10 @@ Build and publish a Gradle project with SonarQube analysis and Artifactory deplo - `public-reader` or `private-reader`: Artifactory role for reading dependencies - `public-deployer` or `qa-deployer`: Artifactory role for deployment +#### SonarQube Cloud Authentication + +Analysis on `sqc-eu` and `sqc-us` uses the GitHub OIDC token, see [SonarQube Cloud authentication](#sonarqube-cloud-authentication). + #### Other Dependencies **Java**: Not pre-installed in the runner image. We recommend using `mise` to install and manage Java versions. @@ -1107,6 +1141,10 @@ Build, test, analyze with SonarQube, and deploy an NPM project to JFrog Artifact - `public-reader` or `private-reader`: Artifactory role for reading dependencies - `public-deployer` or `qa-deployer`: Artifactory role for deployment +#### SonarQube Cloud Authentication + +Analysis on `sqc-eu` and `sqc-us` uses the GitHub OIDC token, see [SonarQube Cloud authentication](#sonarqube-cloud-authentication). + #### Other Dependencies The Node.js and NPM tools must be pre-installed. Use of `mise` is recommended. @@ -1219,6 +1257,10 @@ Build, test, analyze, and deploy a Yarn project with SonarQube integration and A - `public-reader` or `private-reader`: Artifactory role for reading dependencies - `public-deployer` or `qa-deployer`: Artifactory role for deployment +#### SonarQube Cloud Authentication + +Analysis on `sqc-eu` and `sqc-us` uses the GitHub OIDC token, see [SonarQube Cloud authentication](#sonarqube-cloud-authentication). + #### Other Dependencies The Node.js and Yarn tools must be pre-installed. Use of `mise` is recommended. diff --git a/build-gradle/action.yml b/build-gradle/action.yml index ed7043ef..a9141c1b 100644 --- a/build-gradle/action.yml +++ b/build-gradle/action.yml @@ -142,16 +142,25 @@ runs: with: # yamllint disable rule:line-length secrets: | - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us token | SQC_US_TOKEN;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud token | SQC_EU_TOKEN;' || '' }} + ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} + ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} development/kv/data/sign key | SIGN_KEY; development/kv/data/sign passphrase | PGP_PASSPHRASE; development/kv/data/sign key_id | SIGN_KEY_ID; # yamllint enable rule:line-length + # The SonarQube Cloud tokens expire after 30 minutes: keep these steps right before the build step. + - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 + id: sqc-eu-auth + if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-eu' + with: + sonar-host-url: https://sonarcloud.io + export-env: 'false' + - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 + id: sqc-us-auth + if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-us' + with: + sonar-host-url: https://sonarqube.us + export-env: 'false' - name: Build, analyze and deploy shell: bash @@ -181,10 +190,10 @@ runs: # yamllint enable rule:line-length NEXT_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_URL || '' }} NEXT_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_TOKEN || '' }} - SQC_EU_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_EU_URL || '' }} - SQC_EU_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_EU_TOKEN || '' }} - SQC_US_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_US_URL || '' }} - SQC_US_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_US_TOKEN || '' }} + SQC_EU_URL: ${{ steps.sqc-eu-auth.outputs.sonar-host-url }} + SQC_EU_TOKEN: ${{ steps.sqc-eu-auth.outputs.sonar-token }} + SQC_US_URL: ${{ steps.sqc-us-auth.outputs.sonar-host-url }} + SQC_US_TOKEN: ${{ steps.sqc-us-auth.outputs.sonar-token }} ORG_GRADLE_PROJECT_signingKey: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SIGN_KEY || '' }} ORG_GRADLE_PROJECT_signingPassword: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).PGP_PASSPHRASE || '' }} ORG_GRADLE_PROJECT_signingKeyId: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SIGN_KEY_ID || '' }} diff --git a/build-gradle/build.sh b/build-gradle/build.sh index 2c94b984..1706a681 100755 --- a/build-gradle/build.sh +++ b/build-gradle/build.sh @@ -55,9 +55,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/../shared/common-functions.sh" if [[ "${DEPLOY:=true}" != "false" && "$RUN_SHADOW_SCANS" != "true" ]]; then : "${ARTIFACTORY_DEPLOY_USERNAME:?}" "${ARTIFACTORY_DEPLOY_ACCESS_TOKEN:?}" fi -if [[ "${SONAR_PLATFORM:?}" != "none" || "$RUN_SHADOW_SCANS" == "true" ]]; then - : "${NEXT_URL:?}" "${NEXT_TOKEN:?}" "${SQC_US_URL:?}" "${SQC_US_TOKEN:?}" "${SQC_EU_URL:?}" "${SQC_EU_TOKEN:?}" -fi +require_sonar_env : "${ORG_GRADLE_PROJECT_signingKey:?}" "${ORG_GRADLE_PROJECT_signingPassword:?}" "${ORG_GRADLE_PROJECT_signingKeyId:?}" : "${SKIP_TESTS:=false}" "${GRADLE_ARGS:=}" export DEPLOY DEPLOY_PULL_REQUEST SKIP_TESTS GRADLE_ARGS diff --git a/build-maven/action.yml b/build-maven/action.yml index e36fa1ca..c0603d44 100644 --- a/build-maven/action.yml +++ b/build-maven/action.yml @@ -177,15 +177,24 @@ runs: with: # yamllint disable rule:line-length secrets: | - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us token | SQC_US_TOKEN;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud token | SQC_EU_TOKEN;' || '' }} + ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} + ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} development/kv/data/sign key | SIGN_KEY; development/kv/data/sign passphrase | PGP_PASSPHRASE; # yamllint enable rule:line-length + # The SonarQube Cloud tokens expire after 30 minutes: keep these steps right before the build step. + - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 + id: sqc-eu-auth + if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-eu' + with: + sonar-host-url: https://sonarcloud.io + export-env: 'false' + - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 + id: sqc-us-auth + if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-us' + with: + sonar-host-url: https://sonarqube.us + export-env: 'false' - name: Build, analyze and deploy shell: bash @@ -215,10 +224,10 @@ runs: # yamllint enable rule:line-length NEXT_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_URL || '' }} NEXT_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_TOKEN || '' }} - SQC_EU_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_EU_URL || '' }} - SQC_EU_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_EU_TOKEN || '' }} - SQC_US_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_US_URL || '' }} - SQC_US_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_US_TOKEN || '' }} + SQC_EU_URL: ${{ steps.sqc-eu-auth.outputs.sonar-host-url }} + SQC_EU_TOKEN: ${{ steps.sqc-eu-auth.outputs.sonar-token }} + SQC_US_URL: ${{ steps.sqc-us-auth.outputs.sonar-host-url }} + SQC_US_TOKEN: ${{ steps.sqc-us-auth.outputs.sonar-token }} SIGN_KEY: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SIGN_KEY || '' }} PGP_PASSPHRASE: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).PGP_PASSPHRASE || '' }} working-directory: ${{ inputs.working-directory }} diff --git a/build-maven/build.sh b/build-maven/build.sh index a8375651..8764a1a5 100755 --- a/build-maven/build.sh +++ b/build-maven/build.sh @@ -53,9 +53,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/../shared/common-functions.sh" if [[ "${DEPLOY:=true}" != "false" && "$RUN_SHADOW_SCANS" != "true" ]]; then : "${ARTIFACTORY_DEPLOY_USERNAME:?}" "${ARTIFACTORY_DEPLOY_PASSWORD:?}" fi -if [[ "${SONAR_PLATFORM:?}" != "none" || "$RUN_SHADOW_SCANS" == "true" ]]; then - : "${NEXT_URL:?}" "${NEXT_TOKEN:?}" "${SQC_US_URL:?}" "${SQC_US_TOKEN:?}" "${SQC_EU_URL:?}" "${SQC_EU_TOKEN:?}" -fi +require_sonar_env : "${USER_MAVEN_ARGS:=}" : "${SKIP_BUILD:=false}" if [[ "$SKIP_BUILD" == "true" && "$DEPLOY" != "false" ]]; then diff --git a/build-npm/action.yml b/build-npm/action.yml index de0473a0..1cf28a2c 100644 --- a/build-npm/action.yml +++ b/build-npm/action.yml @@ -147,13 +147,22 @@ runs: # yamllint disable rule:line-length with: secrets: | - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us token | SQC_US_TOKEN;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud token | SQC_EU_TOKEN;' || '' }} + ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} + ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} # yamllint enable rule:line-length + # The SonarQube Cloud tokens expire after 30 minutes: keep these steps right before the build step. + - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 + id: sqc-eu-auth + if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-eu' + with: + sonar-host-url: https://sonarcloud.io + export-env: 'false' + - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 + id: sqc-us-auth + if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-us' + with: + sonar-host-url: https://sonarqube.us + export-env: 'false' - name: Build, test, analyze and deploy id: build shell: bash @@ -178,10 +187,10 @@ runs: SONAR_PLATFORM: ${{ inputs.sonar-platform }} NEXT_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_URL || '' }} NEXT_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_TOKEN || '' }} - SQC_EU_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_EU_URL || '' }} - SQC_EU_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_EU_TOKEN || '' }} - SQC_US_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_US_URL || '' }} - SQC_US_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_US_TOKEN || '' }} + SQC_EU_URL: ${{ steps.sqc-eu-auth.outputs.sonar-host-url }} + SQC_EU_TOKEN: ${{ steps.sqc-eu-auth.outputs.sonar-token }} + SQC_US_URL: ${{ steps.sqc-us-auth.outputs.sonar-host-url }} + SQC_US_TOKEN: ${{ steps.sqc-us-auth.outputs.sonar-token }} JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR: ${{ runner.temp }}/jfrog-summary # JFROG_CLI_GITHUB_TOKEN: working-directory: ${{ inputs.working-directory }} diff --git a/build-npm/build.sh b/build-npm/build.sh index ec23b006..cc79fa81 100755 --- a/build-npm/build.sh +++ b/build-npm/build.sh @@ -49,9 +49,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/../shared/common-functions.sh" if [[ "${DEPLOY:=true}" != "false" && "$RUN_SHADOW_SCANS" != "true" ]]; then : "${ARTIFACTORY_DEPLOY_ACCESS_TOKEN:?}" fi -if [[ "${SONAR_PLATFORM:?}" != "none" || "$RUN_SHADOW_SCANS" == "true" ]]; then - : "${NEXT_URL:?}" "${NEXT_TOKEN:?}" "${SQC_US_URL:?}" "${SQC_US_TOKEN:?}" "${SQC_EU_URL:?}" "${SQC_EU_TOKEN:?}" -fi +require_sonar_env : "${SKIP_TESTS:=false}" "${BUILD_NAME:?}" "${PROJECT_VERSION:?}" "${CURRENT_VERSION:?}" "${SQ_SCANNER_VERSION:=4.3.0}" export DEPLOY DEPLOY_PULL_REQUEST SKIP_TESTS SQ_SCANNER_VERSION diff --git a/build-poetry/action.yml b/build-poetry/action.yml index 88e74417..79a976fa 100644 --- a/build-poetry/action.yml +++ b/build-poetry/action.yml @@ -135,13 +135,22 @@ runs: # yamllint disable rule:line-length with: secrets: | - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us token | SQC_US_TOKEN;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud token | SQC_EU_TOKEN;' || '' }} + ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} + ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} # yamllint enable rule:line-length + # The SonarQube Cloud tokens expire after 30 minutes: keep these steps right before the build step. + - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 + id: sqc-eu-auth + if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-eu' + with: + sonar-host-url: https://sonarcloud.io + export-env: 'false' + - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 + id: sqc-us-auth + if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-us' + with: + sonar-host-url: https://sonarqube.us + export-env: 'false' - name: Build, Analyze and deploy id: build shell: bash @@ -164,12 +173,12 @@ runs: POETRY_CACHE_DIR: ${{ github.workspace }}/${{ inputs.poetry-cache-dir }} # Vault secrets - SQC_EU_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_EU_URL || '' }} - SQC_US_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_US_URL || '' }} + SQC_EU_URL: ${{ steps.sqc-eu-auth.outputs.sonar-host-url }} + SQC_US_URL: ${{ steps.sqc-us-auth.outputs.sonar-host-url }} NEXT_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_URL || '' }} NEXT_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_TOKEN || '' }} - SQC_EU_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_EU_TOKEN || '' }} - SQC_US_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_US_TOKEN || '' }} + SQC_EU_TOKEN: ${{ steps.sqc-eu-auth.outputs.sonar-token }} + SQC_US_TOKEN: ${{ steps.sqc-us-auth.outputs.sonar-token }} SONAR_PLATFORM: ${{ inputs.sonar-platform }} RUN_SHADOW_SCANS: ${{ inputs.run-shadow-scans }} JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR: ${{ runner.temp }}/jfrog-summary diff --git a/build-poetry/build.sh b/build-poetry/build.sh index cf565a86..a94b475a 100755 --- a/build-poetry/build.sh +++ b/build-poetry/build.sh @@ -50,9 +50,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/../shared/common-functions.sh" if [[ "${DEPLOY:=true}" != "false" && "$RUN_SHADOW_SCANS" != "true" ]]; then : "${ARTIFACTORY_DEPLOY_ACCESS_TOKEN:?}" fi -if [[ "${SONAR_PLATFORM:?}" != "none" || "$RUN_SHADOW_SCANS" == "true" ]]; then - : "${NEXT_URL:?}" "${NEXT_TOKEN:?}" "${SQC_US_URL:?}" "${SQC_US_TOKEN:?}" "${SQC_EU_URL:?}" "${SQC_EU_TOKEN:?}" -fi +require_sonar_env export DEPLOY DEPLOY_PULL_REQUEST # Unshallow and fetch all commit history for SonarQube analysis and issue assignment diff --git a/build-yarn/action.yml b/build-yarn/action.yml index 2214ed04..5685abe5 100644 --- a/build-yarn/action.yml +++ b/build-yarn/action.yml @@ -153,13 +153,22 @@ runs: # yamllint disable rule:line-length with: secrets: | - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us token | SQC_US_TOKEN;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} - ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud token | SQC_EU_TOKEN;' || '' }} + ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} + ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} # yamllint enable rule:line-length + # The SonarQube Cloud tokens expire after 30 minutes: keep these steps right before the build step. + - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 + id: sqc-eu-auth + if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-eu' + with: + sonar-host-url: https://sonarcloud.io + export-env: 'false' + - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 + id: sqc-us-auth + if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-us' + with: + sonar-host-url: https://sonarqube.us + export-env: 'false' - name: Build, test, analyze and deploy id: build @@ -185,12 +194,12 @@ runs: SKIP_TESTS: ${{ inputs.skip-tests }} # Vault secrets - SQC_EU_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_EU_URL || '' }} - SQC_US_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_US_URL || '' }} + SQC_EU_URL: ${{ steps.sqc-eu-auth.outputs.sonar-host-url }} + SQC_US_URL: ${{ steps.sqc-us-auth.outputs.sonar-host-url }} NEXT_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_URL || '' }} NEXT_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_TOKEN || '' }} - SQC_EU_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_EU_TOKEN || '' }} - SQC_US_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_US_TOKEN || '' }} + SQC_EU_TOKEN: ${{ steps.sqc-eu-auth.outputs.sonar-token }} + SQC_US_TOKEN: ${{ steps.sqc-us-auth.outputs.sonar-token }} SONAR_PLATFORM: ${{ inputs.sonar-platform }} RUN_SHADOW_SCANS: ${{ inputs.run-shadow-scans }} JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR: ${{ runner.temp }}/jfrog-summary diff --git a/build-yarn/build.sh b/build-yarn/build.sh index 28b08b7f..95788fff 100755 --- a/build-yarn/build.sh +++ b/build-yarn/build.sh @@ -54,9 +54,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/../shared/common-functions.sh" if [[ "${DEPLOY:=true}" != "false" && "$RUN_SHADOW_SCANS" != "true" ]]; then : "${ARTIFACTORY_DEPLOY_ACCESS_TOKEN:?}" fi -if [[ "${SONAR_PLATFORM:?}" != "none" || "$RUN_SHADOW_SCANS" == "true" ]]; then - : "${NEXT_URL:?}" "${NEXT_TOKEN:?}" "${SQC_US_URL:?}" "${SQC_US_TOKEN:?}" "${SQC_EU_URL:?}" "${SQC_EU_TOKEN:?}" -fi +require_sonar_env export DEPLOY DEPLOY_PULL_REQUEST SKIP_TESTS SQ_SCANNER_VERSION git_fetch_unshallow() { diff --git a/shared/common-functions.sh b/shared/common-functions.sh index e5694055..721c52b1 100755 --- a/shared/common-functions.sh +++ b/shared/common-functions.sh @@ -45,6 +45,29 @@ set_sonar_platform_vars() { echo "Using Sonar platform: $platform (URL: ${SONAR_HOST_URL#*//}, Region: ${SONAR_REGION:-none})" } +SONAR_PLATFORMS=("next" "sqc-us" "sqc-eu") + +# Fails when the URL or token of a platform that is going to be analyzed is missing +require_sonar_env() { + local platforms=() + if [[ "${RUN_SHADOW_SCANS:-false}" == "true" ]]; then + platforms=("${SONAR_PLATFORMS[@]}") + else + platforms=("${SONAR_PLATFORM:?}") + fi + + local platform + for platform in "${platforms[@]}"; do + case "$platform" in + next) : "${NEXT_URL:?}" "${NEXT_TOKEN:?}" ;; + sqc-us) : "${SQC_US_URL:?}" "${SQC_US_TOKEN:?}" ;; + sqc-eu) : "${SQC_EU_URL:?}" "${SQC_EU_TOKEN:?}" ;; + *) ;; + esac + done + return 0 +} + # ORCHESTRATOR FUNCTION: Multi-platform SonarQube analysis coordinator # # This function: @@ -62,7 +85,7 @@ orchestrate_sonar_platforms() { if [[ "$RUN_SHADOW_SCANS" = "true" ]]; then echo "=== ORCHESTRATOR: Running Sonar analysis on all platforms (shadow scan enabled) ===" - local platforms=("next" "sqc-us" "sqc-eu") + local platforms=("${SONAR_PLATFORMS[@]}") for platform in "${platforms[@]}"; do echo "::group::Sonar analysis on $platform" diff --git a/spec/shared-functions_spec.sh b/spec/shared-functions_spec.sh index ccf9da17..3a5b902b 100755 --- a/spec/shared-functions_spec.sh +++ b/spec/shared-functions_spec.sh @@ -57,6 +57,48 @@ Describe 'shared/common-functions.sh' End End + Describe 'require_sonar_env()' + check_sonar_env() ( + unset NEXT_URL NEXT_TOKEN SQC_US_URL SQC_US_TOKEN SQC_EU_URL SQC_EU_TOKEN + local environment="$1" assignments assignment + IFS=' ' read -ra assignments <<< "$environment" + for assignment in "${assignments[@]}"; do + declare -gx "$assignment" + done + require_sonar_env + return $? + ) + + Describe 'when the variables of every analyzed platform are set' + Parameters + "RUN_SHADOW_SCANS=false SONAR_PLATFORM=none" + "RUN_SHADOW_SCANS=false SONAR_PLATFORM=next NEXT_URL=u NEXT_TOKEN=t" + "RUN_SHADOW_SCANS=false SONAR_PLATFORM=sqc-us SQC_US_URL=u SQC_US_TOKEN=t" + "RUN_SHADOW_SCANS=false SONAR_PLATFORM=sqc-eu SQC_EU_URL=u SQC_EU_TOKEN=t" + "RUN_SHADOW_SCANS=true SONAR_PLATFORM=none NEXT_URL=u NEXT_TOKEN=t SQC_US_URL=u SQC_US_TOKEN=t SQC_EU_URL=u SQC_EU_TOKEN=t" + End + + It "succeeds with: $1" + When call check_sonar_env "$1" + The status should be success + End + End + + Describe 'when a variable of an analyzed platform is missing' + Parameters + "RUN_SHADOW_SCANS=false SONAR_PLATFORM=sqc-eu SQC_EU_URL=u" "SQC_EU_TOKEN" + "RUN_SHADOW_SCANS=false SONAR_PLATFORM=sqc-us SQC_EU_URL=u SQC_EU_TOKEN=t" "SQC_US_URL" + "RUN_SHADOW_SCANS=true SONAR_PLATFORM=sqc-eu SQC_EU_URL=u SQC_EU_TOKEN=t" "NEXT_URL" + End + + It "fails with: $1" + When call check_sonar_env "$1" + The status should not be success + The stderr should include "$2" + End + End + End + Describe 'orchestrate_sonar_platforms()' # Mock sonar_scanner_implementation for orchestrator tests sonar_scanner_implementation() { From 74bbca3672eb11cd3d47fde53734ffc3ef31d8cb Mon Sep 17 00:00:00 2001 From: Aurelien Poscia Date: Fri, 23 Oct 2026 12:38:46 +0200 Subject: [PATCH 2/4] SC-60373 Extract the SonarQube Cloud authentication into a shared action The two sonarqube-auth-action steps were duplicated in the five build-* actions. They now live in shared/sonar-auth, so the SHA pin and the conditions are maintained in one place. Co-Authored-By: Claude Sonnet 5.5 --- build-gradle/action.yml | 25 ++++++++------------- build-maven/action.yml | 25 ++++++++------------- build-npm/action.yml | 25 ++++++++------------- build-poetry/action.yml | 25 ++++++++------------- build-yarn/action.yml | 25 ++++++++------------- shared/sonar-auth/action.yml | 43 ++++++++++++++++++++++++++++++++++++ 6 files changed, 88 insertions(+), 80 deletions(-) create mode 100644 shared/sonar-auth/action.yml diff --git a/build-gradle/action.yml b/build-gradle/action.yml index a9141c1b..d1d477b6 100644 --- a/build-gradle/action.yml +++ b/build-gradle/action.yml @@ -148,19 +148,12 @@ runs: development/kv/data/sign passphrase | PGP_PASSPHRASE; development/kv/data/sign key_id | SIGN_KEY_ID; # yamllint enable rule:line-length - # The SonarQube Cloud tokens expire after 30 minutes: keep these steps right before the build step. - - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 - id: sqc-eu-auth - if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-eu' + # The SonarQube Cloud tokens expire after 30 minutes: keep this step right before the build step. + - uses: ./.actions/shared/sonar-auth + id: sonar-auth with: - sonar-host-url: https://sonarcloud.io - export-env: 'false' - - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 - id: sqc-us-auth - if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-us' - with: - sonar-host-url: https://sonarqube.us - export-env: 'false' + sonar-platform: ${{ inputs.sonar-platform }} + run-shadow-scans: ${{ inputs.run-shadow-scans }} - name: Build, analyze and deploy shell: bash @@ -190,10 +183,10 @@ runs: # yamllint enable rule:line-length NEXT_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_URL || '' }} NEXT_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_TOKEN || '' }} - SQC_EU_URL: ${{ steps.sqc-eu-auth.outputs.sonar-host-url }} - SQC_EU_TOKEN: ${{ steps.sqc-eu-auth.outputs.sonar-token }} - SQC_US_URL: ${{ steps.sqc-us-auth.outputs.sonar-host-url }} - SQC_US_TOKEN: ${{ steps.sqc-us-auth.outputs.sonar-token }} + SQC_EU_URL: ${{ steps.sonar-auth.outputs.sqc-eu-url }} + SQC_EU_TOKEN: ${{ steps.sonar-auth.outputs.sqc-eu-token }} + SQC_US_URL: ${{ steps.sonar-auth.outputs.sqc-us-url }} + SQC_US_TOKEN: ${{ steps.sonar-auth.outputs.sqc-us-token }} ORG_GRADLE_PROJECT_signingKey: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SIGN_KEY || '' }} ORG_GRADLE_PROJECT_signingPassword: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).PGP_PASSPHRASE || '' }} ORG_GRADLE_PROJECT_signingKeyId: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SIGN_KEY_ID || '' }} diff --git a/build-maven/action.yml b/build-maven/action.yml index c0603d44..6d6870eb 100644 --- a/build-maven/action.yml +++ b/build-maven/action.yml @@ -182,19 +182,12 @@ runs: development/kv/data/sign key | SIGN_KEY; development/kv/data/sign passphrase | PGP_PASSPHRASE; # yamllint enable rule:line-length - # The SonarQube Cloud tokens expire after 30 minutes: keep these steps right before the build step. - - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 - id: sqc-eu-auth - if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-eu' + # The SonarQube Cloud tokens expire after 30 minutes: keep this step right before the build step. + - uses: ./.actions/shared/sonar-auth + id: sonar-auth with: - sonar-host-url: https://sonarcloud.io - export-env: 'false' - - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 - id: sqc-us-auth - if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-us' - with: - sonar-host-url: https://sonarqube.us - export-env: 'false' + sonar-platform: ${{ inputs.sonar-platform }} + run-shadow-scans: ${{ inputs.run-shadow-scans }} - name: Build, analyze and deploy shell: bash @@ -224,10 +217,10 @@ runs: # yamllint enable rule:line-length NEXT_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_URL || '' }} NEXT_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_TOKEN || '' }} - SQC_EU_URL: ${{ steps.sqc-eu-auth.outputs.sonar-host-url }} - SQC_EU_TOKEN: ${{ steps.sqc-eu-auth.outputs.sonar-token }} - SQC_US_URL: ${{ steps.sqc-us-auth.outputs.sonar-host-url }} - SQC_US_TOKEN: ${{ steps.sqc-us-auth.outputs.sonar-token }} + SQC_EU_URL: ${{ steps.sonar-auth.outputs.sqc-eu-url }} + SQC_EU_TOKEN: ${{ steps.sonar-auth.outputs.sqc-eu-token }} + SQC_US_URL: ${{ steps.sonar-auth.outputs.sqc-us-url }} + SQC_US_TOKEN: ${{ steps.sonar-auth.outputs.sqc-us-token }} SIGN_KEY: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SIGN_KEY || '' }} PGP_PASSPHRASE: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).PGP_PASSPHRASE || '' }} working-directory: ${{ inputs.working-directory }} diff --git a/build-npm/action.yml b/build-npm/action.yml index 1cf28a2c..bbf714ea 100644 --- a/build-npm/action.yml +++ b/build-npm/action.yml @@ -150,19 +150,12 @@ runs: ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} # yamllint enable rule:line-length - # The SonarQube Cloud tokens expire after 30 minutes: keep these steps right before the build step. - - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 - id: sqc-eu-auth - if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-eu' + # The SonarQube Cloud tokens expire after 30 minutes: keep this step right before the build step. + - uses: ./.actions/shared/sonar-auth + id: sonar-auth with: - sonar-host-url: https://sonarcloud.io - export-env: 'false' - - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 - id: sqc-us-auth - if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-us' - with: - sonar-host-url: https://sonarqube.us - export-env: 'false' + sonar-platform: ${{ inputs.sonar-platform }} + run-shadow-scans: ${{ inputs.run-shadow-scans }} - name: Build, test, analyze and deploy id: build shell: bash @@ -187,10 +180,10 @@ runs: SONAR_PLATFORM: ${{ inputs.sonar-platform }} NEXT_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_URL || '' }} NEXT_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_TOKEN || '' }} - SQC_EU_URL: ${{ steps.sqc-eu-auth.outputs.sonar-host-url }} - SQC_EU_TOKEN: ${{ steps.sqc-eu-auth.outputs.sonar-token }} - SQC_US_URL: ${{ steps.sqc-us-auth.outputs.sonar-host-url }} - SQC_US_TOKEN: ${{ steps.sqc-us-auth.outputs.sonar-token }} + SQC_EU_URL: ${{ steps.sonar-auth.outputs.sqc-eu-url }} + SQC_EU_TOKEN: ${{ steps.sonar-auth.outputs.sqc-eu-token }} + SQC_US_URL: ${{ steps.sonar-auth.outputs.sqc-us-url }} + SQC_US_TOKEN: ${{ steps.sonar-auth.outputs.sqc-us-token }} JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR: ${{ runner.temp }}/jfrog-summary # JFROG_CLI_GITHUB_TOKEN: working-directory: ${{ inputs.working-directory }} diff --git a/build-poetry/action.yml b/build-poetry/action.yml index 79a976fa..0b21c7f4 100644 --- a/build-poetry/action.yml +++ b/build-poetry/action.yml @@ -138,19 +138,12 @@ runs: ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} # yamllint enable rule:line-length - # The SonarQube Cloud tokens expire after 30 minutes: keep these steps right before the build step. - - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 - id: sqc-eu-auth - if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-eu' + # The SonarQube Cloud tokens expire after 30 minutes: keep this step right before the build step. + - uses: ./.actions/shared/sonar-auth + id: sonar-auth with: - sonar-host-url: https://sonarcloud.io - export-env: 'false' - - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 - id: sqc-us-auth - if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-us' - with: - sonar-host-url: https://sonarqube.us - export-env: 'false' + sonar-platform: ${{ inputs.sonar-platform }} + run-shadow-scans: ${{ inputs.run-shadow-scans }} - name: Build, Analyze and deploy id: build shell: bash @@ -173,12 +166,12 @@ runs: POETRY_CACHE_DIR: ${{ github.workspace }}/${{ inputs.poetry-cache-dir }} # Vault secrets - SQC_EU_URL: ${{ steps.sqc-eu-auth.outputs.sonar-host-url }} - SQC_US_URL: ${{ steps.sqc-us-auth.outputs.sonar-host-url }} + SQC_EU_URL: ${{ steps.sonar-auth.outputs.sqc-eu-url }} + SQC_US_URL: ${{ steps.sonar-auth.outputs.sqc-us-url }} NEXT_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_URL || '' }} NEXT_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_TOKEN || '' }} - SQC_EU_TOKEN: ${{ steps.sqc-eu-auth.outputs.sonar-token }} - SQC_US_TOKEN: ${{ steps.sqc-us-auth.outputs.sonar-token }} + SQC_EU_TOKEN: ${{ steps.sonar-auth.outputs.sqc-eu-token }} + SQC_US_TOKEN: ${{ steps.sonar-auth.outputs.sqc-us-token }} SONAR_PLATFORM: ${{ inputs.sonar-platform }} RUN_SHADOW_SCANS: ${{ inputs.run-shadow-scans }} JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR: ${{ runner.temp }}/jfrog-summary diff --git a/build-yarn/action.yml b/build-yarn/action.yml index 5685abe5..a149f3e2 100644 --- a/build-yarn/action.yml +++ b/build-yarn/action.yml @@ -156,19 +156,12 @@ runs: ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} # yamllint enable rule:line-length - # The SonarQube Cloud tokens expire after 30 minutes: keep these steps right before the build step. - - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 - id: sqc-eu-auth - if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-eu' + # The SonarQube Cloud tokens expire after 30 minutes: keep this step right before the build step. + - uses: ./.actions/shared/sonar-auth + id: sonar-auth with: - sonar-host-url: https://sonarcloud.io - export-env: 'false' - - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 - id: sqc-us-auth - if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-us' - with: - sonar-host-url: https://sonarqube.us - export-env: 'false' + sonar-platform: ${{ inputs.sonar-platform }} + run-shadow-scans: ${{ inputs.run-shadow-scans }} - name: Build, test, analyze and deploy id: build @@ -194,12 +187,12 @@ runs: SKIP_TESTS: ${{ inputs.skip-tests }} # Vault secrets - SQC_EU_URL: ${{ steps.sqc-eu-auth.outputs.sonar-host-url }} - SQC_US_URL: ${{ steps.sqc-us-auth.outputs.sonar-host-url }} + SQC_EU_URL: ${{ steps.sonar-auth.outputs.sqc-eu-url }} + SQC_US_URL: ${{ steps.sonar-auth.outputs.sqc-us-url }} NEXT_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_URL || '' }} NEXT_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_TOKEN || '' }} - SQC_EU_TOKEN: ${{ steps.sqc-eu-auth.outputs.sonar-token }} - SQC_US_TOKEN: ${{ steps.sqc-us-auth.outputs.sonar-token }} + SQC_EU_TOKEN: ${{ steps.sonar-auth.outputs.sqc-eu-token }} + SQC_US_TOKEN: ${{ steps.sonar-auth.outputs.sqc-us-token }} SONAR_PLATFORM: ${{ inputs.sonar-platform }} RUN_SHADOW_SCANS: ${{ inputs.run-shadow-scans }} JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR: ${{ runner.temp }}/jfrog-summary diff --git a/shared/sonar-auth/action.yml b/shared/sonar-auth/action.yml new file mode 100644 index 00000000..ff200c28 --- /dev/null +++ b/shared/sonar-auth/action.yml @@ -0,0 +1,43 @@ +--- +name: Authenticate to SonarQube Cloud +description: >- + Internal action used by the build-* actions. Requests short-lived SonarQube Cloud tokens (EU and US) + with the GitHub OIDC token of the job. Requires the id-token write permission. + +inputs: + sonar-platform: + description: Sonar platform analyzed by the build (next, sqc-us, sqc-eu or none) + required: true + run-shadow-scans: + description: Whether the build analyzes all platforms + required: true + +outputs: + sqc-eu-url: + description: URL of SonarQube Cloud EU + value: ${{ steps.sqc-eu-auth.outputs.sonar-host-url }} + sqc-eu-token: + description: Token to analyze the project on SonarQube Cloud EU + value: ${{ steps.sqc-eu-auth.outputs.sonar-token }} + sqc-us-url: + description: URL of SonarQube Cloud US + value: ${{ steps.sqc-us-auth.outputs.sonar-host-url }} + sqc-us-token: + description: Token to analyze the project on SonarQube Cloud US + value: ${{ steps.sqc-us-auth.outputs.sonar-token }} + +runs: + using: composite + steps: + - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 + id: sqc-eu-auth + if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-eu' + with: + sonar-host-url: https://sonarcloud.io + export-env: 'false' + - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 + id: sqc-us-auth + if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-us' + with: + sonar-host-url: https://sonarqube.us + export-env: 'false' From 3f6069a2fb8546962f03b7040059d1b70589f28e Mon Sep 17 00:00:00 2001 From: Aurelien Poscia Date: Fri, 23 Oct 2026 12:51:00 +0200 Subject: [PATCH 3/4] SC-60373 Address review comments Use the shared platform list in the poetry shadow scan loop, and state in the README that check-sca still reads the SonarQube Cloud Vault secrets. Co-Authored-By: Claude Sonnet 5.5 --- README.md | 3 ++- build-poetry/build.sh | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 44ea7ad0..21256daa 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,8 @@ Requirements for each SonarQube Cloud platform that is analyzed: The token exchange fails the job when the repository is not bound, or when the job has no OIDC token. This is the case for pull requests from forks, as GitHub does not grant `id-token: write` to them. -The secrets `development/kv/data/sonarcloud` and `development/kv/data/sonarqube-us` are not read anymore. +The `build-*` actions do not read the secrets `development/kv/data/sonarcloud` and `development/kv/data/sonarqube-us` anymore. +They are still used by [`check-sca`](#check-sca), so they must not be revoked yet. --- diff --git a/build-poetry/build.sh b/build-poetry/build.sh index a94b475a..4f29a882 100755 --- a/build-poetry/build.sh +++ b/build-poetry/build.sh @@ -128,7 +128,7 @@ run_sonar_analysis() { echo "run_sonar_analysis()" if [[ "${RUN_SHADOW_SCANS}" = "true" ]]; then echo "=== Running Sonar analysis on all platforms (shadow scan enabled) ===" - local platforms=("next" "sqc-us" "sqc-eu") + local platforms=("${SONAR_PLATFORMS[@]}") for platform in "${platforms[@]}"; do echo "::group::Sonar analysis on $platform" From 0991c9f16a76c68ca7717012ac659796618ddacf Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Mon, 26 Oct 2026 12:20:04 +0100 Subject: [PATCH 4/4] SC-60373 Use the sonar-region input of sonarqube-auth-action sonarqube-auth-action replaced the sonar-host-url input and output with sonar-region. The shared sonar-auth action now only provides the tokens, and the build actions read the SonarQube Cloud URLs from Vault again. Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 4 ++-- build-gradle/action.yml | 6 ++++-- build-maven/action.yml | 6 ++++-- build-npm/action.yml | 6 ++++-- build-poetry/action.yml | 6 ++++-- build-yarn/action.yml | 6 ++++-- shared/sonar-auth/action.yml | 13 +++---------- 7 files changed, 25 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 21256daa..57721a9a 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,8 @@ The selected URL also determines the matching Vault server and reader-token engi The `build-*` actions authenticate to SonarQube Cloud (`sonar-platform: sqc-eu` or `sqc-us`, and the matching platforms of `run-shadow-scans`) with the GitHub OIDC token of the job, through -[`sonarqube-auth-action`](https://github.com/SonarSource/sonarqube-auth-action), instead of with Vault secrets. -The `next` platform still reads its URL and token from Vault. +[`sonarqube-auth-action`](https://github.com/SonarSource/sonarqube-auth-action), instead of with tokens stored in Vault. +The SonarQube Cloud URLs, and the URL and token of the `next` platform, are still read from Vault. Requirements for each SonarQube Cloud platform that is analyzed: diff --git a/build-gradle/action.yml b/build-gradle/action.yml index d1d477b6..a5c76836 100644 --- a/build-gradle/action.yml +++ b/build-gradle/action.yml @@ -144,6 +144,8 @@ runs: secrets: | ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} development/kv/data/sign key | SIGN_KEY; development/kv/data/sign passphrase | PGP_PASSPHRASE; development/kv/data/sign key_id | SIGN_KEY_ID; @@ -183,9 +185,9 @@ runs: # yamllint enable rule:line-length NEXT_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_URL || '' }} NEXT_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_TOKEN || '' }} - SQC_EU_URL: ${{ steps.sonar-auth.outputs.sqc-eu-url }} + SQC_EU_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_EU_URL || '' }} SQC_EU_TOKEN: ${{ steps.sonar-auth.outputs.sqc-eu-token }} - SQC_US_URL: ${{ steps.sonar-auth.outputs.sqc-us-url }} + SQC_US_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_US_URL || '' }} SQC_US_TOKEN: ${{ steps.sonar-auth.outputs.sqc-us-token }} ORG_GRADLE_PROJECT_signingKey: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SIGN_KEY || '' }} ORG_GRADLE_PROJECT_signingPassword: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).PGP_PASSPHRASE || '' }} diff --git a/build-maven/action.yml b/build-maven/action.yml index 6d6870eb..abea3e82 100644 --- a/build-maven/action.yml +++ b/build-maven/action.yml @@ -179,6 +179,8 @@ runs: secrets: | ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} development/kv/data/sign key | SIGN_KEY; development/kv/data/sign passphrase | PGP_PASSPHRASE; # yamllint enable rule:line-length @@ -217,9 +219,9 @@ runs: # yamllint enable rule:line-length NEXT_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_URL || '' }} NEXT_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_TOKEN || '' }} - SQC_EU_URL: ${{ steps.sonar-auth.outputs.sqc-eu-url }} + SQC_EU_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_EU_URL || '' }} SQC_EU_TOKEN: ${{ steps.sonar-auth.outputs.sqc-eu-token }} - SQC_US_URL: ${{ steps.sonar-auth.outputs.sqc-us-url }} + SQC_US_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_US_URL || '' }} SQC_US_TOKEN: ${{ steps.sonar-auth.outputs.sqc-us-token }} SIGN_KEY: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SIGN_KEY || '' }} PGP_PASSPHRASE: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).PGP_PASSPHRASE || '' }} diff --git a/build-npm/action.yml b/build-npm/action.yml index bbf714ea..cc8dd225 100644 --- a/build-npm/action.yml +++ b/build-npm/action.yml @@ -149,6 +149,8 @@ runs: secrets: | ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} # yamllint enable rule:line-length # The SonarQube Cloud tokens expire after 30 minutes: keep this step right before the build step. - uses: ./.actions/shared/sonar-auth @@ -180,9 +182,9 @@ runs: SONAR_PLATFORM: ${{ inputs.sonar-platform }} NEXT_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_URL || '' }} NEXT_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_TOKEN || '' }} - SQC_EU_URL: ${{ steps.sonar-auth.outputs.sqc-eu-url }} + SQC_EU_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_EU_URL || '' }} SQC_EU_TOKEN: ${{ steps.sonar-auth.outputs.sqc-eu-token }} - SQC_US_URL: ${{ steps.sonar-auth.outputs.sqc-us-url }} + SQC_US_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_US_URL || '' }} SQC_US_TOKEN: ${{ steps.sonar-auth.outputs.sqc-us-token }} JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR: ${{ runner.temp }}/jfrog-summary # JFROG_CLI_GITHUB_TOKEN: diff --git a/build-poetry/action.yml b/build-poetry/action.yml index 0b21c7f4..0ca95229 100644 --- a/build-poetry/action.yml +++ b/build-poetry/action.yml @@ -137,6 +137,8 @@ runs: secrets: | ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} # yamllint enable rule:line-length # The SonarQube Cloud tokens expire after 30 minutes: keep this step right before the build step. - uses: ./.actions/shared/sonar-auth @@ -166,8 +168,8 @@ runs: POETRY_CACHE_DIR: ${{ github.workspace }}/${{ inputs.poetry-cache-dir }} # Vault secrets - SQC_EU_URL: ${{ steps.sonar-auth.outputs.sqc-eu-url }} - SQC_US_URL: ${{ steps.sonar-auth.outputs.sqc-us-url }} + SQC_EU_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_EU_URL || '' }} + SQC_US_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_US_URL || '' }} NEXT_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_URL || '' }} NEXT_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_TOKEN || '' }} SQC_EU_TOKEN: ${{ steps.sonar-auth.outputs.sqc-eu-token }} diff --git a/build-yarn/action.yml b/build-yarn/action.yml index a149f3e2..98712864 100644 --- a/build-yarn/action.yml +++ b/build-yarn/action.yml @@ -155,6 +155,8 @@ runs: secrets: | ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next url | NEXT_URL;' || '' }} ${{ (inputs.sonar-platform == 'next' || inputs.run-shadow-scans == 'true') && 'development/kv/data/next token | NEXT_TOKEN;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarqube-us url | SQC_US_URL;' || '' }} + ${{ (inputs.sonar-platform != 'none' || inputs.run-shadow-scans == 'true') && 'development/kv/data/sonarcloud url | SQC_EU_URL;' || '' }} # yamllint enable rule:line-length # The SonarQube Cloud tokens expire after 30 minutes: keep this step right before the build step. - uses: ./.actions/shared/sonar-auth @@ -187,8 +189,8 @@ runs: SKIP_TESTS: ${{ inputs.skip-tests }} # Vault secrets - SQC_EU_URL: ${{ steps.sonar-auth.outputs.sqc-eu-url }} - SQC_US_URL: ${{ steps.sonar-auth.outputs.sqc-us-url }} + SQC_EU_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_EU_URL || '' }} + SQC_US_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).SQC_US_URL || '' }} NEXT_URL: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_URL || '' }} NEXT_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).NEXT_TOKEN || '' }} SQC_EU_TOKEN: ${{ steps.sonar-auth.outputs.sqc-eu-token }} diff --git a/shared/sonar-auth/action.yml b/shared/sonar-auth/action.yml index ff200c28..21d6289c 100644 --- a/shared/sonar-auth/action.yml +++ b/shared/sonar-auth/action.yml @@ -13,15 +13,9 @@ inputs: required: true outputs: - sqc-eu-url: - description: URL of SonarQube Cloud EU - value: ${{ steps.sqc-eu-auth.outputs.sonar-host-url }} sqc-eu-token: description: Token to analyze the project on SonarQube Cloud EU value: ${{ steps.sqc-eu-auth.outputs.sonar-token }} - sqc-us-url: - description: URL of SonarQube Cloud US - value: ${{ steps.sqc-us-auth.outputs.sonar-host-url }} sqc-us-token: description: Token to analyze the project on SonarQube Cloud US value: ${{ steps.sqc-us-auth.outputs.sonar-token }} @@ -29,15 +23,14 @@ outputs: runs: using: composite steps: - - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 + - uses: SonarSource/sonarqube-auth-action@ae14f168e34a5309326fa31d3df09245c2c8a611 # master id: sqc-eu-auth if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-eu' with: - sonar-host-url: https://sonarcloud.io export-env: 'false' - - uses: SonarSource/sonarqube-auth-action@0c409727662aa45022382faba75676ae449788ad # v1 + - uses: SonarSource/sonarqube-auth-action@ae14f168e34a5309326fa31d3df09245c2c8a611 # master id: sqc-us-auth if: inputs.run-shadow-scans == 'true' || inputs.sonar-platform == 'sqc-us' with: - sonar-host-url: https://sonarqube.us + sonar-region: us export-env: 'false'