From 10108d035881b42e644875440065a4a8717fc2ec Mon Sep 17 00:00:00 2001 From: Mauren Berti Date: Mon, 12 Jun 2023 18:04:24 -0400 Subject: [PATCH] Add end-to-end tests for Builder/ClusterBuilder signing. Add a new test suite to the end-to-end tests to verify that once a Builder or ClusterBuilder is created, if a cosign secret is present, then the image will be signed and the signature will be verifiable. --- pkg/cosign/image_signer_test.go | 101 ++---- pkg/cosign/test_util.go | 57 +++ test/execute_build_test.go | 622 +++++++++++++++++++++++++++++++- 3 files changed, 706 insertions(+), 74 deletions(-) create mode 100644 pkg/cosign/test_util.go diff --git a/pkg/cosign/image_signer_test.go b/pkg/cosign/image_signer_test.go index 2f7b17918..7221b0a29 100644 --- a/pkg/cosign/image_signer_test.go +++ b/pkg/cosign/image_signer_test.go @@ -3,8 +3,6 @@ package cosign import ( "bufio" "context" - "crypto" - "encoding/base64" "fmt" "io/ioutil" "log" @@ -30,10 +28,8 @@ import ( "github.com/sigstore/cosign/v2/cmd/cosign/cli/download" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" - verifypkg "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" sigstoreCosign "github.com/sigstore/cosign/v2/pkg/cosign" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" - "github.com/sigstore/cosign/v2/pkg/signature" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -150,10 +146,10 @@ func testImageSigner(t *testing.T, when spec.G, it spec.S) { assert.Equal(t, 1, password1Count) assert.Equal(t, 1, password2Count) - err = verify(publicKey1, expectedImageName, nil) + err = Verify(t, publicKey1, expectedImageName, nil) assert.Nil(t, err) - err = verify(publicKey2, expectedImageName, nil) + err = Verify(t, publicKey2, expectedImageName, nil) assert.Nil(t, err) err = download.SignatureCmd(context.Background(), options.RegistryOptions{}, expectedImageName) @@ -194,21 +190,21 @@ func testImageSigner(t *testing.T, when spec.G, it spec.S) { assert.Equal(t, 2, cliSignCmdCallCount) // Should error when validating annotations that dont exist - err = verify(publicKey1, expectedImageName, unexpectedAnnotation) + err = Verify(t, publicKey1, expectedImageName, unexpectedAnnotation) assert.Error(t, err) - err = verify(publicKey2, expectedImageName, unexpectedAnnotation) + err = Verify(t, publicKey2, expectedImageName, unexpectedAnnotation) assert.Error(t, err) // Should not error when validating annotations that exist - err = verify(publicKey1, expectedImageName, expectedAnnotation) + err = Verify(t, publicKey1, expectedImageName, expectedAnnotation) assert.Nil(t, err) - err = verify(publicKey2, expectedImageName, expectedAnnotation) + err = Verify(t, publicKey2, expectedImageName, expectedAnnotation) assert.Nil(t, err) // Should not error when not validating annotations - err = verify(publicKey1, expectedImageName, nil) + err = Verify(t, publicKey1, expectedImageName, nil) assert.Nil(t, err) - err = verify(publicKey2, expectedImageName, nil) + err = Verify(t, publicKey2, expectedImageName, nil) assert.Nil(t, err) err = download.SignatureCmd(context.Background(), options.RegistryOptions{}, expectedImageName) @@ -304,9 +300,9 @@ func testImageSigner(t *testing.T, when spec.G, it spec.S) { assertUnset(t, cosignRepositoryEnv) - err = verify(publicKey1, expectedImageName, nil) + err = Verify(t, publicKey1, expectedImageName, nil) assert.Nil(t, err) - err = verify(publicKey2, expectedImageName, nil) + err = Verify(t, publicKey2, expectedImageName, nil) assert.Error(t, err) err = download.SignatureCmd(context.Background(), options.RegistryOptions{}, expectedImageName) assert.Nil(t, err) @@ -315,9 +311,9 @@ func testImageSigner(t *testing.T, when spec.G, it spec.S) { // on a registry that does not contain the image os.Setenv(cosignRepositoryEnv, altImageName) defer os.Unsetenv(cosignRepositoryEnv) - err = verify(publicKey1, expectedImageName, nil) + err = Verify(t, publicKey1, expectedImageName, nil) assert.Error(t, err) - err = verify(publicKey2, expectedImageName, nil) + err = Verify(t, publicKey2, expectedImageName, nil) assert.Nil(t, err) }) @@ -463,7 +459,7 @@ func testImageSigner(t *testing.T, when spec.G, it spec.S) { ctx := context.Background() // Verify+download should fail at first - err := verify(pubKeyPath, imgName, nil) + err := Verify(t, pubKeyPath, imgName, nil) assert.Error(t, err) err = download.SignatureCmd(ctx, options.RegistryOptions{}, imgName) assert.Error(t, err) @@ -488,7 +484,7 @@ func testImageSigner(t *testing.T, when spec.G, it spec.S) { assert.Nil(t, err) // Verify+download should pass - err = verify(pubKeyPath, imgName, nil) + err = Verify(t, pubKeyPath, imgName, nil) assert.Nil(t, err) err = download.SignatureCmd(ctx, options.RegistryOptions{}, imgName) assert.Nil(t, err) @@ -496,6 +492,12 @@ func testImageSigner(t *testing.T, when spec.G, it spec.S) { }) when("#SignBuilder", func() { + const ( + cosignSecretName = "cosign-creds" + testNamespaceName = "test-namespace" + cosignServiceAccountName = "cosign-sa" + ) + it("resolves the digest of a signature correctly", func() { var ( signCallCount = 0 @@ -521,12 +523,12 @@ func testImageSigner(t *testing.T, when spec.G, it spec.S) { }, } - fakeSecret := generateFakeKeyPair(t, "", nil) + fakeSecret := GenerateFakeKeyPair(t, cosignSecretName, testNamespaceName, "", nil) cosignCreds := []corev1.Secret{fakeSecret} cosignSA := corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: "cosign-sa", - Namespace: "test-namespace", + Name: cosignServiceAccountName, + Namespace: testNamespaceName, }, Secrets: []corev1.ObjectReference{ { @@ -599,12 +601,12 @@ func testImageSigner(t *testing.T, when spec.G, it spec.S) { "kpack.io/cosign.docker-media-types": dockerMediaTypesValue, } - fakeSecret := generateFakeKeyPair(t, "", annotations) + fakeSecret := GenerateFakeKeyPair(t, cosignSecretName, testNamespaceName, "", annotations) cosignCreds := []corev1.Secret{fakeSecret} cosignSA := corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: "cosign-sa", - Namespace: "test-namespace", + Name: cosignServiceAccountName, + Namespace: testNamespaceName, }, Secrets: []corev1.ObjectReference{ { @@ -637,41 +639,6 @@ func testImageSigner(t *testing.T, when spec.G, it spec.S) { }) } -func generateFakeKeyPair(t *testing.T, password string, annotations map[string]string) corev1.Secret { - passFunc := func(_ bool) ([]byte, error) { - return []byte(password), nil - } - - keys, err := sigstoreCosign.GenerateKeyPair(passFunc) - require.NoError(t, err) - - encodedPublicKey := make([]byte, base64.StdEncoding.EncodedLen(len(keys.PublicBytes))) - base64.StdEncoding.Encode(encodedPublicKey, keys.PublicBytes) - - encodedPrivateKey := make([]byte, base64.StdEncoding.EncodedLen(len(keys.PrivateBytes))) - base64.StdEncoding.Encode(encodedPrivateKey, keys.PrivateBytes) - - encodedPassword := make([]byte, base64.StdEncoding.EncodedLen(len([]byte(password)))) - base64.StdEncoding.Encode(encodedPassword, []byte(password)) - - data := map[string][]byte{ - SecretDataCosignPublicKey: encodedPublicKey, - SecretDataCosignKey: encodedPrivateKey, - SecretDataCosignPassword: encodedPassword, - } - - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cosign-creds", - Namespace: "test-namespace", - Annotations: annotations, - }, - Data: data, - } - - return secret -} - func mockLogger(t *testing.T) (*bufio.Scanner, *os.File, *os.File) { reader, writer, err := os.Pipe() if err != nil { @@ -763,6 +730,8 @@ func registryClientOpts(ctx context.Context) []remote.Option { } func keypair(t *testing.T, dirPath, secretName, password string) { + t.Helper() + passFunc := func(_ bool) ([]byte, error) { return []byte(password), nil } @@ -786,17 +755,3 @@ func keypair(t *testing.T, dirPath, secretName, password string) { err = ioutil.WriteFile(passwordPath, passwordBytes, 0600) assert.Nil(t, err) } - -func verify(keyRef, imageRef string, annotations map[string]interface{}) error { - cmd := verifypkg.VerifyCommand{ - KeyRef: keyRef, - Annotations: signature.AnnotationsMap{Annotations: annotations}, - CheckClaims: true, - HashAlgorithm: crypto.SHA256, - IgnoreTlog: true, - } - - args := []string{imageRef} - - return cmd.Exec(context.Background(), args) -} diff --git a/pkg/cosign/test_util.go b/pkg/cosign/test_util.go new file mode 100644 index 000000000..828215139 --- /dev/null +++ b/pkg/cosign/test_util.go @@ -0,0 +1,57 @@ +package cosign + +import ( + "context" + "crypto" + cosignVerify "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" + "github.com/sigstore/cosign/v2/pkg/cosign" + "github.com/sigstore/cosign/v2/pkg/signature" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "testing" +) + +func GenerateFakeKeyPair(t *testing.T, secretName string, secretNamespace string, password string, annotations map[string]string) corev1.Secret { + t.Helper() + + passFunc := func(_ bool) ([]byte, error) { + return []byte(password), nil + } + + keys, err := cosign.GenerateKeyPair(passFunc) + require.NoError(t, err) + + data := map[string][]byte{ + SecretDataCosignPublicKey: keys.PublicBytes, + SecretDataCosignKey: keys.PrivateBytes, + SecretDataCosignPassword: []byte(password), + } + + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: secretNamespace, + Annotations: annotations, + }, + Data: data, + } + + return secret +} + +func Verify(t *testing.T, keyRef, imageRef string, annotations map[string]interface{}) error { + t.Helper() + + cmd := cosignVerify.VerifyCommand{ + KeyRef: keyRef, + Annotations: signature.AnnotationsMap{Annotations: annotations}, + CheckClaims: true, + HashAlgorithm: crypto.SHA256, + IgnoreTlog: true, + } + + args := []string{imageRef} + + return cmd.Exec(context.Background(), args) +} diff --git a/test/execute_build_test.go b/test/execute_build_test.go index f4380b67f..6bbee6bf8 100644 --- a/test/execute_build_test.go +++ b/test/execute_build_test.go @@ -4,6 +4,8 @@ import ( "bytes" "context" "fmt" + kpackCosign "github.com/pivotal/kpack/pkg/cosign" + "github.com/stretchr/testify/assert" "math/rand" "os" "strings" @@ -39,7 +41,13 @@ func TestCreateImage(t *testing.T) { spec.Run(t, "CreateImage", testCreateImage) } -func testCreateImage(t *testing.T, when spec.G, it spec.S) { +func TestSignBuilder(t *testing.T) { + rand.Seed(time.Now().Unix()) + + spec.Run(t, "SignBuilder", testSignBuilder) +} + +func testCreateImage(t *testing.T, _ spec.G, it spec.S) { const ( testNamespace = "test" dockerSecret = "docker-secret" @@ -469,6 +477,618 @@ func testCreateImage(t *testing.T, when spec.G, it spec.S) { }) } +func testSignBuilder(t *testing.T, _ spec.G, it spec.S) { + const ( + testNamespace = "test" + dockerSecret = "docker-secret" + serviceAccountName = "image-service-account" + clusterStoreName = "store" + buildpackName = "buildpack" + clusterBuildpackName = "cluster-buildpack" + clusterStackName = "stack" + builderName = "custom-signed-builder" + clusterBuilderName = "custom-signed-cluster-builder" + cosignSecretName = "cosign-creds" + ) + + var ( + cfg config + clients *clients + ctx = context.Background() + builtImages map[string]struct{} + ) + + it.Before(func() { + cfg = loadConfig(t) + builtImages = map[string]struct{}{} + + var err error + clients, err = newClients(t) + require.NoError(t, err) + + err = clients.client.KpackV1alpha2().ClusterStores().Delete(ctx, clusterStoreName, metav1.DeleteOptions{}) + if !errors.IsNotFound(err) { + require.NoError(t, err) + } + + err = clients.client.KpackV1alpha2().Buildpacks(testNamespace).Delete(ctx, buildpackName, metav1.DeleteOptions{}) + if !errors.IsNotFound(err) { + require.NoError(t, err) + } + + err = clients.client.KpackV1alpha2().ClusterBuildpacks().Delete(ctx, clusterBuildpackName, metav1.DeleteOptions{}) + if !errors.IsNotFound(err) { + require.NoError(t, err) + } + + err = clients.client.KpackV1alpha2().ClusterStacks().Delete(ctx, clusterStackName, metav1.DeleteOptions{}) + if !errors.IsNotFound(err) { + require.NoError(t, err) + } + + err = clients.client.KpackV1alpha2().ClusterBuilders().Delete(ctx, clusterBuilderName, metav1.DeleteOptions{}) + if !errors.IsNotFound(err) { + require.NoError(t, err) + } + + deleteNamespace(t, ctx, clients, testNamespace) + + _, err = clients.k8sClient.CoreV1().Namespaces().Create(ctx, &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: testNamespace, + Labels: readNamespaceLabelsFromEnv(), + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + }) + + it.After(func() { + for tag := range builtImages { + deleteImageTag(t, tag) + } + }) + + it.Before(func() { + secret, err := cfg.makeRegistrySecret(dockerSecret, testNamespace) + require.NoError(t, err) + + _, err = clients.k8sClient.CoreV1().Secrets(testNamespace).Create(ctx, secret, metav1.CreateOptions{}) + require.NoError(t, err) + + _, err = clients.k8sClient.CoreV1().ServiceAccounts(testNamespace).Create(ctx, &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceAccountName, + }, + Secrets: []corev1.ObjectReference{ + { + Name: dockerSecret, + }, + }, + ImagePullSecrets: []corev1.LocalObjectReference{ + { + Name: dockerSecret, + }, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + _, err = clients.client.KpackV1alpha2().ClusterStores().Create(ctx, &buildapi.ClusterStore{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterStoreName, + }, + Spec: buildapi.ClusterStoreSpec{ + Sources: []corev1alpha1.ImageSource{ + {Image: "gcr.io/paketo-buildpacks/bellsoft-liberica"}, + {Image: "gcr.io/paketo-buildpacks/gradle"}, + {Image: "gcr.io/paketo-buildpacks/syft"}, + {Image: "gcr.io/paketo-buildpacks/executable-jar"}, + {Image: "gcr.io/paketo-buildpacks/dist-zip"}, + {Image: "gcr.io/paketo-buildpacks/spring-boot"}, + }, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + _, err = clients.client.KpackV1alpha2().Buildpacks(testNamespace).Create(ctx, &buildapi.Buildpack{ + ObjectMeta: metav1.ObjectMeta{ + Name: buildpackName, + }, + Spec: buildapi.BuildpackSpec{ + ImageSource: corev1alpha1.ImageSource{ + Image: "gcr.io/paketo-buildpacks/bellsoft-liberica", + }, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + _, err = clients.client.KpackV1alpha2().ClusterBuildpacks().Create(ctx, &buildapi.ClusterBuildpack{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterBuildpackName, + }, + Spec: buildapi.ClusterBuildpackSpec{ + ImageSource: corev1alpha1.ImageSource{ + Image: "gcr.io/paketo-buildpacks/nodejs", + }, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + _, err = clients.client.KpackV1alpha2().ClusterStacks().Create(ctx, &buildapi.ClusterStack{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterStackName, + }, + Spec: buildapi.ClusterStackSpec{ + Id: "io.buildpacks.stacks.bionic", + BuildImage: buildapi.ClusterStackSpecImage{ + Image: "gcr.io/paketo-buildpacks/build:base-cnb", + }, + RunImage: buildapi.ClusterStackSpecImage{ + Image: "gcr.io/paketo-buildpacks/run:base-cnb", + }, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + }) + + it("Signs a Builder image successfully when the key is not password-protected", func() { + cosignCredSecret := kpackCosign.GenerateFakeKeyPair(t, cosignSecretName, testNamespace, "", nil) + _, err := clients.k8sClient.CoreV1().Secrets(testNamespace).Create(ctx, &cosignCredSecret, metav1.CreateOptions{}) + require.NoError(t, err) + + serviceAccount, err := clients.k8sClient.CoreV1().ServiceAccounts(testNamespace).Get(ctx, serviceAccountName, metav1.GetOptions{}) + require.NoError(t, err) + + if serviceAccount.Secrets == nil { + serviceAccount.Secrets = make([]corev1.ObjectReference, 0) + } + serviceAccount.Secrets = append(serviceAccount.Secrets, corev1.ObjectReference{Name: cosignCredSecret.Name}) + + _, err = clients.k8sClient.CoreV1().ServiceAccounts(testNamespace).Update(ctx, serviceAccount, metav1.UpdateOptions{}) + require.NoError(t, err) + + builder, err := clients.client.KpackV1alpha2().Builders(testNamespace).Create(ctx, &buildapi.Builder{ + ObjectMeta: metav1.ObjectMeta{ + Name: builderName, + Namespace: testNamespace, + }, + Spec: buildapi.NamespacedBuilderSpec{ + BuilderSpec: buildapi.BuilderSpec{ + Tag: cfg.newImageTag(), + Stack: corev1.ObjectReference{ + Name: clusterStackName, + Kind: "ClusterStack", + }, + Store: corev1.ObjectReference{ + Name: clusterStoreName, + Kind: "ClusterStore", + }, + Order: []buildapi.BuilderOrderEntry{ + { + Group: []buildapi.BuilderBuildpackRef{ + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/nodejs", + }, + }, + }, + }, + }, + { + Group: []buildapi.BuilderBuildpackRef{ + { + ObjectReference: corev1.ObjectReference{ + Name: buildpackName, + Kind: "Buildpack", + }, + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/bellsoft-liberica", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/gradle", + }, + Optional: true, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/syft", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/executable-jar", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/dist-zip", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/spring-boot", + }, + }, + }, + }, + }, + }, + }, + ServiceAccountName: serviceAccountName, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + waitUntilReady(t, ctx, clients, builder) + + updatedBuilder, err := clients.client.KpackV1alpha2().Builders(testNamespace).Get(ctx, builderName, metav1.GetOptions{}) + require.NoError(t, err) + + require.True(t, updatedBuilder.Status.Signed) + assert.NotEmpty(t, updatedBuilder.Status.SignaturePaths) + assert.NotNil(t, updatedBuilder.Status.SignaturePaths[0]) + + err = kpackCosign.Verify(t, fmt.Sprintf("k8s://%s/%s", testNamespace, cosignSecretName), updatedBuilder.Status.LatestImage, nil) + require.NoError(t, err) + }) + + it("Signs a Builder image successfully when the key is password-protected", func() { + const CosignKeyPassword = "password" + + cosignCredSecret := kpackCosign.GenerateFakeKeyPair(t, cosignSecretName, testNamespace, CosignKeyPassword, nil) + _, err := clients.k8sClient.CoreV1().Secrets(testNamespace).Create(ctx, &cosignCredSecret, metav1.CreateOptions{}) + require.NoError(t, err) + + serviceAccount, err := clients.k8sClient.CoreV1().ServiceAccounts(testNamespace).Get(ctx, serviceAccountName, metav1.GetOptions{}) + require.NoError(t, err) + + if serviceAccount.Secrets == nil { + serviceAccount.Secrets = make([]corev1.ObjectReference, 0) + } + serviceAccount.Secrets = append(serviceAccount.Secrets, corev1.ObjectReference{Name: cosignCredSecret.Name}) + + _, err = clients.k8sClient.CoreV1().ServiceAccounts(testNamespace).Update(ctx, serviceAccount, metav1.UpdateOptions{}) + require.NoError(t, err) + + builder, err := clients.client.KpackV1alpha2().Builders(testNamespace).Create(ctx, &buildapi.Builder{ + ObjectMeta: metav1.ObjectMeta{ + Name: builderName, + Namespace: testNamespace, + }, + Spec: buildapi.NamespacedBuilderSpec{ + BuilderSpec: buildapi.BuilderSpec{ + Tag: cfg.newImageTag(), + Stack: corev1.ObjectReference{ + Name: clusterStackName, + Kind: "ClusterStack", + }, + Store: corev1.ObjectReference{ + Name: clusterStoreName, + Kind: "ClusterStore", + }, + Order: []buildapi.BuilderOrderEntry{ + { + Group: []buildapi.BuilderBuildpackRef{ + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/nodejs", + }, + }, + }, + }, + }, + { + Group: []buildapi.BuilderBuildpackRef{ + { + ObjectReference: corev1.ObjectReference{ + Name: buildpackName, + Kind: "Buildpack", + }, + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/bellsoft-liberica", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/gradle", + }, + Optional: true, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/syft", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/executable-jar", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/dist-zip", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/spring-boot", + }, + }, + }, + }, + }, + }, + }, + ServiceAccountName: serviceAccountName, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + waitUntilReady(t, ctx, clients, builder) + + updatedBuilder, err := clients.client.KpackV1alpha2().Builders(testNamespace).Get(ctx, builderName, metav1.GetOptions{}) + require.NoError(t, err) + + require.True(t, updatedBuilder.Status.Signed) + assert.NotEmpty(t, updatedBuilder.Status.SignaturePaths) + assert.NotNil(t, updatedBuilder.Status.SignaturePaths[0]) + + err = kpackCosign.Verify(t, fmt.Sprintf("k8s://%s/%s", testNamespace, cosignSecretName), updatedBuilder.Status.LatestImage, nil) + require.NoError(t, err) + }) + + it("Signs a ClusterBuilder image successfully when the key is not password-protected", func() { + cosignCredSecret := kpackCosign.GenerateFakeKeyPair(t, cosignSecretName, testNamespace, "", nil) + _, err := clients.k8sClient.CoreV1().Secrets(testNamespace).Create(ctx, &cosignCredSecret, metav1.CreateOptions{}) + require.NoError(t, err) + + serviceAccount, err := clients.k8sClient.CoreV1().ServiceAccounts(testNamespace).Get(ctx, serviceAccountName, metav1.GetOptions{}) + require.NoError(t, err) + + if serviceAccount.Secrets == nil { + serviceAccount.Secrets = make([]corev1.ObjectReference, 0) + } + serviceAccount.Secrets = append(serviceAccount.Secrets, corev1.ObjectReference{Name: cosignCredSecret.Name}) + + _, err = clients.k8sClient.CoreV1().ServiceAccounts(testNamespace).Update(ctx, serviceAccount, metav1.UpdateOptions{}) + require.NoError(t, err) + + clusterBuilder, err := clients.client.KpackV1alpha2().ClusterBuilders().Create(ctx, &buildapi.ClusterBuilder{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterBuilderName, + }, + Spec: buildapi.ClusterBuilderSpec{ + BuilderSpec: buildapi.BuilderSpec{ + Tag: cfg.newImageTag(), + Stack: corev1.ObjectReference{ + Name: clusterStackName, + Kind: "ClusterStack", + }, + Store: corev1.ObjectReference{ + Name: clusterStoreName, + Kind: "ClusterStore", + }, + Order: []buildapi.BuilderOrderEntry{ + { + Group: []buildapi.BuilderBuildpackRef{ + { + ObjectReference: corev1.ObjectReference{ + Name: clusterBuildpackName, + Kind: "ClusterBuildpack", + }, + }, + }, + }, + { + Group: []buildapi.BuilderBuildpackRef{ + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/bellsoft-liberica", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/gradle", + }, + Optional: true, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/syft", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/executable-jar", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/dist-zip", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/spring-boot", + }, + }, + }, + }, + }, + }, + }, + ServiceAccountRef: corev1.ObjectReference{ + Namespace: testNamespace, + Name: serviceAccountName, + }, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + waitUntilReady(t, ctx, clients, clusterBuilder) + + updatedBuilder, err := clients.client.KpackV1alpha2().ClusterBuilders().Get(ctx, clusterBuilderName, metav1.GetOptions{}) + require.NoError(t, err) + + require.True(t, updatedBuilder.Status.Signed) + assert.NotEmpty(t, updatedBuilder.Status.SignaturePaths) + assert.NotNil(t, updatedBuilder.Status.SignaturePaths[0]) + + err = kpackCosign.Verify(t, fmt.Sprintf("k8s://%s/%s", testNamespace, cosignSecretName), updatedBuilder.Status.LatestImage, nil) + require.NoError(t, err) + }) + + it("Signs a ClusterBuilder image successfully when the key is password-protected", func() { + const CosignKeyPassword = "password" + + cosignCredSecret := kpackCosign.GenerateFakeKeyPair(t, cosignSecretName, testNamespace, CosignKeyPassword, nil) + _, err := clients.k8sClient.CoreV1().Secrets(testNamespace).Create(ctx, &cosignCredSecret, metav1.CreateOptions{}) + require.NoError(t, err) + + serviceAccount, err := clients.k8sClient.CoreV1().ServiceAccounts(testNamespace).Get(ctx, serviceAccountName, metav1.GetOptions{}) + require.NoError(t, err) + + if serviceAccount.Secrets == nil { + serviceAccount.Secrets = make([]corev1.ObjectReference, 0) + } + serviceAccount.Secrets = append(serviceAccount.Secrets, corev1.ObjectReference{Name: cosignCredSecret.Name}) + + _, err = clients.k8sClient.CoreV1().ServiceAccounts(testNamespace).Update(ctx, serviceAccount, metav1.UpdateOptions{}) + require.NoError(t, err) + + clusterBuilder, err := clients.client.KpackV1alpha2().ClusterBuilders().Create(ctx, &buildapi.ClusterBuilder{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterBuilderName, + }, + Spec: buildapi.ClusterBuilderSpec{ + BuilderSpec: buildapi.BuilderSpec{ + Tag: cfg.newImageTag(), + Stack: corev1.ObjectReference{ + Name: clusterStackName, + Kind: "ClusterStack", + }, + Store: corev1.ObjectReference{ + Name: clusterStoreName, + Kind: "ClusterStore", + }, + Order: []buildapi.BuilderOrderEntry{ + { + Group: []buildapi.BuilderBuildpackRef{ + { + ObjectReference: corev1.ObjectReference{ + Name: clusterBuildpackName, + Kind: "ClusterBuildpack", + }, + }, + }, + }, + { + Group: []buildapi.BuilderBuildpackRef{ + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/bellsoft-liberica", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/gradle", + }, + Optional: true, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/syft", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/executable-jar", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/dist-zip", + }, + }, + }, + { + BuildpackRef: corev1alpha1.BuildpackRef{ + BuildpackInfo: corev1alpha1.BuildpackInfo{ + Id: "paketo-buildpacks/spring-boot", + }, + }, + }, + }, + }, + }, + }, + ServiceAccountRef: corev1.ObjectReference{ + Namespace: testNamespace, + Name: serviceAccountName, + }, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + waitUntilReady(t, ctx, clients, clusterBuilder) + + updatedBuilder, err := clients.client.KpackV1alpha2().ClusterBuilders().Get(ctx, clusterBuilderName, metav1.GetOptions{}) + require.NoError(t, err) + + require.True(t, updatedBuilder.Status.Signed) + assert.NotEmpty(t, updatedBuilder.Status.SignaturePaths) + assert.NotNil(t, updatedBuilder.Status.SignaturePaths[0]) + + err = kpackCosign.Verify(t, fmt.Sprintf("k8s://%s/%s", testNamespace, cosignSecretName), updatedBuilder.Status.LatestImage, nil) + require.NoError(t, err) + }) +} + func generateRebuild(ctx *context.Context, t *testing.T, cfg config, clients *clients, cacheConfig *buildapi.ImageCacheConfig, testNamespace, clusterBuilderName, serviceAccountName string) string { expectedResources := corev1.ResourceRequirements{ Limits: corev1.ResourceList{