From ede25b9abe9e5dec912a86383df29fd1a996792f Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Wed, 3 Apr 2024 07:40:50 +0000 Subject: [PATCH 01/10] feat: add Stores interface to wrap operations on namespaced stores --- pkg/controllers/resource_map.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/controllers/resource_map.go b/pkg/controllers/resource_map.go index 7e250322b..7c75f5058 100644 --- a/pkg/controllers/resource_map.go +++ b/pkg/controllers/resource_map.go @@ -18,6 +18,7 @@ import ( "github.com/deislabs/ratify/pkg/customresources/policies" rs "github.com/deislabs/ratify/pkg/customresources/referrerstores" "github.com/deislabs/ratify/pkg/customresources/verifiers" + rs "github.com/deislabs/ratify/pkg/customresources/referrerstores" ) var ( From d76e2a9e9267ab3e92f7eef5f781bf20caec7771 Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Tue, 2 Apr 2024 09:50:48 +0000 Subject: [PATCH 02/10] feat: add Policies interface to wrap operations on namespaced policies --- pkg/controllers/resource_map.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/controllers/resource_map.go b/pkg/controllers/resource_map.go index 7c75f5058..7e250322b 100644 --- a/pkg/controllers/resource_map.go +++ b/pkg/controllers/resource_map.go @@ -18,7 +18,6 @@ import ( "github.com/deislabs/ratify/pkg/customresources/policies" rs "github.com/deislabs/ratify/pkg/customresources/referrerstores" "github.com/deislabs/ratify/pkg/customresources/verifiers" - rs "github.com/deislabs/ratify/pkg/customresources/referrerstores" ) var ( From 0eb8df594a340f01100304ae1d869636f864d866 Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Fri, 12 Apr 2024 02:35:49 +0000 Subject: [PATCH 03/10] feat: add KMPManager interface to wrap operations on namespaced kmp --- .../keymanagementprovider_controller.go | 10 +- pkg/controllers/resource_map.go | 7 + pkg/controllers/utils/kmp.go | 28 ++++ pkg/controllers/utils/kmp_test.go | 41 ++++++ .../keymanagementproviders/api.go | 47 +++++++ .../keymanagementproviders/kmpcertmanager.go | 69 +++++++++ .../kmpcertmanager_test.go | 68 +++++++++ .../keymanagementproviders/kmpkeymanager.go | 69 +++++++++ .../kmpkeymanager_test.go | 59 ++++++++ .../keymanagementprovider.go | 56 ++++---- .../keymanagementprovider_test.go | 51 +++---- pkg/utils/test_utils.go | 78 +++++++++++ pkg/utils/test_utils_test.go | 132 ++++++++++++++++++ pkg/verifier/notation/truststore.go | 15 +- 14 files changed, 676 insertions(+), 54 deletions(-) create mode 100644 pkg/controllers/utils/kmp.go create mode 100644 pkg/controllers/utils/kmp_test.go create mode 100644 pkg/customresources/keymanagementproviders/api.go create mode 100644 pkg/customresources/keymanagementproviders/kmpcertmanager.go create mode 100644 pkg/customresources/keymanagementproviders/kmpcertmanager_test.go create mode 100644 pkg/customresources/keymanagementproviders/kmpkeymanager.go create mode 100644 pkg/customresources/keymanagementproviders/kmpkeymanager_test.go create mode 100644 pkg/utils/test_utils_test.go diff --git a/pkg/controllers/keymanagementprovider_controller.go b/pkg/controllers/keymanagementprovider_controller.go index 787ca1fb4..d37614a85 100644 --- a/pkg/controllers/keymanagementprovider_controller.go +++ b/pkg/controllers/keymanagementprovider_controller.go @@ -22,6 +22,7 @@ import ( "fmt" "maps" + "github.com/deislabs/ratify/internal/constants" _ "github.com/deislabs/ratify/pkg/keymanagementprovider/azurekeyvault" // register azure key vault key management provider _ "github.com/deislabs/ratify/pkg/keymanagementprovider/inline" // register inline key management provider apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -60,7 +61,9 @@ func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctr if err := r.Get(ctx, req.NamespacedName, &keyManagementProvider); err != nil { if apierrors.IsNotFound(err) { logger.Infof("deletion detected, removing key management provider %v", resource) - keymanagementprovider.DeleteCertificatesFromMap(resource) + // TODO: pass the actual namespace once multi-tenancy is supported. + KMPCertificateMap.DeleteCerts(constants.EmptyNamespace, resource) + KMPKeyMap.DeleteKeys(constants.EmptyNamespace, resource) } else { logger.Error(err, "unable to fetch key management provider") } @@ -103,8 +106,9 @@ func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctr writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil) return ctrl.Result{}, fmt.Errorf("Error fetching keys in KMProvider %v with %v provider, error: %w", resource, keyManagementProvider.Spec.Type, err) } - keymanagementprovider.SetCertificatesInMap(resource, certificates) - keymanagementprovider.SetKeysInMap(resource, keys) + // TODO: pass the actual namespace once multi-tenancy is supported. + KMPCertificateMap.AddCerts(constants.EmptyNamespace, resource, certificates) + KMPKeyMap.AddKeys(constants.EmptyNamespace, resource, keys) // merge certificates and keys status into one maps.Copy(keyAttributes, certAttributes) isFetchSuccessful = true diff --git a/pkg/controllers/resource_map.go b/pkg/controllers/resource_map.go index 7e250322b..a82e17acd 100644 --- a/pkg/controllers/resource_map.go +++ b/pkg/controllers/resource_map.go @@ -15,6 +15,7 @@ package controllers import ( cs "github.com/deislabs/ratify/pkg/customresources/certificatestores" + kmp "github.com/deislabs/ratify/pkg/customresources/keymanagementproviders" "github.com/deislabs/ratify/pkg/customresources/policies" rs "github.com/deislabs/ratify/pkg/customresources/referrerstores" "github.com/deislabs/ratify/pkg/customresources/verifiers" @@ -33,4 +34,10 @@ var ( // NamespacedCertStores is a map between namespace and CertificateStores. NamespacedCertStores = cs.NewActiveCertStores() + + // KMPCertificateMap is a map to store certificates fetched from key management provider across namespaces. + KMPCertificateMap = kmp.NewActiveCertStores() + + // KMPKeyMap is a map to store keys fetched from key management provider across namespaces. + KMPKeyMap = kmp.NewActiveKeyStores() ) diff --git a/pkg/controllers/utils/kmp.go b/pkg/controllers/utils/kmp.go new file mode 100644 index 000000000..896cea258 --- /dev/null +++ b/pkg/controllers/utils/kmp.go @@ -0,0 +1,28 @@ +/* +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 ( + "context" + "crypto/x509" + + ctxUtils "github.com/deislabs/ratify/internal/context" + "github.com/deislabs/ratify/pkg/controllers" +) + +// GetKMPCertificates returns internal certificate map from KMP. +// TODO: returns certificates from both cluster-wide and given namespace as namespaced verifier could access both. +func GetKMPCertificates(ctx context.Context, certStore string) []*x509.Certificate { + return controllers.KMPCertificateMap.GetCertStores(ctxUtils.GetNamespace(ctx), certStore) +} diff --git a/pkg/controllers/utils/kmp_test.go b/pkg/controllers/utils/kmp_test.go new file mode 100644 index 000000000..cd85554ab --- /dev/null +++ b/pkg/controllers/utils/kmp_test.go @@ -0,0 +1,41 @@ +/* +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 ( + "context" + "crypto/x509" + "testing" + + ctxUtils "github.com/deislabs/ratify/internal/context" + "github.com/deislabs/ratify/pkg/controllers" + kmp "github.com/deislabs/ratify/pkg/customresources/keymanagementproviders" + "github.com/deislabs/ratify/pkg/keymanagementprovider" +) + +func TestGetKMPCertificates(t *testing.T) { + kmpCerts := map[keymanagementprovider.KMPMapKey][]*x509.Certificate{ + { + Name: "testName", + Version: "testVersion", + }: {}, + } + controllers.KMPCertificateMap = kmp.NewActiveCertStores() + controllers.KMPCertificateMap.AddCerts("default", "default/certStore", kmpCerts) + ctx := ctxUtils.SetContextWithNamespace(context.Background(), "default") + + if certs := GetKMPCertificates(ctx, "default/certStore"); len(certs) != 0 { + t.Fatalf("Expected 0 certificate, got %d", len(certs)) + } +} diff --git a/pkg/customresources/keymanagementproviders/api.go b/pkg/customresources/keymanagementproviders/api.go new file mode 100644 index 000000000..59c9d2683 --- /dev/null +++ b/pkg/customresources/keymanagementproviders/api.go @@ -0,0 +1,47 @@ +/* +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 keymanagementproviders + +import ( + "crypto" + "crypto/x509" + + kmp "github.com/deislabs/ratify/pkg/keymanagementprovider" +) + +// KMPCertManager is an interface that defines the methods for managing certificate stores across different scopes. +type KMPCertManager interface { + // GetCertStores returns certificates for the given scope. + GetCertStores(scope, storeName string) []*x509.Certificate + + // AddCerts adds the given certificate under the given scope. + AddCerts(scope, storeName string, certs map[kmp.KMPMapKey][]*x509.Certificate) + + // DeleteCerts deletes the store from the given scope. + DeleteCerts(scope, storeName string) +} + +// KMPKeyManager is an interface that defines the methods for managing key stores across different scopes. +type KMPKeyManager interface { + // GetKeyStores returns keys for the given scope. + GetKeyStores(scope, storeName string) map[kmp.KMPMapKey]crypto.PublicKey + + // AddKeys adds the given keys under the given scope. + AddKeys(scope, storeName string, keys map[kmp.KMPMapKey]crypto.PublicKey) + + // DeleteKeys deletes the store from the given scope. + DeleteKeys(scope, storeName string) +} diff --git a/pkg/customresources/keymanagementproviders/kmpcertmanager.go b/pkg/customresources/keymanagementproviders/kmpcertmanager.go new file mode 100644 index 000000000..87210a7a7 --- /dev/null +++ b/pkg/customresources/keymanagementproviders/kmpcertmanager.go @@ -0,0 +1,69 @@ +/* +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 keymanagementproviders + +import ( + "crypto/x509" + "sync" + + "github.com/deislabs/ratify/internal/constants" + kmp "github.com/deislabs/ratify/pkg/keymanagementprovider" +) + +// ActiveCertStores implements the KMPCertManager interface. +type ActiveCertStores struct { + // scopedStores maps from scope to CertificateMap defined in /pkg/keymanagementprovider/keymanagementprovider.go + // Example: + // { + // "namespace1": kmp.CertificateMap{}, + // "namespace2": kmp.CertificateMap{} + // } + scopedStores sync.Map +} + +func NewActiveCertStores() KMPCertManager { + return &ActiveCertStores{} +} + +// GetCertStores fulfills the KMPCertManager interface. +// It returns the certificates for the given scope. If no certificates are found for the given scope, it returns cluster-wide certificates. +// TODO: Current implementation always fetches cluster-wide cert stores. Will support actual namespaced certStores in future. +func (c *ActiveCertStores) GetCertStores(_, storeName string) []*x509.Certificate { + namespacedProvider, ok := c.scopedStores.Load(constants.EmptyNamespace) + if !ok { + return []*x509.Certificate{} + } + certMap := namespacedProvider.(*kmp.CertificateMap) + return kmp.FlattenKMPMap(certMap.GetCertificatesFromMap(storeName)) +} + +// AddCerts fulfills the KMPCertManager interface. +// It adds the given certificates under the given scope. +// TODO: Current implementation always adds the given certificate to cluster-wide cert store. Will support actual namespaced certStores in future. +func (c *ActiveCertStores) AddCerts(_, storeName string, certs map[kmp.KMPMapKey][]*x509.Certificate) { + scopedStore, _ := c.scopedStores.LoadOrStore(constants.EmptyNamespace, &kmp.CertificateMap{}) + scopedStore.(*kmp.CertificateMap).SetCertificatesInMap(storeName, certs) +} + +// DeleteCerts fulfills the KMPCertManager interface. +// It deletes the store from the given scope. +// TODO: Current implementation always deletes the given certificate from cluster-wide cert store. Will support actual namespaced certStores in future. +func (c *ActiveCertStores) DeleteCerts(_, storeName string) { + scopedKMPStore, ok := c.scopedStores.Load(constants.EmptyNamespace) + if ok { + scopedKMPStore.(*kmp.CertificateMap).DeleteCertificatesFromMap(storeName) + } +} diff --git a/pkg/customresources/keymanagementproviders/kmpcertmanager_test.go b/pkg/customresources/keymanagementproviders/kmpcertmanager_test.go new file mode 100644 index 000000000..6f56ef8fb --- /dev/null +++ b/pkg/customresources/keymanagementproviders/kmpcertmanager_test.go @@ -0,0 +1,68 @@ +/* +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 keymanagementproviders + +import ( + "crypto/x509" + "testing" + + "github.com/deislabs/ratify/pkg/keymanagementprovider" + "github.com/deislabs/ratify/pkg/utils" +) + +const ( + namespace1 = "namespace1" + namespace2 = "namespace2" + name1 = "name1" + name2 = "name2" +) + +func TestCertStoresOperations(t *testing.T) { + activeCertStores := NewActiveCertStores() + + certStore1 := map[keymanagementprovider.KMPMapKey][]*x509.Certificate{ + {Name: "testName1", Version: "testVersion1"}: {utils.CreateTestCert()}, + } + certStore2 := map[keymanagementprovider.KMPMapKey][]*x509.Certificate{ + {Name: "testName2", Version: "testVersion2"}: {utils.CreateTestCert()}, + } + + if len(activeCertStores.GetCertStores(namespace1, name1)) != 0 { + t.Errorf("Expected activeCertStores to have 0 cert store, but got %d", len(activeCertStores.GetCertStores(namespace1, name1))) + } + + activeCertStores.AddCerts(namespace1, name1, certStore1) + activeCertStores.AddCerts(namespace2, name2, certStore2) + + if len(activeCertStores.GetCertStores(namespace1, name1)) != 1 { + t.Errorf("Expected activeCertStores to have 1 cert store, but got %d", len(activeCertStores.GetCertStores(namespace1, name1))) + } + + if len(activeCertStores.GetCertStores(namespace2, name2)) != 1 { + t.Errorf("Expected activeCertStores to have 1 cert store, but got %d", len(activeCertStores.GetCertStores(namespace2, name2))) + } + + activeCertStores.DeleteCerts(namespace1, name1) + activeCertStores.DeleteCerts(namespace2, name2) + + if len(activeCertStores.GetCertStores(namespace1, name1)) != 0 { + t.Errorf("Expected activeCertStores to have 0 cert store, but got %d", len(activeCertStores.GetCertStores(namespace1, name1))) + } + + if len(activeCertStores.GetCertStores(namespace2, name2)) != 0 { + t.Errorf("Expected activeCertStores to have 0 cert store, but got %d", len(activeCertStores.GetCertStores(namespace2, name2))) + } +} diff --git a/pkg/customresources/keymanagementproviders/kmpkeymanager.go b/pkg/customresources/keymanagementproviders/kmpkeymanager.go new file mode 100644 index 000000000..24e558f60 --- /dev/null +++ b/pkg/customresources/keymanagementproviders/kmpkeymanager.go @@ -0,0 +1,69 @@ +/* +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 keymanagementproviders + +import ( + "crypto" + "sync" + + "github.com/deislabs/ratify/internal/constants" + kmp "github.com/deislabs/ratify/pkg/keymanagementprovider" +) + +// ActiveKeyStores implements the KMPKeyManager interface. +type ActiveKeyStores struct { + // scopedStores maps from scope to KeyMap defined in /pkg/keymanagementprovider/keymanagementprovider.go + // Example: + // { + // "namespace1": kmp.KeyMap{}, + // "namespace2": kmp.KeyMap{} + // } + scopedStores sync.Map +} + +func NewActiveKeyStores() KMPKeyManager { + return &ActiveKeyStores{} +} + +// GetKeyStores fulfills the KMPKeyManager interface. +// It returns the keys for the given scope. If no keys are found for the given scope, it returns cluster-wide keys. +// TODO: Current implementation always fetches cluster-wide key stores. Will support actual namespaced keyStores in future. +func (k *ActiveKeyStores) GetKeyStores(_, storeName string) map[kmp.KMPMapKey]crypto.PublicKey { + namespacedProvider, ok := k.scopedStores.Load(constants.EmptyNamespace) + if !ok { + return map[kmp.KMPMapKey]crypto.PublicKey{} + } + keyMap := namespacedProvider.(*kmp.KeyMap) + return keyMap.GetKeysFromMap(storeName) +} + +// AddKeys fulfills the KMPKeyManager interface. +// It adds the given keys under the given scope. +// TODO: Current implementation always adds cluster-wide key stores. Will support actual namespaced keyStores in future. +func (k *ActiveKeyStores) AddKeys(_, storeName string, keys map[kmp.KMPMapKey]crypto.PublicKey) { + scopedStore, _ := k.scopedStores.LoadOrStore(constants.EmptyNamespace, &kmp.KeyMap{}) + scopedStore.(*kmp.KeyMap).SetKeysInMap(storeName, keys) +} + +// DeleteKeys fulfills the KMPKeyManager interface. +// It deletes the keys for the given scope. +// TODO: Current implementation always deletes cluster-wide key stores. Will support actual namespaced keyStores in future. +func (k *ActiveKeyStores) DeleteKeys(_, storeName string) { + scopedKMPStore, ok := k.scopedStores.Load(constants.EmptyNamespace) + if ok { + scopedKMPStore.(*kmp.KeyMap).DeleteKeysFromMap(storeName) + } +} diff --git a/pkg/customresources/keymanagementproviders/kmpkeymanager_test.go b/pkg/customresources/keymanagementproviders/kmpkeymanager_test.go new file mode 100644 index 000000000..529247162 --- /dev/null +++ b/pkg/customresources/keymanagementproviders/kmpkeymanager_test.go @@ -0,0 +1,59 @@ +/* +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 keymanagementproviders + +import ( + "crypto" + "testing" + + "github.com/deislabs/ratify/pkg/keymanagementprovider" + "github.com/deislabs/ratify/pkg/utils" +) + +func TestKeyStoresOperations(t *testing.T) { + activeKeyStores := NewActiveKeyStores() + + keyStore1 := map[keymanagementprovider.KMPMapKey]crypto.PublicKey{ + {Name: "testName1", Version: "testVersion1"}: utils.CreateTestPublicKey(), + } + keyStore2 := map[keymanagementprovider.KMPMapKey]crypto.PublicKey{ + {Name: "testName2", Version: "testVersion2"}: utils.CreateTestPublicKey(), + } + + if len(activeKeyStores.GetKeyStores(namespace1, name1)) != 0 { + t.Errorf("Expected activeKeyStores to have 0 key store, but got %d", len(activeKeyStores.GetKeyStores(namespace1, name1))) + } + + activeKeyStores.AddKeys(namespace1, name1, keyStore1) + activeKeyStores.AddKeys(namespace2, name2, keyStore2) + + if len(activeKeyStores.GetKeyStores(namespace1, name1)) != 1 { + t.Errorf("Expected activeKeyStores to have 1 key store, but got %d", len(activeKeyStores.GetKeyStores(namespace1, name1))) + } + if len(activeKeyStores.GetKeyStores(namespace2, name2)) != 1 { + t.Errorf("Expected activeKeyStores to have 1 key store, but got %d", len(activeKeyStores.GetKeyStores(namespace2, name2))) + } + + activeKeyStores.DeleteKeys(namespace1, name1) + activeKeyStores.DeleteKeys(namespace2, name2) + + if len(activeKeyStores.GetKeyStores(namespace1, name1)) != 0 { + t.Errorf("Expected activeKeyStores to have 0 key store, but got %d", len(activeKeyStores.GetKeyStores(namespace1, name1))) + } + if len(activeKeyStores.GetKeyStores(namespace2, name2)) != 0 { + t.Errorf("Expected activeKeyStores to have 0 key store, but got %d", len(activeKeyStores.GetKeyStores(namespace2, name2))) + } +} diff --git a/pkg/keymanagementprovider/keymanagementprovider.go b/pkg/keymanagementprovider/keymanagementprovider.go index b0731f342..c78fafea5 100644 --- a/pkg/keymanagementprovider/keymanagementprovider.go +++ b/pkg/keymanagementprovider/keymanagementprovider.go @@ -46,20 +46,26 @@ type KeyManagementProvider interface { GetKeys(ctx context.Context) (map[KMPMapKey]crypto.PublicKey, KeyManagementProviderStatus, error) } -// static concurrency-safe map to store certificates fetched from key management provider -// layout: -// -// map["/"] = map[KMPMapKey][]*x509.Certificate -// where KMPMapKey is dimensioned by the name and version of the certificate. -// Array of x509 Certificates for certificate chain scenarios -var certificatesMap sync.Map +// CertificateMap wraps a sync.Map to store certificates fetched from key management provider. +type CertificateMap struct { + // concurrency-safe map to store certificates fetched from key management provider + // layout: + // + // map["/"] = map[KMPMapKey][]*x509.Certificate + // where KMPMapKey is dimensioned by the name and version of the certificate. + // Array of x509 Certificates for certificate chain scenarios + mapping sync.Map +} -// static concurrency-safe map to store keys fetched from key management provider -// layout: -// -// map["/"] = map[KMPMapKey]PublicKey -// where KMPMapKey is dimensioned by the name and version of the public key. -var keyMap sync.Map +// KeyMap wraps a sync.Map to store keys fetched from key management provider. +type KeyMap struct { + // concurrency-safe map to store keys fetched from key management provider + // layout: + // + // map["/"] = map[KMPMapKey]PublicKey + // where KMPMapKey is dimensioned by the name and version of the public key. + mapping sync.Map +} // DecodeCertificates decodes PEM-encoded bytes into an x509.Certificate chain. func DecodeCertificates(value []byte) ([]*x509.Certificate, error) { @@ -99,13 +105,13 @@ func DecodeKey(value []byte) (crypto.PublicKey, error) { // SetCertificatesInMap sets the certificates in the map // it is concurrency-safe -func SetCertificatesInMap(resource string, certs map[KMPMapKey][]*x509.Certificate) { - certificatesMap.Store(resource, certs) +func (c *CertificateMap) SetCertificatesInMap(resource string, certs map[KMPMapKey][]*x509.Certificate) { + c.mapping.Store(resource, certs) } // GetCertificatesFromMap gets the certificates from the map and returns an empty map of certificate arrays if not found -func GetCertificatesFromMap(resource string) map[KMPMapKey][]*x509.Certificate { - certs, ok := certificatesMap.Load(resource) +func (c *CertificateMap) GetCertificatesFromMap(resource string) map[KMPMapKey][]*x509.Certificate { + certs, ok := c.mapping.Load(resource) if !ok { return map[KMPMapKey][]*x509.Certificate{} } @@ -114,8 +120,8 @@ func GetCertificatesFromMap(resource string) map[KMPMapKey][]*x509.Certificate { // DeleteCertificatesFromMap deletes the certificates from the map // it is concurrency-safe -func DeleteCertificatesFromMap(resource string) { - certificatesMap.Delete(resource) +func (c *CertificateMap) DeleteCertificatesFromMap(resource string) { + c.mapping.Delete(resource) } // FlattenKMPMap flattens the map of certificates fetched for a single key management provider resource and returns a single array @@ -137,13 +143,13 @@ func FlattenKMPMapKeys(keyMap map[KMPMapKey]crypto.PublicKey) []crypto.PublicKey } // SetKeysInMap sets the keys in the map -func SetKeysInMap(resource string, keys map[KMPMapKey]crypto.PublicKey) { - keyMap.Store(resource, keys) +func (k *KeyMap) SetKeysInMap(resource string, keys map[KMPMapKey]crypto.PublicKey) { + k.mapping.Store(resource, keys) } // GetKeysFromMap gets the keys from the map and returns an empty map of keys if not found -func GetKeysFromMap(resource string) map[KMPMapKey]crypto.PublicKey { - keys, ok := keyMap.Load(resource) +func (k *KeyMap) GetKeysFromMap(resource string) map[KMPMapKey]crypto.PublicKey { + keys, ok := k.mapping.Load(resource) if !ok { return map[KMPMapKey]crypto.PublicKey{} } @@ -151,6 +157,6 @@ func GetKeysFromMap(resource string) map[KMPMapKey]crypto.PublicKey { } // DeleteKeysFromMap deletes the keys from the map -func DeleteKeysFromMap(resource string) { - keyMap.Delete(resource) +func (k *KeyMap) DeleteKeysFromMap(resource string) { + k.mapping.Delete(resource) } diff --git a/pkg/keymanagementprovider/keymanagementprovider_test.go b/pkg/keymanagementprovider/keymanagementprovider_test.go index 3607d2d98..fe34f9d59 100644 --- a/pkg/keymanagementprovider/keymanagementprovider_test.go +++ b/pkg/keymanagementprovider/keymanagementprovider_test.go @@ -26,6 +26,9 @@ import ( "github.com/stretchr/testify/assert" ) +var certificatesMap CertificateMap +var keyMap KeyMap + // TestDecodeCertificates tests the DecodeCertificates method func TestDecodeCertificates(t *testing.T) { cases := []struct { @@ -131,18 +134,18 @@ func TestDecodeCertificates_FailedX509ParseError(t *testing.T) { // TestSetCertificatesInMap checks if certificates are set in the map func TestSetCertificatesInMap(t *testing.T) { - certificatesMap.Delete("test") - SetCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) - if _, ok := certificatesMap.Load("test"); !ok { + certificatesMap.mapping.Delete("test") + certificatesMap.SetCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) + if _, ok := certificatesMap.mapping.Load("test"); !ok { t.Fatalf("certificatesMap should have been set for key") } } // TestGetCertificatesFromMap checks if certificates are fetched from the map func TestGetCertificatesFromMap(t *testing.T) { - certificatesMap.Delete("test") - SetCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) - certs := GetCertificatesFromMap("test") + certificatesMap.mapping.Delete("test") + certificatesMap.SetCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) + certs := certificatesMap.GetCertificatesFromMap("test") if len(certs) != 1 { t.Fatalf("certificates should have been fetched from the map") } @@ -150,8 +153,8 @@ func TestGetCertificatesFromMap(t *testing.T) { // TestGetCertificatesFromMap_FailedToFetch checks if certificates are fetched from the map func TestGetCertificatesFromMap_FailedToFetch(t *testing.T) { - certificatesMap.Delete("test") - certs := GetCertificatesFromMap("test") + certificatesMap.mapping.Delete("test") + certs := certificatesMap.GetCertificatesFromMap("test") if len(certs) != 0 { t.Fatalf("certificates should not have been fetched from the map") } @@ -159,10 +162,10 @@ func TestGetCertificatesFromMap_FailedToFetch(t *testing.T) { // TestDeleteCertificatesFromMap checks if certificates are deleted from the map func TestDeleteCertificatesFromMap(t *testing.T) { - certificatesMap.Delete("test") - SetCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) - DeleteCertificatesFromMap("test") - if _, ok := certificatesMap.Load("test"); ok { + certificatesMap.mapping.Delete("test") + certificatesMap.SetCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) + certificatesMap.DeleteCertificatesFromMap("test") + if _, ok := certificatesMap.mapping.Load("test"); ok { t.Fatalf("certificatesMap should have been deleted for key") } } @@ -177,18 +180,18 @@ func TestFlattenKMPMap(t *testing.T) { // TestSetKeysInMap checks if keys are set in the map func TestSetKeysInMap(t *testing.T) { - keyMap.Delete("test") - SetKeysInMap("test", map[KMPMapKey]crypto.PublicKey{{}: &rsa.PublicKey{}}) - if _, ok := keyMap.Load("test"); !ok { + keyMap.mapping.Delete("test") + keyMap.SetKeysInMap("test", map[KMPMapKey]crypto.PublicKey{{}: &rsa.PublicKey{}}) + if _, ok := keyMap.mapping.Load("test"); !ok { t.Fatalf("keysMap should have been set for key") } } // TestGetKeysFromMap checks if keys are fetched from the map func TestGetKeysFromMap(t *testing.T) { - keyMap.Delete("test") - SetKeysInMap("test", map[KMPMapKey]crypto.PublicKey{{}: &rsa.PublicKey{}}) - keys := GetKeysFromMap("test") + keyMap.mapping.Delete("test") + keyMap.SetKeysInMap("test", map[KMPMapKey]crypto.PublicKey{{}: &rsa.PublicKey{}}) + keys := keyMap.GetKeysFromMap("test") if len(keys) != 1 { t.Fatalf("keys should have been fetched from the map") } @@ -196,8 +199,8 @@ func TestGetKeysFromMap(t *testing.T) { // TestGetKeysFromMap_FailedToFetch checks if keys fail to fetch from map func TestGetKeysFromMap_FailedToFetch(t *testing.T) { - keyMap.Delete("test") - keys := GetKeysFromMap("test") + keyMap.mapping.Delete("test") + keys := keyMap.GetKeysFromMap("test") if len(keys) != 0 { t.Fatalf("keys should not have been fetched from the map") } @@ -205,10 +208,10 @@ func TestGetKeysFromMap_FailedToFetch(t *testing.T) { // TestDeleteKeysFromMap checks if key map entry is deleted from the map func TestDeleteKeysFromMap(t *testing.T) { - keyMap.Delete("test") - SetKeysInMap("test", map[KMPMapKey]crypto.PublicKey{{}: &rsa.PublicKey{}}) - DeleteKeysFromMap("test") - if _, ok := keyMap.Load("test"); ok { + keyMap.mapping.Delete("test") + keyMap.SetKeysInMap("test", map[KMPMapKey]crypto.PublicKey{{}: &rsa.PublicKey{}}) + keyMap.DeleteKeysFromMap("test") + if _, ok := keyMap.mapping.Load("test"); ok { t.Fatalf("keysMap should have been deleted for key") } } diff --git a/pkg/utils/test_utils.go b/pkg/utils/test_utils.go index 4cb400dc9..59a752abe 100644 --- a/pkg/utils/test_utils.go +++ b/pkg/utils/test_utils.go @@ -16,8 +16,15 @@ limitations under the License. package utils import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" "os" "path/filepath" + "time" ) func CreatePlugin(pluginName string) (string, error) { @@ -34,3 +41,74 @@ func CreatePlugin(pluginName string) (string, error) { defer file.Close() return tempDir, nil } + +func CreateTestCert() *x509.Certificate { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil + } + + // Create a certificate template + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"My Organization"}, + Country: []string{"Country"}, + Province: []string{"Province"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(365 * 24 * time.Hour), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + // Create a self-signed certificate + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return nil + } + + cert, _ := x509.ParseCertificate(derBytes) + return cert +} + +func CreateTestPublicKey() interface{} { + // Generate a private key + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil + } + + // Marshal the public key + publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) + if err != nil { + return nil + } + + // Create a PEM block for the public key + publicKeyPEM := &pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: publicKeyBytes, + } + + // Encode the PEM block + publicKeyPEMEncoded := pem.EncodeToMemory(publicKeyPEM) + if publicKeyPEMEncoded == nil { + return nil + } + + // Decode the public key + block, _ := pem.Decode(publicKeyPEMEncoded) + if block == nil { + return nil + } + + // Parse the public key + publicKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil + } + + return publicKey +} diff --git a/pkg/utils/test_utils_test.go b/pkg/utils/test_utils_test.go new file mode 100644 index 000000000..8b2087aea --- /dev/null +++ b/pkg/utils/test_utils_test.go @@ -0,0 +1,132 @@ +/* +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 ( + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "math/big" + "reflect" + "testing" + "time" +) + +func TestCreateTestCert(t *testing.T) { + cert := CreateTestCert() + + if cert == nil { + t.Fatal("Expected a non-nil certificate, got nil") + } + + // Check certificate fields + expectedSerialNumber := big.NewInt(1) + if cert.SerialNumber.Cmp(expectedSerialNumber) != 0 { + t.Fatalf("Expected serial number %v, got %v", expectedSerialNumber, cert.SerialNumber) + } + + expectedOrganization := []string{"My Organization"} + if !reflect.DeepEqual(cert.Subject.Organization, expectedOrganization) { + t.Fatalf("Expected organization %v, got %v", expectedOrganization, cert.Subject.Organization) + } + + expectedCountry := []string{"Country"} + if !reflect.DeepEqual(cert.Subject.Country, expectedCountry) { + t.Fatalf("Expected country %v, got %v", expectedCountry, cert.Subject.Country) + } + + expectedProvince := []string{"Province"} + if !reflect.DeepEqual(cert.Subject.Province, expectedProvince) { + t.Fatalf("Expected province %v, got %v", expectedProvince, cert.Subject.Province) + } + + // Check NotBefore and NotAfter dates + now := time.Now() + if cert.NotBefore.After(now) { + t.Fatalf("NotBefore is after current time: %v", cert.NotBefore) + } + + // Check KeyUsage + expectedKeyUsage := x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature + if cert.KeyUsage != expectedKeyUsage { + t.Fatalf("Expected KeyUsage %v, got %v", expectedKeyUsage, cert.KeyUsage) + } + + // Check ExtKeyUsage + expectedExtKeyUsage := []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} + if !reflect.DeepEqual(cert.ExtKeyUsage, expectedExtKeyUsage) { + t.Fatalf("Expected ExtKeyUsage %v, got %v", expectedExtKeyUsage, cert.ExtKeyUsage) + } + + // Check BasicConstraintsValid + if !cert.BasicConstraintsValid { + t.Fatal("Expected BasicConstraintsValid to be true, got false") + } + + // Check PublicKey + if cert.PublicKey.(*rsa.PublicKey).N.Cmp(cert.PublicKey.(*rsa.PublicKey).N) != 0 { + t.Fatal("Public key mismatch") + } +} + +func TestCreateTestPublicKey(t *testing.T) { + publicKey := CreateTestPublicKey() + + if publicKey == nil { + t.Fatal("Expected a non-nil public key, got nil") + } + + // Check the type of the public key + _, ok := publicKey.(*rsa.PublicKey) + if !ok { + t.Fatal("Expected *rsa.PublicKey, got", reflect.TypeOf(publicKey)) + } + + // Marshal the public key + publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey.(*rsa.PublicKey)) + if err != nil { + t.Fatal("Error marshaling public key:", err) + } + + // Create a PEM block for the public key + expectedPublicKeyPEM := &pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: publicKeyBytes, + } + + // Encode the PEM block + expectedPublicKeyPEMEncoded := pem.EncodeToMemory(expectedPublicKeyPEM) + if expectedPublicKeyPEMEncoded == nil { + t.Fatal("Error encoding PEM block") + } + + // Decode the public key from the function's output + block, _ := pem.Decode(expectedPublicKeyPEMEncoded) + if block == nil { + t.Fatal("Error decoding PEM block") + } + + // Parse the public key + expectedPublicKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + t.Fatal("Error parsing public key:", err) + } + + // Check if the parsed public key matches the expected public key + if !reflect.DeepEqual(publicKey, expectedPublicKey) { + t.Fatal("Parsed public key does not match the expected public key") + } +} diff --git a/pkg/verifier/notation/truststore.go b/pkg/verifier/notation/truststore.go index 89a7fa968..cb84e3f23 100644 --- a/pkg/verifier/notation/truststore.go +++ b/pkg/verifier/notation/truststore.go @@ -21,9 +21,9 @@ import ( "errors" "fmt" + ctxUtils "github.com/deislabs/ratify/internal/context" "github.com/deislabs/ratify/internal/logger" cutils "github.com/deislabs/ratify/pkg/controllers/utils" - "github.com/deislabs/ratify/pkg/keymanagementprovider" "github.com/deislabs/ratify/pkg/utils" "github.com/notaryproject/notation-go/verifier/truststore" ) @@ -56,7 +56,11 @@ func (s trustStore) getCertificatesInternal(ctx context.Context, namedStore stri if certGroup := s.certStores[namedStore]; len(certGroup) > 0 { for _, certStore := range certGroup { logger.GetLogger(ctx, logOpt).Debugf("truststore getting certStore %v", certStore) - result := keymanagementprovider.FlattenKMPMap(keymanagementprovider.GetCertificatesFromMap(certStore)) + if !isCompatibleNamespace(ctx, certStore) { + logger.GetLogger(ctx, logOpt).Warnf("verifier in namespace: %s cannot access certStore: %s in different namespace.", ctxUtils.GetNamespace(ctx), certStore) + continue + } + result := cutils.GetKMPCertificates(ctx, certStore) // notation verifier does not consider specific named/versioned certificates within a key management provider resource if len(result) == 0 { logger.GetLogger(ctx, logOpt).Warnf("no certificate fetched for Key Management Provider %+v", certStore) @@ -103,3 +107,10 @@ func (s trustStore) filterValidCerts(certs []*x509.Certificate) ([]*x509.Certifi } return filteredCerts, nil } + +// Namespaced verifiers could access both cluster-scoped and namespaced certStores. +// But cluster-wide verifiers could only access cluster-scoped certStores. +// TODO: current implementation always returns true. Check the namespace once we support multi-tenancy. +func isCompatibleNamespace(_ context.Context, _ string) bool { + return true +} From c0b5769b7422e4ad7bca6ee167f0e91afce6d90b Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Fri, 19 Apr 2024 12:43:24 +0000 Subject: [PATCH 04/10] feat: revert extra namespace mapping --- .../keymanagementprovider_controller.go | 17 ++- pkg/controllers/resource_map.go | 7 - pkg/controllers/utils/kmp.go | 28 ---- pkg/controllers/utils/kmp_test.go | 41 ------ .../keymanagementproviders/api.go | 47 ------- .../keymanagementproviders/kmpcertmanager.go | 69 --------- .../kmpcertmanager_test.go | 68 --------- .../keymanagementproviders/kmpkeymanager.go | 69 --------- .../kmpkeymanager_test.go | 59 -------- .../keymanagementprovider.go | 69 +++++---- .../keymanagementprovider_test.go | 52 ++++--- pkg/utils/test_utils.go | 78 ----------- pkg/utils/test_utils_test.go | 132 ------------------ pkg/verifier/notation/truststore.go | 15 +- 14 files changed, 73 insertions(+), 678 deletions(-) delete mode 100644 pkg/controllers/utils/kmp.go delete mode 100644 pkg/controllers/utils/kmp_test.go delete mode 100644 pkg/customresources/keymanagementproviders/api.go delete mode 100644 pkg/customresources/keymanagementproviders/kmpcertmanager.go delete mode 100644 pkg/customresources/keymanagementproviders/kmpcertmanager_test.go delete mode 100644 pkg/customresources/keymanagementproviders/kmpkeymanager.go delete mode 100644 pkg/customresources/keymanagementproviders/kmpkeymanager_test.go delete mode 100644 pkg/utils/test_utils_test.go diff --git a/pkg/controllers/keymanagementprovider_controller.go b/pkg/controllers/keymanagementprovider_controller.go index d37614a85..93fd873b5 100644 --- a/pkg/controllers/keymanagementprovider_controller.go +++ b/pkg/controllers/keymanagementprovider_controller.go @@ -22,7 +22,6 @@ import ( "fmt" "maps" - "github.com/deislabs/ratify/internal/constants" _ "github.com/deislabs/ratify/pkg/keymanagementprovider/azurekeyvault" // register azure key vault key management provider _ "github.com/deislabs/ratify/pkg/keymanagementprovider/inline" // register inline key management provider apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -34,7 +33,7 @@ import ( configv1beta1 "github.com/deislabs/ratify/api/v1beta1" c "github.com/deislabs/ratify/config" - "github.com/deislabs/ratify/pkg/keymanagementprovider" + kmp "github.com/deislabs/ratify/pkg/keymanagementprovider" "github.com/deislabs/ratify/pkg/keymanagementprovider/config" "github.com/deislabs/ratify/pkg/keymanagementprovider/factory" "github.com/deislabs/ratify/pkg/keymanagementprovider/types" @@ -62,8 +61,8 @@ func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctr if apierrors.IsNotFound(err) { logger.Infof("deletion detected, removing key management provider %v", resource) // TODO: pass the actual namespace once multi-tenancy is supported. - KMPCertificateMap.DeleteCerts(constants.EmptyNamespace, resource) - KMPKeyMap.DeleteKeys(constants.EmptyNamespace, resource) + kmp.DeleteCertificatesFromMap(resource) + kmp.DeleteKeysFromMap(resource) } else { logger.Error(err, "unable to fetch key management provider") } @@ -107,8 +106,8 @@ func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctr return ctrl.Result{}, fmt.Errorf("Error fetching keys in KMProvider %v with %v provider, error: %w", resource, keyManagementProvider.Spec.Type, err) } // TODO: pass the actual namespace once multi-tenancy is supported. - KMPCertificateMap.AddCerts(constants.EmptyNamespace, resource, certificates) - KMPKeyMap.AddKeys(constants.EmptyNamespace, resource, keys) + kmp.SetCertificatesInMap(resource, certificates) + kmp.SetKeysInMap(resource, keys) // merge certificates and keys status into one maps.Copy(keyAttributes, certAttributes) isFetchSuccessful = true @@ -134,7 +133,7 @@ func (r *KeyManagementProviderReconciler) SetupWithManager(mgr ctrl.Manager) err } // specToKeyManagementProvider creates KeyManagementProviderProvider from KeyManagementProviderSpec config -func specToKeyManagementProvider(spec configv1beta1.KeyManagementProviderSpec) (keymanagementprovider.KeyManagementProvider, error) { +func specToKeyManagementProvider(spec configv1beta1.KeyManagementProviderSpec) (kmp.KeyManagementProvider, error) { kmProviderConfig, err := rawToKeyManagementProviderConfig(spec.Parameters.Raw, spec.Type) if err != nil { return nil, fmt.Errorf("failed to parse key management provider config: %w", err) @@ -166,7 +165,7 @@ func rawToKeyManagementProviderConfig(raw []byte, keyManagamentSystemName string } // writeKMProviderStatus updates the status of the key management provider resource -func writeKMProviderStatus(ctx context.Context, r client.StatusClient, keyManagementProvider *configv1beta1.KeyManagementProvider, logger *logrus.Entry, isSuccess bool, errorString string, operationTime metav1.Time, kmProviderStatus keymanagementprovider.KeyManagementProviderStatus) { +func writeKMProviderStatus(ctx context.Context, r client.StatusClient, keyManagementProvider *configv1beta1.KeyManagementProvider, logger *logrus.Entry, isSuccess bool, errorString string, operationTime metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) { if isSuccess { updateKMProviderSuccessStatus(keyManagementProvider, &operationTime, kmProviderStatus) } else { @@ -192,7 +191,7 @@ func updateKMProviderErrorStatus(keyManagementProvider *configv1beta1.KeyManagem // updateKMProviderSuccessStatus updates the key management provider status if status argument is non nil // Success status includes last fetched time and other provider-specific properties -func updateKMProviderSuccessStatus(keyManagementProvider *configv1beta1.KeyManagementProvider, lastOperationTime *metav1.Time, kmProviderStatus keymanagementprovider.KeyManagementProviderStatus) { +func updateKMProviderSuccessStatus(keyManagementProvider *configv1beta1.KeyManagementProvider, lastOperationTime *metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) { keyManagementProvider.Status.IsSuccess = true keyManagementProvider.Status.Error = "" keyManagementProvider.Status.BriefError = "" diff --git a/pkg/controllers/resource_map.go b/pkg/controllers/resource_map.go index a82e17acd..7e250322b 100644 --- a/pkg/controllers/resource_map.go +++ b/pkg/controllers/resource_map.go @@ -15,7 +15,6 @@ package controllers import ( cs "github.com/deislabs/ratify/pkg/customresources/certificatestores" - kmp "github.com/deislabs/ratify/pkg/customresources/keymanagementproviders" "github.com/deislabs/ratify/pkg/customresources/policies" rs "github.com/deislabs/ratify/pkg/customresources/referrerstores" "github.com/deislabs/ratify/pkg/customresources/verifiers" @@ -34,10 +33,4 @@ var ( // NamespacedCertStores is a map between namespace and CertificateStores. NamespacedCertStores = cs.NewActiveCertStores() - - // KMPCertificateMap is a map to store certificates fetched from key management provider across namespaces. - KMPCertificateMap = kmp.NewActiveCertStores() - - // KMPKeyMap is a map to store keys fetched from key management provider across namespaces. - KMPKeyMap = kmp.NewActiveKeyStores() ) diff --git a/pkg/controllers/utils/kmp.go b/pkg/controllers/utils/kmp.go deleted file mode 100644 index 896cea258..000000000 --- a/pkg/controllers/utils/kmp.go +++ /dev/null @@ -1,28 +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 utils - -import ( - "context" - "crypto/x509" - - ctxUtils "github.com/deislabs/ratify/internal/context" - "github.com/deislabs/ratify/pkg/controllers" -) - -// GetKMPCertificates returns internal certificate map from KMP. -// TODO: returns certificates from both cluster-wide and given namespace as namespaced verifier could access both. -func GetKMPCertificates(ctx context.Context, certStore string) []*x509.Certificate { - return controllers.KMPCertificateMap.GetCertStores(ctxUtils.GetNamespace(ctx), certStore) -} diff --git a/pkg/controllers/utils/kmp_test.go b/pkg/controllers/utils/kmp_test.go deleted file mode 100644 index cd85554ab..000000000 --- a/pkg/controllers/utils/kmp_test.go +++ /dev/null @@ -1,41 +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 utils - -import ( - "context" - "crypto/x509" - "testing" - - ctxUtils "github.com/deislabs/ratify/internal/context" - "github.com/deislabs/ratify/pkg/controllers" - kmp "github.com/deislabs/ratify/pkg/customresources/keymanagementproviders" - "github.com/deislabs/ratify/pkg/keymanagementprovider" -) - -func TestGetKMPCertificates(t *testing.T) { - kmpCerts := map[keymanagementprovider.KMPMapKey][]*x509.Certificate{ - { - Name: "testName", - Version: "testVersion", - }: {}, - } - controllers.KMPCertificateMap = kmp.NewActiveCertStores() - controllers.KMPCertificateMap.AddCerts("default", "default/certStore", kmpCerts) - ctx := ctxUtils.SetContextWithNamespace(context.Background(), "default") - - if certs := GetKMPCertificates(ctx, "default/certStore"); len(certs) != 0 { - t.Fatalf("Expected 0 certificate, got %d", len(certs)) - } -} diff --git a/pkg/customresources/keymanagementproviders/api.go b/pkg/customresources/keymanagementproviders/api.go deleted file mode 100644 index 59c9d2683..000000000 --- a/pkg/customresources/keymanagementproviders/api.go +++ /dev/null @@ -1,47 +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 keymanagementproviders - -import ( - "crypto" - "crypto/x509" - - kmp "github.com/deislabs/ratify/pkg/keymanagementprovider" -) - -// KMPCertManager is an interface that defines the methods for managing certificate stores across different scopes. -type KMPCertManager interface { - // GetCertStores returns certificates for the given scope. - GetCertStores(scope, storeName string) []*x509.Certificate - - // AddCerts adds the given certificate under the given scope. - AddCerts(scope, storeName string, certs map[kmp.KMPMapKey][]*x509.Certificate) - - // DeleteCerts deletes the store from the given scope. - DeleteCerts(scope, storeName string) -} - -// KMPKeyManager is an interface that defines the methods for managing key stores across different scopes. -type KMPKeyManager interface { - // GetKeyStores returns keys for the given scope. - GetKeyStores(scope, storeName string) map[kmp.KMPMapKey]crypto.PublicKey - - // AddKeys adds the given keys under the given scope. - AddKeys(scope, storeName string, keys map[kmp.KMPMapKey]crypto.PublicKey) - - // DeleteKeys deletes the store from the given scope. - DeleteKeys(scope, storeName string) -} diff --git a/pkg/customresources/keymanagementproviders/kmpcertmanager.go b/pkg/customresources/keymanagementproviders/kmpcertmanager.go deleted file mode 100644 index 87210a7a7..000000000 --- a/pkg/customresources/keymanagementproviders/kmpcertmanager.go +++ /dev/null @@ -1,69 +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 keymanagementproviders - -import ( - "crypto/x509" - "sync" - - "github.com/deislabs/ratify/internal/constants" - kmp "github.com/deislabs/ratify/pkg/keymanagementprovider" -) - -// ActiveCertStores implements the KMPCertManager interface. -type ActiveCertStores struct { - // scopedStores maps from scope to CertificateMap defined in /pkg/keymanagementprovider/keymanagementprovider.go - // Example: - // { - // "namespace1": kmp.CertificateMap{}, - // "namespace2": kmp.CertificateMap{} - // } - scopedStores sync.Map -} - -func NewActiveCertStores() KMPCertManager { - return &ActiveCertStores{} -} - -// GetCertStores fulfills the KMPCertManager interface. -// It returns the certificates for the given scope. If no certificates are found for the given scope, it returns cluster-wide certificates. -// TODO: Current implementation always fetches cluster-wide cert stores. Will support actual namespaced certStores in future. -func (c *ActiveCertStores) GetCertStores(_, storeName string) []*x509.Certificate { - namespacedProvider, ok := c.scopedStores.Load(constants.EmptyNamespace) - if !ok { - return []*x509.Certificate{} - } - certMap := namespacedProvider.(*kmp.CertificateMap) - return kmp.FlattenKMPMap(certMap.GetCertificatesFromMap(storeName)) -} - -// AddCerts fulfills the KMPCertManager interface. -// It adds the given certificates under the given scope. -// TODO: Current implementation always adds the given certificate to cluster-wide cert store. Will support actual namespaced certStores in future. -func (c *ActiveCertStores) AddCerts(_, storeName string, certs map[kmp.KMPMapKey][]*x509.Certificate) { - scopedStore, _ := c.scopedStores.LoadOrStore(constants.EmptyNamespace, &kmp.CertificateMap{}) - scopedStore.(*kmp.CertificateMap).SetCertificatesInMap(storeName, certs) -} - -// DeleteCerts fulfills the KMPCertManager interface. -// It deletes the store from the given scope. -// TODO: Current implementation always deletes the given certificate from cluster-wide cert store. Will support actual namespaced certStores in future. -func (c *ActiveCertStores) DeleteCerts(_, storeName string) { - scopedKMPStore, ok := c.scopedStores.Load(constants.EmptyNamespace) - if ok { - scopedKMPStore.(*kmp.CertificateMap).DeleteCertificatesFromMap(storeName) - } -} diff --git a/pkg/customresources/keymanagementproviders/kmpcertmanager_test.go b/pkg/customresources/keymanagementproviders/kmpcertmanager_test.go deleted file mode 100644 index 6f56ef8fb..000000000 --- a/pkg/customresources/keymanagementproviders/kmpcertmanager_test.go +++ /dev/null @@ -1,68 +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 keymanagementproviders - -import ( - "crypto/x509" - "testing" - - "github.com/deislabs/ratify/pkg/keymanagementprovider" - "github.com/deislabs/ratify/pkg/utils" -) - -const ( - namespace1 = "namespace1" - namespace2 = "namespace2" - name1 = "name1" - name2 = "name2" -) - -func TestCertStoresOperations(t *testing.T) { - activeCertStores := NewActiveCertStores() - - certStore1 := map[keymanagementprovider.KMPMapKey][]*x509.Certificate{ - {Name: "testName1", Version: "testVersion1"}: {utils.CreateTestCert()}, - } - certStore2 := map[keymanagementprovider.KMPMapKey][]*x509.Certificate{ - {Name: "testName2", Version: "testVersion2"}: {utils.CreateTestCert()}, - } - - if len(activeCertStores.GetCertStores(namespace1, name1)) != 0 { - t.Errorf("Expected activeCertStores to have 0 cert store, but got %d", len(activeCertStores.GetCertStores(namespace1, name1))) - } - - activeCertStores.AddCerts(namespace1, name1, certStore1) - activeCertStores.AddCerts(namespace2, name2, certStore2) - - if len(activeCertStores.GetCertStores(namespace1, name1)) != 1 { - t.Errorf("Expected activeCertStores to have 1 cert store, but got %d", len(activeCertStores.GetCertStores(namespace1, name1))) - } - - if len(activeCertStores.GetCertStores(namespace2, name2)) != 1 { - t.Errorf("Expected activeCertStores to have 1 cert store, but got %d", len(activeCertStores.GetCertStores(namespace2, name2))) - } - - activeCertStores.DeleteCerts(namespace1, name1) - activeCertStores.DeleteCerts(namespace2, name2) - - if len(activeCertStores.GetCertStores(namespace1, name1)) != 0 { - t.Errorf("Expected activeCertStores to have 0 cert store, but got %d", len(activeCertStores.GetCertStores(namespace1, name1))) - } - - if len(activeCertStores.GetCertStores(namespace2, name2)) != 0 { - t.Errorf("Expected activeCertStores to have 0 cert store, but got %d", len(activeCertStores.GetCertStores(namespace2, name2))) - } -} diff --git a/pkg/customresources/keymanagementproviders/kmpkeymanager.go b/pkg/customresources/keymanagementproviders/kmpkeymanager.go deleted file mode 100644 index 24e558f60..000000000 --- a/pkg/customresources/keymanagementproviders/kmpkeymanager.go +++ /dev/null @@ -1,69 +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 keymanagementproviders - -import ( - "crypto" - "sync" - - "github.com/deislabs/ratify/internal/constants" - kmp "github.com/deislabs/ratify/pkg/keymanagementprovider" -) - -// ActiveKeyStores implements the KMPKeyManager interface. -type ActiveKeyStores struct { - // scopedStores maps from scope to KeyMap defined in /pkg/keymanagementprovider/keymanagementprovider.go - // Example: - // { - // "namespace1": kmp.KeyMap{}, - // "namespace2": kmp.KeyMap{} - // } - scopedStores sync.Map -} - -func NewActiveKeyStores() KMPKeyManager { - return &ActiveKeyStores{} -} - -// GetKeyStores fulfills the KMPKeyManager interface. -// It returns the keys for the given scope. If no keys are found for the given scope, it returns cluster-wide keys. -// TODO: Current implementation always fetches cluster-wide key stores. Will support actual namespaced keyStores in future. -func (k *ActiveKeyStores) GetKeyStores(_, storeName string) map[kmp.KMPMapKey]crypto.PublicKey { - namespacedProvider, ok := k.scopedStores.Load(constants.EmptyNamespace) - if !ok { - return map[kmp.KMPMapKey]crypto.PublicKey{} - } - keyMap := namespacedProvider.(*kmp.KeyMap) - return keyMap.GetKeysFromMap(storeName) -} - -// AddKeys fulfills the KMPKeyManager interface. -// It adds the given keys under the given scope. -// TODO: Current implementation always adds cluster-wide key stores. Will support actual namespaced keyStores in future. -func (k *ActiveKeyStores) AddKeys(_, storeName string, keys map[kmp.KMPMapKey]crypto.PublicKey) { - scopedStore, _ := k.scopedStores.LoadOrStore(constants.EmptyNamespace, &kmp.KeyMap{}) - scopedStore.(*kmp.KeyMap).SetKeysInMap(storeName, keys) -} - -// DeleteKeys fulfills the KMPKeyManager interface. -// It deletes the keys for the given scope. -// TODO: Current implementation always deletes cluster-wide key stores. Will support actual namespaced keyStores in future. -func (k *ActiveKeyStores) DeleteKeys(_, storeName string) { - scopedKMPStore, ok := k.scopedStores.Load(constants.EmptyNamespace) - if ok { - scopedKMPStore.(*kmp.KeyMap).DeleteKeysFromMap(storeName) - } -} diff --git a/pkg/customresources/keymanagementproviders/kmpkeymanager_test.go b/pkg/customresources/keymanagementproviders/kmpkeymanager_test.go deleted file mode 100644 index 529247162..000000000 --- a/pkg/customresources/keymanagementproviders/kmpkeymanager_test.go +++ /dev/null @@ -1,59 +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 keymanagementproviders - -import ( - "crypto" - "testing" - - "github.com/deislabs/ratify/pkg/keymanagementprovider" - "github.com/deislabs/ratify/pkg/utils" -) - -func TestKeyStoresOperations(t *testing.T) { - activeKeyStores := NewActiveKeyStores() - - keyStore1 := map[keymanagementprovider.KMPMapKey]crypto.PublicKey{ - {Name: "testName1", Version: "testVersion1"}: utils.CreateTestPublicKey(), - } - keyStore2 := map[keymanagementprovider.KMPMapKey]crypto.PublicKey{ - {Name: "testName2", Version: "testVersion2"}: utils.CreateTestPublicKey(), - } - - if len(activeKeyStores.GetKeyStores(namespace1, name1)) != 0 { - t.Errorf("Expected activeKeyStores to have 0 key store, but got %d", len(activeKeyStores.GetKeyStores(namespace1, name1))) - } - - activeKeyStores.AddKeys(namespace1, name1, keyStore1) - activeKeyStores.AddKeys(namespace2, name2, keyStore2) - - if len(activeKeyStores.GetKeyStores(namespace1, name1)) != 1 { - t.Errorf("Expected activeKeyStores to have 1 key store, but got %d", len(activeKeyStores.GetKeyStores(namespace1, name1))) - } - if len(activeKeyStores.GetKeyStores(namespace2, name2)) != 1 { - t.Errorf("Expected activeKeyStores to have 1 key store, but got %d", len(activeKeyStores.GetKeyStores(namespace2, name2))) - } - - activeKeyStores.DeleteKeys(namespace1, name1) - activeKeyStores.DeleteKeys(namespace2, name2) - - if len(activeKeyStores.GetKeyStores(namespace1, name1)) != 0 { - t.Errorf("Expected activeKeyStores to have 0 key store, but got %d", len(activeKeyStores.GetKeyStores(namespace1, name1))) - } - if len(activeKeyStores.GetKeyStores(namespace2, name2)) != 0 { - t.Errorf("Expected activeKeyStores to have 0 key store, but got %d", len(activeKeyStores.GetKeyStores(namespace2, name2))) - } -} diff --git a/pkg/keymanagementprovider/keymanagementprovider.go b/pkg/keymanagementprovider/keymanagementprovider.go index c78fafea5..f5c13f0f9 100644 --- a/pkg/keymanagementprovider/keymanagementprovider.go +++ b/pkg/keymanagementprovider/keymanagementprovider.go @@ -46,26 +46,20 @@ type KeyManagementProvider interface { GetKeys(ctx context.Context) (map[KMPMapKey]crypto.PublicKey, KeyManagementProviderStatus, error) } -// CertificateMap wraps a sync.Map to store certificates fetched from key management provider. -type CertificateMap struct { - // concurrency-safe map to store certificates fetched from key management provider - // layout: - // - // map["/"] = map[KMPMapKey][]*x509.Certificate - // where KMPMapKey is dimensioned by the name and version of the certificate. - // Array of x509 Certificates for certificate chain scenarios - mapping sync.Map -} +// static concurrency-safe map to store certificates fetched from key management provider +// layout: +// +// map["/"] = map[KMPMapKey][]*x509.Certificate +// where KMPMapKey is dimensioned by the name and version of the certificate. +// Array of x509 Certificates for certificate chain scenarios +var certificatesMap sync.Map -// KeyMap wraps a sync.Map to store keys fetched from key management provider. -type KeyMap struct { - // concurrency-safe map to store keys fetched from key management provider - // layout: - // - // map["/"] = map[KMPMapKey]PublicKey - // where KMPMapKey is dimensioned by the name and version of the public key. - mapping sync.Map -} +// static concurrency-safe map to store keys fetched from key management provider +// layout: +// +// map["/"] = map[KMPMapKey]PublicKey +// where KMPMapKey is dimensioned by the name and version of the public key. +var keyMap sync.Map // DecodeCertificates decodes PEM-encoded bytes into an x509.Certificate chain. func DecodeCertificates(value []byte) ([]*x509.Certificate, error) { @@ -105,13 +99,16 @@ func DecodeKey(value []byte) (crypto.PublicKey, error) { // SetCertificatesInMap sets the certificates in the map // it is concurrency-safe -func (c *CertificateMap) SetCertificatesInMap(resource string, certs map[KMPMapKey][]*x509.Certificate) { - c.mapping.Store(resource, certs) +func SetCertificatesInMap(resource string, certs map[KMPMapKey][]*x509.Certificate) { + certificatesMap.Store(resource, certs) } // GetCertificatesFromMap gets the certificates from the map and returns an empty map of certificate arrays if not found -func (c *CertificateMap) GetCertificatesFromMap(resource string) map[KMPMapKey][]*x509.Certificate { - certs, ok := c.mapping.Load(resource) +func GetCertificatesFromMap(ctx context.Context, resource string) map[KMPMapKey][]*x509.Certificate { + if !isCompatibleNamespace(ctx, resource) { + return map[KMPMapKey][]*x509.Certificate{} + } + certs, ok := certificatesMap.Load(resource) if !ok { return map[KMPMapKey][]*x509.Certificate{} } @@ -120,8 +117,8 @@ func (c *CertificateMap) GetCertificatesFromMap(resource string) map[KMPMapKey][ // DeleteCertificatesFromMap deletes the certificates from the map // it is concurrency-safe -func (c *CertificateMap) DeleteCertificatesFromMap(resource string) { - c.mapping.Delete(resource) +func DeleteCertificatesFromMap(resource string) { + certificatesMap.Delete(resource) } // FlattenKMPMap flattens the map of certificates fetched for a single key management provider resource and returns a single array @@ -143,13 +140,16 @@ func FlattenKMPMapKeys(keyMap map[KMPMapKey]crypto.PublicKey) []crypto.PublicKey } // SetKeysInMap sets the keys in the map -func (k *KeyMap) SetKeysInMap(resource string, keys map[KMPMapKey]crypto.PublicKey) { - k.mapping.Store(resource, keys) +func SetKeysInMap(resource string, keys map[KMPMapKey]crypto.PublicKey) { + keyMap.Store(resource, keys) } // GetKeysFromMap gets the keys from the map and returns an empty map of keys if not found -func (k *KeyMap) GetKeysFromMap(resource string) map[KMPMapKey]crypto.PublicKey { - keys, ok := k.mapping.Load(resource) +func GetKeysFromMap(ctx context.Context, resource string) map[KMPMapKey]crypto.PublicKey { + if !isCompatibleNamespace(ctx, resource) { + return map[KMPMapKey]crypto.PublicKey{} + } + keys, ok := keyMap.Load(resource) if !ok { return map[KMPMapKey]crypto.PublicKey{} } @@ -157,6 +157,13 @@ func (k *KeyMap) GetKeysFromMap(resource string) map[KMPMapKey]crypto.PublicKey } // DeleteKeysFromMap deletes the keys from the map -func (k *KeyMap) DeleteKeysFromMap(resource string) { - k.mapping.Delete(resource) +func DeleteKeysFromMap(resource string) { + keyMap.Delete(resource) +} + +// Namespaced verifiers could access both cluster-scoped and namespaced certStores. +// But cluster-wide verifiers could only access cluster-scoped certStores. +// TODO: current implementation always returns true. Check the namespace once we support multi-tenancy. +func isCompatibleNamespace(_ context.Context, _ string) bool { + return true } diff --git a/pkg/keymanagementprovider/keymanagementprovider_test.go b/pkg/keymanagementprovider/keymanagementprovider_test.go index fe34f9d59..9e88857a9 100644 --- a/pkg/keymanagementprovider/keymanagementprovider_test.go +++ b/pkg/keymanagementprovider/keymanagementprovider_test.go @@ -16,6 +16,7 @@ limitations under the License. package keymanagementprovider import ( + "context" "crypto" "crypto/rsa" "crypto/x509" @@ -26,9 +27,6 @@ import ( "github.com/stretchr/testify/assert" ) -var certificatesMap CertificateMap -var keyMap KeyMap - // TestDecodeCertificates tests the DecodeCertificates method func TestDecodeCertificates(t *testing.T) { cases := []struct { @@ -134,18 +132,18 @@ func TestDecodeCertificates_FailedX509ParseError(t *testing.T) { // TestSetCertificatesInMap checks if certificates are set in the map func TestSetCertificatesInMap(t *testing.T) { - certificatesMap.mapping.Delete("test") - certificatesMap.SetCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) - if _, ok := certificatesMap.mapping.Load("test"); !ok { + certificatesMap.Delete("test") + SetCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) + if _, ok := certificatesMap.Load("test"); !ok { t.Fatalf("certificatesMap should have been set for key") } } // TestGetCertificatesFromMap checks if certificates are fetched from the map func TestGetCertificatesFromMap(t *testing.T) { - certificatesMap.mapping.Delete("test") - certificatesMap.SetCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) - certs := certificatesMap.GetCertificatesFromMap("test") + certificatesMap.Delete("test") + SetCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) + certs := GetCertificatesFromMap(context.Background(), "test") if len(certs) != 1 { t.Fatalf("certificates should have been fetched from the map") } @@ -153,8 +151,8 @@ func TestGetCertificatesFromMap(t *testing.T) { // TestGetCertificatesFromMap_FailedToFetch checks if certificates are fetched from the map func TestGetCertificatesFromMap_FailedToFetch(t *testing.T) { - certificatesMap.mapping.Delete("test") - certs := certificatesMap.GetCertificatesFromMap("test") + certificatesMap.Delete("test") + certs := GetCertificatesFromMap(context.Background(), "test") if len(certs) != 0 { t.Fatalf("certificates should not have been fetched from the map") } @@ -162,10 +160,10 @@ func TestGetCertificatesFromMap_FailedToFetch(t *testing.T) { // TestDeleteCertificatesFromMap checks if certificates are deleted from the map func TestDeleteCertificatesFromMap(t *testing.T) { - certificatesMap.mapping.Delete("test") - certificatesMap.SetCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) - certificatesMap.DeleteCertificatesFromMap("test") - if _, ok := certificatesMap.mapping.Load("test"); ok { + certificatesMap.Delete("test") + SetCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) + DeleteCertificatesFromMap("test") + if _, ok := certificatesMap.Load("test"); ok { t.Fatalf("certificatesMap should have been deleted for key") } } @@ -180,18 +178,18 @@ func TestFlattenKMPMap(t *testing.T) { // TestSetKeysInMap checks if keys are set in the map func TestSetKeysInMap(t *testing.T) { - keyMap.mapping.Delete("test") - keyMap.SetKeysInMap("test", map[KMPMapKey]crypto.PublicKey{{}: &rsa.PublicKey{}}) - if _, ok := keyMap.mapping.Load("test"); !ok { + keyMap.Delete("test") + SetKeysInMap("test", map[KMPMapKey]crypto.PublicKey{{}: &rsa.PublicKey{}}) + if _, ok := keyMap.Load("test"); !ok { t.Fatalf("keysMap should have been set for key") } } // TestGetKeysFromMap checks if keys are fetched from the map func TestGetKeysFromMap(t *testing.T) { - keyMap.mapping.Delete("test") - keyMap.SetKeysInMap("test", map[KMPMapKey]crypto.PublicKey{{}: &rsa.PublicKey{}}) - keys := keyMap.GetKeysFromMap("test") + keyMap.Delete("test") + SetKeysInMap("test", map[KMPMapKey]crypto.PublicKey{{}: &rsa.PublicKey{}}) + keys := GetKeysFromMap(context.Background(), "test") if len(keys) != 1 { t.Fatalf("keys should have been fetched from the map") } @@ -199,8 +197,8 @@ func TestGetKeysFromMap(t *testing.T) { // TestGetKeysFromMap_FailedToFetch checks if keys fail to fetch from map func TestGetKeysFromMap_FailedToFetch(t *testing.T) { - keyMap.mapping.Delete("test") - keys := keyMap.GetKeysFromMap("test") + keyMap.Delete("test") + keys := GetKeysFromMap(context.Background(), "test") if len(keys) != 0 { t.Fatalf("keys should not have been fetched from the map") } @@ -208,10 +206,10 @@ func TestGetKeysFromMap_FailedToFetch(t *testing.T) { // TestDeleteKeysFromMap checks if key map entry is deleted from the map func TestDeleteKeysFromMap(t *testing.T) { - keyMap.mapping.Delete("test") - keyMap.SetKeysInMap("test", map[KMPMapKey]crypto.PublicKey{{}: &rsa.PublicKey{}}) - keyMap.DeleteKeysFromMap("test") - if _, ok := keyMap.mapping.Load("test"); ok { + keyMap.Delete("test") + SetKeysInMap("test", map[KMPMapKey]crypto.PublicKey{{}: &rsa.PublicKey{}}) + DeleteKeysFromMap("test") + if _, ok := keyMap.Load("test"); ok { t.Fatalf("keysMap should have been deleted for key") } } diff --git a/pkg/utils/test_utils.go b/pkg/utils/test_utils.go index 59a752abe..4cb400dc9 100644 --- a/pkg/utils/test_utils.go +++ b/pkg/utils/test_utils.go @@ -16,15 +16,8 @@ limitations under the License. package utils import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "math/big" "os" "path/filepath" - "time" ) func CreatePlugin(pluginName string) (string, error) { @@ -41,74 +34,3 @@ func CreatePlugin(pluginName string) (string, error) { defer file.Close() return tempDir, nil } - -func CreateTestCert() *x509.Certificate { - privateKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil - } - - // Create a certificate template - template := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - Organization: []string{"My Organization"}, - Country: []string{"Country"}, - Province: []string{"Province"}, - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(365 * 24 * time.Hour), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - } - - // Create a self-signed certificate - derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) - if err != nil { - return nil - } - - cert, _ := x509.ParseCertificate(derBytes) - return cert -} - -func CreateTestPublicKey() interface{} { - // Generate a private key - privateKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil - } - - // Marshal the public key - publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) - if err != nil { - return nil - } - - // Create a PEM block for the public key - publicKeyPEM := &pem.Block{ - Type: "RSA PUBLIC KEY", - Bytes: publicKeyBytes, - } - - // Encode the PEM block - publicKeyPEMEncoded := pem.EncodeToMemory(publicKeyPEM) - if publicKeyPEMEncoded == nil { - return nil - } - - // Decode the public key - block, _ := pem.Decode(publicKeyPEMEncoded) - if block == nil { - return nil - } - - // Parse the public key - publicKey, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - return nil - } - - return publicKey -} diff --git a/pkg/utils/test_utils_test.go b/pkg/utils/test_utils_test.go deleted file mode 100644 index 8b2087aea..000000000 --- a/pkg/utils/test_utils_test.go +++ /dev/null @@ -1,132 +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 utils - -import ( - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "math/big" - "reflect" - "testing" - "time" -) - -func TestCreateTestCert(t *testing.T) { - cert := CreateTestCert() - - if cert == nil { - t.Fatal("Expected a non-nil certificate, got nil") - } - - // Check certificate fields - expectedSerialNumber := big.NewInt(1) - if cert.SerialNumber.Cmp(expectedSerialNumber) != 0 { - t.Fatalf("Expected serial number %v, got %v", expectedSerialNumber, cert.SerialNumber) - } - - expectedOrganization := []string{"My Organization"} - if !reflect.DeepEqual(cert.Subject.Organization, expectedOrganization) { - t.Fatalf("Expected organization %v, got %v", expectedOrganization, cert.Subject.Organization) - } - - expectedCountry := []string{"Country"} - if !reflect.DeepEqual(cert.Subject.Country, expectedCountry) { - t.Fatalf("Expected country %v, got %v", expectedCountry, cert.Subject.Country) - } - - expectedProvince := []string{"Province"} - if !reflect.DeepEqual(cert.Subject.Province, expectedProvince) { - t.Fatalf("Expected province %v, got %v", expectedProvince, cert.Subject.Province) - } - - // Check NotBefore and NotAfter dates - now := time.Now() - if cert.NotBefore.After(now) { - t.Fatalf("NotBefore is after current time: %v", cert.NotBefore) - } - - // Check KeyUsage - expectedKeyUsage := x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature - if cert.KeyUsage != expectedKeyUsage { - t.Fatalf("Expected KeyUsage %v, got %v", expectedKeyUsage, cert.KeyUsage) - } - - // Check ExtKeyUsage - expectedExtKeyUsage := []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} - if !reflect.DeepEqual(cert.ExtKeyUsage, expectedExtKeyUsage) { - t.Fatalf("Expected ExtKeyUsage %v, got %v", expectedExtKeyUsage, cert.ExtKeyUsage) - } - - // Check BasicConstraintsValid - if !cert.BasicConstraintsValid { - t.Fatal("Expected BasicConstraintsValid to be true, got false") - } - - // Check PublicKey - if cert.PublicKey.(*rsa.PublicKey).N.Cmp(cert.PublicKey.(*rsa.PublicKey).N) != 0 { - t.Fatal("Public key mismatch") - } -} - -func TestCreateTestPublicKey(t *testing.T) { - publicKey := CreateTestPublicKey() - - if publicKey == nil { - t.Fatal("Expected a non-nil public key, got nil") - } - - // Check the type of the public key - _, ok := publicKey.(*rsa.PublicKey) - if !ok { - t.Fatal("Expected *rsa.PublicKey, got", reflect.TypeOf(publicKey)) - } - - // Marshal the public key - publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey.(*rsa.PublicKey)) - if err != nil { - t.Fatal("Error marshaling public key:", err) - } - - // Create a PEM block for the public key - expectedPublicKeyPEM := &pem.Block{ - Type: "RSA PUBLIC KEY", - Bytes: publicKeyBytes, - } - - // Encode the PEM block - expectedPublicKeyPEMEncoded := pem.EncodeToMemory(expectedPublicKeyPEM) - if expectedPublicKeyPEMEncoded == nil { - t.Fatal("Error encoding PEM block") - } - - // Decode the public key from the function's output - block, _ := pem.Decode(expectedPublicKeyPEMEncoded) - if block == nil { - t.Fatal("Error decoding PEM block") - } - - // Parse the public key - expectedPublicKey, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - t.Fatal("Error parsing public key:", err) - } - - // Check if the parsed public key matches the expected public key - if !reflect.DeepEqual(publicKey, expectedPublicKey) { - t.Fatal("Parsed public key does not match the expected public key") - } -} diff --git a/pkg/verifier/notation/truststore.go b/pkg/verifier/notation/truststore.go index cb84e3f23..bdf3ad6e7 100644 --- a/pkg/verifier/notation/truststore.go +++ b/pkg/verifier/notation/truststore.go @@ -21,9 +21,9 @@ import ( "errors" "fmt" - ctxUtils "github.com/deislabs/ratify/internal/context" "github.com/deislabs/ratify/internal/logger" cutils "github.com/deislabs/ratify/pkg/controllers/utils" + "github.com/deislabs/ratify/pkg/keymanagementprovider" "github.com/deislabs/ratify/pkg/utils" "github.com/notaryproject/notation-go/verifier/truststore" ) @@ -56,11 +56,7 @@ func (s trustStore) getCertificatesInternal(ctx context.Context, namedStore stri if certGroup := s.certStores[namedStore]; len(certGroup) > 0 { for _, certStore := range certGroup { logger.GetLogger(ctx, logOpt).Debugf("truststore getting certStore %v", certStore) - if !isCompatibleNamespace(ctx, certStore) { - logger.GetLogger(ctx, logOpt).Warnf("verifier in namespace: %s cannot access certStore: %s in different namespace.", ctxUtils.GetNamespace(ctx), certStore) - continue - } - result := cutils.GetKMPCertificates(ctx, certStore) + result := keymanagementprovider.FlattenKMPMap(keymanagementprovider.GetCertificatesFromMap(ctx, certStore)) // notation verifier does not consider specific named/versioned certificates within a key management provider resource if len(result) == 0 { logger.GetLogger(ctx, logOpt).Warnf("no certificate fetched for Key Management Provider %+v", certStore) @@ -107,10 +103,3 @@ func (s trustStore) filterValidCerts(certs []*x509.Certificate) ([]*x509.Certifi } return filteredCerts, nil } - -// Namespaced verifiers could access both cluster-scoped and namespaced certStores. -// But cluster-wide verifiers could only access cluster-scoped certStores. -// TODO: current implementation always returns true. Check the namespace once we support multi-tenancy. -func isCompatibleNamespace(_ context.Context, _ string) bool { - return true -} From 5dc63d08ad03a835de6e6fc52a2cce1a4627d34e Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Thu, 25 Apr 2024 13:56:15 +0000 Subject: [PATCH 05/10] feat: add context to GetKeys --- pkg/verifier/cosign/cosign.go | 2 +- pkg/verifier/cosign/trustpolicy.go | 6 +++--- pkg/verifier/cosign/trustpolicy_test.go | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/verifier/cosign/cosign.go b/pkg/verifier/cosign/cosign.go index 819dd0509..d85a2ccf6 100644 --- a/pkg/verifier/cosign/cosign.go +++ b/pkg/verifier/cosign/cosign.go @@ -529,7 +529,7 @@ func getKeysMapsDefault(ctx context.Context, trustPolicies *TrustPolicies, refer logger.GetLogger(ctx, logOpt).Debugf("selected trust policy %s for reference %s", trustPolicy.GetName(), reference) // get the map of keys for that reference - keysMap, err := trustPolicy.GetKeys(namespace) + keysMap, err := trustPolicy.GetKeys(ctx, namespace) if err != nil { return nil, cosign.CheckOpts{}, err } diff --git a/pkg/verifier/cosign/trustpolicy.go b/pkg/verifier/cosign/trustpolicy.go index 9975af171..371a215eb 100644 --- a/pkg/verifier/cosign/trustpolicy.go +++ b/pkg/verifier/cosign/trustpolicy.go @@ -70,7 +70,7 @@ type trustPolicy struct { type TrustPolicy interface { GetName() string - GetKeys(string) (map[PKKey]keymanagementprovider.PublicKey, error) + GetKeys(ctx context.Context, namespace string) (map[PKKey]keymanagementprovider.PublicKey, error) GetScopes() []string GetCosignOpts(context.Context) (cosign.CheckOpts, error) } @@ -138,7 +138,7 @@ func (tp *trustPolicy) GetName() string { } // GetKeys returns the public keys defined in the trust policy -func (tp *trustPolicy) GetKeys(namespace string) (map[PKKey]keymanagementprovider.PublicKey, error) { +func (tp *trustPolicy) GetKeys(ctx context.Context, namespace string) (map[PKKey]keymanagementprovider.PublicKey, error) { keyMap := make(map[PKKey]keymanagementprovider.PublicKey) // preload the local keys into the map of keys to be returned for key, pubKey := range tp.localKeys { @@ -153,7 +153,7 @@ func (tp *trustPolicy) GetKeys(namespace string) (map[PKKey]keymanagementprovide // must prepend namespace to key management provider name if not provided since namespace is prepended during key management provider intialization namespacedKMP := prependNamespaceToKMPName(keyConfig.Provider, namespace) // get the key management provider resource which contains a map of keys - kmpResource, ok := keymanagementprovider.GetKeysFromMap(namespacedKMP) + kmpResource, ok := keymanagementprovider.GetKeysFromMap(ctx, namespacedKMP) if !ok { return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(tp.verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: key management provider %s not found", tp.config.Name, namespacedKMP)) } diff --git a/pkg/verifier/cosign/trustpolicy_test.go b/pkg/verifier/cosign/trustpolicy_test.go index 84155dae6..948c3d206 100644 --- a/pkg/verifier/cosign/trustpolicy_test.go +++ b/pkg/verifier/cosign/trustpolicy_test.go @@ -16,6 +16,7 @@ limitations under the License. package cosign import ( + "context" "crypto" "crypto/ecdsa" "testing" @@ -188,7 +189,7 @@ func TestGetKeys(t *testing.T) { if err != nil { t.Fatalf("expected no error, got %v", err) } - keys, err := trustPolicy.GetKeys("ns") + keys, err := trustPolicy.GetKeys(context.Background(), "ns") if (err != nil) != tt.wantErr { t.Fatalf("expected %v, got %v", tt.wantErr, err) } From 7d93b962a47f1bf6e7db2fb565c12e49f8abc9c4 Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Wed, 17 Apr 2024 02:22:31 +0000 Subject: [PATCH 06/10] feat: add ClusterPolicy CRD --- Makefile | 2 +- PROJECT | 8 + api/unversioned/namespacedpolicy_types.go | 63 ++++ api/unversioned/zz_generated.deepcopy.go | 74 +++++ api/v1beta1/namespacedpolicy_types.go | 80 +++++ api/v1beta1/zz_generated.conversion.go | 284 ++++++++++++++++++ api/v1beta1/zz_generated.deepcopy.go | 90 ++++++ ...spacedpolicy-customresourcedefinition.yaml | 74 +++++ .../ratify-manager-role-clusterrole.yaml | 26 ++ ...ratify.deislabs.io_namespacedpolicies.yaml | 74 +++++ config/crd/kustomization.yaml | 3 + .../cainjection_in_clusterpolicies.yaml | 7 + .../cainjection_in_namespacedpolicies.yaml | 7 + .../patches/webhook_in_clusterpolicies.yaml | 16 + .../webhook_in_namespacedpolicies.yaml | 16 + config/rbac/namespacedpolicy_editor_role.yaml | 31 ++ config/rbac/namespacedpolicy_viewer_role.yaml | 27 ++ config/rbac/role.yaml | 26 ++ .../policy/config_v1alpha1_policy_json.yaml | 0 .../policy/config_v1alpha1_policy_rego.yaml | 0 .../policy/config_v1beta1_policy_json.yaml | 0 .../policy/config_v1beta1_policy_rego.yaml | 0 .../policy/config_v1alpha1_policy_json.yaml | 8 + .../policy/config_v1alpha1_policy_rego.yaml | 30 ++ .../policy/config_v1beta1_policy_json.yaml | 9 + .../policy/config_v1beta1_policy_rego.yaml | 31 ++ httpserver/handlers.go | 2 +- httpserver/server_test.go | 2 +- internal/constants/constants.go | 1 + .../{ => clustered}/policy_controller.go | 53 +--- .../clustered/policy_controller_test.go | 270 +++++++++++++++++ .../namespaced/policy_controller.go | 124 ++++++++ .../namespaced/policy_controller_test.go | 272 +++++++++++++++++ pkg/controllers/utils/policy.go | 54 ++++ .../policy_test.go} | 123 +------- pkg/controllers/verifier_controller_test.go | 30 ++ pkg/customresources/policies/api.go | 3 - pkg/customresources/policies/policies.go | 43 ++- pkg/customresources/policies/policies_test.go | 8 - pkg/manager/manager.go | 11 +- pkg/utils/test_utils.go | 25 ++ 41 files changed, 1807 insertions(+), 200 deletions(-) create mode 100644 api/unversioned/namespacedpolicy_types.go create mode 100644 api/v1beta1/namespacedpolicy_types.go create mode 100644 charts/ratify/crds/namespacedpolicy-customresourcedefinition.yaml create mode 100644 config/crd/bases/config.ratify.deislabs.io_namespacedpolicies.yaml create mode 100644 config/crd/patches/cainjection_in_clusterpolicies.yaml create mode 100644 config/crd/patches/cainjection_in_namespacedpolicies.yaml create mode 100644 config/crd/patches/webhook_in_clusterpolicies.yaml create mode 100644 config/crd/patches/webhook_in_namespacedpolicies.yaml create mode 100644 config/rbac/namespacedpolicy_editor_role.yaml create mode 100644 config/rbac/namespacedpolicy_viewer_role.yaml rename config/samples/{ => clustered}/policy/config_v1alpha1_policy_json.yaml (100%) rename config/samples/{ => clustered}/policy/config_v1alpha1_policy_rego.yaml (100%) rename config/samples/{ => clustered}/policy/config_v1beta1_policy_json.yaml (100%) rename config/samples/{ => clustered}/policy/config_v1beta1_policy_rego.yaml (100%) create mode 100644 config/samples/namespaced/policy/config_v1alpha1_policy_json.yaml create mode 100644 config/samples/namespaced/policy/config_v1alpha1_policy_rego.yaml create mode 100644 config/samples/namespaced/policy/config_v1beta1_policy_json.yaml create mode 100644 config/samples/namespaced/policy/config_v1beta1_policy_rego.yaml rename pkg/controllers/{ => clustered}/policy_controller.go (69%) create mode 100644 pkg/controllers/clustered/policy_controller_test.go create mode 100644 pkg/controllers/namespaced/policy_controller.go create mode 100644 pkg/controllers/namespaced/policy_controller_test.go create mode 100644 pkg/controllers/utils/policy.go rename pkg/controllers/{policy_controller_test.go => utils/policy_test.go} (53%) 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/policy/config_v1alpha1_policy_json.yaml b/config/samples/clustered/policy/config_v1alpha1_policy_json.yaml similarity index 100% rename from config/samples/policy/config_v1alpha1_policy_json.yaml rename to config/samples/clustered/policy/config_v1alpha1_policy_json.yaml diff --git a/config/samples/policy/config_v1alpha1_policy_rego.yaml b/config/samples/clustered/policy/config_v1alpha1_policy_rego.yaml similarity index 100% rename from config/samples/policy/config_v1alpha1_policy_rego.yaml rename to config/samples/clustered/policy/config_v1alpha1_policy_rego.yaml diff --git a/config/samples/policy/config_v1beta1_policy_json.yaml b/config/samples/clustered/policy/config_v1beta1_policy_json.yaml similarity index 100% rename from config/samples/policy/config_v1beta1_policy_json.yaml rename to config/samples/clustered/policy/config_v1beta1_policy_json.yaml diff --git a/config/samples/policy/config_v1beta1_policy_rego.yaml b/config/samples/clustered/policy/config_v1beta1_policy_rego.yaml similarity index 100% rename from config/samples/policy/config_v1beta1_policy_rego.yaml rename to config/samples/clustered/policy/config_v1beta1_policy_rego.yaml diff --git a/config/samples/namespaced/policy/config_v1alpha1_policy_json.yaml b/config/samples/namespaced/policy/config_v1alpha1_policy_json.yaml new file mode 100644 index 000000000..ac47554fb --- /dev/null +++ b/config/samples/namespaced/policy/config_v1alpha1_policy_json.yaml @@ -0,0 +1,8 @@ +apiVersion: config.ratify.deislabs.io/v1alpha1 +kind: NamespacedPolicy +metadata: + name: "configpolicy" +spec: + parameters: + artifactVerificationPolicies: + default: "all" diff --git a/config/samples/namespaced/policy/config_v1alpha1_policy_rego.yaml b/config/samples/namespaced/policy/config_v1alpha1_policy_rego.yaml new file mode 100644 index 000000000..799ed7060 --- /dev/null +++ b/config/samples/namespaced/policy/config_v1alpha1_policy_rego.yaml @@ -0,0 +1,30 @@ +apiVersion: config.ratify.deislabs.io/v1alpha1 +kind: NamespacedPolicy +metadata: + name: "regopolicy" +spec: + 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/namespaced/policy/config_v1beta1_policy_json.yaml b/config/samples/namespaced/policy/config_v1beta1_policy_json.yaml new file mode 100644 index 000000000..59f02d2d0 --- /dev/null +++ b/config/samples/namespaced/policy/config_v1beta1_policy_json.yaml @@ -0,0 +1,9 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedPolicy +metadata: + name: "ratify-policy" +spec: + type: "config-policy" + 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..64d2a2f9f --- /dev/null +++ b/config/samples/namespaced/policy/config_v1beta1_policy_rego.yaml @@ -0,0 +1,31 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedPolicy +metadata: + name: "ratify-policy" +spec: + type: "rego-policy" + 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/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/clustered/policy_controller.go similarity index 69% rename from pkg/controllers/policy_controller.go rename to pkg/controllers/clustered/policy_controller.go index babec48c6..db0099968 100644 --- a/pkg/controllers/policy_controller.go +++ b/pkg/controllers/clustered/policy_controller.go @@ -13,18 +13,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -package controllers +package clustered 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/clustered/policy_controller_test.go b/pkg/controllers/clustered/policy_controller_test.go new file mode 100644 index 000000000..ed89a0988 --- /dev/null +++ b/pkg/controllers/clustered/policy_controller_test.go @@ -0,0 +1,270 @@ +/* +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 clustered + +import ( + "context" + "errors" + "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" +) + +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 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) + }) + } +} + +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/namespaced/policy_controller.go b/pkg/controllers/namespaced/policy_controller.go new file mode 100644 index 000000000..e34b589c3 --- /dev/null +++ b/pkg/controllers/namespaced/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 namespaced + +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/namespaced/policy_controller_test.go b/pkg/controllers/namespaced/policy_controller_test.go new file mode 100644 index 000000000..ed246b525 --- /dev/null +++ b/pkg/controllers/namespaced/policy_controller_test.go @@ -0,0 +1,272 @@ +/* +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 namespaced + +import ( + "context" + "errors" + "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" + testNamespace = "testNamespace" +) + +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 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, constants.EmptyNamespace) + + 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.NamespacedPolicy + errString string + reconciler client.StatusClient + }{ + { + name: "success status", + isSuccess: true, + policy: &configv1beta1.NamespacedPolicy{}, + reconciler: &mockStatusClient{}, + }, + { + name: "error status", + isSuccess: false, + policy: &configv1beta1.NamespacedPolicy{}, + errString: "a long error string that exceeds the max length of 30 characters", + reconciler: &mockStatusClient{}, + }, + { + name: "status update failed", + isSuccess: true, + policy: &configv1beta1.NamespacedPolicy{}, + 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) + }) + } +} + +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..4814848fb 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/clustered" + "github.com/deislabs/ratify/pkg/controllers/namespaced" 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 = (&namespaced.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 = (&clustered.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..87d1dd30e 100644 --- a/pkg/utils/test_utils.go +++ b/pkg/utils/test_utils.go @@ -18,8 +18,33 @@ package utils import ( "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" ) +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 { From cf7c563326648792612495497fc9c074c5963fe7 Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Wed, 24 Apr 2024 12:18:03 +0000 Subject: [PATCH 07/10] chore: address comments --- .../policy/config_v1alpha1_policy_json.yaml | 4 +- .../policy/config_v1alpha1_policy_rego.yaml | 4 +- .../policy/config_v1beta1_policy_json.yaml | 6 +- .../policy/config_v1beta1_policy_rego.yaml | 6 +- .../policy/config_v1alpha1_policy_json.yaml | 8 --- .../policy/config_v1alpha1_policy_rego.yaml | 30 -------- .../policy/config_v1beta1_policy_json.yaml | 6 +- .../policy/config_v1beta1_policy_rego.yaml | 6 +- .../policy_controller.go | 2 +- .../policy_controller_test.go | 40 ++--------- .../policy_controller.go | 2 +- .../policy_controller_test.go | 71 +++++++++---------- pkg/manager/manager.go | 8 +-- pkg/utils/test_utils.go | 31 ++++++++ 14 files changed, 92 insertions(+), 132 deletions(-) delete mode 100644 config/samples/namespaced/policy/config_v1alpha1_policy_json.yaml delete mode 100644 config/samples/namespaced/policy/config_v1alpha1_policy_rego.yaml rename pkg/controllers/{clustered => clusterresource}/policy_controller.go (99%) rename pkg/controllers/{clustered => clusterresource}/policy_controller_test.go (86%) rename pkg/controllers/{namespaced => namespaceresource}/policy_controller.go (99%) rename pkg/controllers/{namespaced => namespaceresource}/policy_controller_test.go (83%) diff --git a/config/samples/clustered/policy/config_v1alpha1_policy_json.yaml b/config/samples/clustered/policy/config_v1alpha1_policy_json.yaml index 127bab714..a947d5966 100644 --- a/config/samples/clustered/policy/config_v1alpha1_policy_json.yaml +++ b/config/samples/clustered/policy/config_v1alpha1_policy_json.yaml @@ -1,7 +1,7 @@ apiVersion: config.ratify.deislabs.io/v1alpha1 -kind: Policy +kind: Policy # Policy applies to the cluster. metadata: - name: "configpolicy" + name: "configpolicy" # Ensure that metadata.name is either 'regopolicy' or 'configpolicy' spec: parameters: artifactVerificationPolicies: diff --git a/config/samples/clustered/policy/config_v1alpha1_policy_rego.yaml b/config/samples/clustered/policy/config_v1alpha1_policy_rego.yaml index f80978309..45c3695f4 100644 --- a/config/samples/clustered/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 index 1fbf1adba..00c35331b 100644 --- a/config/samples/clustered/policy/config_v1beta1_policy_json.yaml +++ b/config/samples/clustered/policy/config_v1beta1_policy_json.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: "config-policy" + 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/clustered/policy/config_v1beta1_policy_rego.yaml b/config/samples/clustered/policy/config_v1beta1_policy_rego.yaml index 89ac6a056..6bc1451d6 100644 --- a/config/samples/clustered/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_v1alpha1_policy_json.yaml b/config/samples/namespaced/policy/config_v1alpha1_policy_json.yaml deleted file mode 100644 index ac47554fb..000000000 --- a/config/samples/namespaced/policy/config_v1alpha1_policy_json.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: config.ratify.deislabs.io/v1alpha1 -kind: NamespacedPolicy -metadata: - name: "configpolicy" -spec: - parameters: - artifactVerificationPolicies: - default: "all" diff --git a/config/samples/namespaced/policy/config_v1alpha1_policy_rego.yaml b/config/samples/namespaced/policy/config_v1alpha1_policy_rego.yaml deleted file mode 100644 index 799ed7060..000000000 --- a/config/samples/namespaced/policy/config_v1alpha1_policy_rego.yaml +++ /dev/null @@ -1,30 +0,0 @@ -apiVersion: config.ratify.deislabs.io/v1alpha1 -kind: NamespacedPolicy -metadata: - name: "regopolicy" -spec: - 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/namespaced/policy/config_v1beta1_policy_json.yaml b/config/samples/namespaced/policy/config_v1beta1_policy_json.yaml index 59f02d2d0..bc4b32951 100644 --- a/config/samples/namespaced/policy/config_v1beta1_policy_json.yaml +++ b/config/samples/namespaced/policy/config_v1beta1_policy_json.yaml @@ -1,9 +1,9 @@ apiVersion: config.ratify.deislabs.io/v1beta1 -kind: NamespacedPolicy +kind: NamespacedPolicy # NamespacedPolicy only applies to specified namespace. metadata: - name: "ratify-policy" + name: "ratify-policy" # metadata.name MUST be set to ratify-policy since v1beta1. spec: - type: "config-policy" + 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 index 64d2a2f9f..9aaec1393 100644 --- a/config/samples/namespaced/policy/config_v1beta1_policy_rego.yaml +++ b/config/samples/namespaced/policy/config_v1beta1_policy_rego.yaml @@ -1,9 +1,9 @@ apiVersion: config.ratify.deislabs.io/v1beta1 -kind: NamespacedPolicy +kind: NamespacedPolicy # NamespacedPolicy only applies to specified namespace. 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/pkg/controllers/clustered/policy_controller.go b/pkg/controllers/clusterresource/policy_controller.go similarity index 99% rename from pkg/controllers/clustered/policy_controller.go rename to pkg/controllers/clusterresource/policy_controller.go index db0099968..56283e5c9 100644 --- a/pkg/controllers/clustered/policy_controller.go +++ b/pkg/controllers/clusterresource/policy_controller.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package clustered +package clusterresource import ( "context" diff --git a/pkg/controllers/clustered/policy_controller_test.go b/pkg/controllers/clusterresource/policy_controller_test.go similarity index 86% rename from pkg/controllers/clustered/policy_controller_test.go rename to pkg/controllers/clusterresource/policy_controller_test.go index ed89a0988..eb3713557 100644 --- a/pkg/controllers/clustered/policy_controller_test.go +++ b/pkg/controllers/clusterresource/policy_controller_test.go @@ -13,11 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package clustered +package clusterresource import ( "context" - "errors" "testing" configv1beta1 "github.com/deislabs/ratify/api/v1beta1" @@ -40,35 +39,6 @@ const ( policyName2 = "policy2" ) -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 TestPolicyAddOrReplace(t *testing.T) { testCases := []struct { name string @@ -119,21 +89,21 @@ func TestWritePolicyStatus(t *testing.T) { name: "success status", isSuccess: true, policy: &configv1beta1.Policy{}, - reconciler: &mockStatusClient{}, + 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: &mockStatusClient{}, + reconciler: &test.MockStatusClient{}, }, { name: "status update failed", isSuccess: true, policy: &configv1beta1.Policy{}, - reconciler: &mockStatusClient{ - updateFailed: true, + reconciler: &test.MockStatusClient{ + UpdateFailed: true, }, }, } diff --git a/pkg/controllers/namespaced/policy_controller.go b/pkg/controllers/namespaceresource/policy_controller.go similarity index 99% rename from pkg/controllers/namespaced/policy_controller.go rename to pkg/controllers/namespaceresource/policy_controller.go index e34b589c3..988bdd3e2 100644 --- a/pkg/controllers/namespaced/policy_controller.go +++ b/pkg/controllers/namespaceresource/policy_controller.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package namespaced +package namespaceresource import ( "context" diff --git a/pkg/controllers/namespaced/policy_controller_test.go b/pkg/controllers/namespaceresource/policy_controller_test.go similarity index 83% rename from pkg/controllers/namespaced/policy_controller_test.go rename to pkg/controllers/namespaceresource/policy_controller_test.go index ed246b525..2764f0faf 100644 --- a/pkg/controllers/namespaced/policy_controller_test.go +++ b/pkg/controllers/namespaceresource/policy_controller_test.go @@ -13,18 +13,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package namespaced +package namespaceresource import ( "context" - "errors" "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" + _ "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" @@ -41,35 +40,6 @@ const ( testNamespace = "testNamespace" ) -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 TestPolicyAddOrReplace(t *testing.T) { testCases := []struct { name string @@ -98,7 +68,7 @@ func TestPolicyAddOrReplace(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := policyAddOrReplace(tc.spec, constants.EmptyNamespace) + err := policyAddOrReplace(tc.spec, testNamespace) if tc.expectErr != (err != nil) { t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) @@ -107,6 +77,33 @@ func TestPolicyAddOrReplace(t *testing.T) { } } +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 { @@ -120,21 +117,21 @@ func TestWritePolicyStatus(t *testing.T) { name: "success status", isSuccess: true, policy: &configv1beta1.NamespacedPolicy{}, - reconciler: &mockStatusClient{}, + 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: &mockStatusClient{}, + reconciler: &test.MockStatusClient{}, }, { name: "status update failed", isSuccess: true, policy: &configv1beta1.NamespacedPolicy{}, - reconciler: &mockStatusClient{ - updateFailed: true, + reconciler: &test.MockStatusClient{ + UpdateFailed: true, }, }, } diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index 4814848fb..6cafbacd0 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -50,8 +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/clustered" - "github.com/deislabs/ratify/pkg/controllers/namespaced" + "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 ) @@ -214,14 +214,14 @@ func StartManager(certRotatorReady chan struct{}, probeAddr string) { setupLog.Error(err, "unable to create controller", "controller", "Certificate Store") os.Exit(1) } - if err = (&namespaced.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 = (&clustered.PolicyReconciler{ + 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 87d1dd30e..5e0100719 100644 --- a/pkg/utils/test_utils.go +++ b/pkg/utils/test_utils.go @@ -16,6 +16,8 @@ limitations under the License. package utils import ( + "context" + "errors" "os" "path/filepath" @@ -26,6 +28,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 +} + func CreateScheme() (*runtime.Scheme, error) { scheme := runtime.NewScheme() From 7521fb215a4c1dec7b7b581ac1f3278ace416d43 Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Tue, 23 Apr 2024 06:55:16 +0000 Subject: [PATCH 08/10] feat: add NamespacedStore CRD --- PROJECT | 8 + api/unversioned/namespacedstore_types.go | 69 +++++ api/unversioned/zz_generated.deepcopy.go | 79 ++++++ api/v1beta1/namespacedstore_types.go | 85 ++++++ api/v1beta1/zz_generated.conversion.go | 146 ++++++++++ api/v1beta1/zz_generated.deepcopy.go | 95 +++++++ ...espacedstore-customresourcedefinition.yaml | 92 ++++++ .../ratify-manager-role-clusterrole.yaml | 26 ++ ...g.ratify.deislabs.io_namespacedstores.yaml | 92 ++++++ config/crd/kustomization.yaml | 5 +- .../cainjection_in_namespacedstores.yaml | 7 + .../patches/webhook_in_namespacedstores.yaml | 16 ++ config/rbac/namespacedstore_editor_role.yaml | 31 ++ config/rbac/namespacedstore_viewer_role.yaml | 27 ++ .../store}/config_v1beta1_store_dynamic.yaml | 0 .../store}/config_v1beta1_store_oras.yaml | 0 .../config_v1beta1_store_oras_http.yaml | 0 ...onfig_v1beta1_store_oras_k8secretAuth.yaml | 0 .../config_v1beta1_namespacedstore.yaml | 12 + .../store/config_v1beta1_store_dynamic.yaml | 8 + .../store/config_v1beta1_store_oras.yaml | 10 + .../store/config_v1beta1_store_oras_http.yaml | 11 + ...onfig_v1beta1_store_oras_k8secretAuth.yaml | 14 + .../{ => clusterresource}/store_controller.go | 61 +--- .../store_controller_test.go | 146 ++++++++-- .../namespaceresource/store_controller.go | 112 ++++++++ .../store_controller_test.go | 265 ++++++++++++++++++ pkg/controllers/utils/store.go | 68 +++++ pkg/controllers/utils/store_test.go | 105 +++++++ pkg/controllers/verifier_controller_test.go | 2 + pkg/customresources/referrerstores/api.go | 6 - pkg/customresources/referrerstores/stores.go | 50 ++-- .../referrerstores/stores_test.go | 15 +- pkg/manager/manager.go | 9 +- test/bats/base-test.bats | 10 +- test/bats/plugin-test.bats | 40 ++- 36 files changed, 1597 insertions(+), 125 deletions(-) create mode 100644 api/unversioned/namespacedstore_types.go create mode 100644 api/v1beta1/namespacedstore_types.go create mode 100644 charts/ratify/crds/namespacedstore-customresourcedefinition.yaml create mode 100644 config/crd/bases/config.ratify.deislabs.io_namespacedstores.yaml create mode 100644 config/crd/patches/cainjection_in_namespacedstores.yaml create mode 100644 config/crd/patches/webhook_in_namespacedstores.yaml create mode 100644 config/rbac/namespacedstore_editor_role.yaml create mode 100644 config/rbac/namespacedstore_viewer_role.yaml rename config/samples/{ => clustered/store}/config_v1beta1_store_dynamic.yaml (100%) rename config/samples/{ => clustered/store}/config_v1beta1_store_oras.yaml (100%) rename config/samples/{ => clustered/store}/config_v1beta1_store_oras_http.yaml (100%) rename config/samples/{ => clustered/store}/config_v1beta1_store_oras_k8secretAuth.yaml (100%) create mode 100644 config/samples/config_v1beta1_namespacedstore.yaml create mode 100644 config/samples/namespaced/store/config_v1beta1_store_dynamic.yaml create mode 100644 config/samples/namespaced/store/config_v1beta1_store_oras.yaml create mode 100644 config/samples/namespaced/store/config_v1beta1_store_oras_http.yaml create mode 100644 config/samples/namespaced/store/config_v1beta1_store_oras_k8secretAuth.yaml rename pkg/controllers/{ => clusterresource}/store_controller.go (61%) rename pkg/controllers/{ => clusterresource}/store_controller_test.go (51%) create mode 100644 pkg/controllers/namespaceresource/store_controller.go create mode 100644 pkg/controllers/namespaceresource/store_controller_test.go create mode 100644 pkg/controllers/utils/store.go create mode 100644 pkg/controllers/utils/store_test.go diff --git a/PROJECT b/PROJECT index b91527b19..418f44674 100644 --- a/PROJECT +++ b/PROJECT @@ -88,4 +88,12 @@ resources: kind: NamespacedPolicy path: github.com/deislabs/ratify/api/v1beta1 version: v1beta1 +- api: + crdVersion: v1 + namespaced: true + domain: ratify.deislabs.io + group: config + kind: NamespacedStore + path: github.com/deislabs/ratify/api/v1beta1 + version: v1beta1 version: "3" diff --git a/api/unversioned/namespacedstore_types.go b/api/unversioned/namespacedstore_types.go new file mode 100644 index 000000000..c918da3c8 --- /dev/null +++ b/api/unversioned/namespacedstore_types.go @@ -0,0 +1,69 @@ +/* +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" +) + +// NamespacedStoreSpec defines the desired state of NamespacedStore +type NamespacedStoreSpec struct { + // Important: Run "make install-crds" to regenerate code after modifying this file + + // Name of the store + Name string `json:"name"` + // Version of the store plugin. Optional + Version string `json:"version,omitempty"` + // Plugin path, optional + Address string `json:"address,omitempty"` + // OCI Artifact source to download the plugin from, optional + Source *PluginSource `json:"source,omitempty"` + + // Parameters of the store + Parameters runtime.RawExtension `json:"parameters,omitempty"` +} + +// NamespacedStoreStatus defines the observed state of NamespacedStore +type NamespacedStoreStatus struct { + // Important: Run "make install-crds" 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"` +} + +// NamespacedStore is the Schema for the namespacedstores API +type NamespacedStore struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NamespacedStoreSpec `json:"spec,omitempty"` + Status NamespacedStoreStatus `json:"status,omitempty"` +} + +// NamespacedStoreList contains a list of NamespacedStore +type NamespacedStoreList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NamespacedStore `json:"items"` +} diff --git a/api/unversioned/zz_generated.deepcopy.go b/api/unversioned/zz_generated.deepcopy.go index 5e89a6dcb..ccf9bbeee 100644 --- a/api/unversioned/zz_generated.deepcopy.go +++ b/api/unversioned/zz_generated.deepcopy.go @@ -255,6 +255,85 @@ func (in *NamespacedPolicyStatus) DeepCopy() *NamespacedPolicyStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedStore) DeepCopyInto(out *NamespacedStore) { + *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 NamespacedStore. +func (in *NamespacedStore) DeepCopy() *NamespacedStore { + if in == nil { + return nil + } + out := new(NamespacedStore) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedStoreList) DeepCopyInto(out *NamespacedStoreList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NamespacedStore, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedStoreList. +func (in *NamespacedStoreList) DeepCopy() *NamespacedStoreList { + if in == nil { + return nil + } + out := new(NamespacedStoreList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedStoreSpec) DeepCopyInto(out *NamespacedStoreSpec) { + *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 NamespacedStoreSpec. +func (in *NamespacedStoreSpec) DeepCopy() *NamespacedStoreSpec { + if in == nil { + return nil + } + out := new(NamespacedStoreSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedStoreStatus) DeepCopyInto(out *NamespacedStoreStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedStoreStatus. +func (in *NamespacedStoreStatus) DeepCopy() *NamespacedStoreStatus { + if in == nil { + return nil + } + out := new(NamespacedStoreStatus) + 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/namespacedstore_types.go b/api/v1beta1/namespacedstore_types.go new file mode 100644 index 000000000..45a829087 --- /dev/null +++ b/api/v1beta1/namespacedstore_types.go @@ -0,0 +1,85 @@ +/* +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. + +// NamespacedStoreSpec defines the desired state of NamespacedStore +type NamespacedStoreSpec struct { + // Important: Run "make install-crds" to regenerate code after modifying this file + + // Name of the store + Name string `json:"name"` + // Version of the store plugin. Optional + Version string `json:"version,omitempty"` + // Plugin path, optional + Address string `json:"address,omitempty"` + // OCI Artifact source to download the plugin from, optional + Source *PluginSource `json:"source,omitempty"` + + // +kubebuilder:pruning:PreserveUnknownFields + // Parameters of the store + Parameters runtime.RawExtension `json:"parameters,omitempty"` +} + +// NamespacedStoreStatus defines the observed state of NamespacedStore +type NamespacedStoreStatus struct { + // Important: Run "make install-crds" 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` +// NamespacedStore is the Schema for the namespacedstores API +type NamespacedStore struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NamespacedStoreSpec `json:"spec,omitempty"` + Status NamespacedStoreStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:storageversion +// NamespacedStoreList contains a list of NamespacedStore +type NamespacedStoreList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NamespacedStore `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NamespacedStore{}, &NamespacedStoreList{}) +} diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index 2a98f8ac7..b5d5b9c12 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -156,6 +156,46 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*NamespacedStore)(nil), (*unversioned.NamespacedStore)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedStore_To_unversioned_NamespacedStore(a.(*NamespacedStore), b.(*unversioned.NamespacedStore), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedStore)(nil), (*NamespacedStore)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedStore_To_v1beta1_NamespacedStore(a.(*unversioned.NamespacedStore), b.(*NamespacedStore), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedStoreList)(nil), (*unversioned.NamespacedStoreList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedStoreList_To_unversioned_NamespacedStoreList(a.(*NamespacedStoreList), b.(*unversioned.NamespacedStoreList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedStoreList)(nil), (*NamespacedStoreList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedStoreList_To_v1beta1_NamespacedStoreList(a.(*unversioned.NamespacedStoreList), b.(*NamespacedStoreList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedStoreSpec)(nil), (*unversioned.NamespacedStoreSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedStoreSpec_To_unversioned_NamespacedStoreSpec(a.(*NamespacedStoreSpec), b.(*unversioned.NamespacedStoreSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedStoreSpec)(nil), (*NamespacedStoreSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedStoreSpec_To_v1beta1_NamespacedStoreSpec(a.(*unversioned.NamespacedStoreSpec), b.(*NamespacedStoreSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedStoreStatus)(nil), (*unversioned.NamespacedStoreStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedStoreStatus_To_unversioned_NamespacedStoreStatus(a.(*NamespacedStoreStatus), b.(*unversioned.NamespacedStoreStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedStoreStatus)(nil), (*NamespacedStoreStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedStoreStatus_To_v1beta1_NamespacedStoreStatus(a.(*unversioned.NamespacedStoreStatus), b.(*NamespacedStoreStatus), 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 { @@ -597,6 +637,112 @@ func Convert_unversioned_NamespacedPolicyStatus_To_v1beta1_NamespacedPolicyStatu return autoConvert_unversioned_NamespacedPolicyStatus_To_v1beta1_NamespacedPolicyStatus(in, out, s) } +func autoConvert_v1beta1_NamespacedStore_To_unversioned_NamespacedStore(in *NamespacedStore, out *unversioned.NamespacedStore, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_NamespacedStoreSpec_To_unversioned_NamespacedStoreSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_NamespacedStoreStatus_To_unversioned_NamespacedStoreStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_NamespacedStore_To_unversioned_NamespacedStore is an autogenerated conversion function. +func Convert_v1beta1_NamespacedStore_To_unversioned_NamespacedStore(in *NamespacedStore, out *unversioned.NamespacedStore, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedStore_To_unversioned_NamespacedStore(in, out, s) +} + +func autoConvert_unversioned_NamespacedStore_To_v1beta1_NamespacedStore(in *unversioned.NamespacedStore, out *NamespacedStore, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_unversioned_NamespacedStoreSpec_To_v1beta1_NamespacedStoreSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_unversioned_NamespacedStoreStatus_To_v1beta1_NamespacedStoreStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_unversioned_NamespacedStore_To_v1beta1_NamespacedStore is an autogenerated conversion function. +func Convert_unversioned_NamespacedStore_To_v1beta1_NamespacedStore(in *unversioned.NamespacedStore, out *NamespacedStore, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedStore_To_v1beta1_NamespacedStore(in, out, s) +} + +func autoConvert_v1beta1_NamespacedStoreList_To_unversioned_NamespacedStoreList(in *NamespacedStoreList, out *unversioned.NamespacedStoreList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]unversioned.NamespacedStore)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_NamespacedStoreList_To_unversioned_NamespacedStoreList is an autogenerated conversion function. +func Convert_v1beta1_NamespacedStoreList_To_unversioned_NamespacedStoreList(in *NamespacedStoreList, out *unversioned.NamespacedStoreList, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedStoreList_To_unversioned_NamespacedStoreList(in, out, s) +} + +func autoConvert_unversioned_NamespacedStoreList_To_v1beta1_NamespacedStoreList(in *unversioned.NamespacedStoreList, out *NamespacedStoreList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]NamespacedStore)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_unversioned_NamespacedStoreList_To_v1beta1_NamespacedStoreList is an autogenerated conversion function. +func Convert_unversioned_NamespacedStoreList_To_v1beta1_NamespacedStoreList(in *unversioned.NamespacedStoreList, out *NamespacedStoreList, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedStoreList_To_v1beta1_NamespacedStoreList(in, out, s) +} + +func autoConvert_v1beta1_NamespacedStoreSpec_To_unversioned_NamespacedStoreSpec(in *NamespacedStoreSpec, out *unversioned.NamespacedStoreSpec, s conversion.Scope) error { + out.Name = in.Name + out.Version = in.Version + out.Address = in.Address + out.Source = (*unversioned.PluginSource)(unsafe.Pointer(in.Source)) + out.Parameters = in.Parameters + return nil +} + +// Convert_v1beta1_NamespacedStoreSpec_To_unversioned_NamespacedStoreSpec is an autogenerated conversion function. +func Convert_v1beta1_NamespacedStoreSpec_To_unversioned_NamespacedStoreSpec(in *NamespacedStoreSpec, out *unversioned.NamespacedStoreSpec, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedStoreSpec_To_unversioned_NamespacedStoreSpec(in, out, s) +} + +func autoConvert_unversioned_NamespacedStoreSpec_To_v1beta1_NamespacedStoreSpec(in *unversioned.NamespacedStoreSpec, out *NamespacedStoreSpec, s conversion.Scope) error { + out.Name = in.Name + out.Version = in.Version + out.Address = in.Address + out.Source = (*PluginSource)(unsafe.Pointer(in.Source)) + out.Parameters = in.Parameters + return nil +} + +// Convert_unversioned_NamespacedStoreSpec_To_v1beta1_NamespacedStoreSpec is an autogenerated conversion function. +func Convert_unversioned_NamespacedStoreSpec_To_v1beta1_NamespacedStoreSpec(in *unversioned.NamespacedStoreSpec, out *NamespacedStoreSpec, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedStoreSpec_To_v1beta1_NamespacedStoreSpec(in, out, s) +} + +func autoConvert_v1beta1_NamespacedStoreStatus_To_unversioned_NamespacedStoreStatus(in *NamespacedStoreStatus, out *unversioned.NamespacedStoreStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + return nil +} + +// Convert_v1beta1_NamespacedStoreStatus_To_unversioned_NamespacedStoreStatus is an autogenerated conversion function. +func Convert_v1beta1_NamespacedStoreStatus_To_unversioned_NamespacedStoreStatus(in *NamespacedStoreStatus, out *unversioned.NamespacedStoreStatus, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedStoreStatus_To_unversioned_NamespacedStoreStatus(in, out, s) +} + +func autoConvert_unversioned_NamespacedStoreStatus_To_v1beta1_NamespacedStoreStatus(in *unversioned.NamespacedStoreStatus, out *NamespacedStoreStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + return nil +} + +// Convert_unversioned_NamespacedStoreStatus_To_v1beta1_NamespacedStoreStatus is an autogenerated conversion function. +func Convert_unversioned_NamespacedStoreStatus_To_v1beta1_NamespacedStoreStatus(in *unversioned.NamespacedStoreStatus, out *NamespacedStoreStatus, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedStoreStatus_To_v1beta1_NamespacedStoreStatus(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 076806a98..49342bd52 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -305,6 +305,101 @@ func (in *NamespacedPolicyStatus) DeepCopy() *NamespacedPolicyStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedStore) DeepCopyInto(out *NamespacedStore) { + *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 NamespacedStore. +func (in *NamespacedStore) DeepCopy() *NamespacedStore { + if in == nil { + return nil + } + out := new(NamespacedStore) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedStore) 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 *NamespacedStoreList) DeepCopyInto(out *NamespacedStoreList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NamespacedStore, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedStoreList. +func (in *NamespacedStoreList) DeepCopy() *NamespacedStoreList { + if in == nil { + return nil + } + out := new(NamespacedStoreList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedStoreList) 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 *NamespacedStoreSpec) DeepCopyInto(out *NamespacedStoreSpec) { + *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 NamespacedStoreSpec. +func (in *NamespacedStoreSpec) DeepCopy() *NamespacedStoreSpec { + if in == nil { + return nil + } + out := new(NamespacedStoreSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedStoreStatus) DeepCopyInto(out *NamespacedStoreStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedStoreStatus. +func (in *NamespacedStoreStatus) DeepCopy() *NamespacedStoreStatus { + if in == nil { + return nil + } + out := new(NamespacedStoreStatus) + 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/namespacedstore-customresourcedefinition.yaml b/charts/ratify/crds/namespacedstore-customresourcedefinition.yaml new file mode 100644 index 000000000..610929a04 --- /dev/null +++ b/charts/ratify/crds/namespacedstore-customresourcedefinition.yaml @@ -0,0 +1,92 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: namespacedstores.config.ratify.deislabs.io +spec: + group: config.ratify.deislabs.io + names: + kind: NamespacedStore + listKind: NamespacedStoreList + plural: namespacedstores + singular: namespacedstore + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: NamespacedStore is the Schema for the namespacedstores 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: NamespacedStoreSpec defines the desired state of NamespacedStore + properties: + address: + description: Plugin path, optional + type: string + name: + description: Name of the store + type: string + parameters: + description: Parameters of the store + 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 store plugin. Optional + type: string + required: + - name + type: object + status: + description: NamespacedStoreStatus defines the observed state of NamespacedStore + 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 a2bd679b6..14bf0344f 100644 --- a/charts/ratify/templates/ratify-manager-role-clusterrole.yaml +++ b/charts/ratify/templates/ratify-manager-role-clusterrole.yaml @@ -31,6 +31,32 @@ rules: - get - patch - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores/finalizers + verbs: + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores/status + verbs: + - get + - patch + - update - apiGroups: - config.ratify.deislabs.io resources: diff --git a/config/crd/bases/config.ratify.deislabs.io_namespacedstores.yaml b/config/crd/bases/config.ratify.deislabs.io_namespacedstores.yaml new file mode 100644 index 000000000..610929a04 --- /dev/null +++ b/config/crd/bases/config.ratify.deislabs.io_namespacedstores.yaml @@ -0,0 +1,92 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: namespacedstores.config.ratify.deislabs.io +spec: + group: config.ratify.deislabs.io + names: + kind: NamespacedStore + listKind: NamespacedStoreList + plural: namespacedstores + singular: namespacedstore + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: NamespacedStore is the Schema for the namespacedstores 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: NamespacedStoreSpec defines the desired state of NamespacedStore + properties: + address: + description: Plugin path, optional + type: string + name: + description: Name of the store + type: string + parameters: + description: Parameters of the store + 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 store plugin. Optional + type: string + required: + - name + type: object + status: + description: NamespacedStoreStatus defines the observed state of NamespacedStore + 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 5ade6da93..4e4cf1257 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -8,6 +8,7 @@ resources: - bases/config.ratify.deislabs.io_policies.yaml - bases/config.ratify.deislabs.io_keymanagementproviders.yaml - bases/config.ratify.deislabs.io_namespacedpolicies.yaml + - bases/config.ratify.deislabs.io_namespacedstores.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -19,6 +20,7 @@ patchesStrategicMerge: #- patches/webhook_in_policies.yaml #- patches/webhook_in_keymanagementproviders.yaml #- patches/webhook_in_namespacedpolicies.yaml + #- patches/webhook_in_namespacedstores.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -29,8 +31,9 @@ patchesStrategicMerge: #- patches/cainjection_in_policies.yaml #- patches/cainjection_in_keymanagementproviders.yaml #- patches/cainjection_in_namespacedpolicies.yaml + #- patches/cainjection_in_namespacedstores.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. configurations: -- kustomizeconfig.yaml + - kustomizeconfig.yaml diff --git a/config/crd/patches/cainjection_in_namespacedstores.yaml b/config/crd/patches/cainjection_in_namespacedstores.yaml new file mode 100644 index 000000000..db6068033 --- /dev/null +++ b/config/crd/patches/cainjection_in_namespacedstores.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: namespacedstores.config.ratify.deislabs.io diff --git a/config/crd/patches/webhook_in_namespacedstores.yaml b/config/crd/patches/webhook_in_namespacedstores.yaml new file mode 100644 index 000000000..f4ba8b69a --- /dev/null +++ b/config/crd/patches/webhook_in_namespacedstores.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: namespacedstores.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/namespacedstore_editor_role.yaml b/config/rbac/namespacedstore_editor_role.yaml new file mode 100644 index 000000000..6f983a8c3 --- /dev/null +++ b/config/rbac/namespacedstore_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit namespacedstores. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: namespacedstore-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: namespacedstore-editor-role +rules: +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores/status + verbs: + - get diff --git a/config/rbac/namespacedstore_viewer_role.yaml b/config/rbac/namespacedstore_viewer_role.yaml new file mode 100644 index 000000000..f3b276be8 --- /dev/null +++ b/config/rbac/namespacedstore_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view namespacedstores. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: namespacedstore-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: namespacedstore-viewer-role +rules: +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores + verbs: + - get + - list + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores/status + verbs: + - get diff --git a/config/samples/config_v1beta1_store_dynamic.yaml b/config/samples/clustered/store/config_v1beta1_store_dynamic.yaml similarity index 100% rename from config/samples/config_v1beta1_store_dynamic.yaml rename to config/samples/clustered/store/config_v1beta1_store_dynamic.yaml diff --git a/config/samples/config_v1beta1_store_oras.yaml b/config/samples/clustered/store/config_v1beta1_store_oras.yaml similarity index 100% rename from config/samples/config_v1beta1_store_oras.yaml rename to config/samples/clustered/store/config_v1beta1_store_oras.yaml diff --git a/config/samples/config_v1beta1_store_oras_http.yaml b/config/samples/clustered/store/config_v1beta1_store_oras_http.yaml similarity index 100% rename from config/samples/config_v1beta1_store_oras_http.yaml rename to config/samples/clustered/store/config_v1beta1_store_oras_http.yaml diff --git a/config/samples/config_v1beta1_store_oras_k8secretAuth.yaml b/config/samples/clustered/store/config_v1beta1_store_oras_k8secretAuth.yaml similarity index 100% rename from config/samples/config_v1beta1_store_oras_k8secretAuth.yaml rename to config/samples/clustered/store/config_v1beta1_store_oras_k8secretAuth.yaml diff --git a/config/samples/config_v1beta1_namespacedstore.yaml b/config/samples/config_v1beta1_namespacedstore.yaml new file mode 100644 index 000000000..677f45d47 --- /dev/null +++ b/config/samples/config_v1beta1_namespacedstore.yaml @@ -0,0 +1,12 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedStore +metadata: + labels: + app.kubernetes.io/name: namespacedstore + app.kubernetes.io/instance: namespacedstore-sample + app.kubernetes.io/part-of: ratify + app.kuberentes.io/managed-by: kustomize + app.kubernetes.io/created-by: ratify + name: namespacedstore-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/namespaced/store/config_v1beta1_store_dynamic.yaml b/config/samples/namespaced/store/config_v1beta1_store_dynamic.yaml new file mode 100644 index 000000000..1e9926345 --- /dev/null +++ b/config/samples/namespaced/store/config_v1beta1_store_dynamic.yaml @@ -0,0 +1,8 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedStore +metadata: + name: store-dynamic +spec: + name: dynamic + source: + artifact: wabbitnetworks.azurecr.io/test/sample-store-plugin:v1 diff --git a/config/samples/namespaced/store/config_v1beta1_store_oras.yaml b/config/samples/namespaced/store/config_v1beta1_store_oras.yaml new file mode 100644 index 000000000..d9774331e --- /dev/null +++ b/config/samples/namespaced/store/config_v1beta1_store_oras.yaml @@ -0,0 +1,10 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedStore +metadata: + name: store-oras +spec: + name: oras + parameters: + cacheEnabled: true + cosignEnabled: true + ttl: 10 diff --git a/config/samples/namespaced/store/config_v1beta1_store_oras_http.yaml b/config/samples/namespaced/store/config_v1beta1_store_oras_http.yaml new file mode 100644 index 000000000..dfd1bc390 --- /dev/null +++ b/config/samples/namespaced/store/config_v1beta1_store_oras_http.yaml @@ -0,0 +1,11 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedStore +metadata: + name: store-oras +spec: + name: oras + parameters: + cacheEnabled: true + cosignEnabled: true + ttl: 10 + useHttp: true \ No newline at end of file diff --git a/config/samples/namespaced/store/config_v1beta1_store_oras_k8secretAuth.yaml b/config/samples/namespaced/store/config_v1beta1_store_oras_k8secretAuth.yaml new file mode 100644 index 000000000..965bced51 --- /dev/null +++ b/config/samples/namespaced/store/config_v1beta1_store_oras_k8secretAuth.yaml @@ -0,0 +1,14 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedStore +metadata: + name: store-oras +spec: + name: oras + parameters: + cacheEnabled: true + ttl: 10 + useHttp: true + authProvider: + name: k8Secrets + secrets: + - secretName: ratify-dockerconfig \ No newline at end of file diff --git a/pkg/controllers/store_controller.go b/pkg/controllers/clusterresource/store_controller.go similarity index 61% rename from pkg/controllers/store_controller.go rename to pkg/controllers/clusterresource/store_controller.go index 6349818af..b9cfac901 100644 --- a/pkg/controllers/store_controller.go +++ b/pkg/controllers/clusterresource/store_controller.go @@ -13,11 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package controllers +package clusterresource import ( "context" - "encoding/json" "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -26,11 +25,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" configv1beta1 "github.com/deislabs/ratify/api/v1beta1" - "github.com/deislabs/ratify/config" "github.com/deislabs/ratify/internal/constants" - rc "github.com/deislabs/ratify/pkg/referrerstore/config" - sf "github.com/deislabs/ratify/pkg/referrerstore/factory" - "github.com/deislabs/ratify/pkg/referrerstore/types" + "github.com/deislabs/ratify/pkg/controllers" + "github.com/deislabs/ratify/pkg/controllers/utils" "github.com/sirupsen/logrus" ) @@ -54,13 +51,12 @@ func (r *StoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl var store configv1beta1.Store var resource = req.Name - storeLogger.Infof("reconciling store '%v'", resource) + storeLogger.Infof("reconciling cluster store '%v'", resource) if err := r.Get(ctx, req.NamespacedName, &store); err != nil { if apierrors.IsNotFound(err) { storeLogger.Infof("deletion detected, removing store %v", req.Name) - // TODO: pass the actual namespace once multi-tenancy is supported. - NamespacedStores.DeleteStore(constants.EmptyNamespace, resource) + controllers.NamespacedStores.DeleteStore(constants.EmptyNamespace, resource) } else { storeLogger.Error(err, "unable to fetch store") } @@ -89,51 +85,12 @@ func (r *StoreReconciler) SetupWithManager(mgr ctrl.Manager) error { // Creates a store reference from CRD spec and add store to map func storeAddOrReplace(spec configv1beta1.StoreSpec, fullname string) error { - storeConfig, err := specToStoreConfig(spec) + storeConfig, err := utils.CreateStoreConfig(spec.Parameters.Raw, spec.Name, spec.Source) if err != nil { return fmt.Errorf("unable to convert store spec to store config, err: %w", err) } - // if the default version is not suitable, the plugin configuration should specify the desired version - 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) - } - storeReference, err := sf.CreateStoreFromConfig(storeConfig, spec.Version, []string{spec.Address}) - - if err != nil || storeReference == nil { - logrus.Error(err, "store factory failed to create store from store config") - return fmt.Errorf("store factory failed to create store from store config, err: %w", err) - } - - // TODO: pass the actual namespace once multi-tenancy is supported. - NamespacedStores.AddStore(constants.EmptyNamespace, fullname, storeReference) - logrus.Infof("store '%v' added to store map", storeReference.Name()) - - return nil -} - -// Returns a store reference from spec -func specToStoreConfig(storeSpec configv1beta1.StoreSpec) (rc.StorePluginConfig, error) { - storeConfig := rc.StorePluginConfig{} - - if string(storeSpec.Parameters.Raw) != "" { - if err := json.Unmarshal(storeSpec.Parameters.Raw, &storeConfig); err != nil { - logrus.Error(err, "unable to decode store parameters", "Parameters.Raw", storeSpec.Parameters.Raw) - return rc.StorePluginConfig{}, err - } - } - storeConfig[types.Name] = storeSpec.Name - if storeSpec.Source != nil { - storeConfig[types.Source] = storeSpec.Source - } - - return storeConfig, nil + return utils.UpsertStoreMap(spec.Version, spec.Address, fullname, constants.EmptyNamespace, storeConfig) } func writeStoreStatus(ctx context.Context, r client.StatusClient, store *configv1beta1.Store, logger *logrus.Entry, isSuccess bool, errorString string) { @@ -144,8 +101,8 @@ func writeStoreStatus(ctx context.Context, r client.StatusClient, store *configv } else { store.Status.IsSuccess = false store.Status.Error = errorString - if len(errorString) > maxBriefErrLength { - store.Status.BriefError = fmt.Sprintf("%s...", errorString[:maxBriefErrLength]) + if len(errorString) > constants.MaxBriefErrLength { + store.Status.BriefError = fmt.Sprintf("%s...", errorString[:constants.MaxBriefErrLength]) } } diff --git a/pkg/controllers/store_controller_test.go b/pkg/controllers/clusterresource/store_controller_test.go similarity index 51% rename from pkg/controllers/store_controller_test.go rename to pkg/controllers/clusterresource/store_controller_test.go index 1aa408447..4123e5e4c 100644 --- a/pkg/controllers/store_controller_test.go +++ b/pkg/controllers/clusterresource/store_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" @@ -23,14 +23,25 @@ import ( configv1beta1 "github.com/deislabs/ratify/api/v1beta1" "github.com/deislabs/ratify/internal/constants" + "github.com/deislabs/ratify/pkg/controllers" rs "github.com/deislabs/ratify/pkg/customresources/referrerstores" "github.com/deislabs/ratify/pkg/utils" + 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 sampleName = "sample" +const ( + storeName = "testStore" + testNamespace = "testNamespace" + sampleName = "sample" + orasName = "oras" +) func TestStoreAdd_EmptyParameter(t *testing.T) { resetStoreMap() @@ -48,15 +59,16 @@ func TestStoreAdd_EmptyParameter(t *testing.T) { if err := storeAddOrReplace(testStoreSpec, "oras"); err != nil { t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) } - if NamespacedStores.GetStoreCount() != 1 { - t.Fatalf("Store map expected size 1, actual %v", NamespacedStores.GetStoreCount()) + stores := controllers.NamespacedStores.GetStores(constants.EmptyNamespace) + if len(stores) != 1 { + t.Fatalf("Store map expected size 1, actual %v", len(stores)) } } func TestStoreAdd_WithParameters(t *testing.T) { resetStoreMap() - if NamespacedStores.GetStoreCount() != 0 { - t.Fatalf("Store map expected size 0, actual %v", NamespacedStores.GetStoreCount()) + if len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace)) != 0 { + t.Fatalf("Store map expected size 0, actual %v", len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace))) } dirPath, err := utils.CreatePlugin(sampleName) if err != nil { @@ -69,8 +81,8 @@ func TestStoreAdd_WithParameters(t *testing.T) { if err := storeAddOrReplace(testStoreSpec, "testObject"); err != nil { t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) } - if NamespacedStores.GetStoreCount() != 1 { - t.Fatalf("Store map expected size 1, actual %v", NamespacedStores.GetStoreCount()) + if len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace)) != 1 { + t.Fatalf("Store map expected size 1, actual %v", len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace))) } } @@ -87,21 +99,21 @@ func TestWriteStoreStatus(t *testing.T) { name: "success status", isSuccess: true, store: &configv1beta1.Store{}, - reconciler: &mockStatusClient{}, + reconciler: &test.MockStatusClient{}, }, { name: "error status", isSuccess: false, store: &configv1beta1.Store{}, errString: "a long error string that exceeds the max length of 30 characters", - reconciler: &mockStatusClient{}, + reconciler: &test.MockStatusClient{}, }, { name: "status update failed", isSuccess: true, store: &configv1beta1.Store{}, - reconciler: &mockStatusClient{ - updateFailed: true, + reconciler: &test.MockStatusClient{ + UpdateFailed: true, }, }, } @@ -138,8 +150,8 @@ func TestStore_UpdateAndDelete(t *testing.T) { if err := storeAddOrReplace(testStoreSpec, sampleName); err != nil { t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) } - if NamespacedStores.GetStoreCount() != 1 { - t.Fatalf("Store map expected size 1, actual %v", NamespacedStores.GetStoreCount()) + if len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace)) != 1 { + t.Fatalf("Store map expected size 1, actual %v", len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace))) } // modify the Store @@ -153,19 +165,113 @@ func TestStore_UpdateAndDelete(t *testing.T) { } // validate no Store has been added - if NamespacedStores.GetStoreCount() != 1 { - t.Fatalf("Store map should be 1 after replacement, actual %v", NamespacedStores.GetStoreCount()) + if len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace)) != 1 { + t.Fatalf("Store map should be 1 after replacement, actual %v", len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace))) + } + + controllers.NamespacedStores.DeleteStore(constants.EmptyNamespace, sampleName) + + if len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace)) != 0 { + t.Fatalf("Store map should be 0 after deletion, actual %v", len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace))) + } +} + +func TestStoreReconcile(t *testing.T) { + dirPath, err := test.CreatePlugin(orasName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + tests := []struct { + name string + store *configv1beta1.Store + req *reconcile.Request + expectedErr bool + expectedStoreCount int + }{ + { + name: "nonexistent store", + req: &reconcile.Request{ + NamespacedName: types.NamespacedName{Name: "nonexistent"}, + }, + store: &configv1beta1.Store{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: constants.EmptyNamespace, + Name: storeName, + }, + Spec: configv1beta1.StoreSpec{ + Name: orasName, + }, + }, + expectedErr: false, + expectedStoreCount: 0, + }, + { + name: "valid spec", + store: &configv1beta1.Store{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: constants.EmptyNamespace, + Name: storeName, + }, + Spec: configv1beta1.StoreSpec{ + Name: orasName, + Address: dirPath, + }, + }, + expectedErr: false, + expectedStoreCount: 1, + }, + { + name: "invalid parameters", + store: &configv1beta1.Store{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: constants.EmptyNamespace, + Name: storeName, + }, + Spec: configv1beta1.StoreSpec{ + Parameters: runtime.RawExtension{ + Raw: []byte("test"), + }, + }, + }, + expectedErr: true, + expectedStoreCount: 0, + }, } - NamespacedStores.DeleteStore(constants.EmptyNamespace, sampleName) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resetStoreMap() + scheme, _ := test.CreateScheme() + client := fake.NewClientBuilder().WithScheme(scheme) + client.WithObjects(tt.store) + r := &StoreReconciler{ + Scheme: scheme, + Client: client.Build(), + } + var req reconcile.Request + if tt.req != nil { + req = *tt.req + } else { + req = reconcile.Request{ + NamespacedName: test.KeyFor(tt.store), + } + } - if NamespacedStores.GetStoreCount() != 0 { - t.Fatalf("Store map should be 0 after deletion, actual %v", NamespacedStores.GetStoreCount()) + _, 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.NamespacedStores.GetStores(constants.EmptyNamespace)) != tt.expectedStoreCount { + t.Fatalf("Store map expected size %v, actual %v", tt.expectedStoreCount, len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace))) + } + }) } } func resetStoreMap() { - NamespacedStores = rs.NewActiveStores() + controllers.NamespacedStores = rs.NewActiveStores() } func getOrasStoreSpec(pluginName, pluginPath string) configv1beta1.StoreSpec { diff --git a/pkg/controllers/namespaceresource/store_controller.go b/pkg/controllers/namespaceresource/store_controller.go new file mode 100644 index 000000000..b150de35e --- /dev/null +++ b/pkg/controllers/namespaceresource/store_controller.go @@ -0,0 +1,112 @@ +/* +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" + + 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" + + 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" +) + +// StoreReconciler reconciles a Store object +type StoreReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedstores,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedstores/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedstores/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 *StoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + storeLogger := logrus.WithContext(ctx) + + var store configv1beta1.NamespacedStore + var resource = req.Name + storeLogger.Infof("reconciling store '%v'", resource) + + if err := r.Get(ctx, req.NamespacedName, &store); err != nil { + if apierrors.IsNotFound(err) { + storeLogger.Infof("deletion detected, removing store %v", req.Name) + controllers.NamespacedStores.DeleteStore(req.Namespace, resource) + } else { + storeLogger.Error(err, "unable to fetch store") + } + + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + if err := storeAddOrReplace(store.Spec, resource, req.Namespace); err != nil { + storeLogger.Error(err, "unable to create store from store crd") + writeStoreStatus(ctx, r, &store, storeLogger, false, err.Error()) + return ctrl.Result{}, err + } + + writeStoreStatus(ctx, r, &store, storeLogger, true, "") + + // returning empty result and no error to indicate we’ve successfully reconciled this object + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *StoreReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&configv1beta1.NamespacedStore{}). + Complete(r) +} + +// Creates a store reference from CRD spec and add store to map +func storeAddOrReplace(spec configv1beta1.NamespacedStoreSpec, fullname, namespace string) error { + storeConfig, err := utils.CreateStoreConfig(spec.Parameters.Raw, spec.Name, spec.Source) + if err != nil { + return fmt.Errorf("unable to convert store spec to store config, err: %w", err) + } + + return utils.UpsertStoreMap(spec.Version, spec.Address, fullname, namespace, storeConfig) +} + +func writeStoreStatus(ctx context.Context, r client.StatusClient, store *configv1beta1.NamespacedStore, logger *logrus.Entry, isSuccess bool, errorString string) { + if isSuccess { + store.Status.IsSuccess = true + store.Status.Error = "" + store.Status.BriefError = "" + } else { + store.Status.IsSuccess = false + store.Status.Error = errorString + if len(errorString) > constants.MaxBriefErrLength { + store.Status.BriefError = fmt.Sprintf("%s...", errorString[:constants.MaxBriefErrLength]) + } + } + + if statusErr := r.Status().Update(ctx, store); statusErr != nil { + logger.Error(statusErr, ",unable to update store error status") + } +} diff --git a/pkg/controllers/namespaceresource/store_controller_test.go b/pkg/controllers/namespaceresource/store_controller_test.go new file mode 100644 index 000000000..a66f84b47 --- /dev/null +++ b/pkg/controllers/namespaceresource/store_controller_test.go @@ -0,0 +1,265 @@ +/* +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" + "os" + "strings" + "testing" + + configv1beta1 "github.com/deislabs/ratify/api/v1beta1" + "github.com/deislabs/ratify/pkg/controllers" + "github.com/deislabs/ratify/pkg/customresources/referrerstores" + test "github.com/deislabs/ratify/pkg/utils" + 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/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const ( + storeName = "testStore" + sampleName = "sample" + orasName = "oras" +) + +func TestStoreAdd_EmptyParameter(t *testing.T) { + resetStoreMap() + dirPath, err := test.CreatePlugin(sampleName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testStoreSpec = configv1beta1.NamespacedStoreSpec{ + Name: sampleName, + Address: dirPath, + } + + if err := storeAddOrReplace(testStoreSpec, "oras", testNamespace); err != nil { + t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedStores.GetStores(testNamespace)) != 1 { + t.Fatalf("Store map expected size 1, actual %v", len(controllers.NamespacedStores.GetStores(testNamespace))) + } +} + +func TestStoreAdd_InvalidConfig(t *testing.T) { + resetStoreMap() + var testStoreSpec = configv1beta1.NamespacedStoreSpec{ + Name: orasName, + Parameters: runtime.RawExtension{ + Raw: []byte("test"), + }, + } + + if err := storeAddOrReplace(testStoreSpec, orasName, testNamespace); err == nil { + t.Fatalf("storeAddOrReplace() expected error, actual %v", err) + } + if len(controllers.NamespacedStores.GetStores(testNamespace)) != 0 { + t.Fatalf("Store map expected size 0, actual %v", len(controllers.NamespacedStores.GetStores(testNamespace))) + } +} + +func TestStoreAdd_WithParameters(t *testing.T) { + resetStoreMap() + if len(controllers.NamespacedStores.GetStores(testNamespace)) != 0 { + t.Fatalf("Store map expected size 0, actual %v", len(controllers.NamespacedStores.GetStores(testNamespace))) + } + dirPath, err := test.CreatePlugin(sampleName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testStoreSpec = getOrasStoreSpec(sampleName, dirPath) + + if err := storeAddOrReplace(testStoreSpec, "testObject", testNamespace); err != nil { + t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedStores.GetStores(testNamespace)) != 1 { + t.Fatalf("Store map expected size 1, actual %v", len(controllers.NamespacedStores.GetStores(testNamespace))) + } +} + +func TestStoreAddOrReplace_PluginNotFound(t *testing.T) { + resetStoreMap() + var resource = "invalidplugin" + expectedMsg := "plugin not found" + var spec = getInvalidStoreSpec() + err := storeAddOrReplace(spec, resource, testNamespace) + + if !strings.Contains(err.Error(), expectedMsg) { + t.Fatalf("TestStoreAddOrReplace_PluginNotFound expected msg: '%v', actual %v", expectedMsg, err.Error()) + } +} + +func TestStore_UpdateAndDelete(t *testing.T) { + resetStoreMap() + dirPath, err := test.CreatePlugin(sampleName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testStoreSpec = getOrasStoreSpec(sampleName, dirPath) + // add a Store + if err := storeAddOrReplace(testStoreSpec, sampleName, testNamespace); err != nil { + t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedStores.GetStores(testNamespace)) != 1 { + t.Fatalf("Store map expected size 1, actual %v", len(controllers.NamespacedStores.GetStores(testNamespace))) + } + + // modify the Store + var updatedSpec = configv1beta1.NamespacedStoreSpec{ + Name: sampleName, + Address: dirPath, + } + + if err := storeAddOrReplace(updatedSpec, sampleName, testNamespace); err != nil { + t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) + } + + // validate no Store has been added + if len(controllers.NamespacedStores.GetStores(testNamespace)) != 1 { + t.Fatalf("Store map should be 1 after replacement, actual %v", len(controllers.NamespacedStores.GetStores(testNamespace))) + } + + controllers.NamespacedStores.DeleteStore(testNamespace, sampleName) + + if len(controllers.NamespacedStores.GetStores(testNamespace)) != 0 { + t.Fatalf("Store map should be 0 after deletion, actual %v", len(controllers.NamespacedStores.GetStores(testNamespace))) + } +} + +func TestStoreReconcile(t *testing.T) { + dirPath, err := test.CreatePlugin(orasName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + tests := []struct { + name string + store *configv1beta1.NamespacedStore + req *reconcile.Request + expectedErr bool + expectedStoreCount int + }{ + { + name: "nonexistent store", + req: &reconcile.Request{ + NamespacedName: types.NamespacedName{Name: "nonexistent"}, + }, + store: &configv1beta1.NamespacedStore{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: storeName, + }, + Spec: configv1beta1.NamespacedStoreSpec{ + Name: orasName, + }, + }, + expectedErr: false, + expectedStoreCount: 0, + }, + { + name: "valid spec", + store: &configv1beta1.NamespacedStore{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: storeName, + }, + Spec: configv1beta1.NamespacedStoreSpec{ + Name: orasName, + Address: dirPath, + }, + }, + expectedErr: false, + expectedStoreCount: 1, + }, + { + name: "invalid parameters", + store: &configv1beta1.NamespacedStore{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: storeName, + }, + Spec: configv1beta1.NamespacedStoreSpec{ + Parameters: runtime.RawExtension{ + Raw: []byte("test"), + }, + }, + }, + expectedErr: true, + expectedStoreCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resetStoreMap() + scheme, _ := test.CreateScheme() + client := fake.NewClientBuilder().WithScheme(scheme) + client.WithObjects(tt.store) + r := &StoreReconciler{ + Scheme: scheme, + Client: client.Build(), + } + var req reconcile.Request + if tt.req != nil { + req = *tt.req + } else { + req = reconcile.Request{ + NamespacedName: test.KeyFor(tt.store), + } + } + + _, 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.NamespacedStores.GetStores(testNamespace)) != tt.expectedStoreCount { + t.Fatalf("Store map expected size %v, actual %v", tt.expectedStoreCount, len(controllers.NamespacedStores.GetStores(testNamespace))) + } + }) + } +} + +func resetStoreMap() { + controllers.NamespacedStores = referrerstores.NewActiveStores() +} + +func getOrasStoreSpec(pluginName, pluginPath string) configv1beta1.NamespacedStoreSpec { + var parametersString = "{\"authProvider\":{\"name\":\"k8Secrets\",\"secrets\":[{\"secretName\":\"myregistrykey\"}]},\"cosignEnabled\":false,\"useHttp\":false}" + var storeParameters = []byte(parametersString) + + return configv1beta1.NamespacedStoreSpec{ + Name: pluginName, + Address: pluginPath, + Parameters: runtime.RawExtension{ + Raw: storeParameters, + }, + } +} + +func getInvalidStoreSpec() configv1beta1.NamespacedStoreSpec { + return configv1beta1.NamespacedStoreSpec{ + Name: "pluginnotfound", + Address: "test/path", + } +} diff --git a/pkg/controllers/utils/store.go b/pkg/controllers/utils/store.go new file mode 100644 index 000000000..2ff0d9f03 --- /dev/null +++ b/pkg/controllers/utils/store.go @@ -0,0 +1,68 @@ +/* +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" + + configv1beta1 "github.com/deislabs/ratify/api/v1beta1" + "github.com/deislabs/ratify/config" + "github.com/deislabs/ratify/pkg/controllers" + rc "github.com/deislabs/ratify/pkg/referrerstore/config" + sf "github.com/deislabs/ratify/pkg/referrerstore/factory" + "github.com/deislabs/ratify/pkg/verifier/types" + "github.com/sirupsen/logrus" +) + +func UpsertStoreMap(version, address, fullname, namespace string, storeConfig rc.StorePluginConfig) error { + // if the default version is not suitable, the plugin configuration should specify the desired version + 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) + } + storeReference, err := sf.CreateStoreFromConfig(storeConfig, version, []string{address}) + + if err != nil || storeReference == nil { + logrus.Error(err, "store factory failed to create store from store config") + return fmt.Errorf("store factory failed to create store from store config, err: %w", err) + } + controllers.NamespacedStores.AddStore(namespace, fullname, storeReference) + logrus.Infof("store '%v' added to store map in namespace: %s", storeReference.Name(), namespace) + + return nil +} + +// Returns a store reference from spec +func CreateStoreConfig(raw []byte, name string, source *configv1beta1.PluginSource) (rc.StorePluginConfig, error) { + storeConfig := rc.StorePluginConfig{} + + if string(raw) != "" { + if err := json.Unmarshal(raw, &storeConfig); err != nil { + logrus.Error(err, "unable to decode store parameters", "Parameters.Raw", raw) + return rc.StorePluginConfig{}, err + } + } + storeConfig[types.Name] = name + if source != nil { + storeConfig[types.Source] = source + } + + return storeConfig, nil +} diff --git a/pkg/controllers/utils/store_test.go b/pkg/controllers/utils/store_test.go new file mode 100644 index 000000000..970cf9744 --- /dev/null +++ b/pkg/controllers/utils/store_test.go @@ -0,0 +1,105 @@ +/* +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" + rc "github.com/deislabs/ratify/pkg/referrerstore/config" + test "github.com/deislabs/ratify/pkg/utils" + "github.com/deislabs/ratify/pkg/verifier/types" +) + +const ( + storeName = "storeName" + testNamespace = "testNamespace" +) + +func TestUpsertStoreMap(t *testing.T) { + dirPath, err := test.CreatePlugin(storeName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + tests := []struct { + name string + address string + storeConfig rc.StorePluginConfig + expectedErr bool + }{ + { + name: "empty config", + storeConfig: rc.StorePluginConfig{}, + expectedErr: true, + }, + { + name: "valid config", + address: dirPath, + storeConfig: rc.StorePluginConfig{ + "name": storeName, + }, + expectedErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := UpsertStoreMap("", tt.address, storeName, testNamespace, tt.storeConfig) + if tt.expectedErr != (err != nil) { + t.Fatalf("expected error: %v, got: %v", tt.expectedErr, err) + } + }) + } +} + +func TestCreateStoreConfig(t *testing.T) { + tests := []struct { + name string + raw []byte + source *configv1beta1.PluginSource + expectedErr bool + expectedStoreName string + }{ + { + name: "invalid raw", + raw: []byte("invalid\n"), + expectedErr: true, + }, + { + name: "valid raw", + raw: []byte("{\"name\": \"storeName\"}"), + source: &configv1beta1.PluginSource{}, + expectedErr: false, + expectedStoreName: storeName, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config, err := CreateStoreConfig(tt.raw, storeName, tt.source) + if tt.expectedErr != (err != nil) { + t.Fatalf("expected error: %v, got: %v", tt.expectedErr, err) + } + if _, ok := config[types.Name]; !ok { + config[types.Name] = "" + } + if config[types.Name] != tt.expectedStoreName { + t.Fatalf("expected store name: %s, got: %s", tt.expectedStoreName, config[types.Name]) + } + }) + } +} diff --git a/pkg/controllers/verifier_controller_test.go b/pkg/controllers/verifier_controller_test.go index 105b67aec..9b5deabc2 100644 --- a/pkg/controllers/verifier_controller_test.go +++ b/pkg/controllers/verifier_controller_test.go @@ -31,6 +31,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) +const sampleName = "sample" + type mockResourceWriter struct { updateFailed bool } diff --git a/pkg/customresources/referrerstores/api.go b/pkg/customresources/referrerstores/api.go index 6b435c7e9..63fcc47ae 100644 --- a/pkg/customresources/referrerstores/api.go +++ b/pkg/customresources/referrerstores/api.go @@ -29,10 +29,4 @@ type ReferrerStoreManager interface { // DeleteStore deletes the policy from the given scope. DeleteStore(scope, storeName string) - - // IsEmpty returns true if there are no stores. - IsEmpty() bool - - // GetStoreCount returns the number of stores in all scopes. - GetStoreCount() int } diff --git a/pkg/customresources/referrerstores/stores.go b/pkg/customresources/referrerstores/stores.go index f60f83b0e..fc1e67f56 100644 --- a/pkg/customresources/referrerstores/stores.go +++ b/pkg/customresources/referrerstores/stores.go @@ -16,12 +16,14 @@ limitations under the License. package referrerstores import ( + "sync" + + "github.com/deislabs/ratify/internal/constants" "github.com/deislabs/ratify/pkg/referrerstore" ) // ActiveStores implements the ReferrerStoreManager interface. type ActiveStores struct { - // TODO: Implement concurrent safety using sync.Map // The structure of the map is as follows: // The first level maps from scope to stores // The second level maps from store name to store @@ -33,57 +35,43 @@ type ActiveStores struct { // } // } // Note: Scope is utilized for organizing and isolating stores. In a Kubernetes (K8s) environment, the scope can be either a namespace or an empty string ("") for cluster-wide stores. - ScopedStores map[string]map[string]referrerstore.ReferrerStore + ScopedStores sync.Map } func NewActiveStores() ReferrerStoreManager { - return &ActiveStores{ - ScopedStores: make(map[string]map[string]referrerstore.ReferrerStore), - } + return &ActiveStores{} } // GetStores fulfills the ReferrerStoreManager interface. // It returns all the stores in the ActiveStores for the given scope. If no stores are found for the given scope, it returns cluster-wide stores. -// TODO: Current implementation fetches stores for all namespaces including cluster-wide ones. Will support actual namespace based stores in future. -func (s *ActiveStores) GetStores(_ string) []referrerstore.ReferrerStore { +func (s *ActiveStores) GetStores(scope string) []referrerstore.ReferrerStore { stores := []referrerstore.ReferrerStore{} - for _, scopedStores := range s.ScopedStores { - for _, store := range scopedStores { + if scopedStore, ok := s.ScopedStores.Load(scope); ok { + for _, store := range scopedStore.(map[string]referrerstore.ReferrerStore) { stores = append(stores, store) } } + if len(stores) == 0 && scope != constants.EmptyNamespace { + if clusterStore, ok := s.ScopedStores.Load(constants.EmptyNamespace); ok { + for _, store := range clusterStore.(map[string]referrerstore.ReferrerStore) { + stores = append(stores, store) + } + } + } return stores } // AddStore fulfills the ReferrerStoreManager interface. // It adds the given store under the given scope. func (s *ActiveStores) AddStore(scope, storeName string, store referrerstore.ReferrerStore) { - if _, ok := s.ScopedStores[scope]; !ok { - s.ScopedStores[scope] = make(map[string]referrerstore.ReferrerStore) - } - s.ScopedStores[scope][storeName] = store + scopedStore, _ := s.ScopedStores.LoadOrStore(scope, make(map[string]referrerstore.ReferrerStore)) + scopedStore.(map[string]referrerstore.ReferrerStore)[storeName] = store } // DeleteStore fulfills the ReferrerStoreManager interface. // It deletes the store with the given name under the given scope. func (s *ActiveStores) DeleteStore(scope, storeName string) { - if stores, ok := s.ScopedStores[scope]; ok { - delete(stores, storeName) - } -} - -// IsEmpty fulfills the ReferrerStoreManager interface. -// It returns true if there are no stores in the ActiveStores. -func (s *ActiveStores) IsEmpty() bool { - return s.GetStoreCount() == 0 -} - -// GetStore fulfills the ReferrerStoreManager interface. -// GetStoreCount returns the total number of stores in the ActiveStores. -func (s *ActiveStores) GetStoreCount() int { - count := 0 - for _, stores := range s.ScopedStores { - count += len(stores) + if scopedStore, ok := s.ScopedStores.Load(scope); ok { + delete(scopedStore.(map[string]referrerstore.ReferrerStore), storeName) } - return count } diff --git a/pkg/customresources/referrerstores/stores_test.go b/pkg/customresources/referrerstores/stores_test.go index 46a375801..b590e8d09 100644 --- a/pkg/customresources/referrerstores/stores_test.go +++ b/pkg/customresources/referrerstores/stores_test.go @@ -74,13 +74,16 @@ func TestStoresOperations(t *testing.T) { stores.AddStore(namespace2, store1.Name(), store1) stores.AddStore(namespace2, store2.Name(), store2) - if stores.GetStoreCount() != 4 { - t.Fatalf("Expected 4 namespaces, got %d", stores.GetStoreCount()) + if len(stores.GetStores(namespace1)) != 2 { + t.Fatalf("Expected 2 stores in namespace %s, got %d", namespace1, len(stores.GetStores(namespace1))) + } + if len(stores.GetStores(namespace2)) != 2 { + t.Fatalf("Expected 2 stores in namespace %s, got %d", namespace2, len(stores.GetStores(namespace2))) } stores.DeleteStore(namespace2, store1.Name()) - if len(stores.GetStores(namespace2)) != 3 { - t.Fatalf("Expected 3 store in namespace %s, got %d", namespace2, len(stores.GetStores(namespace2))) + if len(stores.GetStores(namespace2)) != 1 { + t.Fatalf("Expected 1 store in namespace %s, got %d", namespace2, len(stores.GetStores(namespace2))) } stores.DeleteStore(namespace2, store2.Name()) @@ -97,8 +100,4 @@ func TestStoresOperations(t *testing.T) { if len(stores.GetStores(namespace1)) != 0 { t.Fatalf("Expected 0 stores in namespace %s, got %d", namespace1, len(stores.GetStores(namespace1))) } - - if !stores.IsEmpty() { - t.Fatalf("Expected stores to be empty") - } } diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index 6cafbacd0..5e372b099 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -200,13 +200,20 @@ func StartManager(certRotatorReady chan struct{}, probeAddr string) { setupLog.Error(err, "unable to create controller", "controller", "Verifier") os.Exit(1) } - if err = (&controllers.StoreReconciler{ + if err = (&clusterresource.StoreReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Store") os.Exit(1) } + if err = (&namespaceresource.StoreReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Namespaced Store") + os.Exit(1) + } if err = (&controllers.CertificateStoreReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), diff --git a/test/bats/base-test.bats b/test/bats/base-test.bats index d4a799c23..c3ade58b1 100644 --- a/test/bats/base-test.bats +++ b/test/bats/base-test.bats @@ -190,14 +190,14 @@ RATIFY_NAMESPACE=gatekeeper-system 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/config_v1beta1_store_oras_http.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 sleep 5 - run kubectl replace -f ./config/samples/config_v1beta1_store_oras.yaml + run kubectl replace -f ./config/samples/clustered/store/config_v1beta1_store_oras.yaml sleep 5 wait_for_process 20 10 'kubectl run cosign-demo-keyless --namespace default --image=wabbitnetworks.azurecr.io/test/cosign-image:signed-keyless' @@ -264,7 +264,7 @@ RATIFY_NAMESPACE=gatekeeper-system } # apply a invalid verifier CR, validate status with error - sed 's/:v1/:invalid/' ./config/samples/config_v1beta1_store_dynamic.yaml > invalidstore.yaml + sed 's/:v1/:invalid/' ./config/samples/clustered/store/config_v1beta1_store_dynamic.yaml > invalidstore.yaml run kubectl apply -f invalidstore.yaml assert_success # wait for download of image @@ -438,7 +438,7 @@ RATIFY_NAMESPACE=gatekeeper-system echo "cleaning up" wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo --namespace default --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo1 --namespace default --ignore-not-found=true' - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl replace -f ./config/samples/config_v1beta1_store_oras_http.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl replace -f ./config/samples/clustered/store/config_v1beta1_store_oras_http.yaml' } run kubectl apply -f ./library/default/template.yaml @@ -448,7 +448,7 @@ RATIFY_NAMESPACE=gatekeeper-system assert_success sleep 5 # apply store CRD with K8s secret auth provier enabled - run kubectl apply -f ./config/samples/config_v1beta1_store_oras_k8secretAuth.yaml + run kubectl apply -f ./config/samples/clustered/store/config_v1beta1_store_oras_k8secretAuth.yaml assert_success sleep 5 run kubectl run demo --namespace default --image=registry:5000/notation:signed diff --git a/test/bats/plugin-test.bats b/test/bats/plugin-test.bats index b395325c0..83c926df7 100644 --- a/test/bats/plugin-test.bats +++ b/test/bats/plugin-test.bats @@ -59,6 +59,44 @@ SLEEP_TIME=1 assert_success } +@test "cosign test" { + teardown() { + echo "cleaning up" + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo-key --namespace default --force --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo-unsigned --namespace default --force --ignore-not-found=true' + } + run kubectl apply -f ./library/default/template.yaml + assert_success + sleep 5 + run kubectl apply -f ./library/default/samples/constraint.yaml + assert_success + sleep 5 + + run kubectl run cosign-demo-key --namespace default --image=registry:5000/cosign:signed-key + assert_success + + run kubectl run cosign-demo-unsigned --namespace default --image=registry:5000/cosign:unsigned + assert_failure +} + +@test "cosign keyless test" { + 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/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 + sleep 5 + + run kubectl replace -f ./config/samples/clustered/store/config_v1beta1_store_oras.yaml + sleep 5 + + wait_for_process 20 10 'kubectl run cosign-demo-keyless --namespace default --image=wabbitnetworks.azurecr.io/test/cosign-image:signed-keyless' +} + @test "licensechecker test" { teardown() { echo "cleaning up" @@ -103,7 +141,7 @@ SLEEP_TIME=1 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 # wait for the httpserver cache to be invalidated sleep 15 From 13131f7acca933a6a35fce0237735a320ee35eed Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Mon, 29 Apr 2024 04:54:56 +0000 Subject: [PATCH 09/10] chore: remove deprecated tests --- test/bats/plugin-test.bats | 40 +------------------------------------- 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/test/bats/plugin-test.bats b/test/bats/plugin-test.bats index 83c926df7..b395325c0 100644 --- a/test/bats/plugin-test.bats +++ b/test/bats/plugin-test.bats @@ -59,44 +59,6 @@ SLEEP_TIME=1 assert_success } -@test "cosign test" { - teardown() { - echo "cleaning up" - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo-key --namespace default --force --ignore-not-found=true' - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo-unsigned --namespace default --force --ignore-not-found=true' - } - run kubectl apply -f ./library/default/template.yaml - assert_success - sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml - assert_success - sleep 5 - - run kubectl run cosign-demo-key --namespace default --image=registry:5000/cosign:signed-key - assert_success - - run kubectl run cosign-demo-unsigned --namespace default --image=registry:5000/cosign:unsigned - assert_failure -} - -@test "cosign keyless test" { - 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/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 - sleep 5 - - run kubectl replace -f ./config/samples/clustered/store/config_v1beta1_store_oras.yaml - sleep 5 - - wait_for_process 20 10 'kubectl run cosign-demo-keyless --namespace default --image=wabbitnetworks.azurecr.io/test/cosign-image:signed-keyless' -} - @test "licensechecker test" { teardown() { echo "cleaning up" @@ -141,7 +103,7 @@ SLEEP_TIME=1 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 # wait for the httpserver cache to be invalidated sleep 15 From d06b1a075c8606af3f1e5eae7192a095c292b207 Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Mon, 29 Apr 2024 03:04:31 +0000 Subject: [PATCH 10/10] feat: add NamespacedKMP and switch KMP scope to cluster --- PROJECT | 8 + .../namespacedkeymanagementprovider_types.go | 76 ++++++++ api/unversioned/zz_generated.deepcopy.go | 79 ++++++++ api/v1beta1/keymanagementproviders_types.go | 1 + .../namespacedkeymanagementprovider_types.go | 89 +++++++++ api/v1beta1/zz_generated.conversion.go | 144 ++++++++++++++ api/v1beta1/zz_generated.deepcopy.go | 95 ++++++++++ ...mentprovider-customresourcedefinition.yaml | 2 +- ...mentprovider-customresourcedefinition.yaml | 88 +++++++++ .../akv-key-management-provider.yaml | 2 +- .../inline-key-management-provider.yaml | 6 +- .../ratify-manager-role-clusterrole.yaml | 26 +++ ...fy.deislabs.io_keymanagementproviders.yaml | 2 +- ...s.io_namespacedkeymanagementproviders.yaml | 88 +++++++++ config/crd/kustomization.yaml | 3 + ...n_in_namespacedkeymanagementproviders.yaml | 7 + ...k_in_namespacedkeymanagementproviders.yaml | 16 ++ ...acedkeymanagementprovider_editor_role.yaml | 31 +++ ...acedkeymanagementprovider_viewer_role.yaml | 27 +++ config/rbac/role.yaml | 26 +++ ...fig_v1beta1_keymanagementprovider_akv.yaml | 0 ..._v1beta1_keymanagementprovider_inline.yaml | 0 ...fig_v1beta1_keymanagementprovider_akv.yaml | 13 ++ ..._v1beta1_keymanagementprovider_inline.yaml | 29 +++ .../keymanagementprovider_controller.go | 50 +---- .../keymanagementprovider_controller_test.go | 101 +--------- .../keymanagementprovider_controller.go | 172 +++++++++++++++++ .../keymanagementprovider_controller_test.go | 178 ++++++++++++++++++ pkg/controllers/utils/kmp.go | 57 ++++++ pkg/controllers/utils/kmp_test.go | 101 ++++++++++ pkg/manager/manager.go | 11 +- test/bats/base-test.bats | 22 +-- test/bats/high-availability.bats | 2 +- ..._v1beta1_keymanagementprovider_inline.yaml | 2 +- 34 files changed, 1396 insertions(+), 158 deletions(-) create mode 100644 api/unversioned/namespacedkeymanagementprovider_types.go create mode 100644 api/v1beta1/namespacedkeymanagementprovider_types.go create mode 100644 charts/ratify/crds/namespacedkeymanagementprovider-customresourcedefinition.yaml create mode 100644 config/crd/bases/config.ratify.deislabs.io_namespacedkeymanagementproviders.yaml create mode 100644 config/crd/patches/cainjection_in_namespacedkeymanagementproviders.yaml create mode 100644 config/crd/patches/webhook_in_namespacedkeymanagementproviders.yaml create mode 100644 config/rbac/namespacedkeymanagementprovider_editor_role.yaml create mode 100644 config/rbac/namespacedkeymanagementprovider_viewer_role.yaml rename config/samples/{ => clustered/kmp}/config_v1beta1_keymanagementprovider_akv.yaml (100%) rename config/samples/{ => clustered/kmp}/config_v1beta1_keymanagementprovider_inline.yaml (100%) create mode 100644 config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv.yaml create mode 100644 config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_inline.yaml rename pkg/controllers/{ => clusterresource}/keymanagementprovider_controller.go (78%) rename pkg/controllers/{ => clusterresource}/keymanagementprovider_controller_test.go (59%) create mode 100644 pkg/controllers/namespaceresource/keymanagementprovider_controller.go create mode 100644 pkg/controllers/namespaceresource/keymanagementprovider_controller_test.go create mode 100644 pkg/controllers/utils/kmp.go create mode 100644 pkg/controllers/utils/kmp_test.go diff --git a/PROJECT b/PROJECT index 418f44674..fca492fc9 100644 --- a/PROJECT +++ b/PROJECT @@ -96,4 +96,12 @@ resources: kind: NamespacedStore path: github.com/deislabs/ratify/api/v1beta1 version: v1beta1 +- api: + crdVersion: v1 + namespaced: true + domain: ratify.deislabs.io + group: config + kind: NamespacedKeyManagementProvider + path: github.com/deislabs/ratify/api/v1beta1 + version: v1beta1 version: "3" diff --git a/api/unversioned/namespacedkeymanagementprovider_types.go b/api/unversioned/namespacedkeymanagementprovider_types.go new file mode 100644 index 000000000..70dcf557c --- /dev/null +++ b/api/unversioned/namespacedkeymanagementprovider_types.go @@ -0,0 +1,76 @@ +/* +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" +) + +// 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. + +// NamespacedKeyManagementProviderSpec defines the desired state of NamespacedKeyManagementProvider +type NamespacedKeyManagementProviderSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Name of the key management provider + Type string `json:"type,omitempty"` + + // +kubebuilder:pruning:PreserveUnknownFields + // Parameters of the key management provider + Parameters runtime.RawExtension `json:"parameters,omitempty"` +} + +// NamespacedKeyManagementProviderStatus defines the observed state of NamespacedKeyManagementProvider +type NamespacedKeyManagementProviderStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Is successful in loading certificate/key files + 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"` + // The time stamp of last successful certificate/key fetch operation. If operation failed, last fetched time shows the time of error + // +optional + LastFetchedTime *metav1.Time `json:"lastfetchedtime,omitempty"` + // provider specific properties of the each individual certificate/key + // +optional + Properties runtime.RawExtension `json:"properties,omitempty"` +} + +// NamespacedKeyManagementProvider is the Schema for the namespacedkeymanagementproviders API +type NamespacedKeyManagementProvider struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NamespacedKeyManagementProviderSpec `json:"spec,omitempty"` + Status NamespacedKeyManagementProviderStatus `json:"status,omitempty"` +} + +// NamespacedKeyManagementProviderList contains a list of NamespacedKeyManagementProvider +type NamespacedKeyManagementProviderList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NamespacedKeyManagementProvider `json:"items"` +} diff --git a/api/unversioned/zz_generated.deepcopy.go b/api/unversioned/zz_generated.deepcopy.go index ccf9bbeee..63d22ac1f 100644 --- a/api/unversioned/zz_generated.deepcopy.go +++ b/api/unversioned/zz_generated.deepcopy.go @@ -181,6 +181,85 @@ 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 *NamespacedKeyManagementProvider) DeepCopyInto(out *NamespacedKeyManagementProvider) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyManagementProvider. +func (in *NamespacedKeyManagementProvider) DeepCopy() *NamespacedKeyManagementProvider { + if in == nil { + return nil + } + out := new(NamespacedKeyManagementProvider) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedKeyManagementProviderList) DeepCopyInto(out *NamespacedKeyManagementProviderList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NamespacedKeyManagementProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyManagementProviderList. +func (in *NamespacedKeyManagementProviderList) DeepCopy() *NamespacedKeyManagementProviderList { + if in == nil { + return nil + } + out := new(NamespacedKeyManagementProviderList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedKeyManagementProviderSpec) DeepCopyInto(out *NamespacedKeyManagementProviderSpec) { + *out = *in + in.Parameters.DeepCopyInto(&out.Parameters) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyManagementProviderSpec. +func (in *NamespacedKeyManagementProviderSpec) DeepCopy() *NamespacedKeyManagementProviderSpec { + if in == nil { + return nil + } + out := new(NamespacedKeyManagementProviderSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedKeyManagementProviderStatus) DeepCopyInto(out *NamespacedKeyManagementProviderStatus) { + *out = *in + if in.LastFetchedTime != nil { + in, out := &in.LastFetchedTime, &out.LastFetchedTime + *out = (*in).DeepCopy() + } + in.Properties.DeepCopyInto(&out.Properties) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyManagementProviderStatus. +func (in *NamespacedKeyManagementProviderStatus) DeepCopy() *NamespacedKeyManagementProviderStatus { + if in == nil { + return nil + } + out := new(NamespacedKeyManagementProviderStatus) + in.DeepCopyInto(out) + 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 diff --git a/api/v1beta1/keymanagementproviders_types.go b/api/v1beta1/keymanagementproviders_types.go index d8f10d53c..d957f382e 100644 --- a/api/v1beta1/keymanagementproviders_types.go +++ b/api/v1beta1/keymanagementproviders_types.go @@ -58,6 +58,7 @@ type KeyManagementProviderStatus struct { } // +kubebuilder:object:root=true +// +kubebuilder:resource:scope="Cluster" // +kubebuilder:subresource:status // +kubebuilder:storageversion // +kubebuilder:printcolumn:name="IsSuccess",type=boolean,JSONPath=`.status.issuccess` diff --git a/api/v1beta1/namespacedkeymanagementprovider_types.go b/api/v1beta1/namespacedkeymanagementprovider_types.go new file mode 100644 index 000000000..f4d3e5839 --- /dev/null +++ b/api/v1beta1/namespacedkeymanagementprovider_types.go @@ -0,0 +1,89 @@ +/* +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. + +// NamespacedKeyManagementProviderSpec defines the desired state of NamespacedKeyManagementProvider +type NamespacedKeyManagementProviderSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Name of the key management provider + Type string `json:"type,omitempty"` + + // +kubebuilder:pruning:PreserveUnknownFields + // Parameters of the key management provider + Parameters runtime.RawExtension `json:"parameters,omitempty"` +} + +// NamespacedKeyManagementProviderStatus defines the observed state of NamespacedKeyManagementProvider +type NamespacedKeyManagementProviderStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Is successful in loading certificate/key files + 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"` + // The time stamp of last successful certificate/key fetch operation. If operation failed, last fetched time shows the time of error + // +optional + LastFetchedTime *metav1.Time `json:"lastfetchedtime,omitempty"` + // +kubebuilder:pruning:PreserveUnknownFields + // provider specific properties of the each individual certificate/key + // +optional + Properties runtime.RawExtension `json:"properties,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` +// +kubebuilder:printcolumn:name="LastFetchedTime",type=date,JSONPath=`.status.lastfetchedtime` +// NamespacedKeyManagementProvider is the Schema for the namespacedkeymanagementproviders API +type NamespacedKeyManagementProvider struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NamespacedKeyManagementProviderSpec `json:"spec,omitempty"` + Status NamespacedKeyManagementProviderStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:storageversion +// NamespacedKeyManagementProviderList contains a list of NamespacedKeyManagementProvider +type NamespacedKeyManagementProviderList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NamespacedKeyManagementProvider `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NamespacedKeyManagementProvider{}, &NamespacedKeyManagementProviderList{}) +} diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index b5d5b9c12..9a16b46e3 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -116,6 +116,46 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*NamespacedKeyManagementProvider)(nil), (*unversioned.NamespacedKeyManagementProvider)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedKeyManagementProvider_To_unversioned_NamespacedKeyManagementProvider(a.(*NamespacedKeyManagementProvider), b.(*unversioned.NamespacedKeyManagementProvider), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedKeyManagementProvider)(nil), (*NamespacedKeyManagementProvider)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedKeyManagementProvider_To_v1beta1_NamespacedKeyManagementProvider(a.(*unversioned.NamespacedKeyManagementProvider), b.(*NamespacedKeyManagementProvider), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedKeyManagementProviderList)(nil), (*unversioned.NamespacedKeyManagementProviderList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedKeyManagementProviderList_To_unversioned_NamespacedKeyManagementProviderList(a.(*NamespacedKeyManagementProviderList), b.(*unversioned.NamespacedKeyManagementProviderList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedKeyManagementProviderList)(nil), (*NamespacedKeyManagementProviderList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedKeyManagementProviderList_To_v1beta1_NamespacedKeyManagementProviderList(a.(*unversioned.NamespacedKeyManagementProviderList), b.(*NamespacedKeyManagementProviderList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedKeyManagementProviderSpec)(nil), (*unversioned.NamespacedKeyManagementProviderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedKeyManagementProviderSpec_To_unversioned_NamespacedKeyManagementProviderSpec(a.(*NamespacedKeyManagementProviderSpec), b.(*unversioned.NamespacedKeyManagementProviderSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedKeyManagementProviderSpec)(nil), (*NamespacedKeyManagementProviderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedKeyManagementProviderSpec_To_v1beta1_NamespacedKeyManagementProviderSpec(a.(*unversioned.NamespacedKeyManagementProviderSpec), b.(*NamespacedKeyManagementProviderSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedKeyManagementProviderStatus)(nil), (*unversioned.NamespacedKeyManagementProviderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedKeyManagementProviderStatus_To_unversioned_NamespacedKeyManagementProviderStatus(a.(*NamespacedKeyManagementProviderStatus), b.(*unversioned.NamespacedKeyManagementProviderStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedKeyManagementProviderStatus)(nil), (*NamespacedKeyManagementProviderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedKeyManagementProviderStatus_To_v1beta1_NamespacedKeyManagementProviderStatus(a.(*unversioned.NamespacedKeyManagementProviderStatus), b.(*NamespacedKeyManagementProviderStatus), 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 { @@ -537,6 +577,110 @@ func Convert_unversioned_KeyManagementProviderStatus_To_v1beta1_KeyManagementPro return autoConvert_unversioned_KeyManagementProviderStatus_To_v1beta1_KeyManagementProviderStatus(in, out, s) } +func autoConvert_v1beta1_NamespacedKeyManagementProvider_To_unversioned_NamespacedKeyManagementProvider(in *NamespacedKeyManagementProvider, out *unversioned.NamespacedKeyManagementProvider, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_NamespacedKeyManagementProviderSpec_To_unversioned_NamespacedKeyManagementProviderSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_NamespacedKeyManagementProviderStatus_To_unversioned_NamespacedKeyManagementProviderStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_NamespacedKeyManagementProvider_To_unversioned_NamespacedKeyManagementProvider is an autogenerated conversion function. +func Convert_v1beta1_NamespacedKeyManagementProvider_To_unversioned_NamespacedKeyManagementProvider(in *NamespacedKeyManagementProvider, out *unversioned.NamespacedKeyManagementProvider, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedKeyManagementProvider_To_unversioned_NamespacedKeyManagementProvider(in, out, s) +} + +func autoConvert_unversioned_NamespacedKeyManagementProvider_To_v1beta1_NamespacedKeyManagementProvider(in *unversioned.NamespacedKeyManagementProvider, out *NamespacedKeyManagementProvider, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_unversioned_NamespacedKeyManagementProviderSpec_To_v1beta1_NamespacedKeyManagementProviderSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_unversioned_NamespacedKeyManagementProviderStatus_To_v1beta1_NamespacedKeyManagementProviderStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_unversioned_NamespacedKeyManagementProvider_To_v1beta1_NamespacedKeyManagementProvider is an autogenerated conversion function. +func Convert_unversioned_NamespacedKeyManagementProvider_To_v1beta1_NamespacedKeyManagementProvider(in *unversioned.NamespacedKeyManagementProvider, out *NamespacedKeyManagementProvider, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedKeyManagementProvider_To_v1beta1_NamespacedKeyManagementProvider(in, out, s) +} + +func autoConvert_v1beta1_NamespacedKeyManagementProviderList_To_unversioned_NamespacedKeyManagementProviderList(in *NamespacedKeyManagementProviderList, out *unversioned.NamespacedKeyManagementProviderList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]unversioned.NamespacedKeyManagementProvider)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_NamespacedKeyManagementProviderList_To_unversioned_NamespacedKeyManagementProviderList is an autogenerated conversion function. +func Convert_v1beta1_NamespacedKeyManagementProviderList_To_unversioned_NamespacedKeyManagementProviderList(in *NamespacedKeyManagementProviderList, out *unversioned.NamespacedKeyManagementProviderList, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedKeyManagementProviderList_To_unversioned_NamespacedKeyManagementProviderList(in, out, s) +} + +func autoConvert_unversioned_NamespacedKeyManagementProviderList_To_v1beta1_NamespacedKeyManagementProviderList(in *unversioned.NamespacedKeyManagementProviderList, out *NamespacedKeyManagementProviderList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]NamespacedKeyManagementProvider)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_unversioned_NamespacedKeyManagementProviderList_To_v1beta1_NamespacedKeyManagementProviderList is an autogenerated conversion function. +func Convert_unversioned_NamespacedKeyManagementProviderList_To_v1beta1_NamespacedKeyManagementProviderList(in *unversioned.NamespacedKeyManagementProviderList, out *NamespacedKeyManagementProviderList, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedKeyManagementProviderList_To_v1beta1_NamespacedKeyManagementProviderList(in, out, s) +} + +func autoConvert_v1beta1_NamespacedKeyManagementProviderSpec_To_unversioned_NamespacedKeyManagementProviderSpec(in *NamespacedKeyManagementProviderSpec, out *unversioned.NamespacedKeyManagementProviderSpec, s conversion.Scope) error { + out.Type = in.Type + out.Parameters = in.Parameters + return nil +} + +// Convert_v1beta1_NamespacedKeyManagementProviderSpec_To_unversioned_NamespacedKeyManagementProviderSpec is an autogenerated conversion function. +func Convert_v1beta1_NamespacedKeyManagementProviderSpec_To_unversioned_NamespacedKeyManagementProviderSpec(in *NamespacedKeyManagementProviderSpec, out *unversioned.NamespacedKeyManagementProviderSpec, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedKeyManagementProviderSpec_To_unversioned_NamespacedKeyManagementProviderSpec(in, out, s) +} + +func autoConvert_unversioned_NamespacedKeyManagementProviderSpec_To_v1beta1_NamespacedKeyManagementProviderSpec(in *unversioned.NamespacedKeyManagementProviderSpec, out *NamespacedKeyManagementProviderSpec, s conversion.Scope) error { + out.Type = in.Type + out.Parameters = in.Parameters + return nil +} + +// Convert_unversioned_NamespacedKeyManagementProviderSpec_To_v1beta1_NamespacedKeyManagementProviderSpec is an autogenerated conversion function. +func Convert_unversioned_NamespacedKeyManagementProviderSpec_To_v1beta1_NamespacedKeyManagementProviderSpec(in *unversioned.NamespacedKeyManagementProviderSpec, out *NamespacedKeyManagementProviderSpec, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedKeyManagementProviderSpec_To_v1beta1_NamespacedKeyManagementProviderSpec(in, out, s) +} + +func autoConvert_v1beta1_NamespacedKeyManagementProviderStatus_To_unversioned_NamespacedKeyManagementProviderStatus(in *NamespacedKeyManagementProviderStatus, out *unversioned.NamespacedKeyManagementProviderStatus, 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_NamespacedKeyManagementProviderStatus_To_unversioned_NamespacedKeyManagementProviderStatus is an autogenerated conversion function. +func Convert_v1beta1_NamespacedKeyManagementProviderStatus_To_unversioned_NamespacedKeyManagementProviderStatus(in *NamespacedKeyManagementProviderStatus, out *unversioned.NamespacedKeyManagementProviderStatus, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedKeyManagementProviderStatus_To_unversioned_NamespacedKeyManagementProviderStatus(in, out, s) +} + +func autoConvert_unversioned_NamespacedKeyManagementProviderStatus_To_v1beta1_NamespacedKeyManagementProviderStatus(in *unversioned.NamespacedKeyManagementProviderStatus, out *NamespacedKeyManagementProviderStatus, 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_NamespacedKeyManagementProviderStatus_To_v1beta1_NamespacedKeyManagementProviderStatus is an autogenerated conversion function. +func Convert_unversioned_NamespacedKeyManagementProviderStatus_To_v1beta1_NamespacedKeyManagementProviderStatus(in *unversioned.NamespacedKeyManagementProviderStatus, out *NamespacedKeyManagementProviderStatus, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedKeyManagementProviderStatus_To_v1beta1_NamespacedKeyManagementProviderStatus(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 { diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 49342bd52..05aac89d1 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -215,6 +215,101 @@ 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 *NamespacedKeyManagementProvider) DeepCopyInto(out *NamespacedKeyManagementProvider) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyManagementProvider. +func (in *NamespacedKeyManagementProvider) DeepCopy() *NamespacedKeyManagementProvider { + if in == nil { + return nil + } + out := new(NamespacedKeyManagementProvider) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedKeyManagementProvider) 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 *NamespacedKeyManagementProviderList) DeepCopyInto(out *NamespacedKeyManagementProviderList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NamespacedKeyManagementProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyManagementProviderList. +func (in *NamespacedKeyManagementProviderList) DeepCopy() *NamespacedKeyManagementProviderList { + if in == nil { + return nil + } + out := new(NamespacedKeyManagementProviderList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedKeyManagementProviderList) 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 *NamespacedKeyManagementProviderSpec) DeepCopyInto(out *NamespacedKeyManagementProviderSpec) { + *out = *in + in.Parameters.DeepCopyInto(&out.Parameters) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyManagementProviderSpec. +func (in *NamespacedKeyManagementProviderSpec) DeepCopy() *NamespacedKeyManagementProviderSpec { + if in == nil { + return nil + } + out := new(NamespacedKeyManagementProviderSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedKeyManagementProviderStatus) DeepCopyInto(out *NamespacedKeyManagementProviderStatus) { + *out = *in + if in.LastFetchedTime != nil { + in, out := &in.LastFetchedTime, &out.LastFetchedTime + *out = (*in).DeepCopy() + } + in.Properties.DeepCopyInto(&out.Properties) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyManagementProviderStatus. +func (in *NamespacedKeyManagementProviderStatus) DeepCopy() *NamespacedKeyManagementProviderStatus { + if in == nil { + return nil + } + out := new(NamespacedKeyManagementProviderStatus) + in.DeepCopyInto(out) + 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 diff --git a/charts/ratify/crds/keymanagementprovider-customresourcedefinition.yaml b/charts/ratify/crds/keymanagementprovider-customresourcedefinition.yaml index 29ddb906d..d8110ba31 100644 --- a/charts/ratify/crds/keymanagementprovider-customresourcedefinition.yaml +++ b/charts/ratify/crds/keymanagementprovider-customresourcedefinition.yaml @@ -13,7 +13,7 @@ spec: listKind: KeyManagementProviderList plural: keymanagementproviders singular: keymanagementprovider - scope: Namespaced + scope: Cluster versions: - additionalPrinterColumns: - jsonPath: .status.issuccess diff --git a/charts/ratify/crds/namespacedkeymanagementprovider-customresourcedefinition.yaml b/charts/ratify/crds/namespacedkeymanagementprovider-customresourcedefinition.yaml new file mode 100644 index 000000000..f7b953937 --- /dev/null +++ b/charts/ratify/crds/namespacedkeymanagementprovider-customresourcedefinition.yaml @@ -0,0 +1,88 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: namespacedkeymanagementproviders.config.ratify.deislabs.io +spec: + group: config.ratify.deislabs.io + names: + kind: NamespacedKeyManagementProvider + listKind: NamespacedKeyManagementProviderList + plural: namespacedkeymanagementproviders + singular: namespacedkeymanagementprovider + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + - jsonPath: .status.lastfetchedtime + name: LastFetchedTime + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: NamespacedKeyManagementProvider is the Schema for the namespacedkeymanagementproviders + 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: NamespacedKeyManagementProviderSpec defines the desired state + of NamespacedKeyManagementProvider + properties: + parameters: + description: Parameters of the key management provider + type: object + x-kubernetes-preserve-unknown-fields: true + type: + description: Name of the key management provider + type: string + type: object + status: + description: NamespacedKeyManagementProviderStatus defines the observed + state of NamespacedKeyManagementProvider + 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 loading certificate/key files + type: boolean + lastfetchedtime: + description: The time stamp of last successful certificate/key fetch + operation. If operation failed, last fetched time shows the time + of error + format: date-time + type: string + properties: + description: provider specific properties of the each individual certificate/key + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - issuccess + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/ratify/templates/akv-key-management-provider.yaml b/charts/ratify/templates/akv-key-management-provider.yaml index 831496792..3700b6ad8 100644 --- a/charts/ratify/templates/akv-key-management-provider.yaml +++ b/charts/ratify/templates/akv-key-management-provider.yaml @@ -1,6 +1,6 @@ {{- if or .Values.azurekeyvault.enabled .Values.akvCertConfig.enabled }} apiVersion: config.ratify.deislabs.io/v1beta1 -kind: KeyManagementProvider +kind: NamespacedKeyManagementProvider metadata: name: kmprovider-akv annotations: diff --git a/charts/ratify/templates/inline-key-management-provider.yaml b/charts/ratify/templates/inline-key-management-provider.yaml index 665ea0b75..407bacdc2 100644 --- a/charts/ratify/templates/inline-key-management-provider.yaml +++ b/charts/ratify/templates/inline-key-management-provider.yaml @@ -2,7 +2,7 @@ --- {{- if .Values.notationCert }} apiVersion: config.ratify.deislabs.io/v1beta1 -kind: KeyManagementProvider +kind: NamespacedKeyManagementProvider metadata: name: {{$fullname}}-notation-inline-cert annotations: @@ -17,7 +17,7 @@ spec: --- {{- range $i, $cert := .Values.notationCerts }} apiVersion: config.ratify.deislabs.io/v1beta1 -kind: KeyManagementProvider +kind: NamespacedKeyManagementProvider metadata: name: {{$fullname}}-notation-inline-cert-{{$i}} annotations: @@ -32,7 +32,7 @@ spec: --- {{- range $i, $key := .Values.cosignKeys }} apiVersion: config.ratify.deislabs.io/v1beta1 -kind: KeyManagementProvider +kind: NamespacedKeyManagementProvider metadata: name: {{$fullname}}-cosign-inline-key-{{$i}} annotations: diff --git a/charts/ratify/templates/ratify-manager-role-clusterrole.yaml b/charts/ratify/templates/ratify-manager-role-clusterrole.yaml index 14bf0344f..2653f15f1 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: + - namespacedkeymanagementproviders + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders/finalizers + verbs: + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders/status + verbs: + - get + - patch + - update - apiGroups: - config.ratify.deislabs.io resources: diff --git a/config/crd/bases/config.ratify.deislabs.io_keymanagementproviders.yaml b/config/crd/bases/config.ratify.deislabs.io_keymanagementproviders.yaml index 29ddb906d..d8110ba31 100644 --- a/config/crd/bases/config.ratify.deislabs.io_keymanagementproviders.yaml +++ b/config/crd/bases/config.ratify.deislabs.io_keymanagementproviders.yaml @@ -13,7 +13,7 @@ spec: listKind: KeyManagementProviderList plural: keymanagementproviders singular: keymanagementprovider - scope: Namespaced + scope: Cluster versions: - additionalPrinterColumns: - jsonPath: .status.issuccess diff --git a/config/crd/bases/config.ratify.deislabs.io_namespacedkeymanagementproviders.yaml b/config/crd/bases/config.ratify.deislabs.io_namespacedkeymanagementproviders.yaml new file mode 100644 index 000000000..f7b953937 --- /dev/null +++ b/config/crd/bases/config.ratify.deislabs.io_namespacedkeymanagementproviders.yaml @@ -0,0 +1,88 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: namespacedkeymanagementproviders.config.ratify.deislabs.io +spec: + group: config.ratify.deislabs.io + names: + kind: NamespacedKeyManagementProvider + listKind: NamespacedKeyManagementProviderList + plural: namespacedkeymanagementproviders + singular: namespacedkeymanagementprovider + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + - jsonPath: .status.lastfetchedtime + name: LastFetchedTime + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: NamespacedKeyManagementProvider is the Schema for the namespacedkeymanagementproviders + 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: NamespacedKeyManagementProviderSpec defines the desired state + of NamespacedKeyManagementProvider + properties: + parameters: + description: Parameters of the key management provider + type: object + x-kubernetes-preserve-unknown-fields: true + type: + description: Name of the key management provider + type: string + type: object + status: + description: NamespacedKeyManagementProviderStatus defines the observed + state of NamespacedKeyManagementProvider + 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 loading certificate/key files + type: boolean + lastfetchedtime: + description: The time stamp of last successful certificate/key fetch + operation. If operation failed, last fetched time shows the time + of error + format: date-time + type: string + properties: + description: provider specific properties of the each individual certificate/key + type: object + x-kubernetes-preserve-unknown-fields: true + 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 4e4cf1257..112b1c6c5 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -9,6 +9,7 @@ resources: - bases/config.ratify.deislabs.io_keymanagementproviders.yaml - bases/config.ratify.deislabs.io_namespacedpolicies.yaml - bases/config.ratify.deislabs.io_namespacedstores.yaml + - bases/config.ratify.deislabs.io_namespacedkeymanagementproviders.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -21,6 +22,7 @@ patchesStrategicMerge: #- patches/webhook_in_keymanagementproviders.yaml #- patches/webhook_in_namespacedpolicies.yaml #- patches/webhook_in_namespacedstores.yaml + #- patches/webhook_in_namespacedkeymanagementproviders.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -32,6 +34,7 @@ patchesStrategicMerge: #- patches/cainjection_in_keymanagementproviders.yaml #- patches/cainjection_in_namespacedpolicies.yaml #- patches/cainjection_in_namespacedstores.yaml + #- patches/cainjection_in_namespacedkeymanagementproviders.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_namespacedkeymanagementproviders.yaml b/config/crd/patches/cainjection_in_namespacedkeymanagementproviders.yaml new file mode 100644 index 000000000..d99842389 --- /dev/null +++ b/config/crd/patches/cainjection_in_namespacedkeymanagementproviders.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: namespacedkeymanagementproviders.config.ratify.deislabs.io diff --git a/config/crd/patches/webhook_in_namespacedkeymanagementproviders.yaml b/config/crd/patches/webhook_in_namespacedkeymanagementproviders.yaml new file mode 100644 index 000000000..2d1f077c1 --- /dev/null +++ b/config/crd/patches/webhook_in_namespacedkeymanagementproviders.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: namespacedkeymanagementproviders.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/namespacedkeymanagementprovider_editor_role.yaml b/config/rbac/namespacedkeymanagementprovider_editor_role.yaml new file mode 100644 index 000000000..671f37599 --- /dev/null +++ b/config/rbac/namespacedkeymanagementprovider_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit namespacedkeymanagementproviders. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: namespacedkeymanagementprovider-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: namespacedkeymanagementprovider-editor-role +rules: +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders/status + verbs: + - get diff --git a/config/rbac/namespacedkeymanagementprovider_viewer_role.yaml b/config/rbac/namespacedkeymanagementprovider_viewer_role.yaml new file mode 100644 index 000000000..f9d6a8418 --- /dev/null +++ b/config/rbac/namespacedkeymanagementprovider_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view namespacedkeymanagementproviders. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: namespacedkeymanagementprovider-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: namespacedkeymanagementprovider-viewer-role +rules: +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders + verbs: + - get + - list + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 97cdeaa6b..d7521fbb3 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -186,4 +186,30 @@ rules: verbs: - get - patch + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders/finalizers + verbs: + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders/status + verbs: + - get + - patch - update \ No newline at end of file diff --git a/config/samples/config_v1beta1_keymanagementprovider_akv.yaml b/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv.yaml similarity index 100% rename from config/samples/config_v1beta1_keymanagementprovider_akv.yaml rename to config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv.yaml diff --git a/config/samples/config_v1beta1_keymanagementprovider_inline.yaml b/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_inline.yaml similarity index 100% rename from config/samples/config_v1beta1_keymanagementprovider_inline.yaml rename to config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_inline.yaml diff --git a/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv.yaml b/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv.yaml new file mode 100644 index 000000000..e8971e1bc --- /dev/null +++ b/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv.yaml @@ -0,0 +1,13 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedKeyManagementProvider +metadata: + name: keymanagementprovider-inline +spec: + type: azurekeyvault + parameters: + vaultURI: https://yourkeyvault.vault.azure.net/ + certificates: + - name: yourCertName + version: yourCertVersion # Optional, fetch latest version if empty + tenantID: + clientID: diff --git a/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_inline.yaml b/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_inline.yaml new file mode 100644 index 000000000..1fac2d562 --- /dev/null +++ b/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_inline.yaml @@ -0,0 +1,29 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedKeyManagementProvider +metadata: + name: keymanagementprovider-inline +spec: + type: inline + parameters: + contentType: certificate + value: | + -----BEGIN CERTIFICATE----- + MIIDWDCCAkCgAwIBAgIBUTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJVUzEL + MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEb + MBkGA1UEAxMSd2FiYml0LW5ldHdvcmtzLmlvMCAXDTIyMTIwMjA4MDg0NFoYDzIx + MjIxMjAzMDgwODQ0WjBaMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNV + BAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEbMBkGA1UEAxMSd2FiYml0LW5l + dHdvcmtzLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnoskJWB0 + ZsYcfbTvCYQMLqWaB/yN3Jf7Ryxvndrij83fWEQPBQJi8Mk8SpNqm2x9uP3gsQDc + L/73a0p6/D+hza2jQQVhebe/oB0LJtUoD5LXlJ83UQdZETLMYAzeBNcBR4kMecrY + CnE6yjHeiEWdAH+U7Mt39zJh+9lGIcbk0aUE5UOp8o3t5RWFDcl9hQ7QOXROwmpO + thLUIiY/bcPpsg/2nH1nzFjqiBef3sgopFCTgtJ7qF8B83Xy/+hJ5vD29xsbSwuB + 3iLE7qLxu2NxdIa4oL0Y2QKMh/getjI0xnvwAmPkFiFbzC7LFdDfd6+gA5GpUXxL + u6UmwucAgiljGQIDAQABoycwJTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI + KwYBBQUHAwMwDQYJKoZIhvcNAQELBQADggEBAFvRW/mGjnnMNFKJc/e3o/+yiJor + dcrq/1UzyD7eNmOaASXz8rrrFT/6/TBXExPuB2OIf9OgRJFfPGLxmzCwVgaWQbK0 + VfTN4MQzRrSwPmNYsBAAwLxXbarYlMbm4DEmdJGyVikq08T2dZI51GC/YXEwzlnv + ldN0dBflb/FKkY5rAp0JgpHLGKeStxFvB62noBjWfrm7ShCf9gkn1CjmgvP/sYK0 + pJgA1FHPd6EeB6yRBpLV4EJgQYUJoOpbHz+us62jKj5fAXsX052LPmk9ArmP0uJ1 + CJLNdj+aShCs4paSWOObDmIyXHwCx3MxCvYsFk/Wsnwura6jGC+cNsjzSx4= + -----END CERTIFICATE----- diff --git a/pkg/controllers/keymanagementprovider_controller.go b/pkg/controllers/clusterresource/keymanagementprovider_controller.go similarity index 78% rename from pkg/controllers/keymanagementprovider_controller.go rename to pkg/controllers/clusterresource/keymanagementprovider_controller.go index 13826e09f..f8888ed65 100644 --- a/pkg/controllers/keymanagementprovider_controller.go +++ b/pkg/controllers/clusterresource/keymanagementprovider_controller.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package controllers +package clusterresource import ( "context" @@ -22,6 +22,7 @@ import ( "fmt" "maps" + "github.com/deislabs/ratify/internal/constants" _ "github.com/deislabs/ratify/pkg/keymanagementprovider/azurekeyvault" // register azure key vault key management provider _ "github.com/deislabs/ratify/pkg/keymanagementprovider/inline" // register inline key management provider apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -32,11 +33,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" configv1beta1 "github.com/deislabs/ratify/api/v1beta1" - c "github.com/deislabs/ratify/config" + cutils "github.com/deislabs/ratify/pkg/controllers/utils" kmp "github.com/deislabs/ratify/pkg/keymanagementprovider" - "github.com/deislabs/ratify/pkg/keymanagementprovider/config" - "github.com/deislabs/ratify/pkg/keymanagementprovider/factory" - "github.com/deislabs/ratify/pkg/keymanagementprovider/types" "github.com/sirupsen/logrus" ) @@ -52,10 +50,10 @@ type KeyManagementProviderReconciler struct { func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := logrus.WithContext(ctx) - var resource = req.NamespacedName.String() + var resource = req.Name var keyManagementProvider configv1beta1.KeyManagementProvider - logger.Infof("reconciling key management provider '%v'", resource) + logger.Infof("reconciling cluster key management provider '%v'", resource) if err := r.Get(ctx, req.NamespacedName, &keyManagementProvider); err != nil { if apierrors.IsNotFound(err) { @@ -85,7 +83,7 @@ func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctr logger.Warn("Certificate Store already exists. Key management provider and certificate store should not be configured together. Please migrate to key management provider and delete certificate store.") } - provider, err := specToKeyManagementProvider(keyManagementProvider.Spec) + provider, err := cutils.SpecToKeyManagementProvider(keyManagementProvider.Spec.Parameters.Raw, keyManagementProvider.Spec.Type) if err != nil { writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil) return ctrl.Result{}, err @@ -130,38 +128,6 @@ func (r *KeyManagementProviderReconciler) SetupWithManager(mgr ctrl.Manager) err Complete(r) } -// specToKeyManagementProvider creates KeyManagementProviderProvider from KeyManagementProviderSpec config -func specToKeyManagementProvider(spec configv1beta1.KeyManagementProviderSpec) (kmp.KeyManagementProvider, error) { - kmProviderConfig, err := rawToKeyManagementProviderConfig(spec.Parameters.Raw, spec.Type) - if err != nil { - return nil, fmt.Errorf("failed to parse key management provider config: %w", err) - } - - // TODO: add Version and Address to KeyManagementProviderSpec - keyManagementProviderProvider, err := factory.CreateKeyManagementProviderFromConfig(kmProviderConfig, "0.1.0", c.GetDefaultPluginPath()) - if err != nil { - return nil, fmt.Errorf("failed to create key management provider provider: %w", err) - } - - return keyManagementProviderProvider, nil -} - -// rawToKeyManagementProviderConfig converts raw json to KeyManagementProviderConfig -func rawToKeyManagementProviderConfig(raw []byte, keyManagamentSystemName string) (config.KeyManagementProviderConfig, error) { - pluginConfig := config.KeyManagementProviderConfig{} - - if string(raw) == "" { - return config.KeyManagementProviderConfig{}, fmt.Errorf("no key management provider parameters provided") - } - if err := json.Unmarshal(raw, &pluginConfig); err != nil { - return config.KeyManagementProviderConfig{}, fmt.Errorf("unable to decode key management provider parameters.Raw: %s, err: %w", raw, err) - } - - pluginConfig[types.Type] = keyManagamentSystemName - - return pluginConfig, nil -} - // writeKMProviderStatus updates the status of the key management provider resource func writeKMProviderStatus(ctx context.Context, r client.StatusClient, keyManagementProvider *configv1beta1.KeyManagementProvider, logger *logrus.Entry, isSuccess bool, errorString string, operationTime metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) { if isSuccess { @@ -178,8 +144,8 @@ func writeKMProviderStatus(ctx context.Context, r client.StatusClient, keyManage func updateKMProviderErrorStatus(keyManagementProvider *configv1beta1.KeyManagementProvider, errorString string, operationTime *metav1.Time) { // truncate brief error string to maxBriefErrLength briefErr := errorString - if len(errorString) > maxBriefErrLength { - briefErr = fmt.Sprintf("%s...", errorString[:maxBriefErrLength]) + if len(errorString) > constants.MaxBriefErrLength { + briefErr = fmt.Sprintf("%s...", errorString[:constants.MaxBriefErrLength]) } keyManagementProvider.Status.IsSuccess = false keyManagementProvider.Status.Error = errorString diff --git a/pkg/controllers/keymanagementprovider_controller_test.go b/pkg/controllers/clusterresource/keymanagementprovider_controller_test.go similarity index 59% rename from pkg/controllers/keymanagementprovider_controller_test.go rename to pkg/controllers/clusterresource/keymanagementprovider_controller_test.go index 8a0c6104d..21ad63def 100644 --- a/pkg/controllers/keymanagementprovider_controller_test.go +++ b/pkg/controllers/clusterresource/keymanagementprovider_controller_test.go @@ -13,20 +13,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -package controllers +package clusterresource import ( "context" "fmt" - "reflect" "testing" configv1beta1 "github.com/deislabs/ratify/api/v1beta1" "github.com/deislabs/ratify/pkg/keymanagementprovider" - "github.com/deislabs/ratify/pkg/keymanagementprovider/config" "github.com/sirupsen/logrus" "sigs.k8s.io/controller-runtime/pkg/client" + test "github.com/deislabs/ratify/pkg/utils" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -129,94 +128,6 @@ func TestKMProviderUpdateSuccessStatus_emptyProperties(t *testing.T) { } } -// TestRawToKeyManagementProviderConfig tests the rawToKeyManagementProviderConfig method -func TestRawToKeyManagementProviderConfig(t *testing.T) { - testCases := []struct { - name string - raw []byte - expectErr bool - expectConfig config.KeyManagementProviderConfig - }{ - { - name: "empty Raw", - raw: []byte{}, - expectErr: true, - expectConfig: config.KeyManagementProviderConfig{}, - }, - { - name: "unmarshal failure", - raw: []byte("invalid"), - expectErr: true, - expectConfig: config.KeyManagementProviderConfig{}, - }, - { - name: "valid Raw", - raw: []byte("{\"type\": \"inline\"}"), - expectErr: false, - expectConfig: config.KeyManagementProviderConfig{ - "type": "inline", - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - config, err := rawToKeyManagementProviderConfig(tc.raw, "inline") - - if tc.expectErr != (err != nil) { - t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) - } - if !reflect.DeepEqual(config, tc.expectConfig) { - t.Fatalf("Expected config to be %v, got %v", tc.expectConfig, config) - } - }) - } -} - -// TestSpecToKeyManagementProviderProvider tests the specToKeyManagementProviderProvider method -func TestSpecToKeyManagementProviderProvider(t *testing.T) { - testCases := []struct { - name string - spec configv1beta1.KeyManagementProviderSpec - expectErr bool - }{ - { - name: "empty spec", - spec: configv1beta1.KeyManagementProviderSpec{}, - expectErr: true, - }, - { - name: "missing inline provider required fields", - spec: configv1beta1.KeyManagementProviderSpec{ - Type: "inline", - Parameters: runtime.RawExtension{ - Raw: []byte("{\"type\": \"inline\"}"), - }, - }, - expectErr: true, - }, - { - name: "valid spec", - spec: configv1beta1.KeyManagementProviderSpec{ - Type: "inline", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), - }, - }, - expectErr: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - _, err := specToKeyManagementProvider(tc.spec) - if tc.expectErr != (err != nil) { - t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) - } - }) - } -} - func TestWriteKMProviderStatus(t *testing.T) { logger := logrus.WithContext(context.Background()) lastFetchedTime := metav1.Now() @@ -232,21 +143,21 @@ func TestWriteKMProviderStatus(t *testing.T) { isSuccess: true, errString: "", kmProvider: &configv1beta1.KeyManagementProvider{}, - reconciler: &mockStatusClient{}, + reconciler: &test.MockStatusClient{}, }, { name: "error status", isSuccess: false, kmProvider: &configv1beta1.KeyManagementProvider{}, errString: "a long error string that exceeds the max length of 30 characters", - reconciler: &mockStatusClient{}, + reconciler: &test.MockStatusClient{}, }, { name: "status update failed", isSuccess: true, kmProvider: &configv1beta1.KeyManagementProvider{}, - reconciler: &mockStatusClient{ - updateFailed: true, + reconciler: &test.MockStatusClient{ + UpdateFailed: true, }, }, } diff --git a/pkg/controllers/namespaceresource/keymanagementprovider_controller.go b/pkg/controllers/namespaceresource/keymanagementprovider_controller.go new file mode 100644 index 000000000..b898d38a0 --- /dev/null +++ b/pkg/controllers/namespaceresource/keymanagementprovider_controller.go @@ -0,0 +1,172 @@ +/* +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" + "encoding/json" + "fmt" + "maps" + + "github.com/deislabs/ratify/internal/constants" + _ "github.com/deislabs/ratify/pkg/keymanagementprovider/azurekeyvault" // register azure key vault key management provider + _ "github.com/deislabs/ratify/pkg/keymanagementprovider/inline" // register inline key management provider + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + configv1beta1 "github.com/deislabs/ratify/api/v1beta1" + cutils "github.com/deislabs/ratify/pkg/controllers/utils" + kmp "github.com/deislabs/ratify/pkg/keymanagementprovider" + "github.com/sirupsen/logrus" +) + +// KeyManagementProviderReconciler reconciles a KeyManagementProvider object +type KeyManagementProviderReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedkeymanagementproviders,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedkeymanagementproviders/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedkeymanagementproviders/finalizers,verbs=update +func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := logrus.WithContext(ctx) + + var resource = req.NamespacedName.String() + var keyManagementProvider configv1beta1.NamespacedKeyManagementProvider + + logger.Infof("reconciling namespaced key management provider '%v'", resource) + + if err := r.Get(ctx, req.NamespacedName, &keyManagementProvider); err != nil { + if apierrors.IsNotFound(err) { + logger.Infof("deletion detected, removing key management provider %v", resource) + kmp.DeleteCertificatesFromMap(resource) + kmp.DeleteKeysFromMap(resource) + } else { + logger.Error(err, "unable to fetch key management provider") + } + + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + lastFetchedTime := metav1.Now() + isFetchSuccessful := false + + // get certificate store list to check if certificate store is configured + // TODO: remove check in v2.0.0+ + var certificateStoreList configv1beta1.CertificateStoreList + if err := r.List(ctx, &certificateStoreList); err != nil { + logger.Error(err, "unable to list certificate stores") + return ctrl.Result{}, err + } + // if certificate store is configured, return error. Only one of certificate store and key management provider can be configured + if len(certificateStoreList.Items) > 0 { + // Note: for backwards compatibility in upgrade scenarios, Ratify will only log a warning statement. + logger.Warn("Certificate Store already exists. Key management provider and certificate store should not be configured together. Please migrate to key management provider and delete certificate store.") + } + + provider, err := cutils.SpecToKeyManagementProvider(keyManagementProvider.Spec.Parameters.Raw, keyManagementProvider.Spec.Type) + if err != nil { + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil) + return ctrl.Result{}, err + } + + // fetch certificates and store in map + certificates, certAttributes, err := provider.GetCertificates(ctx) + if err != nil { + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil) + return ctrl.Result{}, fmt.Errorf("Error fetching certificates in KMProvider %v with %v provider, error: %w", resource, keyManagementProvider.Spec.Type, err) + } + + // fetch keys and store in map + keys, keyAttributes, err := provider.GetKeys(ctx) + if err != nil { + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil) + return ctrl.Result{}, fmt.Errorf("Error fetching keys in KMProvider %v with %v provider, error: %w", resource, keyManagementProvider.Spec.Type, err) + } + kmp.SetCertificatesInMap(resource, certificates) + kmp.SetKeysInMap(resource, keyManagementProvider.Spec.Type, keys) + // merge certificates and keys status into one + maps.Copy(keyAttributes, certAttributes) + isFetchSuccessful = true + emptyErrorString := "" + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, emptyErrorString, lastFetchedTime, keyAttributes) + + logger.Infof("%v certificate(s) & %v key(s) fetched for key management provider %v", len(certificates), len(keys), resource) + + // returning empty result and no error to indicate we’ve successfully reconciled this object + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *KeyManagementProviderReconciler) SetupWithManager(mgr ctrl.Manager) error { + pred := predicate.GenerationChangedPredicate{} + + // status updates will trigger a reconcile event + // if there are no changes to spec of CRD, this event should be filtered out by using the predicate + // see more discussions at https://github.com/kubernetes-sigs/kubebuilder/issues/618 + return ctrl.NewControllerManagedBy(mgr). + For(&configv1beta1.NamespacedKeyManagementProvider{}).WithEventFilter(pred). + Complete(r) +} + +// writeKMProviderStatus updates the status of the key management provider resource +func writeKMProviderStatus(ctx context.Context, r client.StatusClient, keyManagementProvider *configv1beta1.NamespacedKeyManagementProvider, logger *logrus.Entry, isSuccess bool, errorString string, operationTime metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) { + if isSuccess { + updateKMProviderSuccessStatus(keyManagementProvider, &operationTime, kmProviderStatus) + } else { + updateKMProviderErrorStatus(keyManagementProvider, errorString, &operationTime) + } + if statusErr := r.Status().Update(ctx, keyManagementProvider); statusErr != nil { + logger.Error(statusErr, ",unable to update key management provider error status") + } +} + +// updateKMProviderErrorStatus updates the key management provider status with error, brief error and last fetched time +func updateKMProviderErrorStatus(keyManagementProvider *configv1beta1.NamespacedKeyManagementProvider, errorString string, operationTime *metav1.Time) { + // truncate brief error string to maxBriefErrLength + briefErr := errorString + if len(errorString) > constants.MaxBriefErrLength { + briefErr = fmt.Sprintf("%s...", errorString[:constants.MaxBriefErrLength]) + } + keyManagementProvider.Status.IsSuccess = false + keyManagementProvider.Status.Error = errorString + keyManagementProvider.Status.BriefError = briefErr + keyManagementProvider.Status.LastFetchedTime = operationTime +} + +// updateKMProviderSuccessStatus updates the key management provider status if status argument is non nil +// Success status includes last fetched time and other provider-specific properties +func updateKMProviderSuccessStatus(keyManagementProvider *configv1beta1.NamespacedKeyManagementProvider, lastOperationTime *metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) { + keyManagementProvider.Status.IsSuccess = true + keyManagementProvider.Status.Error = "" + keyManagementProvider.Status.BriefError = "" + keyManagementProvider.Status.LastFetchedTime = lastOperationTime + + if kmProviderStatus != nil { + jsonString, _ := json.Marshal(kmProviderStatus) + + raw := runtime.RawExtension{ + Raw: jsonString, + } + keyManagementProvider.Status.Properties = raw + } +} diff --git a/pkg/controllers/namespaceresource/keymanagementprovider_controller_test.go b/pkg/controllers/namespaceresource/keymanagementprovider_controller_test.go new file mode 100644 index 000000000..bc6baba74 --- /dev/null +++ b/pkg/controllers/namespaceresource/keymanagementprovider_controller_test.go @@ -0,0 +1,178 @@ +/* +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" + "testing" + + configv1beta1 "github.com/deislabs/ratify/api/v1beta1" + "github.com/deislabs/ratify/pkg/keymanagementprovider" + "github.com/sirupsen/logrus" + "sigs.k8s.io/controller-runtime/pkg/client" + + test "github.com/deislabs/ratify/pkg/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// TestUpdateErrorStatus tests the updateErrorStatus method +func TestKMProviderUpdateErrorStatus(t *testing.T) { + var parametersString = "{\"certs\":{\"name\":\"certName\"}}" + var kmProviderStatus = []byte(parametersString) + + status := configv1beta1.NamespacedKeyManagementProviderStatus{ + IsSuccess: true, + Properties: runtime.RawExtension{ + Raw: kmProviderStatus, + }, + } + keyManagementProvider := configv1beta1.NamespacedKeyManagementProvider{ + Status: status, + } + expectedErr := "it's a long error from unit test" + lastFetchedTime := metav1.Now() + updateKMProviderErrorStatus(&keyManagementProvider, expectedErr, &lastFetchedTime) + + if keyManagementProvider.Status.IsSuccess != false { + t.Fatalf("Unexpected error, expected isSuccess to be false , actual %+v", keyManagementProvider.Status.IsSuccess) + } + + if keyManagementProvider.Status.Error != expectedErr { + t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedErr, keyManagementProvider.Status.Error) + } + expectedBriedErr := fmt.Sprintf("%s...", expectedErr[:30]) + if keyManagementProvider.Status.BriefError != expectedBriedErr { + t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedBriedErr, keyManagementProvider.Status.Error) + } + + //make sure properties of last cached cert was not overridden + if len(keyManagementProvider.Status.Properties.Raw) == 0 { + t.Fatalf("Unexpected properties, expected %+v, got %+v", parametersString, string(keyManagementProvider.Status.Properties.Raw)) + } +} + +// TestKMProviderUpdateSuccessStatus tests the updateSuccessStatus method +func TestKMProviderUpdateSuccessStatus(t *testing.T) { + kmProviderStatus := keymanagementprovider.KeyManagementProviderStatus{} + properties := map[string]string{} + properties["Name"] = "wabbit" + properties["Version"] = "ABC" + + kmProviderStatus["Certificates"] = properties + + lastFetchedTime := metav1.Now() + + status := configv1beta1.NamespacedKeyManagementProviderStatus{ + IsSuccess: false, + Error: "error from last operation", + } + keyManagementProvider := configv1beta1.NamespacedKeyManagementProvider{ + Status: status, + } + + updateKMProviderSuccessStatus(&keyManagementProvider, &lastFetchedTime, kmProviderStatus) + + if keyManagementProvider.Status.IsSuccess != true { + t.Fatalf("Expected isSuccess to be true , actual %+v", keyManagementProvider.Status.IsSuccess) + } + + if keyManagementProvider.Status.Error != "" { + t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error) + } + + //make sure properties of last cached cert was updated + if len(keyManagementProvider.Status.Properties.Raw) == 0 { + t.Fatalf("Properties should not be empty") + } +} + +// TestKMProviderUpdateSuccessStatus tests the updateSuccessStatus method with empty properties +func TestKMProviderUpdateSuccessStatus_emptyProperties(t *testing.T) { + lastFetchedTime := metav1.Now() + status := configv1beta1.NamespacedKeyManagementProviderStatus{ + IsSuccess: false, + Error: "error from last operation", + } + keyManagementProvider := configv1beta1.NamespacedKeyManagementProvider{ + Status: status, + } + + updateKMProviderSuccessStatus(&keyManagementProvider, &lastFetchedTime, nil) + + if keyManagementProvider.Status.IsSuccess != true { + t.Fatalf("Expected isSuccess to be true , actual %+v", keyManagementProvider.Status.IsSuccess) + } + + if keyManagementProvider.Status.Error != "" { + t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error) + } + + //make sure properties of last cached cert was updated + if len(keyManagementProvider.Status.Properties.Raw) != 0 { + t.Fatalf("Properties should be empty") + } +} + +func TestWriteKMProviderStatus(t *testing.T) { + logger := logrus.WithContext(context.Background()) + lastFetchedTime := metav1.Now() + testCases := []struct { + name string + isSuccess bool + kmProvider *configv1beta1.NamespacedKeyManagementProvider + errString string + reconciler client.StatusClient + }{ + { + name: "success status", + isSuccess: true, + errString: "", + kmProvider: &configv1beta1.NamespacedKeyManagementProvider{}, + reconciler: &test.MockStatusClient{}, + }, + { + name: "error status", + isSuccess: false, + kmProvider: &configv1beta1.NamespacedKeyManagementProvider{}, + errString: "a long error string that exceeds the max length of 30 characters", + reconciler: &test.MockStatusClient{}, + }, + { + name: "status update failed", + isSuccess: true, + kmProvider: &configv1beta1.NamespacedKeyManagementProvider{}, + reconciler: &test.MockStatusClient{ + UpdateFailed: true, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + writeKMProviderStatus(context.Background(), tc.reconciler, tc.kmProvider, logger, tc.isSuccess, tc.errString, lastFetchedTime, nil) + + if tc.kmProvider.Status.IsSuccess != tc.isSuccess { + t.Fatalf("Expected isSuccess to be %+v , actual %+v", tc.isSuccess, tc.kmProvider.Status.IsSuccess) + } + + if tc.kmProvider.Status.Error != tc.errString { + t.Fatalf("Expected Error to be %+v , actual %+v", tc.errString, tc.kmProvider.Status.Error) + } + }) + } +} diff --git a/pkg/controllers/utils/kmp.go b/pkg/controllers/utils/kmp.go new file mode 100644 index 000000000..d0b066827 --- /dev/null +++ b/pkg/controllers/utils/kmp.go @@ -0,0 +1,57 @@ +/* +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" + + c "github.com/deislabs/ratify/config" + kmp "github.com/deislabs/ratify/pkg/keymanagementprovider" + "github.com/deislabs/ratify/pkg/keymanagementprovider/config" + "github.com/deislabs/ratify/pkg/keymanagementprovider/factory" + "github.com/deislabs/ratify/pkg/keymanagementprovider/types" +) + +// SpecToKeyManagementProvider creates KeyManagementProvider from KeyManagementProviderSpec config +func SpecToKeyManagementProvider(raw []byte, keyManagamentSystemName string) (kmp.KeyManagementProvider, error) { + kmProviderConfig, err := rawToKeyManagementProviderConfig(raw, keyManagamentSystemName) + if err != nil { + return nil, fmt.Errorf("failed to parse key management provider config: %w", err) + } + + // TODO: add Version and Address to KeyManagementProviderSpec + keyManagementProviderProvider, err := factory.CreateKeyManagementProviderFromConfig(kmProviderConfig, "0.1.0", c.GetDefaultPluginPath()) + if err != nil { + return nil, fmt.Errorf("failed to create key management provider provider: %w", err) + } + + return keyManagementProviderProvider, nil +} + +// rawToKeyManagementProviderConfig converts raw json to KeyManagementProviderConfig +func rawToKeyManagementProviderConfig(raw []byte, keyManagamentSystemName string) (config.KeyManagementProviderConfig, error) { + pluginConfig := config.KeyManagementProviderConfig{} + + if string(raw) == "" { + return config.KeyManagementProviderConfig{}, fmt.Errorf("no key management provider parameters provided") + } + if err := json.Unmarshal(raw, &pluginConfig); err != nil { + return config.KeyManagementProviderConfig{}, fmt.Errorf("unable to decode key management provider parameters.Raw: %s, err: %w", raw, err) + } + + pluginConfig[types.Type] = keyManagamentSystemName + + return pluginConfig, nil +} diff --git a/pkg/controllers/utils/kmp_test.go b/pkg/controllers/utils/kmp_test.go new file mode 100644 index 000000000..eff43b957 --- /dev/null +++ b/pkg/controllers/utils/kmp_test.go @@ -0,0 +1,101 @@ +/* +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 ( + "reflect" + "testing" + + "github.com/deislabs/ratify/pkg/keymanagementprovider/config" + _ "github.com/deislabs/ratify/pkg/keymanagementprovider/inline" +) + +func TestSpecToKeyManagementProviderProvider(t *testing.T) { + testCases := []struct { + name string + raw []byte + kmpType string + expectErr bool + }{ + { + name: "empty spec", + expectErr: true, + }, + { + name: "missing inline provider required fields", + raw: []byte("{\"type\": \"inline\"}"), + kmpType: "inline", + expectErr: true, + }, + { + name: "valid spec", + raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), + kmpType: "inline", + expectErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := SpecToKeyManagementProvider(tc.raw, tc.kmpType) + if tc.expectErr != (err != nil) { + t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) + } + }) + } +} + +// TestRawToKeyManagementProviderConfig tests the rawToKeyManagementProviderConfig method +func TestRawToKeyManagementProviderConfig(t *testing.T) { + testCases := []struct { + name string + raw []byte + expectErr bool + expectConfig config.KeyManagementProviderConfig + }{ + { + name: "empty Raw", + raw: []byte{}, + expectErr: true, + expectConfig: config.KeyManagementProviderConfig{}, + }, + { + name: "unmarshal failure", + raw: []byte("invalid"), + expectErr: true, + expectConfig: config.KeyManagementProviderConfig{}, + }, + { + name: "valid Raw", + raw: []byte("{\"type\": \"inline\"}"), + expectErr: false, + expectConfig: config.KeyManagementProviderConfig{ + "type": "inline", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + config, err := rawToKeyManagementProviderConfig(tc.raw, "inline") + + if tc.expectErr != (err != nil) { + t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) + } + if !reflect.DeepEqual(config, tc.expectConfig) { + t.Fatalf("Expected config to be %v, got %v", tc.expectConfig, config) + } + }) + } +} diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index 5e372b099..e993d118b 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -235,11 +235,18 @@ func StartManager(certRotatorReady chan struct{}, probeAddr string) { setupLog.Error(err, "unable to create controller", "controller", "Policy") os.Exit(1) } - if err = (&controllers.KeyManagementProviderReconciler{ + if err = (&clusterresource.KeyManagementProviderReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Key Management Provider") + setupLog.Error(err, "unable to create controller", "controller", "Cluster Key Management Provider") + os.Exit(1) + } + if err = (&namespaceresource.KeyManagementProviderReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Namespaced Key Management Provider") os.Exit(1) } //+kubebuilder:scaffold:builder diff --git a/test/bats/base-test.bats b/test/bats/base-test.bats index c3ade58b1..58ddae270 100644 --- a/test/bats/base-test.bats +++ b/test/bats/base-test.bats @@ -35,7 +35,7 @@ RATIFY_NAMESPACE=gatekeeper-system assert_success sleep 5 # validate key management provider status property shows success - run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} -o yaml | grep 'issuccess: true'" + run bash -c "kubectl get namespacedkeymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} -o yaml | grep 'issuccess: true'" assert_success run kubectl run demo --namespace default --image=registry:5000/notation:signed assert_success @@ -87,7 +87,7 @@ RATIFY_NAMESPACE=gatekeeper-system sleep 5 # validate key management provider status property shows success - run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} -o yaml | grep 'issuccess: true'" + run bash -c "kubectl get namespacedkeymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} -o yaml | grep 'issuccess: true'" assert_success run kubectl run demo --namespace default --image=registry:5000/notation:signed assert_success @@ -103,8 +103,8 @@ RATIFY_NAMESPACE=gatekeeper-system wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo1 --namespace default --force --ignore-not-found=true' # restore cert store in ratify namespace - run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -o yaml -n default > kmprovider.yaml" - run kubectl delete keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n default + run bash -c "kubectl get namespacedkeymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -o yaml -n default > kmprovider.yaml" + run kubectl delete namespacedkeymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n default sed 's/default/gatekeeper-system/' kmprovider.yaml > kmproviderNewNS.yaml run kubectl apply -f kmproviderNewNS.yaml assert_success @@ -121,12 +121,12 @@ RATIFY_NAMESPACE=gatekeeper-system sleep 5 # apply the key management provider to default namespace - run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -o yaml -n ${RATIFY_NAMESPACE} > kmprovider.yaml" + run bash -c "kubectl get namespacedkeymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -o yaml -n ${RATIFY_NAMESPACE} > kmprovider.yaml" assert_success sed 's/gatekeeper-system/default/' kmprovider.yaml > kmproviderNewNS.yaml run kubectl apply -f kmproviderNewNS.yaml assert_success - run kubectl delete keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} + run kubectl delete namespacedkeymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} assert_success # configure the notation verifier to use inline certificate store with specific namespace @@ -330,7 +330,7 @@ RATIFY_NAMESPACE=gatekeeper-system } # save the existing key management provider inline resource to restore later - run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} -o yaml > kmprovider_staging.yaml" + run bash -c "kubectl get namespacedkeymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} -o yaml > kmprovider_staging.yaml" assert_success # configure the default template/constraint run kubectl apply -f ./library/default/template.yaml @@ -343,7 +343,7 @@ RATIFY_NAMESPACE=gatekeeper-system assert_failure # delete the existing key management provider inline resource since certificate store and key management provider cannot be used together - run kubectl delete keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} + run kubectl delete namespacedkeymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} assert_success # add the alternate certificate as an inline certificate store cat ~/.config/notation/truststore/x509/ca/alternate-cert/alternate-cert.crt | sed 's/^/ /g' >>./test/bats/tests/config/config_v1beta1_certstore_inline.yaml @@ -363,7 +363,7 @@ RATIFY_NAMESPACE=gatekeeper-system @test "validate inline key management provider" { teardown() { - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete keymanagementproviders.config.ratify.deislabs.io/keymanagementprovider-inline --namespace ${RATIFY_NAMESPACE} --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete namespacedkeymanagementproviders.config.ratify.deislabs.io/keymanagementprovider-inline --namespace ${RATIFY_NAMESPACE} --ignore-not-found=true' 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 @@ -414,7 +414,7 @@ RATIFY_NAMESPACE=gatekeeper-system assert_success # validate key management provider status property shows success - run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} -o yaml | grep 'issuccess: true'" + run bash -c "kubectl get namespacedkeymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} -o yaml | grep 'issuccess: true'" assert_success run kubectl run demo --namespace default --image=registry:5000/notation:signed assert_success @@ -459,7 +459,7 @@ RATIFY_NAMESPACE=gatekeeper-system @test "validate image signed by leaf cert" { teardown() { - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete keymanagementproviders.config.ratify.deislabs.io/keymanagementprovider-inline --namespace ${RATIFY_NAMESPACE} --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete namespacedkeymanagementproviders.config.ratify.deislabs.io/keymanagementprovider-inline --namespace ${RATIFY_NAMESPACE} --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo-leaf --namespace default --force --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo-leaf2 --namespace default --force --ignore-not-found=true' diff --git a/test/bats/high-availability.bats b/test/bats/high-availability.bats index e0a11bd72..c45e9f0f3 100644 --- a/test/bats/high-availability.bats +++ b/test/bats/high-availability.bats @@ -32,7 +32,7 @@ SLEEP_TIME=1 assert_success sleep 5 # validate key management provider status property shows success - run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n gatekeeper-system -o yaml | grep 'issuccess: true'" + run bash -c "kubectl get namespacedkeymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n gatekeeper-system -o yaml | grep 'issuccess: true'" assert_success run kubectl run demo --namespace default --image=registry:5000/notation:signed assert_success diff --git a/test/bats/tests/config/config_v1beta1_keymanagementprovider_inline.yaml b/test/bats/tests/config/config_v1beta1_keymanagementprovider_inline.yaml index b0984cbe5..bb5bc47cb 100644 --- a/test/bats/tests/config/config_v1beta1_keymanagementprovider_inline.yaml +++ b/test/bats/tests/config/config_v1beta1_keymanagementprovider_inline.yaml @@ -1,5 +1,5 @@ apiVersion: config.ratify.deislabs.io/v1beta1 -kind: KeyManagementProvider +kind: NamespacedKeyManagementProvider metadata: name: keymanagementprovider-inline spec: