From e13af40a37154da310bc3819b5467eb4da6fbc68 Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Sat, 4 May 2024 08:46:52 +0800 Subject: [PATCH] feat: add NamespacedVerifier CRD [multi-tenancy PR 11] (#1428) Co-authored-by: Akash Singhal --- PROJECT | 8 + api/unversioned/namespacedverifier_types.go | 78 ++++ api/unversioned/zz_generated.deepcopy.go | 79 ++++ api/v1beta1/namespacedverifier_types.go | 93 +++++ api/v1beta1/zz_generated.conversion.go | 148 ++++++++ api/v1beta1/zz_generated.deepcopy.go | 95 +++++ ...acedverifier-customresourcedefinition.yaml | 97 +++++ .../ratify-manager-role-clusterrole.yaml | 26 ++ ...atify.deislabs.io_namespacedverifiers.yaml | 97 +++++ config/crd/kustomization.yaml | 3 + .../cainjection_in_namespacedverifiers.yaml | 7 + .../webhook_in_namespacedverifiers.yaml | 16 + .../rbac/namespacedverifier_editor_role.yaml | 31 ++ .../rbac/namespacedverifier_viewer_role.yaml | 27 ++ config/rbac/role.yaml | 26 ++ .../config_v1alpha1_store_oras_http.yaml | 0 .../config_v1alpha1_verifier_notation.yaml | 0 ...eta1_verifier_complete_licensechecker.yaml | 0 .../config_v1beta1_verifier_cosign.yaml | 0 ...onfig_v1beta1_verifier_cosign_keyless.yaml | 0 ...config_v1beta1_verifier_cosign_legacy.yaml | 0 .../config_v1beta1_verifier_dynamic.yaml | 0 .../config_v1beta1_verifier_notation.yaml | 0 ..._v1beta1_verifier_notation_kmprovider.yaml | 0 ...erifier_notation_specificnskmprovider.yaml | 0 ...beta1_verifier_partial_licensechecker.yaml | 0 .../config_v1beta1_verifier_sbom.yaml | 0 .../config_v1beta1_verifier_sbom_deny.yaml | 0 ...nfig_v1beta1_verifier_schemavalidator.yaml | 0 ..._v1beta1_verifier_schemavalidator_bad.yaml | 0 ..._v1beta1_verifier_vulnerabilityreport.yaml | 0 ...v1beta1_verifier_vulnerabilityreport2.yaml | 0 ...eta1_verifier_complete_licensechecker.yaml | 24 ++ .../config_v1beta1_verifier_cosign.yaml | 14 + ...onfig_v1beta1_verifier_cosign_keyless.yaml | 9 + ...config_v1beta1_verifier_cosign_legacy.yaml | 9 + .../config_v1beta1_verifier_dynamic.yaml | 9 + .../config_v1beta1_verifier_notation.yaml | 23 ++ ..._v1beta1_verifier_notation_kmprovider.yaml | 27 ++ ...erifier_notation_specificnskmprovider.yaml | 23 ++ ...beta1_verifier_partial_licensechecker.yaml | 10 + .../config_v1beta1_verifier_sbom.yaml | 9 + .../config_v1beta1_verifier_sbom_deny.yaml | 15 + ...nfig_v1beta1_verifier_schemavalidator.yaml | 10 + ..._v1beta1_verifier_schemavalidator_bad.yaml | 10 + ..._v1beta1_verifier_vulnerabilityreport.yaml | 14 + ...v1beta1_verifier_vulnerabilityreport2.yaml | 11 + .../verifier_controller.go | 64 +--- .../verifier_controller_test.go | 161 ++++++-- pkg/controllers/constants.go | 16 - .../namespaceresource/verifier_controller.go | 118 ++++++ .../verifier_controller_test.go | 358 ++++++++++++++++++ pkg/controllers/utils/verifier.go | 73 ++++ pkg/controllers/utils/verifier_test.go | 121 ++++++ pkg/customresources/verifiers/api.go | 6 - pkg/customresources/verifiers/verifiers.go | 51 ++- .../verifiers/verifiers_test.go | 33 +- pkg/manager/manager.go | 11 +- test/bats/azure-test.bats | 24 +- test/bats/base-test.bats | 30 +- test/bats/plugin-test.bats | 30 +- 61 files changed, 1953 insertions(+), 191 deletions(-) create mode 100644 api/unversioned/namespacedverifier_types.go create mode 100644 api/v1beta1/namespacedverifier_types.go create mode 100644 charts/ratify/crds/namespacedverifier-customresourcedefinition.yaml create mode 100644 config/crd/bases/config.ratify.deislabs.io_namespacedverifiers.yaml create mode 100644 config/crd/patches/cainjection_in_namespacedverifiers.yaml create mode 100644 config/crd/patches/webhook_in_namespacedverifiers.yaml create mode 100644 config/rbac/namespacedverifier_editor_role.yaml create mode 100644 config/rbac/namespacedverifier_viewer_role.yaml rename config/samples/{ => clustered/verifier}/config_v1alpha1_store_oras_http.yaml (100%) rename config/samples/{ => clustered/verifier}/config_v1alpha1_verifier_notation.yaml (100%) rename config/samples/{ => clustered/verifier}/config_v1beta1_verifier_complete_licensechecker.yaml (100%) rename config/samples/{ => clustered/verifier}/config_v1beta1_verifier_cosign.yaml (100%) rename config/samples/{ => clustered/verifier}/config_v1beta1_verifier_cosign_keyless.yaml (100%) rename config/samples/{ => clustered/verifier}/config_v1beta1_verifier_cosign_legacy.yaml (100%) rename config/samples/{ => clustered/verifier}/config_v1beta1_verifier_dynamic.yaml (100%) rename config/samples/{ => clustered/verifier}/config_v1beta1_verifier_notation.yaml (100%) rename config/samples/{ => clustered/verifier}/config_v1beta1_verifier_notation_kmprovider.yaml (100%) rename config/samples/{ => clustered/verifier}/config_v1beta1_verifier_notation_specificnskmprovider.yaml (100%) rename config/samples/{ => clustered/verifier}/config_v1beta1_verifier_partial_licensechecker.yaml (100%) rename config/samples/{ => clustered/verifier}/config_v1beta1_verifier_sbom.yaml (100%) rename config/samples/{ => clustered/verifier}/config_v1beta1_verifier_sbom_deny.yaml (100%) rename config/samples/{ => clustered/verifier}/config_v1beta1_verifier_schemavalidator.yaml (100%) rename config/samples/{ => clustered/verifier}/config_v1beta1_verifier_schemavalidator_bad.yaml (100%) rename config/samples/{ => clustered/verifier}/config_v1beta1_verifier_vulnerabilityreport.yaml (100%) rename config/samples/{ => clustered/verifier}/config_v1beta1_verifier_vulnerabilityreport2.yaml (100%) create mode 100644 config/samples/namespaced/verifier/config_v1beta1_verifier_complete_licensechecker.yaml create mode 100644 config/samples/namespaced/verifier/config_v1beta1_verifier_cosign.yaml create mode 100644 config/samples/namespaced/verifier/config_v1beta1_verifier_cosign_keyless.yaml create mode 100644 config/samples/namespaced/verifier/config_v1beta1_verifier_cosign_legacy.yaml create mode 100644 config/samples/namespaced/verifier/config_v1beta1_verifier_dynamic.yaml create mode 100644 config/samples/namespaced/verifier/config_v1beta1_verifier_notation.yaml create mode 100644 config/samples/namespaced/verifier/config_v1beta1_verifier_notation_kmprovider.yaml create mode 100644 config/samples/namespaced/verifier/config_v1beta1_verifier_notation_specificnskmprovider.yaml create mode 100644 config/samples/namespaced/verifier/config_v1beta1_verifier_partial_licensechecker.yaml create mode 100644 config/samples/namespaced/verifier/config_v1beta1_verifier_sbom.yaml create mode 100644 config/samples/namespaced/verifier/config_v1beta1_verifier_sbom_deny.yaml create mode 100644 config/samples/namespaced/verifier/config_v1beta1_verifier_schemavalidator.yaml create mode 100644 config/samples/namespaced/verifier/config_v1beta1_verifier_schemavalidator_bad.yaml create mode 100644 config/samples/namespaced/verifier/config_v1beta1_verifier_vulnerabilityreport.yaml create mode 100644 config/samples/namespaced/verifier/config_v1beta1_verifier_vulnerabilityreport2.yaml rename pkg/controllers/{ => clusterresource}/verifier_controller.go (63%) rename pkg/controllers/{ => clusterresource}/verifier_controller_test.go (56%) delete mode 100644 pkg/controllers/constants.go create mode 100644 pkg/controllers/namespaceresource/verifier_controller.go create mode 100644 pkg/controllers/namespaceresource/verifier_controller_test.go create mode 100644 pkg/controllers/utils/verifier.go create mode 100644 pkg/controllers/utils/verifier_test.go diff --git a/PROJECT b/PROJECT index fca492fc9..5841a8f99 100644 --- a/PROJECT +++ b/PROJECT @@ -104,4 +104,12 @@ resources: kind: NamespacedKeyManagementProvider path: github.com/deislabs/ratify/api/v1beta1 version: v1beta1 +- api: + crdVersion: v1 + namespaced: true + domain: ratify.deislabs.io + group: config + kind: NamespacedVerifier + path: github.com/deislabs/ratify/api/v1beta1 + version: v1beta1 version: "3" diff --git a/api/unversioned/namespacedverifier_types.go b/api/unversioned/namespacedverifier_types.go new file mode 100644 index 000000000..7e196a233 --- /dev/null +++ b/api/unversioned/namespacedverifier_types.go @@ -0,0 +1,78 @@ +/* +Copyright The Ratify Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// +kubebuilder:skip +package unversioned + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// NamespacedVerifierSpec defines the desired state of NamespacedVerifier +type NamespacedVerifierSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Name of the verifier + Name string `json:"name"` + + // Version of the verifier plugin. Optional + Version string `json:"version,omitempty"` + + // The type of artifact this verifier handles + ArtifactTypes string `json:"artifactTypes"` + + // # Optional. URL/file path + Address string `json:"address,omitempty"` + + // OCI Artifact source to download the plugin from, optional + Source *PluginSource `json:"source,omitempty"` + + // Parameters for this verifier + Parameters runtime.RawExtension `json:"parameters,omitempty"` +} + +// NamespacedVerifierStatus defines the observed state of NamespacedVerifier +type NamespacedVerifierStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Is successful in finding the plugin + IsSuccess bool `json:"issuccess"` + // Error message if operation was unsuccessful + // +optional + Error string `json:"error,omitempty"` + // Truncated error message if the message is too long + // +optional + BriefError string `json:"brieferror,omitempty"` +} + +// NamespacedVerifier is the Schema for the namespacedverifiers API +type NamespacedVerifier struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NamespacedVerifierSpec `json:"spec,omitempty"` + Status NamespacedVerifierStatus `json:"status,omitempty"` +} + +// NamespacedVerifierList contains a list of NamespacedVerifier +type NamespacedVerifierList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NamespacedVerifier `json:"items"` +} diff --git a/api/unversioned/zz_generated.deepcopy.go b/api/unversioned/zz_generated.deepcopy.go index 63d22ac1f..bb9bacfb8 100644 --- a/api/unversioned/zz_generated.deepcopy.go +++ b/api/unversioned/zz_generated.deepcopy.go @@ -413,6 +413,85 @@ func (in *NamespacedStoreStatus) DeepCopy() *NamespacedStoreStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedVerifier) DeepCopyInto(out *NamespacedVerifier) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedVerifier. +func (in *NamespacedVerifier) DeepCopy() *NamespacedVerifier { + if in == nil { + return nil + } + out := new(NamespacedVerifier) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedVerifierList) DeepCopyInto(out *NamespacedVerifierList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NamespacedVerifier, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedVerifierList. +func (in *NamespacedVerifierList) DeepCopy() *NamespacedVerifierList { + if in == nil { + return nil + } + out := new(NamespacedVerifierList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedVerifierSpec) DeepCopyInto(out *NamespacedVerifierSpec) { + *out = *in + if in.Source != nil { + in, out := &in.Source, &out.Source + *out = new(PluginSource) + (*in).DeepCopyInto(*out) + } + in.Parameters.DeepCopyInto(&out.Parameters) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedVerifierSpec. +func (in *NamespacedVerifierSpec) DeepCopy() *NamespacedVerifierSpec { + if in == nil { + return nil + } + out := new(NamespacedVerifierSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedVerifierStatus) DeepCopyInto(out *NamespacedVerifierStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedVerifierStatus. +func (in *NamespacedVerifierStatus) DeepCopy() *NamespacedVerifierStatus { + if in == nil { + return nil + } + out := new(NamespacedVerifierStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PluginSource) DeepCopyInto(out *PluginSource) { *out = *in diff --git a/api/v1beta1/namespacedverifier_types.go b/api/v1beta1/namespacedverifier_types.go new file mode 100644 index 000000000..31e1c8e9f --- /dev/null +++ b/api/v1beta1/namespacedverifier_types.go @@ -0,0 +1,93 @@ +/* +Copyright The Ratify Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// NamespacedVerifierSpec defines the desired state of NamespacedVerifier +type NamespacedVerifierSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Name of the verifier + Name string `json:"name"` + + // Version of the verifier plugin. Optional + Version string `json:"version,omitempty"` + + // The type of artifact this verifier handles + ArtifactTypes string `json:"artifactTypes"` + + // # Optional. URL/file path + Address string `json:"address,omitempty"` + + // OCI Artifact source to download the plugin from, optional + Source *PluginSource `json:"source,omitempty"` + + // +kubebuilder:pruning:PreserveUnknownFields + // Parameters for this verifier + Parameters runtime.RawExtension `json:"parameters,omitempty"` +} + +// NamespacedVerifierStatus defines the observed state of NamespacedVerifier +type NamespacedVerifierStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Is successful in finding the plugin + IsSuccess bool `json:"issuccess"` + // Error message if operation was unsuccessful + // +optional + Error string `json:"error,omitempty"` + // Truncated error message if the message is too long + // +optional + BriefError string `json:"brieferror,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope="Namespaced" +// +kubebuilder:subresource:status +// +kubebuilder:storageversion +// +kubebuilder:printcolumn:name="IsSuccess",type=boolean,JSONPath=`.status.issuccess` +// +kubebuilder:printcolumn:name="Error",type=string,JSONPath=`.status.brieferror` +// NamespacedVerifier is the Schema for the namespacedverifiers API +type NamespacedVerifier struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NamespacedVerifierSpec `json:"spec,omitempty"` + Status NamespacedVerifierStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:storageversion +// NamespacedVerifierList contains a list of NamespacedVerifier +type NamespacedVerifierList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NamespacedVerifier `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NamespacedVerifier{}, &NamespacedVerifierList{}) +} diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index 9a16b46e3..5b0bd36fb 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -236,6 +236,46 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*NamespacedVerifier)(nil), (*unversioned.NamespacedVerifier)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedVerifier_To_unversioned_NamespacedVerifier(a.(*NamespacedVerifier), b.(*unversioned.NamespacedVerifier), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedVerifier)(nil), (*NamespacedVerifier)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedVerifier_To_v1beta1_NamespacedVerifier(a.(*unversioned.NamespacedVerifier), b.(*NamespacedVerifier), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedVerifierList)(nil), (*unversioned.NamespacedVerifierList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedVerifierList_To_unversioned_NamespacedVerifierList(a.(*NamespacedVerifierList), b.(*unversioned.NamespacedVerifierList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedVerifierList)(nil), (*NamespacedVerifierList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedVerifierList_To_v1beta1_NamespacedVerifierList(a.(*unversioned.NamespacedVerifierList), b.(*NamespacedVerifierList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedVerifierSpec)(nil), (*unversioned.NamespacedVerifierSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedVerifierSpec_To_unversioned_NamespacedVerifierSpec(a.(*NamespacedVerifierSpec), b.(*unversioned.NamespacedVerifierSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedVerifierSpec)(nil), (*NamespacedVerifierSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedVerifierSpec_To_v1beta1_NamespacedVerifierSpec(a.(*unversioned.NamespacedVerifierSpec), b.(*NamespacedVerifierSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedVerifierStatus)(nil), (*unversioned.NamespacedVerifierStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedVerifierStatus_To_unversioned_NamespacedVerifierStatus(a.(*NamespacedVerifierStatus), b.(*unversioned.NamespacedVerifierStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedVerifierStatus)(nil), (*NamespacedVerifierStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedVerifierStatus_To_v1beta1_NamespacedVerifierStatus(a.(*unversioned.NamespacedVerifierStatus), b.(*NamespacedVerifierStatus), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*PluginSource)(nil), (*unversioned.PluginSource)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_PluginSource_To_unversioned_PluginSource(a.(*PluginSource), b.(*unversioned.PluginSource), scope) }); err != nil { @@ -887,6 +927,114 @@ func Convert_unversioned_NamespacedStoreStatus_To_v1beta1_NamespacedStoreStatus( return autoConvert_unversioned_NamespacedStoreStatus_To_v1beta1_NamespacedStoreStatus(in, out, s) } +func autoConvert_v1beta1_NamespacedVerifier_To_unversioned_NamespacedVerifier(in *NamespacedVerifier, out *unversioned.NamespacedVerifier, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_NamespacedVerifierSpec_To_unversioned_NamespacedVerifierSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_NamespacedVerifierStatus_To_unversioned_NamespacedVerifierStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_NamespacedVerifier_To_unversioned_NamespacedVerifier is an autogenerated conversion function. +func Convert_v1beta1_NamespacedVerifier_To_unversioned_NamespacedVerifier(in *NamespacedVerifier, out *unversioned.NamespacedVerifier, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedVerifier_To_unversioned_NamespacedVerifier(in, out, s) +} + +func autoConvert_unversioned_NamespacedVerifier_To_v1beta1_NamespacedVerifier(in *unversioned.NamespacedVerifier, out *NamespacedVerifier, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_unversioned_NamespacedVerifierSpec_To_v1beta1_NamespacedVerifierSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_unversioned_NamespacedVerifierStatus_To_v1beta1_NamespacedVerifierStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_unversioned_NamespacedVerifier_To_v1beta1_NamespacedVerifier is an autogenerated conversion function. +func Convert_unversioned_NamespacedVerifier_To_v1beta1_NamespacedVerifier(in *unversioned.NamespacedVerifier, out *NamespacedVerifier, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedVerifier_To_v1beta1_NamespacedVerifier(in, out, s) +} + +func autoConvert_v1beta1_NamespacedVerifierList_To_unversioned_NamespacedVerifierList(in *NamespacedVerifierList, out *unversioned.NamespacedVerifierList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]unversioned.NamespacedVerifier)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_NamespacedVerifierList_To_unversioned_NamespacedVerifierList is an autogenerated conversion function. +func Convert_v1beta1_NamespacedVerifierList_To_unversioned_NamespacedVerifierList(in *NamespacedVerifierList, out *unversioned.NamespacedVerifierList, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedVerifierList_To_unversioned_NamespacedVerifierList(in, out, s) +} + +func autoConvert_unversioned_NamespacedVerifierList_To_v1beta1_NamespacedVerifierList(in *unversioned.NamespacedVerifierList, out *NamespacedVerifierList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]NamespacedVerifier)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_unversioned_NamespacedVerifierList_To_v1beta1_NamespacedVerifierList is an autogenerated conversion function. +func Convert_unversioned_NamespacedVerifierList_To_v1beta1_NamespacedVerifierList(in *unversioned.NamespacedVerifierList, out *NamespacedVerifierList, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedVerifierList_To_v1beta1_NamespacedVerifierList(in, out, s) +} + +func autoConvert_v1beta1_NamespacedVerifierSpec_To_unversioned_NamespacedVerifierSpec(in *NamespacedVerifierSpec, out *unversioned.NamespacedVerifierSpec, s conversion.Scope) error { + out.Name = in.Name + out.Version = in.Version + out.ArtifactTypes = in.ArtifactTypes + out.Address = in.Address + out.Source = (*unversioned.PluginSource)(unsafe.Pointer(in.Source)) + out.Parameters = in.Parameters + return nil +} + +// Convert_v1beta1_NamespacedVerifierSpec_To_unversioned_NamespacedVerifierSpec is an autogenerated conversion function. +func Convert_v1beta1_NamespacedVerifierSpec_To_unversioned_NamespacedVerifierSpec(in *NamespacedVerifierSpec, out *unversioned.NamespacedVerifierSpec, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedVerifierSpec_To_unversioned_NamespacedVerifierSpec(in, out, s) +} + +func autoConvert_unversioned_NamespacedVerifierSpec_To_v1beta1_NamespacedVerifierSpec(in *unversioned.NamespacedVerifierSpec, out *NamespacedVerifierSpec, s conversion.Scope) error { + out.Name = in.Name + out.Version = in.Version + out.ArtifactTypes = in.ArtifactTypes + out.Address = in.Address + out.Source = (*PluginSource)(unsafe.Pointer(in.Source)) + out.Parameters = in.Parameters + return nil +} + +// Convert_unversioned_NamespacedVerifierSpec_To_v1beta1_NamespacedVerifierSpec is an autogenerated conversion function. +func Convert_unversioned_NamespacedVerifierSpec_To_v1beta1_NamespacedVerifierSpec(in *unversioned.NamespacedVerifierSpec, out *NamespacedVerifierSpec, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedVerifierSpec_To_v1beta1_NamespacedVerifierSpec(in, out, s) +} + +func autoConvert_v1beta1_NamespacedVerifierStatus_To_unversioned_NamespacedVerifierStatus(in *NamespacedVerifierStatus, out *unversioned.NamespacedVerifierStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + return nil +} + +// Convert_v1beta1_NamespacedVerifierStatus_To_unversioned_NamespacedVerifierStatus is an autogenerated conversion function. +func Convert_v1beta1_NamespacedVerifierStatus_To_unversioned_NamespacedVerifierStatus(in *NamespacedVerifierStatus, out *unversioned.NamespacedVerifierStatus, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedVerifierStatus_To_unversioned_NamespacedVerifierStatus(in, out, s) +} + +func autoConvert_unversioned_NamespacedVerifierStatus_To_v1beta1_NamespacedVerifierStatus(in *unversioned.NamespacedVerifierStatus, out *NamespacedVerifierStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + return nil +} + +// Convert_unversioned_NamespacedVerifierStatus_To_v1beta1_NamespacedVerifierStatus is an autogenerated conversion function. +func Convert_unversioned_NamespacedVerifierStatus_To_v1beta1_NamespacedVerifierStatus(in *unversioned.NamespacedVerifierStatus, out *NamespacedVerifierStatus, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedVerifierStatus_To_v1beta1_NamespacedVerifierStatus(in, out, s) +} + func autoConvert_v1beta1_PluginSource_To_unversioned_PluginSource(in *PluginSource, out *unversioned.PluginSource, s conversion.Scope) error { out.Artifact = in.Artifact out.AuthProvider = in.AuthProvider diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 05aac89d1..96754b97d 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -495,6 +495,101 @@ func (in *NamespacedStoreStatus) DeepCopy() *NamespacedStoreStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedVerifier) DeepCopyInto(out *NamespacedVerifier) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedVerifier. +func (in *NamespacedVerifier) DeepCopy() *NamespacedVerifier { + if in == nil { + return nil + } + out := new(NamespacedVerifier) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedVerifier) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedVerifierList) DeepCopyInto(out *NamespacedVerifierList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NamespacedVerifier, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedVerifierList. +func (in *NamespacedVerifierList) DeepCopy() *NamespacedVerifierList { + if in == nil { + return nil + } + out := new(NamespacedVerifierList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedVerifierList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedVerifierSpec) DeepCopyInto(out *NamespacedVerifierSpec) { + *out = *in + if in.Source != nil { + in, out := &in.Source, &out.Source + *out = new(PluginSource) + (*in).DeepCopyInto(*out) + } + in.Parameters.DeepCopyInto(&out.Parameters) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedVerifierSpec. +func (in *NamespacedVerifierSpec) DeepCopy() *NamespacedVerifierSpec { + if in == nil { + return nil + } + out := new(NamespacedVerifierSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedVerifierStatus) DeepCopyInto(out *NamespacedVerifierStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedVerifierStatus. +func (in *NamespacedVerifierStatus) DeepCopy() *NamespacedVerifierStatus { + if in == nil { + return nil + } + out := new(NamespacedVerifierStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PluginSource) DeepCopyInto(out *PluginSource) { *out = *in diff --git a/charts/ratify/crds/namespacedverifier-customresourcedefinition.yaml b/charts/ratify/crds/namespacedverifier-customresourcedefinition.yaml new file mode 100644 index 000000000..d0980163c --- /dev/null +++ b/charts/ratify/crds/namespacedverifier-customresourcedefinition.yaml @@ -0,0 +1,97 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: namespacedverifiers.config.ratify.deislabs.io +spec: + group: config.ratify.deislabs.io + names: + kind: NamespacedVerifier + listKind: NamespacedVerifierList + plural: namespacedverifiers + singular: namespacedverifier + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: NamespacedVerifier is the Schema for the namespacedverifiers + API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: NamespacedVerifierSpec defines the desired state of NamespacedVerifier + properties: + address: + description: '# Optional. URL/file path' + type: string + artifactTypes: + description: The type of artifact this verifier handles + type: string + name: + description: Name of the verifier + type: string + parameters: + description: Parameters for this verifier + type: object + x-kubernetes-preserve-unknown-fields: true + source: + description: OCI Artifact source to download the plugin from, optional + properties: + artifact: + description: OCI Artifact source to download the plugin from + type: string + authProvider: + description: AuthProvider to use to authenticate to the OCI Artifact + source, optional + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + version: + description: Version of the verifier plugin. Optional + type: string + required: + - artifactTypes + - name + type: object + status: + description: NamespacedVerifierStatus defines the observed state of NamespacedVerifier + properties: + brieferror: + description: Truncated error message if the message is too long + type: string + error: + description: Error message if operation was unsuccessful + type: string + issuccess: + description: Is successful in finding the plugin + type: boolean + required: + - issuccess + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/ratify/templates/ratify-manager-role-clusterrole.yaml b/charts/ratify/templates/ratify-manager-role-clusterrole.yaml index 2653f15f1..515843f9c 100644 --- a/charts/ratify/templates/ratify-manager-role-clusterrole.yaml +++ b/charts/ratify/templates/ratify-manager-role-clusterrole.yaml @@ -83,6 +83,32 @@ rules: - get - patch - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers/finalizers + verbs: + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers/status + verbs: + - get + - patch + - update - apiGroups: - config.ratify.deislabs.io resources: diff --git a/config/crd/bases/config.ratify.deislabs.io_namespacedverifiers.yaml b/config/crd/bases/config.ratify.deislabs.io_namespacedverifiers.yaml new file mode 100644 index 000000000..d0980163c --- /dev/null +++ b/config/crd/bases/config.ratify.deislabs.io_namespacedverifiers.yaml @@ -0,0 +1,97 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: namespacedverifiers.config.ratify.deislabs.io +spec: + group: config.ratify.deislabs.io + names: + kind: NamespacedVerifier + listKind: NamespacedVerifierList + plural: namespacedverifiers + singular: namespacedverifier + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: NamespacedVerifier is the Schema for the namespacedverifiers + API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: NamespacedVerifierSpec defines the desired state of NamespacedVerifier + properties: + address: + description: '# Optional. URL/file path' + type: string + artifactTypes: + description: The type of artifact this verifier handles + type: string + name: + description: Name of the verifier + type: string + parameters: + description: Parameters for this verifier + type: object + x-kubernetes-preserve-unknown-fields: true + source: + description: OCI Artifact source to download the plugin from, optional + properties: + artifact: + description: OCI Artifact source to download the plugin from + type: string + authProvider: + description: AuthProvider to use to authenticate to the OCI Artifact + source, optional + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + version: + description: Version of the verifier plugin. Optional + type: string + required: + - artifactTypes + - name + type: object + status: + description: NamespacedVerifierStatus defines the observed state of NamespacedVerifier + properties: + brieferror: + description: Truncated error message if the message is too long + type: string + error: + description: Error message if operation was unsuccessful + type: string + issuccess: + description: Is successful in finding the plugin + type: boolean + required: + - issuccess + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 112b1c6c5..83786a4e6 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -10,6 +10,7 @@ resources: - bases/config.ratify.deislabs.io_namespacedpolicies.yaml - bases/config.ratify.deislabs.io_namespacedstores.yaml - bases/config.ratify.deislabs.io_namespacedkeymanagementproviders.yaml + - bases/config.ratify.deislabs.io_namespacedverifiers.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -23,6 +24,7 @@ patchesStrategicMerge: #- patches/webhook_in_namespacedpolicies.yaml #- patches/webhook_in_namespacedstores.yaml #- patches/webhook_in_namespacedkeymanagementproviders.yaml + #- patches/webhook_in_namespacedverifiers.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -35,6 +37,7 @@ patchesStrategicMerge: #- patches/cainjection_in_namespacedpolicies.yaml #- patches/cainjection_in_namespacedstores.yaml #- patches/cainjection_in_namespacedkeymanagementproviders.yaml + #- patches/cainjection_in_namespacedverifiers.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_namespacedverifiers.yaml b/config/crd/patches/cainjection_in_namespacedverifiers.yaml new file mode 100644 index 000000000..90b322c8d --- /dev/null +++ b/config/crd/patches/cainjection_in_namespacedverifiers.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: namespacedverifiers.config.ratify.deislabs.io diff --git a/config/crd/patches/webhook_in_namespacedverifiers.yaml b/config/crd/patches/webhook_in_namespacedverifiers.yaml new file mode 100644 index 000000000..789395102 --- /dev/null +++ b/config/crd/patches/webhook_in_namespacedverifiers.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: namespacedverifiers.config.ratify.deislabs.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/rbac/namespacedverifier_editor_role.yaml b/config/rbac/namespacedverifier_editor_role.yaml new file mode 100644 index 000000000..d4ff9be23 --- /dev/null +++ b/config/rbac/namespacedverifier_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit namespacedverifiers. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: namespacedverifier-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: ratify + app.kubernetes.io/part-of: ratify + app.kubernetes.io/managed-by: kustomize + name: namespacedverifier-editor-role +rules: +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers/status + verbs: + - get diff --git a/config/rbac/namespacedverifier_viewer_role.yaml b/config/rbac/namespacedverifier_viewer_role.yaml new file mode 100644 index 000000000..8a63ab316 --- /dev/null +++ b/config/rbac/namespacedverifier_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view namespacedverifiers. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: namespacedverifier-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: ratify + app.kubernetes.io/part-of: ratify + app.kubernetes.io/managed-by: kustomize + name: namespacedverifier-viewer-role +rules: +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers + verbs: + - get + - list + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index d7521fbb3..f0f702745 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -212,4 +212,30 @@ rules: verbs: - get - patch + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers/finalizers + verbs: + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers/status + verbs: + - get + - patch - update \ No newline at end of file diff --git a/config/samples/config_v1alpha1_store_oras_http.yaml b/config/samples/clustered/verifier/config_v1alpha1_store_oras_http.yaml similarity index 100% rename from config/samples/config_v1alpha1_store_oras_http.yaml rename to config/samples/clustered/verifier/config_v1alpha1_store_oras_http.yaml diff --git a/config/samples/config_v1alpha1_verifier_notation.yaml b/config/samples/clustered/verifier/config_v1alpha1_verifier_notation.yaml similarity index 100% rename from config/samples/config_v1alpha1_verifier_notation.yaml rename to config/samples/clustered/verifier/config_v1alpha1_verifier_notation.yaml diff --git a/config/samples/config_v1beta1_verifier_complete_licensechecker.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_complete_licensechecker.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml diff --git a/config/samples/config_v1beta1_verifier_cosign.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_cosign.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_cosign.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_cosign.yaml diff --git a/config/samples/config_v1beta1_verifier_cosign_keyless.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_cosign_keyless.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_cosign_keyless.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_cosign_keyless.yaml diff --git a/config/samples/config_v1beta1_verifier_cosign_legacy.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_cosign_legacy.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_cosign_legacy.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_cosign_legacy.yaml diff --git a/config/samples/config_v1beta1_verifier_dynamic.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_dynamic.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_dynamic.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_dynamic.yaml diff --git a/config/samples/config_v1beta1_verifier_notation.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_notation.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml diff --git a/config/samples/config_v1beta1_verifier_notation_kmprovider.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_notation_kmprovider.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_notation_kmprovider.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_notation_kmprovider.yaml diff --git a/config/samples/config_v1beta1_verifier_notation_specificnskmprovider.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_notation_specificnskmprovider.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_notation_specificnskmprovider.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_notation_specificnskmprovider.yaml diff --git a/config/samples/config_v1beta1_verifier_partial_licensechecker.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_partial_licensechecker.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_partial_licensechecker.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_partial_licensechecker.yaml diff --git a/config/samples/config_v1beta1_verifier_sbom.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_sbom.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_sbom.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_sbom.yaml diff --git a/config/samples/config_v1beta1_verifier_sbom_deny.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_sbom_deny.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_sbom_deny.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_sbom_deny.yaml diff --git a/config/samples/config_v1beta1_verifier_schemavalidator.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_schemavalidator.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator.yaml diff --git a/config/samples/config_v1beta1_verifier_schemavalidator_bad.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator_bad.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_schemavalidator_bad.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator_bad.yaml diff --git a/config/samples/config_v1beta1_verifier_vulnerabilityreport.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_vulnerabilityreport.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_vulnerabilityreport.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_vulnerabilityreport.yaml diff --git a/config/samples/config_v1beta1_verifier_vulnerabilityreport2.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_vulnerabilityreport2.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_vulnerabilityreport2.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_vulnerabilityreport2.yaml diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_complete_licensechecker.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_complete_licensechecker.yaml new file mode 100644 index 000000000..e8c0ac4cb --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_complete_licensechecker.yaml @@ -0,0 +1,24 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-license-checker +spec: + name: licensechecker + artifactTypes: application/vnd.ratify.spdx.v0 + parameters: + allowedLicenses: + - MIT + - GPL-2.0-only + - OpenSSL + - BSD-2-Clause AND BSD-3-Clause + - Zlib + - MPL-2.0 AND MIT + - ISC + - Apache-2.0 + - MIT AND BSD-2-Clause AND GPL-2.0-or-later, + - MIT AND LicenseRef-AND AND BSD-2-Clause AND LicenseRef-AND AND GPL-2.0-or-later + - MPL-2.0 AND LicenseRef-AND AND MIT + - BSD-2-Clause AND LicenseRef-AND AND BSD-3-Clause + - NONE + - NOASSERTION + - "" \ No newline at end of file diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign.yaml new file mode 100644 index 000000000..5de436f2c --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign.yaml @@ -0,0 +1,14 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-cosign +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + trustPolicies: + - name: default + scopes: + - "*" + keys: + - provider: ratify-cosign-inline-key-0 \ No newline at end of file diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign_keyless.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign_keyless.yaml new file mode 100644 index 000000000..f973277ca --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign_keyless.yaml @@ -0,0 +1,9 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-cosign +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + rekorURL: https://rekor.sigstore.dev \ No newline at end of file diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign_legacy.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign_legacy.yaml new file mode 100644 index 000000000..7c80620f4 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign_legacy.yaml @@ -0,0 +1,9 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-cosign +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + key: /usr/local/ratify-certs/cosign/cosign.pub \ No newline at end of file diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_dynamic.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_dynamic.yaml new file mode 100644 index 000000000..a795a608b --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_dynamic.yaml @@ -0,0 +1,9 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-dynamic +spec: + name: dynamic + artifactTypes: application/vnd.ratify.spdx.v0 + source: + artifact: wabbitnetworks.azurecr.io/test/sample-verifier-plugin:v1 diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_notation.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_notation.yaml new file mode 100644 index 000000000..b20b4a8c5 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_notation.yaml @@ -0,0 +1,23 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-notation +spec: + name: notation + artifactTypes: application/vnd.cncf.notary.signature + parameters: + verificationCertStores: + certs: + - ratify-notation-inline-cert-0 + trustPolicyDoc: + version: "1.0" + trustPolicies: + - name: default + registryScopes: + - "*" + signatureVerification: + level: strict + trustStores: + - ca:certs + trustedIdentities: + - "*" diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_notation_kmprovider.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_notation_kmprovider.yaml new file mode 100644 index 000000000..a9a8d7d64 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_notation_kmprovider.yaml @@ -0,0 +1,27 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-notation +spec: + name: notation + artifactTypes: application/vnd.cncf.notary.signature + parameters: + verificationCertStores: + certs: + - kmprovider-akv + - kmprovider-akv1 + certs1: + - kmprovider-akv2 + - kmprovider-akv3 + trustPolicyDoc: + version: "1.0" + trustPolicies: + - name: default + registryScopes: + - "*" + signatureVerification: + level: strict + trustStores: + - ca:certs + trustedIdentities: + - "*" diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_notation_specificnskmprovider.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_notation_specificnskmprovider.yaml new file mode 100644 index 000000000..b3c46c3f1 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_notation_specificnskmprovider.yaml @@ -0,0 +1,23 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-notation +spec: + name: notation + artifactTypes: application/vnd.cncf.notary.signature + parameters: + verificationCertStores: + certs: + - default/ratify-notation-inline-cert-0 + trustPolicyDoc: + version: "1.0" + trustPolicies: + - name: default + registryScopes: + - "*" + signatureVerification: + level: strict + trustStores: + - ca:certs + trustedIdentities: + - "*" diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_partial_licensechecker.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_partial_licensechecker.yaml new file mode 100644 index 000000000..7761e77ba --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_partial_licensechecker.yaml @@ -0,0 +1,10 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-license-checker +spec: + name: licensechecker + artifactTypes: application/vnd.ratify.spdx.v0 + parameters: + allowedLicenses: + - MIT diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_sbom.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_sbom.yaml new file mode 100644 index 000000000..d7dee04b7 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_sbom.yaml @@ -0,0 +1,9 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-sbom +spec: + name: sbom + artifactTypes: application/spdx+json + parameters: + nestedReferences: application/vnd.cncf.notary.signature \ No newline at end of file diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_sbom_deny.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_sbom_deny.yaml new file mode 100644 index 000000000..414bf6ac8 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_sbom_deny.yaml @@ -0,0 +1,15 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-sbom +spec: + name: sbom + version: 2.0.0-alpha.1 + artifactTypes: application/spdx+json + parameters: + disallowedLicenses: + - Zlib + disallowedPackages: + - name: musl-utils + version: 1.2.3-r4 + nestedReferences: application/vnd.cncf.notary.signature \ No newline at end of file diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_schemavalidator.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_schemavalidator.yaml new file mode 100644 index 000000000..b432778c2 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_schemavalidator.yaml @@ -0,0 +1,10 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-schemavalidator +spec: + name: schemavalidator + artifactTypes: application/vnd.aquasecurity.trivy.report.sarif.v1 + parameters: + schemas: + application/sarif+json: https://json.schemastore.org/sarif-2.1.0-rtm.5.json diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_schemavalidator_bad.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_schemavalidator_bad.yaml new file mode 100644 index 000000000..ad489fea3 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_schemavalidator_bad.yaml @@ -0,0 +1,10 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-schemavalidator +spec: + name: schemavalidator + artifactTypes: application/vnd.aquasecurity.trivy.report.sarif.v1 + parameters: + schemas: + application/sarif+json: https://json.schemastore.org/sourcehut-build-0.65.0.json diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_vulnerabilityreport.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_vulnerabilityreport.yaml new file mode 100644 index 000000000..879d843b6 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_vulnerabilityreport.yaml @@ -0,0 +1,14 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-vulnerabilityreport +spec: + name: vulnerabilityreport + artifactTypes: application/sarif+json + parameters: + maximumAge: 24h + disallowedSeverities: + - high + - critical + denylistCVEs: + - CVE-2021-44228 # Log4Shell diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_vulnerabilityreport2.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_vulnerabilityreport2.yaml new file mode 100644 index 000000000..adece04ba --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_vulnerabilityreport2.yaml @@ -0,0 +1,11 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-vulnerabilityreport +spec: + name: vulnerabilityreport + artifactTypes: application/sarif+json + parameters: + maximumAge: 24h + denylistCVEs: + - CVE-2021-44228 # Log4Shell diff --git a/pkg/controllers/verifier_controller.go b/pkg/controllers/clusterresource/verifier_controller.go similarity index 63% rename from pkg/controllers/verifier_controller.go rename to pkg/controllers/clusterresource/verifier_controller.go index d91b5bebd..a050dd38e 100644 --- a/pkg/controllers/verifier_controller.go +++ b/pkg/controllers/clusterresource/verifier_controller.go @@ -13,20 +13,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package controllers +package clusterresource import ( "context" - "encoding/json" "fmt" configv1beta1 "github.com/deislabs/ratify/api/v1beta1" - "github.com/deislabs/ratify/config" "github.com/deislabs/ratify/internal/constants" - vc "github.com/deislabs/ratify/pkg/verifier/config" - vf "github.com/deislabs/ratify/pkg/verifier/factory" - "github.com/deislabs/ratify/pkg/verifier/types" + "github.com/deislabs/ratify/pkg/controllers" + cutils "github.com/deislabs/ratify/pkg/controllers/utils" "github.com/sirupsen/logrus" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -64,8 +61,7 @@ func (r *VerifierReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c if err := r.Get(ctx, req.NamespacedName, &verifier); err != nil { if apierrors.IsNotFound(err) { verifierLogger.Infof("delete event detected, removing verifier %v", resource) - // TODO: pass the actual namespace once multi-tenancy is supported. - NamespacedVerifiers.DeleteVerifier(constants.EmptyNamespace, resource) + controllers.NamespacedVerifiers.DeleteVerifier(constants.EmptyNamespace, resource) } else { verifierLogger.Error(err, "unable to fetch verifier") } @@ -73,7 +69,7 @@ func (r *VerifierReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c return ctrl.Result{}, client.IgnoreNotFound(err) } - if err := verifierAddOrReplace(verifier.Spec, resource, constants.EmptyNamespace); err != nil { + if err := verifierAddOrReplace(verifier.Spec, resource); err != nil { verifierLogger.Error(err, "unable to create verifier from verifier crd") writeVerifierStatus(ctx, r, &verifier, verifierLogger, false, err.Error()) return ctrl.Result{}, err @@ -86,54 +82,14 @@ func (r *VerifierReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } // creates a verifier reference from CRD spec and add store to map -func verifierAddOrReplace(spec configv1beta1.VerifierSpec, objectName string, namespace string) error { - verifierConfig, err := specToVerifierConfig(spec, objectName) +func verifierAddOrReplace(spec configv1beta1.VerifierSpec, objectName string) error { + verifierConfig, err := cutils.SpecToVerifierConfig(spec.Parameters.Raw, objectName, spec.Name, spec.ArtifactTypes, spec.Source) if err != nil { logrus.Error(err, "unable to convert crd specification to verifier config") return fmt.Errorf("unable to convert crd specification to verifier config, err: %w", err) } - if len(spec.Version) == 0 { - spec.Version = config.GetDefaultPluginVersion() - logrus.Infof("Version was empty, setting to default version: %v", spec.Version) - } - - if spec.Address == "" { - spec.Address = config.GetDefaultPluginPath() - logrus.Infof("Address was empty, setting to default path: %v", spec.Address) - } - - referenceVerifier, err := vf.CreateVerifierFromConfig(verifierConfig, spec.Version, []string{spec.Address}, namespace) - - if err != nil || referenceVerifier == nil { - logrus.Error(err, "unable to create verifier from verifier config") - return err - } - // TODO: pass the actual namespace once multi-tenancy is supported. - NamespacedVerifiers.AddVerifier(constants.EmptyNamespace, objectName, referenceVerifier) - logrus.Infof("verifier '%v' added to verifier map", referenceVerifier.Name()) - - return nil -} - -// returns a verifier reference from spec -func specToVerifierConfig(verifierSpec configv1beta1.VerifierSpec, verifierName string) (vc.VerifierConfig, error) { - verifierConfig := vc.VerifierConfig{} - - if string(verifierSpec.Parameters.Raw) != "" { - if err := json.Unmarshal(verifierSpec.Parameters.Raw, &verifierConfig); err != nil { - logrus.Error(err, "unable to decode verifier parameters", "Parameters.Raw", verifierSpec.Parameters.Raw) - return vc.VerifierConfig{}, err - } - } - verifierConfig[types.Name] = verifierName - verifierConfig[types.Type] = verifierSpec.Name - verifierConfig[types.ArtifactTypes] = verifierSpec.ArtifactTypes - if verifierSpec.Source != nil { - verifierConfig[types.Source] = verifierSpec.Source - } - - return verifierConfig, nil + return cutils.UpsertVerifier(spec.Version, spec.Address, constants.EmptyNamespace, objectName, verifierConfig) } // SetupWithManager sets up the controller with the Manager. @@ -151,8 +107,8 @@ func writeVerifierStatus(ctx context.Context, r client.StatusClient, verifier *c } else { verifier.Status.IsSuccess = false verifier.Status.Error = errorString - if len(errorString) > maxBriefErrLength { - verifier.Status.BriefError = fmt.Sprintf("%s...", errorString[:maxBriefErrLength]) + if len(errorString) > constants.MaxBriefErrLength { + verifier.Status.BriefError = fmt.Sprintf("%s...", errorString[:constants.MaxBriefErrLength]) } } diff --git a/pkg/controllers/verifier_controller_test.go b/pkg/controllers/clusterresource/verifier_controller_test.go similarity index 56% rename from pkg/controllers/verifier_controller_test.go rename to pkg/controllers/clusterresource/verifier_controller_test.go index 31dc7adcd..525f717b8 100644 --- a/pkg/controllers/verifier_controller_test.go +++ b/pkg/controllers/clusterresource/verifier_controller_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package controllers +package clusterresource import ( "context" @@ -24,15 +24,18 @@ import ( configv1beta1 "github.com/deislabs/ratify/api/v1beta1" "github.com/deislabs/ratify/internal/constants" + "github.com/deislabs/ratify/pkg/controllers" "github.com/deislabs/ratify/pkg/customresources/verifiers" "github.com/deislabs/ratify/pkg/utils" "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" ) -const sampleName = "sample" - type mockResourceWriter struct { updateFailed bool } @@ -62,11 +65,14 @@ func (c mockStatusClient) Status() client.SubResourceWriter { return writer } -const licenseChecker = "licensechecker" +const ( + licenseChecker = "licensechecker" + verifierName = "verifierName" +) func TestMain(m *testing.M) { // make sure to reset verifierMap before each test run - NamespacedVerifiers = verifiers.NewActiveVerifiers() + controllers.NamespacedVerifiers = verifiers.NewActiveVerifiers() code := m.Run() os.Exit(code) } @@ -85,18 +91,18 @@ func TestVerifierAdd_EmptyParameter(t *testing.T) { Address: dirPath, } - if err := verifierAddOrReplace(testVerifierSpec, sampleName, constants.EmptyNamespace); err != nil { + if err := verifierAddOrReplace(testVerifierSpec, sampleName); err != nil { t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) } - if NamespacedVerifiers.GetVerifierCount() != 1 { - t.Fatalf("Verifier map expected size 1, actual %v", NamespacedVerifiers.GetVerifierCount()) + if len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace)) != 1 { + t.Fatalf("Verifier map expected size 1, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace))) } } func TestVerifierAdd_WithParameters(t *testing.T) { resetVerifierMap() - if NamespacedVerifiers.GetVerifierCount() != 0 { - t.Fatalf("Verifier map expected size 0, actual %v", NamespacedVerifiers.GetVerifierCount()) + if len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace)) != 0 { + t.Fatalf("Verifier map expected size 0, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace))) } dirPath, err := utils.CreatePlugin(licenseChecker) @@ -107,11 +113,11 @@ func TestVerifierAdd_WithParameters(t *testing.T) { var testVerifierSpec = getDefaultLicenseCheckerSpec(dirPath) - if err := verifierAddOrReplace(testVerifierSpec, "testObject", constants.EmptyNamespace); err != nil { + if err := verifierAddOrReplace(testVerifierSpec, "testObject"); err != nil { t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) } - if NamespacedVerifiers.GetVerifierCount() != 1 { - t.Fatalf("Verifier map expected size 1, actual %v", NamespacedVerifiers.GetVerifierCount()) + if len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace)) != 1 { + t.Fatalf("Verifier map expected size 1, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace))) } } @@ -120,13 +126,29 @@ func TestVerifierAddOrReplace_PluginNotFound(t *testing.T) { var resource = "invalidplugin" expectedMsg := "plugin not found" var testVerifierSpec = getInvalidVerifierSpec() - err := verifierAddOrReplace(testVerifierSpec, resource, constants.EmptyNamespace) + err := verifierAddOrReplace(testVerifierSpec, resource) if !strings.Contains(err.Error(), expectedMsg) { t.Fatalf("TestVerifierAddOrReplace_PluginNotFound expected msg: '%v', actual %v", expectedMsg, err.Error()) } } +func TestVerifierAdd_InvalidConfig(t *testing.T) { + resetVerifierMap() + var testVerifierSpec = configv1beta1.VerifierSpec{ + Name: "notation", + ArtifactTypes: "application/vnd.cncf.notary.signature", + Parameters: runtime.RawExtension{ + Raw: []byte("test\n"), + }, + } + var resource = "notation" + + if err := verifierAddOrReplace(testVerifierSpec, resource); err == nil { + t.Fatalf("Expected an error but returned nil") + } +} + func TestVerifier_UpdateAndDelete(t *testing.T) { resetVerifierMap() dirPath, err := utils.CreatePlugin(licenseChecker) @@ -138,29 +160,29 @@ func TestVerifier_UpdateAndDelete(t *testing.T) { var testVerifierSpec = getDefaultLicenseCheckerSpec(dirPath) // add a verifier - if err := verifierAddOrReplace(testVerifierSpec, licenseChecker, constants.EmptyNamespace); err != nil { + if err := verifierAddOrReplace(testVerifierSpec, licenseChecker); err != nil { t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) } - if NamespacedVerifiers.GetVerifierCount() != 1 { - t.Fatalf("Verifier map expected size 1, actual %v", NamespacedVerifiers.GetVerifierCount()) + if len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace)) != 1 { + t.Fatalf("Verifier map expected size 1, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace))) } // modify the verifier var parametersString = "{\"allowedLicenses\":[\"MIT\",\"GNU\"]}" testVerifierSpec = getLicenseCheckerFromParam(parametersString, dirPath) - if err := verifierAddOrReplace(testVerifierSpec, licenseChecker, constants.EmptyNamespace); err != nil { + if err := verifierAddOrReplace(testVerifierSpec, licenseChecker); err != nil { t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) } // validate no verifier has been added - if NamespacedVerifiers.GetVerifierCount() != 1 { - t.Fatalf("Verifier map should be 1 after replacement, actual %v", NamespacedVerifiers.GetVerifierCount()) + if len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace)) != 1 { + t.Fatalf("Verifier map should be 1 after replacement, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace))) } - NamespacedVerifiers.DeleteVerifier(constants.EmptyNamespace, licenseChecker) + controllers.NamespacedVerifiers.DeleteVerifier(constants.EmptyNamespace, licenseChecker) - if NamespacedVerifiers.GetVerifierCount() != 0 { - t.Fatalf("Verifier map should be 0 after deletion, actual %v", NamespacedVerifiers.GetVerifierCount()) + if len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace)) != 0 { + t.Fatalf("Verifier map should be 0 after deletion, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace))) } } @@ -212,8 +234,99 @@ func TestWriteVerifierStatus(t *testing.T) { } } +func TestVerifierReconcile(t *testing.T) { + dirPath, err := utils.CreatePlugin(sampleName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + tests := []struct { + name string + verifier *configv1beta1.Verifier + req *reconcile.Request + expectedErr bool + expectedVerifierCount int + }{ + { + name: "nonexistent verifier", + req: &reconcile.Request{ + NamespacedName: types.NamespacedName{Name: "nonexistent"}, + }, + verifier: &configv1beta1.Verifier{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: verifierName, + }, + }, + expectedErr: false, + expectedVerifierCount: 0, + }, + { + name: "invalid parameters", + verifier: &configv1beta1.Verifier{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: verifierName, + }, + Spec: configv1beta1.VerifierSpec{ + Parameters: runtime.RawExtension{ + Raw: []byte("test"), + }, + }, + }, + expectedErr: true, + expectedVerifierCount: 0, + }, + { + name: "valid spec", + verifier: &configv1beta1.Verifier{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: verifierName, + }, + Spec: configv1beta1.VerifierSpec{ + Name: sampleName, + Address: dirPath, + }, + }, + expectedErr: false, + expectedVerifierCount: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resetVerifierMap() + scheme, _ := utils.CreateScheme() + client := fake.NewClientBuilder().WithScheme(scheme) + client.WithObjects(tt.verifier) + r := &VerifierReconciler{ + Scheme: scheme, + Client: client.Build(), + } + var req reconcile.Request + if tt.req != nil { + req = *tt.req + } else { + req = reconcile.Request{ + NamespacedName: utils.KeyFor(tt.verifier), + } + } + + _, err := r.Reconcile(context.Background(), req) + if tt.expectedErr != (err != nil) { + t.Fatalf("Reconcile() expected error %v, actual %v", tt.expectedErr, err) + } + if len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace)) != tt.expectedVerifierCount { + t.Fatalf("Verifier map expected size %v, actual %v", tt.expectedVerifierCount, len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace))) + } + }) + } +} + func resetVerifierMap() { - NamespacedVerifiers = verifiers.NewActiveVerifiers() + controllers.NamespacedVerifiers = verifiers.NewActiveVerifiers() } func getLicenseCheckerFromParam(parametersString, pluginPath string) configv1beta1.VerifierSpec { diff --git a/pkg/controllers/constants.go b/pkg/controllers/constants.go deleted file mode 100644 index 922ab6ef0..000000000 --- a/pkg/controllers/constants.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright The Ratify Authors. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package controllers - -const maxBriefErrLength = 30 diff --git a/pkg/controllers/namespaceresource/verifier_controller.go b/pkg/controllers/namespaceresource/verifier_controller.go new file mode 100644 index 000000000..3c142f77e --- /dev/null +++ b/pkg/controllers/namespaceresource/verifier_controller.go @@ -0,0 +1,118 @@ +/* +Copyright The Ratify Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package namespaceresource + +import ( + "context" + "fmt" + + configv1beta1 "github.com/deislabs/ratify/api/v1beta1" + "github.com/deislabs/ratify/internal/constants" + "github.com/deislabs/ratify/pkg/controllers" + + cutils "github.com/deislabs/ratify/pkg/controllers/utils" + "github.com/sirupsen/logrus" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// VerifierReconciler reconciles a Verifier object +type VerifierReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedverifiers,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedverifiers/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedverifiers/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Verifier object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile +func (r *VerifierReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + verifierLogger := logrus.WithContext(ctx) + + var verifier configv1beta1.NamespacedVerifier + var resource = req.Name + + verifierLogger.Infof("reconciling verifier '%v'", resource) + + if err := r.Get(ctx, req.NamespacedName, &verifier); err != nil { + if apierrors.IsNotFound(err) { + verifierLogger.Infof("delete event detected, removing verifier %v", resource) + controllers.NamespacedVerifiers.DeleteVerifier(req.Namespace, resource) + } else { + verifierLogger.Error(err, "unable to fetch verifier") + } + + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + if err := verifierAddOrReplace(verifier.Spec, resource, req.Namespace); err != nil { + verifierLogger.Error(err, "unable to create verifier from verifier crd") + writeVerifierStatus(ctx, r, &verifier, verifierLogger, false, err.Error()) + return ctrl.Result{}, err + } + + writeVerifierStatus(ctx, r, &verifier, verifierLogger, true, "") + + // returning empty result and no error to indicate we’ve successfully reconciled this object + return ctrl.Result{}, nil +} + +// creates a verifier reference from CRD spec and add store to map +func verifierAddOrReplace(spec configv1beta1.NamespacedVerifierSpec, objectName string, namespace string) error { + verifierConfig, err := cutils.SpecToVerifierConfig(spec.Parameters.Raw, objectName, spec.Name, spec.ArtifactTypes, spec.Source) + if err != nil { + logrus.Error(err, "unable to convert crd specification to verifier config") + return fmt.Errorf("unable to convert crd specification to verifier config, err: %w", err) + } + + return cutils.UpsertVerifier(spec.Version, spec.Address, namespace, objectName, verifierConfig) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *VerifierReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&configv1beta1.NamespacedVerifier{}). + Complete(r) +} + +func writeVerifierStatus(ctx context.Context, r client.StatusClient, verifier *configv1beta1.NamespacedVerifier, logger *logrus.Entry, isSuccess bool, errorString string) { + if isSuccess { + verifier.Status.IsSuccess = true + verifier.Status.Error = "" + verifier.Status.BriefError = "" + } else { + verifier.Status.IsSuccess = false + verifier.Status.Error = errorString + if len(errorString) > constants.MaxBriefErrLength { + verifier.Status.BriefError = fmt.Sprintf("%s...", errorString[:constants.MaxBriefErrLength]) + } + } + + if statusErr := r.Status().Update(ctx, verifier); statusErr != nil { + logger.Error(statusErr, ",unable to update verifier status") + } +} diff --git a/pkg/controllers/namespaceresource/verifier_controller_test.go b/pkg/controllers/namespaceresource/verifier_controller_test.go new file mode 100644 index 000000000..85f9b1f76 --- /dev/null +++ b/pkg/controllers/namespaceresource/verifier_controller_test.go @@ -0,0 +1,358 @@ +/* +Copyright The Ratify Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package namespaceresource + +import ( + "context" + "errors" + "os" + "strings" + "testing" + + configv1beta1 "github.com/deislabs/ratify/api/v1beta1" + "github.com/deislabs/ratify/pkg/controllers" + "github.com/deislabs/ratify/pkg/customresources/verifiers" + "github.com/deislabs/ratify/pkg/utils" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const ( + verifierName = "verifierName" + licenseChecker = "licensechecker" +) + +type mockResourceWriter struct { + updateFailed bool +} + +func (w mockResourceWriter) Create(_ context.Context, _ client.Object, _ client.Object, _ ...client.SubResourceCreateOption) error { + return nil +} + +func (w mockResourceWriter) Update(_ context.Context, _ client.Object, _ ...client.SubResourceUpdateOption) error { + if w.updateFailed { + return errors.New("update failed") + } + return nil +} + +func (w mockResourceWriter) Patch(_ context.Context, _ client.Object, _ client.Patch, _ ...client.SubResourcePatchOption) error { + return nil +} + +type mockStatusClient struct { + updateFailed bool +} + +func (c mockStatusClient) Status() client.SubResourceWriter { + writer := mockResourceWriter{} + writer.updateFailed = c.updateFailed + return writer +} + +func TestMain(m *testing.M) { + // make sure to reset verifierMap before each test run + controllers.NamespacedVerifiers = verifiers.NewActiveVerifiers() + code := m.Run() + os.Exit(code) +} + +func TestVerifierAdd_EmptyParameter(t *testing.T) { + resetVerifierMap() + dirPath, err := utils.CreatePlugin(sampleName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testVerifierSpec = configv1beta1.NamespacedVerifierSpec{ + Name: sampleName, + ArtifactTypes: "application/vnd.cncf.notary.signature", + Address: dirPath, + } + + if err := verifierAddOrReplace(testVerifierSpec, sampleName, testNamespace); err != nil { + t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace)) != 1 { + t.Fatalf("Verifier map expected size 1, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace))) + } +} + +func TestVerifierAdd_InvalidParameter(t *testing.T) { + resetVerifierMap() + var testVerifierSpec = configv1beta1.NamespacedVerifierSpec{ + Name: "notation", + ArtifactTypes: "application/vnd.cncf.notary.signature", + Parameters: runtime.RawExtension{ + Raw: []byte("test"), + }, + } + var resource = "notation" + + if err := verifierAddOrReplace(testVerifierSpec, resource, testNamespace); err == nil { + t.Fatalf("verifierAddOrReplace() expected error, actual nil") + } + if len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace)) != 0 { + t.Fatalf("Verifier map expected size 0, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace))) + } +} + +func TestVerifierAdd_WithParameters(t *testing.T) { + resetVerifierMap() + if len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace)) != 0 { + t.Fatalf("Verifier map expected size 0, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace))) + } + + dirPath, err := utils.CreatePlugin(licenseChecker) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testVerifierSpec = getDefaultLicenseCheckerSpec(dirPath) + + if err := verifierAddOrReplace(testVerifierSpec, "testObject", testNamespace); err != nil { + t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace)) != 1 { + t.Fatalf("Verifier map expected size 1, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace))) + } +} + +func TestVerifierAddOrReplace_PluginNotFound(t *testing.T) { + resetVerifierMap() + var resource = "invalidplugin" + expectedMsg := "plugin not found" + var testVerifierSpec = getInvalidVerifierSpec() + err := verifierAddOrReplace(testVerifierSpec, resource, testNamespace) + + if !strings.Contains(err.Error(), expectedMsg) { + t.Fatalf("TestVerifierAddOrReplace_PluginNotFound expected msg: '%v', actual %v", expectedMsg, err.Error()) + } +} + +func TestVerifier_UpdateAndDelete(t *testing.T) { + resetVerifierMap() + dirPath, err := utils.CreatePlugin(licenseChecker) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testVerifierSpec = getDefaultLicenseCheckerSpec(dirPath) + + // add a verifier + if err := verifierAddOrReplace(testVerifierSpec, licenseChecker, testNamespace); err != nil { + t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace)) != 1 { + t.Fatalf("Verifier map expected size 1, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace))) + } + + // modify the verifier + var parametersString = "{\"allowedLicenses\":[\"MIT\",\"GNU\"]}" + testVerifierSpec = getLicenseCheckerFromParam(parametersString, dirPath) + if err := verifierAddOrReplace(testVerifierSpec, licenseChecker, testNamespace); err != nil { + t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) + } + + // validate no verifier has been added + if len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace)) != 1 { + t.Fatalf("Verifier map should be 1 after replacement, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace))) + } + + controllers.NamespacedVerifiers.DeleteVerifier(testNamespace, licenseChecker) + + if len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace)) != 0 { + t.Fatalf("Verifier map should be 0 after deletion, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace))) + } +} + +func TestWriteVerifierStatus(t *testing.T) { + logger := logrus.WithContext(context.Background()) + testCases := []struct { + name string + isSuccess bool + verifier *configv1beta1.NamespacedVerifier + errString string + reconciler client.StatusClient + }{ + { + name: "success status", + isSuccess: true, + errString: "", + verifier: &configv1beta1.NamespacedVerifier{}, + reconciler: &mockStatusClient{}, + }, + { + name: "error status", + isSuccess: false, + verifier: &configv1beta1.NamespacedVerifier{}, + errString: "a long error string that exceeds the max length of 30 characters", + reconciler: &mockStatusClient{}, + }, + { + name: "status update failed", + isSuccess: true, + verifier: &configv1beta1.NamespacedVerifier{}, + reconciler: &mockStatusClient{ + updateFailed: true, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + writeVerifierStatus(context.Background(), tc.reconciler, tc.verifier, logger, tc.isSuccess, tc.errString) + + if tc.verifier.Status.IsSuccess != tc.isSuccess { + t.Fatalf("Expected isSuccess to be %+v , actual %+v", tc.isSuccess, tc.verifier.Status.IsSuccess) + } + + if tc.verifier.Status.Error != tc.errString { + t.Fatalf("Expected Error to be %+v , actual %+v", tc.errString, tc.verifier.Status.Error) + } + }) + } +} + +func TestVerifierReconcile(t *testing.T) { + dirPath, err := utils.CreatePlugin(sampleName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + tests := []struct { + name string + verifier *configv1beta1.NamespacedVerifier + req *reconcile.Request + expectedErr bool + expectedVerifierCount int + }{ + { + name: "nonexistent verifier", + req: &reconcile.Request{ + NamespacedName: types.NamespacedName{Name: "nonexistent"}, + }, + verifier: &configv1beta1.NamespacedVerifier{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: verifierName, + }, + }, + expectedErr: false, + expectedVerifierCount: 0, + }, + { + name: "invalid parameters", + verifier: &configv1beta1.NamespacedVerifier{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: verifierName, + }, + Spec: configv1beta1.NamespacedVerifierSpec{ + Parameters: runtime.RawExtension{ + Raw: []byte("test"), + }, + }, + }, + expectedErr: true, + expectedVerifierCount: 0, + }, + { + name: "valid spec", + verifier: &configv1beta1.NamespacedVerifier{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: verifierName, + }, + Spec: configv1beta1.NamespacedVerifierSpec{ + Name: sampleName, + Address: dirPath, + }, + }, + expectedErr: false, + expectedVerifierCount: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resetVerifierMap() + scheme, _ := utils.CreateScheme() + client := fake.NewClientBuilder().WithScheme(scheme) + client.WithObjects(tt.verifier) + r := &VerifierReconciler{ + Scheme: scheme, + Client: client.Build(), + } + var req reconcile.Request + if tt.req != nil { + req = *tt.req + } else { + req = reconcile.Request{ + NamespacedName: utils.KeyFor(tt.verifier), + } + } + + _, err := r.Reconcile(context.Background(), req) + if tt.expectedErr != (err != nil) { + t.Fatalf("Reconcile() expected error %v, actual %v", tt.expectedErr, err) + } + if len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace)) != tt.expectedVerifierCount { + t.Fatalf("Verifier map expected size %v, actual %v", tt.expectedVerifierCount, len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace))) + } + }) + } +} + +func resetVerifierMap() { + controllers.NamespacedVerifiers = verifiers.NewActiveVerifiers() +} + +func getLicenseCheckerFromParam(parametersString, pluginPath string) configv1beta1.NamespacedVerifierSpec { + var allowedLicenses = []byte(parametersString) + + return configv1beta1.NamespacedVerifierSpec{ + Name: licenseChecker, + ArtifactTypes: "application/vnd.ratify.spdx.v0", + Address: pluginPath, + Parameters: runtime.RawExtension{ + Raw: allowedLicenses, + }, + } +} + +func getInvalidVerifierSpec() configv1beta1.NamespacedVerifierSpec { + return configv1beta1.NamespacedVerifierSpec{ + Name: "pluginnotfound", + ArtifactTypes: "application/vnd.ratify.spdx.v0", + Address: "test/path", + } +} + +func getDefaultLicenseCheckerSpec(pluginPath string) configv1beta1.NamespacedVerifierSpec { + var parametersString = "{\"allowedLicenses\":[\"MIT\",\"Apache\"]}" + return getLicenseCheckerFromParam(parametersString, pluginPath) +} diff --git a/pkg/controllers/utils/verifier.go b/pkg/controllers/utils/verifier.go new file mode 100644 index 000000000..2dfe5b2d6 --- /dev/null +++ b/pkg/controllers/utils/verifier.go @@ -0,0 +1,73 @@ +/* +Copyright The Ratify Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils + +import ( + "encoding/json" + + configv1beta1 "github.com/deislabs/ratify/api/v1beta1" + vc "github.com/deislabs/ratify/pkg/verifier/config" + vf "github.com/deislabs/ratify/pkg/verifier/factory" + "github.com/deislabs/ratify/pkg/verifier/types" + + "github.com/deislabs/ratify/config" + "github.com/deislabs/ratify/pkg/controllers" + "github.com/sirupsen/logrus" +) + +// UpsertVerifier creates and adds a new verifier based on the provided configuration. +func UpsertVerifier(version, address, namespace, objectName string, verifierConfig vc.VerifierConfig) error { + if len(version) == 0 { + version = config.GetDefaultPluginVersion() + logrus.Infof("Version was empty, setting to default version: %v", version) + } + + if address == "" { + address = config.GetDefaultPluginPath() + logrus.Infof("Address was empty, setting to default path: %v", address) + } + + logrus.Infof("test verifier config: %+v", verifierConfig) + + referenceVerifier, err := vf.CreateVerifierFromConfig(verifierConfig, version, []string{address}, namespace) + if err != nil || referenceVerifier == nil { + logrus.Error(err, " unable to create verifier from verifier config") + return err + } + + controllers.NamespacedVerifiers.AddVerifier(namespace, objectName, referenceVerifier) + logrus.Infof("verifier '%v' added to verifier map in namespace: %s", referenceVerifier.Name(), namespace) + + return nil +} + +// SpecToVerifierConfig returns a VerifierConfig from VerifierSpec +func SpecToVerifierConfig(raw []byte, verifierName, verifierType, artifactTypes string, source *configv1beta1.PluginSource) (vc.VerifierConfig, error) { + verifierConfig := vc.VerifierConfig{} + + if string(raw) != "" { + if err := json.Unmarshal(raw, &verifierConfig); err != nil { + logrus.Error(err, "unable to decode verifier parameters", "Parameters.Raw", raw) + return vc.VerifierConfig{}, err + } + } + verifierConfig[types.Name] = verifierName + verifierConfig[types.Type] = verifierType + verifierConfig[types.ArtifactTypes] = artifactTypes + if source != nil { + verifierConfig[types.Source] = source + } + + return verifierConfig, nil +} diff --git a/pkg/controllers/utils/verifier_test.go b/pkg/controllers/utils/verifier_test.go new file mode 100644 index 000000000..80e8f548b --- /dev/null +++ b/pkg/controllers/utils/verifier_test.go @@ -0,0 +1,121 @@ +/* +Copyright The Ratify Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils + +import ( + "os" + "testing" + + configv1beta1 "github.com/deislabs/ratify/api/v1beta1" + "github.com/deislabs/ratify/pkg/controllers" + "github.com/deislabs/ratify/pkg/customresources/verifiers" + test "github.com/deislabs/ratify/pkg/utils" + vc "github.com/deislabs/ratify/pkg/verifier/config" + "github.com/deislabs/ratify/pkg/verifier/types" +) + +const ( + verifierName = "verifierName" + verifierType = "verifierType" +) + +func TestUpsertVerifier(t *testing.T) { + dirPath, err := test.CreatePlugin(verifierName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + tests := []struct { + name string + address string + namespace string + objectName string + verifierConfig vc.VerifierConfig + expectedErr bool + }{ + { + name: "empty config", + verifierConfig: vc.VerifierConfig{}, + expectedErr: true, + }, + { + name: "empty address", + address: dirPath, + verifierConfig: vc.VerifierConfig{ + "name": verifierName, + }, + expectedErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resetVerifierMap() + + err = UpsertVerifier("", tt.address, tt.namespace, tt.objectName, tt.verifierConfig) + if tt.expectedErr != (err != nil) { + t.Fatalf("UpsertVerifier() expected error %v, actual %v", tt.expectedErr, err) + } + }) + } +} + +func TestSpecToVerifierConfig(t *testing.T) { + tests := []struct { + name string + raw []byte + verifierName string + verifierType string + artifactTypes string + source *configv1beta1.PluginSource + expectedErr bool + expectedVerifierName string + }{ + { + name: "empty raw", + raw: []byte{}, + verifierName: verifierName, + verifierType: verifierType, + source: &configv1beta1.PluginSource{}, + expectedVerifierName: verifierName, + expectedErr: false, + }, + { + name: "invalid raw", + raw: []byte("test\n"), + expectedVerifierName: "", + expectedErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config, err := SpecToVerifierConfig(tt.raw, tt.verifierName, tt.verifierType, tt.artifactTypes, tt.source) + if tt.expectedErr != (err != nil) { + t.Fatalf("SpecToVerifierConfig() expected error %v, actual %v", tt.expectedErr, err) + } + if _, ok := config[types.Name]; !ok { + config[types.Name] = "" + } + if config[types.Name] != tt.expectedVerifierName { + t.Fatalf("SpecToVerifierConfig() expected verifier name %s, actual %s", tt.expectedVerifierName, config[types.Name]) + } + }) + } +} + +func resetVerifierMap() { + controllers.NamespacedVerifiers = verifiers.NewActiveVerifiers() +} diff --git a/pkg/customresources/verifiers/api.go b/pkg/customresources/verifiers/api.go index 7d35e7be5..919c92e39 100644 --- a/pkg/customresources/verifiers/api.go +++ b/pkg/customresources/verifiers/api.go @@ -29,10 +29,4 @@ type VerifierManager interface { // DeleteVerifier deletes a verifier from the given scope. DeleteVerifier(scope, verifierName string) - - // IsEmpty returns true if verifiers are empty. - IsEmpty() bool - - // GetVerifierCount returns the number of verifiers in all scopes. - GetVerifierCount() int } diff --git a/pkg/customresources/verifiers/verifiers.go b/pkg/customresources/verifiers/verifiers.go index 2e96c85ca..758be683f 100644 --- a/pkg/customresources/verifiers/verifiers.go +++ b/pkg/customresources/verifiers/verifiers.go @@ -16,12 +16,14 @@ limitations under the License. package verifiers import ( + "sync" + + "github.com/deislabs/ratify/internal/constants" vr "github.com/deislabs/ratify/pkg/verifier" ) // ActiveVerifiers implements VerifierManger interface. type ActiveVerifiers struct { - // TODO: Implement concurrent safety using sync.Map // The structure of the map is as follows: // The first level maps from scope to verifiers // The second level maps from verifier name to verifier @@ -33,49 +35,44 @@ type ActiveVerifiers struct { // } // } // Note: Scope is utilized for organizing and isolating verifiers. In a Kubernetes (K8s) environment, the scope can be either a namespace or an empty string ("") for cluster-wide verifiers. - ScopedVerifiers map[string]map[string]vr.ReferenceVerifier + //scopedVerifiers map[string]map[string]vr.ReferenceVerifier + scopedVerifiers sync.Map } func NewActiveVerifiers() VerifierManager { - return &ActiveVerifiers{ - ScopedVerifiers: make(map[string]map[string]vr.ReferenceVerifier), - } + return &ActiveVerifiers{} } // GetVerifiers implements the VerifierManager interface. // It returns a list of verifiers for the given scope. If no verifiers are found for the given scope, it returns cluster-wide verifiers. -// TODO: Current implementation fetches verifiers for all namespaces including cluster-wide ones. Will support actual namespace based verifiers in future. -func (v *ActiveVerifiers) GetVerifiers(_ string) []vr.ReferenceVerifier { +func (v *ActiveVerifiers) GetVerifiers(scope string) []vr.ReferenceVerifier { verifiers := []vr.ReferenceVerifier{} - for _, scopedVerifiers := range v.ScopedVerifiers { - for _, verifier := range scopedVerifiers { + if scopedVerifier, ok := v.scopedVerifiers.Load(scope); ok { + for _, verifier := range scopedVerifier.(map[string]vr.ReferenceVerifier) { verifiers = append(verifiers, verifier) } } + if len(verifiers) == 0 && scope != constants.EmptyNamespace { + if clusterVerifier, ok := v.scopedVerifiers.Load(constants.EmptyNamespace); ok { + for _, verifier := range clusterVerifier.(map[string]vr.ReferenceVerifier) { + verifiers = append(verifiers, verifier) + } + } + } return verifiers } +// AddVerifier fulfills the VerifierManager interface. +// It adds the given verifier under the given scope. func (v *ActiveVerifiers) AddVerifier(scope, verifierName string, verifier vr.ReferenceVerifier) { - if _, ok := v.ScopedVerifiers[scope]; !ok { - v.ScopedVerifiers[scope] = make(map[string]vr.ReferenceVerifier) - } - v.ScopedVerifiers[scope][verifierName] = verifier + scopedVerifier, _ := v.scopedVerifiers.LoadOrStore(scope, make(map[string]vr.ReferenceVerifier)) + scopedVerifier.(map[string]vr.ReferenceVerifier)[verifierName] = verifier } +// DeleteVerifier fulfills the VerifierManager interface. +// It deletes the verfier of the given name under the given scope. func (v *ActiveVerifiers) DeleteVerifier(scope, verifierName string) { - if verifiers, ok := v.ScopedVerifiers[scope]; ok { - delete(verifiers, verifierName) - } -} - -func (v *ActiveVerifiers) IsEmpty() bool { - return v.GetVerifierCount() == 0 -} - -func (v *ActiveVerifiers) GetVerifierCount() int { - count := 0 - for _, verifiers := range v.ScopedVerifiers { - count += len(verifiers) + if scopedVerifier, ok := v.scopedVerifiers.Load(scope); ok { + delete(scopedVerifier.(map[string]vr.ReferenceVerifier), verifierName) } - return count } diff --git a/pkg/customresources/verifiers/verifiers_test.go b/pkg/customresources/verifiers/verifiers_test.go index 652a9cfcf..02148d633 100644 --- a/pkg/customresources/verifiers/verifiers_test.go +++ b/pkg/customresources/verifiers/verifiers_test.go @@ -69,37 +69,30 @@ func TestVerifiersOperations(t *testing.T) { verifiers.AddVerifier(namespace2, verifier1.Name(), verifier1) verifiers.AddVerifier(namespace2, verifier2.Name(), verifier2) - if verifiers.IsEmpty() { - t.Error("Expected verifiers to not be empty") + if len(verifiers.GetVerifiers(namespace1)) != 2 { + t.Fatalf("Expected 2 verifiers, got %d", len(verifiers.GetVerifiers(namespace1))) } - - if verifiers.GetVerifierCount() != 4 { - t.Errorf("Expected 4 verifiers, got %d", verifiers.GetVerifierCount()) - } - - if len(verifiers.GetVerifiers(namespace1)) != 4 { - t.Errorf("Expected 4 verifiers, got %d", len(verifiers.GetVerifiers(namespace1))) + if len(verifiers.GetVerifiers(namespace2)) != 2 { + t.Fatalf("Expected 2 verifiers, got %d", len(verifiers.GetVerifiers(namespace2))) } - if len(verifiers.GetVerifiers(namespace2)) != 4 { - t.Errorf("Expected 4 verifiers, got %d", len(verifiers.GetVerifiers(namespace2))) + verifiers.DeleteVerifier(namespace2, verifier1.Name()) + if len(verifiers.GetVerifiers(namespace2)) != 1 { + t.Fatalf("Expected 1 verifier, got %d", len(verifiers.GetVerifiers(namespace2))) } - verifiers.DeleteVerifier(namespace2, verifier1.Name()) verifiers.DeleteVerifier(namespace2, verifier2.Name()) - if len(verifiers.GetVerifiers(namespace2)) != 2 { - t.Errorf("Expected 2 verifiers, got %d", len(verifiers.GetVerifiers(namespace2))) + t.Fatalf("Expected 2 verifiers, got %d", len(verifiers.GetVerifiers(namespace2))) } verifiers.DeleteVerifier(namespace1, verifier1.Name()) - verifiers.DeleteVerifier(namespace1, verifier2.Name()) - - if !verifiers.IsEmpty() { - t.Error("Expected verifiers to be empty") + if len(verifiers.GetVerifiers(namespace1)) != 1 { + t.Fatalf("Expected 1 verifier, got %d", len(verifiers.GetVerifiers(namespace1))) } - if verifiers.GetVerifierCount() != 0 { - t.Errorf("Expected 0 verifiers, got %d", verifiers.GetVerifierCount()) + verifiers.DeleteVerifier(namespace1, verifier2.Name()) + if len(verifiers.GetVerifiers(namespace1)) != 0 { + t.Fatalf("Expected 0 verifiers, got %d", len(verifiers.GetVerifiers(namespace1))) } } diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index bdbac0e7c..ba6876107 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -193,11 +193,18 @@ func StartManager(certRotatorReady chan struct{}, probeAddr string) { close(certRotatorReady) } - if err = (&controllers.VerifierReconciler{ + if err = (&clusterresource.VerifierReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Verifier") + setupLog.Error(err, "unable to create controller", "controller", "Cluster Verifier") + os.Exit(1) + } + if err = (&namespaceresource.VerifierReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Namespaced Verifier") os.Exit(1) } if err = (&clusterresource.StoreReconciler{ diff --git a/test/bats/azure-test.bats b/test/bats/azure-test.bats index f5fdc133b..1a4f94169 100644 --- a/test/bats/azure-test.bats +++ b/test/bats/azure-test.bats @@ -35,7 +35,7 @@ SLEEP_TIME=1 sleep 5 latestpod=$(kubectl -n gatekeeper-system get pod -l=app.kubernetes.io/name=ratify --sort-by=.metadata.creationTimestamp -o=name | tail -n 1) - run kubectl apply -f ./config/samples/config_v1beta1_verifier_dynamic.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_dynamic.yaml sleep 5 # parse the logs for the newly created ratify pod @@ -137,12 +137,12 @@ SLEEP_TIME=1 assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_partial_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_partial_licensechecker.yaml sleep 5 run kubectl run license-checker --namespace default --image=${TEST_REGISTRY}/licensechecker:v0 assert_failure - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml # wait for the httpserver cache to be invalidated sleep 15 run kubectl run license-checker2 --namespace default --image=${TEST_REGISTRY}/licensechecker:v0 @@ -163,7 +163,7 @@ SLEEP_TIME=1 assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_sbom.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_sbom.yaml sleep 5 run kubectl run sbom --namespace default --image=${TEST_REGISTRY}/sbom:v0 assert_success @@ -193,13 +193,13 @@ SLEEP_TIME=1 assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_schemavalidator.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator.yaml sleep 5 run kubectl run schemavalidator --namespace default --image=${TEST_REGISTRY}/schemavalidator:v0 assert_success - run kubectl apply -f ./config/samples/config_v1beta1_verifier_schemavalidator_bad.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator_bad.yaml # wait for the httpserver cache to be invalidated sleep 15 run kubectl run schemavalidator2 --namespace default --image=${TEST_REGISTRY}/schemavalidator:v0 @@ -223,11 +223,11 @@ SLEEP_TIME=1 assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_sbom.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_sbom.yaml sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_schemavalidator.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator.yaml sleep 5 run kubectl run all-in-one --namespace default --image=${TEST_REGISTRY}/all:v0 @@ -241,7 +241,7 @@ SLEEP_TIME=1 } echo "adding license checker, delete notation verifier and validate deployment fails due to missing notation verifier" - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml assert_success run kubectl delete verifiers.config.ratify.deislabs.io/verifier-notation assert_success @@ -251,7 +251,7 @@ SLEEP_TIME=1 assert_failure echo "Add notation verifier and validate deployment succeeds" - run kubectl apply -f ./config/samples/config_v1beta1_verifier_notation_kmprovider.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation_kmprovider.yaml assert_success # wait for the httpserver cache to be invalidated @@ -295,7 +295,7 @@ SLEEP_TIME=1 start=$(date --iso-8601=seconds) latestpod=$(kubectl -n gatekeeper-system get pod -l=app.kubernetes.io/name=ratify --sort-by=.metadata.creationTimestamp -o=name | tail -n 1) - run kubectl apply -f ./config/samples/config_v1beta1_verifier_dynamic.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_dynamic.yaml sleep 5 run bash -c "kubectl -n gatekeeper-system logs $latestpod --since-time=$start | grep 'dynamic plugins are currently disabled'" diff --git a/test/bats/base-test.bats b/test/bats/base-test.bats index e49c32c37..4d194766a 100644 --- a/test/bats/base-test.bats +++ b/test/bats/base-test.bats @@ -60,14 +60,14 @@ RATIFY_NAMESPACE=gatekeeper-system @test "crd version test" { run kubectl delete verifiers.config.ratify.deislabs.io/verifier-notation assert_success - run kubectl apply -f ./config/samples/config_v1alpha1_verifier_notation.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1alpha1_verifier_notation.yaml assert_success run bash -c "kubectl get verifiers.config.ratify.deislabs.io/verifier-notation -o yaml | grep 'apiVersion: config.ratify.deislabs.io/v1beta1'" assert_success run kubectl delete stores.config.ratify.deislabs.io/store-oras assert_success - run kubectl apply -f ./config/samples/config_v1alpha1_store_oras_http.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1alpha1_store_oras_http.yaml assert_success run bash -c "kubectl get stores.config.ratify.deislabs.io/store-oras -o yaml | grep 'apiVersion: config.ratify.deislabs.io/v1beta1'" assert_success @@ -111,7 +111,7 @@ RATIFY_NAMESPACE=gatekeeper-system assert_success # restore the original notation verifier for other tests - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/config_v1beta1_verifier_notation.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml' } run kubectl apply -f ./library/default/template.yaml assert_success @@ -131,7 +131,7 @@ RATIFY_NAMESPACE=gatekeeper-system assert_success # configure the notation verifier to use inline certificate store with specific namespace - run kubectl apply -f ./config/samples/config_v1beta1_verifier_notation_specificnskmprovider.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation_specificnskmprovider.yaml assert_success run kubectl run demo --namespace default --image=registry:5000/notation:signed @@ -169,7 +169,7 @@ RATIFY_NAMESPACE=gatekeeper-system } # use imperative command to guarantee verifier config is updated - run kubectl replace -f ./config/samples/config_v1beta1_verifier_cosign_legacy.yaml + run kubectl replace -f ./config/samples/clustered/verifier/config_v1beta1_verifier_cosign_legacy.yaml sleep 5 run kubectl apply -f ./library/default/template.yaml @@ -190,12 +190,12 @@ RATIFY_NAMESPACE=gatekeeper-system teardown() { echo "cleaning up" wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo-keyless --namespace default --force --ignore-not-found=true' - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl replace -f ./config/samples/config_v1beta1_verifier_cosign.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl replace -f ./config/samples/clustered/verifier/config_v1beta1_verifier_cosign.yaml' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl replace -f ./config/samples/clustered/store/config_v1beta1_store_oras_http.yaml' } # use imperative command to guarantee useHttp is updated - run kubectl replace -f ./config/samples/config_v1beta1_verifier_cosign_keyless.yaml + run kubectl replace -f ./config/samples/clustered/verifier/config_v1beta1_verifier_cosign_keyless.yaml sleep 5 run kubectl replace -f ./config/samples/clustered/store/config_v1beta1_store_oras.yaml @@ -211,7 +211,7 @@ RATIFY_NAMESPACE=gatekeeper-system } echo "adding license checker, delete notation verifier and validate deployment fails due to missing notation verifier" - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml assert_success run kubectl delete verifiers.config.ratify.deislabs.io/verifier-notation assert_success @@ -221,7 +221,7 @@ RATIFY_NAMESPACE=gatekeeper-system assert_failure echo "Add notation verifier and validate deployment succeeds" - run kubectl apply -f ./config/samples/config_v1beta1_verifier_notation.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml assert_success # wait for the httpserver cache to be invalidated @@ -237,20 +237,20 @@ RATIFY_NAMESPACE=gatekeeper-system } # apply a valid verifier, validate status property shows success - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml assert_success run bash -c "kubectl describe verifiers.config.ratify.deislabs.io/verifier-license-checker -n ${RATIFY_NAMESPACE} | grep 'Issuccess: true'" assert_success # apply a invalid verifier CR, validate status with error - sed 's/licensechecker/invalidlicensechecker/' ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml > invalidVerifier.yaml + sed 's/licensechecker/invalidlicensechecker/' ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml > invalidVerifier.yaml run kubectl apply -f invalidVerifier.yaml assert_success run bash -c "kubectl describe verifiers.config.ratify.deislabs.io/verifier-license-checker -n ${RATIFY_NAMESPACE} | grep 'Brieferror: Original Error:'" assert_success # apply a valid verifier, validate status property shows success - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml assert_success run bash -c "kubectl describe verifiers.config.ratify.deislabs.io/verifier-license-checker -n ${RATIFY_NAMESPACE} | grep 'Issuccess: true'" assert_success @@ -327,7 +327,7 @@ RATIFY_NAMESPACE=gatekeeper-system # restore the original key management provider wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f kmprovider_staging.yaml' # restore the original notation verifier for other tests - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/config_v1beta1_verifier_notation.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml' } # save the existing key management provider inline resource to restore later @@ -368,7 +368,7 @@ RATIFY_NAMESPACE=gatekeeper-system wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo-alternate --namespace default --force --ignore-not-found=true' # restore the original notation verifier for other tests - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/config_v1beta1_verifier_notation.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml' } # configure the default template/constraint @@ -465,7 +465,7 @@ RATIFY_NAMESPACE=gatekeeper-system wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo-leaf2 --namespace default --force --ignore-not-found=true' # restore the original notation verifier for other tests - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/config_v1beta1_verifier_notation.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml' } # configure the default template/constraint diff --git a/test/bats/plugin-test.bats b/test/bats/plugin-test.bats index b395325c0..233da79fa 100644 --- a/test/bats/plugin-test.bats +++ b/test/bats/plugin-test.bats @@ -73,12 +73,12 @@ SLEEP_TIME=1 run kubectl apply -f ./library/default/samples/constraint.yaml assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_partial_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_partial_licensechecker.yaml sleep 5 run kubectl run license-checker --namespace default --image=registry:5000/licensechecker:v0 assert_failure - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml # wait for the httpserver cache to be invalidated sleep 15 run kubectl run license-checker2 --namespace default --image=registry:5000/licensechecker:v0 @@ -99,12 +99,12 @@ SLEEP_TIME=1 assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_sbom_deny.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_sbom_deny.yaml sleep 5 run kubectl run sbom --namespace default --image=registry:5000/sbom:v0 assert_failure - run kubectl apply -f ./config/samples/config_v1beta1_verifier_sbom.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_sbom.yaml # wait for the httpserver cache to be invalidated sleep 15 run kubectl run sbom --namespace default --image=registry:5000/sbom:v0 @@ -136,12 +136,12 @@ SLEEP_TIME=1 assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_schemavalidator.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator.yaml sleep 5 run kubectl run schemavalidator --namespace default --image=registry:5000/schemavalidator:v0 assert_success - run kubectl apply -f ./config/samples/config_v1beta1_verifier_schemavalidator_bad.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator_bad.yaml assert_success # wait for the httpserver cache to be invalidated sleep 15 @@ -164,12 +164,12 @@ SLEEP_TIME=1 assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_vulnerabilityreport2.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_vulnerabilityreport2.yaml sleep 5 run kubectl run vulnerabilityreport --namespace default --image=registry:5000/vulnerabilityreport:v0 assert_success sleep 15 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_vulnerabilityreport.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_vulnerabilityreport.yaml sleep 5 run kubectl run vulnerabilityreport2 --namespace default --image=registry:5000/vulnerabilityreport:v0 assert_failure @@ -192,12 +192,12 @@ SLEEP_TIME=1 assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_cosign.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_cosign.yaml sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_sbom.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_sbom.yaml sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml - run kubectl apply -f ./config/samples/config_v1beta1_verifier_schemavalidator.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator.yaml sleep 5 # wait for the httpserver cache to be invalidated @@ -213,7 +213,7 @@ SLEEP_TIME=1 } echo "adding license checker, delete notation verifier and validate deployment fails due to missing notation verifier" - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml assert_success run kubectl delete verifiers.config.ratify.deislabs.io/verifier-notation assert_success @@ -223,7 +223,7 @@ SLEEP_TIME=1 assert_failure echo "Add notation verifier and validate deployment succeeds" - run kubectl apply -f ./config/samples/config_v1beta1_verifier_notation.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml assert_success # wait for the httpserver cache to be invalidated @@ -241,7 +241,7 @@ SLEEP_TIME=1 start=$(date --iso-8601=seconds) latestpod=$(kubectl -n gatekeeper-system get pod -l=app.kubernetes.io/name=ratify --sort-by=.metadata.creationTimestamp -o=name | tail -n 1) - run kubectl apply -f ./config/samples/config_v1beta1_verifier_dynamic.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_dynamic.yaml sleep 5 run bash -c "kubectl -n gatekeeper-system logs $latestpod --since-time=$start | grep 'dynamic plugins are currently disabled'"