diff --git a/Makefile b/Makefile index 72c625e4..38211999 100644 --- a/Makefile +++ b/Makefile @@ -130,6 +130,10 @@ run: manifests generate fmt vet ## Run a controller from your host. docker-build: test ## Build docker image with the manager. docker build . -t ${IMG} +.PHONY: docker-build-skip-test +docker-build-skip-test: ## Build docker image with the manager. + docker build . -t ${IMG} + .PHONY: docker-push docker-push: ## Push docker image with the manager. docker push ${IMG} diff --git a/controllers/constants/labels.go b/controllers/constants/labels.go index 9a8e1ebb..637bad44 100644 --- a/controllers/constants/labels.go +++ b/controllers/constants/labels.go @@ -2,15 +2,8 @@ package constants const ( LabelNamespace = "rhtas.redhat.com" - //DiscoverableByTUFKeyLabel = LabelNamespace + "/tuf-key" - TufLabelNamespace = "tuf." + LabelNamespace ) -func TufDiscoverableSecretLabel(name string, key string) map[string]string { - return map[string]string{ - TufLabelNamespace + "/" + name: key, - } -} func LabelsFor(component, name, instance string) map[string]string { labels := LabelsForComponent(component, instance) labels["app.kubernetes.io/name"] = name diff --git a/controllers/ctlog/actions/generate_keys.go b/controllers/ctlog/actions/generate_keys.go index 515d1c43..59d8d4d3 100644 --- a/controllers/ctlog/actions/generate_keys.go +++ b/controllers/ctlog/actions/generate_keys.go @@ -3,7 +3,6 @@ package actions import ( "context" "fmt" - "maps" "github.com/securesign/operator/api/v1alpha1" "github.com/securesign/operator/controllers/common/action" @@ -43,18 +42,13 @@ func (g generateKeys) Handle(ctx context.Context, instance *v1alpha1.CTlog) *act return g.Failed(err) } - secretLabels := map[string]string{ - constants.TufLabelNamespace + "/ctfe.pub": "public", - } - maps.Copy(secretLabels, labels) - secretName := fmt.Sprintf(KeySecretNameFormat, instance.Name) secret := k8sutils.CreateSecret(secretName, instance.Namespace, map[string][]byte{ "private": config.PrivateKey, "public": config.PublicKey, - }, secretLabels) + }, labels) if err = controllerutil.SetControllerReference(instance, secret, g.Client.Scheme()); err != nil { return g.Failed(fmt.Errorf("could not set controller reference for Secret: %w", err)) diff --git a/controllers/ctlog/actions/handle_fulcio_root.go b/controllers/ctlog/actions/handle_fulcio_root.go index 183f7899..723e0aa3 100644 --- a/controllers/ctlog/actions/handle_fulcio_root.go +++ b/controllers/ctlog/actions/handle_fulcio_root.go @@ -7,7 +7,7 @@ import ( "github.com/securesign/operator/api/v1alpha1" "github.com/securesign/operator/controllers/common/action" k8sutils "github.com/securesign/operator/controllers/common/utils/kubernetes" - "github.com/securesign/operator/controllers/constants" + "github.com/securesign/operator/controllers/fulcio/actions" v1 "k8s.io/api/core/v1" ) @@ -29,7 +29,7 @@ func (g handleFulcioCert) CanHandle(instance *v1alpha1.CTlog) bool { func (g handleFulcioCert) Handle(ctx context.Context, instance *v1alpha1.CTlog) *action.Result { - scr, err := k8sutils.FindSecret(ctx, g.Client, instance.Namespace, constants.TufLabelNamespace+"/fulcio_v1.crt.pem") + scr, err := k8sutils.FindSecret(ctx, g.Client, instance.Namespace, actions.FulcioCALabel) if err != nil { return g.Failed(err) } @@ -37,7 +37,7 @@ func (g handleFulcioCert) Handle(ctx context.Context, instance *v1alpha1.CTlog) //TODO: add status condition - waiting for fulcio return g.Requeue() } - if scr.Data[scr.Labels[constants.TufLabelNamespace+"/fulcio_v1.crt.pem"]] == nil { + if scr.Data[scr.Labels[actions.FulcioCALabel]] == nil { return g.Failed(fmt.Errorf("can't find fulcio certificate in provided secret")) } @@ -45,7 +45,7 @@ func (g handleFulcioCert) Handle(ctx context.Context, instance *v1alpha1.CTlog) LocalObjectReference: v1.LocalObjectReference{ Name: scr.Name, }, - Key: scr.Labels[constants.TufLabelNamespace+"/fulcio_v1.crt.pem"], + Key: scr.Labels[actions.FulcioCALabel], }) return g.Update(ctx, instance) } diff --git a/controllers/ctlog/actions/servrConfig.go b/controllers/ctlog/actions/serverConfig.go similarity index 93% rename from controllers/ctlog/actions/servrConfig.go rename to controllers/ctlog/actions/serverConfig.go index 96206596..f2340ff6 100644 --- a/controllers/ctlog/actions/servrConfig.go +++ b/controllers/ctlog/actions/serverConfig.go @@ -3,6 +3,7 @@ package actions import ( "context" "fmt" + "maps" rhtasv1alpha1 "github.com/securesign/operator/api/v1alpha1" "github.com/securesign/operator/controllers/common/action" @@ -17,6 +18,7 @@ import ( ) const ConfigSecretNameFormat = "ctlog-%s-config" +const CTLLabel = constants.LabelNamespace + "/ctfe.pub" func NewServerConfigAction() action.Action[rhtasv1alpha1.CTlog] { return &serverConfig{} @@ -58,8 +60,13 @@ func (i serverConfig) Handle(ctx context.Context, instance *rhtasv1alpha1.CTlog) } var config *corev1.Secret + secretLabels := map[string]string{ + CTLLabel: "public", + } + maps.Copy(secretLabels, labels) + //TODO: the config is generated in every reconcile loop rotation - it can cause performance issues - if config, err = ctlogUtils.CreateCtlogConfig(ctx, instance.Namespace, trillUrl+":8091", *instance.Spec.TreeID, rootCerts, labels, certConfig); err != nil { + if config, err = ctlogUtils.CreateCtlogConfig(ctx, instance.Namespace, trillUrl+":8091", *instance.Spec.TreeID, rootCerts, secretLabels, certConfig); err != nil { instance.Status.Phase = rhtasv1alpha1.PhaseError return i.FailedWithStatusUpdate(ctx, fmt.Errorf("could not create CTLog configuration: %w", err), instance) } diff --git a/controllers/ctlog/ctlog_controller_test.go b/controllers/ctlog/ctlog_controller_test.go index 3871b8c5..fda286dd 100644 --- a/controllers/ctlog/ctlog_controller_test.go +++ b/controllers/ctlog/ctlog_controller_test.go @@ -25,6 +25,7 @@ import ( "github.com/securesign/operator/controllers/common/utils/kubernetes" "github.com/securesign/operator/controllers/constants" "github.com/securesign/operator/controllers/ctlog/actions" + fulcio "github.com/securesign/operator/controllers/fulcio/actions" trillian "github.com/securesign/operator/controllers/trillian/actions" "k8s.io/apimachinery/pkg/api/errors" @@ -125,7 +126,7 @@ var _ = Describe("CTlog controller", func() { By("Creating fulcio root cert") Expect(k8sClient.Create(ctx, kubernetes.CreateSecret("test", Namespace, map[string][]byte{"cert": []byte("fakeCert")}, - map[string]string{constants.TufLabelNamespace + "/fulcio_v1.crt.pem": "cert"}, + map[string]string{fulcio.FulcioCALabel: "cert"}, ))).To(Succeed()) Eventually(func() v1alpha1.Phase { diff --git a/controllers/fulcio/actions/generate_cert.go b/controllers/fulcio/actions/generate_cert.go index 499e03aa..a9a8787e 100644 --- a/controllers/fulcio/actions/generate_cert.go +++ b/controllers/fulcio/actions/generate_cert.go @@ -21,6 +21,7 @@ import ( ) const SecretNameFormat = "fulcio-%s-cert" +const FulcioCALabel = constants.LabelNamespace + "/fulcio_v1.crt.pem" func NewGenerateCertAction() action.Action[v1alpha1.Fulcio] { return &generateCert{} @@ -65,9 +66,8 @@ func (g generateCert) Handle(ctx context.Context, instance *v1alpha1.Fulcio) *ac labels := constants.LabelsFor(ComponentName, DeploymentName, instance.Name) - // TODO: tturek secretLabels := map[string]string{ - constants.TufLabelNamespace + "/fulcio_v1.crt.pem": "cert", + FulcioCALabel: "cert", } maps.Copy(secretLabels, labels) diff --git a/controllers/rekor/actions/server/generate_signer.go b/controllers/rekor/actions/server/generate_signer.go index 327c12d6..e6813eb8 100644 --- a/controllers/rekor/actions/server/generate_signer.go +++ b/controllers/rekor/actions/server/generate_signer.go @@ -19,7 +19,7 @@ import ( const SecretNameFormat = "rekor-%s-signer" -const RekorPubLabel = constants.TufLabelNamespace + "/rekor.pub" +const RekorPubLabel = constants.LabelNamespace + "/rekor.pub" func NewGenerateSignerAction() action.Action[v1alpha1.Rekor] { return &generateSigner{} diff --git a/controllers/rekor/actions/server/resolve_pub_key.go b/controllers/rekor/actions/server/resolve_pub_key.go index 22975a92..32c8ec27 100644 --- a/controllers/rekor/actions/server/resolve_pub_key.go +++ b/controllers/rekor/actions/server/resolve_pub_key.go @@ -14,7 +14,6 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" rhtasv1alpha1 "github.com/securesign/operator/api/v1alpha1" @@ -35,18 +34,19 @@ func (i resolvePubKeyAction) Name() string { } func (i resolvePubKeyAction) CanHandle(instance *rhtasv1alpha1.Rekor) bool { - return instance.Status.Phase != rhtasv1alpha1.PhaseInitialize + return instance.Status.Phase == rhtasv1alpha1.PhaseInitialize } func (i resolvePubKeyAction) Handle(ctx context.Context, instance *rhtasv1alpha1.Rekor) *action.Result { var ( - err error + err error + updated bool ) - secrets, err := i.findSecret(ctx, instance.Namespace) + secret, err := k8sutils.FindSecret(ctx, i.Client, instance.Namespace, RekorPubLabel) if err != nil { return i.Failed(err) } - if len(secrets.Items) > 0 { + if secret != nil { return i.Continue() } @@ -55,18 +55,19 @@ func (i resolvePubKeyAction) Handle(ctx context.Context, instance *rhtasv1alpha1 return i.Failed(err) } + keyName := "public" secretName := fmt.Sprintf(pubSecretNameFormat, instance.Name) labels := constants.LabelsFor(actions.ServerComponentName, secretName, instance.Name) - labels[RekorPubLabel] = "public" + labels[RekorPubLabel] = keyName - secret := k8sutils.CreateSecret(secretName, instance.Namespace, + scr := k8sutils.CreateSecret(secretName, instance.Namespace, map[string][]byte{ - "public": key, + keyName: key, }, labels) - if err = controllerutil.SetControllerReference(instance, secret, i.Client.Scheme()); err != nil { + if err = controllerutil.SetControllerReference(instance, scr, i.Client.Scheme()); err != nil { return i.Failed(fmt.Errorf("could not set controller reference for Secret: %w", err)) } - if _, err = i.Ensure(ctx, secret); err != nil { + if updated, err = i.Ensure(ctx, scr); err != nil { instance.Status.Phase = rhtasv1alpha1.PhaseError meta.SetStatusCondition(&instance.Status.Conditions, metav1.Condition{ Type: string(rhtasv1alpha1.PhaseReady), @@ -76,15 +77,12 @@ func (i resolvePubKeyAction) Handle(ctx context.Context, instance *rhtasv1alpha1 }) return i.FailedWithStatusUpdate(ctx, fmt.Errorf("could not create secret: %w", err), instance) } + if updated { + i.Recorder.Event(instance, v1.EventTypeNormal, "PublicKeySecretCreated", "New Rekor public key created: "+scr.Name) + } return i.Continue() } -func (i resolvePubKeyAction) findSecret(ctx context.Context, namespace string) (*v1.SecretList, error) { - list := &v1.SecretList{} - err := i.Client.List(ctx, list, client.InNamespace(namespace), client.MatchingLabels{RekorPubLabel: "public"}) - return list, err -} - func (i resolvePubKeyAction) resolvePubKey(instance rhtasv1alpha1.Rekor) ([]byte, error) { var ( pubKeyResponse *http.Response diff --git a/controllers/tuf/actions/generate_cert.go b/controllers/tuf/actions/generate_cert.go index 3fd1eaca..8ec55570 100644 --- a/controllers/tuf/actions/generate_cert.go +++ b/controllers/tuf/actions/generate_cert.go @@ -104,7 +104,7 @@ func (i pendingAction) handleKey(ctx context.Context, instance *rhtasv1alpha1.Tu } func (i pendingAction) discoverSecret(ctx context.Context, namespace string, key *rhtasv1alpha1.TufKey) (*rhtasv1alpha1.SecretKeySelector, error) { - labelName := constants.TufLabelNamespace + "/" + key.Name + labelName := constants.LabelNamespace + "/" + key.Name s, err := k8sutils.FindSecret(ctx, i.Client, namespace, labelName) if err != nil { return nil, err diff --git a/controllers/tuf/tuf_controller_test.go b/controllers/tuf/tuf_controller_test.go index 9fd4b0b9..fac041c3 100644 --- a/controllers/tuf/tuf_controller_test.go +++ b/controllers/tuf/tuf_controller_test.go @@ -141,10 +141,10 @@ var _ = Describe("TUF controller", func() { By("Creating ctlog secret with public key") secretLabels := map[string]string{ - constants.TufLabelNamespace + "/ctfe.pub": "public", + constants.LabelNamespace + "/ctfe.pub": "public", } maps.Copy(secretLabels, constants.LabelsFor(actions2.ComponentName, actions2.ComponentName, actions2.ComponentName)) - _ = k8sClient.Create(ctx, kubernetes.CreateSecret("ctlog", typeNamespaceName.Namespace, map[string][]byte{ + _ = k8sClient.Create(ctx, kubernetes.CreateSecret("ctlog-test", typeNamespaceName.Namespace, map[string][]byte{ "public": []byte("secret"), }, secretLabels)) diff --git a/e2e/key_autodiscovery_test.go b/e2e/key_autodiscovery_test.go new file mode 100644 index 00000000..106f3e17 --- /dev/null +++ b/e2e/key_autodiscovery_test.go @@ -0,0 +1,212 @@ +//go:build integration + +package e2e_test + +import ( + "context" + "time" + + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/securesign/operator/api/v1alpha1" + "github.com/securesign/operator/controllers/common/utils/kubernetes" + "github.com/securesign/operator/e2e/support" + "github.com/securesign/operator/e2e/support/tas" + clients "github.com/securesign/operator/e2e/support/tas/cli" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var _ = Describe("Securesign key autodiscovery test", Ordered, func() { + cli, _ := CreateClient() + ctx := context.TODO() + + targetImageName := "ttl.sh/" + uuid.New().String() + ":5m" + var namespace *v1.Namespace + var securesign *v1alpha1.Securesign + + AfterEach(func() { + if CurrentSpecReport().Failed() { + support.DumpNamespace(ctx, cli, namespace.Name) + } + }) + + BeforeAll(func() { + namespace = support.CreateTestNamespace(ctx, cli) + DeferCleanup(func() { + cli.Delete(ctx, namespace) + }) + + securesign = &v1alpha1.Securesign{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace.Name, + Name: "test", + }, + Spec: v1alpha1.SecuresignSpec{ + Rekor: v1alpha1.RekorSpec{ + ExternalAccess: v1alpha1.ExternalAccess{ + Enabled: true, + }, + Signer: v1alpha1.RekorSigner{ + KMS: "secret", + KeyRef: &v1alpha1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "my-rekor-secret", + }, + Key: "private", + }, + }, + }, + Fulcio: v1alpha1.FulcioSpec{ + ExternalAccess: v1alpha1.ExternalAccess{ + Enabled: true, + }, + Config: v1alpha1.FulcioConfig{ + OIDCIssuers: map[string]v1alpha1.OIDCIssuer{ + support.OidcIssuerUrl(): { + ClientID: support.OidcClientID(), + IssuerURL: support.OidcIssuerUrl(), + Type: "email", + }, + }}, + Certificate: v1alpha1.FulcioCert{ + PrivateKeyRef: &v1alpha1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "my-fulcio-secret", + }, + Key: "private", + }, + PrivateKeyPasswordRef: &v1alpha1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "my-fulcio-secret", + }, + Key: "password", + }, + CARef: &v1alpha1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "my-fulcio-secret", + }, + Key: "cert", + }, + }, + }, + Ctlog: v1alpha1.CTlogSpec{ + PrivateKeyRef: &v1alpha1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "my-ctlog-secret", + }, + Key: "private", + }, + RootCertificates: []v1alpha1.SecretKeySelector{ + { + LocalObjectReference: v1.LocalObjectReference{ + Name: "my-fulcio-secret", + }, + Key: "cert", + }, + }, + }, + Tuf: v1alpha1.TufSpec{ + ExternalAccess: v1alpha1.ExternalAccess{ + Enabled: true, + }, + }, + Trillian: v1alpha1.TrillianSpec{Db: v1alpha1.TrillianDB{ + Create: true, + }}, + }, + } + }) + + BeforeAll(func() { + support.PrepareImage(ctx, targetImageName) + }) + + Describe("Install with provided certificates", func() { + BeforeAll(func() { + Expect(cli.Create(ctx, initCTSecret(namespace.Name, "my-ctlog-secret"))) + Expect(cli.Create(ctx, initFulcioSecret(namespace.Name, "my-fulcio-secret"))) + Expect(cli.Create(ctx, initRekorSecret(namespace.Name, "my-rekor-secret"))) + Expect(cli.Create(ctx, securesign)).To(Succeed()) + }) + + It("All components are running", func() { + tas.VerifyRekor(ctx, cli, namespace.Name, securesign.Name) + tas.VerifyFulcio(ctx, cli, namespace.Name, securesign.Name) + tas.VerifyCTLog(ctx, cli, namespace.Name, securesign.Name) + tas.VerifyTrillian(ctx, cli, namespace.Name, securesign.Name) + tas.VerifyTuf(ctx, cli, namespace.Name, securesign.Name) + }) + + It("Verify TUF keys", func() { + tuf := tas.GetTuf(ctx, cli, namespace.Name, securesign.Name)() + Expect(tuf.Spec.Keys).To(HaveEach(WithTransform(func(k v1alpha1.TufKey) string { return k.SecretRef.Name }, Not(BeEmpty())))) + var ( + expected, actual []byte + err error + ) + for _, k := range tuf.Spec.Keys { + actual, err = kubernetes.GetSecretData(cli, namespace.Name, k.SecretRef) + Expect(err).To(Not(HaveOccurred())) + + switch k.Name { + case "fulcio_v1.crt.pem": + expected, err = kubernetes.GetSecretData(cli, namespace.Name, securesign.Spec.Fulcio.Certificate.CARef) + Expect(err).To(Not(HaveOccurred())) + break + case "rekor.pub": + expectedKeyRef := securesign.Spec.Rekor.Signer.KeyRef.DeepCopy() + expectedKeyRef.Key = "public" + expected, err = kubernetes.GetSecretData(cli, namespace.Name, expectedKeyRef) + Expect(err).To(Not(HaveOccurred())) + break + case "ctfe.pub": + expectedKeyRef := securesign.Spec.Ctlog.PrivateKeyRef.DeepCopy() + expectedKeyRef.Key = "public" + expected, err = kubernetes.GetSecretData(cli, namespace.Name, expectedKeyRef) + Expect(err).To(Not(HaveOccurred())) + break + } + Expect(expected).To(Equal(actual)) + } + }) + + It("Use cosign cli", func() { + fulcio := tas.GetFulcio(ctx, cli, namespace.Name, securesign.Name)() + Expect(fulcio).ToNot(BeNil()) + + rekor := tas.GetRekor(ctx, cli, namespace.Name, securesign.Name)() + Expect(rekor).ToNot(BeNil()) + + tuf := tas.GetTuf(ctx, cli, namespace.Name, securesign.Name)() + Expect(tuf).ToNot(BeNil()) + + oidcToken, err := support.OidcToken(ctx) + Expect(err).ToNot(HaveOccurred()) + Expect(oidcToken).ToNot(BeEmpty()) + + // sleep for a while to be sure everything has settled down + time.Sleep(time.Duration(10) * time.Second) + + Expect(clients.Execute("cosign", "initialize", "--mirror="+tuf.Status.Url, "--root="+tuf.Status.Url+"/root.json")).To(Succeed()) + + Expect(clients.Execute( + "cosign", "sign", "-y", + "--fulcio-url="+fulcio.Status.Url, + "--rekor-url="+rekor.Status.Url, + "--oidc-issuer="+support.OidcIssuerUrl(), + "--identity-token="+oidcToken, + targetImageName, + )).To(Succeed()) + + Expect(clients.Execute( + "cosign", "verify", + "--rekor-url="+rekor.Status.Url, + "--certificate-identity-regexp", ".*@redhat", + "--certificate-oidc-issuer-regexp", ".*keycloak.*", + targetImageName, + )).To(Succeed()) + }) + }) +})