From a9c17b5ef7159af56653c6a8877c05bd71f4daad Mon Sep 17 00:00:00 2001 From: Ahmad Malik Ibrahim Date: Wed, 4 Sep 2024 08:15:10 -0700 Subject: [PATCH] feat: add support for configuring signature verification pubkeys inline (#289) ## Issue Resolves https://github.com/validator-labs/validator-plugin-oci/issues/279 ## Description - Adds the `PublicKeys` field to the `SignatureVerifcation` struct. - Defaults to first checking a secret for public keys, if no secret name is provided the `PublicKeys` field is checked. - Moves `BasicAuthsDirect` and `AllPubKeysDirect` from validatorctl to the plugin. - Fixes issue in `AllPublicKeysDirect` where we were using an array instead of a map --- api/v1alpha1/ocivalidator_types.go | 36 +++++++++++++++++++ api/v1alpha1/zz_generated.deepcopy.go | 7 +++- ...ation.spectrocloud.labs_ocivalidators.yaml | 6 ++++ ...ation.spectrocloud.labs_ocivalidators.yaml | 6 ++++ .../samples/ocivalidator-public-registry.yaml | 22 +++++++++++- .../controller/ocivalidator_controller.go | 24 +++++++++++-- pkg/oci/oci.go | 2 +- 7 files changed, 98 insertions(+), 5 deletions(-) diff --git a/api/v1alpha1/ocivalidator_types.go b/api/v1alpha1/ocivalidator_types.go index e2dea7ce..520ca306 100644 --- a/api/v1alpha1/ocivalidator_types.go +++ b/api/v1alpha1/ocivalidator_types.go @@ -173,6 +173,9 @@ type SignatureVerification struct { // SecretName is the name of the Kubernetes Secret that exists in the same namespace as the OciValidator // and that contains the trusted public keys used to sign artifacts in the OciRegistryRule. SecretName string `json:"secretName" yaml:"secretName"` + + // PublicKeys is a slice of public keys used to verify the signatures of artifacts in the OciRegistryRule. + PublicKeys []string `json:"publicKeys,omitempty" yaml:"publicKeys,omitempty"` } // OciValidatorStatus defines the observed state of OciValidator. @@ -217,3 +220,36 @@ type OciValidatorList struct { func init() { SchemeBuilder.Register(&OciValidator{}, &OciValidatorList{}) } + +// BasicAuthsDirect returns a map of basic authentication details for each rule when invoked directly. +func (s *OciValidatorSpec) BasicAuthsDirect() map[string][]string { + auths := make(map[string][]string) + + for _, r := range s.OciRegistryRules { + if r.Auth.Basic != nil { + auths[r.Name()] = []string{r.Auth.Basic.Username, r.Auth.Basic.Password} + continue + } + } + + return auths +} + +// AllPubKeysDirect returns a map of public keys for each rule when invoked directly. +func (s *OciValidatorSpec) AllPubKeysDirect() map[string][][]byte { + pubKeysMap := make(map[string][][]byte) + + for _, r := range s.OciRegistryRules { + if r.SignatureVerification.PublicKeys == nil || len(r.SignatureVerification.PublicKeys) == 0 { + continue + } + + pubKeys := make([][]byte, len(r.SignatureVerification.PublicKeys)) + for i, pk := range r.SignatureVerification.PublicKeys { + pubKeys[i] = []byte(pk) + } + pubKeysMap[r.Name()] = pubKeys + } + + return pubKeysMap +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 199903db..78485bc3 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -116,7 +116,7 @@ func (in *OciRegistryRule) DeepCopyInto(out *OciRegistryRule) { } } in.Auth.DeepCopyInto(&out.Auth) - out.SignatureVerification = in.SignatureVerification + in.SignatureVerification.DeepCopyInto(&out.SignatureVerification) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OciRegistryRule. @@ -228,6 +228,11 @@ func (in *OciValidatorStatus) DeepCopy() *OciValidatorStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SignatureVerification) DeepCopyInto(out *SignatureVerification) { *out = *in + if in.PublicKeys != nil { + in, out := &in.PublicKeys, &out.PublicKeys + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SignatureVerification. diff --git a/chart/validator-plugin-oci/crds/validation.spectrocloud.labs_ocivalidators.yaml b/chart/validator-plugin-oci/crds/validation.spectrocloud.labs_ocivalidators.yaml index 2a068c43..9f43ddf4 100644 --- a/chart/validator-plugin-oci/crds/validation.spectrocloud.labs_ocivalidators.yaml +++ b/chart/validator-plugin-oci/crds/validation.spectrocloud.labs_ocivalidators.yaml @@ -144,6 +144,12 @@ spec: enum: - cosign type: string + publicKeys: + description: PublicKeys is a slice of public keys used to + verify the signatures of artifacts in the OciRegistryRule. + items: + type: string + type: array secretName: description: |- SecretName is the name of the Kubernetes Secret that exists in the same namespace as the OciValidator diff --git a/config/crd/bases/validation.spectrocloud.labs_ocivalidators.yaml b/config/crd/bases/validation.spectrocloud.labs_ocivalidators.yaml index 2a068c43..9f43ddf4 100644 --- a/config/crd/bases/validation.spectrocloud.labs_ocivalidators.yaml +++ b/config/crd/bases/validation.spectrocloud.labs_ocivalidators.yaml @@ -144,6 +144,12 @@ spec: enum: - cosign type: string + publicKeys: + description: PublicKeys is a slice of public keys used to + verify the signatures of artifacts in the OciRegistryRule. + items: + type: string + type: array secretName: description: |- SecretName is the name of the Kubernetes Secret that exists in the same namespace as the OciValidator diff --git a/config/samples/ocivalidator-public-registry.yaml b/config/samples/ocivalidator-public-registry.yaml index e274d543..a0596e8d 100644 --- a/config/samples/ocivalidator-public-registry.yaml +++ b/config/samples/ocivalidator-public-registry.yaml @@ -19,7 +19,7 @@ spec: - ref: "ahmadibraspectrocloud/kubebuilder-cron" # public oci registry with signature verification enabled - - name: "public oci registry with signature verification enabled" + - name: "public oci registry with signature verification enabled via a pubkey secret" host: "registry.hub.docker.com" validationType: "fast" artifacts: @@ -27,6 +27,26 @@ spec: signatureVerification: secretName: "cosign-public-keys" + # public oci registry with signature verification enabled + - name: "public oci registry with signature verification enabled via inline pubkey" + host: "registry.hub.docker.com" + validationType: "fast" + artifacts: + - ref: "ahmadibraspectrocloud/kb-guestbook:signed" + signatureVerification: + secretName: "" + publicKeys: + - |- + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKPuCo9AmJCpqGWhefjbhkFcr1GA3 + iNa765seE3jYC3MGUe5h52393Dhy7B5bXGsg6EfPpNYamlAEWjxCpHF3Lg== + -----END PUBLIC KEY----- + - |- + -----BEGIN PUBLIC KEY----- + AnotherPublicKey + -----END PUBLIC KEY----- + + # public ecr registry artifact referenced by default "latest" tag - name: "public ecr registry" host: "public.ecr.aws" diff --git a/internal/controller/ocivalidator_controller.go b/internal/controller/ocivalidator_controller.go index 57c1225f..d6d68808 100644 --- a/internal/controller/ocivalidator_controller.go +++ b/internal/controller/ocivalidator_controller.go @@ -181,11 +181,21 @@ func (r *OciValidatorReconciler) secretKeyAuth(req ctrl.Request, rule v1alpha1.O return username, password, nil } +// signaturePubKeys retrieves the public keys that are used for signature verification. +// If a secretName is provided in the SignatureVerification field, then the secret is fetched and the pub keys are retrieved from the secret. +// Otherwise, the public keys are retrieved from the inline PublicKeys field if provided. func (r *OciValidatorReconciler) signaturePubKeys(req ctrl.Request, rule v1alpha1.OciRegistryRule) ([][]byte, error) { - if rule.SignatureVerification.SecretName == "" { - return nil, nil + if rule.SignatureVerification.SecretName != "" { + return r.signaturePubKeysSecret(req, rule) } + if rule.SignatureVerification.PublicKeys != nil && len(rule.SignatureVerification.PublicKeys) > 0 { + return r.signaturePubKeysInline(rule.SignatureVerification.PublicKeys), nil + } + + return nil, nil +} +func (r *OciValidatorReconciler) signaturePubKeysSecret(req ctrl.Request, rule v1alpha1.OciRegistryRule) ([][]byte, error) { pubKeysSecret := &corev1.Secret{} nn := ktypes.NamespacedName{Name: rule.SignatureVerification.SecretName, Namespace: req.Namespace} @@ -207,3 +217,13 @@ func (r *OciValidatorReconciler) signaturePubKeys(req ctrl.Request, rule v1alpha return pubKeys, nil } + +func (r *OciValidatorReconciler) signaturePubKeysInline(pKeys []string) [][]byte { + pubKeys := make([][]byte, len(pKeys)) + + for i, key := range pKeys { + pubKeys[i] = []byte(key) + } + + return pubKeys +} diff --git a/pkg/oci/oci.go b/pkg/oci/oci.go index d6cf09ed..d768e772 100644 --- a/pkg/oci/oci.go +++ b/pkg/oci/oci.go @@ -106,7 +106,7 @@ func (s *RuleService) validateReference(ctx context.Context, ref name.Reference, } // verify image signature (optional) - if sv.SecretName != "" { + if sv.SecretName != "" || len(sv.PublicKeys) > 0 { verifyDetails, verifyErrs := s.ociClient.VerifySignature(ctx, ref) if len(verifyDetails) > 0 { details = append(details, verifyDetails...)