Skip to content

GHCR Cosign Kubernetes E2E

CYPT71 edited this page Aug 21, 2026 · 2 revisions

GitHub → GHCR → Cosign → Kubernetes Admission

The executable demonstration is ci-supply-chain-e2e.yml. It runs on main and manually, and uploads ghcr-cosign-admission-evidence for 30 days.

What the demonstration proves

flowchart LR
  A[Reviewed GitHub SHA] --> B[Deterministic OCI layout]
  B --> C[Independent verification]
  C --> D[GHCR immutable digest]
  D --> E[GitHub OIDC]
  E --> F[Cosign keyless signature]
  F --> G[Sigstore policy-controller]
  G --> H[Signed digest admitted]
  G --> I[Unsigned tag denied]
Loading
  1. GitHub builds the example service and layout.
  2. The independent verifier checks every descriptor and layer.
  3. Docker's consumer stage produces a runnable image and pushes an ephemeral commit tag to `ghcr.io/cypt71/platform-factory.
  4. The workflow records the registry digest and never signs the mutable tag.
  5. Cosign obtains a short-lived Fulcio certificate from GitHub's OIDC token, signs the digest, and verifies issuer plus workflow identity.
  6. A disposable Kind cluster installs Sigstore policy-controller.
  7. ClusterImagePolicy trusts only this repository's E2E workflow identity.
  8. Kubernetes admits the signed digest and rejects the unsigned tag.

Evidence and expected assertions

File Meaning
image-reference.txt exact ghcr.io/...@sha256:... admitted
e2e-layout-validation.txt producer-independent OCI checks passed
e2e-cosign-verification.json certificate, issuer, identity, signature
cluster-image-policy.yaml requested trust policy
installed-policy.yaml policy observed from the API server
admitted-pod.yaml signed digest passed admission
unsigned-rejection.txt same repository via unsigned tag was denied
kubernetes-version.yaml test environment identity

A green workflow without these files is not sufficient; artifact upload uses if: always() so failed runs preserve diagnostic evidence.

Production adaptation

Do not copy the demo's ephemeral tag strategy. Production should:

  • publish only from a protected environment after required reviews;
  • deploy semantic-release digests, never tags;
  • narrow the subject to the release workflow and protected ref;
  • pin and review the admission-controller chart in an internal mirror;
  • set webhook failure policy to Fail, monitor webhook availability, and define a documented emergency process that never silently disables policy;
  • enforce the policy in every production namespace and prevent tenants from modifying namespace labels, policies, or webhook configuration;
  • retain signature, SBOM, provenance, layout checksum, and deployment evidence for the required audit period.

Example production identity:

identities:
  - issuer: https://token.actions.githubusercontent.com
    subjectRegExp: "^https://github.com/CYPT71/platform-factory/.github/workflows/ci-release.yml@refs/tags/v[0-9]+\\.[0-9]+\\.[0-9]+$"

Failure interpretation

  • Push failure: check Actions package permissions and GHCR visibility.
  • Cosign identity failure: compare the certificate subject with the exact workflow/ref; do not broaden the regex merely to make it pass.
  • Webhook timeout: treat as an admission-service outage.
  • Signed image denied: inspect ClusterImagePolicy status and controller logs, then re-run cosign verify against the same digest.
  • Unsigned image admitted: critical policy bypass; stop deployment and verify namespace selection, failure policy, RBAC, and webhook configuration.

Clone this wiki locally