diff --git a/Makefile b/Makefile index b1a3ac0bc..08e470909 100644 --- a/Makefile +++ b/Makefile @@ -135,7 +135,7 @@ delete-demo-constraints: .PHONY: deploy-rego-policy deploy-rego-policy: - kubectl apply -f ./config/samples/policy/config_v1beta1_policy_rego.yaml + kubectl apply -f ./config/samples/clustered/policy/config_v1beta1_policy_rego.yaml .PHONY: deploy-gatekeeper deploy-gatekeeper: diff --git a/PROJECT b/PROJECT index 75078fb96..b91527b19 100644 --- a/PROJECT +++ b/PROJECT @@ -80,4 +80,12 @@ resources: kind: KeyManagementProvider path: github.com/deislabs/ratify/api/v1beta1 version: v1beta1 +- api: + crdVersion: v1 + namespaced: true + domain: ratify.deislabs.io + group: config + kind: NamespacedPolicy + path: github.com/deislabs/ratify/api/v1beta1 + version: v1beta1 version: "3" diff --git a/api/unversioned/namespacedpolicy_types.go b/api/unversioned/namespacedpolicy_types.go new file mode 100644 index 000000000..606fc6b59 --- /dev/null +++ b/api/unversioned/namespacedpolicy_types.go @@ -0,0 +1,63 @@ +/* +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 unversioned + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// NamespacedPolicySpec defines the desired state of Policy +type NamespacedPolicySpec struct { + // Important: Run "make" to regenerate code after modifying this file + + // Type of the policy + Type string `json:"type,omitempty"` + // Parameters for this policy + Parameters runtime.RawExtension `json:"parameters,omitempty"` +} + +// NamespacedPolicyStatus defines the observed state of Policy +type NamespacedPolicyStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Is successful while applying the policy. + IsSuccess bool `json:"issuccess"` + // Error message if NamespacedPolicy is not successfully applied. + // +optional + Error string `json:"error,omitempty"` + // Truncated error message if the message is too long + // +optional + BriefError string `json:"brieferror,omitempty"` +} + +// NamespacedPolicy is the Schema for the policies API +type NamespacedPolicy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NamespacedPolicySpec `json:"spec,omitempty"` + Status NamespacedPolicyStatus `json:"status,omitempty"` +} + +// NamespacedPolicyList contains a list of Policy +type NamespacedPolicyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NamespacedPolicy `json:"items"` +} diff --git a/api/unversioned/zz_generated.deepcopy.go b/api/unversioned/zz_generated.deepcopy.go index 433353d89..5e89a6dcb 100644 --- a/api/unversioned/zz_generated.deepcopy.go +++ b/api/unversioned/zz_generated.deepcopy.go @@ -181,6 +181,80 @@ func (in *KeyManagementProviderStatus) DeepCopy() *KeyManagementProviderStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedPolicy) DeepCopyInto(out *NamespacedPolicy) { + *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 NamespacedPolicy. +func (in *NamespacedPolicy) DeepCopy() *NamespacedPolicy { + if in == nil { + return nil + } + out := new(NamespacedPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedPolicyList) DeepCopyInto(out *NamespacedPolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NamespacedPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedPolicyList. +func (in *NamespacedPolicyList) DeepCopy() *NamespacedPolicyList { + if in == nil { + return nil + } + out := new(NamespacedPolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedPolicySpec) DeepCopyInto(out *NamespacedPolicySpec) { + *out = *in + in.Parameters.DeepCopyInto(&out.Parameters) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedPolicySpec. +func (in *NamespacedPolicySpec) DeepCopy() *NamespacedPolicySpec { + if in == nil { + return nil + } + out := new(NamespacedPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedPolicyStatus) DeepCopyInto(out *NamespacedPolicyStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedPolicyStatus. +func (in *NamespacedPolicyStatus) DeepCopy() *NamespacedPolicyStatus { + if in == nil { + return nil + } + out := new(NamespacedPolicyStatus) + 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/namespacedpolicy_types.go b/api/v1beta1/namespacedpolicy_types.go new file mode 100644 index 000000000..26747d5f6 --- /dev/null +++ b/api/v1beta1/namespacedpolicy_types.go @@ -0,0 +1,80 @@ +/* +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. + +// NamespacedPolicySpec defines the desired state of NamespacedPolicy +type NamespacedPolicySpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Type of the policy + Type string `json:"type,omitempty"` + // +kubebuilder:pruning:PreserveUnknownFields + // Parameters for this policy + Parameters runtime.RawExtension `json:"parameters,omitempty"` +} + +// NamespacedPolicyStatus defines the observed state of NamespacedPolicy +type NamespacedPolicyStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Is successful while applying the policy. + IsSuccess bool `json:"issuccess"` + // Error message if policy is not successfully applied. + // +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` +// NamespacedPolicy is the Schema for the namespacedpolicies API +type NamespacedPolicy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NamespacedPolicySpec `json:"spec,omitempty"` + Status NamespacedPolicyStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:storageversion +// NamespacedPolicyList contains a list of NamespacedPolicy +type NamespacedPolicyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NamespacedPolicy `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NamespacedPolicy{}, &NamespacedPolicyList{}) +} diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index 2e097dc80..2a98f8ac7 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -76,6 +76,86 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*KeyManagementProvider)(nil), (*unversioned.KeyManagementProvider)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_KeyManagementProvider_To_unversioned_KeyManagementProvider(a.(*KeyManagementProvider), b.(*unversioned.KeyManagementProvider), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.KeyManagementProvider)(nil), (*KeyManagementProvider)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_KeyManagementProvider_To_v1beta1_KeyManagementProvider(a.(*unversioned.KeyManagementProvider), b.(*KeyManagementProvider), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*KeyManagementProviderList)(nil), (*unversioned.KeyManagementProviderList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_KeyManagementProviderList_To_unversioned_KeyManagementProviderList(a.(*KeyManagementProviderList), b.(*unversioned.KeyManagementProviderList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.KeyManagementProviderList)(nil), (*KeyManagementProviderList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_KeyManagementProviderList_To_v1beta1_KeyManagementProviderList(a.(*unversioned.KeyManagementProviderList), b.(*KeyManagementProviderList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*KeyManagementProviderSpec)(nil), (*unversioned.KeyManagementProviderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_KeyManagementProviderSpec_To_unversioned_KeyManagementProviderSpec(a.(*KeyManagementProviderSpec), b.(*unversioned.KeyManagementProviderSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.KeyManagementProviderSpec)(nil), (*KeyManagementProviderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_KeyManagementProviderSpec_To_v1beta1_KeyManagementProviderSpec(a.(*unversioned.KeyManagementProviderSpec), b.(*KeyManagementProviderSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*KeyManagementProviderStatus)(nil), (*unversioned.KeyManagementProviderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_KeyManagementProviderStatus_To_unversioned_KeyManagementProviderStatus(a.(*KeyManagementProviderStatus), b.(*unversioned.KeyManagementProviderStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.KeyManagementProviderStatus)(nil), (*KeyManagementProviderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_KeyManagementProviderStatus_To_v1beta1_KeyManagementProviderStatus(a.(*unversioned.KeyManagementProviderStatus), b.(*KeyManagementProviderStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedPolicy)(nil), (*unversioned.NamespacedPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedPolicy_To_unversioned_NamespacedPolicy(a.(*NamespacedPolicy), b.(*unversioned.NamespacedPolicy), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedPolicy)(nil), (*NamespacedPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedPolicy_To_v1beta1_NamespacedPolicy(a.(*unversioned.NamespacedPolicy), b.(*NamespacedPolicy), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedPolicyList)(nil), (*unversioned.NamespacedPolicyList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedPolicyList_To_unversioned_NamespacedPolicyList(a.(*NamespacedPolicyList), b.(*unversioned.NamespacedPolicyList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedPolicyList)(nil), (*NamespacedPolicyList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedPolicyList_To_v1beta1_NamespacedPolicyList(a.(*unversioned.NamespacedPolicyList), b.(*NamespacedPolicyList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedPolicySpec)(nil), (*unversioned.NamespacedPolicySpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedPolicySpec_To_unversioned_NamespacedPolicySpec(a.(*NamespacedPolicySpec), b.(*unversioned.NamespacedPolicySpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedPolicySpec)(nil), (*NamespacedPolicySpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedPolicySpec_To_v1beta1_NamespacedPolicySpec(a.(*unversioned.NamespacedPolicySpec), b.(*NamespacedPolicySpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedPolicyStatus)(nil), (*unversioned.NamespacedPolicyStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedPolicyStatus_To_unversioned_NamespacedPolicyStatus(a.(*NamespacedPolicyStatus), b.(*unversioned.NamespacedPolicyStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedPolicyStatus)(nil), (*NamespacedPolicyStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedPolicyStatus_To_v1beta1_NamespacedPolicyStatus(a.(*unversioned.NamespacedPolicyStatus), b.(*NamespacedPolicyStatus), 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 { @@ -313,6 +393,210 @@ func Convert_unversioned_CertificateStoreStatus_To_v1beta1_CertificateStoreStatu return autoConvert_unversioned_CertificateStoreStatus_To_v1beta1_CertificateStoreStatus(in, out, s) } +func autoConvert_v1beta1_KeyManagementProvider_To_unversioned_KeyManagementProvider(in *KeyManagementProvider, out *unversioned.KeyManagementProvider, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_KeyManagementProviderSpec_To_unversioned_KeyManagementProviderSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_KeyManagementProviderStatus_To_unversioned_KeyManagementProviderStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_KeyManagementProvider_To_unversioned_KeyManagementProvider is an autogenerated conversion function. +func Convert_v1beta1_KeyManagementProvider_To_unversioned_KeyManagementProvider(in *KeyManagementProvider, out *unversioned.KeyManagementProvider, s conversion.Scope) error { + return autoConvert_v1beta1_KeyManagementProvider_To_unversioned_KeyManagementProvider(in, out, s) +} + +func autoConvert_unversioned_KeyManagementProvider_To_v1beta1_KeyManagementProvider(in *unversioned.KeyManagementProvider, out *KeyManagementProvider, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_unversioned_KeyManagementProviderSpec_To_v1beta1_KeyManagementProviderSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_unversioned_KeyManagementProviderStatus_To_v1beta1_KeyManagementProviderStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_unversioned_KeyManagementProvider_To_v1beta1_KeyManagementProvider is an autogenerated conversion function. +func Convert_unversioned_KeyManagementProvider_To_v1beta1_KeyManagementProvider(in *unversioned.KeyManagementProvider, out *KeyManagementProvider, s conversion.Scope) error { + return autoConvert_unversioned_KeyManagementProvider_To_v1beta1_KeyManagementProvider(in, out, s) +} + +func autoConvert_v1beta1_KeyManagementProviderList_To_unversioned_KeyManagementProviderList(in *KeyManagementProviderList, out *unversioned.KeyManagementProviderList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]unversioned.KeyManagementProvider)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_KeyManagementProviderList_To_unversioned_KeyManagementProviderList is an autogenerated conversion function. +func Convert_v1beta1_KeyManagementProviderList_To_unversioned_KeyManagementProviderList(in *KeyManagementProviderList, out *unversioned.KeyManagementProviderList, s conversion.Scope) error { + return autoConvert_v1beta1_KeyManagementProviderList_To_unversioned_KeyManagementProviderList(in, out, s) +} + +func autoConvert_unversioned_KeyManagementProviderList_To_v1beta1_KeyManagementProviderList(in *unversioned.KeyManagementProviderList, out *KeyManagementProviderList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]KeyManagementProvider)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_unversioned_KeyManagementProviderList_To_v1beta1_KeyManagementProviderList is an autogenerated conversion function. +func Convert_unversioned_KeyManagementProviderList_To_v1beta1_KeyManagementProviderList(in *unversioned.KeyManagementProviderList, out *KeyManagementProviderList, s conversion.Scope) error { + return autoConvert_unversioned_KeyManagementProviderList_To_v1beta1_KeyManagementProviderList(in, out, s) +} + +func autoConvert_v1beta1_KeyManagementProviderSpec_To_unversioned_KeyManagementProviderSpec(in *KeyManagementProviderSpec, out *unversioned.KeyManagementProviderSpec, s conversion.Scope) error { + out.Type = in.Type + out.Parameters = in.Parameters + return nil +} + +// Convert_v1beta1_KeyManagementProviderSpec_To_unversioned_KeyManagementProviderSpec is an autogenerated conversion function. +func Convert_v1beta1_KeyManagementProviderSpec_To_unversioned_KeyManagementProviderSpec(in *KeyManagementProviderSpec, out *unversioned.KeyManagementProviderSpec, s conversion.Scope) error { + return autoConvert_v1beta1_KeyManagementProviderSpec_To_unversioned_KeyManagementProviderSpec(in, out, s) +} + +func autoConvert_unversioned_KeyManagementProviderSpec_To_v1beta1_KeyManagementProviderSpec(in *unversioned.KeyManagementProviderSpec, out *KeyManagementProviderSpec, s conversion.Scope) error { + out.Type = in.Type + out.Parameters = in.Parameters + return nil +} + +// Convert_unversioned_KeyManagementProviderSpec_To_v1beta1_KeyManagementProviderSpec is an autogenerated conversion function. +func Convert_unversioned_KeyManagementProviderSpec_To_v1beta1_KeyManagementProviderSpec(in *unversioned.KeyManagementProviderSpec, out *KeyManagementProviderSpec, s conversion.Scope) error { + return autoConvert_unversioned_KeyManagementProviderSpec_To_v1beta1_KeyManagementProviderSpec(in, out, s) +} + +func autoConvert_v1beta1_KeyManagementProviderStatus_To_unversioned_KeyManagementProviderStatus(in *KeyManagementProviderStatus, out *unversioned.KeyManagementProviderStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + out.LastFetchedTime = (*v1.Time)(unsafe.Pointer(in.LastFetchedTime)) + out.Properties = in.Properties + return nil +} + +// Convert_v1beta1_KeyManagementProviderStatus_To_unversioned_KeyManagementProviderStatus is an autogenerated conversion function. +func Convert_v1beta1_KeyManagementProviderStatus_To_unversioned_KeyManagementProviderStatus(in *KeyManagementProviderStatus, out *unversioned.KeyManagementProviderStatus, s conversion.Scope) error { + return autoConvert_v1beta1_KeyManagementProviderStatus_To_unversioned_KeyManagementProviderStatus(in, out, s) +} + +func autoConvert_unversioned_KeyManagementProviderStatus_To_v1beta1_KeyManagementProviderStatus(in *unversioned.KeyManagementProviderStatus, out *KeyManagementProviderStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + out.LastFetchedTime = (*v1.Time)(unsafe.Pointer(in.LastFetchedTime)) + out.Properties = in.Properties + return nil +} + +// Convert_unversioned_KeyManagementProviderStatus_To_v1beta1_KeyManagementProviderStatus is an autogenerated conversion function. +func Convert_unversioned_KeyManagementProviderStatus_To_v1beta1_KeyManagementProviderStatus(in *unversioned.KeyManagementProviderStatus, out *KeyManagementProviderStatus, s conversion.Scope) error { + return autoConvert_unversioned_KeyManagementProviderStatus_To_v1beta1_KeyManagementProviderStatus(in, out, s) +} + +func autoConvert_v1beta1_NamespacedPolicy_To_unversioned_NamespacedPolicy(in *NamespacedPolicy, out *unversioned.NamespacedPolicy, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_NamespacedPolicySpec_To_unversioned_NamespacedPolicySpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_NamespacedPolicyStatus_To_unversioned_NamespacedPolicyStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_NamespacedPolicy_To_unversioned_NamespacedPolicy is an autogenerated conversion function. +func Convert_v1beta1_NamespacedPolicy_To_unversioned_NamespacedPolicy(in *NamespacedPolicy, out *unversioned.NamespacedPolicy, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedPolicy_To_unversioned_NamespacedPolicy(in, out, s) +} + +func autoConvert_unversioned_NamespacedPolicy_To_v1beta1_NamespacedPolicy(in *unversioned.NamespacedPolicy, out *NamespacedPolicy, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_unversioned_NamespacedPolicySpec_To_v1beta1_NamespacedPolicySpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_unversioned_NamespacedPolicyStatus_To_v1beta1_NamespacedPolicyStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_unversioned_NamespacedPolicy_To_v1beta1_NamespacedPolicy is an autogenerated conversion function. +func Convert_unversioned_NamespacedPolicy_To_v1beta1_NamespacedPolicy(in *unversioned.NamespacedPolicy, out *NamespacedPolicy, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedPolicy_To_v1beta1_NamespacedPolicy(in, out, s) +} + +func autoConvert_v1beta1_NamespacedPolicyList_To_unversioned_NamespacedPolicyList(in *NamespacedPolicyList, out *unversioned.NamespacedPolicyList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]unversioned.NamespacedPolicy)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_NamespacedPolicyList_To_unversioned_NamespacedPolicyList is an autogenerated conversion function. +func Convert_v1beta1_NamespacedPolicyList_To_unversioned_NamespacedPolicyList(in *NamespacedPolicyList, out *unversioned.NamespacedPolicyList, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedPolicyList_To_unversioned_NamespacedPolicyList(in, out, s) +} + +func autoConvert_unversioned_NamespacedPolicyList_To_v1beta1_NamespacedPolicyList(in *unversioned.NamespacedPolicyList, out *NamespacedPolicyList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]NamespacedPolicy)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_unversioned_NamespacedPolicyList_To_v1beta1_NamespacedPolicyList is an autogenerated conversion function. +func Convert_unversioned_NamespacedPolicyList_To_v1beta1_NamespacedPolicyList(in *unversioned.NamespacedPolicyList, out *NamespacedPolicyList, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedPolicyList_To_v1beta1_NamespacedPolicyList(in, out, s) +} + +func autoConvert_v1beta1_NamespacedPolicySpec_To_unversioned_NamespacedPolicySpec(in *NamespacedPolicySpec, out *unversioned.NamespacedPolicySpec, s conversion.Scope) error { + out.Type = in.Type + out.Parameters = in.Parameters + return nil +} + +// Convert_v1beta1_NamespacedPolicySpec_To_unversioned_NamespacedPolicySpec is an autogenerated conversion function. +func Convert_v1beta1_NamespacedPolicySpec_To_unversioned_NamespacedPolicySpec(in *NamespacedPolicySpec, out *unversioned.NamespacedPolicySpec, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedPolicySpec_To_unversioned_NamespacedPolicySpec(in, out, s) +} + +func autoConvert_unversioned_NamespacedPolicySpec_To_v1beta1_NamespacedPolicySpec(in *unversioned.NamespacedPolicySpec, out *NamespacedPolicySpec, s conversion.Scope) error { + out.Type = in.Type + out.Parameters = in.Parameters + return nil +} + +// Convert_unversioned_NamespacedPolicySpec_To_v1beta1_NamespacedPolicySpec is an autogenerated conversion function. +func Convert_unversioned_NamespacedPolicySpec_To_v1beta1_NamespacedPolicySpec(in *unversioned.NamespacedPolicySpec, out *NamespacedPolicySpec, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedPolicySpec_To_v1beta1_NamespacedPolicySpec(in, out, s) +} + +func autoConvert_v1beta1_NamespacedPolicyStatus_To_unversioned_NamespacedPolicyStatus(in *NamespacedPolicyStatus, out *unversioned.NamespacedPolicyStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + return nil +} + +// Convert_v1beta1_NamespacedPolicyStatus_To_unversioned_NamespacedPolicyStatus is an autogenerated conversion function. +func Convert_v1beta1_NamespacedPolicyStatus_To_unversioned_NamespacedPolicyStatus(in *NamespacedPolicyStatus, out *unversioned.NamespacedPolicyStatus, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedPolicyStatus_To_unversioned_NamespacedPolicyStatus(in, out, s) +} + +func autoConvert_unversioned_NamespacedPolicyStatus_To_v1beta1_NamespacedPolicyStatus(in *unversioned.NamespacedPolicyStatus, out *NamespacedPolicyStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + return nil +} + +// Convert_unversioned_NamespacedPolicyStatus_To_v1beta1_NamespacedPolicyStatus is an autogenerated conversion function. +func Convert_unversioned_NamespacedPolicyStatus_To_v1beta1_NamespacedPolicyStatus(in *unversioned.NamespacedPolicyStatus, out *NamespacedPolicyStatus, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedPolicyStatus_To_v1beta1_NamespacedPolicyStatus(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 8e390e3c8..076806a98 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -215,6 +215,96 @@ func (in *KeyManagementProviderStatus) DeepCopy() *KeyManagementProviderStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedPolicy) DeepCopyInto(out *NamespacedPolicy) { + *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 NamespacedPolicy. +func (in *NamespacedPolicy) DeepCopy() *NamespacedPolicy { + if in == nil { + return nil + } + out := new(NamespacedPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedPolicy) 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 *NamespacedPolicyList) DeepCopyInto(out *NamespacedPolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NamespacedPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedPolicyList. +func (in *NamespacedPolicyList) DeepCopy() *NamespacedPolicyList { + if in == nil { + return nil + } + out := new(NamespacedPolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedPolicyList) 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 *NamespacedPolicySpec) DeepCopyInto(out *NamespacedPolicySpec) { + *out = *in + in.Parameters.DeepCopyInto(&out.Parameters) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedPolicySpec. +func (in *NamespacedPolicySpec) DeepCopy() *NamespacedPolicySpec { + if in == nil { + return nil + } + out := new(NamespacedPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedPolicyStatus) DeepCopyInto(out *NamespacedPolicyStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedPolicyStatus. +func (in *NamespacedPolicyStatus) DeepCopy() *NamespacedPolicyStatus { + if in == nil { + return nil + } + out := new(NamespacedPolicyStatus) + 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/namespacedpolicy-customresourcedefinition.yaml b/charts/ratify/crds/namespacedpolicy-customresourcedefinition.yaml new file mode 100644 index 000000000..c18d01a6b --- /dev/null +++ b/charts/ratify/crds/namespacedpolicy-customresourcedefinition.yaml @@ -0,0 +1,74 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: namespacedpolicies.config.ratify.deislabs.io +spec: + group: config.ratify.deislabs.io + names: + kind: NamespacedPolicy + listKind: NamespacedPolicyList + plural: namespacedpolicies + singular: namespacedpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: NamespacedPolicy is the Schema for the namespacedpolicies 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: NamespacedPolicySpec defines the desired state of NamespacedPolicy + properties: + parameters: + description: Parameters for this policy + type: object + x-kubernetes-preserve-unknown-fields: true + type: + description: Type of the policy + type: string + type: object + status: + description: NamespacedPolicyStatus defines the observed state of NamespacedPolicy + properties: + brieferror: + description: Truncated error message if the message is too long + type: string + error: + description: Error message if policy is not successfully applied. + type: string + issuccess: + description: Is successful while applying the policy. + 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 854cf542b..a2bd679b6 100644 --- a/charts/ratify/templates/ratify-manager-role-clusterrole.yaml +++ b/charts/ratify/templates/ratify-manager-role-clusterrole.yaml @@ -135,6 +135,32 @@ rules: - get - patch - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies/finalizers + verbs: + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies/status + verbs: + - get + - patch + - update - apiGroups: - externaldata.gatekeeper.sh resources: diff --git a/config/crd/bases/config.ratify.deislabs.io_namespacedpolicies.yaml b/config/crd/bases/config.ratify.deislabs.io_namespacedpolicies.yaml new file mode 100644 index 000000000..c18d01a6b --- /dev/null +++ b/config/crd/bases/config.ratify.deislabs.io_namespacedpolicies.yaml @@ -0,0 +1,74 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: namespacedpolicies.config.ratify.deislabs.io +spec: + group: config.ratify.deislabs.io + names: + kind: NamespacedPolicy + listKind: NamespacedPolicyList + plural: namespacedpolicies + singular: namespacedpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: NamespacedPolicy is the Schema for the namespacedpolicies 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: NamespacedPolicySpec defines the desired state of NamespacedPolicy + properties: + parameters: + description: Parameters for this policy + type: object + x-kubernetes-preserve-unknown-fields: true + type: + description: Type of the policy + type: string + type: object + status: + description: NamespacedPolicyStatus defines the observed state of NamespacedPolicy + properties: + brieferror: + description: Truncated error message if the message is too long + type: string + error: + description: Error message if policy is not successfully applied. + type: string + issuccess: + description: Is successful while applying the policy. + 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 26aa14b25..5ade6da93 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -7,6 +7,7 @@ resources: - bases/config.ratify.deislabs.io_certificatestores.yaml - bases/config.ratify.deislabs.io_policies.yaml - bases/config.ratify.deislabs.io_keymanagementproviders.yaml + - bases/config.ratify.deislabs.io_namespacedpolicies.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -17,6 +18,7 @@ patchesStrategicMerge: #- patches/webhook_in_certificatestores.yaml #- patches/webhook_in_policies.yaml #- patches/webhook_in_keymanagementproviders.yaml + #- patches/webhook_in_namespacedpolicies.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -26,6 +28,7 @@ patchesStrategicMerge: #- patches/cainjection_in_certificatestores.yaml #- patches/cainjection_in_policies.yaml #- patches/cainjection_in_keymanagementproviders.yaml + #- patches/cainjection_in_namespacedpolicies.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_clusterpolicies.yaml b/config/crd/patches/cainjection_in_clusterpolicies.yaml new file mode 100644 index 000000000..8e6a64b27 --- /dev/null +++ b/config/crd/patches/cainjection_in_clusterpolicies.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: clusterpolicies.config.ratify.deislabs.io diff --git a/config/crd/patches/cainjection_in_namespacedpolicies.yaml b/config/crd/patches/cainjection_in_namespacedpolicies.yaml new file mode 100644 index 000000000..be47ac51f --- /dev/null +++ b/config/crd/patches/cainjection_in_namespacedpolicies.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: namespacedpolicies.config.ratify.deislabs.io diff --git a/config/crd/patches/webhook_in_clusterpolicies.yaml b/config/crd/patches/webhook_in_clusterpolicies.yaml new file mode 100644 index 000000000..de0e79ec2 --- /dev/null +++ b/config/crd/patches/webhook_in_clusterpolicies.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: clusterpolicies.config.ratify.deislabs.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_namespacedpolicies.yaml b/config/crd/patches/webhook_in_namespacedpolicies.yaml new file mode 100644 index 000000000..057d373aa --- /dev/null +++ b/config/crd/patches/webhook_in_namespacedpolicies.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: namespacedpolicies.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/namespacedpolicy_editor_role.yaml b/config/rbac/namespacedpolicy_editor_role.yaml new file mode 100644 index 000000000..fb1e31c21 --- /dev/null +++ b/config/rbac/namespacedpolicy_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit namespacedpolicies. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: namespacedpolicy-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: namespacedpolicy-editor-role +rules: +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies/status + verbs: + - get diff --git a/config/rbac/namespacedpolicy_viewer_role.yaml b/config/rbac/namespacedpolicy_viewer_role.yaml new file mode 100644 index 000000000..4dc2f98b2 --- /dev/null +++ b/config/rbac/namespacedpolicy_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view namespacedpolicies. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: namespacedpolicy-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: namespacedpolicy-viewer-role +rules: +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies + verbs: + - get + - list + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 36974c9aa..c787b8937 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -57,6 +57,32 @@ rules: - get - patch - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies/finalizers + verbs: + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies/status + verbs: + - get + - patch + - update - apiGroups: - config.ratify.deislabs.io resources: diff --git a/config/samples/clustered/policy/config_v1alpha1_policy_json.yaml b/config/samples/clustered/policy/config_v1alpha1_policy_json.yaml new file mode 100644 index 000000000..a947d5966 --- /dev/null +++ b/config/samples/clustered/policy/config_v1alpha1_policy_json.yaml @@ -0,0 +1,8 @@ +apiVersion: config.ratify.deislabs.io/v1alpha1 +kind: Policy # Policy applies to the cluster. +metadata: + name: "configpolicy" # Ensure that metadata.name is either 'regopolicy' or 'configpolicy' +spec: + parameters: + artifactVerificationPolicies: + default: "all" diff --git a/config/samples/policy/config_v1alpha1_policy_rego.yaml b/config/samples/clustered/policy/config_v1alpha1_policy_rego.yaml similarity index 83% rename from config/samples/policy/config_v1alpha1_policy_rego.yaml rename to config/samples/clustered/policy/config_v1alpha1_policy_rego.yaml index f80978309..45c3695f4 100644 --- a/config/samples/policy/config_v1alpha1_policy_rego.yaml +++ b/config/samples/clustered/policy/config_v1alpha1_policy_rego.yaml @@ -1,7 +1,7 @@ apiVersion: config.ratify.deislabs.io/v1alpha1 -kind: Policy +kind: Policy # Policy applies to the cluster. metadata: - name: "regopolicy" + name: "regopolicy" # Ensure that metadata.name is either 'regopolicy' or 'configpolicy' spec: parameters: passthroughEnabled: false diff --git a/config/samples/clustered/policy/config_v1beta1_policy_json.yaml b/config/samples/clustered/policy/config_v1beta1_policy_json.yaml new file mode 100644 index 000000000..00c35331b --- /dev/null +++ b/config/samples/clustered/policy/config_v1beta1_policy_json.yaml @@ -0,0 +1,9 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: Policy # Policy applies to the cluster. +metadata: + name: "ratify-policy" # metadata.name MUST be set to ratify-policy since v1beta1. +spec: + type: "config-policy" # Ensure that spec.type is either 'rego-policy' or 'config-policy' in v1beta1. + parameters: + artifactVerificationPolicies: + default: "all" diff --git a/config/samples/policy/config_v1beta1_policy_rego.yaml b/config/samples/clustered/policy/config_v1beta1_policy_rego.yaml similarity index 74% rename from config/samples/policy/config_v1beta1_policy_rego.yaml rename to config/samples/clustered/policy/config_v1beta1_policy_rego.yaml index 89ac6a056..6bc1451d6 100644 --- a/config/samples/policy/config_v1beta1_policy_rego.yaml +++ b/config/samples/clustered/policy/config_v1beta1_policy_rego.yaml @@ -1,9 +1,9 @@ apiVersion: config.ratify.deislabs.io/v1beta1 -kind: Policy +kind: Policy # Policy applies to the cluster. metadata: - name: "ratify-policy" + name: "ratify-policy" # metadata.name MUST be set to ratify-policy since v1beta1. spec: - type: "rego-policy" + type: "rego-policy" # Ensure that spec.type is either 'rego-policy' or 'config-policy' in v1beta1. parameters: passthroughEnabled: false policy: | diff --git a/config/samples/namespaced/policy/config_v1beta1_policy_json.yaml b/config/samples/namespaced/policy/config_v1beta1_policy_json.yaml new file mode 100644 index 000000000..bc4b32951 --- /dev/null +++ b/config/samples/namespaced/policy/config_v1beta1_policy_json.yaml @@ -0,0 +1,9 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedPolicy # NamespacedPolicy only applies to specified namespace. +metadata: + name: "ratify-policy" # metadata.name MUST be set to ratify-policy since v1beta1. +spec: + type: "config-policy" # Ensure that spec.type is either 'rego-policy' or 'config-policy' in v1beta1. + parameters: + artifactVerificationPolicies: + default: "all" diff --git a/config/samples/namespaced/policy/config_v1beta1_policy_rego.yaml b/config/samples/namespaced/policy/config_v1beta1_policy_rego.yaml new file mode 100644 index 000000000..9aaec1393 --- /dev/null +++ b/config/samples/namespaced/policy/config_v1beta1_policy_rego.yaml @@ -0,0 +1,31 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedPolicy # NamespacedPolicy only applies to specified namespace. +metadata: + name: "ratify-policy" # metadata.name MUST be set to ratify-policy since v1beta1. +spec: + type: "rego-policy" # Ensure that spec.type is either 'rego-policy' or 'config-policy' in v1beta1. + parameters: + passthroughEnabled: false + policy: | + package ratify.policy + + default valid := false + + # all artifacts MUST be valid + valid { + not failed_verify(input) + } + + # all reports MUST pass the verification + failed_verify(reports) { + [path, value] := walk(reports) + value == false + path[count(path) - 1] == "isSuccess" + } + + # each artifact MUST have at least one report + failed_verify(reports) { + [path, value] := walk(reports) + path[count(path) - 1] == "verifierReports" + count(value) == 0 + } diff --git a/config/samples/policy/config_v1alpha1_policy_json.yaml b/config/samples/policy/config_v1alpha1_policy_json.yaml deleted file mode 100644 index 127bab714..000000000 --- a/config/samples/policy/config_v1alpha1_policy_json.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: config.ratify.deislabs.io/v1alpha1 -kind: Policy -metadata: - name: "configpolicy" -spec: - parameters: - artifactVerificationPolicies: - default: "all" diff --git a/config/samples/policy/config_v1beta1_policy_json.yaml b/config/samples/policy/config_v1beta1_policy_json.yaml deleted file mode 100644 index 1fbf1adba..000000000 --- a/config/samples/policy/config_v1beta1_policy_json.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: config.ratify.deislabs.io/v1beta1 -kind: Policy -metadata: - name: "ratify-policy" -spec: - type: "config-policy" - parameters: - artifactVerificationPolicies: - default: "all" diff --git a/httpserver/handlers.go b/httpserver/handlers.go index 46267bf91..4f4d02aa7 100644 --- a/httpserver/handlers.go +++ b/httpserver/handlers.go @@ -257,7 +257,7 @@ func (server *Server) validateComponents(ctx context.Context, handlerComponents return errors.ErrorCodeConfigInvalid.WithComponentType(errors.ReferrerStore).WithDetail("referrer store config should have at least one store") } if server.GetExecutor(ctx).PolicyEnforcer == nil { - return errors.ErrorCodeConfigInvalid.WithComponentType(errors.PolicyProvider).WithDetail("policy provider config must be specified") + return errors.ErrorCodeConfigInvalid.WithComponentType(errors.PolicyProvider).WithDetail("policy provider config is not provided") } if len(server.GetExecutor(ctx).Verifiers) == 0 { return errors.ErrorCodeConfigInvalid.WithComponentType(errors.Verifier).WithDetail("verifiers config should have at least one verifier") diff --git a/httpserver/server_test.go b/httpserver/server_test.go index cb04b73d2..1aaf98ecd 100644 --- a/httpserver/server_test.go +++ b/httpserver/server_test.go @@ -592,7 +592,7 @@ func TestServer_Verify_PolicyEnforcerConfigInvalid_Failure(t *testing.T) { t.Fatalf("failed to decode response body: %v", err) } retFirstErr := respBody.Response.Items[0].Error - expectedErr := ratifyerrors.ErrorCodeConfigInvalid.WithComponentType(ratifyerrors.PolicyProvider).WithDetail("policy provider config must be specified").Error() + expectedErr := ratifyerrors.ErrorCodeConfigInvalid.WithComponentType(ratifyerrors.PolicyProvider).WithDetail("policy provider config is not provided").Error() if retFirstErr != expectedErr { t.Fatalf("Expected first subject error to be %s but got %s", expectedErr, retFirstErr) } diff --git a/internal/constants/constants.go b/internal/constants/constants.go index cc6866ec4..adda96a6d 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -19,3 +19,4 @@ package constants const RatifyPolicy = "ratify-policy" const EmptyNamespace = "" const NamespaceSeperator = "/" +const MaxBriefErrLength = 30 diff --git a/pkg/controllers/policy_controller.go b/pkg/controllers/clusterresource/policy_controller.go similarity index 69% rename from pkg/controllers/policy_controller.go rename to pkg/controllers/clusterresource/policy_controller.go index babec48c6..56283e5c9 100644 --- a/pkg/controllers/policy_controller.go +++ b/pkg/controllers/clusterresource/policy_controller.go @@ -13,18 +13,16 @@ 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/internal/constants" - "github.com/deislabs/ratify/pkg/policyprovider" - "github.com/deislabs/ratify/pkg/policyprovider/config" - pf "github.com/deislabs/ratify/pkg/policyprovider/factory" + "github.com/deislabs/ratify/pkg/controllers" + "github.com/deislabs/ratify/pkg/controllers/utils" "github.com/sirupsen/logrus" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -52,13 +50,12 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr var policy configv1beta1.Policy var resource = req.Name - policyLogger.Infof("Reconciling Policy %s", resource) + policyLogger.Infof("Reconciling Cluster Policy %s", resource) if err := r.Get(ctx, req.NamespacedName, &policy); err != nil { if apierrors.IsNotFound(err) { policyLogger.Infof("delete event detected, removing policy %s", resource) - // TODO: pass the actual namespace once multi-tenancy is supported. - NamespacedPolicies.DeletePolicy(constants.EmptyNamespace, resource) + controllers.NamespacedPolicies.DeletePolicy(constants.EmptyNamespace, resource) } else { policyLogger.Error("failed to get Policy: ", err) } @@ -90,47 +87,15 @@ func (r *PolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { } func policyAddOrReplace(spec configv1beta1.PolicySpec) error { - policyEnforcer, err := specToPolicyEnforcer(spec) + policyEnforcer, err := utils.SpecToPolicyEnforcer(spec.Parameters.Raw, spec.Type) if err != nil { return fmt.Errorf("failed to create policy enforcer: %w", err) } - // TODO: pass the actual namespace once multi-tenancy is supported. - NamespacedPolicies.AddPolicy(constants.EmptyNamespace, constants.RatifyPolicy, policyEnforcer) + controllers.NamespacedPolicies.AddPolicy(constants.EmptyNamespace, constants.RatifyPolicy, policyEnforcer) return nil } -func specToPolicyEnforcer(spec configv1beta1.PolicySpec) (policyprovider.PolicyProvider, error) { - policyConfig, err := rawToPolicyConfig(spec.Parameters.Raw, spec.Type) - if err != nil { - return nil, fmt.Errorf("failed to parse policy config: %w", err) - } - - policyEnforcer, err := pf.CreatePolicyProviderFromConfig(policyConfig) - if err != nil { - return nil, fmt.Errorf("failed to create policy provider: %w", err) - } - - return policyEnforcer, nil -} - -func rawToPolicyConfig(raw []byte, policyName string) (config.PoliciesConfig, error) { - pluginConfig := config.PolicyPluginConfig{} - - if string(raw) == "" { - return config.PoliciesConfig{}, fmt.Errorf("no policy parameters provided") - } - if err := json.Unmarshal(raw, &pluginConfig); err != nil { - return config.PoliciesConfig{}, fmt.Errorf("unable to decode policy parameters.Raw: %s, err: %w", raw, err) - } - - pluginConfig["name"] = policyName - - return config.PoliciesConfig{ - PolicyPlugin: pluginConfig, - }, nil -} - func writePolicyStatus(ctx context.Context, r client.StatusClient, policy *configv1beta1.Policy, logger *logrus.Entry, isSuccess bool, errString string) { if isSuccess { updatePolicySuccessStatus(policy) @@ -150,8 +115,8 @@ func updatePolicySuccessStatus(policy *configv1beta1.Policy) { func updatePolicyErrorStatus(policy *configv1beta1.Policy, errString string) { briefErr := errString - if len(errString) > maxBriefErrLength { - briefErr = fmt.Sprintf("%s...", errString[:maxBriefErrLength]) + if len(errString) > constants.MaxBriefErrLength { + briefErr = fmt.Sprintf("%s...", errString[:constants.MaxBriefErrLength]) } policy.Status.IsSuccess = false policy.Status.Error = errString diff --git a/pkg/controllers/clusterresource/policy_controller_test.go b/pkg/controllers/clusterresource/policy_controller_test.go new file mode 100644 index 000000000..eb3713557 --- /dev/null +++ b/pkg/controllers/clusterresource/policy_controller_test.go @@ -0,0 +1,240 @@ +/* +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 clusterresource + +import ( + "context" + "testing" + + 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/policies" + _ "github.com/deislabs/ratify/pkg/policyprovider/configpolicy" + test "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 ( + policyName1 = "policy1" + policyName2 = "policy2" +) + +func TestPolicyAddOrReplace(t *testing.T) { + testCases := []struct { + name string + spec configv1beta1.PolicySpec + policyName string + expectErr bool + }{ + { + name: "invalid spec", + spec: configv1beta1.PolicySpec{ + Type: policyName1, + }, + expectErr: true, + }, + { + name: "valid spec", + spec: configv1beta1.PolicySpec{ + Parameters: runtime.RawExtension{ + Raw: []byte("{\"name\": \"configpolicy\"}"), + }, + Type: "configpolicy", + }, + expectErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := policyAddOrReplace(tc.spec) + + if tc.expectErr != (err != nil) { + t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) + } + }) + } +} + +func TestWritePolicyStatus(t *testing.T) { + logger := logrus.WithContext(context.Background()) + testCases := []struct { + name string + isSuccess bool + policy *configv1beta1.Policy + errString string + reconciler client.StatusClient + }{ + { + name: "success status", + isSuccess: true, + policy: &configv1beta1.Policy{}, + reconciler: &test.MockStatusClient{}, + }, + { + name: "error status", + isSuccess: false, + policy: &configv1beta1.Policy{}, + errString: "a long error string that exceeds the max length of 30 characters", + reconciler: &test.MockStatusClient{}, + }, + { + name: "status update failed", + isSuccess: true, + policy: &configv1beta1.Policy{}, + reconciler: &test.MockStatusClient{ + UpdateFailed: true, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + writePolicyStatus(context.Background(), tc.reconciler, tc.policy, logger, tc.isSuccess, tc.errString) + }) + } +} + +func TestPolicyReconcile(t *testing.T) { + tests := []struct { + name string + policy *configv1beta1.Policy + req *reconcile.Request + expectedErr bool + expectedPolicy bool + }{ + { + name: "nonexistent policy", + req: &reconcile.Request{ + NamespacedName: types.NamespacedName{Name: "nonexistent"}, + }, + policy: &configv1beta1.Policy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: policyName1, + }, + }, + expectedErr: false, + expectedPolicy: false, + }, + { + name: "no policy parameters provided", + policy: &configv1beta1.Policy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: "ratify-policy", + }, + Spec: configv1beta1.PolicySpec{ + Type: "regopolicy", + }, + }, + expectedErr: true, + expectedPolicy: false, + }, + { + name: "wrong policy name", + policy: &configv1beta1.Policy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: "ratify-policy2", + }, + Spec: configv1beta1.PolicySpec{ + Type: "regopolicy", + }, + }, + expectedErr: false, + expectedPolicy: false, + }, + { + name: "invalid params", + policy: &configv1beta1.Policy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: "ratify-policy", + }, + Spec: configv1beta1.PolicySpec{ + Type: "regopolicy", + Parameters: runtime.RawExtension{ + Raw: []byte("test"), + }, + }, + }, + expectedErr: true, + expectedPolicy: false, + }, + { + name: "valid params", + policy: &configv1beta1.Policy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: "ratify-policy", + }, + Spec: configv1beta1.PolicySpec{ + Type: "configpolicy", + Parameters: runtime.RawExtension{ + Raw: []byte("{\"passthroughEnabled:\": false}"), + }, + }, + }, + expectedErr: false, + expectedPolicy: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resetPolicyMap() + scheme, err := test.CreateScheme() + if err != nil { + t.Fatalf("CreateScheme() expected no error, actual %v", err) + } + client := fake.NewClientBuilder().WithScheme(scheme) + client.WithObjects(tt.policy) + r := &PolicyReconciler{ + Scheme: scheme, + Client: client.Build(), + } + var req reconcile.Request + if tt.req != nil { + req = *tt.req + } else { + req = reconcile.Request{ + NamespacedName: test.KeyFor(tt.policy), + } + } + _, err = r.Reconcile(context.Background(), req) + if tt.expectedErr != (err != nil) { + t.Fatalf("Reconcile() expected error to be %t, actual %t", tt.expectedErr, err != nil) + } + + policy := controllers.NamespacedPolicies.GetPolicy(constants.EmptyNamespace) + if (policy != nil) != tt.expectedPolicy { + t.Fatalf("Expected policy to be %t, got %t", tt.expectedPolicy, policy != nil) + } + }) + } +} + +func resetPolicyMap() { + controllers.NamespacedPolicies = policies.NewActivePolicies() +} diff --git a/pkg/controllers/namespaceresource/policy_controller.go b/pkg/controllers/namespaceresource/policy_controller.go new file mode 100644 index 000000000..988bdd3e2 --- /dev/null +++ b/pkg/controllers/namespaceresource/policy_controller.go @@ -0,0 +1,124 @@ +/* +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" + "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" +) + +// PolicyReconciler reconciles a Policy object +type PolicyReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedpolicies,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedpolicies/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedpolicies/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. +// +// 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 *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + policyLogger := logrus.WithContext(ctx) + + var policy configv1beta1.NamespacedPolicy + var resource = req.Name + policyLogger.Infof("Reconciling Namespaced Policy %s", resource) + + if err := r.Get(ctx, req.NamespacedName, &policy); err != nil { + if apierrors.IsNotFound(err) { + policyLogger.Infof("delete event detected, removing policy %s", resource) + controllers.NamespacedPolicies.DeletePolicy(req.Namespace, resource) + } else { + policyLogger.Error("failed to get Policy: ", err) + } + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + if resource != constants.RatifyPolicy { + errStr := fmt.Sprintf("metadata.name must be ratify-policy, got %s", resource) + policyLogger.Error(errStr) + writePolicyStatus(ctx, r, &policy, policyLogger, false, errStr) + return ctrl.Result{}, nil + } + + if err := policyAddOrReplace(policy.Spec, req.Namespace); err != nil { + policyLogger.Error("unable to create policy from policy crd: ", err) + writePolicyStatus(ctx, r, &policy, policyLogger, false, err.Error()) + return ctrl.Result{}, err + } + + writePolicyStatus(ctx, r, &policy, policyLogger, true, "") + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *PolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&configv1beta1.NamespacedPolicy{}). + Complete(r) +} + +func policyAddOrReplace(spec configv1beta1.NamespacedPolicySpec, namespace string) error { + policyEnforcer, err := utils.SpecToPolicyEnforcer(spec.Parameters.Raw, spec.Type) + if err != nil { + return fmt.Errorf("failed to create policy enforcer: %w", err) + } + + controllers.NamespacedPolicies.AddPolicy(namespace, constants.RatifyPolicy, policyEnforcer) + return nil +} + +func writePolicyStatus(ctx context.Context, r client.StatusClient, policy *configv1beta1.NamespacedPolicy, logger *logrus.Entry, isSuccess bool, errString string) { + if isSuccess { + updatePolicySuccessStatus(policy) + } else { + updatePolicyErrorStatus(policy, errString) + } + if statusErr := r.Status().Update(ctx, policy); statusErr != nil { + logger.Error(statusErr, ", unable to update policy error status") + } +} + +func updatePolicySuccessStatus(policy *configv1beta1.NamespacedPolicy) { + policy.Status.IsSuccess = true + policy.Status.Error = "" + policy.Status.BriefError = "" +} + +func updatePolicyErrorStatus(policy *configv1beta1.NamespacedPolicy, errString string) { + briefErr := errString + if len(errString) > constants.MaxBriefErrLength { + briefErr = fmt.Sprintf("%s...", errString[:constants.MaxBriefErrLength]) + } + policy.Status.IsSuccess = false + policy.Status.Error = errString + policy.Status.BriefError = briefErr +} diff --git a/pkg/controllers/namespaceresource/policy_controller_test.go b/pkg/controllers/namespaceresource/policy_controller_test.go new file mode 100644 index 000000000..2764f0faf --- /dev/null +++ b/pkg/controllers/namespaceresource/policy_controller_test.go @@ -0,0 +1,269 @@ +/* +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" + "testing" + + configv1beta1 "github.com/deislabs/ratify/api/v1beta1" + "github.com/deislabs/ratify/pkg/controllers" + "github.com/deislabs/ratify/pkg/customresources/policies" + _ "github.com/deislabs/ratify/pkg/policyprovider/configpolicy" + _ "github.com/deislabs/ratify/pkg/policyprovider/regopolicy" + test "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 ( + policyName1 = "policy1" + policyName2 = "policy2" + testNamespace = "testNamespace" +) + +func TestPolicyAddOrReplace(t *testing.T) { + testCases := []struct { + name string + spec configv1beta1.NamespacedPolicySpec + policyName string + expectErr bool + }{ + { + name: "invalid spec", + spec: configv1beta1.NamespacedPolicySpec{ + Type: policyName1, + }, + expectErr: true, + }, + { + name: "valid spec", + spec: configv1beta1.NamespacedPolicySpec{ + Parameters: runtime.RawExtension{ + Raw: []byte("{\"name\": \"configpolicy\"}"), + }, + Type: "configpolicy", + }, + expectErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := policyAddOrReplace(tc.spec, testNamespace) + + if tc.expectErr != (err != nil) { + t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) + } + }) + } +} + +func TestPolicyAddedTwice(t *testing.T) { + resetPolicyMap() + spec1 := configv1beta1.NamespacedPolicySpec{ + Parameters: runtime.RawExtension{ + Raw: []byte("{\"name\": \"configpolicy\"}"), + }, + Type: "configpolicy", + } + spec2 := configv1beta1.NamespacedPolicySpec{ + Type: "regopolicy", + Parameters: runtime.RawExtension{ + Raw: []byte("{\"name\": \"regopolicy\", \"policy\": \"package ratify.policy\"}"), + }, + } + if err := policyAddOrReplace(spec1, testNamespace); err != nil { + t.Fatalf("expected no error, got %v", err) + } + if err := policyAddOrReplace(spec2, testNamespace); err != nil { + t.Fatalf("expected no error, got %v", err) + } + + policyType := controllers.NamespacedPolicies.GetPolicy(testNamespace).GetPolicyType(context.Background()) + if policyType != "regopolicy" { + t.Fatalf("expected policy type to be regopolicy, got %s", policyType) + } +} + +func TestWritePolicyStatus(t *testing.T) { + logger := logrus.WithContext(context.Background()) + testCases := []struct { + name string + isSuccess bool + policy *configv1beta1.NamespacedPolicy + errString string + reconciler client.StatusClient + }{ + { + name: "success status", + isSuccess: true, + policy: &configv1beta1.NamespacedPolicy{}, + reconciler: &test.MockStatusClient{}, + }, + { + name: "error status", + isSuccess: false, + policy: &configv1beta1.NamespacedPolicy{}, + errString: "a long error string that exceeds the max length of 30 characters", + reconciler: &test.MockStatusClient{}, + }, + { + name: "status update failed", + isSuccess: true, + policy: &configv1beta1.NamespacedPolicy{}, + reconciler: &test.MockStatusClient{ + UpdateFailed: true, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + writePolicyStatus(context.Background(), tc.reconciler, tc.policy, logger, tc.isSuccess, tc.errString) + }) + } +} + +func TestPolicyReconcile(t *testing.T) { + tests := []struct { + name string + policy *configv1beta1.NamespacedPolicy + req *reconcile.Request + expectedErr bool + expectedPolicy bool + }{ + { + name: "nonexistent policy", + req: &reconcile.Request{ + NamespacedName: types.NamespacedName{Name: "nonexistent"}, + }, + policy: &configv1beta1.NamespacedPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: policyName1, + }, + }, + expectedErr: false, + expectedPolicy: false, + }, + { + name: "no policy parameters provided", + policy: &configv1beta1.NamespacedPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "ratify-policy", + }, + Spec: configv1beta1.NamespacedPolicySpec{ + Type: "regopolicy", + }, + }, + expectedErr: true, + expectedPolicy: false, + }, + { + name: "wrong policy name", + policy: &configv1beta1.NamespacedPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "ratify-policy2", + }, + Spec: configv1beta1.NamespacedPolicySpec{ + Type: "regopolicy", + }, + }, + expectedErr: false, + expectedPolicy: false, + }, + { + name: "invalid params", + policy: &configv1beta1.NamespacedPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "ratify-policy", + }, + Spec: configv1beta1.NamespacedPolicySpec{ + Type: "regopolicy", + Parameters: runtime.RawExtension{ + Raw: []byte("test"), + }, + }, + }, + expectedErr: true, + expectedPolicy: false, + }, + { + name: "valid params", + policy: &configv1beta1.NamespacedPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "ratify-policy", + }, + Spec: configv1beta1.NamespacedPolicySpec{ + Type: "configpolicy", + Parameters: runtime.RawExtension{ + Raw: []byte("{\"passthroughEnabled:\": false}"), + }, + }, + }, + expectedErr: false, + expectedPolicy: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resetPolicyMap() + scheme, err := test.CreateScheme() + if err != nil { + t.Fatalf("CreateScheme() expected no error, actual %v", err) + } + client := fake.NewClientBuilder().WithScheme(scheme) + client.WithObjects(tt.policy) + r := &PolicyReconciler{ + Scheme: scheme, + Client: client.Build(), + } + var req reconcile.Request + if tt.req != nil { + req = *tt.req + } else { + req = reconcile.Request{ + NamespacedName: test.KeyFor(tt.policy), + } + } + _, err = r.Reconcile(context.Background(), req) + if tt.expectedErr != (err != nil) { + t.Fatalf("Reconcile() expected error to be %t, actual %t", tt.expectedErr, err != nil) + } + test := controllers.NamespacedPolicies + + policy := test.GetPolicy(testNamespace) + if (policy != nil) != tt.expectedPolicy { + t.Fatalf("Expected policy to be %t, got %t", tt.expectedPolicy, policy != nil) + } + }) + } +} + +func resetPolicyMap() { + controllers.NamespacedPolicies = policies.NewActivePolicies() +} diff --git a/pkg/controllers/utils/policy.go b/pkg/controllers/utils/policy.go new file mode 100644 index 000000000..27014d739 --- /dev/null +++ b/pkg/controllers/utils/policy.go @@ -0,0 +1,54 @@ +/* +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" + "fmt" + + "github.com/deislabs/ratify/pkg/policyprovider" + "github.com/deislabs/ratify/pkg/policyprovider/config" + pf "github.com/deislabs/ratify/pkg/policyprovider/factory" +) + +func SpecToPolicyEnforcer(raw []byte, policyType string) (policyprovider.PolicyProvider, error) { + policyConfig, err := rawToPolicyConfig(raw, policyType) + if err != nil { + return nil, fmt.Errorf("failed to parse policy config: %w", err) + } + + policyEnforcer, err := pf.CreatePolicyProviderFromConfig(policyConfig) + if err != nil { + return nil, fmt.Errorf("failed to create policy provider: %w", err) + } + + return policyEnforcer, nil +} + +func rawToPolicyConfig(raw []byte, policyType string) (config.PoliciesConfig, error) { + pluginConfig := config.PolicyPluginConfig{} + + if string(raw) == "" { + return config.PoliciesConfig{}, fmt.Errorf("no policy parameters provided") + } + if err := json.Unmarshal(raw, &pluginConfig); err != nil { + return config.PoliciesConfig{}, fmt.Errorf("unable to decode policy parameters.Raw: %s, err: %w", raw, err) + } + + pluginConfig["name"] = policyType + + return config.PoliciesConfig{ + PolicyPlugin: pluginConfig, + }, nil +} diff --git a/pkg/controllers/policy_controller_test.go b/pkg/controllers/utils/policy_test.go similarity index 53% rename from pkg/controllers/policy_controller_test.go rename to pkg/controllers/utils/policy_test.go index 6e81a5764..752910cda 100644 --- a/pkg/controllers/policy_controller_test.go +++ b/pkg/controllers/utils/policy_test.go @@ -3,9 +3,7 @@ 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. @@ -13,55 +11,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -package controllers +package utils import ( - "context" - "errors" "reflect" "testing" configv1beta1 "github.com/deislabs/ratify/api/v1beta1" - "github.com/deislabs/ratify/pkg/policyprovider/config" _ "github.com/deislabs/ratify/pkg/policyprovider/configpolicy" - "github.com/sirupsen/logrus" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client" -) -const ( - policyName1 = "policy1" - policyName2 = "policy2" + "github.com/deislabs/ratify/pkg/policyprovider/config" + "k8s.io/apimachinery/pkg/runtime" ) -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 -} +const policyName1 = "policy1" func TestRawToPolicyConfig(t *testing.T) { testCases := []struct { @@ -151,7 +114,7 @@ func TestSpecToPolicyEnforcer(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - provider, err := specToPolicyEnforcer(tc.spec) + provider, err := SpecToPolicyEnforcer(tc.spec.Parameters.Raw, tc.spec.Type) if tc.expectErr != (err != nil) { t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) @@ -162,79 +125,3 @@ func TestSpecToPolicyEnforcer(t *testing.T) { }) } } - -func TestPolicyAddOrReplace(t *testing.T) { - testCases := []struct { - name string - spec configv1beta1.PolicySpec - policyName string - expectErr bool - }{ - { - name: "invalid spec", - spec: configv1beta1.PolicySpec{ - Type: policyName1, - }, - expectErr: true, - }, - { - name: "valid spec", - spec: configv1beta1.PolicySpec{ - Parameters: runtime.RawExtension{ - Raw: []byte("{\"name\": \"configpolicy\"}"), - }, - Type: "configpolicy", - }, - expectErr: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - err := policyAddOrReplace(tc.spec) - - if tc.expectErr != (err != nil) { - t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) - } - }) - } -} - -func TestWritePolicyStatus(t *testing.T) { - logger := logrus.WithContext(context.Background()) - testCases := []struct { - name string - isSuccess bool - policy *configv1beta1.Policy - errString string - reconciler client.StatusClient - }{ - { - name: "success status", - isSuccess: true, - policy: &configv1beta1.Policy{}, - reconciler: &mockStatusClient{}, - }, - { - name: "error status", - isSuccess: false, - policy: &configv1beta1.Policy{}, - errString: "a long error string that exceeds the max length of 30 characters", - reconciler: &mockStatusClient{}, - }, - { - name: "status update failed", - isSuccess: true, - policy: &configv1beta1.Policy{}, - reconciler: &mockStatusClient{ - updateFailed: true, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - writePolicyStatus(context.Background(), tc.reconciler, tc.policy, logger, tc.isSuccess, tc.errString) - }) - } -} diff --git a/pkg/controllers/verifier_controller_test.go b/pkg/controllers/verifier_controller_test.go index 71d2afa0f..105b67aec 100644 --- a/pkg/controllers/verifier_controller_test.go +++ b/pkg/controllers/verifier_controller_test.go @@ -17,6 +17,7 @@ package controllers import ( "context" + "errors" "os" "strings" "testing" @@ -30,6 +31,35 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) +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 +} + const licenseChecker = "licensechecker" func TestMain(m *testing.M) { diff --git a/pkg/customresources/policies/api.go b/pkg/customresources/policies/api.go index 1973c8cc4..92d87ee75 100644 --- a/pkg/customresources/policies/api.go +++ b/pkg/customresources/policies/api.go @@ -27,7 +27,4 @@ type PolicyManager interface { // DeletePolicy deletes the policy from the given scope. DeletePolicy(scope, policyName string) - - // IsEmpty returns true if there are no policies. - IsEmpty() bool } diff --git a/pkg/customresources/policies/policies.go b/pkg/customresources/policies/policies.go index d562d659c..425d3bc13 100644 --- a/pkg/customresources/policies/policies.go +++ b/pkg/customresources/policies/policies.go @@ -16,6 +16,8 @@ limitations under the License. package policies import ( + "sync" + "github.com/deislabs/ratify/internal/constants" "github.com/deislabs/ratify/pkg/policyprovider" ) @@ -28,25 +30,26 @@ type PolicyWrapper struct { // ActivePolicies implements PolicyManager interface. type ActivePolicies struct { - // TODO: Implement concurrent safety using sync.Map - // ScopedPolicies is a mapping from scope to a policy. - // 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. - ScopedPolicies map[string]PolicyWrapper + // scopedPolicies is a mapping from scope to a policy. + // Note: Scope is utilized for organizing and isolating policies. In a Kubernetes (K8s) environment, the scope can be either a namespace or an empty string ("") for cluster-wide policy. + scopedPolicies sync.Map } func NewActivePolicies() PolicyManager { - return &ActivePolicies{ - ScopedPolicies: make(map[string]PolicyWrapper), - } + return &ActivePolicies{} } // GetPolicy fulfills the PolicyManager interface. // It returns the policy for the given scope. If no policy is found for the given scope, it returns cluster-wide policy. -// TODO: Current implementation always fetches the cluster-wide policy. Will implement the logic to fetch the policy for the given scope. -func (p *ActivePolicies) GetPolicy(_ string) policyprovider.PolicyProvider { - policy, ok := p.ScopedPolicies[constants.EmptyNamespace] - if ok { - return policy.Policy +func (p *ActivePolicies) GetPolicy(scope string) policyprovider.PolicyProvider { + if scopedPolicy, ok := p.scopedPolicies.Load(scope); ok { + return scopedPolicy.(PolicyWrapper).Policy + } + + if scope != constants.EmptyNamespace { + if policy, ok := p.scopedPolicies.Load(constants.EmptyNamespace); ok { + return policy.(PolicyWrapper).Policy + } } return nil } @@ -54,24 +57,18 @@ func (p *ActivePolicies) GetPolicy(_ string) policyprovider.PolicyProvider { // AddPolicy fulfills the PolicyManager interface. // It adds the given policy under the given scope. func (p *ActivePolicies) AddPolicy(scope, policyName string, policy policyprovider.PolicyProvider) { - p.ScopedPolicies[scope] = PolicyWrapper{ + p.scopedPolicies.Store(scope, PolicyWrapper{ Name: policyName, Policy: policy, - } + }) } // DeletePolicy fulfills the PolicyManager interface. // It deletes the policy from the given scope. func (p *ActivePolicies) DeletePolicy(scope, policyName string) { - if policy, ok := p.ScopedPolicies[scope]; ok { - if policy.Name == policyName { - delete(p.ScopedPolicies, scope) + if policy, ok := p.scopedPolicies.Load(scope); ok { + if policy.(PolicyWrapper).Name == policyName { + p.scopedPolicies.Delete(scope) } } } - -// IsEmpty fulfills the PolicyManager interface. -// IsEmpty returns true if there are no policies. -func (p *ActivePolicies) IsEmpty() bool { - return len(p.ScopedPolicies) == 0 -} diff --git a/pkg/customresources/policies/policies_test.go b/pkg/customresources/policies/policies_test.go index 69cd407a1..6acfbc48b 100644 --- a/pkg/customresources/policies/policies_test.go +++ b/pkg/customresources/policies/policies_test.go @@ -62,17 +62,9 @@ var ( func TestPoliciesOperations(t *testing.T) { policies := NewActivePolicies() - if !policies.IsEmpty() { - t.Errorf("Expected policies to be empty") - } - policies.AddPolicy(namespace1, name1, policy1) policies.AddPolicy(namespace2, name1, policy2) - if policies.IsEmpty() { - t.Errorf("Expected policies to not be empty") - } - if policies.GetPolicy(namespace1) != policy1 { t.Errorf("Expected policy1 to be returned") } diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index 2f1874f92..6cafbacd0 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -50,6 +50,8 @@ import ( configv1beta1 "github.com/deislabs/ratify/api/v1beta1" ctxUtils "github.com/deislabs/ratify/internal/context" "github.com/deislabs/ratify/pkg/controllers" + "github.com/deislabs/ratify/pkg/controllers/clusterresource" + "github.com/deislabs/ratify/pkg/controllers/namespaceresource" ef "github.com/deislabs/ratify/pkg/executor/core" //+kubebuilder:scaffold:imports ) @@ -212,7 +214,14 @@ func StartManager(certRotatorReady chan struct{}, probeAddr string) { setupLog.Error(err, "unable to create controller", "controller", "Certificate Store") os.Exit(1) } - if err = (&controllers.PolicyReconciler{ + if err = (&namespaceresource.PolicyReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Namespaced Policy") + os.Exit(1) + } + if err = (&clusterresource.PolicyReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { diff --git a/pkg/utils/test_utils.go b/pkg/utils/test_utils.go index 4cb400dc9..5e0100719 100644 --- a/pkg/utils/test_utils.go +++ b/pkg/utils/test_utils.go @@ -16,10 +16,66 @@ limitations under the License. package utils import ( + "context" + "errors" "os" "path/filepath" + + configv1beta1 "github.com/deislabs/ratify/api/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" ) +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 CreateScheme() (*runtime.Scheme, error) { + scheme := runtime.NewScheme() + + b := runtime.SchemeBuilder{ + clientgoscheme.AddToScheme, + configv1beta1.AddToScheme, + } + + if err := b.AddToScheme(scheme); err != nil { + return nil, err + } + + return scheme, nil +} + +func KeyFor(obj client.Object) types.NamespacedName { + return client.ObjectKeyFromObject(obj) +} + func CreatePlugin(pluginName string) (string, error) { tempDir, err := os.MkdirTemp("", "directory") if err != nil {