Files
sabsariandClaude Opus 4.8 c638121997 feat(gateway): expose gatewayAPI.gateway.infrastructure passthrough (#939)
Render Gateway spec.infrastructure (labels/annotations) so Gateway API
controllers propagate the metadata onto the provisioned Service/Deployment,
e.g. AWS internal NLB annotations. Purely additive: guarded by `with`, so
the default {} renders no change.

- spire-lib: toYaml passthrough in the shared Gateway helper
- spire, spire-nested: add the values param; regenerate READMEs
- tests/unit: cover the positive passthrough case

Signed-off-by: sabsari <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>
2026-09-01 05:41:12 -07:00

612 lines
21 KiB
Smarty

{{- define "spire-lib.cluster-name" }}
{{- if ne (len (dig "spire" "clusterName" "" .Values.global)) 0 }}
{{- .Values.global.spire.clusterName }}
{{- else }}
{{- .Values.clusterName }}
{{- end }}
{{- end }}
{{- define "spire-lib.trust-domain" }}
{{- if ne (len (dig "spire" "trustDomain" "" .Values.global)) 0 }}
{{- .Values.global.spire.trustDomain }}
{{- else }}
{{- .Values.trustDomain }}
{{- end }}
{{- end }}
{{- define "spire-lib.jwt-issuer" }}
{{- if ne (len (dig "spire" "jwtIssuer" "" .Values.global)) 0 }}
{{- .Values.global.spire.jwtIssuer }}
{{- else if ne (len .Values.jwtIssuer) 0 }}
{{- .Values.jwtIssuer }}
{{- else }}
{{- printf "https://oidc-discovery.%s" (include "spire-lib.trust-domain" .) }}
{{- end }}
{{- end }}
{{- define "spire-lib.bundle-configmap" }}
{{- if ne (len (dig "spire" "bundleConfigMap" "" .Values.global)) 0 }}
{{- .Values.global.spire.bundleConfigMap }}
{{- else }}
{{- .Values.bundleConfigMap }}
{{- end }}
{{- end }}
{{- define "spire-lib.cluster-domain" -}}
{{- if ne (len (dig "k8s" "clusterDomain" "" .Values.global)) 0 }}
{{- .Values.global.k8s.clusterDomain }}
{{- else }}
{{- .Values.clusterDomain }}
{{- end }}
{{- end }}
{{- define "spire-lib.registry" }}
{{- if ne (len (dig "spire" "image" "registry" "" .global)) 0 }}
{{- print .global.spire.image.registry "/"}}
{{- else if ne (len (.image.registry)) 0 }}
{{- print .image.registry "/"}}
{{- end }}
{{- end }}
{{- define "spire-lib.image" -}}
{{- $registry := include "spire-lib.registry" . }}
{{- $repo := .image.repository }}
{{- $tag := .image.tag | toString }}
{{- if eq (substr 0 7 $tag) "sha256:" }}
{{- printf "%s%s@%s" $registry $repo $tag | quote }}
{{- else if .appVersion }}
{{- $appVersion := .appVersion }}
{{- if and (hasKey . "ubi") (dig "openshift" false .global) }}
{{- $appVersion = printf "ubi-%s" $appVersion }}
{{- end }}
{{- printf "%s%s:%s" $registry $repo (default $appVersion $tag) | quote }}
{{- else if $tag }}
{{- printf "%s%s:%s" $registry $repo $tag | quote }}
{{- else }}
{{- printf "%s%s" $registry $repo | quote }}
{{- end }}
{{- end }}
{{/* Takes in a dictionary with keys:
* global - the standard global object
* ingress - a standard format ingress config object
*/}}
{{- define "spire-lib.ingress-controller-type" }}
{{- $type := "" }}
{{- if ne (len (dig "spire" "ingressControllerType" "" .global)) 0 }}
{{- $type = .global.spire.ingressControllerType }}
{{- else if ne .ingress.controllerType "" }}
{{- $type = .ingress.controllerType }}
{{- else if (dig "openshift" false .global) }}
{{- $type = "openshift" }}
{{- else }}
{{- $type = "other" }}
{{- end }}
{{- if not (has $type (list "ingress-nginx" "openshift" "other")) }}
{{- fail "Unsupported ingress controller type specified. Must be one of [ingress-nginx, openshift, other]" }}
{{- end }}
{{- $type }}
{{- end }}
{{/* Takes in a dictionary with keys:
* ingress - the standardized ingress object
* Values - Chart values
*/}}
{{ define "spire-lib.ingress-calculated-name" }}
{{- $host := .ingress.host }}
{{- if not (contains "." $host) }}
{{- $host = printf "%s.%s" $host (include "spire-lib.trust-domain" .) }}
{{- end }}
{{- $host }}
{{- end }}
{{/* Takes in a dictionary with keys:
* ingress - the standardized ingress object
* svcName - The service to route to
* port - which port on the service to use
* path - optional path to set on the rules
* pathType - typical ingress path type
* tlsSection - bool specifying to add by default the tls section to the ingress. Ingress-nginx needs true, openshift needs false.
* Values - Chart values
*/}}
{{ define "spire-lib.ingress-spec" }}
{{- $host := include "spire-lib.ingress-calculated-name" . }}
{{- $svcName := .svcName }}
{{- $port := .port }}
{{- with .ingress.className }}
ingressClassName: {{ . | quote }}
{{- end }}
{{- if eq (add (len .ingress.tls) (len .ingress.hosts)) 0 }}
{{ if or .tlsSection .ingress.tlsSecret }}
tls:
- hosts:
- {{ $host | quote }}
{{- with .ingress.tlsSecret }}
secretName: {{ . | quote }}
{{- end }}
{{- end }}
rules:
- host: {{ $host | quote }}
http:
paths:
- pathType: {{ .pathType }}
{{- with .path }}
path: {{ . }}
{{- end }}
backend:
service:
name: {{ $svcName | quote }}
port:
number: {{ $port }}
{{- else }}
{{- if .ingress.tls }}
tls:
{{- range .ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName | quote }}
{{- end }}
{{- end }}
rules:
{{- range .ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ $svcName | quote }}
port:
number: {{ $port }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- define "spire-lib.kubectl-image" }}
{{- $root := deepCopy . }}
{{- $tag := $root.image.tag | toString }}
{{- if eq (len $tag) 0 }}
{{- if dig "spire" "tools" "kubectl" "tag" "" $root.global }}
{{- $_ := set $root.image "tag" $root.global.spire.tools.kubectl.tag }}
{{- else }}
{{- $_ := set $root.image "tag" (regexReplaceAll "^(v?\\d+\\.\\d+\\.\\d+).*" $root.KubeVersion "${1}") }}
{{- end }}
{{- end }}
{{- include "spire-lib.image" $root }}
{{- end }}
{{/*
Take in an array of, '.', a failure string to display, and boolean to to display it,
if strictMode is enabled and the boolean is true
*/}}
{{- define "spire-lib.check-strict-mode" }}
{{ $root := index . 0 }}
{{ $message := index . 1 }}
{{ $condition := index . 2 }}
{{- if or (dig "spire" "strictMode" false $root.Values.global) (and (dig "spire" "recommendations" "enabled" false $root.Values.global) (dig "spire" "recommendations" "strictMode" true $root.Values.global)) }}
{{- if $condition }}
{{- fail $message }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Take a copy of the config and merge in .Values.customPlugins and .Values.unsupportedBuiltInPlugins passed through as root.
*/}}
{{- define "spire-lib.config_merge" }}
{{- $pluginsToMerge := dict "plugins" dict }}
{{- range $type, $instances := .root.Values.customPlugins }}
{{- if $instances }}
{{- $nt := (eq $type "svidStore") | ternary "SVIDStore" (printf "%s%s" (substr 0 1 $type | upper) (substr 1 -1 $type)) }}
{{- $processedInstances := dict }}
{{- range $instanceName, $config := $instances }}
{{- $pluginData := deepCopy $config }}
{{- $hasImage := hasKey $pluginData "image" }}
{{- $_ := unset $pluginData "image" }}
{{- if and $hasImage $pluginData.plugin_cmd }}
{{- $_ := set $pluginData "plugin_cmd" (printf "/plugins/%s/%s" $type $instanceName) }}
{{- end }}
{{- $_ := set $processedInstances $instanceName $pluginData }}
{{- end }}
{{- $_ := set $pluginsToMerge.plugins $nt $processedInstances }}
{{- end }}
{{- end }}
{{- range $type, $val := .root.Values.unsupportedBuiltInPlugins }}
{{- if . }}
{{- if eq $type "svidStore" }}
{{- $_ := set $pluginsToMerge.plugins "SVIDStore" (deepCopy $val) }}
{{- else }}
{{- $nt := printf "%s%s" (substr 0 1 $type | upper) (substr 1 -1 $type) }}
{{- $_ := set $pluginsToMerge.plugins $nt (deepCopy $val) }}
{{- end }}
{{- end }}
{{- end }}
{{- $newConfig := .config | fromYaml | mustMerge $pluginsToMerge }}
{{- $newConfig | toYaml }}
{{- end }}
{{/*
Find all customPlugins that specify an image, and build a list of binaries to copy into the plugin volume in the plugin loader.
*/}}
{{- define "spire-lib.extract_custom_plugin_images" }}
{{- $pluginList := list }}
{{- range $type, $instances := .Values.customPlugins }}
{{- range $instanceName, $config := $instances }}
{{- if $config.image }}
{{- $entry := dict "plugin_cmd" $config.plugin_cmd "image" $config.image "name" (printf "%s/%s" $type $instanceName) }}
{{- $pluginList = append $pluginList $entry }}
{{- end }}
{{- end }}
{{- end }}
{{- $pluginList | toYaml }}
{{- end }}
{{/*
Take a copy of the plugin section and return a yaml string based version
reformatted from a dict of dicts to a dict of lists of dicts
*/}}
{{- define "spire-lib.plugins_reformat" }}
{{- range $type, $v := . }}
{{ $type }}:
{{- range $name, $v2 := $v }}
- {{ $name }}: {{ $v2 | toYaml | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Take a copy of the config as a yaml config and root var.
Merge in .root.Values.customPlugins and .Values.unsupportedBuiltInPlugins into config,
Reformat the plugin section from a dict of dicts to a dict of lists of dicts,
and export it back as as json string.
This makes it much easier for users to merge in plugin configs, as dicts are easier
to merge in values, but spire needs arrays.
*/}}
{{- define "spire-lib.reformat-and-yaml2json" -}}
{{- $config := include "spire-lib.config_merge" . | fromYaml }}
{{- $plugins := include "spire-lib.plugins_reformat" $config.plugins | fromYaml }}
{{- $_ := set $config "plugins" $plugins }}
{{- $config | toPrettyJson }}
{{- end }}
{{- define "spire-lib.default_securitycontext_values" }}
allowPrivilegeEscalation: false
runAsNonRoot: true
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
seccompProfile:
type: RuntimeDefault
{{- end }}
{{- define "spire-lib.default_k8s_podsecuritycontext_values" }}
fsGroupChangePolicy: OnRootMismatch
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
{{- end }}
{{/*
Note: runAsUser, runAsGroup, fsGroup, are not needed due to it autoassigning restricted users feature of openshift
*/}}
{{- define "spire-lib.default_openshift_podsecuritycontext_values" }}
fsGroupChangePolicy: OnRootMismatch
{{- end }}
{{- define "spire-lib.securitycontext" }}
{{- include "spire-lib.securitycontext-extended" (dict "root" . "securityContext" .Values.securityContext) }}
{{- end }}
{{/* Same as securitycontext but takes in:
root - global . context for the chart
securityContext - the subbranch of values that contains the securityContext to merge
*/}}
{{- define "spire-lib.securitycontext-extended" }}
{{- if and (dig "spire" "recommendations" "enabled" false .root.Values.global) (dig "spire" "recommendations" "securityContexts" true .root.Values.global) }}
{{- $vals := deepCopy (include "spire-lib.default_securitycontext_values" .root | fromYaml) }}
{{- $vals = mergeOverwrite $vals .securityContext }}
{{- toYaml $vals }}
{{- else }}
{{- toYaml .securityContext }}
{{- end }}
{{- end }}
{{- define "spire-lib.podsecuritycontext" }}
{{- $vals := dict }}
{{- if and (dig "spire" "recommendations" "enabled" false .Values.global) (dig "spire" "recommendations" "securityContexts" true .Values.global) }}
{{- if (dig "openshift" false .Values.global) }}
{{- $vals = mergeOverwrite $vals (include "spire-lib.default_openshift_podsecuritycontext_values" . | fromYaml) }}
{{- else }}
{{- $vals = mergeOverwrite $vals (include "spire-lib.default_k8s_podsecuritycontext_values" . | fromYaml) }}
{{- end }}
{{- end }}
{{- $vals = mergeOverwrite $vals .Values.podSecurityContext }}
{{- toYaml $vals }}
{{- end }}
{{- define "spire-lib.default_node_priority_class_name" }}
{{- if .Values.priorityClassName }}
priorityClassName: {{ .Values.priorityClassName | quote }}
{{- else if and (dig "spire" "recommendations" "enabled" false .Values.global) (dig "spire" "recommendations" "priorityClassName" true .Values.global) }}
priorityClassName: system-node-critical
{{- end }}
{{- end }}
{{- define "spire-lib.default_cluster_priority_class_name" }}
{{- if .Values.priorityClassName }}
priorityClassName: {{ .Values.priorityClassName | quote }}
{{- else if and (dig "spire" "recommendations" "enabled" false .Values.global) (dig "spire" "recommendations" "priorityClassName" true .Values.global) }}
priorityClassName: system-cluster-critical
{{- end }}
{{- end }}
{{/*
Use autoscaling/v2 (Kubernetes 1.23 and newer) or autoscaling/v2beta2 (Kubernetes 1.12-1.25) based on cluster capabilities.
Anything lower has an incompatible API.
*/}}
{{- define "spire-lib.autoscalingVersion" -}}
{{- if (.Capabilities.APIVersions.Has "autoscaling/v2") }}
{{- print "autoscaling/v2" }}
{{- else if (.Capabilities.APIVersions.Has "autoscaling/v2beta2") }}
{{- print "autoscaling/v2beta2" }}
{{- else }}
{{- fail "Unsupported autoscaling API version" }}
{{- end }}
{{- end }}
{{- define "spire-lib.trust-bundle-ext" -}}
{{- if eq .trustBundleFormat "spiffe" }}
{{- print "spiffe" }}
{{- else }}
{{- print "crt" }}
{{- end }}
{{- end }}
{{/* ---------------------------------------------------------------------------
* Gateway API helpers
*
* Gateway API support is entirely independent of Ingress. It is driven by a
* per-service `gatewayAPI:` values block (sibling of `ingress:`) with its own
* `enabled` flag, so a service can expose both an Ingress and Gateway API
* resources at the same time. The existing ingress helpers above are untouched.
*
* Only Standard-channel, v1 Gateway API resources are used
* (gateway.networking.k8s.io/v1): Gateway, HTTPRoute, TLSRoute, ListenerSet,
* BackendTLSPolicy. Requires Gateway API v1.5+ CRDs pre-installed.
* --------------------------------------------------------------------------- */}}
{{/* Shared Gateway object name (global reference). Input: dict {global} */}}
{{- define "spire-lib.gateway-name" -}}
{{- dig "spire" "gatewayAPI" "gateway" "name" "spire" .global -}}
{{- end }}
{{/* Shared Gateway namespace. Input: dict {global, root}. Defaults to the release namespace. */}}
{{- define "spire-lib.gateway-namespace" -}}
{{- $ns := dig "spire" "gatewayAPI" "gateway" "namespace" "" .global -}}
{{- if $ns -}}{{ $ns }}{{- else -}}{{ .root.Release.Namespace }}{{- end -}}
{{- end }}
{{/* Shared Gateway listener port (global reference, ListenerSets must match). Input: dict {global} */}}
{{- define "spire-lib.gateway-port" -}}
{{- dig "spire" "gatewayAPI" "gateway" "port" 443 .global -}}
{{- end }}
{{/* Resolve whether ListenerSet management is on for a service.
* Input: dict {gatewayAPI, global}. Per-service listenerSet.enabled (null=inherit)
* falls back to global.spire.gatewayAPI.manageListenerSets (default true).
* Returns the string "true" or "false".
*/}}
{{- define "spire-lib.gateway-manage-listenersets" -}}
{{- $ls := dig "listenerSet" "enabled" nil .gatewayAPI -}}
{{- if ne $ls nil -}}
{{- $ls -}}
{{- else -}}
{{- dig "spire" "gatewayAPI" "manageListenerSets" true .global -}}
{{- end -}}
{{- end }}
{{/* Default route kind from tlsSecret. Input: dict {gatewayAPI}. Empty tlsSecret => TLSRoute (passthrough). */}}
{{- define "spire-lib.gateway-route-kind" -}}
{{- if .gatewayAPI.tlsSecret -}}HTTPRoute{{- else -}}TLSRoute{{- end -}}
{{- end }}
{{/* parentRefs list items for a route.
* Input: dict {manageLS(bool), name, gatewayAPI, gwName, gwNS}
* - manageLS on: attach to the service's ListenerSet (kind ListenerSet, sectionName=name)
* - off + gatewayAPI.parentRefs set: use those verbatim
* - off + no override: attach directly to the shared Gateway
*/}}
{{- define "spire-lib.gateway-parentref" -}}
{{- if .manageLS }}
- group: gateway.networking.k8s.io
kind: ListenerSet
name: {{ .name | quote }}
sectionName: {{ .name | quote }}
{{- else if .gatewayAPI.parentRefs }}
{{ toYaml .gatewayAPI.parentRefs }}
{{- else }}
- group: gateway.networking.k8s.io
kind: Gateway
name: {{ .gwName | quote }}
namespace: {{ .gwNS | quote }}
{{- with .gatewayAPI.sectionName }}
sectionName: {{ . | quote }}
{{- end }}
{{- end }}
{{- end }}
{{/* Emit Gateway API resources for one service: a Route, plus (optionally) a
* ListenerSet and a BackendTLSPolicy. Direct analogue of spire-lib.ingress-spec.
* Input dict:
* root - chart root context (.)
* gatewayAPI - the per-service gatewayAPI values block
* name - base name for the emitted resources
* namespace - namespace for the emitted resources
* svcName - backend Service name
* port - backend Service port (number)
* labels - pre-rendered labels YAML (string)
* routeKind - "TLSRoute" or "HTTPRoute"
* backendTLS - bool; when true and HTTPRoute, emit a BackendTLSPolicy (reencrypt)
* path - HTTPRoute path prefix (default "/")
*/}}
{{- define "spire-lib.gateway-routes" -}}
{{- $g := .gatewayAPI -}}
{{- $global := .root.Values.global -}}
{{- $host := include "spire-lib.ingress-calculated-name" (dict "ingress" (dict "host" $g.host) "Values" .root.Values) | trim -}}
{{- $gwName := include "spire-lib.gateway-name" (dict "global" $global) -}}
{{- $gwNS := include "spire-lib.gateway-namespace" (dict "global" $global "root" .root) -}}
{{- $port := include "spire-lib.gateway-port" (dict "global" $global) -}}
{{- $manageLS := eq (include "spire-lib.gateway-manage-listenersets" (dict "gatewayAPI" $g "global" $global)) "true" -}}
{{- $terminate := eq .routeKind "HTTPRoute" -}}
{{- $path := default "/" .path -}}
{{- $parentRefs := include "spire-lib.gateway-parentref" (dict "manageLS" $manageLS "name" .name "gatewayAPI" $g "gwName" $gwName "gwNS" $gwNS) -}}
apiVersion: gateway.networking.k8s.io/v1
kind: {{ .routeKind }}
metadata:
name: {{ .name }}
namespace: {{ .namespace }}
labels:
{{- .labels | nindent 4 }}
{{- with $g.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
parentRefs:
{{- $parentRefs | trim | nindent 4 }}
hostnames:
- {{ $host | quote }}
rules:
{{- if $terminate }}
- matches:
- path:
type: PathPrefix
value: {{ $path | quote }}
backendRefs:
- name: {{ .svcName | quote }}
port: {{ .port }}
{{- else }}
- backendRefs:
- name: {{ .svcName | quote }}
port: {{ .port }}
{{- end }}
{{- if $manageLS }}
---
apiVersion: gateway.networking.k8s.io/v1
kind: ListenerSet
metadata:
name: {{ .name }}
namespace: {{ .namespace }}
labels:
{{- .labels | nindent 4 }}
spec:
parentRef:
group: gateway.networking.k8s.io
kind: Gateway
name: {{ $gwName | quote }}
namespace: {{ $gwNS | quote }}
listeners:
- name: {{ .name }}
hostname: {{ $host | quote }}
port: {{ $port }}
{{- if $terminate }}
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- kind: Secret
group: ""
name: {{ $g.tlsSecret | quote }}
allowedRoutes:
namespaces:
from: Same
kinds:
- group: gateway.networking.k8s.io
kind: HTTPRoute
{{- else }}
protocol: TLS
tls:
mode: Passthrough
allowedRoutes:
namespaces:
from: Same
kinds:
- group: gateway.networking.k8s.io
kind: TLSRoute
{{- end }}
{{- end }}
{{- if and $terminate .backendTLS }}
---
apiVersion: gateway.networking.k8s.io/v1
kind: BackendTLSPolicy
metadata:
name: {{ .name }}
namespace: {{ .namespace }}
labels:
{{- .labels | nindent 4 }}
spec:
targetRefs:
- group: ""
kind: Service
name: {{ .svcName | quote }}
validation:
hostname: {{ $host | quote }}
caCertificateRefs:
{{- if dig "backendTLS" "caCertificateRefs" (list) $g }}
{{- toYaml $g.backendTLS.caCertificateRefs | nindent 6 }}
{{- else }}
- group: ""
kind: ConfigMap
name: {{ include "spire-lib.bundle-configmap" .root | trim | quote }}
{{- end }}
{{- end }}
{{- end }}
{{/* The shared Gateway object. Rendered only by the umbrella chart.
* Input: dict {root, gatewayObject} where gatewayObject is the umbrella-local
* `gatewayAPI.gateway` values block. name/namespace/port come from the global
* reference; className and listener policy are local.
*/}}
{{- define "spire-lib.gateway-resource" -}}
{{- $global := .root.Values.global -}}
{{- $obj := .gatewayObject -}}
{{- $gwName := include "spire-lib.gateway-name" (dict "global" $global) -}}
{{- $gwNS := include "spire-lib.gateway-namespace" (dict "global" $global "root" .root) -}}
{{- $port := include "spire-lib.gateway-port" (dict "global" $global) -}}
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: {{ $gwName }}
namespace: {{ $gwNS }}
{{- with $obj.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
gatewayClassName: {{ required "gatewayAPI.gateway.className is required to render the shared Gateway" $obj.className | quote }}
{{- with $obj.infrastructure }}
infrastructure:
{{- toYaml . | nindent 4 }}
{{- end }}
allowedListeners:
namespaces:
from: {{ default "All" $obj.allowedListenersNamespaces }}
listeners:
- name: base
protocol: TLS
port: {{ $port }}
tls:
mode: Passthrough
allowedRoutes:
namespaces:
from: {{ default "All" $obj.allowedRoutesNamespaces }}
{{- with $obj.extraListeners }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}