-
Notifications
You must be signed in to change notification settings - Fork 165
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
Add RFC for cluster-wide image signing via cosign #765
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
# Allow cluster-wide image signing with `cosign` | ||
|
||
## Problem | ||
|
||
The signing solutions in `kpack` are currently all namespace or `Image`/`Build` specific. This means that it is harder | ||
for kpack operators to set image-signing up for all of the users of a `kpack` cluster. Given that these signing solutions | ||
are `namespace` specific, each user needs to set up their signing toolchain on their own which can be quite a bit of an effort. | ||
|
||
In the past we have discussed having the ability to provide cluster-wide image signing that would allow all images generated by a `kpack` cluster to be signed. This supplements the [`cosign RFC`](https://github.com/pivotal/kpack/pull/694). | ||
|
||
But the main blocker to such an implementation has been the architectural issue of signing images without cross-mounting secrets from different namespaces or leaking the cluster-wide signing key to the users. See discussions at this [Github comment thread](https://github.com/pivotal/kpack/pull/694#discussion_r631321627) | ||
|
||
`cosign` however provides an architectural design that would allow us to enable cluster wide image signing without leaking or cross mounting secrets. | ||
|
||
## Outcome | ||
|
||
`kpack` integrates with `cosign` to sign each and every image on a cluster-wide basis (if configured) and it builds and pushs the | ||
signatures to a registry so that users can ensure the chain of custody of a generated artifact. | ||
|
||
This proposal aims to cover specifically the flow of signing an image produced by `kpack Image` resource without verification of base or builder images pulled in the process. | ||
|
||
## Actions to take | ||
|
||
### Enable cluster-wide image signing using `cosign` | ||
|
||
- Implement a flow that generates the `cosign` signature payload for the image on the controller side based on the image digest output from the `Build`, then calculates its signature and pushes it either to the registry specified using the [to the registry specified in the `COSIGN_REPOSITORY` environment variable](#key-generation-and-storage), using the image signing credentials provided to the controller. This flow must happen after the image has been pushed to the registry. | ||
|
||
- If `cosign` fails to sign an image, the build should fail and output an error message in the build log, so the operator can troubleshoot the issue. The error messages should also be added in any other places where error messages are presented. | ||
|
||
- Whenever `kpack` signs an image using `cosign`, it should add these annotations: | ||
- Build number. | ||
- Build time. | ||
|
||
- Signing with `cosign` should not affect any configurations that enable signing using the Notary v1 mechanism or the per-image `cosign` mechanism. These signing mechanisms should be able to coexist. | ||
|
||
### Key generation and storage | ||
|
||
`cosign` can use file-based keys, key management systems or hardware keys to ingest private keys, as well as a keyless flow that requires integration with [Rekor](https://github.com/sigstore/rekor). | ||
|
||
`kpack` should not generate the keys used for signing and verification. The user should pass them in using one of the mechanisms supported by `cosign`. In this RFC, we suggest that the first mechanism implemented be the use of Kubernetes `Secret`s for sending the relevant data into the build pod, by means of attaching secrets to the service account used by `kpack`. These secrets should follow the format generated by [`cosign generate-key-pair -k8s <kpack-controller-namespace>/<name>`](https://github.com/sigstore/cosign/pull/345). | ||
|
||
The service account can have more than one `cosign` secret attached to it. For each `cosign` secret attached, a separate signature should be generated and stored. | ||
|
||
`kpack` will be able to determine that the user wants to sign images using `cosign` by scanning the annotations in each of the secrets. Secrets to be used by `cosign` signing should be annotated with `kpack.io/cosign-credentials`. | ||
Optionally, the user can also annotate the secrets with the following information: | ||
- `kpack.io/cosign.repository`: a separate registry URL to upload the generated signatures, instead of pushing the signatures collocated with the images. Adding this annotation should have the same effect as setting the `COSIGN_REPOSITORY` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is optional where will the signature be written? |
||
variable. The user must provide credentials for `kpack` to authenticate with the specified registry. In the case where this repository is inaccessible, the build should fail and output an error message to allow the operator to troubleshoot. | ||
- `kpack.io/cosign.docker-media-types`: a flag indicating whether legacy media types should be used instead of OCI media types. Adding this annotation should have the same effect as setting the `COSIGN_DOCKER_MEDIA_TYPES` variable. | ||
|
||
Secret format example: | ||
```yaml | ||
--- | ||
apiVersion: v1 | ||
kind: Secret | ||
metadata: | ||
name: cosign-key | ||
annotations: | ||
kpack.io/cosign-credentials: "" | ||
# Optional | ||
kpack.io/cosign.repository: "<repository-url>" | ||
# Optional | ||
kpack.io/cosign.docker-media-types: "1" | ||
type: Opaque | ||
stringData: | ||
cosign.key: <cosign-private-key> | ||
cosign.pub: <cosign-public-key> | ||
cosign.password: <cosign-private-key-passphrase> | ||
``` | ||
|
||
### Generating the signature payload | ||
|
||
In order to generate the signature payload on the controller-side `kpack` will do the following for every build - | ||
|
||
- Use the image digest output from the `Build` from the user-namespace pod. The controller should have the appropriate RBAC permissions to read this and to verify that this is not tampered with. | ||
- Use this digest to create a `cosign` payload on the controller side for each `cosign` secret attached to the `kpack` controller service account. | ||
- Use the appropriate `docker-registry` secrets attached to the kpack controller service account to push this signed payload to an appropriate registry. | ||
- Update the `Build` and `Image` status to reflect the output from pushing the signature. | ||
|
||
This assumes that the `kpack` controller service account is configured appropriately to have the `cosign` and [`docker-registry`](https://github.com/pivotal/kpack/blob/main/docs/secrets.md#docker-registry-secrets) secrets as needed. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How will the controller's service account be configured and specified? |
||
|
||
If the controller service account has no `cosign` secrets, it will not attempt to do any cluster-wide image signing. If the controller has a `cosign` service account secret but doesn't have corresponding `docker-registry` secrets, it should fail with an appropriate status and attach a scrubbed reason the the user's `Image`/`Build` output. | ||
|
||
### Authentication to a container registry | ||
|
||
`cosign` uses the `DefaultKeychain` from [`go-containerregistry`](https://github.com/google/go-containerregistry/blob/main/pkg/authn/README.md#tldr-for-consumers-of-this-package) to authenticate the command-line interface using the machine's Docker CLI | ||
credentials. For `kpack`, it may be possible to use the same authentication mechanism that is in-place for uploading an image. | ||
|
||
## Complexity | ||
|
||
Integration with `cosign` is estimated to be of medium complexity. Since `cosign` is also written in Go, it is possible to import code from the tool directly within `kpack`, which can help make the integration easier. | ||
|
||
## Prior art | ||
|
||
- [Issue](https://github.com/pivotal/kpack/issues/684) filed on May 4th. | ||
- [cosign RFC](https://github.com/pivotal/kpack/pull/694) | ||
- [Use of `cosign` in Kubernetes](https://github.com/kubernetes/release/pull/2016) | ||
for validation of distroless images. | ||
- [Use of `cosign` in Connaisseur](https://github.com/sse-secure-systems/connaisseur/pull/107) | ||
for policy enforcement in container images. | ||
- [Notary v1 implementation pull request](https://github.com/pivotal/kpack/pull/541). | ||
|
||
## Alternatives | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this be a completely separate kubernetes deployment that automatically signs every |
||
|
||
- Use a CI/CD step to sign images using `cosign` instead of implementing it as a part of `kpack`. | ||
|
||
## Risks | ||
|
||
- Notary v2 support might be a requirement in the future. | ||
- Added complexity |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the signing is happening in the controller would it be acceptable to simply report the signing error on the build status?