diff --git a/charts/external-dns/README.md b/charts/external-dns/README.md index c147cc7bee..f1b5f4b677 100644 --- a/charts/external-dns/README.md +++ b/charts/external-dns/README.md @@ -61,6 +61,8 @@ For set up for a specific provider using the Helm chart, see the following links external-dns supports running on a namespaced only scope, too. If `namespaced=true` is defined, the helm chart will setup `Roles` and `RoleBindings` instead `ClusterRoles` and `ClusterRoleBindings`. +Note: When using Gateway API sources in namespaced mode, a cluster-scoped permission to list namespaces is required, unless you also set `gatewayNamespace`. If you set `gatewayNamespace`, all RBAC remains namespaced and no `ClusterRole`/`ClusterRoleBinding` is created. + ### Limited Supported Not all sources are supported in namespaced scope, since some sources depends on cluster-wide resources. @@ -110,7 +112,7 @@ If `namespaced` is set to `true`, please ensure that `sources` my only contains | extraVolumeMounts | list | `[]` | Extra [volume mounts](https://kubernetes.io/docs/concepts/storage/volumes/) for the `external-dns` container. | | extraVolumes | list | `[]` | Extra [volumes](https://kubernetes.io/docs/concepts/storage/volumes/) for the `Pod`. | | fullnameOverride | string | `nil` | Override the full name of the chart. | -| gatewayNamespace | string | `nil` | _Gateway API_ gateway namespace to watch. | +| gatewayNamespace | string | `nil` | _Gateway API_ gateway namespace to watch. When `namespaced=true`, setting this value avoids creating any cluster-scoped RBAC (no ClusterRole/ClusterRoleBinding) for Gateway sources. | | global.imagePullSecrets | list | `[]` | Global image pull secrets. | | image.pullPolicy | string | `"IfNotPresent"` | Image pull policy for the `external-dns` container. | | image.repository | string | `"registry.k8s.io/external-dns/external-dns"` | Image repository for the `external-dns` container. | diff --git a/charts/external-dns/README.md.gotmpl b/charts/external-dns/README.md.gotmpl index fc4ada14be..3e67cfa793 100644 --- a/charts/external-dns/README.md.gotmpl +++ b/charts/external-dns/README.md.gotmpl @@ -56,6 +56,8 @@ For set up for a specific provider using the Helm chart, see the following links external-dns supports running on a namespaced only scope, too. If `namespaced=true` is defined, the helm chart will setup `Roles` and `RoleBindings` instead `ClusterRoles` and `ClusterRoleBindings`. +Note: When using Gateway API sources in namespaced mode, a cluster-scoped permission to list namespaces is required, unless you also set `gatewayNamespace`. If you set `gatewayNamespace`, all RBAC remains namespaced and no `ClusterRole`/`ClusterRoleBinding` is created. + ### Limited Supported Not all sources are supported in namespaced scope, since some sources depends on cluster-wide resources. diff --git a/charts/external-dns/templates/clusterrole.yaml b/charts/external-dns/templates/clusterrole.yaml index 52c525ead2..ef9b243708 100644 --- a/charts/external-dns/templates/clusterrole.yaml +++ b/charts/external-dns/templates/clusterrole.yaml @@ -134,6 +134,13 @@ rules: {{- toYaml . | nindent 2 }} {{- end }} {{- if and .Values.rbac.create .Values.namespaced (include "external-dns.hasGatewaySources" .) }} +{{- /* + If namespaced=true and gatewayNamespace is NOT set, we need to list namespaces + cluster-wide to discover Gateways across the cluster. In that case, create a + ClusterRole to grant access to namespaces. If gatewayNamespace IS set, we are + fully namespaced for both scopes and can skip cluster-wide RBAC. + */ -}} +{{- if not .Values.gatewayNamespace }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -145,6 +152,7 @@ rules: - apiGroups: [""] resources: ["namespaces"] verbs: ["get","watch","list"] +{{- end }} {{- if .Values.gatewayNamespace }} --- apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/external-dns/templates/clusterrolebinding.yaml b/charts/external-dns/templates/clusterrolebinding.yaml index 49400c0be6..70a61b6123 100644 --- a/charts/external-dns/templates/clusterrolebinding.yaml +++ b/charts/external-dns/templates/clusterrolebinding.yaml @@ -14,6 +14,11 @@ subjects: name: {{ template "external-dns.serviceAccountName" . }} namespace: {{ .Release.Namespace }} {{- if and .Values.rbac.create .Values.namespaced (include "external-dns.hasGatewaySources" .) }} +{{- /* + If namespaced=true and gatewayNamespace is NOT set, bind the namespaces ClusterRole. + If gatewayNamespace IS set, we skip cluster-scoped RBAC entirely. + */ -}} +{{- if not .Values.gatewayNamespace }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -29,6 +34,7 @@ subjects: - kind: ServiceAccount name: {{ template "external-dns.serviceAccountName" . }} namespace: {{ .Release.Namespace }} +{{- end }} {{- if .Values.gatewayNamespace }} --- apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/external-dns/tests/json-schema_test.yaml b/charts/external-dns/tests/json-schema_test.yaml index 68b9b6ba3d..3ff1a1c50a 100644 --- a/charts/external-dns/tests/json-schema_test.yaml +++ b/charts/external-dns/tests/json-schema_test.yaml @@ -30,7 +30,8 @@ tests: enabled: "abrakadabra" asserts: - failedTemplate: - errorPattern: "Invalid type. Expected: [boolean,null], given: string" + # Accept Helm/JSONSchema error message variations across versions + errorPattern: "(Invalid type\\. Expected: \\[[bB]oolean,null\\], given: string|at '/enabled': got string, want null or boolean)" - it: should fail if provider is null set: diff --git a/charts/external-dns/tests/rbac_test.yaml b/charts/external-dns/tests/rbac_test.yaml index 4658d7ee2d..d5547f3ff7 100644 --- a/charts/external-dns/tests/rbac_test.yaml +++ b/charts/external-dns/tests/rbac_test.yaml @@ -229,19 +229,19 @@ tests: value: rbac-external-dns-viewer template: clusterrolebinding.yaml - - it: should create all required resources when namespaced=true and gatewayNamespace is specified + - it: should create only namespaced RBAC when namespaced=true and gatewayNamespace is specified set: namespaced: true gatewayNamespace: gateway-ns sources: - gateway-httproute asserts: - # Should have: main Role + ClusterRole for namespaces + Gateway Role + # Should have: main Role + Gateway Role only (no cluster-scoped RBAC) - hasDocuments: - count: 3 + count: 2 template: clusterrole.yaml - hasDocuments: - count: 3 + count: 2 template: clusterrolebinding.yaml # Main role should exist and contain route permissions but NOT gateway permissions @@ -272,12 +272,18 @@ tests: value: rbac-external-dns template: clusterrole.yaml - # ClusterRole for namespaces should exist + # Both documents should be Roles (no ClusterRole present) - isKind: - of: ClusterRole + of: Role documentSelector: path: metadata.name - value: rbac-external-dns-namespaces + value: rbac-external-dns + template: clusterrole.yaml + - isKind: + of: Role + documentSelector: + path: metadata.name + value: rbac-external-dns-gateway template: clusterrole.yaml # Gateway role should exist and have gateway permissions only diff --git a/charts/external-dns/values.schema.json b/charts/external-dns/values.schema.json index 22bfef2ab5..6832099291 100644 --- a/charts/external-dns/values.schema.json +++ b/charts/external-dns/values.schema.json @@ -105,7 +105,7 @@ ] }, "gatewayNamespace": { - "description": "_Gateway API_ gateway namespace to watch.", + "description": "_Gateway API_ gateway namespace to watch. When `namespaced=true`, setting this value avoids creating any cluster-scoped RBAC (no ClusterRole/ClusterRoleBinding) for Gateway sources.", "type": [ "string", "null" diff --git a/charts/external-dns/values.yaml b/charts/external-dns/values.yaml index 9b9292c512..47430a5e8a 100644 --- a/charts/external-dns/values.yaml +++ b/charts/external-dns/values.yaml @@ -206,6 +206,8 @@ triggerLoopOnEvent: false namespaced: false # -- _Gateway API_ gateway namespace to watch. +# When `namespaced=true`, setting this value avoids creating any cluster-scoped RBAC +# (no ClusterRole/ClusterRoleBinding) for Gateway sources. gatewayNamespace: # @schema type:[string, null]; default: null # -- _Kubernetes_ resources to monitor for DNS entries.