Skip to content

Signing

Kokumi signs every artifact and attestation it writes with one key, the signing key of the Kitchen. Signatures use the Sigstore bundle format that cosign v3 writes, so the stock cosign CLI verifies them offline. Kokumi itself deploys nothing whose signatures do not verify, and it can require the sources it renders to be signed by their publishers.

What is signed

ArtifactAttestation (predicate type)Written
PreparationSignature (https://sigstore.dev/cosign/sign/v1)When the Preparation is created
PreparationProvenance (https://slsa.dev/provenance/v1)When the Preparation is created
PreparationSealed approvals (https://kokumi.dev/attestations/approval/v1)When an approved Preparation is promoted

Each attestation is an OCI referrer of the artifact: a Sigstore bundle (application/vnd.dev.sigstore.bundle.v0.3+json) holding a DSSE envelope over an in-toto statement. The bundles carry no transparency log entry and no timestamp; they are verified against the public key alone.

A Preparation is Ready only once it is Signed and its provenance is attached. Before the Serving controller deploys a Preparation, it reads the signature, the provenance and, when an approval policy applies, the sealed approvals from the registry and verifies them. Argo CD verifies no OCI signatures, so this is the only check, and Argo CD pulls exactly the digest that was verified. The Serving’s Verified condition reports the result.

Verify an artifact

The public key is published in the Kitchen status, in the Settings page under Signing, and without login at GET /api/v1/signing:

kubectl -n kokumi get kitchen default -o jsonpath='{.status.signing.publicKey}' > kokumi.pub
cosign verify --key kokumi.pub --insecure-ignore-tlog=true <registry>/shop/podinfo-prod@sha256:c3…
cosign verify-attestation --key kokumi.pub --insecure-ignore-tlog=true --type slsaprovenance1 <registry>/shop/podinfo-prod@sha256:c3…

--insecure-ignore-tlog=true tells cosign that the signature is not in a transparency log; the key still has to verify it. Add --allow-http-registry for a registry served over plain HTTP, such as the bundled one.

cosign verify accepts any bundle the key signed, whatever its predicate type. Kokumi’s own checks always verify the predicate type and the subject.

The signing key

The key is an ECDSA P-256 key in the Secret kokumi-signing-key in the kokumi-signing namespace, in the layout cosign’s k8s:// key references use:

KeyContent
cosign.keyThe encrypted private key (PEM)
cosign.passwordIts password. It protects nothing at rest; it exists so that cosign can read the Secret.
cosign.pubThe public key (PEM)

The install manifests create the kokumi-signing namespace and allow the controller to create Secrets in it. The Kitchen controller generates the key when the Secret does not exist and publishes its public half in status.signing. The SigningKeyReady condition of the Kitchen reports whether the key could be loaded:

ReasonMeaning
KeyReadyThe key is loaded.
KeyMissingThe Secret does not exist and could not be generated, for example because the kokumi-signing namespace is missing. Re-apply the install manifests.
KeyInvalidThe Secret cannot be used: not cosign’s layout, a wrong password, not a P-256 key, or cosign.pub does not match.
TrustedKeyInvalidA key in spec.signing.trustedKeys cannot be parsed.

While the key is not ready, nothing is signed and nothing is deployed.

To bring your own key, create the Secret before installing Kokumi, or before the controller first starts:

kubectl create namespace kokumi-signing
cosign generate-key-pair k8s://kokumi-signing/kokumi-signing-key

The key has a namespace of its own, apart from the install namespace and everything else users may be granted Secret access to. Its namespace and name are fixed, so that RBAC and admission can protect it.

Who can read and write the key

Only the controller manager and cluster admins can read or write the key:

  • The signing-key-integrity ValidatingAdmissionPolicy rejects every create, update and delete of the Secret except by the kokumi-controller-manager ServiceAccount and cluster admins. A cluster admin is whoever may use the custom verb manage-signing-key on kitchens: cluster-admin and system:masters can, the Kokumi roles cannot, because they list their verbs. Grant the verb to delegate key rotation:

    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: kokumi-signing-key-manager
      namespace: kokumi
    rules:
    - apiGroups: ["delivery.kokumi.dev"]
      resources: ["kitchens"]
      resourceNames: ["default"]
      verbs: ["manage-signing-key"]

    The holder still needs ordinary RBAC on the Secret. The namespace controller may delete the Secret with the kokumi-signing namespace.

  • No Kokumi role grants read access. Only the controller is granted access to Secrets in kokumi-signing. The built-in admin, editor and viewer roles hold no Secret access, because they are bound cluster-wide, and the server reads Secrets only in the install namespace. See Authorization for granting Pantry credentials without exposing the key.

  • The status-integrity policy lets only the controller write the Kitchen status, which publishes the public key.

Anyone who can read the key can forge signatures that Kokumi trusts. Never grant access to Secrets in kokumi-signing, and treat cluster-wide Secret read access as access to the key.

Rotate the key

  1. In Settings → Signing, choose Trust the current key and save. Or add the current status.signing.publicKey to spec.signing.trustedKeys of the Kitchen yourself.

  2. As a cluster admin, delete the Secret:

    kubectl -n kokumi-signing delete secret kokumi-signing-key
  3. The controller generates a new key and publishes it.

Artifacts signed with the old key keep verifying as long as its public key is trusted. Remove it from the trusted keys once nothing signed with it needs to be deployed any more.

Trusted keys

spec.signing.trustedKeys lists further public keys whose signatures Kokumi trusts for its own artifacts: the key used before a rotation, or the key of another installation whose releases are imported.

apiVersion: delivery.kokumi.dev/v1alpha1
kind: Kitchen
metadata:
  name: default
  namespace: kokumi
spec:
  signing:
    trustedKeys:
    - name: before-2026-10
      publicKey: |
        -----BEGIN PUBLIC KEY-----
        …
        -----END PUBLIC KEY-----
    requireSourceVerification: true

A trusted key that does not parse stops all signing and verification until it is fixed; the Kitchen reports TrustedKeyInvalid. The Settings page validates keys before saving them.

Signed sources

A Product can require its source to be signed by its publisher, by key or keyless against the expected signer identity. See Source verification for the fields. A source signed by a key the Kitchen trusts verifies without any configuration.

Set spec.signing.requireSourceVerification: true on the Kitchen to make verification mandatory: a Product whose source does not verify, through its own verify or by a trusted key, is not ready, and none of its Orders render.

Air-gapped clusters

Key-based verification works offline. Keyless verification needs the Sigstore trusted root:

  • Online, Kokumi fetches the Sigstore public-good trusted root through TUF, keeps it in memory, and refreshes it once a day.

  • Air-gapped, store a trusted root in a Secret under the key trusted_root.json and name it in spec.source.verify.trustedRootSecretRef:

    cosign trusted-root create … > trusted_root.json   # or fetch it while online
    kubectl -n shop create secret generic sigstore-root --from-file=trusted_root.json

Sigstore rotates its keys and log shards over time. Refresh the stored trusted root regularly; Kokumi does not update it.

Last updated on