-
Notifications
You must be signed in to change notification settings - Fork 165
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Bohan Chen <[email protected]>
- Loading branch information
Showing
1 changed file
with
361 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,361 @@ | ||
## 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/<namespace>/<build_name>/<build_pod_name>@<build_pod_node_name>` | ||
- `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": <provenance from previous step> | ||
"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": <base64 encoded statement base previous step> | ||
"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 | ||
`<namespace>/<secret-name>` 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 | ||
* What could we do instead? | ||
|
||
## Risks | ||
* What makes this proposal risky? | ||
|
||
## 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 |