Skip to content
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

SLSA attestations for the Build resource (aka app image attestations) #1449

Merged
merged 11 commits into from
Jan 17, 2024

Conversation

chenbh
Copy link
Contributor

@chenbh chenbh commented Dec 13, 2023

Implementation of #1374, the feature is marked experimental until the RFC has been merged.

This PR adds SLSA attestations to the Build resource:

  • EXPERIMENTAL_GENERATE_SLSA_ATTESTATION="true" can be set on the controller to enable these behaviours

  • A new status .status.latestAttestationImage will stamp out the immutable digest of the attestation

  • The attestation is also pushed to a mutable image tag based off of the app image's digest (digest my-app-image@sha:1234 will have the attestation stored at tag my-app-image:sha256-1234). This is consistent with how cosign stores their attestations and can be verified with cosign verify-attestation ...

  • Recommend viewing signed images with cosign verify-attestation --type=slsaprovenance1 ..., alternatively unsigned or signed attestations can be viewed with

    crane export <ATTESTATION_IMAGE> - | jq -r '.payload' | base64 -D | jq
    
  • Any secrets attached to the Build's service account, or the kpack controller's service account with the annotation kpack.io/slsa: "" will be used as private keys for signing

  • Signing RSA, ECDSA, ED25519, and Cosign keys are supported, the way to use them is specified in the RFC

Things still needed:

  • E2E tests
  • Docs about how to use/configure it
  • Docs about SLSA level provided by kpack

@codecov-commenter
Copy link

codecov-commenter commented Dec 13, 2023

Codecov Report

Attention: 200 lines in your changes are missing coverage. Please review.

Comparison is base (155c8a7) 67.17% compared to head (2046f40) 67.19%.
Report is 10 commits behind head on main.

Files Patch % Lines
pkg/reconciler/build/build.go 50.00% 39 Missing and 12 partials ⚠️
pkg/slsa/sign.go 57.14% 24 Missing and 12 partials ⚠️
pkg/slsa/attest.go 83.67% 17 Missing and 7 partials ⚠️
pkg/slsa/image.go 51.06% 16 Missing and 7 partials ⚠️
pkg/slsa/pkcs8_signer.go 67.30% 14 Missing and 3 partials ⚠️
pkg/blob/fetch.go 60.52% 10 Missing and 5 partials ⚠️
pkg/cosign/image_signer.go 50.00% 6 Missing and 4 partials ⚠️
pkg/secret/signing_secret.go 83.92% 6 Missing and 3 partials ⚠️
pkg/registry/fetch.go 73.07% 4 Missing and 3 partials ⚠️
pkg/config/lifecycle_provider.go 50.00% 2 Missing and 1 partial ⚠️
... and 2 more

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1449      +/-   ##
==========================================
+ Coverage   67.17%   67.19%   +0.02%     
==========================================
  Files         134      140       +6     
  Lines        8276     8827     +551     
==========================================
+ Hits         5559     5931     +372     
- Misses       2264     2389     +125     
- Partials      453      507      +54     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@chenbh chenbh force-pushed the implement-slsa branch 2 times, most recently from 09bc5a4 to 755cdc1 Compare December 15, 2023 20:57
@chenbh chenbh marked this pull request as ready for review December 15, 2023 20:57
@chenbh chenbh requested a review from a team as a code owner December 15, 2023 20:57
@chenbh chenbh changed the title Implement SLSA attestations SLSA attestations for the Build resource (aka app image attestations) Dec 15, 2023
@chenbh
Copy link
Contributor Author

chenbh commented Jan 5, 2024

@matthewmcnew @samj1912 @AidanDelaney can I get some non-Broadcom 👀 on this or the RFC?

Copy link

@AidanDelaney AidanDelaney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hate to provide a partial review, but I have limited time today. Will complete the review tomorrow. Most of my comments are trivial and can be ignored as style changes. I have one question on whether the start/stop time is necessary in an attestation.

pkg/blob/fetch.go Show resolved Hide resolved
pkg/blob/fetch.go Show resolved Hide resolved
pkg/blob/fetch.go Show resolved Hide resolved
if _, found := os.LookupEnv(cosignutil.CosignDockerMediaTypesEnv); found {
err = os.Unsetenv(cosignutil.CosignDockerMediaTypesEnv)
if _, found := os.LookupEnv(CosignDockerMediaTypesEnv); found {
err = os.Unsetenv(CosignDockerMediaTypesEnv)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this to prevent an end-user from injecting an incorrect media type through an environmental variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is to avoid pollution from having multiple signing secrets. Since each signing secret specifies the media type to be generated, if the current secret doesn't specify a format, we should use cosign's default type instead of whatever the previous secret used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way we do cosign is also a bit interesting because the builder signing component (SignBuilder) is done in the controller container, but the app image signing (Sign) is done in the build pod.

I do want to unify them sometime in the future (currently playing around with the idea of a new pod/deployment in the kpack namespace that handles both signing and attestation), but for now the 2 methods of this ImageSigner object is used in completely different codepaths


report = createReportToml(t, expectedImageName, imageDigest)

os.Unsetenv(cosignutil.CosignRepositoryEnv)
os.Unsetenv(cosignutil.CosignDockerMediaTypesEnv)
os.Unsetenv(CosignRepositoryEnv)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any guarantees about the order of the tests? AFAIK unsetting these env vars here have an impact on tests that run subsequently. Do we want to change the environment for subsequent tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more of a preliminary cleaning of the test environment, any test that requires the env var will set it explicitly. Technically it should be enough to unset it at the end of the tests, but I guess cleaning before the tests protects against accidentally inheriting env vars from the shell when running the tests.

return extractAttestationKeyFromSecrets(cosignSecrets, privKeySecrets)
}

func filterCosignSecrets(serviceAccountSecrets []*corev1.Secret, annotation string) []*corev1.Secret {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this filterCosignSecrets or gatherCosignSecrets? I'd expect a filter to apply a predicate to some collection. Here it looks like we're gathering the secrets and packing them into a struct, should they exist.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm indifferent to either name. I would say that this function does apply a predicate: it throws away any items that doesn't have certain annotations. It's return value is also a list of the same type as the input list, it's even in the same order.

pkg/slsa/attest.go Outdated Show resolved Hide resolved
pkg/slsa/attest.go Outdated Show resolved Hide resolved
pkg/slsa/attest.go Outdated Show resolved Hide resolved
pkg/slsa/attest.go Outdated Show resolved Hide resolved
before we were only generating it for the git source type.

this eventually gets turned into a label on the image as specified in
https://github.com/buildpacks/spec/blob/main/platform.md#iobuildpacksprojectmetadata-json

Signed-off-by: Bohan Chen <[email protected]>
todo:
- extract source from app image labels
- record builder image

Signed-off-by: Bohan Chen <[email protected]>
The source metadata is read in from the project-metadata label on the
built image. This is done for 2 reasons:

1. It resolves floating pointers to concrete shas, this can be the case
   with a git source pointing to a branch, or a registry source pointing
   to a tag. We also can't figure out a blob's checksum without actually
   pulling it
2. It guarantees that this is the version pulled down by the `prepare`
   step and avoid weird TOCTOU edge cases where the underlying bits
   changed between the creation of the Build and the execution of the
   container

By exporting the Project, Source, Metadata, and Version structs, I hope
to create a code relation between the different packages, so that if we
every change one in the future, the compiler will yell at us instead of
us having to remember the relationship.

Signed-off-by: Bohan Chen <[email protected]>
supported key types are rsa, ecdsa, ed25519, and cosign (which is
basically a wrapper around the other 3).

there are some smoke tests with hard coded signatures, but i feel the
main tests should be the interop with cosign cli

Signed-off-by: Bohan Chen <[email protected]>
since we already have a package dedicated to grabbing secrets out of
k8s, lets expand it further so that it knows how to extract signing keys
out of it.

Signed-off-by: Bohan Chen <[email protected]>
Copy link
Collaborator

@tomkennedy513 tomkennedy513 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is awesome

Copy link
Contributor

@natalieparellano natalieparellano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing work. I only reviewed the docs files

docs/slsa.md Outdated Show resolved Hide resolved
docs/slsa.md Outdated Show resolved Hide resolved
- split out signing and pushing into different functions mainly to make
the error messages clearer

- move attestation sign and write functions to obj so that we can mock out
the attester interface later

- also change the slsa buildid to include `slsa` as a subpath to make it
clearer what its used for

Signed-off-by: Bohan Chen <[email protected]>
- the controller is now aware of its own service account name, this is
  to fetch all the secrets associated with it (currently for attestation
  only, but can be expanded to cosign later)

- the feature flag for enabling slsa is marked experimental since the
  RFC hasn't been merged in yet

Signed-off-by: Bohan Chen <[email protected]>
rename slsa.go as the name wasn't really descriptive of what its doing

bugs:
- client-go strips the TypeMeta from the object so we have to pass it in
manually.
- granting rbac for the controller to get namespaces
- and using version instead of identifier so we don't get the git sha
  when generating the build type link
- ecdsa signing requires digest not full msg
- stick to consistent tense for err msgs
- and make the start/stop time parsing best effort - the slsa spec says
  its optional so we shouldn't fail the whole attestation if we can't
  find it

Signed-off-by: Bohan Chen <[email protected]>
since tests are run in parallel, cluster-scoped resources needed to be
created with a different name for each of the suites

Signed-off-by: Bohan Chen <[email protected]>
Signed-off-by: Bohan Chen <[email protected]>
@chenbh chenbh merged commit 3344d70 into main Jan 17, 2024
3 checks passed
@chenbh chenbh deleted the implement-slsa branch January 17, 2024 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants