Skip to content

Commit

Permalink
introduce rfc for slsa attestation
Browse files Browse the repository at this point in the history
Signed-off-by: Bohan Chen <[email protected]>
  • Loading branch information
chenbh committed Nov 13, 2023
1 parent 759bd29 commit de95acb
Showing 1 changed file with 361 additions and 0 deletions.
361 changes: 361 additions & 0 deletions rfcs/0000-slsa-attestation.md
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

0 comments on commit de95acb

Please sign in to comment.