fix(helm): resolve RBAC permissions for namespaced gateway sources (#5578)

* fix(helm): resolve RBAC permissions for namespaced gateway sources

* feat(helm): add support for gateway namespace in RBAC configuration

* chore(helm): update docs and fix formatting issues

* fix(helm): revert README changes and add gatewayNamespace docs

* chore lint fmt
This commit is contained in:
Kai Udo 2025-07-14 18:18:24 +09:00 committed by GitHub
parent a386197f0a
commit a270a32bf6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 318 additions and 1 deletions

View File

@ -109,6 +109,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. |
| 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. |

View File

@ -103,3 +103,12 @@ labelSelector:
matchLabels:
{{ include "external-dns.selectorLabels" . | nindent 4 }}
{{- end }}
{{/*
Check if any Gateway API sources are enabled
*/}}
{{- define "external-dns.hasGatewaySources" -}}
{{- if or (has "gateway-httproute" .Values.sources) (has "gateway-grpcroute" .Values.sources) (has "gateway-tlsroute" .Values.sources) (has "gateway-tcproute" .Values.sources) (has "gateway-udproute" .Values.sources) -}}
true
{{- end -}}
{{- end }}

View File

@ -58,14 +58,18 @@ rules:
resources: ["dnsendpoints/status"]
verbs: ["*"]
{{- end }}
{{- if or (has "gateway-httproute" .Values.sources) (has "gateway-grpcroute" .Values.sources) (has "gateway-tlsroute" .Values.sources) (has "gateway-tcproute" .Values.sources) (has "gateway-udproute" .Values.sources) }}
{{- if include "external-dns.hasGatewaySources" . }}
{{- if or (not .Values.namespaced) (and .Values.namespaced (not .Values.gatewayNamespace)) }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
{{- end }}
{{- if not .Values.namespaced }}
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get","watch","list"]
{{- end }}
{{- end }}
{{- if has "gateway-httproute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes"]
@ -127,4 +131,31 @@ rules:
{{- with .Values.rbac.additionalPermissions }}
{{- toYaml . | nindent 2 }}
{{- end }}
{{- if and .Values.rbac.create .Values.namespaced (include "external-dns.hasGatewaySources" .) }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ template "external-dns.fullname" . }}-namespaces
labels:
{{- include "external-dns.labels" . | nindent 4 }}
rules:
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get","watch","list"]
{{- if .Values.gatewayNamespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ template "external-dns.fullname" . }}-gateway
namespace: {{ .Values.gatewayNamespace }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
rules:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
{{- end }}
{{- end }}
{{- end }}

View File

@ -13,4 +13,39 @@ subjects:
- kind: ServiceAccount
name: {{ template "external-dns.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- if and .Values.rbac.create .Values.namespaced (include "external-dns.hasGatewaySources" .) }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ template "external-dns.fullname" . }}-namespaces
labels:
{{- include "external-dns.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ template "external-dns.fullname" . }}-namespaces
subjects:
- kind: ServiceAccount
name: {{ template "external-dns.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- if .Values.gatewayNamespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ template "external-dns.fullname" . }}-gateway
namespace: {{ .Values.gatewayNamespace }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ template "external-dns.fullname" . }}-gateway
subjects:
- kind: ServiceAccount
name: {{ template "external-dns.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}
{{- end }}
{{- end }}

View File

@ -111,6 +111,9 @@ spec:
{{- if .Values.namespaced }}
- --namespace={{ .Release.Namespace }}
{{- end }}
{{- if .Values.gatewayNamespace }}
- --gateway-namespace={{ .Values.gatewayNamespace }}
{{- end }}
{{- range .Values.domainFilters }}
- --domain-filter={{ . }}
{{- end }}

View File

@ -156,3 +156,238 @@ tests:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["udproutes"]
verbs: ["get","watch","list"]
- it: should create Role instead of ClusterRole when namespaced is true
set:
namespaced: true
sources:
- service
asserts:
- isKind:
of: Role
template: clusterrole.yaml
- equal:
path: metadata.name
value: rbac-external-dns
template: clusterrole.yaml
- it: should create RoleBinding instead of ClusterRoleBinding when namespaced is true
set:
namespaced: true
sources:
- service
asserts:
- isKind:
of: RoleBinding
template: clusterrolebinding.yaml
- equal:
path: metadata.name
value: rbac-external-dns-viewer
template: clusterrolebinding.yaml
- it: should create all required resources 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
- hasDocuments:
count: 3
template: clusterrole.yaml
- hasDocuments:
count: 3
template: clusterrolebinding.yaml
# Main role should exist and contain route permissions but NOT gateway permissions
- isKind:
of: Role
documentSelector:
path: metadata.name
value: rbac-external-dns
template: clusterrole.yaml
- contains:
path: rules
content:
apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes"]
verbs: ["get","watch","list"]
documentSelector:
path: metadata.name
value: rbac-external-dns
template: clusterrole.yaml
- notContains:
path: rules
content:
apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
documentSelector:
path: metadata.name
value: rbac-external-dns
template: clusterrole.yaml
# ClusterRole for namespaces should exist
- isKind:
of: ClusterRole
documentSelector:
path: metadata.name
value: rbac-external-dns-namespaces
template: clusterrole.yaml
# Gateway role should exist and have gateway permissions only
- isKind:
of: Role
documentSelector:
path: metadata.name
value: rbac-external-dns-gateway
template: clusterrole.yaml
- equal:
path: metadata.namespace
value: gateway-ns
documentSelector:
path: metadata.name
value: rbac-external-dns-gateway
template: clusterrole.yaml
- contains:
path: rules
content:
apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
documentSelector:
path: metadata.name
value: rbac-external-dns-gateway
template: clusterrole.yaml
- it: should create main Role with gateway permissions and ClusterRole for namespaces when namespaced=true and gatewayNamespace is not set
set:
namespaced: true
sources:
- gateway-httproute
asserts:
# Should have: main Role + ClusterRole for namespaces
- hasDocuments:
count: 2
template: clusterrole.yaml
- hasDocuments:
count: 2
template: clusterrolebinding.yaml
# Main Role should exist and contain gateway permissions
- isKind:
of: Role
documentSelector:
path: metadata.name
value: rbac-external-dns
template: clusterrole.yaml
- contains:
path: rules
content:
apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
documentSelector:
path: metadata.name
value: rbac-external-dns
template: clusterrole.yaml
- contains:
path: rules
content:
apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes"]
verbs: ["get","watch","list"]
documentSelector:
path: metadata.name
value: rbac-external-dns
template: clusterrole.yaml
# ClusterRole for namespaces should exist
- isKind:
of: ClusterRole
documentSelector:
path: metadata.name
value: rbac-external-dns-namespaces
template: clusterrole.yaml
- contains:
path: rules
content:
apiGroups: [""]
resources: ["namespaces"]
verbs: ["get","watch","list"]
documentSelector:
path: metadata.name
value: rbac-external-dns-namespaces
template: clusterrole.yaml
- it: should create ClusterRole with all permissions when namespaced=false and gateway sources exist
set:
namespaced: false
sources:
- gateway-httproute
asserts:
- hasDocuments:
count: 1
template: clusterrole.yaml
- hasDocuments:
count: 1
template: clusterrolebinding.yaml
- isKind:
of: ClusterRole
template: clusterrole.yaml
- contains:
path: rules
content:
apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
template: clusterrole.yaml
- contains:
path: rules
content:
apiGroups: [""]
resources: ["namespaces"]
verbs: ["get","watch","list"]
template: clusterrole.yaml
- it: should create only ClusterRole when namespaced=false and no gateway sources
set:
namespaced: false
sources:
- service
- ingress
asserts:
- hasDocuments:
count: 1
template: clusterrole.yaml
- hasDocuments:
count: 1
template: clusterrolebinding.yaml
- isKind:
of: ClusterRole
template: clusterrole.yaml
- notContains:
path: rules
content:
apiGroups: ["gateway.networking.k8s.io"]
template: clusterrole.yaml
- it: should create only Role when namespaced=true and no gateway sources
set:
namespaced: true
sources:
- service
- ingress
asserts:
- hasDocuments:
count: 1
template: clusterrole.yaml
- hasDocuments:
count: 1
template: clusterrolebinding.yaml
- isKind:
of: Role
template: clusterrole.yaml
- isKind:
of: RoleBinding
template: clusterrolebinding.yaml

View File

@ -205,6 +205,9 @@ triggerLoopOnEvent: false
# -- if `true`, _ExternalDNS_ will run in a namespaced scope (`Role`` and `Rolebinding`` will be namespaced too).
namespaced: false
# -- _Gateway API_ gateway namespace to watch.
gatewayNamespace: # @schema type:[string, null]; default: null
# -- _Kubernetes_ resources to monitor for DNS entries.
sources:
- service