From 50b1a91c2ec6a0de3c2024667bf3b427e7365ec2 Mon Sep 17 00:00:00 2001 From: Dion Gionet Mallet Date: Fri, 17 Feb 2023 16:55:21 -0500 Subject: [PATCH] feat(controller): added modifieddate check before fetching sensitive data --- api/v1alpha1/dvlssecret_types.go | 3 +- api/v1alpha1/zz_generated.deepcopy.go | 1 + .../dvls.devolutions.com_dvlssecrets.yaml | 5 + config/manager/manager.yaml | 14 ++- controllers/dvlssecret_controller.go | 95 +++++++++++-------- go.mod | 2 +- go.sum | 4 +- main.go | 6 +- 8 files changed, 82 insertions(+), 48 deletions(-) diff --git a/api/v1alpha1/dvlssecret_types.go b/api/v1alpha1/dvlssecret_types.go index 16d3d10..f37c3a4 100644 --- a/api/v1alpha1/dvlssecret_types.go +++ b/api/v1alpha1/dvlssecret_types.go @@ -37,7 +37,8 @@ type DvlsSecretSpec struct { type DvlsSecretStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file - Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` + EntryModifiedDate metav1.Time `json:"entryModifiedDate"` } //+kubebuilder:object:root=true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 716420b..25a9b3b 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -110,6 +110,7 @@ func (in *DvlsSecretStatus) DeepCopyInto(out *DvlsSecretStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + in.EntryModifiedDate.DeepCopyInto(&out.EntryModifiedDate) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DvlsSecretStatus. diff --git a/config/crd/bases/dvls.devolutions.com_dvlssecrets.yaml b/config/crd/bases/dvls.devolutions.com_dvlssecrets.yaml index 227d06a..95e2f89 100644 --- a/config/crd/bases/dvls.devolutions.com_dvlssecrets.yaml +++ b/config/crd/bases/dvls.devolutions.com_dvlssecrets.yaml @@ -114,6 +114,11 @@ spec: - type type: object type: array + entryModifiedDate: + format: date-time + type: string + required: + - entryModifiedDate type: object type: object served: true diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index c80ae02..7e6603b 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -37,7 +37,7 @@ spec: control-plane: controller-manager spec: # TODO(user): Uncomment the following code to configure the nodeAffinity expression - # according to the platforms which are supported by your solution. + # according to the platforms which are supported by your solution. # It is considered best practice to support multiple architectures. You can # build your manager image using the makefile target docker-buildx. # affinity: @@ -98,5 +98,17 @@ spec: requests: cpu: 10m memory: 64Mi + env: + - name: DEVO_OPERATOR_DVLS_APPID + value: "00000000-0000-0000-0000-000000000000" + - name: DEVO_OPERATOR_DVLS_BASEURI + value: "https://your-dvls-instance.com" + - name: DEVO_OPERATOR_REQUEUE_DURATION + value: 60s + - name: DEVO_OPERATOR_DVLS_APPSECRET + valueFrom: + secretKeyRef: + key: secret + name: appid serviceAccountName: controller-manager terminationGracePeriodSeconds: 10 diff --git a/controllers/dvlssecret_controller.go b/controllers/dvlssecret_controller.go index a262d5f..17f62a4 100644 --- a/controllers/dvlssecret_controller.go +++ b/controllers/dvlssecret_controller.go @@ -19,7 +19,6 @@ package controllers import ( "context" "fmt" - "reflect" "time" corev1 "k8s.io/api/core/v1" @@ -87,8 +86,9 @@ func (r *DvlsSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, fmt.Errorf("failed to get DvlsSecret object, %w", err) } - if dvlsSecret.Status.Conditions == nil || len(dvlsSecret.Status.Conditions) == 0 { + if dvlsSecret.Status.Conditions == nil || len(dvlsSecret.Status.Conditions) == 0 || dvlsSecret.Status.EntryModifiedDate.IsZero() { meta.SetStatusCondition(&dvlsSecret.Status.Conditions, v1.Condition{Type: statusAvailableDvlsSecret, Status: v1.ConditionUnknown, Reason: "Reconciling"}) + dvlsSecret.Status.EntryModifiedDate = v1.Date(0001, time.January, 1, 1, 1, 1, 1, time.UTC) if err := r.Status().Update(ctx, dvlsSecret); err != nil { return ctrl.Result{}, fmt.Errorf("failed to update DvlsSecret status, %w", err) } @@ -118,7 +118,26 @@ func (r *DvlsSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, nil } - secret, err := DvlsClient.GetSecret(dvlsSecret.Spec.EntryID) + kSecret := &corev1.Secret{} + err = r.Get(ctx, req.NamespacedName, kSecret) + if err != nil && !apierrors.IsNotFound(err) { + return ctrl.Result{}, fmt.Errorf("failed to get kubernetes secret object, %w", err) + } + kSecretNotFound := apierrors.IsNotFound(err) + + var entryTime, secretTime time.Time + if !dvlsSecret.Status.EntryModifiedDate.IsZero() && entry.ModifiedDate != nil { + secretTime = dvlsSecret.Status.EntryModifiedDate.Time + entryTime = entry.ModifiedDate.Time + } + + if entryTime.Equal(secretTime) && !kSecretNotFound { + return ctrl.Result{ + RequeueAfter: RequeueDuration, + }, nil + } + + secret, err := DvlsClient.GetEntryCredentialsPassword(entry) if err != nil { log.Error(err, "unable to fetch dvls secret", "entryId", dvlsSecret.Spec.EntryID) meta.SetStatusCondition(&dvlsSecret.Status.Conditions, v1.Condition{Type: statusDegradedDvlsSecret, Status: v1.ConditionTrue, Reason: "Reconciling", Message: "Unable to fetch secret on DVLS instance"}) @@ -129,34 +148,32 @@ func (r *DvlsSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) } secretMap := make(map[string]string) secretMap["entry-id"] = secret.ID - secretMap["username"] = secret.Username - secretMap["password"] = secret.Password + secretMap["entry-name"] = secret.EntryName + secretMap["username"] = secret.Credentials.Username + if secret.Credentials.Password != nil { + secretMap["password"] = *secret.Credentials.Password + } - kSecret := &corev1.Secret{} - err = r.Get(ctx, req.NamespacedName, kSecret) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Kubernetes secret not found, creating") - kSecret.ObjectMeta = v1.ObjectMeta{ - Name: req.Name, - Namespace: req.Namespace, - } - err := ctrl.SetControllerReference(dvlsSecret, kSecret, r.Scheme) - if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to set kubernetes secret owner, %w", err) - } - - kSecret.Type = corev1.SecretType(dvlsSecretType) - kSecret.StringData = secretMap - - err = r.Create(ctx, kSecret) - if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to create kubernetes secret, %w", err) - } + if kSecretNotFound { + log.Info("Kubernetes secret not found, creating") + kSecret.ObjectMeta = v1.ObjectMeta{ + Name: req.Name, + Namespace: req.Namespace, + } + err := ctrl.SetControllerReference(dvlsSecret, kSecret, r.Scheme) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to set kubernetes secret owner, %w", err) + } - return ctrl.Result{}, nil + kSecret.Type = corev1.SecretType(dvlsSecretType) + kSecret.StringData = secretMap + + err = r.Create(ctx, kSecret) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to create kubernetes secret, %w", err) } - return ctrl.Result{}, fmt.Errorf("failed to get kubernetes secret object, %w", err) + + return ctrl.Result{}, nil } var owned bool @@ -171,23 +188,21 @@ func (r *DvlsSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, fmt.Errorf("found existing kubernetes secret with name %s in namespace %s but is either not the correct type or not owned by the DvlsSecret resource. Either delete the existing secret or use a different name", kSecret.GetName(), kSecret.GetNamespace()) } - kSecretDataMapString := make(map[string]string) - for k, v := range kSecret.Data { - kSecretDataMapString[k] = string(v) + kSecret.StringData = secretMap + err = r.Update(ctx, kSecret) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to get update kubernetes secret object, %w", err) } - - if !reflect.DeepEqual(kSecretDataMapString, secretMap) { - kSecret.StringData = secretMap - err := r.Update(ctx, kSecret) - if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to get update kubernetes secret object, %w", err) - } - - log.Info("updated secret") + log.Info("updated secret") + err = r.Get(ctx, req.NamespacedName, dvlsSecret) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to get DvlsSecret object, %w", err) } meta.SetStatusCondition(&dvlsSecret.Status.Conditions, v1.Condition{Type: statusAvailableDvlsSecret, Status: v1.ConditionTrue, Reason: "Reconciling"}) meta.RemoveStatusCondition(&dvlsSecret.Status.Conditions, statusDegradedDvlsSecret) + dvlsSecret.Status.EntryModifiedDate = v1.NewTime(entryTime) + if err := r.Status().Update(ctx, dvlsSecret); err != nil { return ctrl.Result{}, fmt.Errorf("failed to update DvlsSecret status, %w", err) } diff --git a/go.mod b/go.mod index 1fd16d4..5884dc3 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Devolutions/devolutions-kubernetes-operator go 1.19 require ( - github.com/Devolutions/go-dvls v0.2.0 + github.com/Devolutions/go-dvls v0.4.0 github.com/onsi/ginkgo/v2 v2.4.0 github.com/onsi/gomega v1.23.0 k8s.io/api v0.26.1 diff --git a/go.sum b/go.sum index 4700fff..afc0f03 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Devolutions/go-dvls v0.2.0 h1:es+PdbFiz2EsrgyaTRMa5AvlUVRKBl9fxyC16jpclWA= -github.com/Devolutions/go-dvls v0.2.0/go.mod h1:LWmMkJugG1/aUH5oXwHN13EvJC+YhvyKH/11pPecPtw= +github.com/Devolutions/go-dvls v0.4.0 h1:7zNbQ2LtfDIVTGiqVP3qntGPJCEadkgDXVaf0eIWdEI= +github.com/Devolutions/go-dvls v0.4.0/go.mod h1:LWmMkJugG1/aUH5oXwHN13EvJC+YhvyKH/11pPecPtw= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= diff --git a/main.go b/main.go index 4ac3810..3ead48d 100644 --- a/main.go +++ b/main.go @@ -117,14 +117,14 @@ func main() { os.Exit(1) } - dvlsClient, dvlsUser, err := dvls.NewClient(appId, appSecret, dvlsBaseUri) + dvlsClient, err := dvls.NewClient(appId, appSecret, dvlsBaseUri) if err != nil { setupLog.Error(err, "unable to set up dvls client") os.Exit(1) } - if dvlsUser.UserType != dvls.UserAuthenticationApplication { - setupLog.Error(nil, "provided credentials are not for an Application user type", "userType", dvlsUser.UserType) + if dvlsClient.ClientUser.UserType != dvls.UserAuthenticationApplication { + setupLog.Error(nil, "provided credentials are not for an Application user type", "userType", dvlsClient.ClientUser.UserType) os.Exit(1) }