You're setting up signed publishing in CI for a library that releases to Maven Central. How do you make sure every release artifact is correctly signed, and how would you validate it before the upload actually hits Central?
answer
- sign publication for full coverage
- gpg --verify gate in CI
- publishToMavenLocal pre-flight
- keys via CI secrets, public on keyservers
- stage -> validate -> release
basics
~20 sSign the publication so every artifact (incl. POM) gets a .asc, then run signMavenPublication plus publishToMavenLocal in CI and gpg --verify the outputs as a gate before the real upload. Keep key material out of the repo, injected via CI secrets.
solid answer
~50 sThe strategy has three parts. **Completeness:** sign the *publication* (`sign(publishing.publications["maven"])`), not loose files, so the main jar, sources, javadoc and the generated POM are all signed and registered for upload — Central rejects any unsigned member. **Pre-flight validation:** in CI, before the real release, run `./gradlew signMavenPublication publishToMavenLocal` and then `gpg --verify` each `.asc` against its artifact, failing the build on any bad/missing signature; this catches a wrong key, an empty passphrase, or a missing artifact early. **Key handling:** never commit keys; inject the key material via CI secrets (env-based in-memory key + passphrase) and ensure the key's public half is published to keyservers so Central can verify. I'd also stage to Sonatype's staging repo, run their validation, and only release after it passes — so a bad signature fails staging, not the immutable release. Optionally make signing required only on real releases so local dev builds aren't blocked by missing keys.
code
bash · 8 lines# CI pre-flight gate before real release
./gradlew clean signMavenPublication publishToMavenLocal
fail=0
for a in build/libs/*.jar build/libs/*.pom; do
[ -f "$a.asc" ] || { echo "missing sig: $a"; fail=1; continue; }
gpg --verify "$a.asc" "$a" || { echo "bad sig: $a"; fail=1; }
done
exit $failgo deeper
Knows you must sign and that Central requires it; not expected to design the gate.
Can sign the publication and run gpg --verify locally to confirm outputs.
Designs the full CI gate, key-secret injection, and stage→validate→release flow; explains why publication-level signing avoids POM gaps.
Owns org-wide signing/provenance policy: key custody, rotation, reusable convention plugin, and supply-chain guarantees across all libraries.
## Goal Maven Central is immutable and validates signatures. A botched release (missing POM signature, wrong key) is painful to undo, so the strategy is to **fail fast, before the upload commits**. ## 1. Sign at the right granularity Sign the publication so coverage is exhaustive: ```kotlin signing { sign(publishing.publications["maven"]) } ``` This guarantees the generated POM and every jar (main/sources/javadoc) get a `.asc` registered as an artifact. Signing individual files risks missing the POM — a classic cause of Central rejection. ## 2. Pre-flight verification gate in CI Produce signatures and verify them *before* the network upload: ```bash ./gradlew clean signMavenPublication publishToMavenLocal for jar in build/libs/*.jar; do gpg --verify "$jar.asc" "$jar" || { echo "BAD SIG: $jar"; exit 1; } done ``` Also verify the signed POM under `build/publications/maven/`. This step catches: an env var that didn't expand (empty passphrase → unusable signature), a truncated key, or an artifact that exists without a corresponding `.asc`. Because it runs locally in the runner, it costs nothing and protects the immutable repo. ## 3. Key custody (architecture, not the per-key mechanics) Keep private keys out of the repo. In CI, inject them as masked secrets and feed them to signing via in-memory configuration; expose only on the release job, not on PR builds. Publish the public key to keyservers so Central (and downstream consumers) can verify. Rotate and scope keys per the org's secret-management policy. ## 4. Stage, validate, then release For Central, publish to a **staging** repository first (Sonatype). Their validation re-checks every signature; only after it passes do you 'release' the staging repo to the public index. This adds a second safety net beyond your local `gpg --verify`. ## 5. Make signing conditional for dev ergonomics Developers without keys shouldn't be blocked. A common pattern gates the `sign(...)` so it only engages on real release builds (e.g. when keys are present), keeping local `build`/`publishToMavenLocal` fast — the precise conditional/required mechanics are a separate topic, but architecturally this keeps the gate strict on release and lax locally. ## Summary checklist - Sign the publication (full coverage incl. POM). - CI gate: `signMavenPublication` + `gpg --verify` every artifact. - Keys via CI secrets, public key on keyservers. - Stage → Sonatype validation → release. - Signing engaged on release builds, relaxed locally.
- Why sign the publication rather than each file in a CI release?Publication-level signing guarantees every member — including the generated POM — is signed and registered for upload, eliminating the common 'POM not signed' Central rejection.
- How do you verify signatures before the upload reaches Central?Run `signMavenPublication`/`publishToMavenLocal` in CI and `gpg --verify` each `.asc` against its artifact, failing the build on any missing or bad signature; then rely on Sonatype staging validation as a second gate.
- Where should the private key live in this pipeline?Outside the repo — injected as masked CI secrets only on the release job, with the public key published to keyservers so Central and consumers can verify.
saying these in an interview costs you the question
- Committing the private key or passphrase to the repo or build scripts.
- Signing loose files and shipping an unsigned POM to Central.
- Skipping local verification and discovering a bad signature only after the immutable release.