diff --git a/pkg/controllers/keymanagementprovider_controller.go b/pkg/controllers/keymanagementprovider_controller.go index 787ca1fb45..d37614a852 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 c47a686874..a6db65e94e 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" @@ -32,4 +33,10 @@ var ( // a map between CertificateStore name to array of x509 certificates CertificatesMap = cs.NewActiveCertStores() + + // a map to store certificates fetched from key management provider + KMPCertificateMap = kmp.NewActiveCertStores() + + // a map to store keys fetched from key management provider + KMPKeyMap = kmp.NewActiveKeyStores() ) diff --git a/pkg/controllers/utils/kmp.go b/pkg/controllers/utils/kmp.go new file mode 100644 index 0000000000..896cea2584 --- /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 0000000000..cd85554ab7 --- /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 0000000000..59c9d26837 --- /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 0000000000..87210a7a71 --- /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 0000000000..6f56ef8fb7 --- /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 0000000000..24e558f60b --- /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 0000000000..5292471621 --- /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 b0731f3426..c78fafea5e 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 3607d2d98e..fe34f9d59f 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 4cb400dc9c..59a752abe2 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 0000000000..8b2087aea8 --- /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 89a7fa9686..cb84e3f233 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 +}