diff --git a/pkg/controllers/clusterresource/keymanagementprovider_controller.go b/pkg/controllers/clusterresource/keymanagementprovider_controller.go index 93dda9806f..efb4606203 100644 --- a/pkg/controllers/clusterresource/keymanagementprovider_controller.go +++ b/pkg/controllers/clusterresource/keymanagementprovider_controller.go @@ -18,11 +18,18 @@ package clusterresource import ( "context" + "encoding/json" "fmt" + "github.com/ratify-project/ratify/internal/constants" + cutils "github.com/ratify-project/ratify/pkg/controllers/utils" + kmp "github.com/ratify-project/ratify/pkg/keymanagementprovider" _ "github.com/ratify-project/ratify/pkg/keymanagementprovider/azurekeyvault" // register azure key vault key management provider _ "github.com/ratify-project/ratify/pkg/keymanagementprovider/inline" // register inline key management provider "github.com/ratify-project/ratify/pkg/keymanagementprovider/refresh" + "github.com/sirupsen/logrus" + 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" @@ -37,20 +44,83 @@ type KeyManagementProviderReconciler struct { Scheme *runtime.Scheme } -func (r *KeyManagementProviderReconciler) ReconcileWithConfig(ctx context.Context, config map[string]interface{}) (ctrl.Result, error) { +func (r *KeyManagementProviderReconciler) ReconcileWithType(ctx context.Context, req ctrl.Request, refresherType string) (ctrl.Result, error) { + logger := logrus.WithContext(ctx) + + var keyManagementProvider configv1beta1.KeyManagementProvider + + resource := req.Name + + logger.Infof("reconciling cluster 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.DeleteResourceFromMap(resource) + } else { + logger.Error(err, "unable to fetch key management provider") + } + + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + lastFetchedTime := metav1.Now() + + // 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 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 { + kmp.SetCertificateError(resource, err) + kmp.SetKeyError(resource, err) + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, false, err.Error(), lastFetchedTime, nil) + return ctrl.Result{}, err + } + + config := map[string]interface{}{ + "refresherType": refresherType, + "provider": provider, + "providerType": keyManagementProvider.Spec.Type, + "providerRefreshInterval": keyManagementProvider.Spec.RefreshInterval, + "resource": resource, + } + refresher, err := refresh.CreateRefresherFromConfig(config) if err != nil { + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, false, err.Error(), lastFetchedTime, nil) return ctrl.Result{}, err } + err = refresher.Refresh(ctx) if err != nil { + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, false, err.Error(), lastFetchedTime, nil) return ctrl.Result{}, err } result, ok := refresher.GetResult().(ctrl.Result) if !ok { + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, false, "reconcile failed, unexpected result type returned by key management provider", lastFetchedTime, nil) return ctrl.Result{}, fmt.Errorf("unexpected type returned from GetResult: %T", refresher.GetResult()) } + + status, ok := refresher.GetStatus().(kmp.KeyManagementProviderStatus) + if !ok { + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, false, "reconcile failed, unexpected status type returned by key management provider", lastFetchedTime, nil) + return ctrl.Result{}, fmt.Errorf("unexpected type returned from GetStatus: %T", refresher.GetStatus()) + } + + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, true, "", lastFetchedTime, status) + return result, nil } @@ -58,12 +128,7 @@ func (r *KeyManagementProviderReconciler) ReconcileWithConfig(ctx context.Contex // +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=keymanagementproviders/status,verbs=get;update;patch // +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=keymanagementproviders/finalizers,verbs=update func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - refresherConfig := map[string]interface{}{ - "type": refresh.KubeRefresherType, - "client": r.Client, - "request": req, - } - return r.ReconcileWithConfig(ctx, refresherConfig) + return r.ReconcileWithType(ctx, req, refresh.KubeRefresherType) } // SetupWithManager sets up the controller with the Manager. @@ -77,3 +142,45 @@ func (r *KeyManagementProviderReconciler) SetupWithManager(mgr ctrl.Manager) err For(&configv1beta1.KeyManagementProvider{}).WithEventFilter(pred). Complete(r) } + +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 { + 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.KeyManagementProvider, 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.KeyManagementProvider, 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/clusterresource/keymanagementprovider_controller_test.go b/pkg/controllers/clusterresource/keymanagementprovider_controller_test.go index d4129e2c6e..4049459309 100644 --- a/pkg/controllers/clusterresource/keymanagementprovider_controller_test.go +++ b/pkg/controllers/clusterresource/keymanagementprovider_controller_test.go @@ -18,82 +18,355 @@ package clusterresource import ( "context" "errors" + "fmt" "reflect" "testing" - "github.com/ratify-project/ratify/pkg/keymanagementprovider/refresh" + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/mocks" test "github.com/ratify-project/ratify/pkg/utils" + "github.com/sirupsen/logrus" + + kmp "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/refresh" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" ) -func TestKeyManagementProviderReconciler_ReconcileWithConfig(t *testing.T) { +func init() { + refresh.Register("mockRefresher", &MockRefresher{}) +} + +type MockRefresher struct { + Result ctrl.Result + RefreshError bool + ResultError bool + StatusError bool + Status kmp.KeyManagementProviderStatus +} + +func (mr *MockRefresher) Refresh(_ context.Context) error { + if mr.RefreshError { + return errors.New("error from refresh") + } + return nil +} + +func (mr *MockRefresher) GetResult() interface{} { + if mr.ResultError { + return errors.New("error from result") + } + return mr.Result +} + +func (mr *MockRefresher) GetStatus() interface{} { + if mr.StatusError { + return errors.New("error from status") + } + return mr.Status +} + +func (mr *MockRefresher) Create(config map[string]interface{}) (refresh.Refresher, error) { + if resource, ok := config["resource"].(string); ok && resource == "refreshError" { + return &MockRefresher{ + RefreshError: true, + }, nil + } + if resource, ok := config["resource"].(string); ok && resource == "resultError" { + return &MockRefresher{ + ResultError: true, + }, nil + } + if resource, ok := config["resource"].(string); ok && resource == "statusError" { + return &MockRefresher{ + StatusError: true, + }, nil + } + return &MockRefresher{}, nil +} + +func TestKeyManagementProviderReconciler_ReconcileWithType(t *testing.T) { tests := []struct { - name string - refresherType string - createConfigError bool - refreshError bool - expectedError bool + name string + clientGetFunc func(_ context.Context, key types.NamespacedName, obj client.Object) error + clientListFunc func(_ context.Context, list client.ObjectList) error + resourceName string + refresherType string + expectedResult ctrl.Result + expectedError bool }{ { - name: "Successful Reconcile", - refresherType: "mockRefresher", - createConfigError: false, - refreshError: false, - expectedError: false, + // TODO: Add SetLogger to internal/logger/logger.go to compare log messages + // https://maxchadwick.xyz/blog/testing-log-output-in-go-logrus + name: "api is not found", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, _ client.Object) error { + resource := schema.GroupResource{ + Group: "", // Use an empty string for core resources (like Pod) + Resource: "pods", // Resource type, e.g., "pods" for Pod resources + } + return apierrors.NewNotFound(resource, "test") + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: false, + }, + { + name: "unable to fetch key management provider", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, _ client.Object) error { + return fmt.Errorf("unable to fetch key management provider") + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: false, + }, + { + name: "unable to list certificate stores", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, _ client.Object) error { + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return fmt.Errorf("unable to list certificate stores") + }, + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "certificate store already exists", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.KeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.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"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, list client.ObjectList) error { + certStoreList, ok := list.(*configv1beta1.CertificateStoreList) + if !ok { + return errors.New("expected CertificateStoreList") + } + + certStoreList.Items = []configv1beta1.CertificateStore{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + }, + } + return nil + }, + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: false, + }, + { + name: "cutils.SpecToKeyManagementProvider failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.KeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "refresh.CreateRefresherFromConfig failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.KeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.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"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + refresherType: "invalidRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, }, { - name: "Refresher Error", - refresherType: "mockRefresher", - createConfigError: false, - refreshError: true, - expectedError: true, + name: "refresh.Refresh failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.KeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.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"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceName: "refreshError", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, }, { - name: "Invalid Refresher Type", - refresherType: "invalidRefresher", - createConfigError: true, - refreshError: false, - expectedError: true, + name: "refresher.GetResult failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.KeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.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"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceName: "resultError", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "refresher.GetStatus failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.KeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.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"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceName: "statusError", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "successfully reconciled", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.KeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.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"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: false, }, } + for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req := ctrl.Request{ - NamespacedName: client.ObjectKey{ - Name: "fake-name", - Namespace: "fake-namespace", - }, - } - scheme, _ := test.CreateScheme() - client := fake.NewClientBuilder().WithScheme(scheme).Build() + fmt.Println(tt.name) + mockClient := mocks.TestClient{ + GetFunc: tt.clientGetFunc, + ListFunc: tt.clientListFunc, + } + req := ctrl.Request{ + NamespacedName: client.ObjectKey{ + Name: tt.resourceName, + Namespace: "test", + }, + } + scheme, _ := test.CreateScheme() - r := &KeyManagementProviderReconciler{ - Client: client, - Scheme: runtime.NewScheme(), - } + r := &KeyManagementProviderReconciler{ + Client: &mockClient, + Scheme: scheme, + } - refresherConfig := map[string]interface{}{ - "type": tt.refresherType, - "client": client, - "request": req, - "createConfigError": tt.createConfigError, - "refreshError": tt.refreshError, - "shouldError": tt.expectedError, - } + result, err := r.ReconcileWithType(context.Background(), req, tt.refresherType) - _, err := r.ReconcileWithConfig(context.TODO(), refresherConfig) - if tt.expectedError && err == nil { - t.Errorf("Expected error, got nil") - } - if !tt.expectedError && err != nil { - t.Errorf("Expected no error, got %v", err) - } - }) + if !reflect.DeepEqual(result, tt.expectedResult) { + t.Fatalf("Expected result %v, got %v", tt.expectedResult, result) + } + if tt.expectedError && err == nil { + t.Fatalf("Expected error, got nil") + } } } + func TestKeyManagementProviderReconciler_Reconcile(t *testing.T) { req := ctrl.Request{ NamespacedName: client.ObjectKey{ @@ -124,36 +397,148 @@ func TestKeyManagementProviderReconciler_Reconcile(t *testing.T) { } } -type MockRefresher struct { - Results ctrl.Result - CreateConfigError bool - RefreshError bool - ShouldError bool -} +func TestKMProviderUpdateErrorStatus(t *testing.T) { + var parametersString = "{\"certs\":{\"name\":\"certName\"}}" + var kmProviderStatus = []byte(parametersString) -func (mr *MockRefresher) Refresh(_ context.Context) error { - if mr.RefreshError { - return errors.New("refresh error") + status := configv1beta1.KeyManagementProviderStatus{ + IsSuccess: true, + Properties: runtime.RawExtension{ + Raw: kmProviderStatus, + }, + } + keyManagementProvider := configv1beta1.KeyManagementProvider{ + 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)) } - return nil } -func (mr *MockRefresher) GetResult() interface{} { - return ctrl.Result{} +// TestKMProviderUpdateSuccessStatus tests the updateSuccessStatus method +func TestKMProviderUpdateSuccessStatus(t *testing.T) { + kmProviderStatus := kmp.KeyManagementProviderStatus{} + properties := map[string]string{} + properties["Name"] = "wabbit" + properties["Version"] = "ABC" + + kmProviderStatus["Certificates"] = properties + + lastFetchedTime := metav1.Now() + + status := configv1beta1.KeyManagementProviderStatus{ + IsSuccess: false, + Error: "error from last operation", + } + keyManagementProvider := configv1beta1.KeyManagementProvider{ + 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") + } } -func (mr *MockRefresher) Create(config map[string]interface{}) (refresh.Refresher, error) { - createConfigError := config["createConfigError"].(bool) - refreshError := config["refreshError"].(bool) - if createConfigError { - return nil, errors.New("create error") - } - return &MockRefresher{ - CreateConfigError: createConfigError, - RefreshError: refreshError, - }, nil +// TestKMProviderUpdateSuccessStatus tests the updateSuccessStatus method with empty properties +func TestKMProviderUpdateSuccessStatus_emptyProperties(t *testing.T) { + lastFetchedTime := metav1.Now() + status := configv1beta1.KeyManagementProviderStatus{ + IsSuccess: false, + Error: "error from last operation", + } + keyManagementProvider := configv1beta1.KeyManagementProvider{ + 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 init() { - refresh.Register("mockRefresher", &MockRefresher{}) +func TestWriteKMProviderStatus(t *testing.T) { + logger := logrus.WithContext(context.Background()) + lastFetchedTime := metav1.Now() + testCases := []struct { + name string + isSuccess bool + kmProvider *configv1beta1.KeyManagementProvider + errString string + reconciler client.StatusClient + }{ + { + name: "success status", + isSuccess: true, + errString: "", + kmProvider: &configv1beta1.KeyManagementProvider{}, + 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: &test.MockStatusClient{}, + }, + { + name: "status update failed", + isSuccess: true, + kmProvider: &configv1beta1.KeyManagementProvider{}, + 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/namespaceresource/keymanagementprovider_controller.go b/pkg/controllers/namespaceresource/keymanagementprovider_controller.go index ba439a2541..5116d3e58e 100644 --- a/pkg/controllers/namespaceresource/keymanagementprovider_controller.go +++ b/pkg/controllers/namespaceresource/keymanagementprovider_controller.go @@ -18,11 +18,18 @@ package namespaceresource import ( "context" + "encoding/json" "fmt" + "github.com/ratify-project/ratify/internal/constants" + cutils "github.com/ratify-project/ratify/pkg/controllers/utils" + kmp "github.com/ratify-project/ratify/pkg/keymanagementprovider" _ "github.com/ratify-project/ratify/pkg/keymanagementprovider/azurekeyvault" // register azure key vault key management provider _ "github.com/ratify-project/ratify/pkg/keymanagementprovider/inline" // register inline key management provider "github.com/ratify-project/ratify/pkg/keymanagementprovider/refresh" + "github.com/sirupsen/logrus" + 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" @@ -37,20 +44,86 @@ type KeyManagementProviderReconciler struct { Scheme *runtime.Scheme } -func (r *KeyManagementProviderReconciler) ReconcileWithConfig(ctx context.Context, config map[string]interface{}) (ctrl.Result, error) { +func (r *KeyManagementProviderReconciler) ReconcileWithType(ctx context.Context, req ctrl.Request, refresherType string) (ctrl.Result, error) { + logger := logrus.WithContext(ctx) + + var resource = req.NamespacedName.String() + var keyManagementProvider configv1beta1.NamespacedKeyManagementProvider + + logger.Infof("reconciling cluster 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.DeleteResourceFromMap(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 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 { + kmp.SetCertificateError(resource, err) + kmp.SetKeyError(resource, err) + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil) + return ctrl.Result{}, err + } + + config := map[string]interface{}{ + "type": refresherType, + "provider": provider, + "providerType": keyManagementProvider.Spec.Type, + "providerRefreshInterval": keyManagementProvider.Spec.RefreshInterval, + "resource": resource, + } + refresher, err := refresh.CreateRefresherFromConfig(config) if err != nil { + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil) return ctrl.Result{}, err } + err = refresher.Refresh(ctx) if err != nil { + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil) return ctrl.Result{}, err } result, ok := refresher.GetResult().(ctrl.Result) if !ok { + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, "reconcile failed, unexpected result type returned by key management provider", lastFetchedTime, nil) return ctrl.Result{}, fmt.Errorf("unexpected type returned from GetResult: %T", refresher.GetResult()) } + + status, ok := refresher.GetStatus().(kmp.KeyManagementProviderStatus) + if !ok { + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, "reconcile failed, unexpected status type returned by key management provider", lastFetchedTime, nil) + return ctrl.Result{}, fmt.Errorf("unexpected type returned from GetStatus: %T", refresher.GetStatus()) + } + + isFetchSuccessful = true + emptyErrorString := "" + + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, emptyErrorString, lastFetchedTime, status) + return result, nil } @@ -58,12 +131,7 @@ func (r *KeyManagementProviderReconciler) ReconcileWithConfig(ctx context.Contex // +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) { - refresherConfig := map[string]interface{}{ - "type": refresh.KubeRefresherNamespacedType, - "client": r.Client, - "request": req, - } - return r.ReconcileWithConfig(ctx, refresherConfig) + return r.ReconcileWithType(ctx, req, refresh.KubeRefresherType) } // SetupWithManager sets up the controller with the Manager. @@ -77,3 +145,46 @@ func (r *KeyManagementProviderReconciler) SetupWithManager(mgr ctrl.Manager) err 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 index 7e4717a8e6..7c4a763185 100644 --- a/pkg/controllers/namespaceresource/keymanagementprovider_controller_test.go +++ b/pkg/controllers/namespaceresource/keymanagementprovider_controller_test.go @@ -18,80 +18,357 @@ package namespaceresource import ( "context" "errors" + "fmt" "reflect" "testing" + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + kmp "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/mocks" "github.com/ratify-project/ratify/pkg/keymanagementprovider/refresh" test "github.com/ratify-project/ratify/pkg/utils" + "github.com/sirupsen/logrus" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" ) -func TestKeyManagementProviderReconciler_ReconcileWithConfig(t *testing.T) { +func init() { + refresh.Register("mockRefresher", &MockRefresher{}) +} + +type MockRefresher struct { + Result ctrl.Result + RefreshError bool + ResultError bool + StatusError bool + Status kmp.KeyManagementProviderStatus +} + +func (mr *MockRefresher) Refresh(_ context.Context) error { + if mr.RefreshError { + return errors.New("error from refresh") + } + return nil +} + +func (mr *MockRefresher) GetResult() interface{} { + if mr.ResultError { + return errors.New("error from result") + } + return mr.Result +} + +func (mr *MockRefresher) GetStatus() interface{} { + if mr.StatusError { + return errors.New("error from status") + } + return mr.Status +} + +func (mr *MockRefresher) Create(config map[string]interface{}) (refresh.Refresher, error) { + if resource, ok := config["resource"].(string); ok && resource == "refreshError/test" { + return &MockRefresher{ + RefreshError: true, + }, nil + } + if resource, ok := config["resource"].(string); ok && resource == "resultError/test" { + return &MockRefresher{ + ResultError: true, + }, nil + } + if resource, ok := config["resource"].(string); ok && resource == "statusError/test" { + return &MockRefresher{ + StatusError: true, + }, nil + } + return &MockRefresher{}, nil +} + +func TestKeyManagementProviderReconciler_ReconcileWithType(t *testing.T) { tests := []struct { name string + clientGetFunc func(_ context.Context, key types.NamespacedName, obj client.Object) error + clientListFunc func(_ context.Context, list client.ObjectList) error + resourceNamespace string refresherType string - createConfigError bool - refreshError bool + expectedResult ctrl.Result expectedError bool }{ { - name: "Successful Reconcile", + // TODO: Add SetLogger to internal/logger/logger.go to compare log messages + // https://maxchadwick.xyz/blog/testing-log-output-in-go-logrus + name: "api is not found", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, _ client.Object) error { + resource := schema.GroupResource{ + Group: "", // Use an empty string for core resources (like Pod) + Resource: "pods", // Resource type, e.g., "pods" for Pod resources + } + return apierrors.NewNotFound(resource, "test") + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceNamespace: "test", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: false, + }, + { + name: "unable to fetch key management provider", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, _ client.Object) error { + return fmt.Errorf("unable to fetch key management provider") + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceNamespace: "test", refresherType: "mockRefresher", - createConfigError: false, - refreshError: false, + expectedResult: ctrl.Result{}, expectedError: false, }, { - name: "Refresher Error", + name: "unable to list certificate stores", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, _ client.Object) error { + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return fmt.Errorf("unable to list certificate stores") + }, + resourceNamespace: "test", refresherType: "mockRefresher", - createConfigError: false, - refreshError: true, + expectedResult: ctrl.Result{}, expectedError: true, }, { - name: "Invalid Refresher Type", + name: "certificate store already exists", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.NamespacedKeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.NamespacedKeyManagementProviderSpec{ + 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"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, list client.ObjectList) error { + certStoreList, ok := list.(*configv1beta1.CertificateStoreList) + if !ok { + return errors.New("expected CertificateStoreList") + } + + certStoreList.Items = []configv1beta1.CertificateStore{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + }, + } + return nil + }, + resourceNamespace: "test", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: false, + }, + { + name: "cutils.SpecToKeyManagementProvider failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.NamespacedKeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceNamespace: "test", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "refresh.CreateRefresherFromConfig failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.NamespacedKeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.NamespacedKeyManagementProviderSpec{ + 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"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceNamespace: "test", refresherType: "invalidRefresher", - createConfigError: true, - refreshError: false, + expectedResult: ctrl.Result{}, expectedError: true, }, + { + name: "refresh.Refresh failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.NamespacedKeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.NamespacedKeyManagementProviderSpec{ + 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"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceNamespace: "refreshError", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "refresher.GetResult failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.NamespacedKeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.NamespacedKeyManagementProviderSpec{ + 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"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceNamespace: "resultError", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "refresher.GetStatus failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.NamespacedKeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.NamespacedKeyManagementProviderSpec{ + 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"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceNamespace: "statusError", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "successfully reconciled", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.NamespacedKeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.NamespacedKeyManagementProviderSpec{ + 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"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceNamespace: "test", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: false, + }, } + for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req := ctrl.Request{ - NamespacedName: client.ObjectKey{ - Name: "fake-name", - Namespace: "fake-namespace", - }, - } - scheme, _ := test.CreateScheme() - client := fake.NewClientBuilder().WithScheme(scheme).Build() + fmt.Println(tt.name) + mockClient := mocks.TestClient{ + GetFunc: tt.clientGetFunc, + ListFunc: tt.clientListFunc, + } + req := ctrl.Request{ + NamespacedName: client.ObjectKey{ + Name: "test", + Namespace: tt.resourceNamespace, + }, + } + scheme, _ := test.CreateScheme() - r := &KeyManagementProviderReconciler{ - Client: client, - Scheme: runtime.NewScheme(), - } + r := &KeyManagementProviderReconciler{ + Client: &mockClient, + Scheme: scheme, + } - refresherConfig := map[string]interface{}{ - "type": tt.refresherType, - "client": client, - "request": req, - "createConfigError": tt.createConfigError, - "refreshError": tt.refreshError, - "shouldError": tt.expectedError, - } + result, err := r.ReconcileWithType(context.Background(), req, tt.refresherType) - _, err := r.ReconcileWithConfig(context.TODO(), refresherConfig) - if tt.expectedError && err == nil { - t.Errorf("Expected error, got nil") - } - if !tt.expectedError && err != nil { - t.Errorf("Expected no error, got %v", err) - } - }) + if !reflect.DeepEqual(result, tt.expectedResult) { + t.Fatalf("Expected result %v, got %v", tt.expectedResult, result) + } + if tt.expectedError && err == nil { + t.Fatalf("Expected error, got nil") + } } } @@ -125,36 +402,149 @@ func TestKeyManagementProviderReconciler_Reconcile(t *testing.T) { } } -type MockRefresher struct { - Results ctrl.Result - CreateConfigError bool - RefreshError bool - ShouldError bool -} +// TestUpdateErrorStatus tests the updateErrorStatus method +func TestKMProviderUpdateErrorStatus(t *testing.T) { + var parametersString = "{\"certs\":{\"name\":\"certName\"}}" + var kmProviderStatus = []byte(parametersString) -func (mr *MockRefresher) Refresh(_ context.Context) error { - if mr.RefreshError { - return errors.New("refresh error") + 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)) } - return nil } -func (mr *MockRefresher) GetResult() interface{} { - return ctrl.Result{} +// TestKMProviderUpdateSuccessStatus tests the updateSuccessStatus method +func TestKMProviderUpdateSuccessStatus(t *testing.T) { + kmProviderStatus := kmp.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") + } } -func (mr *MockRefresher) Create(config map[string]interface{}) (refresh.Refresher, error) { - createConfigError := config["shouldError"].(bool) - refreshError := config["refreshError"].(bool) - if createConfigError { - return nil, errors.New("create error") - } - return &MockRefresher{ - CreateConfigError: createConfigError, - RefreshError: refreshError, - }, nil +// 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 init() { - refresh.Register("mockRefresher", &MockRefresher{}) +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/keymanagementprovider/mocks/client.go b/pkg/keymanagementprovider/mocks/client.go index f24c190297..4d651ce925 100644 --- a/pkg/keymanagementprovider/mocks/client.go +++ b/pkg/keymanagementprovider/mocks/client.go @@ -17,7 +17,7 @@ package mocks import ( "context" - "fmt" + "errors" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -25,8 +25,43 @@ import ( type TestClient struct { client.Client + GetFunc func(ctx context.Context, key types.NamespacedName, obj client.Object) error + ListFunc func(ctx context.Context, list client.ObjectList) error } -func (m TestClient) Get(_ context.Context, _ types.NamespacedName, _ client.Object, _ ...client.GetOption) error { - return fmt.Errorf("error") +func (m TestClient) Get(_ context.Context, key types.NamespacedName, obj client.Object, _ ...client.GetOption) error { + if m.GetFunc != nil { + return m.GetFunc(context.Background(), key, obj) + } + return nil +} + +func (m TestClient) List(_ context.Context, list client.ObjectList, _ ...client.ListOption) error { + if m.ListFunc != nil { + return m.ListFunc(context.Background(), list) + } + return nil +} + +type mockSubResourceWriter struct { + updateFailed bool +} + +func (m *TestClient) Status() client.StatusWriter { + return &mockSubResourceWriter{updateFailed: false} +} + +func (m *mockSubResourceWriter) Create(_ context.Context, _ client.Object, _ client.Object, _ ...client.SubResourceCreateOption) error { + return nil +} + +func (m *mockSubResourceWriter) Patch(_ context.Context, _ client.Object, _ client.Patch, _ ...client.SubResourcePatchOption) error { + return nil +} + +func (m *mockSubResourceWriter) Update(_ context.Context, _ client.Object, _ ...client.SubResourceUpdateOption) error { + if m.updateFailed { + return errors.New("update failed") + } + return nil } diff --git a/pkg/keymanagementprovider/mocks/factory.go b/pkg/keymanagementprovider/mocks/factory.go index 0230582e2b..15a0e0d7db 100644 --- a/pkg/keymanagementprovider/mocks/factory.go +++ b/pkg/keymanagementprovider/mocks/factory.go @@ -16,6 +16,7 @@ limitations under the License. package mocks import ( + "context" "crypto" "crypto/x509" @@ -24,10 +25,19 @@ import ( ) type TestKeyManagementProviderFactory struct { + GetCertsFunc func(ctx context.Context) (map[keymanagementprovider.KMPMapKey][]*x509.Certificate, keymanagementprovider.KeyManagementProviderStatus, error) + GetKeysFunc func(ctx context.Context) (map[keymanagementprovider.KMPMapKey]crypto.PublicKey, keymanagementprovider.KeyManagementProviderStatus, error) + IsRefreshableFunc func() bool } func (f *TestKeyManagementProviderFactory) Create(_ string, _ config.KeyManagementProviderConfig, _ string) (keymanagementprovider.KeyManagementProvider, error) { var certMap map[keymanagementprovider.KMPMapKey][]*x509.Certificate var keyMap map[keymanagementprovider.KMPMapKey]crypto.PublicKey - return &TestKeyManagementProvider{certificates: certMap, keys: keyMap}, nil + return &TestKeyManagementProvider{ + certificates: certMap, + keys: keyMap, + GetCertificatesFunc: f.GetCertsFunc, + GetKeysFunc: f.GetKeysFunc, + IsRefreshableFunc: f.IsRefreshableFunc, + }, nil } diff --git a/pkg/keymanagementprovider/mocks/types.go b/pkg/keymanagementprovider/mocks/types.go index 246de21ba9..5cfa216459 100644 --- a/pkg/keymanagementprovider/mocks/types.go +++ b/pkg/keymanagementprovider/mocks/types.go @@ -24,20 +24,32 @@ import ( ) type TestKeyManagementProvider struct { - certificates map[keymanagementprovider.KMPMapKey][]*x509.Certificate - keys map[keymanagementprovider.KMPMapKey]crypto.PublicKey - status keymanagementprovider.KeyManagementProviderStatus - err error + certificates map[keymanagementprovider.KMPMapKey][]*x509.Certificate + keys map[keymanagementprovider.KMPMapKey]crypto.PublicKey + status keymanagementprovider.KeyManagementProviderStatus + err error + GetCertificatesFunc func(ctx context.Context) (map[keymanagementprovider.KMPMapKey][]*x509.Certificate, keymanagementprovider.KeyManagementProviderStatus, error) + GetKeysFunc func(ctx context.Context) (map[keymanagementprovider.KMPMapKey]crypto.PublicKey, keymanagementprovider.KeyManagementProviderStatus, error) + IsRefreshableFunc func() bool } func (c *TestKeyManagementProvider) GetCertificates(_ context.Context) (map[keymanagementprovider.KMPMapKey][]*x509.Certificate, keymanagementprovider.KeyManagementProviderStatus, error) { + if c.GetCertificatesFunc != nil { + return c.GetCertificatesFunc(context.Background()) + } return c.certificates, c.status, c.err } func (c *TestKeyManagementProvider) GetKeys(_ context.Context) (map[keymanagementprovider.KMPMapKey]crypto.PublicKey, keymanagementprovider.KeyManagementProviderStatus, error) { + if c.GetKeysFunc != nil { + return c.GetKeysFunc(context.Background()) + } return c.keys, c.status, c.err } func (c *TestKeyManagementProvider) IsRefreshable() bool { - return true + if c.IsRefreshableFunc != nil { + return c.IsRefreshableFunc() + } + return false } diff --git a/pkg/keymanagementprovider/refresh/factory.go b/pkg/keymanagementprovider/refresh/factory.go index 186951c163..53e3962f4e 100644 --- a/pkg/keymanagementprovider/refresh/factory.go +++ b/pkg/keymanagementprovider/refresh/factory.go @@ -38,12 +38,16 @@ func Register(name string, factory RefresherFactory) { // CreateRefresherFromConfig creates a new instance of the refresher using the provided configuration func CreateRefresherFromConfig(refresherConfig map[string]interface{}) (Refresher, error) { - refresherType, ok := refresherConfig["type"].(string) + refresherTypeValue, exists := refresherConfig["type"] + if !exists { + return nil, fmt.Errorf("refresherType not found in config") + } + refresherType, ok := refresherTypeValue.(string) if !ok { - return nil, fmt.Errorf("refresher type is not a string") + return nil, fmt.Errorf("refresherType is not a string") } if !ok || refresherType == "" { - return nil, fmt.Errorf("refresher type cannot be empty") + return nil, fmt.Errorf("refresherType cannot be empty") } factory, ok := refresherFactories[refresherType] if !ok { diff --git a/pkg/keymanagementprovider/refresh/factory_test.go b/pkg/keymanagementprovider/refresh/factory_test.go index a4a267cc2f..90accdec45 100644 --- a/pkg/keymanagementprovider/refresh/factory_test.go +++ b/pkg/keymanagementprovider/refresh/factory_test.go @@ -34,6 +34,10 @@ func (f *MockRefresher) GetResult() interface{} { return nil } +func (f *MockRefresher) GetStatus() interface{} { + return nil +} + func TestRefreshFactory_Create(t *testing.T) { Register("mockRefresher", &MockRefresher{}) refresherConfig := map[string]interface{}{ diff --git a/pkg/keymanagementprovider/refresh/kubeRefresh.go b/pkg/keymanagementprovider/refresh/kubeRefresh.go index acd967e0d2..8168e9137d 100644 --- a/pkg/keymanagementprovider/refresh/kubeRefresh.go +++ b/pkg/keymanagementprovider/refresh/kubeRefresh.go @@ -18,28 +18,22 @@ package refresh import ( "context" - "encoding/json" "fmt" "maps" "time" - configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" - re "github.com/ratify-project/ratify/errors" - "github.com/ratify-project/ratify/internal/constants" - cutils "github.com/ratify-project/ratify/pkg/controllers/utils" kmp "github.com/ratify-project/ratify/pkg/keymanagementprovider" "github.com/sirupsen/logrus" - 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" ) type KubeRefresher struct { - client.Client - Request ctrl.Request - Result ctrl.Result + Provider kmp.KeyManagementProvider + ProviderType string + ProviderRefreshInterval string + Resource string + Result ctrl.Result + Status kmp.KeyManagementProviderStatus } // Register registers the kubeRefresher factory @@ -51,99 +45,42 @@ func init() { func (kr *KubeRefresher) Refresh(ctx context.Context) error { logger := logrus.WithContext(ctx) - var resource = kr.Request.Name - var keyManagementProvider configv1beta1.KeyManagementProvider - - logger.Infof("reconciling cluster key management provider '%v'", resource) - - if err := kr.Get(ctx, kr.Request.NamespacedName, &keyManagementProvider); err != nil { - if apierrors.IsNotFound(err) { - logger.Infof("deletion detected, removing key management provider %v", resource) - kmp.DeleteResourceFromMap(resource) - } else { - logger.Error(err, "unable to fetch key management provider") - } - - kr.Result = ctrl.Result{} - - return 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 := kr.List(ctx, &certificateStoreList); err != nil { - logger.Error(err, "unable to list certificate stores") - kr.Result = ctrl.Result{} - return err - } - - 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 { - kmpErr := re.ErrorCodePluginInitFailure.WithError(err).WithDetail("Failed to create key management provider from CR") - - kmp.SetCertificateError(resource, kmpErr) - kmp.SetKeyError(resource, kmpErr) - writeKMProviderStatus(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) - kr.Request = ctrl.Request{} - return kmpErr - } - // fetch certificates and store in map - certificates, certAttributes, err := provider.GetCertificates(ctx) + certificates, certAttributes, err := kr.Provider.GetCertificates(ctx) if err != nil { - kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail(fmt.Sprintf("Unable to fetch certificates from key management provider [%s] of type [%s]", resource, keyManagementProvider.Spec.Type)) - - kmp.SetCertificateError(resource, err) - writeKMProviderStatus(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) - kr.Request = ctrl.Request{} - return kmpErr + kmp.SetCertificateError(kr.Resource, err) + return fmt.Errorf("error fetching certificates in KMProvider %v with %v provider, error: %w", kr.Resource, kr.ProviderType, err) } // fetch keys and store in map - keys, keyAttributes, err := provider.GetKeys(ctx) + keys, keyAttributes, err := kr.Provider.GetKeys(ctx) if err != nil { - kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail(fmt.Sprintf("Unable to fetch keys from key management provider [%s] of type [%s]", resource, keyManagementProvider.Spec.Type)) - - kmp.SetKeyError(resource, kmpErr) - writeKMProviderStatus(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) - kr.Request = ctrl.Request{} - return kmpErr + kmp.SetKeyError(kr.Resource, err) + return fmt.Errorf("error fetching keys in KMProvider %v with %v provider, error: %w", kr.Resource, kr.ProviderType, err) } - kmp.SaveSecrets(resource, keyManagementProvider.Spec.Type, keys, certificates) + + kmp.SaveSecrets(kr.Resource, kr.ProviderType, keys, certificates) // merge certificates and keys status into one maps.Copy(keyAttributes, certAttributes) - isFetchSuccessful = true - writeKMProviderStatus(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, nil, lastFetchedTime, keyAttributes) + kr.Status = keyAttributes - logger.Infof("%v certificate(s) & %v key(s) fetched for key management provider %v", len(certificates), len(keys), resource) + logger.Infof("%v certificate(s) & %v key(s) fetched for key management provider %v", len(certificates), len(keys), kr.Resource) - // returning empty result and no error to indicate we’ve successfully reconciled this object + // Resource is not refreshable, returning empty result and no error to indicate we’ve successfully reconciled this object // will not reconcile again unless resource is recreated - if !provider.IsRefreshable() { - kr.Request = ctrl.Request{} + if !kr.Provider.IsRefreshable() { return nil } // if interval is not set, disable refresh - if keyManagementProvider.Spec.RefreshInterval == "" { - kr.Result = ctrl.Result{} + if kr.ProviderRefreshInterval == "" { return nil } + // resource is refreshable, requeue after interval - intervalDuration, err := time.ParseDuration(keyManagementProvider.Spec.RefreshInterval) + intervalDuration, err := time.ParseDuration(kr.ProviderRefreshInterval) if err != nil { - logger.Error(err, "unable to parse interval duration") - kr.Result = ctrl.Result{} - return err + return fmt.Errorf("unable to parse interval duration: %w", err) } logger.Info("Reconciled KeyManagementProvider", "intervalDuration", intervalDuration) @@ -157,57 +94,55 @@ func (kr *KubeRefresher) GetResult() interface{} { return kr.Result } +func (kr *KubeRefresher) GetStatus() interface{} { + return kr.Status +} + // Create creates a new KubeRefresher instance func (kr *KubeRefresher) Create(config map[string]interface{}) (Refresher, error) { - client, ok := config["client"].(client.Client) + providerValue, exists := config["provider"] + if !exists { + return nil, fmt.Errorf("provider is required in config") + } + provider, ok := providerValue.(kmp.KeyManagementProvider) if !ok { - return nil, fmt.Errorf("client is required in config") + return nil, fmt.Errorf("provider is not of type KeyManagementProvider") } - request, ok := config["request"].(ctrl.Request) - if !ok { - return nil, fmt.Errorf("request is required in config") + provierTypeValue, exists := config["providerType"] + if !exists { + return nil, fmt.Errorf("providerType is required in config") } - return &KubeRefresher{ - Client: client, - Request: request, - }, nil -} + providerType, ok := provierTypeValue.(string) + if !ok { + return nil, fmt.Errorf("providerType is not of type string") + } -func writeKMProviderStatus(ctx context.Context, r client.StatusClient, keyManagementProvider *configv1beta1.KeyManagementProvider, logger *logrus.Entry, isSuccess bool, err *re.Error, operationTime metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) { - if isSuccess { - updateKMProviderSuccessStatus(keyManagementProvider, &operationTime, kmProviderStatus) - } else { - updateKMProviderErrorStatus(keyManagementProvider, err, &operationTime) + providerRefreshIntervaleValue, exists := config["providerRefreshInterval"] + if !exists { + return nil, fmt.Errorf("providerRefreshInterval is required in config") } - if statusErr := r.Status().Update(ctx, keyManagementProvider); statusErr != nil { - logger.Error(statusErr, ",unable to update key management provider error status") + + providerRefreshInterval, ok := providerRefreshIntervaleValue.(string) + if !ok { + return nil, fmt.Errorf("providerRefreshInterval is not of type string") } -} -// updateKMProviderErrorStatus updates the key management provider status with error, brief error and last fetched time -func updateKMProviderErrorStatus(keyManagementProvider *configv1beta1.KeyManagementProvider, err *re.Error, operationTime *metav1.Time) { - keyManagementProvider.Status.IsSuccess = false - keyManagementProvider.Status.Error = err.Error() - keyManagementProvider.Status.BriefError = err.GetConciseError(constants.MaxBriefErrLength) - keyManagementProvider.Status.LastFetchedTime = operationTime -} + resourceValue, exists := config["resource"] + if !exists { + return nil, fmt.Errorf("resource is required in config") + } -// 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 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 + resource, ok := resourceValue.(string) + if !ok { + return nil, fmt.Errorf("resource is not of type string") } + + return &KubeRefresher{ + Provider: provider, + ProviderType: providerType, + ProviderRefreshInterval: providerRefreshInterval, + Resource: resource, + }, nil } diff --git a/pkg/keymanagementprovider/refresh/kubeRefreshNamespaced.go b/pkg/keymanagementprovider/refresh/kubeRefreshNamespaced.go deleted file mode 100644 index 8bd4ade205..0000000000 --- a/pkg/keymanagementprovider/refresh/kubeRefreshNamespaced.go +++ /dev/null @@ -1,211 +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 refresh - -import ( - "context" - "encoding/json" - "fmt" - "maps" - "time" - - configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" - re "github.com/ratify-project/ratify/errors" - "github.com/ratify-project/ratify/internal/constants" - cutils "github.com/ratify-project/ratify/pkg/controllers/utils" - kmp "github.com/ratify-project/ratify/pkg/keymanagementprovider" - "github.com/sirupsen/logrus" - 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" -) - -type KubeRefresherNamespaced struct { - client.Client - Request ctrl.Request - Result ctrl.Result -} - -// Register registers the kubeRefresherNamespaced factory -func init() { - Register(KubeRefresherNamespacedType, &KubeRefresherNamespaced{}) -} - -// Refresh the certificates/keys for the key management provider by calling the GetCertificates and GetKeys methods -func (kr *KubeRefresherNamespaced) Refresh(ctx context.Context) error { - logger := logrus.WithContext(ctx) - - var resource = kr.Request.NamespacedName.String() - var keyManagementProvider configv1beta1.NamespacedKeyManagementProvider - - logger.Infof("reconciling namespaced key management provider '%v'", resource) - - if err := kr.Get(ctx, kr.Request.NamespacedName, &keyManagementProvider); err != nil { - if apierrors.IsNotFound(err) { - logger.Infof("deletion detected, removing key management provider %v", resource) - kmp.DeleteResourceFromMap(resource) - } else { - logger.Error(err, "unable to fetch key management provider") - } - - kr.Result = ctrl.Result{} - - return 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 := kr.List(ctx, &certificateStoreList); err != nil { - logger.Error(err, "unable to list certificate stores") - kr.Result = ctrl.Result{} - return 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 { - kmpErr := re.ErrorCodePluginInitFailure.WithError(err).WithDetail("Failed to create key management provider from CR") - - kmp.SetCertificateError(resource, kmpErr) - kmp.SetKeyError(resource, kmpErr) - writeKMProviderStatusNamespaced(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) - kr.Result = ctrl.Result{} - return kmpErr - } - - // fetch certificates and store in map - certificates, certAttributes, err := provider.GetCertificates(ctx) - if err != nil { - kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail(fmt.Sprintf("Unable to fetch certificates from key management provider: %s of type: %s", resource, keyManagementProvider.Spec.Type)) - - kmp.SetCertificateError(resource, kmpErr) - writeKMProviderStatusNamespaced(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) - kr.Result = ctrl.Result{} - return kmpErr - } - - // fetch keys and store in map - keys, keyAttributes, err := provider.GetKeys(ctx) - if err != nil { - kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail(fmt.Sprintf("Unable to fetch keys from key management provider: %s of type: %s", resource, keyManagementProvider.Spec.Type)) - - kmp.SetKeyError(resource, kmpErr) - writeKMProviderStatusNamespaced(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) - kr.Result = ctrl.Result{} - return kmpErr - } - kmp.SaveSecrets(resource, keyManagementProvider.Spec.Type, keys, certificates) - // merge certificates and keys status into one - maps.Copy(keyAttributes, certAttributes) - isFetchSuccessful = true - writeKMProviderStatusNamespaced(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, nil, lastFetchedTime, keyAttributes) - - logger.Infof("%v certificate(s) & %v key(s) fetched for key management provider %v", len(certificates), len(keys), resource) - - if !provider.IsRefreshable() { - kr.Result = ctrl.Result{} - return nil - } - - // if interval is not set, disable refresh - if keyManagementProvider.Spec.RefreshInterval == "" { - kr.Result = ctrl.Result{} - return nil - } - - intervalDuration, err := time.ParseDuration(keyManagementProvider.Spec.RefreshInterval) - if err != nil { - logger.Error(err, "unable to parse interval duration") - kr.Result = ctrl.Result{} - return err - } - - logger.Info("Reconciled KeyManagementProvider", "intervalDuration", intervalDuration) - kr.Result = ctrl.Result{RequeueAfter: intervalDuration} - - return nil -} - -// GetResult returns the result of the refresh as ctrl.Result -func (kr *KubeRefresherNamespaced) GetResult() interface{} { - return kr.Result -} - -// Create creates a new instance of KubeRefresherNamespaced -func (kr *KubeRefresherNamespaced) Create(config map[string]interface{}) (Refresher, error) { - client, ok := config["client"].(client.Client) - if !ok { - return nil, fmt.Errorf("client is required in config") - } - - request, ok := config["request"].(ctrl.Request) - if !ok { - return nil, fmt.Errorf("request is required in config") - } - - return &KubeRefresherNamespaced{ - Client: client, - Request: request, - }, nil -} - -// writeKMProviderStatus updates the status of the key management provider resource -func writeKMProviderStatusNamespaced(ctx context.Context, r client.StatusClient, keyManagementProvider *configv1beta1.NamespacedKeyManagementProvider, logger *logrus.Entry, isSuccess bool, err *re.Error, operationTime metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) { - if isSuccess { - updateKMProviderSuccessStatusNamespaced(keyManagementProvider, &operationTime, kmProviderStatus) - } else { - updateKMProviderErrorStatusNamespaced(keyManagementProvider, err, &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 updateKMProviderErrorStatusNamespaced(keyManagementProvider *configv1beta1.NamespacedKeyManagementProvider, err *re.Error, operationTime *metav1.Time) { - keyManagementProvider.Status.IsSuccess = false - keyManagementProvider.Status.Error = err.Error() - keyManagementProvider.Status.BriefError = err.GetConciseError(constants.MaxBriefErrLength) - 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 updateKMProviderSuccessStatusNamespaced(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/keymanagementprovider/refresh/kubeRefreshNamespaced_test.go b/pkg/keymanagementprovider/refresh/kubeRefreshNamespaced_test.go deleted file mode 100644 index 944f54884b..0000000000 --- a/pkg/keymanagementprovider/refresh/kubeRefreshNamespaced_test.go +++ /dev/null @@ -1,378 +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 refresh - -import ( - "context" - "reflect" - "testing" - "time" - - configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" - re "github.com/ratify-project/ratify/errors" - "github.com/ratify-project/ratify/pkg/keymanagementprovider" - "github.com/ratify-project/ratify/pkg/keymanagementprovider/mocks" - test "github.com/ratify-project/ratify/pkg/utils" - "github.com/sirupsen/logrus" - 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/client/fake" -) - -func TestKubeRefresherNamespaced_Refresh(t *testing.T) { - tests := []struct { - name string - provider *configv1beta1.NamespacedKeyManagementProvider - request ctrl.Request - mockClient bool - expectedResult ctrl.Result - expectedError bool - }{ - { - name: "Non-refreshable", - provider: &configv1beta1.NamespacedKeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.NamespacedKeyManagementProviderSpec{ - 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"}`), - }, - }, - }, - request: ctrl.Request{ - NamespacedName: client.ObjectKey{ - Namespace: "", - Name: "kmpName", - }, - }, - expectedResult: ctrl.Result{}, - expectedError: false, - }, - { - name: "Disabled", - provider: &configv1beta1.NamespacedKeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.NamespacedKeyManagementProviderSpec{ - Type: "test-kmp", - RefreshInterval: "", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), - }, - }, - }, - request: ctrl.Request{ - NamespacedName: client.ObjectKey{ - Namespace: "", - Name: "kmpName", - }, - }, - expectedResult: ctrl.Result{}, - expectedError: false, - }, - { - name: "Refreshable", - provider: &configv1beta1.NamespacedKeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.NamespacedKeyManagementProviderSpec{ - Type: "test-kmp", - RefreshInterval: "1m", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), - }, - }, - }, - request: ctrl.Request{ - NamespacedName: client.ObjectKey{ - Namespace: "", - Name: "kmpName", - }, - }, - expectedResult: ctrl.Result{RequeueAfter: time.Minute}, - expectedError: false, - }, - { - name: "Invalid Interval", - provider: &configv1beta1.NamespacedKeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.NamespacedKeyManagementProviderSpec{ - Type: "", - RefreshInterval: "1mm", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), - }, - }, - }, - request: ctrl.Request{ - NamespacedName: client.ObjectKey{ - Namespace: "", - Name: "kmpName", - }, - }, - expectedResult: ctrl.Result{}, - expectedError: true, - }, - { - name: "IsNotFound", - provider: &configv1beta1.NamespacedKeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.NamespacedKeyManagementProviderSpec{ - Type: "", - RefreshInterval: "", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), - }, - }, - }, - expectedResult: ctrl.Result{}, - expectedError: false, - }, - { - name: "UnableToFetchKMP", - mockClient: true, - expectedError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var client client.Client - - if tt.mockClient { - client = mocks.TestClient{} - } else { - scheme, _ := test.CreateScheme() - client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.provider).Build() - } - - kr := &KubeRefresherNamespaced{ - Client: client, - Request: tt.request, - } - err := kr.Refresh(context.Background()) - result := kr.GetResult() - if !reflect.DeepEqual(result, tt.expectedResult) { - t.Fatalf("Expected nil but got %v with error %v", result, err) - } - if tt.expectedError && err == nil { - t.Fatalf("Expected error but got nil") - } - }) - } -} - -func TestKubeRefresherNamespaced_Create(t *testing.T) { - tests := []struct { - name string - config map[string]interface{} - expectedError bool - }{ - { - name: "Success", - config: map[string]interface{}{ - "client": &mocks.TestClient{}, - "request": ctrl.Request{}, - }, - expectedError: false, - }, - { - name: "ClientMissing", - config: map[string]interface{}{ - "request": ctrl.Request{}, - }, - expectedError: true, - }, - { - name: "RequestMissing", - config: map[string]interface{}{ - "client": &mocks.TestClient{}, - }, - expectedError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - kr := &KubeRefresherNamespaced{} - _, err := kr.Create(tt.config) - if tt.expectedError && err == nil { - t.Fatalf("Expected error but got nil") - } - }) - } -} - -func TestKMProviderUpdateErrorStatusNamespaced(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 := re.ErrorCodeUnknown.WithDetail("it's a long error from unit test") - lastFetchedTime := metav1.Now() - updateKMProviderErrorStatusNamespaced(&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.Error() { - t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedErr, keyManagementProvider.Status.Error) - } - if keyManagementProvider.Status.BriefError != expectedErr.GetConciseError(150) { - t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedErr.GetConciseError(150), 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)) - } -} - -func TestKMProviderUpdateSuccessStatusNamespaced(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, - } - - updateKMProviderSuccessStatusNamespaced(&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") - } -} - -func TestKMProviderUpdateSuccessStatusNamespaced_emptyProperties(t *testing.T) { - lastFetchedTime := metav1.Now() - status := configv1beta1.NamespacedKeyManagementProviderStatus{ - IsSuccess: false, - Error: "error from last operation", - } - keyManagementProvider := configv1beta1.NamespacedKeyManagementProvider{ - Status: status, - } - - updateKMProviderSuccessStatusNamespaced(&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 TestWriteKMProviderStatusNamespaced(t *testing.T) { - logger := logrus.WithContext(context.Background()) - lastFetchedTime := metav1.Now() - testCases := []struct { - name string - isSuccess bool - kmProvider *configv1beta1.NamespacedKeyManagementProvider - errString string - expectedErrString 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", - expectedErrString: "UNKNOWN: 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) { - err := re.ErrorCodeUnknown.WithDetail(tc.errString) - writeKMProviderStatusNamespaced(context.Background(), tc.reconciler, tc.kmProvider, logger, tc.isSuccess, &err, 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.expectedErrString { - t.Fatalf("Expected Error to be %+v , actual %+v", tc.expectedErrString, tc.kmProvider.Status.Error) - } - }) - } -} diff --git a/pkg/keymanagementprovider/refresh/kubeRefresh_test.go b/pkg/keymanagementprovider/refresh/kubeRefresh_test.go index 29319abcd6..ccf6317252 100644 --- a/pkg/keymanagementprovider/refresh/kubeRefresh_test.go +++ b/pkg/keymanagementprovider/refresh/kubeRefresh_test.go @@ -18,168 +18,120 @@ package refresh import ( "context" + "crypto" + "crypto/x509" + "errors" "reflect" "testing" "time" - configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" - re "github.com/ratify-project/ratify/errors" "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/config" _ "github.com/ratify-project/ratify/pkg/keymanagementprovider/inline" - "github.com/ratify-project/ratify/pkg/keymanagementprovider/mocks" - test "github.com/ratify-project/ratify/pkg/utils" - "github.com/sirupsen/logrus" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" + mock "github.com/ratify-project/ratify/pkg/keymanagementprovider/mocks" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" ) func TestKubeRefresher_Refresh(t *testing.T) { tests := []struct { - name string - provider *configv1beta1.KeyManagementProvider - request ctrl.Request - mockClient bool - expectedResult ctrl.Result - expectedError bool + name string + providerRawParameters []byte + providerType string + providerRefreshInterval string + GetCertsFunc func(_ context.Context) (map[keymanagementprovider.KMPMapKey][]*x509.Certificate, keymanagementprovider.KeyManagementProviderStatus, error) + GetKeysFunc func(_ context.Context) (map[keymanagementprovider.KMPMapKey]crypto.PublicKey, keymanagementprovider.KeyManagementProviderStatus, error) + IsRefreshableFunc func() bool + expectedResult ctrl.Result + expectedError bool }{ { - name: "Non-refreshable", - provider: &configv1beta1.KeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - 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"}`), - }, - }, - }, - request: ctrl.Request{ - NamespacedName: client.ObjectKey{ - Namespace: "", - Name: "kmpName", - }, - }, - expectedResult: ctrl.Result{}, - expectedError: false, + name: "Non-refreshable", + providerRawParameters: []byte(`{"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"}`), + providerType: "inline", + IsRefreshableFunc: func() bool { return false }, + expectedResult: ctrl.Result{}, + expectedError: false, }, { - name: "Disabled", - provider: &configv1beta1.KeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.KeyManagementProviderSpec{ - Type: "test-kmp", - RefreshInterval: "", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), - }, - }, - }, - request: ctrl.Request{ - NamespacedName: client.ObjectKey{ - Namespace: "", - Name: "kmpName", - }, - }, - expectedResult: ctrl.Result{}, - expectedError: false, + name: "Disabled", + providerRawParameters: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), + providerType: "test-kmp", + providerRefreshInterval: "", + IsRefreshableFunc: func() bool { return true }, + expectedResult: ctrl.Result{}, + expectedError: false, }, { - name: "Refreshable", - provider: &configv1beta1.KeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.KeyManagementProviderSpec{ - Type: "test-kmp", - RefreshInterval: "1m", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), - }, - }, - }, - request: ctrl.Request{ - NamespacedName: client.ObjectKey{ - Namespace: "", - Name: "kmpName", - }, - }, - expectedResult: ctrl.Result{RequeueAfter: time.Minute}, - expectedError: false, + name: "Refreshable", + providerRawParameters: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), + providerType: "test-kmp", + providerRefreshInterval: "1m", + IsRefreshableFunc: func() bool { return true }, + expectedResult: ctrl.Result{RequeueAfter: time.Minute}, + expectedError: false, }, { - name: "Invalid Interval", - provider: &configv1beta1.KeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.KeyManagementProviderSpec{ - Type: "", - RefreshInterval: "1mm", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), - }, - }, - }, - request: ctrl.Request{ - NamespacedName: client.ObjectKey{ - Namespace: "", - Name: "kmpName", - }, - }, - expectedResult: ctrl.Result{}, - expectedError: true, + name: "Invalid Interval", + providerRawParameters: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), + providerType: "test-kmp", + providerRefreshInterval: "1mm", + IsRefreshableFunc: func() bool { return true }, + expectedResult: ctrl.Result{}, + expectedError: true, }, { - name: "IsNotFound", - provider: &configv1beta1.KeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.KeyManagementProviderSpec{ - Type: "", - RefreshInterval: "", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), - }, - }, + name: "Error Fetching Certificates", + GetCertsFunc: func(_ context.Context) (map[keymanagementprovider.KMPMapKey][]*x509.Certificate, keymanagementprovider.KeyManagementProviderStatus, error) { + // Example behavior: Return an error + return nil, nil, errors.New("test error") }, - expectedResult: ctrl.Result{}, - expectedError: false, + providerRawParameters: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), + providerType: "test-kmp-error", + IsRefreshableFunc: func() bool { return true }, + expectedError: true, }, { - name: "UnableToFetchKMP", - mockClient: true, - expectedError: true, + name: "Error Fetching Keys", + GetKeysFunc: func(_ context.Context) (map[keymanagementprovider.KMPMapKey]crypto.PublicKey, keymanagementprovider.KeyManagementProviderStatus, error) { + // Example behavior: Return an error + return nil, nil, errors.New("test error") + }, + providerRawParameters: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), + providerType: "test-kmp-error", + IsRefreshableFunc: func() bool { return true }, + expectedError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var client client.Client - - if tt.mockClient { - client = mocks.TestClient{} + var factory mock.TestKeyManagementProviderFactory + + if tt.GetCertsFunc != nil { + factory = mock.TestKeyManagementProviderFactory{ + GetCertsFunc: tt.GetCertsFunc, + IsRefreshableFunc: tt.IsRefreshableFunc, + } + } else if tt.GetKeysFunc != nil { + factory = mock.TestKeyManagementProviderFactory{ + GetKeysFunc: tt.GetKeysFunc, + IsRefreshableFunc: tt.IsRefreshableFunc, + } } else { - scheme, _ := test.CreateScheme() - client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.provider).Build() + factory = mock.TestKeyManagementProviderFactory{ + IsRefreshableFunc: tt.IsRefreshableFunc, + } } + provider, _ := factory.Create("", config.KeyManagementProviderConfig{}, "") + kr := &KubeRefresher{ - Client: client, - Request: tt.request, + Provider: provider, + ProviderType: tt.providerType, + ProviderRefreshInterval: tt.providerRefreshInterval, + Resource: "kmpname", } + err := kr.Refresh(context.Background()) result := kr.GetResult() if !reflect.DeepEqual(result, tt.expectedResult) { @@ -193,32 +145,108 @@ func TestKubeRefresher_Refresh(t *testing.T) { } func TestKubeRefresher_Create(t *testing.T) { + factory := mock.TestKeyManagementProviderFactory{} + provider, _ := factory.Create("", config.KeyManagementProviderConfig{}, "") + tests := []struct { - name string - config map[string]interface{} - expectedError bool + name string + config map[string]interface{} + expectedError bool + expectedErrMsg string }{ { name: "Success", config: map[string]interface{}{ - "client": &mocks.TestClient{}, - "request": ctrl.Request{}, + "provider": provider, + "providerType": "inline", + "providerRefreshInterval": "", + "resource": "kmpname", }, expectedError: false, }, { - name: "ClientMissing", + name: "ProviderMissing", + config: map[string]interface{}{ + "providerRefreshInterval": "", + "providerType": "inline", + "resource": "kmpname", + }, + expectedError: true, + expectedErrMsg: "provider is required in config", + }, + { + name: "ProviderTypeMissing", + config: map[string]interface{}{ + "provider": provider, + "providerRefreshInterval": "", + "resource": "kmpname", + }, + expectedError: true, + expectedErrMsg: "providerType is required in config", + }, + { + name: "ProviderRefreshIntervalMissing", config: map[string]interface{}{ - "request": ctrl.Request{}, + "provider": provider, + "providerType": "inline", + "resource": "kmpname", }, - expectedError: true, + expectedError: true, + expectedErrMsg: "providerRefreshInterval is required in config", }, { - name: "RequestMissing", + name: "ResourceMissing", config: map[string]interface{}{ - "client": &mocks.TestClient{}, + "provider": provider, + "providerType": "inline", + "providerRefreshInterval": "", }, - expectedError: true, + expectedError: true, + expectedErrMsg: "resource is required in config", + }, + { + name: "ProviderInvalid", + config: map[string]interface{}{ + "provider": 123, + "providerType": "inline", + "providerRefreshInterval": "", + "resource": "kmpname", + }, + expectedError: true, + expectedErrMsg: "provider is not of type KeyManagementProvider", + }, + { + name: "ProviderTypeInvalid", + config: map[string]interface{}{ + "provider": provider, + "providerType": 123, + "providerRefreshInterval": "", + "resource": "kmpname", + }, + expectedError: true, + expectedErrMsg: "providerType is not of type string", + }, + { + name: "ProviderRefreshIntervalInvalid", + config: map[string]interface{}{ + "provider": provider, + "providerType": "inline", + "providerRefreshInterval": 123, + "resource": "kmpname", + }, + expectedError: true, + expectedErrMsg: "providerRefreshInterval is not of type string", + }, + { + name: "ResourceInvalid", + config: map[string]interface{}{ + "provider": provider, + "providerType": "inline", + "providerRefreshInterval": "", + "resource": 123, + }, + expectedError: true, + expectedErrMsg: "resource is not of type string", }, } @@ -226,157 +254,42 @@ func TestKubeRefresher_Create(t *testing.T) { t.Run(tt.name, func(t *testing.T) { kr := &KubeRefresher{} _, err := kr.Create(tt.config) - if tt.expectedError && err == nil { - t.Fatalf("Expected error but got nil") + if err != nil && err.Error() != tt.expectedErrMsg { + t.Errorf("expected error message '%s', got '%s'", tt.expectedErrMsg, err.Error()) + } + if err == nil && tt.expectedErrMsg != "" { + t.Errorf("expected error message '%s', got no error", tt.expectedErrMsg) } }) } } - -func TestKMProviderUpdateErrorStatus(t *testing.T) { - var parametersString = "{\"certs\":{\"name\":\"certName\"}}" - var kmProviderStatus = []byte(parametersString) - - status := configv1beta1.KeyManagementProviderStatus{ - IsSuccess: true, - Properties: runtime.RawExtension{ - Raw: kmProviderStatus, - }, - } - keyManagementProvider := configv1beta1.KeyManagementProvider{ - Status: status, +func TestKubeRefresher_GetResult(t *testing.T) { + kr := &KubeRefresher{ + Result: ctrl.Result{RequeueAfter: time.Minute}, } - expectedErr := re.ErrorCodeUnknown.WithDetail("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.Error() { - t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedErr, keyManagementProvider.Status.Error) - } - if keyManagementProvider.Status.BriefError != expectedErr.GetConciseError(150) { - t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedErr.GetConciseError(150), 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.KeyManagementProviderStatus{ - IsSuccess: false, - Error: "error from last operation", - } - keyManagementProvider := configv1beta1.KeyManagementProvider{ - 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) - } + result := kr.GetResult() + expectedResult := ctrl.Result{RequeueAfter: time.Minute} - //make sure properties of last cached cert was updated - if len(keyManagementProvider.Status.Properties.Raw) == 0 { - t.Fatalf("Properties should not be empty") + if !reflect.DeepEqual(result, expectedResult) { + t.Fatalf("Expected result %v, but got %v", expectedResult, result) } } - -// TestKMProviderUpdateSuccessStatus tests the updateSuccessStatus method with empty properties -func TestKMProviderUpdateSuccessStatus_emptyProperties(t *testing.T) { - lastFetchedTime := metav1.Now() - status := configv1beta1.KeyManagementProviderStatus{ - IsSuccess: false, - Error: "error from last operation", - } - keyManagementProvider := configv1beta1.KeyManagementProvider{ - 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.KeyManagementProvider - errString string - expectedErrString string - reconciler client.StatusClient - }{ - { - name: "success status", - isSuccess: true, - errString: "", - kmProvider: &configv1beta1.KeyManagementProvider{}, - reconciler: &test.MockStatusClient{}, - }, - { - name: "error status", - isSuccess: false, - kmProvider: &configv1beta1.KeyManagementProvider{}, - errString: "a long error string that exceeds the max length of 150 characters", - expectedErrString: "UNKNOWN: a long error string that exceeds the max length of 150 characters", - reconciler: &test.MockStatusClient{}, - }, - { - name: "status update failed", - isSuccess: true, - kmProvider: &configv1beta1.KeyManagementProvider{}, - reconciler: &test.MockStatusClient{ - UpdateFailed: true, - }, +func TestKubeRefresher_GetStatus(t *testing.T) { + kr := &KubeRefresher{ + Status: keymanagementprovider.KeyManagementProviderStatus{ + "attribute1": "value1", + "attribute2": "value2", }, } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - err := re.ErrorCodeUnknown.WithDetail(tc.errString) - writeKMProviderStatus(context.Background(), tc.reconciler, tc.kmProvider, logger, tc.isSuccess, &err, lastFetchedTime, nil) - - if tc.kmProvider.Status.IsSuccess != tc.isSuccess { - t.Fatalf("Expected isSuccess to be: %+v, actual: %+v", tc.isSuccess, tc.kmProvider.Status.IsSuccess) - } + status := kr.GetStatus() + expectedStatus := keymanagementprovider.KeyManagementProviderStatus{ + "attribute1": "value1", + "attribute2": "value2", + } - if tc.kmProvider.Status.Error != tc.expectedErrString { - t.Fatalf("Expected Error to be: %+v, actual: %+v", tc.expectedErrString, tc.kmProvider.Status.Error) - } - }) + if !reflect.DeepEqual(status, expectedStatus) { + t.Fatalf("Expected status %v, but got %v", expectedStatus, status) } } diff --git a/pkg/keymanagementprovider/refresh/refresh.go b/pkg/keymanagementprovider/refresh/refresh.go index 78e32b12d4..462d38691c 100644 --- a/pkg/keymanagementprovider/refresh/refresh.go +++ b/pkg/keymanagementprovider/refresh/refresh.go @@ -21,8 +21,7 @@ import ( ) const ( - KubeRefresherType = "kubeRefresher" - KubeRefresherNamespacedType = "kubeRefresherNamespaced" + KubeRefresherType = "kubeRefresher" ) // Refresher is an interface that defines methods to be implemented by a each refresher @@ -31,4 +30,6 @@ type Refresher interface { Refresh(ctx context.Context) error // GetResult is a method that returns the result of the refresh GetResult() interface{} + // GetStatus is a method that returns the status of the refresh + GetStatus() interface{} }