{{- /* TWO INGRESS OBJECTS, AND THE SECOND ONE IS NOT A SECURITY BOUNDARY. The public one publishes the login endpoints, the token endpoints and the account console. The admin one publishes the SAME backend under a second hostname, so that the administration console - whose URLs Keycloak generates from hostname-admin - can live on an internal ingress class or in a different DNS zone. WHAT IT DOES NOT DO, because the value's name invites the opposite assumption. Upstream, verbatim: "Using the hostname-admin option does not prevent accessing the Administration REST API endpoints via the frontend URL specified by the hostname option. If you want to restrict access to the Administration REST API, you need to do it on the reverse proxy level." So /admin/realms/... stays reachable through ingress.domain, and an IP allow-list on the admin host restricts nothing by itself. This chart deliberately renders NO deny rule for /admin on the public Ingress. Two reasons, and the second is the one that decided it: the administration console loads assets and performs redirects across more paths than /admin, so a rule written from the path name breaks the console in ways that only show up in a browser; and a rule nobody has tested against a running console is a measure that looks like one, which is worse than an absent one. Whoever needs the restriction puts it on the proxy in front of the public host, where it can be tested. GUIDELINE 12.1, CHECKED IN BOTH DIRECTIONS: ingress.domain is read a second time outside this template - as KC_HOSTNAME on the Deployment - so it stays required even with ingress.enabled: false, and its required() lives in the keycloak.frontendUrl helper rather than here. clusterIssuer is read only in this file, so switching both Ingresses off frees it. */ -}} {{- $adminEnabled := eq (include "keycloak.adminHostnameEnabled" .) "true" }} {{- if .Values.ingress.enabled }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ .Release.Name }}-{{ .Chart.Name }}-ingress labels: app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment annotations: cert-manager.io/cluster-issuer: {{ required "A Cluster-Issuer must be provided (ingress.clusterIssuer)" .Values.ingress.clusterIssuer }} {{- with .Values.ingress.annotations }} {{- toYaml . | nindent 4 }} {{- end }} spec: ingressClassName: {{ .Values.ingress.className }} rules: - host: {{ required "A Domain/Host must be provided (ingress.domain)" .Values.ingress.domain }} http: paths: - backend: service: name: {{ .Release.Name }}-{{ .Chart.Name }}-service port: name: http path: / pathType: Prefix tls: - hosts: - {{ required "A Domain/Host must be provided (ingress.domain)" .Values.ingress.domain }} secretName: tls-{{ .Release.Name }}-{{ .Chart.Name }}-ingress {{- end }} {{- if $adminEnabled }} {{- if .Values.ingress.enabled }} --- {{- end }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ .Release.Name }}-{{ .Chart.Name }}-admin-ingress labels: app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment annotations: # Its own certificate, for its own host. A separate TLS secret # (tls--keycloak-admin-ingress) rather than a shared one, because # these are two different hostnames that may well sit on two different # ingress classes - the shared-secret form in the style guide is for # several objects on ONE host. cert-manager.io/cluster-issuer: {{ required "A Cluster-Issuer must be provided (ingress.clusterIssuer)" .Values.ingress.clusterIssuer }} {{- with ((.Values.ingress.admin | default dict).annotations) }} {{- toYaml . | nindent 4 }} {{- end }} spec: # Falls back to ingress.className when not set. An internal ingress class # here is the useful half of the admin hostname - and still not the access # control the comment at the top of this file is about. ingressClassName: {{ (.Values.ingress.admin | default dict).className | default .Values.ingress.className }} rules: - host: {{ required "An admin Domain/Host must be provided (ingress.admin.domain) while ingress.admin.enabled is true" ((.Values.ingress.admin | default dict).domain) }} http: paths: # The SAME backend and the same path as the public Ingress. The # administration console is not a separate application and not a # separate path - only the hostname differs, and Keycloak generates # its admin URLs for that hostname because of KC_HOSTNAME_ADMIN. - backend: service: name: {{ .Release.Name }}-{{ .Chart.Name }}-service port: name: http path: / pathType: Prefix tls: - hosts: - {{ required "An admin Domain/Host must be provided (ingress.admin.domain) while ingress.admin.enabled is true" ((.Values.ingress.admin | default dict).domain) }} secretName: tls-{{ .Release.Name }}-{{ .Chart.Name }}-admin-ingress {{- end }}