Skip to content

chore(ci): harden container publish before GHCR goes public#127

Closed
abubnalitic-nbl wants to merge 1 commit into
mainfrom
chore/harden-container-publish
Closed

chore(ci): harden container publish before GHCR goes public#127
abubnalitic-nbl wants to merge 1 commit into
mainfrom
chore/harden-container-publish

Conversation

@abubnalitic-nbl

Copy link
Copy Markdown
Contributor

Prepares the published image for public distribution (relates to #83). The docker-publish.yml workflow already publishes multi-arch images to GHCR; this PR adds the supply-chain guarantees we want in place before flipping package visibility to public.

What changes

  • Base image pinned by digest (Dockerfile, both stages). Prevents a compromised python:3.14-alpine3.23 tag from substituting the base under us. Renovate's docker manager will bump digests.
  • Cosign keyless signing of every published image (Sigstore + GitHub OIDC, no key material to manage). Pullers can verify each image came from this repo's Actions.
  • SLSA build provenance attestations via actions/attest-build-provenance, pushed to the registry alongside the image. Cryptographically links any published digest back to the exact workflow run and commit that produced it.
  • Minimum extra permissions on the job: id-token: write, attestations: write.
  • README documents the GHCR pull, strongly recommends pinning to a version tag over latest, and shows the cosign verify command.

Verify

After merge + next main push, pulling + verifying an image should work:

cosign verify \
  --certificate-identity-regexp '^https://github.com/netboxlabs/netbox-mcp-server/' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  ghcr.io/netboxlabs/netbox-mcp-server:<tag>

Out of scope (intentionally)

  • Changing latest to track only tagged releases instead of every push to main. The current behavior is unchanged; the README now makes it explicit. Happy to follow up if the team wants stricter semantics.
  • Flipping package visibility to public — that's an org-admin action and will be requested separately once this lands.

Test plan

  • CI workflows (test.yml, container-scan.yaml, docker-publish.yml PR run) pass on this branch
  • After merge, next push to main produces a signed image with a provenance attestation at ghcr.io/netboxlabs/netbox-mcp-server
  • cosign verify against a post-merge tag returns success

🤖 Generated with Claude Code

Prepares the published image for public distribution (issue #83).

- Pin python:3.14-alpine3.23 base image by digest in both Dockerfile
  stages so a compromised upstream tag cannot substitute the base.
  Renovate's docker manager will bump digests.
- Sign published images with cosign keyless (Sigstore + GitHub OIDC).
  No long-lived keys; signatures are tied to this repo's workflow.
- Emit SLSA build provenance attestations via
  actions/attest-build-provenance, pushed to the registry alongside
  each image.
- Grant the minimum additional permissions required (id-token,
  attestations).
- README: document GHCR pulls, version pinning guidance, and the
  cosign verify command.

Relates to #83

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@github-actions

Copy link
Copy Markdown
Contributor

Vulnerability Scan: Passed

Image: netbox-mcp-server:scan

Source Library CVE Severity Installed Fixed Title
Python Authlib GHSA-jj8c-mmj3-mmgv 🟡 MEDIUM 1.6.9 1.6.11 Authlib: Cross-site request forging when using cache
Python Pygments CVE-2026-4539 ⚪ LOW 2.19.2 2.20.0 pygments: Pygments: Denial of Service via inefficient regular expression process
Python cryptography CVE-2026-39892 🟡 MEDIUM 46.0.6 46.0.7 cryptography: Cryptography: Buffer overflow via non-contiguous buffer in API
Python python-dotenv CVE-2026-28684 🟡 MEDIUM 1.2.1 1.2.2 python-dotenv: python-dotenv: Arbitrary file overwrite via symbolic link followi
Python python-multipart CVE-2026-40347 🟡 MEDIUM 0.0.22 0.0.26 python-multipart: Python-Multipart: Denial of Service via crafted multipart/form

Commit: 0895161

@marc-barry

Copy link
Copy Markdown
Contributor

Heads up — per the discussion on ITH-1687, we're standardising public images on Docker Hub rather than flipping the GHCR package public. #132 retargets the publish workflow at docker.io/netboxlabs/netbox-mcp-server.

Your hardening here is still valuable and we'd like to land it before the first public Docker Hub release. Once #132 merges, could you rebase this branch and update the cosign / SLSA provenance / README references from ghcr.io to docker.io/netboxlabs/netbox-mcp-server? The Dockerfile digest pin doesn't need any changes.

@abubnalitic-nbl

Copy link
Copy Markdown
Contributor Author

Closing in favour of a fresh PR rebased on top of #132 (Docker Hub publishing). The Dockerfile digest pin carries over unchanged; the cosign signing + SLSA attestation steps need to be restructured to fit the new matrix-build / manifest-merge workflow, so a clean diff against main is more readable than rebasing this branch.

abubnalitic-nbl added a commit that referenced this pull request May 8, 2026
Adds supply-chain hardening to the Docker Hub publish pipeline introduced
in #132:

- Pin the python:3.14-alpine3.23 base image by digest in both Dockerfile
  stages so a compromised tag cannot substitute the base.
- Sign the published multi-arch manifest with cosign keyless (Sigstore +
  GitHub OIDC) so pullers can verify each image came from this repo's
  Actions.
- Generate SLSA build provenance attestations via
  actions/attest-build-provenance, pushed to the registry alongside the
  manifest, linking any published digest back to the workflow run that
  produced it.
- Document `cosign verify` in the README's Docker Hub section.

Carries over the intent of #127, restructured to fit the matrix-build /
manifest-merge workflow merged in #132. Signing happens once on the
manifest-list digest in the merge job; all aliased tags (latest, X.Y.Z,
X.Y, X) resolve to the same digest, so a single signature covers them.

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants