Просмотр исходного кода

feat(security): Enable Authentication and Authorization for Metrics Endpoint using FilterProvider (#5574)

Co-authored-by: Bharath B <bhb@redhat.com>
Co-authored-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
Co-authored-by: Gergely Bräutigam <skarlso777@gmail.com>
Co-authored-by: Gergely Bräutigam <gergely.brautigam@sap.com>
Signed-off-by: Daniyar Alkulov <medvedicabolsaa@gmail.com>
Signed-off-by: Daniyar Alkulov <93855417+Zacky3181V@users.noreply.github.com>
Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
Signed-off-by: Gergely Bräutigam <skarlso777@gmail.com>
Signed-off-by: Gergely Bräutigam <gergely.brautigam@sap.com>
Daniyar Alkulov 2 месяцев назад
Родитель
Сommit
e12d858a09

+ 20 - 0
cmd/controller/certcontroller.go

@@ -34,6 +34,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/healthz"
 	"sigs.k8s.io/controller-runtime/pkg/log/zap"
+	"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
 	"sigs.k8s.io/controller-runtime/pkg/metrics/server"
 	"sigs.k8s.io/controller-runtime/pkg/webhook"
 
@@ -80,6 +81,20 @@ var certcontrollerCmd = &cobra.Command{
 			BindAddress: metricsAddr,
 		}
 
+		if metricsSecure {
+			metricsServerOpts.SecureServing = true
+			metricsServerOpts.CertDir = metricsCertDir
+			metricsServerOpts.CertName = metricsCertName
+			metricsServerOpts.KeyName = metricsKeyName
+		}
+
+		if metricsAuth {
+			metricsServerOpts.FilterProvider = filters.WithAuthenticationAndAuthorization
+		}
+		if metricsAuth && !metricsSecure {
+			setupLog.Error(nil, "--metrics-auth requires --metrics-secure; bearer tokens over plaintext HTTP is not allowed")
+			os.Exit(1)
+		}
 		// Disable HTTP/2 if not explicitly enabled
 		if !enableHTTP2 {
 			metricsServerOpts.TLSOpts = []func(*tls.Config){disableHTTP2}
@@ -187,6 +202,11 @@ func init() {
 
 	certcontrollerCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
 	certcontrollerCmd.Flags().StringVar(&healthzAddr, "healthz-addr", ":8081", "The address the health endpoint binds to.")
+	certcontrollerCmd.Flags().BoolVar(&metricsAuth, "metrics-auth", false, "Enable Kubernetes RBAC-based authentication and authorization for the metrics endpoint.")
+	certcontrollerCmd.Flags().BoolVar(&metricsSecure, "metrics-secure", false, "Enable HTTPS for the metrics endpoint.")
+	certcontrollerCmd.Flags().StringVar(&metricsCertDir, "metrics-cert-dir", "", "Directory containing TLS certificate and key for metrics endpoint.")
+	certcontrollerCmd.Flags().StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", "TLS certificate filename for metrics endpoint.")
+	certcontrollerCmd.Flags().StringVar(&metricsKeyName, "metrics-key-name", "tls.key", "TLS key filename for metrics endpoint.")
 	certcontrollerCmd.Flags().StringVar(&serviceName, "service-name", "external-secrets-webhook", "Webhook service name")
 	certcontrollerCmd.Flags().StringVar(&serviceNamespace, "service-namespace", "default", "Webhook service namespace")
 	certcontrollerCmd.Flags().StringVar(&secretName, "secret-name", "external-secrets-webhook", "Secret to store certs for webhook")

+ 10 - 1
cmd/controller/root.go

@@ -31,6 +31,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/cache"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/healthz"
+	"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
 	"sigs.k8s.io/controller-runtime/pkg/metrics/server"
 	"sigs.k8s.io/controller-runtime/pkg/webhook"
 
@@ -65,6 +66,7 @@ var (
 	liveAddr                              string
 	metricsAddr                           string
 	metricsSecure                         bool
+	metricsAuth                           bool
 	metricsCertDir                        string
 	metricsCertName                       string
 	metricsKeyName                        string
@@ -155,7 +157,13 @@ var rootCmd = &cobra.Command{
 			metricsOpts.CertName = metricsCertName
 			metricsOpts.KeyName = metricsKeyName
 		}
-
+		if metricsAuth {
+			metricsOpts.FilterProvider = filters.WithAuthenticationAndAuthorization
+		}
+		if metricsAuth && !metricsSecure {
+			setupLog.Error(nil, "--metrics-auth requires --metrics-secure; bearer tokens over plaintext HTTP is not allowed")
+			os.Exit(1)
+		}
 		// Disable HTTP/2 if not explicitly enabled
 		if !enableHTTP2 {
 			metricsOpts.TLSOpts = []func(*tls.Config){disableHTTP2}
@@ -328,6 +336,7 @@ func Execute() {
 
 func init() {
 	rootCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
+	rootCmd.Flags().BoolVar(&metricsAuth, "metrics-auth", false, "Enable Kubernetes RBAC-based authentication and authorization for the metrics endpoint.")
 	rootCmd.Flags().BoolVar(&metricsSecure, "metrics-secure", false, "Enable HTTPS for the metrics endpoint.")
 	rootCmd.Flags().StringVar(&metricsCertDir, "metrics-cert-dir", "", "Directory containing TLS certificate and key for metrics endpoint.")
 	rootCmd.Flags().StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", "TLS certificate filename for metrics endpoint.")

+ 21 - 0
cmd/controller/webhook.go

@@ -32,6 +32,7 @@ import (
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/healthz"
+	"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
 	"sigs.k8s.io/controller-runtime/pkg/metrics/server"
 	"sigs.k8s.io/controller-runtime/pkg/webhook"
 
@@ -128,6 +129,21 @@ var webhookCmd = &cobra.Command{
 			BindAddress: metricsAddr,
 		}
 
+		if metricsSecure {
+			metricsServerOpts.SecureServing = true
+			metricsServerOpts.CertDir = metricsCertDir
+			metricsServerOpts.CertName = metricsCertName
+			metricsServerOpts.KeyName = metricsKeyName
+		}
+
+		if metricsAuth {
+			metricsServerOpts.FilterProvider = filters.WithAuthenticationAndAuthorization
+		}
+		if metricsAuth && !metricsSecure {
+			setupLog.Error(nil, "--metrics-auth requires --metrics-secure; bearer tokens over plaintext HTTP is not allowed")
+			os.Exit(1)
+		}
+
 		// Configure TLS options for metrics server
 		var metricsTLSOpts []func(*tls.Config)
 		if !enableHTTP2 {
@@ -248,6 +264,11 @@ func init() {
 	rootCmd.AddCommand(webhookCmd)
 	webhookCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
 	webhookCmd.Flags().StringVar(&healthzAddr, "healthz-addr", ":8081", "The address the health endpoint binds to.")
+	webhookCmd.Flags().BoolVar(&metricsAuth, "metrics-auth", false, "Enable Kubernetes RBAC-based authentication and authorization for the metrics endpoint.")
+	webhookCmd.Flags().BoolVar(&metricsSecure, "metrics-secure", false, "Enable HTTPS for the metrics endpoint.")
+	webhookCmd.Flags().StringVar(&metricsCertDir, "metrics-cert-dir", "", "Directory containing TLS certificate and key for metrics endpoint.")
+	webhookCmd.Flags().StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", "TLS certificate filename for metrics endpoint.")
+	webhookCmd.Flags().StringVar(&metricsKeyName, "metrics-key-name", "tls.key", "TLS key filename for metrics endpoint.")
 	webhookCmd.Flags().IntVar(&port, "port", 10250, "Port number that the webhook server will serve.")
 	webhookCmd.Flags().StringVar(&dnsName, "dns-name", "localhost", "DNS name to validate certificates with")
 	webhookCmd.Flags().StringVar(&certDir, "cert-dir", "/tmp/k8s-webhook-server/serving-certs", "path to check for certs")

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

@@ -61,7 +61,12 @@ The command removes all the Kubernetes components associated with the chart and
 | certController.livenessProbe.successThreshold | int | `1` |  |
 | certController.livenessProbe.timeoutSeconds | int | `5` |  |
 | certController.log | object | `{"level":"info","timeEncoding":"epoch"}` | Specifies Log Params to the Certificate Controller |
+| certController.metrics.listen.auth.enabled | bool | `false` | Enable Kubernetes RBAC-based authentication for certController's metrics endpoint. Requires certController.metrics.listen.secure to be true. Default value is false. |
 | certController.metrics.listen.port | int | `8080` |  |
+| certController.metrics.listen.secure.certDir | string | `"/etc/tls"` | TLS cert directory path |
+| certController.metrics.listen.secure.certFile | string | `"/etc/tls/tls.crt"` | TLS cert file path |
+| certController.metrics.listen.secure.enabled | bool | `false` |  |
+| certController.metrics.listen.secure.keyFile | string | `"/etc/tls/tls.key"` | TLS key file path |
 | certController.metrics.service.annotations | object | `{}` | Additional service annotations |
 | certController.metrics.service.enabled | bool | `false` | Enable if you use another monitoring tool than Prometheus to scrape the metrics |
 | certController.metrics.service.port | int | `8080` | Metrics service port to scrape |
@@ -170,6 +175,7 @@ The command removes all the Kubernetes components associated with the chart and
 | livenessProbe.spec.successThreshold | int | `1` | Number of successful probes to mark probe successful. |
 | livenessProbe.spec.timeoutSeconds | int | `5` | Specify the maximum amount of time to wait for a probe to respond before considering it fails. |
 | log | object | `{"level":"info","timeEncoding":"epoch"}` | Specifies Log Params to the External Secrets Operator |
+| metrics.listen.auth.enabled | bool | `false` | Enable Kubernetes RBAC-based authentication for metrics endpoint. Requires metrics.listen.secure to be true. Default value is false. |
 | metrics.listen.port | int | `8080` |  |
 | metrics.listen.secure.certDir | string | `"/etc/tls"` | TLS cert directory path |
 | metrics.listen.secure.certFile | string | `"/etc/tls/tls.crt"` | TLS cert file path |
@@ -283,7 +289,12 @@ The command removes all the Kubernetes components associated with the chart and
 | webhook.livenessProbe.timeoutSeconds | int | `5` |  |
 | webhook.log | object | `{"level":"info","timeEncoding":"epoch"}` | Specifies Log Params to the Webhook |
 | webhook.lookaheadInterval | string | `""` | Specifies the lookaheadInterval for certificate validity |
+| webhook.metrics.listen.auth.enabled | bool | `false` | Enable Kubernetes RBAC-based authentication for webhook's metrics endpoint. Requires webhook.metrics.listen.secure to be true. Default value is false. |
 | webhook.metrics.listen.port | int | `8080` |  |
+| webhook.metrics.listen.secure.certDir | string | `"/etc/tls"` | TLS cert directory path |
+| webhook.metrics.listen.secure.certFile | string | `"/etc/tls/tls.crt"` | TLS cert file path |
+| webhook.metrics.listen.secure.enabled | bool | `false` |  |
+| webhook.metrics.listen.secure.keyFile | string | `"/etc/tls/tls.key"` | TLS key file path |
 | webhook.metrics.service.annotations | object | `{}` | Additional service annotations |
 | webhook.metrics.service.enabled | bool | `false` | Enable if you use another monitoring tool than Prometheus to scrape the metrics |
 | webhook.metrics.service.port | int | `8080` | Metrics service port to scrape |

+ 9 - 0
deploy/charts/external-secrets/templates/cert-controller-deployment.yaml

@@ -99,6 +99,15 @@ spec:
           - --{{ $key }}
             {{- end }}
           {{- end }}
+          {{- if .Values.certController.metrics.listen.secure.enabled }}
+          - --metrics-secure=true
+          - --metrics-cert-dir={{ .Values.certController.metrics.listen.secure.certDir }}
+          - --metrics-cert-name={{ .Values.certController.metrics.listen.secure.certFile }}
+          - --metrics-key-name={{ .Values.certController.metrics.listen.secure.keyFile }}
+          {{- end }}
+          {{- if .Values.certController.metrics.listen.auth.enabled }}
+          - --metrics-auth=true
+          {{- end }}
           ports:
             - containerPort: {{ .Values.certController.metrics.listen.port }}
               protocol: TCP

+ 14 - 0
deploy/charts/external-secrets/templates/cert-controller-rbac.yaml

@@ -76,6 +76,20 @@ rules:
     - "create"
     - "update"
     - "patch"
+  {{- if .Values.certController.metrics.listen.auth.enabled }}
+  - apiGroups:
+    - "authentication.k8s.io"
+    resources:
+    - "tokenreviews"
+    verbs:
+    - "create"
+  - apiGroups:
+    - "authorization.k8s.io"
+    resources:
+    - "subjectaccessreviews"
+    verbs:
+    - "create"
+  {{- end }}
 ---
 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding

+ 19 - 0
deploy/charts/external-secrets/templates/cert-controller-serviceaccount.yaml

@@ -14,3 +14,22 @@ metadata:
     {{- toYaml . | nindent 4 }}
   {{- end }}
 {{- end }}
+---
+{{- if and .Values.certController.metrics.listen.auth.enabled .Values.certController.create .Values.certController.serviceAccount.create (not .Values.webhook.certManager.enabled) -}}
+apiVersion: v1
+kind: Secret
+type: kubernetes.io/service-account-token
+metadata:
+  name: {{ include "external-secrets-cert-controller.serviceAccountName" . }}-token
+  namespace: {{ template "external-secrets.namespace" . }}
+  labels:
+    {{- include "external-secrets-cert-controller.labels" . | nindent 4 }}
+    {{- with .Values.certController.serviceAccount.extraLabels }}
+    {{- toYaml . | nindent 4 }}
+    {{- end }}
+  annotations:
+    kubernetes.io/service-account.name: {{ include "external-secrets-cert-controller.serviceAccountName" . }}
+    {{- with .Values.certController.serviceAccount.annotations }}
+    {{- toYaml . | nindent 4 }}
+    {{- end }}
+{{- end }}

+ 3 - 0
deploy/charts/external-secrets/templates/deployment.yaml

@@ -146,6 +146,9 @@ spec:
           - --metrics-cert-name={{ .Values.metrics.listen.secure.certFile }}
           - --metrics-key-name={{ .Values.metrics.listen.secure.keyFile }}
           {{- end }}
+          {{- if .Values.metrics.listen.auth.enabled }}
+          - --metrics-auth=true
+          {{- end }}
           ports:
             - containerPort: {{ .Values.metrics.listen.port }}
               protocol: TCP

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

@@ -215,6 +215,20 @@ rules:
     - "update"
     - "delete"
   {{- end }}
+  {{- if .Values.metrics.listen.auth.enabled }}
+  - apiGroups:
+    - "authentication.k8s.io"
+    resources: 
+    - "tokenreviews"
+    verbs:
+    - "create"
+  - apiGroups:
+    - "authorization.k8s.io"
+    resources: 
+    - "subjectaccessreviews"
+    verbs:
+    - "create"
+  {{- end }}
 ---
 apiVersion: rbac.authorization.k8s.io/v1
 {{- if .Values.scopedRBAC }}

+ 19 - 0
deploy/charts/external-secrets/templates/serviceaccount.yaml

@@ -14,3 +14,22 @@ metadata:
     {{- toYaml . | nindent 4 }}
   {{- end }}
 {{- end }}
+---
+{{- if and .Values.metrics.listen.auth.enabled .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: Secret
+type: kubernetes.io/service-account-token
+metadata:
+  name: {{ include "external-secrets.serviceAccountName" . }}-token
+  namespace: {{ template "external-secrets.namespace" . }}
+  labels:
+    {{- include "external-secrets.labels" . | nindent 4 }}
+    {{- with .Values.serviceAccount.extraLabels }}
+    {{- toYaml . | nindent 4 }}
+    {{- end }}
+  annotations:
+    kubernetes.io/service-account.name: {{ include "external-secrets.serviceAccountName" . }}
+    {{- with .Values.serviceAccount.annotations }}
+    {{- toYaml . | nindent 4 }}
+    {{- end }}
+{{- end }}

+ 21 - 0
deploy/charts/external-secrets/templates/servicemonitor.yaml

@@ -30,6 +30,13 @@ spec:
     relabelings:
       {{- toYaml . | nindent 6 }}
     {{- end }}
+    {{- if .Values.metrics.listen.auth.enabled }}
+    authorization:
+      credentials:
+        name: {{ include "external-secrets.serviceAccountName" . }}-token
+        key: token
+      type: Bearer
+    {{- end }}
 ---
 {{- if .Values.webhook.create }}
 apiVersion: "monitoring.coreos.com/v1"
@@ -62,6 +69,13 @@ spec:
     relabelings:
       {{- toYaml . | nindent 6 }}
     {{- end }}
+    {{- if .Values.webhook.metrics.listen.auth.enabled }}
+    authorization:
+      credentials:
+       name: {{ include "external-secrets-webhook.serviceAccountName" . }}-token
+       key: token
+      type: Bearer
+    {{- end }}
 {{- end }}
 ---
 {{- if .Values.certController.create }}
@@ -95,5 +109,12 @@ spec:
     relabelings:
       {{- toYaml . | nindent 6 }}
     {{- end }}
+    {{- if .Values.certController.metrics.listen.auth.enabled }}
+    authorization:
+      credentials:
+       name: {{ include "external-secrets-cert-controller.serviceAccountName" . }}-token
+       key: token
+      type: Bearer
+    {{- end }}
 {{- end }}
 {{- end }}

+ 9 - 0
deploy/charts/external-secrets/templates/webhook-deployment.yaml

@@ -91,6 +91,15 @@ spec:
           - --{{ $key }}
             {{- end }}
           {{- end }}
+          {{- if .Values.webhook.metrics.listen.secure.enabled }}
+          - --metrics-secure=true
+          - --metrics-cert-dir={{ .Values.webhook.metrics.listen.secure.certDir }}
+          - --metrics-cert-name={{ .Values.webhook.metrics.listen.secure.certFile }}
+          - --metrics-key-name={{ .Values.webhook.metrics.listen.secure.keyFile }}
+          {{- end }}
+          {{- if .Values.webhook.metrics.listen.auth.enabled }}
+          - --metrics-auth=true
+          {{- end }}
           ports:
             - containerPort: {{ .Values.webhook.metrics.listen.port }}
               protocol: TCP

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

@@ -0,0 +1,36 @@
+{{- if and .Values.webhook.metrics.listen.auth.enabled .Values.webhook.create .Values.webhook.serviceAccount.create }}
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: {{ include "external-secrets.fullname" . }}-webhook-metrics-auth
+  labels:
+    {{- include "external-secrets-webhook.labels" . | nindent 4 }}
+rules:
+  - apiGroups:
+    - "authentication.k8s.io"
+    resources:
+    - "tokenreviews"
+    verbs:
+    - "create"
+  - apiGroups:
+    - "authorization.k8s.io"
+    resources:
+    - "subjectaccessreviews"
+    verbs:
+    - "create"
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: {{ include "external-secrets.fullname" . }}-webhook-metrics-auth
+  labels:
+    {{- include "external-secrets-webhook.labels" . | nindent 4 }}
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: {{ include "external-secrets.fullname" . }}-webhook-metrics-auth
+subjects:
+  - name: {{ include "external-secrets-webhook.serviceAccountName" . }}
+    namespace: {{ template "external-secrets.namespace" . }}
+    kind: ServiceAccount
+{{- end }}

+ 19 - 0
deploy/charts/external-secrets/templates/webhook-serviceaccount.yaml

@@ -14,3 +14,22 @@ metadata:
     {{- toYaml . | nindent 4 }}
   {{- end }}
 {{- end }}
+---
+{{- if and .Values.webhook.metrics.listen.auth.enabled .Values.webhook.create .Values.webhook.serviceAccount.create -}}
+apiVersion: v1
+kind: Secret
+type: kubernetes.io/service-account-token
+metadata:
+  name: {{ include "external-secrets-webhook.serviceAccountName" . }}-token
+  namespace: {{ template "external-secrets.namespace" . }}
+  labels: 
+    {{- include "external-secrets-webhook.labels" . | nindent 4 }}
+    {{- with .Values.webhook.serviceAccount.extraLabels }}
+    {{- toYaml . | nindent 4 }}
+    {{- end }}
+  annotations:
+    kubernetes.io/service-account.name: {{ include "external-secrets-webhook.serviceAccountName" . }}
+    {{- with .Values.webhook.serviceAccount.annotations }}
+    {{- toYaml . | nindent 4 }}
+    {{- end }}
+{{- end }}

+ 89 - 0
deploy/charts/external-secrets/tests/cert_controller_serviceaccount_test.yaml

@@ -0,0 +1,89 @@
+suite: test certcontroller serviceaccount and secret
+templates:
+  - cert-controller-serviceaccount.yaml
+
+tests:
+  - it: should create certController ServiceAccount when certController.create, serviceAccount.create are true and webhook.certManager.enabled is false
+    set:
+      certController.create: true
+      certController.serviceAccount.create: true
+      webhook.certManager.enabled: false
+    asserts:
+      - containsDocument:
+          apiVersion: v1
+          kind: ServiceAccount
+
+  # certController.create is false
+  - it: should not create certController ServiceAccount when certController.create is false
+    set:
+      certController.create: false
+      certController.serviceAccount.create: true
+      webhook.certManager.enabled: false
+    asserts:
+      - hasDocuments:
+          count: 0
+
+  # serviceAccount.create is false
+  - it: should not create certController ServiceAccount when serviceAccount.create is false
+    set:
+      certController.create: true
+      certController.serviceAccount.create: false
+      webhook.certManager.enabled: false
+    asserts:
+      - hasDocuments:
+          count: 0
+
+  # the `not` flag - this is the inverted one
+  - it: should not create certController ServiceAccount when webhook.certManager.enabled is true
+    set:
+      certController.create: true
+      certController.serviceAccount.create: true
+      webhook.certManager.enabled: true
+    asserts:
+      - hasDocuments:
+          count: 0
+  - it: should create Secret when all conditions are met
+    set:
+      certController.metrics.listen.auth.enabled: true
+      certController.create: true
+      certController.serviceAccount.create: true
+      webhook.certManager.enabled: false
+    asserts:
+      - hasDocuments:
+          count: 2
+  - it: should not create Secret when certController.metrics.listen.auth.enabled is false
+    set:
+      certController.metrics.listen.auth.enabled: false
+      certController.create: true
+      certController.serviceAccount.create: true
+      webhook.certManager.enabled: false
+    asserts:
+      - hasDocuments:
+          count: 1
+  - it: should not create Secret and ServiceAccount when certController.create is false
+    set:
+      certController.metrics.listen.auth.enabled: true
+      certController.create: false
+      certController.serviceAccount.create: true
+      webhook.certManager.enabled: false
+    asserts:
+      - hasDocuments:
+          count: 0
+  - it: should not create Secret and ServiceAccount when certController.serviceAccount.create is false
+    set:
+      certController.metrics.listen.auth.enabled: true
+      certController.create: true
+      certController.serviceAccount.create: false
+      webhook.certManager.enabled: false
+    asserts:
+      - hasDocuments:
+          count: 0
+  - it: should not create Secret and ServiceAccount when webhook.certManager.enabled is true
+    set:
+      certController.metrics.listen.auth.enabled: true
+      certController.create: true
+      certController.serviceAccount.create: true
+      webhook.certManager.enabled: true
+    asserts:
+      - hasDocuments:
+          count: 0

+ 40 - 0
deploy/charts/external-secrets/tests/serviceaccount_test.yaml

@@ -0,0 +1,40 @@
+suite: test serviceaccount and secret
+templates:
+  - serviceaccount.yaml
+
+tests:
+  - it: should create ServiceAccount when serviceAccount.create is true
+    set: 
+      serviceAccount.create: true
+    asserts:
+      - containsDocument:
+          apiVersion: v1
+          kind: ServiceAccount
+  - it: should create Secret and ServiceAccount when metrics.listen.auth.enabled and serviceAccount.create are true
+    set:
+      metrics.listen.auth.enabled: true
+      serviceAccount.create: true
+    asserts:
+      - hasDocuments:
+          count: 2
+  - it: should not create Secret when metrics.listen.auth.enabled is false 
+    set:
+      metrics.listen.auth.enabled: false
+      serviceAccount.create: true
+    asserts:
+      - hasDocuments:
+          count: 1
+  - it: should not create Secret and ServiceAccount when serviceAccount.create is false 
+    set:
+      metrics.listen.auth.enabled: true
+      serviceAccount.create: false
+    asserts:
+      - hasDocuments:
+          count: 0
+  - it: should not create Secret and ServiceAccount when metrics.listen.auth.enabled and serviceAccount.create are false 
+    set:
+      metrics.listen.auth.enabled: false
+      serviceAccount.create: false
+    asserts:
+      - hasDocuments:
+          count: 0

+ 53 - 0
deploy/charts/external-secrets/tests/webhook_serviceaccount_test.yaml

@@ -0,0 +1,53 @@
+suite: test webhook serviceaccount and secret
+templates:
+  - webhook-serviceaccount.yaml
+
+tests:
+  - it: should create ServiceAccount when webhook.create and webhook.serviceAccount.create are true
+    set: 
+      webhook.create: true
+      webhook.serviceAccount.create: true
+    asserts:
+      - containsDocument:
+          apiVersion: v1
+          kind: ServiceAccount
+  - it: should create Secret and ServiceAccount when webhook.metrics.listen.auth.enabled webhook.create and webhook.serviceAccount.create set to true
+    set:
+      webhook.metrics.listen.auth.enabled: true
+      webhook.create: true
+      webhook.serviceAccount.create: true
+    asserts:
+      - hasDocuments:
+          count: 2
+  - it: should not create Secret when webhook.metrics.listen.auth.enabled is false 
+    set:
+      webhook.metrics.listen.auth.enabled: false
+      webhook.create: true
+      webhook.serviceAccount.create: true
+    asserts:
+      - hasDocuments:
+          count: 1
+  - it: should not create Secret and ServiceAccount when webhook.create is false 
+    set:
+      webhook.metrics.listen.auth.enabled: true
+      webhook.create: false
+      webhook.serviceAccount.create: true
+    asserts:
+      - hasDocuments:
+          count: 0
+  - it: should not create Secret and ServiceAccount when webhook.serviceAccount.create is false 
+    set:
+      webhook.metrics.listen.auth.enabled: true
+      webhook.create: true
+      webhook.serviceAccount.create: false
+    asserts:
+      - hasDocuments:
+          count: 0
+  - it: should not create Secret and ServiceAccount when webhook.metrics.listen.auth.enabled webhook.serviceAccount.create webhook.create are false 
+    set:
+      webhook.metrics.listen.auth.enabled: false
+      webhook.create: false
+      webhook.serviceAccount.create: false
+    asserts:
+      - hasDocuments:
+          count: 0

+ 58 - 0
deploy/charts/external-secrets/values.schema.json

@@ -121,8 +121,33 @@
                         "listen": {
                             "type": "object",
                             "properties": {
+                                "auth": {
+                                    "type": "object",
+                                    "properties": {
+                                        "enabled": {
+                                            "type": "boolean"
+                                        }
+                                    }
+                                },
                                 "port": {
                                     "type": "integer"
+                                },
+                                "secure": {
+                                    "type": "object",
+                                    "properties": {
+                                        "certDir": {
+                                            "type": "string"
+                                        },
+                                        "certFile": {
+                                            "type": "string"
+                                        },
+                                        "enabled": {
+                                            "type": "boolean"
+                                        },
+                                        "keyFile": {
+                                            "type": "string"
+                                        }
+                                    }
                                 }
                             }
                         },
@@ -585,6 +610,14 @@
                 "listen": {
                     "type": "object",
                     "properties": {
+                        "auth": {
+                            "type": "object",
+                            "properties": {
+                                "enabled": {
+                                    "type": "boolean"
+                                }
+                            }
+                        },
                         "port": {
                             "type": "integer"
                         },
@@ -1073,8 +1106,33 @@
                         "listen": {
                             "type": "object",
                             "properties": {
+                                "auth": {
+                                    "type": "object",
+                                    "properties": {
+                                        "enabled": {
+                                            "type": "boolean"
+                                        }
+                                    }
+                                },
                                 "port": {
                                     "type": "integer"
+                                },
+                                "secure": {
+                                    "type": "object",
+                                    "properties": {
+                                        "certDir": {
+                                            "type": "string"
+                                        },
+                                        "certFile": {
+                                            "type": "string"
+                                        },
+                                        "enabled": {
+                                            "type": "boolean"
+                                        },
+                                        "keyFile": {
+                                            "type": "string"
+                                        }
+                                    }
                                 }
                             }
                         },

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

@@ -321,6 +321,9 @@ metrics:
 
   listen:
     port: 8080
+    auth:
+      # -- Enable Kubernetes RBAC-based authentication for metrics endpoint. Requires metrics.listen.secure to be true. Default value is false.
+      enabled: false
     secure:
       enabled: false
       # -- if those are not set or invalid, self-signed certs will be generated
@@ -556,6 +559,18 @@ webhook:
 
     listen:
       port: 8080
+      auth:
+        # -- Enable Kubernetes RBAC-based authentication for webhook's metrics endpoint. Requires webhook.metrics.listen.secure to be true. Default value is false.
+        enabled: false
+      secure:
+        enabled: false
+        # -- if those are not set or invalid, self-signed certs will be generated
+        # -- TLS cert directory path
+        certDir: /etc/tls
+        # -- TLS cert file path
+        certFile: /etc/tls/tls.crt
+        # -- TLS key file path
+        keyFile: /etc/tls/tls.key
 
     service:
       # -- Enable if you use another monitoring tool than Prometheus to scrape the metrics
@@ -720,6 +735,18 @@ certController:
 
     listen:
       port: 8080
+      auth:
+        # -- Enable Kubernetes RBAC-based authentication for certController's metrics endpoint. Requires certController.metrics.listen.secure to be true. Default value is false.
+        enabled: false
+      secure:
+        enabled: false
+        # -- if those are not set or invalid, self-signed certs will be generated
+        # -- TLS cert directory path
+        certDir: /etc/tls
+        # -- TLS cert file path
+        certFile: /etc/tls/tls.crt
+        # -- TLS key file path
+        keyFile: /etc/tls/tls.key
 
     service:
       # -- Enable if you use another monitoring tool than Prometheus to scrape the metrics

+ 12 - 0
go.mod

@@ -179,6 +179,7 @@ require (
 require (
 	al.essio.dev/pkg/shellescape v1.6.0 // indirect
 	buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 // indirect
+	cel.dev/expr v0.25.1 // indirect
 	cloud.google.com/go/auth v0.17.0 // indirect
 	cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
 	cloud.google.com/go/compute/metadata v0.9.0 // indirect
@@ -202,6 +203,7 @@ require (
 	github.com/ProtonMail/gopenpgp/v3 v3.3.0 // indirect
 	github.com/agext/levenshtein v1.2.3 // indirect
 	github.com/akeylesslabs/akeyless-go/v4 v4.3.0 // indirect
+	github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
 	github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
 	github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
 	github.com/atotto/clipboard v0.1.4 // indirect
@@ -225,6 +227,7 @@ require (
 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
 	github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
 	github.com/blang/semver v3.5.1+incompatible // indirect
+	github.com/blang/semver/v4 v4.0.0 // indirect
 	github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 // indirect
 	github.com/cenkalti/backoff/v4 v4.3.0 // indirect
 	github.com/charmbracelet/bubbles v0.21.0 // indirect
@@ -285,6 +288,7 @@ require (
 	github.com/golang/glog v1.2.5 // indirect
 	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
 	github.com/google/btree v1.1.3 // indirect
+	github.com/google/cel-go v0.26.1 // indirect
 	github.com/google/gnostic-models v0.7.1 // indirect
 	github.com/google/go-github/v56 v56.0.0 // indirect
 	github.com/google/go-github/v75 v75.0.0 // indirect
@@ -292,6 +296,7 @@ require (
 	github.com/gophercloud/gophercloud/v2 v2.8.0 // indirect
 	github.com/grafana/grafana-openapi-client-go v0.0.0-20250925215610-d92957c70d5c // indirect
 	github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 // indirect
+	github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
 	github.com/hashicorp/go-secure-stdlib/awsutil v0.3.0 // indirect
 	github.com/hashicorp/go-uuid v1.0.3 // indirect
 	github.com/hashicorp/golang-lru v1.0.2 // indirect
@@ -342,6 +347,7 @@ require (
 	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/skeema/knownhosts v1.3.2 // indirect
 	github.com/spf13/pflag v1.0.10 // indirect
+	github.com/stoewer/go-strcase v1.3.1 // indirect
 	github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect
 	github.com/tetratelabs/wazero v1.9.0 // indirect
 	github.com/texttheater/golang-levenshtein v1.0.1 // indirect
@@ -358,7 +364,10 @@ require (
 	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
 	go.opentelemetry.io/otel v1.43.0 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect
 	go.opentelemetry.io/otel/metric v1.43.0 // indirect
+	go.opentelemetry.io/otel/sdk v1.43.0 // indirect
 	go.opentelemetry.io/otel/trace v1.43.0 // indirect
 	go.opentelemetry.io/proto/otlp v1.8.0 // indirect
 	go.yaml.in/yaml/v2 v2.4.4 // indirect
@@ -370,10 +379,13 @@ require (
 	gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
 	gopkg.in/ghodss/yaml.v1 v1.0.0 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
+	k8s.io/apiserver v0.35.2 // indirect
 	k8s.io/code-generator v0.35.2 // indirect
+	k8s.io/component-base v0.35.2 // indirect
 	k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect
 	k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf // indirect
 	lukechampine.com/frand v1.5.1 // indirect
+	sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
 	sigs.k8s.io/randfill v1.0.0 // indirect
 	sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
 	software.sslmate.com/src/go-pkcs12 v0.7.0 // indirect

+ 4 - 5
go.sum

@@ -631,7 +631,6 @@ github.com/grafana/grafana-openapi-client-go v0.0.0-20250925215610-d92957c70d5c/
 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs=
 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
@@ -1142,10 +1141,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0u
 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
 go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
 go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
-go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
-go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
-go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
-go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
+go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
+go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
+go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
+go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
 go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
 go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=