From c8fbcdbcb8628f59c2939986fcce12f77511bb80 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Thu, 9 Jul 2026 18:05:25 -0400 Subject: [PATCH 01/14] Harden CI: Artifactory OIDC, fork-aware workflows, tightened permissions - Add Artifactory OIDC composite action (writes ~/.m2/settings.xml mirroring virtual-maven-thirdparty) - Add ci.yml: fork-aware, Java 11/17/21 matrix, spotless/animal-sniffer, SHA-pinned actions - Add deploy.yml: tag-triggered, environment: maven-central gate, GPG signing - Update e2e-tests.yml: remove E2E_TESTS_TOKEN, fork-aware --- .github/actions/artifactory-oidc/action.yml | 71 +++++++++++++++ .github/workflows/ci.yml | 98 +++++++++++++++++++++ .github/workflows/deploy.yml | 74 ++++++++++++++++ .github/workflows/e2e-tests.yml | 50 ++++++----- 4 files changed, 271 insertions(+), 22 deletions(-) create mode 100644 .github/actions/artifactory-oidc/action.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/deploy.yml diff --git a/.github/actions/artifactory-oidc/action.yml b/.github/actions/artifactory-oidc/action.yml new file mode 100644 index 00000000..c41de0e1 --- /dev/null +++ b/.github/actions/artifactory-oidc/action.yml @@ -0,0 +1,71 @@ +name: 'Artifactory OIDC Authentication' +description: 'Exchange GitHub OIDC token for Artifactory credentials and configure Maven settings.xml' + +inputs: + artifactory_url: + description: 'Artifactory base URL (e.g. https://segment.jfrog.io)' + required: true + +outputs: + token: + description: 'Artifactory access token' + value: ${{ steps.exchange.outputs.token }} + +runs: + using: 'composite' + steps: + - name: Get OIDC token + id: oidc + shell: bash + run: | + OIDC_TOKEN=$(curl -s -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ + "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=jfrog-github" | jq -r '.value') + echo "::add-mask::$OIDC_TOKEN" + echo "oidc_token=$OIDC_TOKEN" >> "$GITHUB_OUTPUT" + + - name: Exchange OIDC token for Artifactory token + id: exchange + shell: bash + env: + ARTIFACTORY_URL: ${{ inputs.artifactory_url }} + run: | + HOST=$(echo "$ARTIFACTORY_URL" | sed 's|https://||') + ART_TOKEN=$(curl -s -X POST \ + "$ARTIFACTORY_URL/access/api/v1/oidc/token" \ + -H "Content-Type: application/json" \ + -d "{\"grant_type\": \"urn:ietf:params:oauth:grant-type:token-exchange\", \"subject_token\": \"${{ steps.oidc.outputs.oidc_token }}\", \"subject_token_type\": \"urn:ietf:params:oauth:token-type:id_token\", \"provider_name\": \"github\"}" \ + | jq -r '.access_token') + echo "::add-mask::$ART_TOKEN" + echo "token=$ART_TOKEN" >> "$GITHUB_OUTPUT" + echo "host=$HOST" >> "$GITHUB_OUTPUT" + + - name: Write Maven settings.xml + shell: bash + env: + ART_TOKEN: ${{ steps.exchange.outputs.token }} + ART_HOST: ${{ steps.exchange.outputs.host }} + ARTIFACTORY_URL: ${{ inputs.artifactory_url }} + run: | + mkdir -p ~/.m2 + cat > ~/.m2/settings.xml << 'EOF' + + + + artifactory + central + __ARTIFACTORY_URL__/artifactory/virtual-maven-thirdparty + + + + + artifactory + _ + __ART_TOKEN__ + + + + EOF + sed -i "s|__ARTIFACTORY_URL__|${ARTIFACTORY_URL}|g" ~/.m2/settings.xml + sed -i "s|__ART_TOKEN__|${ART_TOKEN}|g" ~/.m2/settings.xml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..e484483a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,98 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +permissions: + id-token: write + contents: read + +env: + ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }} + +jobs: + lint: + name: Lint (spotless + animal-sniffer) + runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }} + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Set up JDK 11 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4 + with: + distribution: 'temurin' + java-version: '11' + cache: maven + + - name: Artifactory OIDC + if: ${{ !github.event.pull_request.head.repo.fork }} + uses: ./.github/actions/artifactory-oidc + with: + artifactory_url: ${{ vars.ARTIFACTORY_URL }} + + - name: Spotless check + run: mvn -B spotless:check + + - name: Animal Sniffer check + run: mvn -B animal-sniffer:check + + test: + name: Test (Java ${{ matrix.java-version }}) + runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }} + strategy: + fail-fast: false + matrix: + java-version: ['11', '17', '21'] + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Set up JDK ${{ matrix.java-version }} + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4 + with: + distribution: 'temurin' + java-version: ${{ matrix.java-version }} + cache: maven + + - name: Artifactory OIDC + if: ${{ !github.event.pull_request.head.repo.fork }} + uses: ./.github/actions/artifactory-oidc + with: + artifactory_url: ${{ vars.ARTIFACTORY_URL }} + + - name: Run tests + run: mvn -B test + + build: + name: Build verification + runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }} + needs: [lint, test] + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Set up JDK 11 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4 + with: + distribution: 'temurin' + java-version: '11' + cache: maven + + - name: Artifactory OIDC + if: ${{ !github.event.pull_request.head.repo.fork }} + uses: ./.github/actions/artifactory-oidc + with: + artifactory_url: ${{ vars.ARTIFACTORY_URL }} + + - name: Build + run: mvn -B package -DskipTests + + - name: Verify + run: mvn -B verify -DskipTests diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..2632436c --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,74 @@ +name: Deploy to Maven Central + +on: + release: + types: [published] + +permissions: + id-token: write + contents: read + +env: + ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }} + +jobs: + test: + name: Pre-deploy tests + runs-on: ubuntu-x64 + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Set up JDK 11 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4 + with: + distribution: 'temurin' + java-version: '11' + cache: maven + + - name: Artifactory OIDC + uses: ./.github/actions/artifactory-oidc + with: + artifactory_url: ${{ vars.ARTIFACTORY_URL }} + + - name: Run tests + run: mvn -B test + + deploy: + name: Deploy to Maven Central + runs-on: ubuntu-x64 + needs: [test] + environment: maven-central + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: Set up JDK 11 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4 + with: + distribution: 'temurin' + java-version: '11' + cache: maven + server-id: central + server-username: CI_DEPLOY_USERNAME + server-password: CI_DEPLOY_PASSWORD + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: GPG_PASSPHRASE + + - name: Artifactory OIDC + uses: ./.github/actions/artifactory-oidc + with: + artifactory_url: ${{ vars.ARTIFACTORY_URL }} + + - name: Deploy to Maven Central + env: + CI_DEPLOY_USERNAME: ${{ secrets.CI_DEPLOY_USERNAME }} + CI_DEPLOY_PASSWORD: ${{ secrets.CI_DEPLOY_PASSWORD }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + run: | + mvn -B deploy \ + -DskipTests \ + -Dgpg.passphrase="$GPG_PASSPHRASE" \ + -P release diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index c59ec2cb..dd706a10 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -1,18 +1,12 @@ -# E2E Tests for analytics-java -# Copy this file to: analytics-java/.github/workflows/e2e-tests.yml -# -# This workflow: -# 1. Checks out the SDK and sdk-e2e-tests repos -# 2. Builds the SDK and e2e-cli -# 3. Runs the e2e test suite - name: E2E Tests on: push: - branches: [main, master] + branches: [master] pull_request: - branches: [main, master] + branches: [master] + schedule: + - cron: '0 6 * * 1-5' workflow_dispatch: inputs: e2e_tests_ref: @@ -20,47 +14,59 @@ on: required: false default: 'main' +permissions: + id-token: write + contents: read + +env: + ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }} + jobs: e2e-tests: - # Skip on fork PRs where repo secrets aren't available - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} - runs-on: ubuntu-latest + name: E2E Test Suite + if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }} + runs-on: ubuntu-x64 steps: - name: Checkout SDK - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: path: sdk - name: Checkout sdk-e2e-tests - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: repository: segmentio/sdk-e2e-tests ref: ${{ inputs.e2e_tests_ref || 'main' }} - token: ${{ secrets.E2E_TESTS_TOKEN }} path: sdk-e2e-tests - - name: Setup Java - uses: actions/setup-java@v4 + - name: Set up JDK 11 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4 with: distribution: 'temurin' java-version: '11' + cache: maven + + - name: Artifactory OIDC + uses: ./sdk/.github/actions/artifactory-oidc + with: + artifactory_url: ${{ vars.ARTIFACTORY_URL }} - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4 with: node-version: '20' - name: Build Java SDK and e2e-cli working-directory: sdk - run: mvn package -pl e2e-cli -am -DskipTests + run: mvn -B package -pl e2e-cli -am -DskipTests - name: Find e2e-cli jar id: find-jar working-directory: sdk run: | JAR_PATH=$(find e2e-cli/target -name "e2e-cli-*-jar-with-dependencies.jar" | head -1) - echo "jar_path=$JAR_PATH" >> $GITHUB_OUTPUT + echo "jar_path=$JAR_PATH" >> "$GITHUB_OUTPUT" - name: Run E2E tests working-directory: sdk-e2e-tests @@ -71,7 +77,7 @@ jobs: - name: Upload test results if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: e2e-test-results path: sdk-e2e-tests/test-results/ From ed0e41153995d13ce498410875ae86d2ee2294a4 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Mon, 13 Jul 2026 11:32:20 -0400 Subject: [PATCH 02/14] fix(ci): correct Artifactory OIDC provider name and exchange pattern Rewrite composite action to use single-script pattern matching analytics-python: audience=${ARTIFACTORY_URL}, provider_name= github-actions-segmentio, and add JWT claim logging for debugging. --- .github/actions/artifactory-oidc/action.yml | 103 ++++++++++++-------- 1 file changed, 60 insertions(+), 43 deletions(-) diff --git a/.github/actions/artifactory-oidc/action.yml b/.github/actions/artifactory-oidc/action.yml index c41de0e1..6c204e88 100644 --- a/.github/actions/artifactory-oidc/action.yml +++ b/.github/actions/artifactory-oidc/action.yml @@ -1,53 +1,71 @@ -name: 'Artifactory OIDC Authentication' -description: 'Exchange GitHub OIDC token for Artifactory credentials and configure Maven settings.xml' +name: "Artifactory OIDC Auth" +description: "Exchange GitHub OIDC token for Artifactory access token and configure Maven" inputs: - artifactory_url: - description: 'Artifactory base URL (e.g. https://segment.jfrog.io)' - required: true - -outputs: - token: - description: 'Artifactory access token' - value: ${{ steps.exchange.outputs.token }} + artifactory-url: + description: "JFrog platform base URL. Falls back to ARTIFACTORY_URL env var." + required: false + default: "" + oidc-provider-name: + description: "OIDC provider name configured in Artifactory" + required: false + default: "github-actions-segmentio" + maven-repo: + description: "Artifactory virtual Maven repository name" + required: false + default: "virtual-maven-thirdparty" runs: - using: 'composite' + using: "composite" steps: - - name: Get OIDC token - id: oidc - shell: bash - run: | - OIDC_TOKEN=$(curl -s -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ - "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=jfrog-github" | jq -r '.value') - echo "::add-mask::$OIDC_TOKEN" - echo "oidc_token=$OIDC_TOKEN" >> "$GITHUB_OUTPUT" - - - name: Exchange OIDC token for Artifactory token - id: exchange + - name: Exchange GitHub OIDC token for Artifactory token shell: bash env: - ARTIFACTORY_URL: ${{ inputs.artifactory_url }} + INPUT_ARTIFACTORY_URL: ${{ inputs.artifactory-url }} + OIDC_PROVIDER_NAME: ${{ inputs.oidc-provider-name }} + MAVEN_REPO: ${{ inputs.maven-repo }} run: | - HOST=$(echo "$ARTIFACTORY_URL" | sed 's|https://||') - ART_TOKEN=$(curl -s -X POST \ - "$ARTIFACTORY_URL/access/api/v1/oidc/token" \ - -H "Content-Type: application/json" \ - -d "{\"grant_type\": \"urn:ietf:params:oauth:grant-type:token-exchange\", \"subject_token\": \"${{ steps.oidc.outputs.oidc_token }}\", \"subject_token_type\": \"urn:ietf:params:oauth:token-type:id_token\", \"provider_name\": \"github\"}" \ - | jq -r '.access_token') + set -euo pipefail + ARTIFACTORY_URL="${INPUT_ARTIFACTORY_URL:-${ARTIFACTORY_URL:-}}" + if [ -z "${ARTIFACTORY_URL}" ]; then + echo "::error::ARTIFACTORY_URL is not set (pass as input or set as env var)"; exit 1 + fi + + OIDC_JWT=$(curl -sS \ + "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=${ARTIFACTORY_URL}" \ + -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" | jq -r '.value') + + if [ -z "$OIDC_JWT" ] || [ "$OIDC_JWT" = "null" ]; then + echo "::error::Failed to obtain GitHub OIDC token"; exit 1 + fi + + decode_seg() { local s="${1}"; local m=$(( ${#s} % 4 )); [ $m -ne 0 ] && s="${s}$(printf '=%.0s' $(seq 1 $((4-m))))"; echo "$s" | tr '_-' '/+' | base64 -d 2>/dev/null; } + PAYLOAD=$(decode_seg "$(echo "$OIDC_JWT" | cut -d. -f2)") + echo "OIDC token claims:" + echo " sub = $(echo "$PAYLOAD" | jq -r '.sub')" + echo " aud = $(echo "$PAYLOAD" | jq -r '.aud')" + echo " iss = $(echo "$PAYLOAD" | jq -r '.iss')" + + RESP=$(curl -sS "${ARTIFACTORY_URL}/access/api/v1/oidc/token" \ + -H 'Content-Type: application/json' \ + -d "{\"grant_type\":\"urn:ietf:params:oauth:grant-type:token-exchange\", + \"subject_token_type\":\"urn:ietf:params:oauth:token-type:id_token\", + \"subject_token\":\"${OIDC_JWT}\", + \"provider_name\":\"${OIDC_PROVIDER_NAME}\"}") + + ART_TOKEN=$(echo "$RESP" | jq -r '.access_token // empty') + + if [ -z "$ART_TOKEN" ]; then + echo "::error::OIDC token exchange failed." + echo "$RESP" | jq 'if .access_token then .access_token="" else . end' 2>/dev/null || echo "$RESP" + exit 1 + fi echo "::add-mask::$ART_TOKEN" - echo "token=$ART_TOKEN" >> "$GITHUB_OUTPUT" - echo "host=$HOST" >> "$GITHUB_OUTPUT" - - name: Write Maven settings.xml - shell: bash - env: - ART_TOKEN: ${{ steps.exchange.outputs.token }} - ART_HOST: ${{ steps.exchange.outputs.host }} - ARTIFACTORY_URL: ${{ inputs.artifactory_url }} - run: | + HOST=$(echo "${ARTIFACTORY_URL}" | sed -E 's#^https?://##') + mkdir -p ~/.m2 - cat > ~/.m2/settings.xml << 'EOF' + cat > ~/.m2/settings.xml << EOF @@ -55,17 +73,16 @@ runs: artifactory central - __ARTIFACTORY_URL__/artifactory/virtual-maven-thirdparty + https://${HOST}/artifactory/${MAVEN_REPO} artifactory _ - __ART_TOKEN__ + ${ART_TOKEN} EOF - sed -i "s|__ARTIFACTORY_URL__|${ARTIFACTORY_URL}|g" ~/.m2/settings.xml - sed -i "s|__ART_TOKEN__|${ART_TOKEN}|g" ~/.m2/settings.xml + echo "Configured Maven to resolve through Artifactory (${MAVEN_REPO})" From 373795d6343a70a18ae771d33e03f212c51b375e Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Mon, 13 Jul 2026 16:10:03 -0400 Subject: [PATCH 03/14] fix(ci): move central-publishing-maven-plugin into release profile The plugin is a build extension resolved on every mvn invocation, causing CI test jobs to fail when Artifactory doesn't have the Sonatype Central plugin. Gate it behind -P release so it only loads during actual deploys. --- pom.xml | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 21a869da..f517308a 100644 --- a/pom.xml +++ b/pom.xml @@ -247,15 +247,6 @@ - - org.sonatype.central - central-publishing-maven-plugin - 0.9.0 - true - - central - - org.apache.maven.plugins maven-deploy-plugin @@ -341,4 +332,24 @@ + + + + + release + + + + org.sonatype.central + central-publishing-maven-plugin + 0.9.0 + true + + central + + + + + + From fe56a63aba050bb960c3f6cb19d38ba7ab484858 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Mon, 13 Jul 2026 16:27:01 -0400 Subject: [PATCH 04/14] fix(ci): bump spotless-maven-plugin to 2.44.0 (available in Artifactory) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f517308a..411cd23e 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ UTF-8 8 1.9.0 - 2.27.2 + 2.44.0 2.11.0 1.10.1 From d7edd3ad69269b94a999f004ae200d9bfb599dfb Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Mon, 13 Jul 2026 17:09:13 -0400 Subject: [PATCH 05/14] fix(ci): route Maven plugin resolution through Artifactory Switch default repo to virtual-maven-twilio (includes remote-maven-maven-central-cache, which virtual-maven-thirdparty doesn't). Add pluginRepositories in settings.xml so Maven resolves build plugins (spotless, central-publishing, etc.) through Artifactory rather than falling back to Maven Central directly. Also broaden mirrorOf from 'central' to '*' to catch all repos. --- .github/actions/artifactory-oidc/action.yml | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/actions/artifactory-oidc/action.yml b/.github/actions/artifactory-oidc/action.yml index 6c204e88..0787e500 100644 --- a/.github/actions/artifactory-oidc/action.yml +++ b/.github/actions/artifactory-oidc/action.yml @@ -13,7 +13,7 @@ inputs: maven-repo: description: "Artifactory virtual Maven repository name" required: false - default: "virtual-maven-thirdparty" + default: "virtual-maven-twilio" runs: using: "composite" @@ -72,7 +72,7 @@ runs: artifactory - central + * https://${HOST}/artifactory/${MAVEN_REPO} @@ -83,6 +83,22 @@ runs: ${ART_TOKEN} + + + artifactory + + + artifactory + https://${HOST}/artifactory/${MAVEN_REPO} + true + false + + + + + + artifactory + EOF echo "Configured Maven to resolve through Artifactory (${MAVEN_REPO})" From 0a90b75fdb3d604c739ca3e221876c2ad3f676ee Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Mon, 13 Jul 2026 17:11:42 -0400 Subject: [PATCH 06/14] fix(ci): revert to virtual-maven-thirdparty (virtual-maven-twilio requires twilio org token) --- .github/actions/artifactory-oidc/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/artifactory-oidc/action.yml b/.github/actions/artifactory-oidc/action.yml index 0787e500..f71460db 100644 --- a/.github/actions/artifactory-oidc/action.yml +++ b/.github/actions/artifactory-oidc/action.yml @@ -13,7 +13,7 @@ inputs: maven-repo: description: "Artifactory virtual Maven repository name" required: false - default: "virtual-maven-twilio" + default: "virtual-maven-thirdparty" runs: using: "composite" From 2c48dbce9f54dd8050bbaba76d631ce49f90590d Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Wed, 22 Jul 2026 20:10:25 -0400 Subject: [PATCH 07/14] fix(ci): harden OIDC error redaction to cover all token fields --- .github/actions/artifactory-oidc/action.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/actions/artifactory-oidc/action.yml b/.github/actions/artifactory-oidc/action.yml index f71460db..312b8dc3 100644 --- a/.github/actions/artifactory-oidc/action.yml +++ b/.github/actions/artifactory-oidc/action.yml @@ -57,7 +57,8 @@ runs: if [ -z "$ART_TOKEN" ]; then echo "::error::OIDC token exchange failed." - echo "$RESP" | jq 'if .access_token then .access_token="" else . end' 2>/dev/null || echo "$RESP" + echo "$RESP" | jq 'walk(if type == "object" then with_entries(if (.key | test("token"; "i")) then .value = "" else . end) else . end)' 2>/dev/null \ + || echo "::error::(response withheld — not valid JSON)" exit 1 fi echo "::add-mask::$ART_TOKEN" From c4af5d47b1369dcadbfd27dd3eb3bb3b16695a0d Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Fri, 25 Sep 2026 22:17:54 -0400 Subject: [PATCH 08/14] fix(ci): authenticate Maven with the username the OIDC exchange returns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JFrog binds the access token to the subject identity it resolved and returns that identity in the exchange response. settings.xml hardcoded '_' instead, so the exchange succeeded and every subsequent artifact fetch got 401 — which read as an Artifactory permissions problem rather than a client-side auth bug. twilio/sdk-actions/artifactory-oidc reads .username for exactly this reason, and analytics-kotlin resolves through the same virtual-maven-thirdparty on the same host, so the repository itself is readable. --- .github/actions/artifactory-oidc/action.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/actions/artifactory-oidc/action.yml b/.github/actions/artifactory-oidc/action.yml index 312b8dc3..98f88a60 100644 --- a/.github/actions/artifactory-oidc/action.yml +++ b/.github/actions/artifactory-oidc/action.yml @@ -54,6 +54,10 @@ runs: \"provider_name\":\"${OIDC_PROVIDER_NAME}\"}") ART_TOKEN=$(echo "$RESP" | jq -r '.access_token // empty') + # JFrog binds the token to the subject identity it resolved, and returns + # that identity alongside it. Maven basic-auth must present that exact + # username: a placeholder exchanges fine and then 401s on every artifact. + ART_USERNAME=$(echo "$RESP" | jq -r '.username // empty') if [ -z "$ART_TOKEN" ]; then echo "::error::OIDC token exchange failed." @@ -63,6 +67,11 @@ runs: fi echo "::add-mask::$ART_TOKEN" + if [ -z "$ART_USERNAME" ]; then + echo "::error::Token exchange returned no username; Maven auth would 401 on every artifact." + exit 1 + fi + HOST=$(echo "${ARTIFACTORY_URL}" | sed -E 's#^https?://##') mkdir -p ~/.m2 @@ -80,7 +89,7 @@ runs: artifactory - _ + ${ART_USERNAME} ${ART_TOKEN} From 73124d68b24e1ba6c59c3d48f42f083b88a04907 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Fri, 25 Sep 2026 22:29:55 -0400 Subject: [PATCH 09/14] fix(deps): drop vestigial findbugs, bump assertj and google-java-format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit com.google.code.findbugs:findbugs was declared provided in both modules but nothing references it — no imports, no @SuppressFBWarnings, no findbugs plugin. The annotations actually in use come from jakarta.annotation-api. Its only effect was dragging in dom4j 1.6.1, which Artifactory curation refuses (403) and which blocked dependency resolution outright. assertj-core 3.24.2 is likewise curation-blocked; 3.27.7 is the current 3.x. google-java-format stays as low as Spotless permits on JVM 11 to keep the formatting delta from 1.5 as small as possible. --- analytics-core/pom.xml | 6 ------ analytics/pom.xml | 6 ------ pom.xml | 11 +++-------- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/analytics-core/pom.xml b/analytics-core/pom.xml index 4c0ed0cd..7fbc3184 100644 --- a/analytics-core/pom.xml +++ b/analytics-core/pom.xml @@ -51,12 +51,6 @@ gson ${gson.version} - - com.google.code.findbugs - findbugs - ${findbugs.version} - provided - com.google.auto.value auto-value diff --git a/analytics/pom.xml b/analytics/pom.xml index 0c19a777..58311bb3 100644 --- a/analytics/pom.xml +++ b/analytics/pom.xml @@ -66,12 +66,6 @@ retrofit-mock ${retrofit.version} - - com.google.code.findbugs - findbugs - ${findbugs.version} - provided - com.segment.backo backo diff --git a/pom.xml b/pom.xml index 411cd23e..b5eb1816 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,6 @@ 2.11.0 1.10.1 - 3.0.1 2.9.1 4.12.0 4.10.0 @@ -53,7 +52,7 @@ 4.13.2 1.2.0 4.11.0 - 3.24.2 + 3.27.7 @@ -103,11 +102,6 @@ auto-value-annotations ${auto.version} - - com.google.code.findbugs - findbugs - ${findbugs.version} - com.google.code.gson gson @@ -193,7 +187,8 @@ - 1.5 + + 1.8 From 3630085596db77339d676a8f441a00e8f17c2421 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Fri, 25 Sep 2026 22:34:18 -0400 Subject: [PATCH 10/14] fix(build): bump templating-maven-plugin off curated-blocked deps, reformat stub templating-maven-plugin 1.0.0 pulls maven-core 2.2.1 (2009) and maven-shared-utils 0.6, both refused by curation. google-java-format 1.8 collapses a short Javadoc onto one line where 1.5 kept it expanded; JavadocStub.java was the only file affected. --- analytics-cli/src/main/kotlin/cli/JavadocStub.java | 4 +--- analytics/pom.xml | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/analytics-cli/src/main/kotlin/cli/JavadocStub.java b/analytics-cli/src/main/kotlin/cli/JavadocStub.java index 403beaa7..a33e30f0 100644 --- a/analytics-cli/src/main/kotlin/cli/JavadocStub.java +++ b/analytics-cli/src/main/kotlin/cli/JavadocStub.java @@ -1,8 +1,6 @@ package cli; -/** - * Placeholder class to generate a Javadoc JAR for the CLI artifact. - */ +/** Placeholder class to generate a Javadoc JAR for the CLI artifact. */ public final class JavadocStub { private JavadocStub() {} } diff --git a/analytics/pom.xml b/analytics/pom.xml index 58311bb3..0230c840 100644 --- a/analytics/pom.xml +++ b/analytics/pom.xml @@ -113,7 +113,8 @@ org.codehaus.mojo templating-maven-plugin - 1.0.0 + + 3.1.1 From 7aa5b6ae49dbd066d2cfc6bcf816167f6e1df678 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Fri, 25 Sep 2026 22:39:17 -0400 Subject: [PATCH 11/14] style: apply google-java-format 1.8 to e2e-cli Main Output of mvn spotless:apply. Indentation and line wrapping only; no semantic change (git diff -w shows re-wrapping alone). --- e2e-cli/src/main/java/cli/Main.java | 340 ++++++++++++++-------------- 1 file changed, 173 insertions(+), 167 deletions(-) diff --git a/e2e-cli/src/main/java/cli/Main.java b/e2e-cli/src/main/java/cli/Main.java index 7c10646d..4e213ef4 100644 --- a/e2e-cli/src/main/java/cli/Main.java +++ b/e2e-cli/src/main/java/cli/Main.java @@ -5,7 +5,6 @@ import com.segment.analytics.Analytics; import com.segment.analytics.Callback; import com.segment.analytics.messages.*; - import java.lang.reflect.Type; import java.time.Instant; import java.util.*; @@ -15,189 +14,196 @@ public class Main { - private static final Gson gson = new Gson(); - private static final int DEFAULT_MAX_RETRIES = 1000; + private static final Gson gson = new Gson(); + private static final int DEFAULT_MAX_RETRIES = 1000; - public static void main(String[] args) { - Map output; - try { - output = run(args); - } catch (Exception e) { - output = errorOutput(e.getMessage()); - } - System.out.println(gson.toJson(output)); + public static void main(String[] args) { + Map output; + try { + output = run(args); + } catch (Exception e) { + output = errorOutput(e.getMessage()); + } + System.out.println(gson.toJson(output)); + } + + @SuppressWarnings("unchecked") + private static Map run(String[] args) throws Exception { + int inputIndex = indexOf(args, "--input"); + if (inputIndex == -1 || inputIndex + 1 >= args.length) { + throw new IllegalArgumentException("Missing required --input argument"); } - @SuppressWarnings("unchecked") - private static Map run(String[] args) throws Exception { - int inputIndex = indexOf(args, "--input"); - if (inputIndex == -1 || inputIndex + 1 >= args.length) { - throw new IllegalArgumentException("Missing required --input argument"); - } - - String inputJson = args[inputIndex + 1]; - Type mapType = new TypeToken>() {}.getType(); - Map input = gson.fromJson(inputJson, mapType); - - String writeKey = (String) input.get("writeKey"); - String apiHost = (String) input.get("apiHost"); - Map config = (Map) input.getOrDefault("config", Collections.emptyMap()); - List> sequences = (List>) input.get("sequences"); - - int flushAt = intVal(config, "flushAt", 20); - long flushIntervalMs = longVal(config, "flushInterval", 10000L); - int maxRetries = intVal(config, "maxRetries", DEFAULT_MAX_RETRIES); - - AtomicBoolean hasError = new AtomicBoolean(false); - AtomicReference errorMessage = new AtomicReference<>(); - - Analytics analytics = Analytics.builder(writeKey) - .endpoint(apiHost) - .flushQueueSize(flushAt) - .flushInterval(Math.max(flushIntervalMs, 1000L), TimeUnit.MILLISECONDS) - .retries(maxRetries) - .callback(new Callback() { - @Override - public void success(Message message) { - } - - @Override - public void failure(Message message, Throwable throwable) { - hasError.set(true); - errorMessage.set(throwable != null ? throwable.getMessage() : "unknown error"); - } + String inputJson = args[inputIndex + 1]; + Type mapType = new TypeToken>() {}.getType(); + Map input = gson.fromJson(inputJson, mapType); + + String writeKey = (String) input.get("writeKey"); + String apiHost = (String) input.get("apiHost"); + Map config = + (Map) input.getOrDefault("config", Collections.emptyMap()); + List> sequences = (List>) input.get("sequences"); + + int flushAt = intVal(config, "flushAt", 20); + long flushIntervalMs = longVal(config, "flushInterval", 10000L); + int maxRetries = intVal(config, "maxRetries", DEFAULT_MAX_RETRIES); + + AtomicBoolean hasError = new AtomicBoolean(false); + AtomicReference errorMessage = new AtomicReference<>(); + + Analytics analytics = + Analytics.builder(writeKey) + .endpoint(apiHost) + .flushQueueSize(flushAt) + .flushInterval(Math.max(flushIntervalMs, 1000L), TimeUnit.MILLISECONDS) + .retries(maxRetries) + .callback( + new Callback() { + @Override + public void success(Message message) {} + + @Override + public void failure(Message message, Throwable throwable) { + hasError.set(true); + errorMessage.set(throwable != null ? throwable.getMessage() : "unknown error"); + } }) - .build(); - - for (Map seq : sequences) { - long delayMs = longVal(seq, "delayMs", 0L); - if (delayMs > 0) { - Thread.sleep(delayMs); - } - List> events = (List>) seq.get("events"); - if (events != null) { - for (Map event : events) { - sendEvent(analytics, event); - } - } + .build(); + + for (Map seq : sequences) { + long delayMs = longVal(seq, "delayMs", 0L); + if (delayMs > 0) { + Thread.sleep(delayMs); + } + List> events = (List>) seq.get("events"); + if (events != null) { + for (Map event : events) { + sendEvent(analytics, event); } - - analytics.flush(); - analytics.shutdown(); - - if (hasError.get()) { - return errorOutput(errorMessage.get()); - } - - Map result = new LinkedHashMap<>(); - result.put("success", true); - result.put("sentBatches", 1); - return result; + } } - @SuppressWarnings("unchecked") - private static void sendEvent(Analytics analytics, Map event) { - String type = (String) event.get("type"); - if (type == null) { - throw new IllegalArgumentException("Event missing 'type' field"); - } - - String userId = strVal(event, "userId", ""); - String anonymousId = (String) event.get("anonymousId"); - String messageId = (String) event.get("messageId"); - String timestamp = (String) event.get("timestamp"); - Map traits = (Map) event.getOrDefault("traits", Collections.emptyMap()); - Map properties = (Map) event.getOrDefault("properties", Collections.emptyMap()); - String eventName = (String) event.get("event"); - String name = (String) event.get("name"); - String groupId = (String) event.get("groupId"); - String previousId = (String) event.get("previousId"); - Map context = (Map) event.get("context"); - Map integrations = (Map) event.get("integrations"); - - MessageBuilder builder; - switch (type) { - case "identify": - builder = IdentifyMessage.builder().traits(traits); - break; - case "track": - builder = TrackMessage.builder(eventName != null ? eventName : "Unknown Event").properties(properties); - break; - case "page": - builder = PageMessage.builder(name != null ? name : "Unknown Page").properties(properties); - break; - case "screen": - builder = ScreenMessage.builder(name != null ? name : "Unknown Screen").properties(properties); - break; - case "alias": - builder = AliasMessage.builder(previousId != null ? previousId : ""); - break; - case "group": - builder = GroupMessage.builder(groupId != null ? groupId : "").traits(traits); - break; - default: - throw new IllegalArgumentException("Unknown event type: " + type); - } + analytics.flush(); + analytics.shutdown(); - if (!userId.isEmpty()) { - builder.userId(userId); - } - if (anonymousId != null) { - builder.anonymousId(anonymousId); - } - if (messageId != null) { - builder.messageId(messageId); - } - if (timestamp != null) { - builder.timestamp(Date.from(Instant.parse(timestamp))); - } - if (context != null) { - builder.context(context); - } - if (integrations != null) { - for (Map.Entry entry : integrations.entrySet()) { - Object value = entry.getValue(); - if (value instanceof Boolean) { - builder.enableIntegration(entry.getKey(), (Boolean) value); - } else if (value instanceof Map) { - builder.integrationOptions(entry.getKey(), (Map) value); - } - } - } + if (hasError.get()) { + return errorOutput(errorMessage.get()); + } - analytics.enqueue(builder); + Map result = new LinkedHashMap<>(); + result.put("success", true); + result.put("sentBatches", 1); + return result; + } + + @SuppressWarnings("unchecked") + private static void sendEvent(Analytics analytics, Map event) { + String type = (String) event.get("type"); + if (type == null) { + throw new IllegalArgumentException("Event missing 'type' field"); } - private static Map errorOutput(String error) { - Map result = new LinkedHashMap<>(); - result.put("success", false); - result.put("error", error); - result.put("sentBatches", 0); - return result; + String userId = strVal(event, "userId", ""); + String anonymousId = (String) event.get("anonymousId"); + String messageId = (String) event.get("messageId"); + String timestamp = (String) event.get("timestamp"); + Map traits = + (Map) event.getOrDefault("traits", Collections.emptyMap()); + Map properties = + (Map) event.getOrDefault("properties", Collections.emptyMap()); + String eventName = (String) event.get("event"); + String name = (String) event.get("name"); + String groupId = (String) event.get("groupId"); + String previousId = (String) event.get("previousId"); + Map context = (Map) event.get("context"); + Map integrations = (Map) event.get("integrations"); + + MessageBuilder builder; + switch (type) { + case "identify": + builder = IdentifyMessage.builder().traits(traits); + break; + case "track": + builder = + TrackMessage.builder(eventName != null ? eventName : "Unknown Event") + .properties(properties); + break; + case "page": + builder = PageMessage.builder(name != null ? name : "Unknown Page").properties(properties); + break; + case "screen": + builder = + ScreenMessage.builder(name != null ? name : "Unknown Screen").properties(properties); + break; + case "alias": + builder = AliasMessage.builder(previousId != null ? previousId : ""); + break; + case "group": + builder = GroupMessage.builder(groupId != null ? groupId : "").traits(traits); + break; + default: + throw new IllegalArgumentException("Unknown event type: " + type); } - private static int indexOf(String[] arr, String target) { - for (int i = 0; i < arr.length; i++) { - if (target.equals(arr[i])) return i; + if (!userId.isEmpty()) { + builder.userId(userId); + } + if (anonymousId != null) { + builder.anonymousId(anonymousId); + } + if (messageId != null) { + builder.messageId(messageId); + } + if (timestamp != null) { + builder.timestamp(Date.from(Instant.parse(timestamp))); + } + if (context != null) { + builder.context(context); + } + if (integrations != null) { + for (Map.Entry entry : integrations.entrySet()) { + Object value = entry.getValue(); + if (value instanceof Boolean) { + builder.enableIntegration(entry.getKey(), (Boolean) value); + } else if (value instanceof Map) { + builder.integrationOptions(entry.getKey(), (Map) value); } - return -1; + } } - private static int intVal(Map map, String key, int defaultVal) { - Object v = map.get(key); - if (v instanceof Number) return ((Number) v).intValue(); - return defaultVal; - } + analytics.enqueue(builder); + } - private static long longVal(Map map, String key, long defaultVal) { - Object v = map.get(key); - if (v instanceof Number) return ((Number) v).longValue(); - return defaultVal; - } + private static Map errorOutput(String error) { + Map result = new LinkedHashMap<>(); + result.put("success", false); + result.put("error", error); + result.put("sentBatches", 0); + return result; + } - private static String strVal(Map map, String key, String defaultVal) { - Object v = map.get(key); - if (v instanceof String) return (String) v; - return defaultVal; + private static int indexOf(String[] arr, String target) { + for (int i = 0; i < arr.length; i++) { + if (target.equals(arr[i])) return i; } + return -1; + } + + private static int intVal(Map map, String key, int defaultVal) { + Object v = map.get(key); + if (v instanceof Number) return ((Number) v).intValue(); + return defaultVal; + } + + private static long longVal(Map map, String key, long defaultVal) { + Object v = map.get(key); + if (v instanceof Number) return ((Number) v).longValue(); + return defaultVal; + } + + private static String strVal(Map map, String key, String defaultVal) { + Object v = map.get(key); + if (v instanceof String) return (String) v; + return defaultVal; + } } From 885cf4108a469fc94129fc9865de1b56f0cc7d0e Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Fri, 25 Sep 2026 22:43:20 -0400 Subject: [PATCH 12/14] fix(ci): build the reactor before animal-sniffer The pre-hardening workflow ran spotless, animal-sniffer, test and verify in one Maven invocation, so inter-module SNAPSHOTs resolved from the reactor. Split into its own call, animal-sniffer could not resolve analytics-core. --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e484483a..e065f4e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,9 @@ jobs: run: mvn -B spotless:check - name: Animal Sniffer check - run: mvn -B animal-sniffer:check + # Needs the reactor built first: `analytics` compiles against the + # analytics-core SNAPSHOT, which exists nowhere until this run makes it. + run: mvn -B -DskipTests package animal-sniffer:check test: name: Test (Java ${{ matrix.java-version }}) From 3c6ce4cc3d36073136d845fc03f3a2d61ad09d19 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Fri, 25 Sep 2026 22:46:59 -0400 Subject: [PATCH 13/14] fix(build): bump maven-javadoc-plugin, scope the lint build to compile javadoc-plugin 3.6.3 resolves maven-core 3.1.1 (2013), which curation refuses. The build-verification job packages, so this blocks it regardless of lint. The lint job only needs compiled classes for animal-sniffer, so compile rather than package keeps javadoc, source and gpg out of it entirely. --- .github/workflows/ci.yml | 6 ++++-- pom.xml | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e065f4e1..4c2f03c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,9 +39,11 @@ jobs: run: mvn -B spotless:check - name: Animal Sniffer check - # Needs the reactor built first: `analytics` compiles against the + # Needs the reactor compiled first: `analytics` builds against the # analytics-core SNAPSHOT, which exists nowhere until this run makes it. - run: mvn -B -DskipTests package animal-sniffer:check + # `compile` rather than `package` — linting has no use for the javadoc, + # source and signing plugins that packaging would drag in. + run: mvn -B -DskipTests compile animal-sniffer:check test: name: Test (Java ${{ matrix.java-version }}) diff --git a/pom.xml b/pom.xml index b5eb1816..8ad9ed3d 100644 --- a/pom.xml +++ b/pom.xml @@ -301,7 +301,8 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.6.3 + + 3.12.0 attach-javadocs From d6655bdc9f998b405e97d4bf0994d93389889064 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Fri, 25 Sep 2026 22:51:31 -0400 Subject: [PATCH 14/14] refactor(ci): use twilio/sdk-actions for Artifactory OIDC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the repo-local copy with the shared first-party action, pinned. It covers maven natively and is the same action analytics-kotlin adopted, so fixes land once instead of per SDK — including the username handling this repo had to discover on its own. --- .github/actions/artifactory-oidc/action.yml | 114 -------------------- .github/workflows/ci.yml | 15 +-- .github/workflows/deploy.yml | 10 +- .github/workflows/e2e-tests.yml | 5 +- 4 files changed, 18 insertions(+), 126 deletions(-) delete mode 100644 .github/actions/artifactory-oidc/action.yml diff --git a/.github/actions/artifactory-oidc/action.yml b/.github/actions/artifactory-oidc/action.yml deleted file mode 100644 index 98f88a60..00000000 --- a/.github/actions/artifactory-oidc/action.yml +++ /dev/null @@ -1,114 +0,0 @@ -name: "Artifactory OIDC Auth" -description: "Exchange GitHub OIDC token for Artifactory access token and configure Maven" - -inputs: - artifactory-url: - description: "JFrog platform base URL. Falls back to ARTIFACTORY_URL env var." - required: false - default: "" - oidc-provider-name: - description: "OIDC provider name configured in Artifactory" - required: false - default: "github-actions-segmentio" - maven-repo: - description: "Artifactory virtual Maven repository name" - required: false - default: "virtual-maven-thirdparty" - -runs: - using: "composite" - steps: - - name: Exchange GitHub OIDC token for Artifactory token - shell: bash - env: - INPUT_ARTIFACTORY_URL: ${{ inputs.artifactory-url }} - OIDC_PROVIDER_NAME: ${{ inputs.oidc-provider-name }} - MAVEN_REPO: ${{ inputs.maven-repo }} - run: | - set -euo pipefail - ARTIFACTORY_URL="${INPUT_ARTIFACTORY_URL:-${ARTIFACTORY_URL:-}}" - if [ -z "${ARTIFACTORY_URL}" ]; then - echo "::error::ARTIFACTORY_URL is not set (pass as input or set as env var)"; exit 1 - fi - - OIDC_JWT=$(curl -sS \ - "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=${ARTIFACTORY_URL}" \ - -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" | jq -r '.value') - - if [ -z "$OIDC_JWT" ] || [ "$OIDC_JWT" = "null" ]; then - echo "::error::Failed to obtain GitHub OIDC token"; exit 1 - fi - - decode_seg() { local s="${1}"; local m=$(( ${#s} % 4 )); [ $m -ne 0 ] && s="${s}$(printf '=%.0s' $(seq 1 $((4-m))))"; echo "$s" | tr '_-' '/+' | base64 -d 2>/dev/null; } - PAYLOAD=$(decode_seg "$(echo "$OIDC_JWT" | cut -d. -f2)") - echo "OIDC token claims:" - echo " sub = $(echo "$PAYLOAD" | jq -r '.sub')" - echo " aud = $(echo "$PAYLOAD" | jq -r '.aud')" - echo " iss = $(echo "$PAYLOAD" | jq -r '.iss')" - - RESP=$(curl -sS "${ARTIFACTORY_URL}/access/api/v1/oidc/token" \ - -H 'Content-Type: application/json' \ - -d "{\"grant_type\":\"urn:ietf:params:oauth:grant-type:token-exchange\", - \"subject_token_type\":\"urn:ietf:params:oauth:token-type:id_token\", - \"subject_token\":\"${OIDC_JWT}\", - \"provider_name\":\"${OIDC_PROVIDER_NAME}\"}") - - ART_TOKEN=$(echo "$RESP" | jq -r '.access_token // empty') - # JFrog binds the token to the subject identity it resolved, and returns - # that identity alongside it. Maven basic-auth must present that exact - # username: a placeholder exchanges fine and then 401s on every artifact. - ART_USERNAME=$(echo "$RESP" | jq -r '.username // empty') - - if [ -z "$ART_TOKEN" ]; then - echo "::error::OIDC token exchange failed." - echo "$RESP" | jq 'walk(if type == "object" then with_entries(if (.key | test("token"; "i")) then .value = "" else . end) else . end)' 2>/dev/null \ - || echo "::error::(response withheld — not valid JSON)" - exit 1 - fi - echo "::add-mask::$ART_TOKEN" - - if [ -z "$ART_USERNAME" ]; then - echo "::error::Token exchange returned no username; Maven auth would 401 on every artifact." - exit 1 - fi - - HOST=$(echo "${ARTIFACTORY_URL}" | sed -E 's#^https?://##') - - mkdir -p ~/.m2 - cat > ~/.m2/settings.xml << EOF - - - - artifactory - * - https://${HOST}/artifactory/${MAVEN_REPO} - - - - - artifactory - ${ART_USERNAME} - ${ART_TOKEN} - - - - - artifactory - - - artifactory - https://${HOST}/artifactory/${MAVEN_REPO} - true - false - - - - - - artifactory - - - EOF - echo "Configured Maven to resolve through Artifactory (${MAVEN_REPO})" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c2f03c5..9b072cda 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,9 +31,10 @@ jobs: - name: Artifactory OIDC if: ${{ !github.event.pull_request.head.repo.fork }} - uses: ./.github/actions/artifactory-oidc + uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main with: - artifactory_url: ${{ vars.ARTIFACTORY_URL }} + ecosystem: maven + provider-name: github-actions-segmentio - name: Spotless check run: mvn -B spotless:check @@ -66,9 +67,10 @@ jobs: - name: Artifactory OIDC if: ${{ !github.event.pull_request.head.repo.fork }} - uses: ./.github/actions/artifactory-oidc + uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main with: - artifactory_url: ${{ vars.ARTIFACTORY_URL }} + ecosystem: maven + provider-name: github-actions-segmentio - name: Run tests run: mvn -B test @@ -91,9 +93,10 @@ jobs: - name: Artifactory OIDC if: ${{ !github.event.pull_request.head.repo.fork }} - uses: ./.github/actions/artifactory-oidc + uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main with: - artifactory_url: ${{ vars.ARTIFACTORY_URL }} + ecosystem: maven + provider-name: github-actions-segmentio - name: Build run: mvn -B package -DskipTests diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2632436c..50db64ba 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -28,9 +28,10 @@ jobs: cache: maven - name: Artifactory OIDC - uses: ./.github/actions/artifactory-oidc + uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main with: - artifactory_url: ${{ vars.ARTIFACTORY_URL }} + ecosystem: maven + provider-name: github-actions-segmentio - name: Run tests run: mvn -B test @@ -58,9 +59,10 @@ jobs: gpg-passphrase: GPG_PASSPHRASE - name: Artifactory OIDC - uses: ./.github/actions/artifactory-oidc + uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main with: - artifactory_url: ${{ vars.ARTIFACTORY_URL }} + ecosystem: maven + provider-name: github-actions-segmentio - name: Deploy to Maven Central env: diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index dd706a10..8255dc98 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -48,9 +48,10 @@ jobs: cache: maven - name: Artifactory OIDC - uses: ./sdk/.github/actions/artifactory-oidc + uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main with: - artifactory_url: ${{ vars.ARTIFACTORY_URL }} + ecosystem: maven + provider-name: github-actions-segmentio - name: Setup Node.js uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4