Skip to content

Commit

Permalink
feat: add KMPManager interface to wrap operations on namespaced kmp
Browse files Browse the repository at this point in the history
  • Loading branch information
binbin-li committed Apr 16, 2024
1 parent f3f2d13 commit a935b5f
Show file tree
Hide file tree
Showing 14 changed files with 676 additions and 54 deletions.
10 changes: 7 additions & 3 deletions pkg/controllers/keymanagementprovider_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions pkg/controllers/resource_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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()
)
28 changes: 28 additions & 0 deletions pkg/controllers/utils/kmp.go
Original file line number Diff line number Diff line change
@@ -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)
}
41 changes: 41 additions & 0 deletions pkg/controllers/utils/kmp_test.go
Original file line number Diff line number Diff line change
@@ -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))
}
}
47 changes: 47 additions & 0 deletions pkg/customresources/keymanagementproviders/api.go
Original file line number Diff line number Diff line change
@@ -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)
}
69 changes: 69 additions & 0 deletions pkg/customresources/keymanagementproviders/kmpcertmanager.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
68 changes: 68 additions & 0 deletions pkg/customresources/keymanagementproviders/kmpcertmanager_test.go
Original file line number Diff line number Diff line change
@@ -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)))
}
}
69 changes: 69 additions & 0 deletions pkg/customresources/keymanagementproviders/kmpkeymanager.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading

0 comments on commit a935b5f

Please sign in to comment.