diff --git a/charts/eoapi/templates/_helpers/networking.tpl b/charts/eoapi/templates/_helpers/networking.tpl index 81efc07d..4c32bc90 100644 --- a/charts/eoapi/templates/_helpers/networking.tpl +++ b/charts/eoapi/templates/_helpers/networking.tpl @@ -1,6 +1,6 @@ {{/* -Return JSON array of enabled ingress services with resolved path, backend, and rewrite metadata. -Browser remains on the main ingress; skipStripPrefix excludes it from Traefik strip-prefix only. +Return JSON array of enabled API ingress services with resolved path, backend, and rewrite metadata. +Browser is intentionally excluded; it uses its own rewrite-free ingress. */}} {{- define "eoapi.enabledIngressServices" -}} {{- $root := . -}} @@ -9,45 +9,191 @@ Browser remains on the main ingress; skipStripPrefix excludes it from Traefik st (dict "key" "raster") (dict "key" "vector") (dict "key" "multidim") - (dict "key" "browser" "defaultPath" "/browser" "hasOwnPort" true "skipStripPrefix" true) - (dict "key" "mockOidcServer" "actualName" "mock-oidc-server" "hasOwnPort" true) + (dict "key" "mockOidcServer" "actualName" "mock-oidc-server" "hasOwnPort" true "defaultPath" "/mock-oidc" "config" $root.Values.testing.mockOidcServer) -}} {{- $resolved := list -}} {{- range $entries }} {{- $entry := . -}} - {{- $service := ternary (index $root.Values "testing" "mockOidcServer") (index $root.Values $entry.key) (eq $entry.key "mockOidcServer") -}} + {{- $service := $entry.config | default (index $root.Values $entry.key) -}} {{- $ingress := (($service | default dict).ingress) | default dict -}} {{- if and $service $service.enabled (or (not $service.ingress) $service.ingress.enabled) }} {{- $path := $ingress.path | default $entry.defaultPath -}} {{- $useAuthProxy := and $entry.usesAuthProxy (index $root.Values "stac-auth-proxy" "enabled") -}} - {{/* nginxStrip: NGINX rewrite path shape; stripPrefix: Traefik middleware (matches main, includes "/") */}} - {{- $nginxStrip := and (ne $path "/") (not $useAuthProxy) -}} - {{- $stripPrefix := and (not $entry.skipStripPrefix) (not $useAuthProxy) -}} + {{- $stripPath := and (ne $path "/") (not $useAuthProxy) -}} {{- $serviceName := $entry.actualName | default $entry.key -}} {{- $port := $root.Values.service.port -}} {{- if $entry.hasOwnPort }} {{- $port = (($service.service).port | default 8080) }} {{- end }} - {{- $resolved = append $resolved (dict "path" $path "serviceName" $serviceName "port" $port "useAuthProxy" $useAuthProxy "stripPath" $nginxStrip "stripPrefix" $stripPrefix) -}} + {{- $resolved = append $resolved (dict "path" $path "serviceName" $serviceName "port" $port "useAuthProxy" $useAuthProxy "stripPath" $stripPath) -}} {{- end }} {{- end }} {{- toJson $resolved -}} {{- end -}} {{/* -Return true when at least one ingress service or doc server is enabled. +Compute ingress gating flags and Traefik strip-prefixes in one pass. +Returns JSON: mainIngress, passthroughIngress, nginxRewrite, stripPrefixes. */}} -{{- define "eoapi.hasEnabledService" -}} -{{- if or (include "eoapi.enabledIngressServices" . | fromJsonArray) .Values.docServer.enabled -}}true{{- end -}} +{{- define "eoapi.ingressFlags" -}} +{{- $root := . -}} +{{- $services := include "eoapi.enabledIngressServices" $root | fromJsonArray -}} +{{- $isNginx := eq $root.Values.ingress.className "nginx" -}} +{{- $hasNonAuth := false -}} +{{- $hasAuthProxy := false -}} +{{- $nginxRewrite := false -}} +{{- $prefixes := list -}} +{{- range $services }} + {{- if .useAuthProxy }}{{ $hasAuthProxy = true }}{{ else }}{{ $hasNonAuth = true }}{{ end -}} + {{- if .stripPath }} + {{- $nginxRewrite = true -}} + {{- if .path }}{{ $prefixes = append $prefixes .path }}{{ end -}} + {{- end -}} +{{- end -}} +{{- $mainIngress := false -}} +{{- if $isNginx -}} + {{- if or $hasNonAuth $root.Values.docServer.enabled }}{{ $mainIngress = true }}{{ end -}} +{{- else -}} + {{- if or $hasNonAuth $hasAuthProxy $root.Values.docServer.enabled }}{{ $mainIngress = true }}{{ end -}} +{{- end -}} +{{- toJson (dict + "mainIngress" $mainIngress + "passthroughIngress" (and $isNginx $hasAuthProxy) + "nginxRewrite" $nginxRewrite + "stripPrefixes" $prefixes +) -}} {{- end -}} {{/* -Generate ingress path rules for enabled services and doc server. +Shared ingress annotations: entrypoints, user annotations, then chart-owned keys (last wins). +Call with dict "root" . "owned" "omit" — omit drops keys after merge. */}} -{{- define "eoapi.ingressPaths" -}} -{{- $root := . -}} +{{- define "eoapi.ingressCommonAnnotations" -}} +{{- $root := .root -}} +{{- $owned := .owned | default dict -}} +{{- $annotations := dict -}} +{{- if and (eq $root.Values.ingress.className "traefik") $root.Values.ingress.entrypoints -}} +{{- $_ := set $annotations "traefik.ingress.kubernetes.io/router.entrypoints" ($root.Values.ingress.entrypoints | toString) -}} +{{- end -}} +{{- $annotations = mergeOverwrite $annotations ($root.Values.ingress.annotations | default dict) -}} +{{- $mwKey := "traefik.ingress.kubernetes.io/router.middlewares" -}} +{{- if and (hasKey $annotations $mwKey) (hasKey $owned $mwKey) -}} +{{- $_ := set $owned $mwKey (printf "%s,%s" (get $annotations $mwKey) (get $owned $mwKey)) -}} +{{- end -}} +{{- $annotations = mergeOverwrite $annotations $owned -}} +{{- range (.omit | default list) }}{{ $_ := unset $annotations . }}{{ end -}} +{{- if not (empty $annotations) -}} +{{- toYaml $annotations -}} +{{- end -}} +{{- end -}} + +{{/* +Shared ingress rules block for host(s) and path lists. +Call with dict "root" . "pathsTemplate" "pathsMode" (empty mode is fine). +Always passes dict "root" $root "mode" $pathsMode to the paths template. +*/}} +{{- define "eoapi.ingressRules" -}} +{{- $root := .root -}} +{{- $pathsTemplate := .pathsTemplate -}} +{{- $pathsMode := .pathsMode | default "" -}} +{{- $pathsCtx := dict "root" $root "mode" $pathsMode -}} +{{- if $root.Values.ingress.hosts }} +{{- range $root.Values.ingress.hosts }} +- host: {{ . }} + http: + paths: + {{- include $pathsTemplate $pathsCtx | nindent 6 }} +{{- end }} +{{- else }} +- {{- if $root.Values.ingress.host }} + host: {{ $root.Values.ingress.host }} + {{- end }} + http: + paths: + {{- include $pathsTemplate $pathsCtx | nindent 6 }} +{{- end }} +{{- end -}} + +{{/* +Shared TLS block for ingress resources. +*/}} +{{- define "eoapi.ingressTls" -}} +{{- if and .Values.ingress.tls.enabled (or .Values.ingress.hosts .Values.ingress.host) }} +tls: + - hosts: + {{- if .Values.ingress.hosts }} + {{- range .Values.ingress.hosts }} + - {{ . }} + {{- end }} + {{- else if .Values.ingress.host }} + - {{ .Values.ingress.host }} + {{- end }} + secretName: {{ .Values.ingress.tls.secretName }} +{{- end }} +{{- end -}} + +{{/* +Render a full Ingress manifest. +Call with dict: + root, suffix, variant (main|auth-proxy|browser), pathsTemplate, + pathsMode (optional), flags (required for main; caller-computed eoapi.ingressFlags) +*/}} +{{- define "eoapi.ingressManifest" -}} +{{- $root := .root -}} +{{- $suffix := .suffix -}} +{{- $variant := .variant -}} +{{- $owned := dict -}} +{{- $omit := list -}} +{{- $nginxRewriteKeys := list "nginx.ingress.kubernetes.io/rewrite-target" "nginx.ingress.kubernetes.io/use-regex" -}} +{{- if eq $variant "main" }} + {{- $flags := .flags -}} + {{- if and (eq $root.Values.ingress.className "nginx") $flags.nginxRewrite }} + {{- $_ := set $owned "nginx.ingress.kubernetes.io/rewrite-target" "/$2" -}} + {{- $_ := set $owned "nginx.ingress.kubernetes.io/use-regex" "true" -}} + {{- end }} + {{- if and (eq $root.Values.ingress.className "traefik") (not (empty $flags.stripPrefixes)) }} + {{- $_ := set $owned "traefik.ingress.kubernetes.io/router.middlewares" (printf "%s-%s-strip-prefix-middleware@kubernetescrd" $root.Release.Namespace $root.Release.Name) -}} + {{- end }} +{{- end }} +{{- if ne $variant "main" }} + {{- $omit = $nginxRewriteKeys -}} +{{- end }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $root.Release.Name }}-{{ $suffix }} + labels: + app: {{ $root.Release.Name }}-{{ $suffix }} + {{- with include "eoapi.ingressCommonAnnotations" (dict "root" $root "owned" $owned "omit" $omit) | trim }} + annotations: + {{- . | nindent 4 }} + {{- end }} +spec: + {{- with $root.Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + rules: +{{- include "eoapi.ingressRules" (dict "root" $root "pathsTemplate" .pathsTemplate "pathsMode" (.pathsMode | default "")) | nindent 4 }} +{{- with include "eoapi.ingressTls" $root | trim }} +{{ . | indent 2 }} +{{- end }} +{{- end -}} + +{{/* +API ingress path rules. Call with dict "root" . "mode" "main"|"passthrough". +*/}} +{{- define "eoapi.apiIngressPaths" -}} +{{- $root := .root -}} +{{- $mode := .mode | default "main" -}} {{- $isNginx := eq $root.Values.ingress.className "nginx" -}} {{- range include "eoapi.enabledIngressServices" $root | fromJsonArray }} +{{- $include := false -}} +{{- if eq $mode "passthrough" }} + {{- $include = .useAuthProxy -}} +{{- else -}} + {{- $include = or (not $isNginx) (not .useAuthProxy) -}} +{{- end -}} +{{- if $include }} - pathType: {{ if and $isNginx .stripPath }}ImplementationSpecific{{ else }}Prefix{{ end }} path: {{ .path }}{{ if and $isNginx .stripPath }}(/|$)(.*){{ end }} backend: @@ -60,7 +206,8 @@ Generate ingress path rules for enabled services and doc server. port: number: {{ .port }} {{- end }} -{{- if $root.Values.docServer.enabled }} +{{- end }} +{{- if and (eq $mode "main") $root.Values.docServer.enabled }} - pathType: Prefix path: "/{{ $root.Values.ingress.rootPath | default "" }}" backend: @@ -72,21 +219,38 @@ Generate ingress path rules for enabled services and doc server. {{- end -}} {{/* -Return JSON array of path prefixes for Traefik strip-prefix middleware. +Return true when the browser should be exposed via its own ingress. */}} -{{- define "eoapi.traefikStripPrefixes" -}} -{{- $prefixes := list -}} -{{- range include "eoapi.enabledIngressServices" . | fromJsonArray }} - {{- if and .stripPrefix .path }} - {{- $prefixes = append $prefixes .path }} - {{- end }} -{{- end }} -{{- toJson $prefixes -}} +{{- define "eoapi.browserIngressEnabled" -}} +{{- $browser := .Values.browser -}} +{{- if and .Values.ingress.enabled $browser $browser.enabled (or (not $browser.ingress) $browser.ingress.enabled) -}}true{{- end -}} {{- end -}} {{/* -Return the configured browser ingress path without trailing slash. +Return the configured browser ingress path without trailing slash ("/" for a root path). */}} {{- define "eoapi.browserIngressPath" -}} {{- trimSuffix "/" ((((.Values.browser).ingress).path) | default "/browser") | default "/" -}} {{- end -}} + +{{/* +Return the canonical browser path prefix with trailing slash, as served by the pod (SB_pathPrefix). +*/}} +{{- define "eoapi.browserPathPrefix" -}} +{{- $bare := include "eoapi.browserIngressPath" . -}} +{{- if eq $bare "/" -}}/{{- else -}}{{ $bare }}/{{- end -}} +{{- end -}} + +{{/* +Browser ingress path rule. Call with dict "root" . +*/}} +{{- define "eoapi.browserIngressPaths" -}} +{{- $root := .root -}} +- pathType: Prefix + path: {{ include "eoapi.browserIngressPath" $root }} + backend: + service: + name: {{ $root.Release.Name }}-browser + port: + number: {{ (($root.Values.browser.service).port | default 8080) }} +{{- end -}} diff --git a/charts/eoapi/templates/networking/auth-proxy-ingress.yaml b/charts/eoapi/templates/networking/auth-proxy-ingress.yaml new file mode 100644 index 00000000..ca36211b --- /dev/null +++ b/charts/eoapi/templates/networking/auth-proxy-ingress.yaml @@ -0,0 +1,4 @@ +{{- $flags := include "eoapi.ingressFlags" . | fromJson -}} +{{- if and .Values.ingress.enabled $flags.passthroughIngress }} +{{- include "eoapi.ingressManifest" (dict "root" . "suffix" "auth-proxy-ingress" "variant" "auth-proxy" "pathsTemplate" "eoapi.apiIngressPaths" "pathsMode" "passthrough") }} +{{- end }} diff --git a/charts/eoapi/templates/networking/browser-ingress.yaml b/charts/eoapi/templates/networking/browser-ingress.yaml new file mode 100644 index 00000000..647b212b --- /dev/null +++ b/charts/eoapi/templates/networking/browser-ingress.yaml @@ -0,0 +1,3 @@ +{{- if include "eoapi.browserIngressEnabled" . | trim }} +{{- include "eoapi.ingressManifest" (dict "root" . "suffix" "browser-ingress" "variant" "browser" "pathsTemplate" "eoapi.browserIngressPaths") }} +{{- end }} diff --git a/charts/eoapi/templates/networking/ingress.yaml b/charts/eoapi/templates/networking/ingress.yaml index 8b7a657e..1ab01b9c 100644 --- a/charts/eoapi/templates/networking/ingress.yaml +++ b/charts/eoapi/templates/networking/ingress.yaml @@ -1,62 +1,4 @@ -{{- if and .Values.ingress.enabled (include "eoapi.hasEnabledService" . | trim | eq "true") }} -{{- $annotations := dict -}} -{{- if and (eq .Values.ingress.className "traefik") .Values.ingress.entrypoints -}} -{{- $_ := set $annotations "traefik.ingress.kubernetes.io/router.entrypoints" (.Values.ingress.entrypoints | toString) -}} -{{- end -}} -{{- $annotations = mergeOverwrite $annotations (.Values.ingress.annotations | default dict) -}} -{{- if eq .Values.ingress.className "nginx" }} -{{- $_ := set $annotations "nginx.ingress.kubernetes.io/rewrite-target" "/$2" -}} -{{- $_ := set $annotations "nginx.ingress.kubernetes.io/use-regex" "true" -}} -{{- end }} -{{- if and (eq .Values.ingress.className "traefik") (include "eoapi.traefikStripPrefixes" . | fromJsonArray) }} -{{- $_ := set $annotations "traefik.ingress.kubernetes.io/router.middlewares" (printf "%s-%s-strip-prefix-middleware@kubernetescrd" .Release.Namespace .Release.Name) -}} -{{- end }} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} -apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion }} -apiVersion: networking.k8s.io/v1beta1 -{{- else }} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ .Release.Name }}-ingress - labels: - app: {{ .Release.Name }}-ingress - {{- with $annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} - ingressClassName: {{ .Values.ingress.className }} - {{- end }} - rules: - {{- if .Values.ingress.hosts }} - {{- range .Values.ingress.hosts }} - - host: {{ . }} - http: - paths: - {{- include "eoapi.ingressPaths" $ | nindent 10 }} - {{- end }} - {{- else }} - - {{- if .Values.ingress.host }} - host: {{ .Values.ingress.host }} - {{- end }} - http: - paths: - {{- include "eoapi.ingressPaths" . | nindent 10 }} - {{- end }} - {{- if and .Values.ingress.tls.enabled (or .Values.ingress.hosts .Values.ingress.host) }} - tls: - - hosts: - {{- if .Values.ingress.hosts }} - {{- range .Values.ingress.hosts }} - - {{ . }} - {{- end }} - {{- else if .Values.ingress.host }} - - {{ .Values.ingress.host }} - {{- end }} - secretName: {{ .Values.ingress.tls.secretName }} - {{- end }} +{{- $flags := include "eoapi.ingressFlags" . | fromJson -}} +{{- if and .Values.ingress.enabled $flags.mainIngress }} +{{- include "eoapi.ingressManifest" (dict "root" . "suffix" "ingress" "variant" "main" "pathsTemplate" "eoapi.apiIngressPaths" "pathsMode" "main" "flags" $flags) }} {{- end }} diff --git a/charts/eoapi/templates/networking/traefik-middleware.yaml b/charts/eoapi/templates/networking/traefik-middleware.yaml index 74abce6a..41b93a82 100644 --- a/charts/eoapi/templates/networking/traefik-middleware.yaml +++ b/charts/eoapi/templates/networking/traefik-middleware.yaml @@ -1,6 +1,6 @@ {{- if and .Values.ingress.enabled (eq .Values.ingress.className "traefik") }} -{{- $prefixes := include "eoapi.traefikStripPrefixes" . | fromJsonArray -}} -{{- if $prefixes }} +{{- $flags := include "eoapi.ingressFlags" . | fromJson -}} +{{- if not (empty $flags.stripPrefixes) }} apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: @@ -10,7 +10,7 @@ metadata: spec: stripPrefix: prefixes: - {{- range $prefixes }} + {{- range $flags.stripPrefixes }} - {{ . }} {{- end }} {{- end }} diff --git a/charts/eoapi/templates/services/browser/deployment.yaml b/charts/eoapi/templates/services/browser/deployment.yaml index b108b7e1..a9e8e6c5 100644 --- a/charts/eoapi/templates/services/browser/deployment.yaml +++ b/charts/eoapi/templates/services/browser/deployment.yaml @@ -26,9 +26,8 @@ spec: ports: - containerPort: 8080 env: - {{- $browserPath := include "eoapi.browserIngressPath" . }} - name: SB_pathPrefix - value: {{ ternary "/" (printf "%s/" $browserPath) (eq $browserPath "/") | quote }} + value: {{ include "eoapi.browserPathPrefix" . | quote }} - name: SB_catalogUrl value: {{ .Values.browser.catalogUrl | default (printf "%s://%s%s" (ternary "https" "http" .Values.ingress.tls.enabled) .Values.ingress.host .Values.stac.ingress.path) | quote }} {{- if .Values.browser.catalogTitle }} @@ -50,7 +49,7 @@ spec: "openIdConnectUrl": "{{ .Values.browser.oidcDiscoveryUrl }}", "oidcConfig": { "client_id": "{{ .Values.browser.oidcClientId | default "test-client" }}", - "redirect_uri": "{{ if .Values.ingress.tls.enabled }}https{{ else }}http{{ end }}://{{ .Values.ingress.host }}{{ .Values.browser.ingress.path | default "/browser" | trimSuffix "/" }}/auth" + "redirect_uri": "{{ if .Values.ingress.tls.enabled }}https{{ else }}http{{ end }}://{{ .Values.ingress.host }}{{ trimSuffix "/" (((.Values.browser).ingress).path | default "/browser") }}/auth" } } {{- end }} diff --git a/charts/eoapi/tests/browser_ingress_test.yaml b/charts/eoapi/tests/browser_ingress_test.yaml new file mode 100644 index 00000000..be509d28 --- /dev/null +++ b/charts/eoapi/tests/browser_ingress_test.yaml @@ -0,0 +1,51 @@ +suite: browser ingress +templates: + - templates/_helpers/networking.tpl + - templates/networking/browser-ingress.yaml +set: + ingress.enabled: true + ingress.host: eoapi.local + stac.enabled: false + raster.enabled: false + vector.enabled: false + multidim.enabled: false + docServer.enabled: false + +tests: + - it: "creates the browser ingress when browser is the only enabled service" + template: templates/networking/browser-ingress.yaml + set: + ingress.className: traefik + browser.enabled: true + asserts: + - isKind: + of: Ingress + - equal: + path: metadata.labels.app + value: RELEASE-NAME-browser-ingress + - equal: + path: spec.rules[0].http.paths[0].path + value: "/browser" + + - it: "strips nginx rewrite annotations from the browser ingress" + template: templates/networking/browser-ingress.yaml + set: + ingress.className: nginx + ingress.annotations: + nginx.ingress.kubernetes.io/rewrite-target: /$2 + nginx.ingress.kubernetes.io/use-regex: "true" + cert-manager.io/cluster-issuer: letsencrypt-prod + browser.enabled: true + asserts: + - isKind: + of: Ingress + - equal: + path: spec.rules[0].http.paths[0].path + value: "/browser" + - notExists: + path: metadata.annotations["nginx.ingress.kubernetes.io/rewrite-target"] + - notExists: + path: metadata.annotations["nginx.ingress.kubernetes.io/use-regex"] + - equal: + path: metadata.annotations["cert-manager.io/cluster-issuer"] + value: letsencrypt-prod diff --git a/charts/eoapi/tests/ingress_test.yaml b/charts/eoapi/tests/ingress_test.yaml index 05919c04..40d7bff6 100644 --- a/charts/eoapi/tests/ingress_test.yaml +++ b/charts/eoapi/tests/ingress_test.yaml @@ -2,6 +2,7 @@ suite: unified ingress tests templates: - templates/_helpers/networking.tpl - templates/networking/ingress.yaml + tests: - it: "should not create ingress when all services disabled" template: templates/networking/ingress.yaml @@ -73,6 +74,8 @@ tests: - equal: path: spec.ingressClassName value: "traefik" + - notExists: + path: metadata.annotations["traefik.ingress.kubernetes.io/router.middlewares"] - it: "vector ingress with nginx controller" template: templates/networking/ingress.yaml @@ -223,12 +226,11 @@ tests: - equal: path: spec.rules[0].http.paths[2].path value: "/features(/|$)(.*)" - - equal: - path: spec.rules[0].http.paths[3].path - value: "/browser(/|$)(.*)" - - equal: - path: spec.rules[0].http.paths[0].pathType - value: "ImplementationSpecific" + - notContains: + path: spec.rules[0].http.paths + content: + path: "/browser" + any: true - it: "multiple hosts with services" template: templates/networking/ingress.yaml @@ -313,27 +315,7 @@ tests: path: spec.tls[0].secretName value: "eoapi-tls" - - it: "stac with auth proxy enabled" - template: templates/networking/ingress.yaml - set: - ingress.enabled: true - ingress.className: "nginx" - raster.enabled: false - stac.enabled: true - stac.ingress.enabled: true - stac.ingress.path: "/stac" - stac-auth-proxy.enabled: true - vector.enabled: false - multidim.enabled: false - browser.enabled: false - asserts: - - isKind: - of: Ingress - - equal: - path: spec.rules[0].http.paths[0].backend.service.name - value: RELEASE-NAME-stac-auth-proxy - - - it: "browser with default path" + - it: "docServer creates root path entry" template: templates/networking/ingress.yaml set: ingress.enabled: true @@ -342,19 +324,19 @@ tests: stac.enabled: false vector.enabled: false multidim.enabled: false - browser.enabled: true - browser.ingress.enabled: true + browser.enabled: false + docServer.enabled: true asserts: - isKind: of: Ingress - equal: path: spec.rules[0].http.paths[0].path - value: "/browser(/|$)(.*)" + value: "/" - equal: path: spec.rules[0].http.paths[0].backend.service.name - value: RELEASE-NAME-browser + value: RELEASE-NAME-doc-server - - it: "docServer creates root path entry" + - it: "mockOidcServer with custom path" template: templates/networking/ingress.yaml set: ingress.enabled: true @@ -364,18 +346,21 @@ tests: vector.enabled: false multidim.enabled: false browser.enabled: false - docServer.enabled: true + testing.mockOidcServer.enabled: true + testing.mockOidcServer.service.port: 8080 + testing.mockOidcServer.ingress.enabled: true + testing.mockOidcServer.ingress.path: "/auth" asserts: - isKind: of: Ingress - equal: path: spec.rules[0].http.paths[0].path - value: "/" + value: "/auth(/|$)(.*)" - equal: path: spec.rules[0].http.paths[0].backend.service.name - value: RELEASE-NAME-doc-server + value: RELEASE-NAME-mock-oidc-server - - it: "mockOidcServer with custom path" + - it: "mockOidcServer without ingress block falls back to default path" template: templates/networking/ingress.yaml set: ingress.enabled: true @@ -386,18 +371,18 @@ tests: multidim.enabled: false browser.enabled: false testing.mockOidcServer.enabled: true - testing.mockOidcServer.service.port: 8080 - testing.mockOidcServer.ingress.enabled: true - testing.mockOidcServer.ingress.path: "/auth" asserts: - isKind: of: Ingress - equal: path: spec.rules[0].http.paths[0].path - value: "/auth(/|$)(.*)" + value: "/mock-oidc(/|$)(.*)" - equal: path: spec.rules[0].http.paths[0].backend.service.name value: RELEASE-NAME-mock-oidc-server + - equal: + path: spec.rules[0].http.paths[0].backend.service.port.number + value: 8080 - it: "custom annotations preserved" template: templates/networking/ingress.yaml @@ -446,7 +431,7 @@ tests: cert-manager.io/cluster-issuer: "letsencrypt-prod" traefik.ingress.kubernetes.io/router.middlewares: NAMESPACE-RELEASE-NAME-strip-prefix-middleware@kubernetescrd - - it: "traefik chart middlewares win over user router.middlewares annotation" + - it: "traefik chart middlewares merge with user router.middlewares annotation" template: templates/networking/ingress.yaml set: ingress.className: "traefik" @@ -461,7 +446,7 @@ tests: asserts: - equal: path: metadata.annotations["traefik.ingress.kubernetes.io/router.middlewares"] - value: NAMESPACE-RELEASE-NAME-strip-prefix-middleware@kubernetescrd + value: custom-auth@kubernetescrd,NAMESPACE-RELEASE-NAME-strip-prefix-middleware@kubernetescrd - it: "nginx chart rewrite annotations win over user rewrite-target/use-regex" template: templates/networking/ingress.yaml @@ -537,6 +522,7 @@ tests: path: spec.rules[0].http.paths content: path: "/raster(/|$)(.*)" + any: true - it: "multidim ingress with traefik controller" template: templates/networking/ingress.yaml diff --git a/charts/eoapi/tests/stac-auth-proxy-ingress_test.yaml b/charts/eoapi/tests/stac-auth-proxy-ingress_test.yaml index e25ae95c..eaa7c704 100644 --- a/charts/eoapi/tests/stac-auth-proxy-ingress_test.yaml +++ b/charts/eoapi/tests/stac-auth-proxy-ingress_test.yaml @@ -1,11 +1,13 @@ suite: test ingress routing without stripPrefix middleware templates: - templates/_helpers/networking.tpl + - templates/networking/auth-proxy-ingress.yaml + - templates/networking/browser-ingress.yaml - templates/networking/ingress.yaml tests: - - it: should route ingress to stac-auth-proxy when enabled - template: templates/networking/ingress.yaml + - it: should route auth-proxy on rewrite-free ingress when enabled on nginx + template: templates/networking/auth-proxy-ingress.yaml set: ingress.enabled: true ingress.className: nginx @@ -15,6 +17,9 @@ tests: stac-auth-proxy.enabled: true service.port: 8080 asserts: + - equal: + path: metadata.name + value: RELEASE-NAME-auth-proxy-ingress - contains: path: spec.rules[0].http.paths content: @@ -25,6 +30,30 @@ tests: name: RELEASE-NAME-stac-auth-proxy port: number: 8080 + - notExists: + path: metadata.annotations["nginx.ingress.kubernetes.io/rewrite-target"] + - notExists: + path: metadata.annotations["nginx.ingress.kubernetes.io/use-regex"] + + - it: should omit auth-proxy path from main nginx ingress + template: templates/networking/ingress.yaml + set: + ingress.enabled: true + ingress.className: nginx + stac.enabled: true + stac.ingress.path: "/stac" + stac-auth-proxy.enabled: true + raster.enabled: true + service.port: 8080 + asserts: + - notContains: + path: spec.rules[0].http.paths + content: + path: /stac + any: true + - equal: + path: metadata.annotations["nginx.ingress.kubernetes.io/rewrite-target"] + value: /$2 - it: should route ingress directly to stac when auth-proxy is disabled template: templates/networking/ingress.yaml @@ -48,27 +77,31 @@ tests: port: number: 8080 - - it: should not create ingress when both stac and browser are disabled - template: templates/networking/ingress.yaml + - it: should not create auth-proxy ingress when auth-proxy is disabled + template: templates/networking/auth-proxy-ingress.yaml set: ingress.enabled: true - stac.enabled: false - browser.enabled: false - stac-auth-proxy.enabled: true - raster.enabled: false - vector.enabled: false - multidim.enabled: false - docServer.enabled: false + ingress.className: nginx + stac.enabled: true + stac-auth-proxy.enabled: false asserts: - hasDocuments: count: 0 - - it: should route correctly with experimental profile + - it: should keep auth-proxy on main ingress for traefik template: templates/networking/ingress.yaml - values: - - ../profiles/experimental.yaml set: ingress.enabled: true + ingress.className: traefik + stac.enabled: true + stac.ingress.path: "/stac" + stac-auth-proxy.enabled: true + raster.enabled: false + vector.enabled: false + multidim.enabled: false + browser.enabled: false + docServer.enabled: false + service.port: 8080 asserts: - contains: path: spec.rules[0].http.paths @@ -80,30 +113,23 @@ tests: name: RELEASE-NAME-stac-auth-proxy port: number: 8080 + # no path needs stripping here, so the strip-prefix middleware never renders; + # the router annotation must not dangle a reference to it either (#568) + - notExists: + path: metadata.annotations["traefik.ingress.kubernetes.io/router.middlewares"] - - it: should route ingress to browser - template: templates/networking/ingress.yaml + - it: should not create auth-proxy ingress for traefik + template: templates/networking/auth-proxy-ingress.yaml set: ingress.enabled: true - ingress.className: nginx - browser.enabled: true - stac.enabled: false - raster.enabled: false - vector.enabled: false - multidim.enabled: false + ingress.className: traefik + stac.enabled: true + stac-auth-proxy.enabled: true asserts: - - contains: - path: spec.rules[0].http.paths - content: - pathType: ImplementationSpecific - path: /browser(/|$)(.*) - backend: - service: - name: RELEASE-NAME-browser - port: - number: 8080 + - hasDocuments: + count: 0 - - it: should include both stac and browser when both enabled + - it: should omit auth-proxy and browser from main nginx ingress when both enabled template: templates/networking/ingress.yaml set: ingress.enabled: true @@ -113,25 +139,16 @@ tests: stac.ingress.path: "/stac" stac-auth-proxy.enabled: true browser.enabled: true + raster.enabled: true service.port: 8080 asserts: - - contains: + - notContains: path: spec.rules[0].http.paths content: - pathType: Prefix path: /stac - backend: - service: - name: RELEASE-NAME-stac-auth-proxy - port: - number: 8080 - - contains: + any: true + - notContains: path: spec.rules[0].http.paths content: - pathType: ImplementationSpecific - path: /browser(/|$)(.*) - backend: - service: - name: RELEASE-NAME-browser - port: - number: 8080 + path: /browser + any: true diff --git a/charts/eoapi/tests/stac_browser_tests.yaml b/charts/eoapi/tests/stac_browser_tests.yaml index 5c4107e3..a13c5220 100644 --- a/charts/eoapi/tests/stac_browser_tests.yaml +++ b/charts/eoapi/tests/stac_browser_tests.yaml @@ -22,6 +22,27 @@ tests: - matchRegex: path: metadata.labels.app pattern: ^RELEASE-NAME-browser$ + - contains: + path: spec.template.spec.containers[0].env + content: + name: SB_pathPrefix + value: "/browser/" + - it: "stac browser deployment with custom ingress path" + set: + raster.enabled: false + stac.enabled: false + vector.enabled: false + multidim.enabled: false + browser.enabled: true + browser.ingress.path: "/stac-browser" + template: templates/services/browser/deployment.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: SB_pathPrefix + value: "/stac-browser/" + - it: "stac browser service" set: raster.enabled: false @@ -84,6 +105,35 @@ tests: "redirect_uri": "http://localhost/browser/auth" } } + - it: "stac browser auth redirect_uri with root ingress path" + set: + raster.enabled: false + stac.enabled: true + vector.enabled: false + multidim.enabled: false + browser.enabled: true + browser.ingress.path: "/" + stac-auth-proxy.enabled: true + stac-auth-proxy.env.OIDC_DISCOVERY_URL: "http://localhost/mock-oidc/.well-known/openid-configuration" + ingress.host: "localhost" + stac.ingress.path: "/stac" + browser.oidcClientId: "test-client" + browser.oidcDiscoveryUrl: "http://localhost/mock-oidc/.well-known/openid-configuration" + template: templates/services/browser/deployment.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: SB_authConfig + value: |- + { + "type": "openIdConnect", + "openIdConnectUrl": "http://localhost/mock-oidc/.well-known/openid-configuration", + "oidcConfig": { + "client_id": "test-client", + "redirect_uri": "http://localhost/auth" + } + } - it: "stac browser deployment with custom OIDC_DISCOVERY_URL" set: raster.enabled: false @@ -137,6 +187,7 @@ tests: path: spec.template.spec.containers[0].env content: name: SB_authConfig + any: true - it: "stac browser deployment with custom catalogUrl override" set: raster.enabled: false @@ -363,7 +414,7 @@ tests: asserts: - isKind: of: Deployment - # default extraEnv ([]) adds nothing — only the built-in SB_pathPrefix and SB_catalogUrl remain + # default extraEnv ([]) adds nothing — built-in SB_pathPrefix and SB_catalogUrl remain - lengthEqual: path: spec.template.spec.containers[0].env count: 2 diff --git a/charts/eoapi/tests/traefik_middleware_test.yaml b/charts/eoapi/tests/traefik_middleware_test.yaml index c5e4afe4..14773328 100644 --- a/charts/eoapi/tests/traefik_middleware_test.yaml +++ b/charts/eoapi/tests/traefik_middleware_test.yaml @@ -1,8 +1,8 @@ suite: traefik strip-prefix middleware templates: - templates/_helpers/networking.tpl - - templates/networking/ingress.yaml - templates/networking/traefik-middleware.yaml + tests: - it: "traefik strip-prefix middleware renders enabled service prefixes" template: templates/networking/traefik-middleware.yaml @@ -43,22 +43,16 @@ tests: path: spec.stripPrefix.prefixes value: - /raster - - - it: "omits the strip-prefix annotation when no middleware renders, so the router reference never dangles (#568)" + - it: "renders no strip-prefix middleware when no path needs stripping" + template: templates/networking/traefik-middleware.yaml set: - ingress.className: traefik - ingress.host: eoapi.local - browser.enabled: true - browser.ingress.path: "/" - stac.enabled: false + ingress.className: "traefik" + stac.enabled: true + stac.ingress.path: "/" raster.enabled: false vector.enabled: false multidim.enabled: false - docServer.enabled: false + browser.enabled: false asserts: - - template: templates/networking/traefik-middleware.yaml - hasDocuments: + - hasDocuments: count: 0 - - template: templates/networking/ingress.yaml - notExists: - path: metadata.annotations["traefik.ingress.kubernetes.io/router.middlewares"] diff --git a/docs/unified-ingress.md b/docs/unified-ingress.md index 83ec93ce..356d08a5 100644 --- a/docs/unified-ingress.md +++ b/docs/unified-ingress.md @@ -31,7 +31,7 @@ eoAPI includes a single streamlined ingress configuration with smart defaults th - Custom paths: `raster.ingress.path: "/tiles"` - Internal-only services stay off ingress -**Note:** Ingress is only created when at least one service is enabled. +**Note:** The main ingress is created when at least one API service or doc server is enabled. The browser has its own rewrite-free ingress when `ingress.enabled` is true, browser is enabled, and `browser.ingress` is not disabled (omitting `browser.ingress` still enables it). ## Configuration @@ -91,7 +91,7 @@ All services use `pathType: Prefix` and handle their own path prefixes internall - **Raster**: `--root-path=/raster` - **Vector**: `--root-path=/vector` - **Multidim**: `--root-path=/multidim` -- **Browser**: Configured via environment variable +- **Browser**: Served from a separate rewrite-free ingress; the pod receives `SB_pathPrefix` from `browser.ingress.path` ### Ingress Controller Support @@ -141,21 +141,18 @@ Ingress strips /raster Service receives: GET /tiles/123 ``` -**Exception:** STAC with `stac-auth-proxy.enabled: true` receives full path `/stac/...` +**Exception:** STAC with `stac-auth-proxy.enabled: true` receives full path `/stac/...`. On NGINX this is served from a separate rewrite-free ingress; Traefik keeps auth-proxy on the main ingress and omits `/stac` from strip-prefix. ## STAC Browser Configuration -The STAC browser now uses a separate ingress configuration to handle its unique requirements: -- Fixed `/browser` path prefix -- Special rewrite rules for browser-specific routes -- Maintains compatibility with both NGINX and Traefik +The STAC browser uses its own ingress so path rewriting does not strip the browser prefix. The pod receives `SB_pathPrefix` from `browser.ingress.path`. -The browser-specific ingress is automatically configured when browser is enabled: ```yaml browser: enabled: true ingress: enabled: true # Can be disabled independently + path: "/browser" ``` ### Custom Ingress Solutions