Browse Source

Merge pull request #800 from kinyat/feat/scoped-rbac

Add the ability to support scoped RBAC with a scoped namespace
paul-the-alien[bot] 4 years ago
parent
commit
85937c99e7

+ 49 - 40
cmd/root.go

@@ -41,22 +41,24 @@ import (
 )
 
 var (
-	scheme                        = runtime.NewScheme()
-	setupLog                      = ctrl.Log.WithName("setup")
-	dnsName                       string
-	certDir                       string
-	metricsAddr                   string
-	healthzAddr                   string
-	controllerClass               string
-	enableLeaderElection          bool
-	concurrent                    int
-	loglevel                      string
-	namespace                     string
-	storeRequeueInterval          time.Duration
-	serviceName, serviceNamespace string
-	secretName, secretNamespace   string
-	crdRequeueInterval            time.Duration
-	certCheckInterval             time.Duration
+	scheme                                = runtime.NewScheme()
+	setupLog                              = ctrl.Log.WithName("setup")
+	dnsName                               string
+	certDir                               string
+	metricsAddr                           string
+	healthzAddr                           string
+	controllerClass                       string
+	enableLeaderElection                  bool
+	concurrent                            int
+	loglevel                              string
+	namespace                             string
+	enableClusterStoreReconciler          bool
+	enableClusterExternalSecretReconciler bool
+	storeRequeueInterval                  time.Duration
+	serviceName, serviceNamespace         string
+	secretName, secretNamespace           string
+	crdRequeueInterval                    time.Duration
+	certCheckInterval                     time.Duration
 )
 
 const (
@@ -116,38 +118,43 @@ var rootCmd = &cobra.Command{
 			setupLog.Error(err, errCreateController, "controller", "SecretStore")
 			os.Exit(1)
 		}
-		if err = (&secretstore.ClusterStoreReconciler{
-			Client:          mgr.GetClient(),
-			Log:             ctrl.Log.WithName("controllers").WithName("ClusterSecretStore"),
-			Scheme:          mgr.GetScheme(),
-			ControllerClass: controllerClass,
-			RequeueInterval: storeRequeueInterval,
-		}).SetupWithManager(mgr); err != nil {
-			setupLog.Error(err, errCreateController, "controller", "ClusterSecretStore")
-			os.Exit(1)
+		if enableClusterStoreReconciler {
+			if err = (&secretstore.ClusterStoreReconciler{
+				Client:          mgr.GetClient(),
+				Log:             ctrl.Log.WithName("controllers").WithName("ClusterSecretStore"),
+				Scheme:          mgr.GetScheme(),
+				ControllerClass: controllerClass,
+				RequeueInterval: storeRequeueInterval,
+			}).SetupWithManager(mgr); err != nil {
+				setupLog.Error(err, errCreateController, "controller", "ClusterSecretStore")
+				os.Exit(1)
+			}
 		}
 		if err = (&externalsecret.Reconciler{
-			Client:          mgr.GetClient(),
-			Log:             ctrl.Log.WithName("controllers").WithName("ExternalSecret"),
-			Scheme:          mgr.GetScheme(),
-			ControllerClass: controllerClass,
-			RequeueInterval: time.Hour,
+			Client:                    mgr.GetClient(),
+			Log:                       ctrl.Log.WithName("controllers").WithName("ExternalSecret"),
+			Scheme:                    mgr.GetScheme(),
+			ControllerClass:           controllerClass,
+			RequeueInterval:           time.Hour,
+			ClusterSecretStoreEnabled: enableClusterStoreReconciler,
 		}).SetupWithManager(mgr, controller.Options{
 			MaxConcurrentReconciles: concurrent,
 		}); err != nil {
 			setupLog.Error(err, errCreateController, "controller", "ExternalSecret")
 			os.Exit(1)
 		}
-		if err = (&clusterexternalsecret.Reconciler{
-			Client:          mgr.GetClient(),
-			Log:             ctrl.Log.WithName("controllers").WithName("ClusterExternalSecret"),
-			Scheme:          mgr.GetScheme(),
-			RequeueInterval: time.Hour,
-		}).SetupWithManager(mgr, controller.Options{
-			MaxConcurrentReconciles: concurrent,
-		}); err != nil {
-			setupLog.Error(err, errCreateController, "controller", "ClusterExternalSecret")
-			os.Exit(1)
+		if enableClusterExternalSecretReconciler {
+			if err = (&clusterexternalsecret.Reconciler{
+				Client:          mgr.GetClient(),
+				Log:             ctrl.Log.WithName("controllers").WithName("ClusterExternalSecret"),
+				Scheme:          mgr.GetScheme(),
+				RequeueInterval: time.Hour,
+			}).SetupWithManager(mgr, controller.Options{
+				MaxConcurrentReconciles: concurrent,
+			}); err != nil {
+				setupLog.Error(err, errCreateController, "controller", "ClusterExternalSecret")
+				os.Exit(1)
+			}
 		}
 		setupLog.Info("starting manager")
 		if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
@@ -171,5 +178,7 @@ func init() {
 	rootCmd.Flags().IntVar(&concurrent, "concurrent", 1, "The number of concurrent ExternalSecret reconciles.")
 	rootCmd.Flags().StringVar(&loglevel, "loglevel", "info", "loglevel to use, one of: debug, info, warn, error, dpanic, panic, fatal")
 	rootCmd.Flags().StringVar(&namespace, "namespace", "", "watch external secrets scoped in the provided namespace only. ClusterSecretStore can be used but only work if it doesn't reference resources from other namespaces")
+	rootCmd.Flags().BoolVar(&enableClusterStoreReconciler, "enable-cluster-store-reconciler", true, "Enable cluster store reconciler.")
+	rootCmd.Flags().BoolVar(&enableClusterExternalSecretReconciler, "enable-cluster-external-secret-reconciler", true, "Enable cluster external secret reconciler.")
 	rootCmd.Flags().DurationVar(&storeRequeueInterval, "store-requeue-interval", time.Minute*5, "Time duration between reconciling (Cluster)SecretStores")
 }

+ 3 - 0
deploy/charts/external-secrets/README.md

@@ -80,12 +80,15 @@ The command removes all the Kubernetes components associated with the chart and
 | podLabels | object | `{}` |  |
 | podSecurityContext | object | `{}` |  |
 | priorityClassName | string | `""` | Pod priority class name. |
+| processClusterExternalSecret | bool | `true` | if true, the operator will process cluster external secret. Else, it will ignore them. |
+| processClusterStore | bool | `true` | if true, the operator will process cluster store. Else, it will ignore them. |
 | prometheus.enabled | bool | `false` | Specifies whether to expose Service resource for collecting Prometheus metrics |
 | prometheus.service.port | int | `8080` |  |
 | rbac.create | bool | `true` | Specifies whether role and rolebinding resources should be created. |
 | replicaCount | int | `1` |  |
 | resources | object | `{}` |  |
 | scopedNamespace | string | `""` | If set external secrets are only reconciled in the provided namespace |
+| scopedRBAC | bool | `false` | Must be used with scopedNamespace. If true, create scoped RBAC roles under the scoped namespace and implicitly disable cluster stores and cluster external secrets |
 | securityContext | object | `{}` |  |
 | serviceAccount.annotations | object | `{}` | Annotations to add to the service account. |
 | serviceAccount.create | bool | `true` | Specifies whether a service account should be created. |

+ 12 - 1
deploy/charts/external-secrets/templates/deployment.yaml

@@ -44,7 +44,7 @@ spec:
           {{- end }}
           image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
           imagePullPolicy: {{ .Values.image.pullPolicy }}
-          {{- if or (.Values.leaderElect) (.Values.scopedNamespace) (.Values.concurrent) (.Values.extraArgs) }}
+          {{- if or (.Values.leaderElect) (.Values.scopedNamespace) (.Values.processClusterStore) (.Values.processClusterExternalSecret) (.Values.concurrent) (.Values.extraArgs) }}
           args:
           {{- if .Values.leaderElect }}
           - --enable-leader-election=true
@@ -52,6 +52,17 @@ spec:
           {{- if .Values.scopedNamespace }}
           - --namespace={{ .Values.scopedNamespace }}
           {{- end }}
+          {{- if and .Values.scopedNamespace .Values.scopedRBAC }}
+          - --enable-cluster-store-reconciler=false
+          - --enable-cluster-external-secret-reconciler=false
+          {{- else }}
+            {{- if not .Values.processClusterStore }}
+          - --enable-cluster-store-reconciler=false
+            {{- end }}
+            {{- if not .Values.processClusterExternalSecret }}
+          - --enable-cluster-external-secret-reconciler=false
+            {{- end }}
+          {{- end }}
           {{- if .Values.controllerClass }}
           - --controller-class={{ .Values.controllerClass }}
           {{- end }}

+ 32 - 0
deploy/charts/external-secrets/templates/rbac.yaml

@@ -1,8 +1,15 @@
 {{- if .Values.rbac.create -}}
 apiVersion: rbac.authorization.k8s.io/v1
+{{- if and .Values.scopedNamespace .Values.scopedRBAC }}
+kind: Role
+{{- else }}
 kind: ClusterRole
+{{- end }}
 metadata:
   name: {{ include "external-secrets.fullname" . }}-controller
+  {{- if and .Values.scopedNamespace .Values.scopedRBAC }}
+  namespace: {{ .Values.scopedNamespace | quote }}
+  {{- end }}
   labels:
     {{- include "external-secrets.labels" . | nindent 4 }}
 rules:
@@ -86,9 +93,16 @@ rules:
     - "update"
 ---
 apiVersion: rbac.authorization.k8s.io/v1
+{{- if and .Values.scopedNamespace .Values.scopedRBAC }}
+kind: Role
+{{- else }}
 kind: ClusterRole
+{{- end }}
 metadata:
   name: {{ include "external-secrets.fullname" . }}-view
+  {{- if and .Values.scopedNamespace .Values.scopedRBAC }}
+  namespace: {{ .Values.scopedNamespace | quote }}
+  {{- end }}
   labels:
     {{- include "external-secrets.labels" . | nindent 4 }}
     rbac.authorization.k8s.io/aggregate-to-view: "true"
@@ -107,9 +121,16 @@ rules:
       - "list"
 ---
 apiVersion: rbac.authorization.k8s.io/v1
+{{- if and .Values.scopedNamespace .Values.scopedRBAC }}
+kind: Role
+{{- else }}
 kind: ClusterRole
+{{- end }}
 metadata:
   name: {{ include "external-secrets.fullname" . }}-edit
+  {{- if and .Values.scopedNamespace .Values.scopedRBAC }}
+  namespace: {{ .Values.scopedNamespace | quote }}
+  {{- end }}
   labels:
     {{- include "external-secrets.labels" . | nindent 4 }}
     rbac.authorization.k8s.io/aggregate-to-edit: "true"
@@ -129,14 +150,25 @@ rules:
       - "update"
 ---
 apiVersion: rbac.authorization.k8s.io/v1
+{{- if and .Values.scopedNamespace .Values.scopedRBAC }}
+kind: RoleBinding
+{{- else }}
 kind: ClusterRoleBinding
+{{- end }}
 metadata:
   name: {{ include "external-secrets.fullname" . }}-controller
+  {{- if and .Values.scopedNamespace .Values.scopedRBAC }}
+  namespace: {{ .Values.scopedNamespace | quote }}
+  {{- end }}
   labels:
     {{- include "external-secrets.labels" . | nindent 4 }}
 roleRef:
   apiGroup: rbac.authorization.k8s.io
+  {{- if and .Values.scopedNamespace .Values.scopedRBAC }}
+  kind: Role
+  {{- else }}
   kind: ClusterRole
+  {{- end }}
   name: {{ include "external-secrets.fullname" . }}-controller
 subjects:
   - name: {{ include "external-secrets.serviceAccountName" . }}

+ 10 - 0
deploy/charts/external-secrets/values.yaml

@@ -25,6 +25,16 @@ controllerClass: ""
 # provided namespace
 scopedNamespace: ""
 
+# -- Must be used with scopedNamespace. If true, create scoped RBAC roles under the scoped namespace
+# and implicitly disable cluster stores and cluster external secrets
+scopedRBAC: false
+
+# -- if true, the operator will process cluster external secret. Else, it will ignore them.
+processClusterExternalSecret: true
+
+# -- if true, the operator will process cluster store. Else, it will ignore them.
+processClusterStore: true
+
 # -- Specifies whether an external secret operator deployment be created.
 createOperator: true
 

+ 15 - 5
pkg/controllers/externalsecret/externalsecret_controller.go

@@ -74,11 +74,12 @@ const (
 // Reconciler reconciles a ExternalSecret object.
 type Reconciler struct {
 	client.Client
-	Log             logr.Logger
-	Scheme          *runtime.Scheme
-	ControllerClass string
-	RequeueInterval time.Duration
-	recorder        record.EventRecorder
+	Log                       logr.Logger
+	Scheme                    *runtime.Scheme
+	ControllerClass           string
+	RequeueInterval           time.Duration
+	ClusterSecretStoreEnabled bool
+	recorder                  record.EventRecorder
 }
 
 // Reconcile implements the main reconciliation loop
@@ -108,6 +109,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 		return ctrl.Result{}, nil
 	}
 
+	if shouldSkipClusterSecretStore(r, externalSecret) {
+		log.Info("skipping cluster secret store as it is disabled")
+		return ctrl.Result{}, nil
+	}
+
 	// patch status when done processing
 	p := client.MergeFrom(externalSecret.DeepCopy())
 	defer func() {
@@ -320,6 +326,10 @@ func hashMeta(m metav1.ObjectMeta) string {
 	})
 }
 
+func shouldSkipClusterSecretStore(r *Reconciler, es esv1beta1.ExternalSecret) bool {
+	return !r.ClusterSecretStoreEnabled && es.Spec.SecretStoreRef.Kind == esv1beta1.ClusterSecretStoreKind
+}
+
 func shouldRefresh(es esv1beta1.ExternalSecret) bool {
 	// refresh if resource version changed
 	if es.Status.SyncedResourceVersion != getResourceVersion(es) {

+ 17 - 0
pkg/controllers/externalsecret/externalsecret_controller_test.go

@@ -971,6 +971,22 @@ var _ = Describe("ExternalSecret controller", func() {
 		}
 	}
 
+	ignoreClusterSecretStoreWhenDisabled := func(tc *testCase) {
+		tc.externalSecret.Spec.SecretStoreRef.Kind = esv1beta1.ClusterSecretStoreKind
+
+		Expect(shouldSkipClusterSecretStore(
+			&Reconciler{
+				ClusterSecretStoreEnabled: false,
+			},
+			*tc.externalSecret,
+		)).To(BeTrue())
+
+		tc.checkCondition = func(es *esv1beta1.ExternalSecret) bool {
+			cond := GetExternalSecretCondition(es.Status, esv1beta1.ExternalSecretReady)
+			return cond == nil
+		}
+	}
+
 	// When the ownership is set to owner, and we delete a dependent child kind=secret
 	// it should be recreated without waiting for refresh interval
 	checkDeletion := func(tc *testCase) {
@@ -1113,6 +1129,7 @@ var _ = Describe("ExternalSecret controller", func() {
 		Entry("should set an error condition when store does not exist", storeMissingErrCondition),
 		Entry("should set an error condition when store provider constructor fails", storeConstructErrCondition),
 		Entry("should not process store with mismatching controller field", ignoreMismatchController),
+		Entry("should not process cluster secret store when it is disabled", ignoreClusterSecretStoreWhenDisabled),
 	)
 })