Skip to content

Document how to verify image signatures in an air-gapped environment #526

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 0 additions & 26 deletions modules/tutorials/examples/verify-signatures/kyverno-policy.yaml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: policy.sigstore.dev/v1alpha1
kind: ClusterImagePolicy
metadata:
name: stackable-image-is-signed-by-github-actions
spec:
images:
- glob: "**.stackable.tech/**"
authorities:
- keyless:
url: https://fulcio.sigstore.dev
identities:
- issuer: https://token.actions.githubusercontent.com
subjectRegExp: "https://github.com/stackabletech/.+/.github/workflows/build.yml@refs.+"
ctlog:
url: https://rekor.sigstore.dev
Original file line number Diff line number Diff line change
@@ -1,35 +1,58 @@
= Enabling verification of image signatures

Image signing is a security measure that helps ensure the authenticity and integrity of container images. Starting with SDP 23.7, all our images are signed https://docs.sigstore.dev/cosign/openid_signing/["keyless"]. By verifying these signatures, cluster administrators can ensure that the images pulled from Stackable's container registry are authentic and have not been tampered with.
Since Kubernetes does not have native support for verifying image signatures yet, we will use a tool called https://kyverno.io/[Kyverno] in this tutorial.
Image signing is a security measure that helps ensure the authenticity and integrity of container images. Starting with SDP 23.11, all our images are signed https://docs.sigstore.dev/cosign/openid_signing/["keyless"]. By verifying these signatures, cluster administrators can ensure that the images pulled from Stackable's container registry are authentic and have not been tampered with.
Since Kubernetes does not have native support for verifying image signatures yet, we will use Sigstore's https://docs.sigstore.dev/policy-controller/overview/[Policy Controller] in this tutorial.

IMPORTANT: Releases prior to SDP 23.7 do not have signed images. If you are using an older release and enforce image signature verification, Pods with Stackable images will be prevented from starting.
IMPORTANT: Releases prior to SDP 23.11 do not have signed images. If you are using an older release and enforce image signature verification, Pods with Stackable images will be prevented from starting.

== Installing Kyverno
Kyverno can be easily installed via Helm:
== Installing the Policy Controller
The Policy Controller can be easily installed via Helm:

[source,bash]
----
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo add sigstore https://sigstore.github.io/helm-charts
helm repo update
helm install kyverno kyverno/kyverno -n kyverno --create-namespace
helm install policy-controller sigstore/policy-controller
----

Other installation methods and options to run Kyverno in a highly-available fashion are described in the https://kyverno.io/docs/installation/methods/[Kyverno documentation].
The default settings might not be appropriate for your environment, please refer to the https://artifacthub.io/packages/helm/sigstore/policy-controller[configurable values for the Helm chart] for more information.


== Creating a policy to verify image signatures

Now that Kyverno is installed, we can create a policy that verifies that all images provided by Stackable are signed by Stackable's CI pipeline (Github Actions):
Now that the Policy Controller is installed, we can create a policy that verifies that all images provided by Stackable are signed by Stackable's CI pipeline (Github Actions):

[source,yaml]
include::example$verify-signatures/kyverno-policy.yaml[]
include::example$verify-signatures/stackable-image-policy.yaml[]

Apply this policy to the cluster by saving it as `stackable-image-policy.yaml` and running:
[source,bash]
----
kubectl apply -f stackable-image-policy.yaml
----

Apply this policy to the cluster by saving it as `kyverno-policy.yaml` and running:
If you used the default values for the Helm chart, policies will only be applied to namespaces labeled with `policy.sigstore.dev/include: "true"`.
Add a label for the namespace where you deployed SDP:
[source,bash]
----
kubectl apply -f kyverno-policy.yaml
kubectl label namespace stackable policy.sigstore.dev/include=true
----

The policy will be applied to all namespaces in the cluster. It checks all newly created Pods that run any image matching the expression `docker.stackable.tech/+++*+++` (all images provided by Stackable) and ensures that these images have been signed by a Stackable Github Action (`https://github.com/stackabletech/+++*+++/.github/workflows/build.yml@refs/+++*+++`). If the signature of an image is invalid or missing, the policy will deny the pod creation.
For a more detailed explanation of the policy options, please refer to the https://kyverno.io/docs/writing-policies/verify-images/sigstore/#keyless-signing-and-verification[Kyverno documentation].
If the `subject` field in the policy is changed to something like `https://github.com/test/+++*+++`, the policy will deny the creation of pods with Stackable images because the signature is no longer valid.
The Policy Controller checks all newly created Pods in this namespace that run any image matching `+++**+++.stackable.tech/+++**+++` (this matches images provided by Stackable) and ensures that these images have been signed by a Stackable Github Action. If the signature of an image is invalid or missing, the policy will deny the pod creation.
For a more detailed explanation of the policy options, please refer to the https://docs.sigstore.dev/policy-controller/overview/#configuring-image-patterns[Sigstore documentation].
If the `subjectRegExp` field in the policy is changed to something like `https://github.com/test/.+`, the policy will deny the creation of pods with Stackable images because the identity of the subject that signed the image (a Stackable Github Action Workflow) will no longer match the expression specified in the policy.

== Verifying image signatures in an air-gapped environment
As mentioned before, our images and Helm charts for SDP are signed keyless. Keyless signing is more complex than "classic" signing with a private and public key, but brings several https://www.chainguard.dev/unchained/benefits-of-keyless-software-signing[benefits]. It's also in line with Kubernetes, https://kubernetes.io/docs/tasks/administer-cluster/verify-signed-artifacts/[which uses keyless signing as well].

Describing the whole flow with all the components is out of scope for this documentation, so we will try to provide a summary of the most important parts instead: +
To verify that an image has been signed by Stackable, customers check that the image has a valid signature and that this signature was created by Stackable's CI (Github Actions). More specifically, they check that the identity of the signer is one of Stackable's Github Actions workflows and that this identity has been confirmed by a trusted authority (Github in that case). The role of the Sigstore project https://github.com/sigstore/fulcio[Fulcio] is to issue a certificate for exactly that: +
"This Fulcio instance confirms that this signature was created by `https://github.com/stackabletech/docker-images/.github/workflows/release.yml@refs/tags/23.11.0` and `https://token.actions.githubusercontent.com` confirmed that identity".

By default, the public Fulcio instance hosted by Sigstore is used for this, which is what we do at Stackable as well.

That means customers wanting to verify these image signatures need to trust the Fulcio instance, which issues the certificates that attest the identity of the signer. The root of trust for Sigstore components like the public Fulcio instance is distributed by a framework called https://docs.sigstore.dev/signing/overview/#root-of-trust[The Update Framework (TUF)]. Thankfully, the whole initialization of the root of trust via TUF is handled by the Policy Controller.

The problem for air-gapped environments is that expiration of keys is built into TUF. That means, to verify image signatures continuously, the Policy Controller needs an up-to-date version of the root of trust. In a environment with internet access, it can just connect to Sigstore's TUF repository and get the latest contents. In an air-gapped environment, this is not possible. It is possible, however, to specify a TUF mirror that is reachable from the air-gapped environment, as explained https://docs.sigstore.dev/policy-controller/overview/#configuring-trustroot-for-custom-tuf-root[here]. This mirror could for example serve the contents of https://tuf-repo-cdn.sigstore.dev via HTTPS. Another way is to provide a base64 encoded, gzipped tarball of the TUF repository, as explained https://docs.sigstore.dev/policy-controller/overview/#configuring-trustroot-for-custom-tuf-repository[here]. Remember that in both cases the contents of the TUF repository need to be updated regularly. The Sigstore TUF repository is hosted at https://tuf-repo-cdn.sigstore.dev/ and the contents are also available https://github.com/sigstore/root-signing/tree/main/repository/repository[on github].

You can then refer to the newly created `TrustRoot` (which is configured to use the TUF mirror) in the policy via the `trustRootRef` attribute, as shown https://docs.sigstore.dev/policy-controller/overview/#configuring-verification-against-different-sigstore-instances[in the Policy Controller's documentation].