diff --git a/charts/library/common-test/tests/ingress/nginx_test.yaml b/charts/library/common-test/tests/ingress/nginx_test.yaml new file mode 100644 index 00000000000..fffa0486e18 --- /dev/null +++ b/charts/library/common-test/tests/ingress/nginx_test.yaml @@ -0,0 +1,252 @@ +suite: ingress - nginx metadata test +templates: + - common.yaml +chart: + appVersion: &appVer v9.9.9 +release: + name: test-release-name + namespace: test-release-namespace +tests: + - it: should pass with ingress created with ipWhiteList annotations from nginx + set: + service: &service + my-service: + enabled: true + primary: true + ports: + main: + enabled: true + primary: true + port: 80 + ingress: + my-ingress: + enabled: true + primary: true + hosts: &hosts + - host: test-host + paths: + - path: /test-path + integrations: + nginx: + enabled: true + ipWhitelist: + - 10.0.0.0/8 + - 192.168.0.0/16 + asserts: + - documentIndex: &ingressDoc 1 + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + isSubset: + path: metadata.annotations + content: + nginx.ingress.kubernetes.io/whitelist-source-range: 10.0.0.0/8,192.168.0.0/16 + + - it: should pass with ingress created with themePark annotations from nginx + set: + service: *service + ingress: + my-ingress: + enabled: true + primary: true + hosts: *hosts + integrations: + nginx: + enabled: true + themePark: + enabled: true + css: https://example.com/theme.css + asserts: + - documentIndex: &ingressDoc 1 + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + isSubset: + path: metadata.annotations + content: + nginx.ingress.kubernetes.io/configuration-snippet: |- + proxy_set_header Accept-Encoding ""; + sub_filter + '' + ' + '; + sub_filter_once on; + + - it: should pass with ingress created with authentik annotations from nginx + set: + service: *service + ingress: + my-ingress: + enabled: true + primary: true + hosts: *hosts + integrations: + nginx: + enabled: true + auth: + type: authentik + internalHost: internal.host + externalHost: external.host + asserts: + - documentIndex: &ingressDoc 1 + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + isSubset: + path: metadata.annotations + content: + nginx.ingress.kubernetes.io/auth-method: GET + nginx.ingress.kubernetes.io/auth-response-headers: Set-Cookie,X-authentik-username,X-authentik-groups,X-authentik-entitlements,X-authentik-email,X-authentik-name,X-authentik-uid + nginx.ingress.kubernetes.io/auth-signin: https://external.host/outpost.goauthentik.io/start?rd=$scheme://$http_host$escaped_request_uri + nginx.ingress.kubernetes.io/auth-snippet: proxy_set_header X-Forwarded-Host $http_host; + nginx.ingress.kubernetes.io/auth-url: http://internal.host/outpost.goauthentik.io/auth/nginx + nginx.ingress.kubernetes.io/backend-protocol: HTTPS + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + + - it: should pass with ingress created with authelia annotations from nginx + set: + service: *service + ingress: + my-ingress: + enabled: true + primary: true + hosts: *hosts + integrations: + nginx: + enabled: true + auth: + type: authelia + internalHost: internal.host + externalHost: external.host + asserts: + - documentIndex: &ingressDoc 1 + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + isSubset: + path: metadata.annotations + content: + nginx.ingress.kubernetes.io/auth-method: GET + nginx.ingress.kubernetes.io/auth-signin: https://external.host?rm=$request_method + nginx.ingress.kubernetes.io/auth-url: http://internal.host/api/verify + + # Failures + + - it: should fail with ipWhitelist not a slice + set: + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: + nginx: + enabled: true + ipWhitelist: "not a slice" + hosts: *hosts + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [integrations.nginx.ipWhitelist] to be a [slice], but got [string] + + - it: should fail with auth.type not a valid type + set: + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: + nginx: + enabled: true + auth: + type: "invalid" + hosts: *hosts + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [integrations.nginx.auth.type] to be one of [authentik, authelia], but got [invalid] + + - it: should fail with auth.type set to authentik but no auth.internalHost + set: + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: + nginx: + enabled: true + auth: + type: authentik + hosts: *hosts + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [integrations.nginx.auth.internalHost] and [integrations.nginx.auth.externalHost] to be set + + - it: should fail with auth.type set to authentik but no auth.externalHost + set: + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: + nginx: + enabled: true + auth: + type: authentik + internalHost: internal.host + hosts: *hosts + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [integrations.nginx.auth.internalHost] and [integrations.nginx.auth.externalHost] to be set + + - it: should fail with auth.type set to authelia but no auth.internalHost + set: + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: + nginx: + enabled: true + auth: + type: authelia + hosts: *hosts + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [integrations.nginx.auth.internalHost] and [integrations.nginx.auth.externalHost] to be set + + - it: should fail with auth.type set to authelia but no auth.externalHost + set: + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: + nginx: + enabled: true + auth: + type: authelia + internalHost: internal.host + hosts: *hosts + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [integrations.nginx.auth.internalHost] and [integrations.nginx.auth.externalHost] to be set diff --git a/charts/library/common/Chart.yaml b/charts/library/common/Chart.yaml index 99c7c19a02b..3acc7116e1d 100644 --- a/charts/library/common/Chart.yaml +++ b/charts/library/common/Chart.yaml @@ -48,4 +48,4 @@ sources: - https://hub.docker.com/_/ - https://hub.docker.com/r/mikefarah/yq type: library -version: 28.3.0 +version: 28.4.0 diff --git a/charts/library/common/templates/class/_ingress.tpl b/charts/library/common/templates/class/_ingress.tpl index 1b5969395f2..14c730eeb68 100644 --- a/charts/library/common/templates/class/_ingress.tpl +++ b/charts/library/common/templates/class/_ingress.tpl @@ -49,6 +49,7 @@ objectData: The object data to be used to render the Ingress. {{- include "tc.v1.common.lib.ingress.integration.certManager" (dict "rootCtx" $rootCtx "objectData" $objectData) -}} {{- include "tc.v1.common.lib.ingress.integration.traefik" (dict "rootCtx" $rootCtx "objectData" $objectData) -}} + {{- include "tc.v1.common.lib.ingress.integration.nginx" (dict "rootCtx" $rootCtx "objectData" $objectData) -}} {{- if ne $ingressClassName "tc-stopped" -}}{{/* If is stopped, dont render homepage annotations */}} {{- include "tc.v1.common.lib.ingress.integration.homepage" (dict "rootCtx" $rootCtx "objectData" $objectData) -}} {{- end }} diff --git a/charts/library/common/templates/lib/ingress/integrations/_nginx.tpl b/charts/library/common/templates/lib/ingress/integrations/_nginx.tpl new file mode 100644 index 00000000000..8c53b1b713b --- /dev/null +++ b/charts/library/common/templates/lib/ingress/integrations/_nginx.tpl @@ -0,0 +1,32 @@ +{{- define "tc.v1.common.lib.ingress.integration.nginx" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx -}} + + {{- $nginx := $objectData.integrations.nginx -}} + + {{- if $nginx.enabled -}} + + {{/* ipWhiteList */}} + {{- if $nginx.ipWhitelist -}} + {{- include "tc.v1.common.lib.ingress.integration.nginx.ipWhitelist" (dict "objectData" $objectData "whiteList" $nginx.ipWhitelist) -}} + {{- end -}} + + {{/* themePark */}} + {{- if and $nginx.themePark $nginx.themePark.enabled -}} + {{- include "tc.v1.common.lib.ingress.integration.nginx.themePark" (dict "objectData" $objectData "themePark" $nginx.themePark) -}} + {{- end -}} + + {{/* Auth */}} + {{- $validAuthTypes := (list "authentik" "authelia") -}} + {{- if and $nginx.auth $nginx.auth.type -}} + {{- if eq $nginx.auth.type "authentik" -}} + {{- include "tc.v1.common.lib.ingress.integration.nginx.auth.authentik" (dict "objectData" $objectData "auth" $nginx.auth) -}} + {{- else if eq $nginx.auth.type "authelia" -}} + {{- include "tc.v1.common.lib.ingress.integration.nginx.auth.authelia" (dict "objectData" $objectData "auth" $nginx.auth) -}} + {{- else -}} + {{- fail (printf "Ingress - Expected [integrations.nginx.auth.type] to be one of [%s], but got [%s]" (join ", " $validAuthTypes) $nginx.auth.type) -}} + {{- end -}} + {{- end -}} + + {{- end -}} +{{- end -}} diff --git a/charts/library/common/templates/lib/ingress/integrations/nginx/_auth.tpl b/charts/library/common/templates/lib/ingress/integrations/nginx/_auth.tpl new file mode 100644 index 00000000000..626bf772603 --- /dev/null +++ b/charts/library/common/templates/lib/ingress/integrations/nginx/_auth.tpl @@ -0,0 +1,54 @@ +{{- define "tc.v1.common.lib.ingress.integration.nginx.auth.authentik" -}} + {{- $objectData := .objectData -}} + {{- $auth := .auth -}} + + {{- if and $auth.respondHeaders (not (kindIs "slice" $auth.responseHeaders)) -}} + {{- fail (printf "Ingress - Expected [integrations.nginx.auth.responseHeaders] to be a [slice], but got [%s]" (kindOf $auth.responseHeaders)) -}} + {{- end -}} + + {{- $respHeaders := ($auth.responseHeaders | default (list + "Set-Cookie" + "X-authentik-username" + "X-authentik-groups" + "X-authentik-entitlements" + "X-authentik-email" + "X-authentik-name" + "X-authentik-uid" + )) -}} + + {{- if or (not $auth.internalHost) (not $auth.externalHost) -}} + {{- fail "Ingress - Expected [integrations.nginx.auth.internalHost] and [integrations.nginx.auth.externalHost] to be set" -}} + {{- end -}} + + {{- $_ := set $objectData.annotations "nginx.ingress.kubernetes.io/auth-method" "GET" -}} + {{- $_ := set $objectData.annotations "nginx.ingress.kubernetes.io/auth-response-headers" (join "," $respHeaders) -}} + {{- $_ := set $objectData.annotations "nginx.ingress.kubernetes.io/auth-snippet" "proxy_set_header X-Forwarded-Host $http_host;" -}} + {{- $_ := set $objectData.annotations "nginx.ingress.kubernetes.io/backend-protocol" "HTTPS" -}} + {{- $_ := set $objectData.annotations "nginx.ingress.kubernetes.io/force-ssl-redirect" "true" -}} + {{- $_ := set $objectData.annotations "nginx.ingress.kubernetes.io/auth-url" (printf "http://%s/outpost.goauthentik.io/auth/nginx" $auth.internalHost) -}} + {{- $_ := set $objectData.annotations "nginx.ingress.kubernetes.io/auth-signin" (printf "https://%s/outpost.goauthentik.io/start?rd=$scheme://$http_host$escaped_request_uri" $auth.externalHost) -}} +{{- end -}} + +{{- define "tc.v1.common.lib.ingress.integration.nginx.auth.authelia" -}} + {{- $objectData := .objectData -}} + {{- $auth := .auth -}} + + {{- if and $auth.respondHeaders (not (kindIs "slice" $auth.responseHeaders)) -}} + {{- fail (printf "Ingress - Expected [integrations.nginx.auth.responseHeaders] to be a [slice], but got [%s]" (kindOf $auth.responseHeaders)) -}} + {{- end -}} + + {{- $respHeaders := ($auth.responseHeaders | default (list + "Remote-User" + "Remote-Name" + "Remote-Groups" + "Remote-Email" + )) -}} + + {{- if or (not $auth.internalHost) (not $auth.externalHost) -}} + {{- fail "Ingress - Expected [integrations.nginx.auth.internalHost] and [integrations.nginx.auth.externalHost] to be set" -}} + {{- end -}} + + {{- $_ := set $objectData.annotations "nginx.ingress.kubernetes.io/auth-method" "GET" -}} + {{- $_ := set $objectData.annotations "nginx.ingress.kubernetes.io/auth-url" (printf "http://%s/api/verify" $auth.internalHost) -}} + {{- $_ := set $objectData.annotations "nginx.ingress.kubernetes.io/auth-signin" (printf "https://%s?rm=$request_method" $auth.externalHost) -}} +{{- end -}} diff --git a/charts/library/common/templates/lib/ingress/integrations/nginx/_ipWhiteList.tpl b/charts/library/common/templates/lib/ingress/integrations/nginx/_ipWhiteList.tpl new file mode 100644 index 00000000000..f7e958b8f9e --- /dev/null +++ b/charts/library/common/templates/lib/ingress/integrations/nginx/_ipWhiteList.tpl @@ -0,0 +1,12 @@ +{{- define "tc.v1.common.lib.ingress.integration.nginx.ipWhitelist" -}} + {{- $objectData := .objectData -}} + {{- $whiteList := .whiteList -}} + + {{- if not (kindIs "slice" $whiteList) -}} + {{- fail (printf "Ingress - Expected [integrations.nginx.ipWhitelist] to be a [slice], but got [%s]" (kindOf $whiteList)) -}} + {{- end -}} + + {{- if $whiteList -}} + {{- $_ := set $objectData.annotations "nginx.ingress.kubernetes.io/whitelist-source-range" (join "," $whiteList) -}} + {{- end -}} +{{- end -}} diff --git a/charts/library/common/templates/lib/ingress/integrations/nginx/_themePark.tpl b/charts/library/common/templates/lib/ingress/integrations/nginx/_themePark.tpl new file mode 100644 index 00000000000..81e4e7f2c06 --- /dev/null +++ b/charts/library/common/templates/lib/ingress/integrations/nginx/_themePark.tpl @@ -0,0 +1,18 @@ +{{- define "tc.v1.common.lib.ingress.integration.nginx.themePark" -}} + {{- $objectData := .objectData -}} + {{- $theme := .themePark -}} + {{- if and $theme $theme.enabled (not (kindIs "string" $theme.css)) -}} + {{- fail (printf "Ingress - Expected [integrations.nginx.themepark.css] to be a [string], but got [%s]" (kindOf $theme.css)) -}} + {{- end -}} + + {{- $snippet := (list + "proxy_set_header Accept-Encoding \"\";" + "sub_filter" + "''" + (printf "'" $theme.css) + "';" + "sub_filter_once on;" + ) -}} + + {{- $_ := set $objectData.annotations "nginx.ingress.kubernetes.io/configuration-snippet" (join "\n" $snippet) -}} +{{- end -}} diff --git a/charts/library/common/values.yaml b/charts/library/common/values.yaml index fe9762e7645..1ede6196d31 100644 --- a/charts/library/common/values.yaml +++ b/charts/library/common/values.yaml @@ -665,6 +665,21 @@ ingress: # # Optional, by default will try to # # "lookup" the namespace based on the name # namespace: "" + nginx: + enabled: false + themepark: + enabled: false + css: "" + ipWhitelist: [] + auth: + # empty to disable, options: "authentik" or "authelia" + type: "" + # Internal Domain name + port to reach the auth provider, excluding http(s) + internalHost: "" + # External (ingress) Domain name to reach the auth provider, excluding http(s) + externalHost: "" + # Optional: override default response headers + responseHeaders: [] homepage: enabled: false # Default: chart name diff --git a/cspell.config.yaml b/cspell.config.yaml index 176cfac3d58..56fb268e25e 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -333,6 +333,7 @@ words: - statping - steamcommunica - steammasterser + - stylesheet - subchart - sublist - subquestion