diff --git a/rfcs/0000-slsa-attestation.md b/rfcs/0000-slsa-attestation.md new file mode 100644 index 000000000..a6ff9ceaf --- /dev/null +++ b/rfcs/0000-slsa-attestation.md @@ -0,0 +1,365 @@ +## Problem +The [Supply Chain Levels for Software Artifacts (SLSA)][slsa-dev] is a framework that aims to attest to the integrity of +the build system that produced an artifact. SLSA is under the Open Source Security Foundation (OpenSSF), meaning it's a +sibling project of Sigstore (cosign, rekor, fulcio) and ultimately a Linux Foundation project. + +SLSA is divided into a [series of levels][slsa-levels] that provide increasing assurances about the security of the +build system. It's currently divided into 4 levels (0 to 3 where 0 means no SLSA) although there is work on introducing +a fifth. + +> Pedantics: technically SLSA is divided into tracks which is then further divided into levels, but since there's +> (currently) only one track, the rest of this document will implicitly refer to the Build track. + +A quick rundown on the promises of each level: + +0. No SLSA +1. Provenance regarding the build system is generated and accessible +2. Build runs on dedicated infrastructure and provenance is signed via key only available to infrastructure +3. Builds are isolated from other builds and the signing key can only be accessed by the build platform (inaccessible to + user defined build steps) + +SLSA v1 defines a schema for build systems to provide [provenance][slsa-provenance] about a snapshot of the system that +is used to build an artifact. This schema records the user-defined inputs (e.g source revision, build env vars), +system-internal inputs (e.g image digests), build dependencies (e.g SBoMs from the build), and metadata about the build +system itself (e.g kpack version, pod name, node name) + +[slsa-dev]: https://slsa.dev/ +[slsa-provenance]: https://slsa.dev/spec/v1.0/provenance +[slsa-levels]: https://slsa.dev/spec/v1.0/levels + +## Outcome + +Define how kpack should generate SLSA compliant attestations during the build process, what this attestations should +look like, and how this provenance will be stored and surfaced to the user. + +## Actions to take + +A new flag(`--generate-slsa-attestation`) and env var (`GENERATE_SLSA_ATTESTATION`) will be introduced in the controller +deployment. If this flag is set to `"true"`, all images built by kpack in the cluster will have an attestation attached +to it. + +### Attestation format + +The proposed format for SLSA provenance generated by kpack is the [SLSA recommended suite][slsa-recommended-suite]: + +#### Provenance schema + +Build metadata and information is stored using the [SLSA provenance schema][slsa-schema]: + +- `buildDefinition`: + - `buildType`: Must be `https://github.com/buildpacks-community/kpack/blob/${KPACK_VERSION}/docs/slsa.md`. According + to SLSA, this should resolve to a human readable spec that documents the build definition and run details. The link + should point to the version of the doc that has been compiled into the controller via `cmd/version.go`. + - `externalParameters`: This should be the JSON serialized contents of the `.spec` field from the Build resource. + - `internalParameters`: + - All the image values: `buildInitImage`, `buildInitWindowsImage`, `buildWaiterImage`, `completionImage`, + `completionWindowsImage`, `lifecycleImage`, and `rebaseImage` + - All the controller flags: `enablePriorityClasses`, `maximumPlatformApiVersion`, `injectedSidecarSupport`, + and `insecureSshTrustUnknownHosts` + - `resolvedDependencies`: + - The source URI and the sha + - The builder image, its digest, and the `"id": "version"` of all the buildpacks as annotations +- `runDetails`: + - `builder`: + - `id`: Either `https://kpack.io/signed-build` or `https://kpack.io/unsigned-build` depending on if a signing secret + is found. + - `version`: This should contain versions of kpack and its components + - `kpack`: version of the controller as compiled into `cmd/version.go` + - `lifecycle`: version of the lifecycle as read from the lifecycle image metadata + - `builderDependencies`: + - Namespace where the Build occured in. + - Build (name and resource version only) resource that generated the build pod. + - ServiceAccount (name and resource version only) associated with the Build. + - Secrets (name and resource version only) used by the Build. + - `metadata` + - `invocationID`: Follows the format `https://kpack.io///@` + - `startedOn`: Time the build pod was created in RFC3339 format. + - `finishedOn`: Time the build pod (or completion container) finished in RFC3339 format. + - `byproducts`: Empty array for now, but may contain things like link to SBoMs in the future. + +Example: +```json +{ + "buildDefinition": { + "buildType": "https://github.com/buildpacks-community/kpack/blob/v0.12.2/docs/slsa.md", + "externalParameters": { + "builder": { + "image": "my.registry.com/my/builder@sha256:de9964b5f501a77b8cf549659f81e29dbac4f8df7f1890ddc2b568dbed428b73" + }, + "cache": { + "volume": { + "persistentVolumeClaimName": "test-cache" + } + }, + "env": [ + { + "name": "BP_GO_TARGETS", + "value": "./cmd/controller" + } + ], + "resources": {}, + "runImage": { + "image": "index.docker.io/paketobuildpacks/run-jammy-base@sha256:e817bca35911221677b678bf8bf29a18c17ce867b29bd9d0b0c3342c063854e5" + }, + "serviceAccountName": "default", + "source": { + "git": { + "revision": "82cb521d636b282340378d80a6307a08e3d4a4c4", + "url": "https://github.com/buildpacks-community/kpack" + } + }, + "tags": [ + "my.registry.com/my/app", + "my.registry.com/my/app:b1.20231108.210915" + ] + }, + "internalParameters": { + "builderImage": "my.registry.com/my/builder@sha256:de9964b5f501a77b8cf549659f81e29dbac4f8df7f1890ddc2b568dbed428b73", + "buildInitImage": "gcr.io/cf-build-service-public/kpack/build-init@sha256:1791a66ce28ab4ac707185a088b42e990088e0d824f8688056c60fee511109c6", + "buildInitWindowsImage": "gcr.io/cf-build-service-public/kpack/build-init-windows@sha256:6b0093996e5db36580843ca394c249acc19883375c7d7b925b2bed5d8a64bd8a", + "buildWaiterImage": "gcr.io/cf-build-service-public/kpack/build-waiter@sha256:e80c75eff83fc530701e82f5fa6aecff8f09dfc8f64ae7b0c155bd42504d5437", + "completionImage": "gcr.io/cf-build-service-public/kpack/completion@sha256:082433ee02227d8cb77380fa24e1ee09a878d822232b74f822f6cbd6fc059876", + "completionWindowsImage": "gcr.io/cf-build-service-public/kpack/completion-windows@sha256:15171135840442d4e9a8c9c64e97efefc698584ee6299d2de6a8fcafe7ade69e", + "lifecycleImage": "gcr.io/cf-build-service-public/kpack/lifecycle@sha256:0b1cd35012f7152053c42e0d6835cbb5b7c9c24207a0627f556bd931e678f8d7", + "rebaseImage": "gcr.io/cf-build-service-public/kpack/rebase@sha256:c0b3bbcf55750ddda69c48b1b0554470536af05556a0d639f8eb65e4a6265745", + "enablePriorityClasses": "false", + "maximumPlatformApiVersion": "", + "injectedSidecarSupport": "false", + "insecureSshTrustUnknownHosts": "true" + }, + "resolvedDependencies": [ + { + "uri": "git+https://github.com/buildpacks-community/kpack@refs/heads/main", + "digest": { + "sha1": "759bd290a5310bef5069385b649883e586024dae" + } + }, + { + "uri": "paketobuildpacks/builder-jammy-tiny", + "digest": { + "sha256": "78d74bd1c27f633341045f1c5f7f33209f6af0a5dc5700fdfd71200b5b5a0b9a" + }, + "annotations": { + "paketo-buildpacks/go-build": "2.1.0", + "paketo-buildpacks/go-mod-vendor": "1.0.25", + "paketo-buildpacks/node-engine": "3.0.1", + "paketo-buildpacks/go-dist": "2.4.2", + "paketo-buildpacks/yarn": "1.2.0", + "paketo-buildpacks/liberty": "3.8.9", + "paketo-buildpacks/bellsoft-liberica": "10.4.2", + "paketo-buildpacks/spring-boot": "5.27.5", + "paketo-buildpacks/google-stackdriver": "8.0.3", + "paketo-buildpacks/ca-certificates": "3.6.6", + "paketo-buildpacks/ca-certificates": "3.6.5", + "paketo-buildpacks/apache-tomee": "1.7.7", + "paketo-buildpacks/apache-tomcat": "7.13.15", + "paketo-buildpacks/azure-application-insights": "5.17.1", + "paketo-buildpacks/procfile": "5.6.7", + "paketo-buildpacks/procfile": "5.6.6", + "paketo-buildpacks/java-memory-assistant": "1.4.8", + "paketo-buildpacks/datadog": "4.3.0", + "paketo-buildpacks/encrypt-at-rest": "4.5.9", + "paketo-buildpacks/maven": "6.15.11", + "paketo-buildpacks/gradle": "7.6.1", + "paketo-buildpacks/sbt": "6.12.9", + "paketo-buildpacks/clojure-tools": "2.8.12", + "paketo-buildpacks/leiningen": "4.6.8", + "paketo-buildpacks/watchexec": "2.8.6", + "paketo-buildpacks/watchexec": "2.8.5", + "paketo-buildpacks/syft": "1.39.0", + "paketo-buildpacks/jattach": "1.4.8", + "paketo-buildpacks/executable-jar": "6.8.2", + "paketo-buildpacks/git": "1.0.7", + "paketo-buildpacks/dist-zip": "5.6.7", + "paketo-buildpacks/environment-variables": "4.5.6", + "paketo-buildpacks/environment-variables": "4.5.5", + "paketo-buildpacks/image-labels": "4.5.5", + "paketo-buildpacks/image-labels": "4.5.4", + "paketo-buildpacks/java": "10.3.1", + "paketo-buildpacks/go": "4.6.0" + } + } + ] + }, + "runDetails": { + "builder": { + "id": "https://kpack.io/signed-build", + "version": { + "kpack": "0.12.2" + "lifecycle": "0.17.0" + }, + "builderDependencies": [ + { + "name": "Namespace", + "content": "{\"name\":\"default\",\"resourceVersion\":\"195\"}" + }, + { + "name": "Build", + "content": "{\"name\":\"test-build-1\",\"resourceVersion\":\"5\"}" + }, + { + "name": "ServiceAccount", + "content": "{\"name\":\"default\",\"resourceVersion\":\"269\"}" + }, + { + "name": "Secrets", + "content": "[{\"name\":\"my-registry-creds\",\"resourceVersion\":\"53\"},{\"name\":\"my-git-creds\"}]" + }, + ] + }, + "metadata": { + "invocationID": "https://kpack.io/default/test-build-1/test-build-1-build-pod@gke-test-default-pool-0582cba3-2dp4", + "startedOn": "2023-11-09T16:47:17.215551-05:00", + "finishedOn": "2023-11-09T16:54:17.215551-05:00" + }, + "byproducts": [] + } +} +``` + +#### Statement +The provenance is wrapped in an [in-toto statement][in-toto-statement] and formated as JSON. The `subject.name` and +`subject.digest` should be set to the built image and digest. +```json +{ + "_type": "https://in-toto.io/Statement/v0.1", + "predicateType": "https://slsa.dev/provenance/v1", + "predicate": + "subject": [ + { + "name": "gcr.io/cf-build-service-public/kpack/controller", + "digest": { + "sha256": "sha256:5e743affae0df727dce82df3f4fe5256cd66f41d919b3cd2aa91605eea2371f8" + } + } + ], +} +``` +#### Envelope + +The statement is stored in a [DSSE envelope][dsse-envelope], the signatures field may be empty. +```json +{ + "payloadType": "application/vnd.in-toto+json", + "payload": + "signatures": [ + { + "keyid": "default/my-cosign-secret", + "sig": "MEUCIQD/aWTUPVTRhWoGv1jYAvrnmYRJQmHVdy4NrmmLIxUaaAIgSumlFxSX9FG/wfbpYUAQJtE1/vzfVtRXmlr2LwpU670=" + } + ] +} +``` + +#### Storage + +The attestation is attached to the target image tag (as well as any `additionalTags`) using the +[cosign attestation spec][cosign-attestation], where the envelope becomes a single layer. The attestation image also +follows the [cosign tag-based discovery][cosign-tag] except that the suffix is `.att` instead of `.sig`. + +For example, +`gcr.io/cf-build-service-public/kpack/controller@sha256:5e743affae0df727dce82df3f4fe5256cd66f41d919b3cd2aa91605eea2371f8` +will have its corresponding attestation saved to +`gcr.io/cf-build-service-public/kpack/controller:sha256-5e743affae0df727dce82df3f4fe5256cd66f41d919b3cd2aa91605eea2371f8.att` + +[slsa-recommended-suite]: https://slsa.dev/attestation-model#recommended-suite +[slsa-schema]: https://slsa.dev/spec/v1.0/provenance#schema +[in-toto-statement]: https://github.com/in-toto/attestation/blob/main/spec/v1/statement.md +[dsse-envelope]: https://github.com/secure-systems-lab/dsse/blob/v1.0.0/envelope.md +[cosign-attestation]: https://github.com/sigstore/cosign/blob/main/specs/ATTESTATION_SPEC.md +[cosign-tag]: https://github.com/sigstore/cosign/blob/main/specs/SIGNATURE_SPEC.md#tag-based-discovery + +### Key types + +At minimum, the implementation should support the following key types: +- [cosign key][cosign-key-format]. This is to ensure interop with `cosign verify-attestation`. +- ECDSA private keys. This is due to it being the recommended SLSA suite. +- RSA private keys. This is due to it being an extreamly popular key type. + +Keyless signing via fulcio + rekor will not be part of the initial implementation but may be considered in the future. + +[cosign-key-format]: https://github.com/sigstore/cosign/blob/main/doc/cosign_generate-key-pair.md + +### Adding provenance generation and signing to build process + +Upon a successful completion of the build pod, the build reconciler should look through all the secrets attached to the +Build's ServiceAccount, as well as the secrets attached to the kpack-controller's ServiceAccount. Every key that has the +`type: kubernetes.io/ssh-auth` [built-in secret type][built-in-secret-types], or has the `cosign.key`, `cosign.pub` +fields will be used as signing keys. + +Once the provenance has been stamped out according to the template above, these keys will be used to sign the DSSE +envelope. The signatures may be in any arbitrary order, each signature should have the `keyid` set to the +`/` that was used. + +Any attestation errors (signing errors, push errors, registry errors, etc) should be surfaced to the Build status via a +new condition. + +[built-in-secret-types]: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types + +### Documentation needed + +A core part of SLSA is proper documentation detailing the expected provenance and the security levels a build system +offers. kpack's SLSA documentation must be in `docs/slsa.md` and contain a breakdown of all the fields in the provenance +similar to the [provenance section](#provenance-schema). + +Example of documentation for [GitHub Actions][gha-doc] and [Google Cloud Build][gcb-doc]. + +[gha-doc]: https://github.com/slsa-framework/github-actions-buildtypes/tree/main/workflow/v1 +[gcb-doc]: https://github.com/slsa-framework/gcb-buildtypes/tree/main/triggered-build/v1 + +## Complexity + +One major implementation detail is if the attestation generation and/or signing should occur in the build pod or in the +controller. If it's part of the build pod, it would occur in the completion step after the build metadata is read. If +it's part of the controller, it would occur after the build pod has completed. + + +### Attestation in controller +Pros: +- Can configure a cluster-wide signing secret +- Easier access to `internalParameters` (i.e. passing all the internal parameters as flags to the completion step is + ugly) + +Cons: +- Writing to registry will block reconcilation of other builds +- Failing to sign or push attestation can only be surfaced on the build status, not in the logs + + +#### Attestation in build pod +Pros: +- No additional scaling problems +- Easier access to SBoM if we expand on this in the future +- May tap into future improvements by the CNB lifecycle +- Any errors can be written to the build log, which is the first place users will look + +Cons: +- Much harder to support a cluster-wider signing secret (since the private key might need to be shared across + namespaces) +- Theoretically a cluster with injected sidecars can intercept and change the config (and hence attestation) of the + build pod + +## Prior Art + +- Docker: https://docs.docker.com/build/attestations/slsa-definitions/ +- Tekton: https://tekton.dev/docs/concepts/supply-chain-security/#how-does-tekton-chains-work +- GitHub Actions: https://github.blog/2022-04-07-slsa-3-compliance-with-github-actions/ + +## Alternatives + +There's really no alternative, SLSA is designed as a way for the build platform to attest to the integrity of the build +process and the system itself. + +## Risks + +- SLSA is relatively early in its life, while it's part of the Open Source Security Foundation (OpenSSF), industry + adoption is still getting started. + +## Additional considerations and open questions +- Should the signing key secrets require an additional annotation instead of just matching on the type/field? This would + bring more inline with the [Cluste wide cosign RFC][cluster-wide-cosign-rfc] +- Currently there's no tools on the market that can actually verify generic key-based attestations. + [slsa-verifier][slsa-verifier] uses keyless signing and ony supports GitHub Actions and Google Cloud Build, + `cosign` does key based verification, but only with its own key type. + +[cluster-wide-cosign-rfc]: https://github.com/samj1912/kpack/blob/cluster-signing/rfcs/0000-cluster-signing.md#key-generation-and-storage +[slsa-verifier]: https://github.com/slsa-framework/slsa-verifier