Publisher #6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publisher | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| build_run_id: | |
| required: true | |
| type: string | |
| workflow_call: | |
| inputs: | |
| # docs.github.com/en/actions/reference/workflows-and-actions/contexts | |
| run_id: | |
| description: "Workflow run id that produced signed artifacts" | |
| required: true | |
| type: string | |
| vcsver: | |
| description: "Short git version" | |
| required: false | |
| type: string | |
| artifact_subjects: | |
| description: "JSON array of artifact subjects with sha256 digests" | |
| required: false | |
| type: string | |
| sbom_info: | |
| description: "SBOM info JSON blob with subjects and digest" | |
| required: false | |
| type: string | |
| jobs: | |
| publish: | |
| name: 🚚 Publish | |
| runs-on: ubuntu-latest | |
| env: | |
| # docs.github.com/en/actions/reference/workflows-and-actions/contexts#github-context | |
| GROUP_GITHUB: ${{ format('com.github.{0}', github.repository_owner) }} | |
| GROUP_OSSRH: com.celzero | |
| # project artifactId; see: pom.xml | |
| ARTIFACT: firestack | |
| REPO_GITHUB: github | |
| # central.sonatype.org/pages/ossrh-eol | |
| # or "central" | |
| REPO_OSSRH: ossrh | |
| # artefact type | |
| PACK: aar | |
| # final out from make-aar | |
| FOUT: firestack.aar | |
| FOUTDBG: firestack-debug.aar | |
| # artifact classifier; full unused | |
| CLASSFULL: full | |
| CLASSDBG: debug | |
| # artifact bytecode sources | |
| SOURCES: build/intra/tun2socks-sources.jar | |
| # POM for Maven Central | |
| POM_OSSRH: ossrhpom.xml | |
| DIST_DIR: dist | |
| RUN_ID: ${{ inputs.run_id || inputs.build_run_id }} | |
| VCSVER_INPUT: ${{ inputs.vcsver }} | |
| # workflow input constants | |
| ARTIFACT_SUBJECTS: ${{ inputs.artifact_subjects }} | |
| SBOM_INFO: ${{ inputs.sbom_info }} | |
| ARTIFACT_PATTERN: "firestack-aar-*" | |
| SBOM_PATTERN: "firestack-sbom-*" | |
| ARTIFACT_PREFIX: "firestack-aar-" | |
| SBOM_PREFIX: "firestack-sbom-" | |
| SBOM_MANIFEST: "manifest.spdx.json" | |
| SBOM_PREDICATE: "https://spdx.dev/Document/v2.2" | |
| permissions: | |
| contents: read | |
| actions: read | |
| attestations: read | |
| packages: write | |
| steps: | |
| - name: 🥏 Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| - name: 📀 Metadata | |
| id: runmeta | |
| env: | |
| RUN_ID: ${{ env.RUN_ID }} | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| if [ -n "${VCSVER_INPUT:-}" ]; then | |
| printf 'vcsver=%s\n' "${VCSVER_INPUT}" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| info=$(gh run view "$RUN_ID" --json headSha,headBranch,event,displayTitle) | |
| echo "$info" | jq | |
| sha=$(echo "$info" | jq -r '.headSha') | |
| if [ -z "$sha" ] || [ "$sha" = "null" ]; then | |
| echo "::error::unable to resolve head sha for run $RUN_ID" >&2 | |
| exit 11 | |
| fi | |
| # git version (short commit sha) | |
| printf 'sha=%s\n' "$sha" >> "$GITHUB_OUTPUT" | |
| printf 'vcsver=%s\n' "${sha:0:10}" >> "$GITHUB_OUTPUT" | |
| - name: ⬇️ Download artifacts | |
| id: dlaar | |
| uses: actions/download-artifact@v7 | |
| with: | |
| pattern: ${{ env.ARTIFACT_PATTERN }} | |
| run-id: ${{ env.RUN_ID }} | |
| github-token: ${{ github.token }} | |
| path: ${{ env.DIST_DIR }} | |
| - name: ⬇️ Download SBOM artifact | |
| id: dlsbom | |
| uses: actions/download-artifact@v7 | |
| with: | |
| pattern: ${{ env.SBOM_PATTERN }} | |
| run-id: ${{ env.RUN_ID }} | |
| github-token: ${{ github.token }} | |
| path: ${{ env.DIST_DIR }} | |
| - name: 🔐 Verify build provenance | |
| env: | |
| REPO: ${{ github.repository }} | |
| ART_DIR: ${{ steps.dlaar.outputs.download-path }} | |
| GH_TOKEN: ${{ github.token }} | |
| SHA: ${{ steps.runmeta.outputs.sha }} | |
| run: | | |
| set -xeuo pipefail | |
| ls -ltr "${ART_DIR}/" | |
| # need to go one dir further for download-artifact v4 not v7 | |
| # ART_DIR="${ART_DIR}/${ARTIFACT_PREFIX}${SHA}" | |
| # ls -ltr "${ART_DIR}/" | |
| for file in "$ART_DIR/${FOUT}" "$ART_DIR/${FOUTDBG}"; do | |
| if [ ! -f "$file" ]; then | |
| echo "::error::missing artifact $file" >&2 | |
| exit 12 | |
| fi | |
| gh attestation verify "$file" -R "$REPO" | |
| done | |
| if [ -n "${ARTIFACT_SUBJECTS:-}" ]; then | |
| jq -c '.[]' <<<"${ARTIFACT_SUBJECTS}" | while read -r subject; do | |
| name=$(jq -r '.name' <<<"$subject") | |
| digest=$(jq -r '.digest' <<<"$subject") | |
| file="${ART_DIR}/${name##*/}" | |
| if [ ! -f "$file" ]; then | |
| echo "::error::missing artifact $file for digest check" >&2 | |
| exit 13 | |
| fi | |
| want=${digest#sha256:} | |
| got=$(sha256sum "$file" | awk '{print $1}') | |
| if [ "$got" != "$want" ]; then | |
| echo "::error::digest mismatch for $file (got $got, want $want)" >&2 | |
| exit 14 | |
| fi | |
| done | |
| fi | |
| - name: 🔐 Verify SBOM attestation | |
| env: | |
| REPO: ${{ github.repository }} | |
| ART_DIR: ${{ steps.dlaar.outputs.download-path }} | |
| SBOM_DIR: ${{ steps.dlsbom.outputs.download-path }} | |
| GH_TOKEN: ${{ github.token }} | |
| SHA: ${{ steps.runmeta.outputs.sha }} | |
| run: | | |
| # andrewlock.net/creating-sbom-attestations-in-github-actions/ | |
| set -xeuo pipefail | |
| ls -ltr "${SBOM_DIR}/" | |
| # need to go one dir further for download-artifact v4 not v7 | |
| # SBOM_DIR="${SBOM_DIR}/${SBOM_PREFIX}${SHA}" | |
| # ls -ltr "${SBOM_DIR}/" | |
| if [ -n "${SBOM_INFO:-}" ]; then | |
| name=$(jq -r '.path' <<<"${SBOM_INFO}") | |
| sbom_file="$SBOM_DIR/$(jq -r '.artifactName' <<<"${SBOM_INFO}")/${name}" | |
| digest=$(jq -r '.digest' <<<"${SBOM_INFO}") | |
| # github.com/celzero/firestack/blob/86af89da10abe/.github/workflows/go.yml#L398 | |
| jq -c '.subjects[]' <<<"$SBOM_INFO" | while read -r subject; do | |
| name=$(jq -r '.name' <<<"$subject") | |
| file="${ART_DIR}/${name##*/}" | |
| if [ ! -f "$file" ]; then | |
| echo "::error::missing SBOM subject artifact $file" | |
| exit 14 | |
| fi | |
| gh attestation verify "$file" -R "$REPO" --predicate-type "$predicate" | |
| echo "Verified SBOM subject artifact $file" | |
| done | |
| else | |
| sbom_file=$(find "${SBOM_DIR}" -name "${SBOM_MANIFEST}" -print -quit) | |
| digest=$(cat < $(find "${SBOM_DIR}" -name "${SBOM_MANIFEST}.sha256" -print -quit)) | |
| # iterate in ART_DIR for artifacts then verify sbom attestations | |
| for file in "$ART_DIR/${FOUT}" "$ART_DIR/${FOUTDBG}"; do | |
| if [ ! -f "$file" ]; then | |
| echo "::error::missing artifact $file for SBOM attestation check" >&2 | |
| exit 13 | |
| fi | |
| gh attestation verify "$file" -R "$REPO" --predicate-type "${SBOM_PREDICATE}" | |
| echo "Verified SBOM attestation for artifact $file" | |
| done | |
| fi | |
| if [ -z "$sbom_file" ]; then | |
| echo "::error::SBOM file not found in ${SBOM_DIR}/" >&2 | |
| exit 15 | |
| fi | |
| if [ -n "$digest" ] && [ "$digest" != "null" ]; then | |
| want=${digest#sha256:} | |
| got=$(sha256sum "$sbom_file" | awk '{print $1}') | |
| if [ "$got" != "$want" ]; then | |
| echo "::error::SBOM digest mismatch (got $got, want $want)" >&2 | |
| exit 16 | |
| fi | |
| else | |
| echo "No SBOM digest; skipping digest verification" >&2 | |
| fi | |
| - name: 🏷️ Setup for GitHub Packages | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: '17' | |
| distribution: 'temurin' | |
| # docs.github.com/en/actions/tutorials/build-and-test-code/java-with-maven | |
| # docs.github.com/en/actions/tutorials/publish-packages/publish-java-packages-with-maven#publishing-packages-to-github-packages | |
| - name: 😺 Publish to GitHub Packages | |
| shell: bash | |
| env: | |
| REPOSITORY: ${{ github.repository }} | |
| GITHUB_ACTOR: ${{ github.actor }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| VCSVER: ${{ steps.runmeta.outputs.vcsver }} | |
| run: | | |
| # uploaded at: | |
| # maven.pkg.github.com/celzero/firestack/com/github/celzero/firestack/<commit>/firestack-<commit>.aar | |
| # github.com/deelaa-marketplace/commons-workflow/blob/637dc111/flows/publish-api.yml#L49 | |
| # github.com/markocto/cf-octopub/blob/bba2de2c/github/script/action.yaml#L118 | |
| # publish both stripped and debug | |
| mvn deploy:deploy-file \ | |
| -DgroupId="${GROUP_GITHUB}" \ | |
| -DartifactId="${ARTIFACT}" \ | |
| -Dversion="$VCSVER" \ | |
| -Dpackaging="${PACK}" \ | |
| -Dfile="${DIST_DIR}/${FOUT}" \ | |
| -Dfiles="${DIST_DIR}/${FOUTDBG}" \ | |
| -Dtypes="${PACK}" \ | |
| -Dclassifiers=${CLASSDBG} \ | |
| -DrepositoryId="${REPO_GITHUB}" \ | |
| -Dsources="${DIST_DIR}/${SOURCES}" \ | |
| -Durl="https://maven.pkg.github.com/${REPOSITORY}" | |
| # central.sonatype.org/publish/publish-portal-api/#authentication-authorization | |
| # github.com/slsa-framework/slsa-github-generator/blob/4876e96b8268/actions/maven/publish/action.yml#L49 | |
| # docs.github.com/en/actions/tutorials/publish-packages/publish-java-packages-with-maven#publishing-packages-to-the-maven-central-repository-and-github-packages | |
| - name: 🏛️ Setup for Maven Central | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: '17' | |
| distribution: 'temurin' | |
| server-id: ossrh | |
| server-username: MAVEN_USERNAME | |
| server-password: MAVEN_PASSWORD | |
| gpg-private-key: ${{ secrets.OSSRH_CELZERO_GPG_PRIVATE_KEY }} | |
| gpg-passphrase: ${{ secrets.OSSRH_CELZERO_GPG_PASSPHRASE }} | |
| - name: 📦 Publish to Maven Central | |
| shell: bash | |
| env: | |
| MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} | |
| MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} | |
| MAVEN_NS: ${{ secrets.OSSRH_CELZERO_NAMESPACE }} | |
| MAVEN_GPG_PASSPHRASE: ${{ secrets.OSSRH_CELZERO_GPG_PASSPHRASE }} | |
| VCSVER: ${{ steps.runmeta.outputs.vcsver }} | |
| run: | | |
| mvn -f ${POM_OSSRH} versions:set -DnewVersion=${VCSVER} -DgenerateBackupPoms=false | |
| # central.sonatype.org/publish/publish-portal-ossrh-staging-api/#getting-started-for-maven-api-like-plugins | |
| # github.com/videolan/vlc-android/blob/c393dd0699/buildsystem/maven/deploy-to-mavencentral.sh#L119 | |
| mvn gpg:sign-and-deploy-file \ | |
| -DgroupId="${GROUP_OSSRH}" \ | |
| -DartifactId="${ARTIFACT}" \ | |
| -Dversion="$VCSVER" \ | |
| -Dpackaging="${PACK}" \ | |
| -Dfile="${DIST_DIR}/${FOUT}" \ | |
| -DrepositoryId="${REPO_OSSRH}" \ | |
| -DpomFile=${POM_OSSRH} \ | |
| -Dgpg.keyname=C3F3F4A160BB2CFFB5528699F19CE6642C40085C \ | |
| -Dsources="${DIST_DIR}/${SOURCES}" \ | |
| -Durl="https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/" | |
| mvn gpg:sign-and-deploy-file \ | |
| -DgroupId="${GROUP_OSSRH}" \ | |
| -DartifactId="${ARTIFACT}" \ | |
| -Dversion="$VCSVER" \ | |
| -Dpackaging="${PACK}" \ | |
| -Dfile="${DIST_DIR}/${FOUTDBG}" \ | |
| -Dclassifier=${CLASSDBG} \ | |
| -DrepositoryId="${REPO_OSSRH}" \ | |
| -DgeneratePom=false \ | |
| -Dgpg.keyname=C3F3F4A160BB2CFFB5528699F19CE6642C40085C \ | |
| -Durl="https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/" | |
| # central.sonatype.org/publish/publish-portal-api/#authentication-authorization | |
| tok=$(printf "${MAVEN_USERNAME}:${MAVEN_PASSWORD}" | base64) | |
| # central.sonatype.org/publish/publish-portal-ossrh-staging-api/#1-modify-your-ci-script | |
| # central.sonatype.org/publish/publish-portal-ossrh-staging-api/#post-to-manualuploaddefaultrepositorynamespace | |
| # auth required for publishing_type=automatic | |
| curl -D - -X POST -H "Authorization: Bearer ${tok}" \ | |
| "https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/${GROUP_OSSRH}?publishing_type=automatic" |