Skip to content
 
 

Repository files navigation

Keycloak + Sigstore: identity-bound signing demo

Built for the KeycloakCon Japan talk "Binding human identity to artifact signatures."

What this demonstrates

A container image gets signed with a certificate that is cryptographically bound to a real Keycloak identity — not a GitHub Actions issuer, not Google, not a static keypair sitting in a secrets manager. Your own IdP is the root of trust for "who signed this." The signed app deploys and shows that identity live on its own page — signer, issuer, sign time, pod name — so the payoff is visible, not just a terminal message.

Flow:

Developer --login--> Keycloak --ID token--> Fulcio --short-lived cert--> Cosign (signs image)
                                                                                |
                                                                                v
                                                                        ttl.sh (image + signature)
                                                                                |
                                                                                v
                                                            cosign verify (checks issuer = Keycloak)
                                                                                |
                                                                                v
                                                              App deploys, shows signer identity live

This is proven working end-to-end.

The live signer-identity page

app/templates/index.html.template isn't a static page — nginx's built-in envsubst templating renders it at container start using real env vars set by 05-deploy-app.sh (SIGNER_IDENTITY, SIGNER_ISSUER, SIGNED_TIME, and POD_NAME via the Kubernetes downward API). What's on screen when you open the app is the actual identity that signed the image, not a hardcoded placeholder — worth calling out on camera.

Why Fulcio only — no Rekor, CTLog, or Trillian/MySQL

The talk's actual point is the Keycloak → Fulcio identity binding. Rekor (transparency log) and CTLog (certificate transparency) both need a Trillian + MySQL backend, and none of them are required to demonstrate that binding — they add a public audit trail, which is valuable in production but orthogonal to what this demo needs to prove. Dropping them entirely keeps the setup light and fast.

Signing uses --new-bundle-format with --signing-config (Fulcio only, no Rekor entry) and --trusted-root (Fulcio's current root cert, fetched fresh every run — see below for why). Verification runs offline with --insecure-ignore-tlog and --insecure-ignore-sct — both flags exist specifically for setups like this one that intentionally don't run a transparency log.

Why Fulcio's root cert is fetched fresh every run, never cached

Fulcio's fileca mode generates a brand-new, ephemeral self-signed root CA every time its pod restarts — it is not persisted. Caching a copy from setup time and reusing it later breaks the moment Fulcio restarts for any reason. 03-build-sign.sh fetches it live from Fulcio's own /api/v1/rootCert endpoint immediately before every sign, so it's always current.

Why the official Keycloak image, not the Bitnami chart

The Bitnami Helm chart for Keycloak pulls in a Postgres subchart pinned to a specific versioned image tag. Bitnami restructured its container catalog in 2025 — most versioned tags were pulled from the free docker.io/bitnami repo, so charts pinned to an old tag now fail with ImagePullBackOff / "not found." This demo runs Keycloak's own official image (quay.io/keycloak/keycloak) in dev mode with its built-in in-memory database instead — no Postgres, no third-party catalog dependency.

Why standalone sigstore/fulcio, not the sigstore/scaffold umbrella chart

The scaffold umbrella chart hardcodes Fulcio's trusted OIDC issuer to a Kubernetes service-account issuer, for its own internal CI/e2e testing — there's no values override to point it at an external IdP like Keycloak. The standalone sigstore/fulcio chart exposes the real config path (config.contents.OIDCIssuers) needed for this.

Why ttl.sh instead of a local registry

ttl.sh is a free, public, ephemeral container registry — no account, no auth, images just expire after the TTL you give them. If your cluster's DNS ever breaks (a kind + Ubuntu + systemd-resolved issue, unrelated to this demo — see CoreDNS's Corefile forward directive if ttl.sh stops resolving), that's a cluster networking problem to fix directly rather than a reason to avoid ttl.sh.

Files

File Purpose
01-setup-cluster.sh kind cluster + Keycloak (official image, dev mode) + Fulcio (standalone chart, trusts Keycloak)
keycloak-realm-sigstore-demo.json Realm export — client, user (developer), mappers, pre-configured for import
fulcio-values.yaml Helm values for the standalone Fulcio chart — this is where Fulcio trusts Keycloak
app/ The demo web app — nginx + a live-templated page showing the real signer identity
03-build-sign.sh Builds the app, pushes both tags to ttl.sh, gets a Keycloak token, signs the :signed tag
04-verify.sh Verifies the signature offline against Fulcio's current root — the demo's strongest moment
05-deploy-app.sh Deploys the signed app with real signing data injected into the live page
06-destroy.sh Tears everything down — kind cluster, temp files
DEMO_SCRIPT.md Recording runbook — talking points, timing, retake notes

Prerequisites

  • kind, kubectl, helm, cosign (v2.x), jq, curl, Docker running locally
  • Internet connection (to pull charts/images and push to ttl.sh)
  • ~10 min lead time before recording to bring the cluster up

Quick start

./01-setup-cluster.sh
./03-build-sign.sh
./04-verify.sh
./05-deploy-app.sh
# open http://localhost:8081 to see the live signer-identity page
echo '127.0.0.1 keycloak.keycloak.svc.cluster.local' | sudo tee -a /etc/hosts  -> Access Keycloak UI on browser
# when you're done:
./06-destroy.sh

About

This repository contains demo for keycloakcon-kubecon japan 2026

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages