feat(traefik-forward-auth): add traefik-forward-auth (#6965)

* Initial commit for traefik-forward-auth

* Add providers, change variable naming

* Change chart version

* Add container SHA, add secrets as env

* feat(traefik-forward-auth): add traefik-fwd-auth

* Follow prettier in docs, adding default values

* Follow prettier in docs and Chart.yaml
This commit is contained in:
Csaba Engedi 2023-02-19 21:54:23 +01:00 committed by GitHub
parent 45c820f4d4
commit b5d4aced41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 737 additions and 0 deletions

View File

@ -0,0 +1,30 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
# OWNERS file for Kubernetes
OWNERS
# helm-docs templates
*.gotmpl
# docs folder
/docs
# icon
icon.png

View File

@ -0,0 +1,35 @@
apiVersion: v2
kubeVersion: ">=1.16.0-0"
name: traefik-forward-auth
version: 0.1.0
appVersion: "2.2.0"
description: A minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer. An example for a typical setup is included in the source (docs/how-to.md).
type: application
deprecated: false
home: https://github.com/truecharts/charts/tree/master/charts/incubator/traefik-forward-auth
icon: https://raw.githubusercontent.com/truecharts/charts/master/incubator/traefik-forward-auth/icon.png?raw=true
keywords:
- traefik-forward-auth
- traefik
- forward-auth
- auth
- ingress
- middleware
sources:
- https://github.com/truecharts/charts/tree/master/charts/incubator/traefik-forward-auth
- https://github.com/thomseddon/traefik-forward-auth
dependencies:
- name: common
repository: https://library-charts.truecharts.org
version:
11.1.2
# condition:
maintainers:
- email: info@truecharts.org
name: TrueCharts
url: https://truecharts.org
annotations:
truecharts.org/catagories: |
- network
truecharts.org/SCALE-support: "true"
truecharts.org/grade: U

View File

@ -0,0 +1,26 @@
# README
## General Info
TrueCharts can be installed as both *normal* Helm Charts or as Apps on TrueNAS SCALE.
However only installations using the TrueNAS SCALE Apps system are supported.
For more information about this App, please check the docs on the TrueCharts [website](https://truecharts.org/charts/stable/)
**This chart is not maintained by the upstream project and any issues with the chart should be raised [here](https://github.com/truecharts/charts/issues/new/choose)**
## Support
- Please check our [quick-start guides for TrueNAS SCALE](https://truecharts.org/manual/SCALE%20Apps/Important-MUST-READ).
- See the [Website](https://truecharts.org)
- Check our [Discord](https://discord.gg/tVsPTHWTtr)
- Open a [issue](https://github.com/truecharts/charts/issues/new/choose)
---
## Sponsor TrueCharts
TrueCharts can only exist due to the incredible effort of our staff.
Please consider making a [donation](https://truecharts.org/sponsor) or contributing back to the project any way you can!
*All Rights Reserved - The TrueCharts Project*

View File

@ -0,0 +1,71 @@
# traefik-forward-auth
This app makes it possible to have a layer of security in front of your publicly exposed apps.
It supports Google, OIDC, and generic OAuth2.
Please read the [GitHub README of the original project](https://github.com/thomseddon/traefik-forward-auth) for your
authentication options.
Note: generally, phone apps do not support redirection during their authentication flow, so this middleware is more suitable for
protecting portals that you would access through a browser.
## A standard setup (auth host mode)
This method will add a middleware to the traefik instance with Google authentication which then you can apply on any apps
with either subdomain or path prefix Ingress rules.
The example domain will be `https://example.com` which should be substituted to your externally accessible domain name.
### Setting up traefik-forward-auth
Assuming you have set up the `Client ID for Web application` in the developer console and set the `Authorized redirect URIs` to
`https://auth.example.com/_oauth`, start installing traefik-forward-auth:
- Set a `Secret` (it's mandatory, but can be any string of your choosing).
- Set `Auth host` to `auth.example.com`.
- Add your email to `Whitelist`.
![auth-options](img/auth-options.png)
- Add your root domain to `Cookie options`.
![cookie-options](img/cookie-options.png)
- Set the `Client ID` and `Client Secret` to the ones presented on the developer console. `Prompt` can be left empty.
![google-options](img/google-options.png)
- Set up Ingress for the app as `auth.example.com` as usual.
- That's it, since this is a stateless program, it doesn't need any special permissions or storage.
### Creating the middleware on traefik
The traefik instance has to be made aware of the forward authentication. Edit your traefik installation:
- Go to `websecure` entrypoint and enable `Accept forwarded headers`.
- Add your default gateway (internal router IP) to `Trusted IPs` if using hairpin NAT. If you are using DNS override for your
domain name, you can probably skip this step.
![traefik-forwarded-headers](img/traefik-forwarded-headers.png)
- Go to `Middlewares` and add a `forward-auth` one.
- Name it (use a short one if planning to add to many apps).
- Add `https://auth.example.com/_oauth` as address. This will redirect initial requests to the traefik-forward-auth container.
- Check `trustForwardHeader`.
- Add `X-Forwarded-User` to `authResponseHeaders`.
![traefik-middleware](img/traefik-middleware.png)
### Applying the middleware on apps
To actually use the forward authentication, you have to add it to either a chain or by itself to existing app Ingresses.
![ingress-middleware](img/ingress-middleware.png)
### Per-app whitelist
In case you need per-app whitelists, you have two options: set up multiple instances of traefik-forward-auth (cumbersome) or
use custom rules. Consult the readme of the original project how to set them.
### Testing
Opening your URL should result in being redirected to Google authentication. Subsequent checks if the auth is working can be
made by opening an incognito window.

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,409 @@
# Include{groups}
questions:
# Include{global}
# Include{controller}
# Include{replicas}
# Include{replica1}
# Include{controllerExpertExtraArgs}
# Include{containerConfig}
# Include{controllerExpert}
- variable: tfaAppOptions
group: App Configuration
label: Application Options
schema:
type: dict
attrs:
- variable: secret
label: Secret
description: Mandatory, can be any string.
schema:
type: string
required: true
private: true
- variable: port
label: Port
schema:
type: int
default: 4181
- variable: logLevel
label: Log level
schema:
type: string
default: warn
enum:
- value: trace
description: Trace (most detailed)
- value: debug
description: Debug
- value: info
description: Information
- value: warn
description: Warning
- value: error
description: Error
- value: fatal
description: Fatal
- value: panic
description: Panic (least detailed)
- variable: logFormat
label: Log format
schema:
type: string
default: text
enum:
- value: text
description: Text
- value: json
description: JSON
- value: pretty
description: Pretty
- variable: tfaAuthOptions
group: App Configuration
label: Auth Options
schema:
type: dict
attrs:
- variable: authHost
label: Auth host
description: Single host to use when returning from 3rd party auth.
schema:
type: string
- variable: urlPath
label: Callback URL Path
schema:
type: string
default: "/_oauth"
- variable: defaultAction
label: Default action
schema:
type: string
default: auth
enum:
- value: auth
description: Authenticate
- value: allow
description: Allow (do not require authentication)
- variable: defaultProvider
label: Default provider
schema:
type: string
default: google
enum:
- value: google
description: Google Provider
- value: oidc
description: OIDC Provider
- value: generic-oauth
description: Generic OAuth2 Provider
- variable: domain
label: Domains
description: Only allow given email domains.
schema:
type: list
default: []
items:
- variable: allowedDomain
label: Host
schema:
type: string
required: true
- variable: whitelist
label: Whitelist
description: Only allow given email addresses.
schema:
type: list
default: []
items:
- variable: allowedEmail
label: Host
schema:
type: string
required: true
- variable: rules
label: Rules
description: Additional rules in rule.<name>.<param>=<value> format.
schema:
type: list
default: []
items:
- variable: rule
label: Rule
schema:
type: string
required: true
- variable: logoutRedirect
label: Logout redirect
description: URL to redirect to following logout.
schema:
type: string
- variable: tfaCookieOptions
group: App Configuration
label: Cookie Options
schema:
type: dict
attrs:
- variable: cookieDomain
label: Cookie domain hosts
schema:
type: list
default: []
items:
- variable: cookieDomainHost
label: Host
schema:
type: string
required: true
- variable: cookieName
label: Cookie name
schema:
type: string
default: "_forward_auth"
- variable: csrfCookieName
label: CSRF cookie name
schema:
type: string
default: "_forward_auth_csrf"
- variable: lifetime
label: Lifetime
description: Lifetime in seconds.
schema:
type: int
default: 43200
- variable: insecureCookie
label: Use insecure cookies
schema:
type: boolean
default: false
- variable: tfaGoogleOptions
group: App Configuration
label: Google Provider
schema:
type: dict
attrs:
- variable: clientId
label: Client ID
schema:
type: string
private: true
- variable: clientSecret
label: Client Secret
schema:
type: string
private: true
- variable: prompt
label: Prompt
description: Space separated list of OpenID prompt options.
schema:
type: string
- variable: tfaOidcOptions
group: App Configuration
label: OIDC Provider
schema:
type: dict
attrs:
- variable: issuerUrl
label: Issuer URL
schema:
type: string
- variable: clientId
label: Client ID
schema:
type: string
private: true
- variable: clientSecret
label: Client Secret
schema:
type: string
private: true
- variable: resource
label: Resource
description: Optional resource indicator.
schema:
type: string
- variable: tfaOauthOptions
group: App Configuration
label: Generic OAuth2 Provider
schema:
type: dict
attrs:
- variable: authUrl
label: Auth/Login URL
schema:
type: string
- variable: tokenUrl
label: Token URL
schema:
type: string
- variable: userUrl
label: User URL
description: URL used to retrieve user info.
schema:
type: string
- variable: clientId
label: Client ID
schema:
type: string
private: true
- variable: clientSecret
label: Client Secret
schema:
type: string
private: true
- variable: scopes
label: Scopes
schema:
type: string
default: profile, email
- variable: tokenStyle
label: Token style
description: How token is presented when querying the User URL
schema:
type: string
default: header
enum:
- value: header
description: Header
- value: query
description: Query
- variable: resource
label: Resource
description: Optional resource indicator.
schema:
type: string
# Include{fixedEnv}
# Include{containerConfig}
# Include{serviceRoot}
- variable: main
label: "Main Service"
description: "The Primary service on which the healthcheck runs, often the webUI"
schema:
type: dict
attrs:
# Include{serviceSelector}
- variable: main
label: "Main Service Port Configuration"
schema:
type: dict
attrs:
- variable: port
label: "Port"
description: "This port exposes the container port on the service"
schema:
type: int
default: 4181
required: true
- variable: advanced
label: "Show Advanced settings"
schema:
type: boolean
default: false
show_subquestions_if: true
subquestions:
- variable: enabled
label: "Enable the port"
schema:
type: boolean
default: true
- variable: protocol
label: "Port Type"
schema:
type: string
default: "HTTP"
enum:
- value: HTTP
description: "HTTP"
- value: "HTTPS"
description: "HTTPS"
- value: TCP
description: "TCP"
- value: "UDP"
description: "UDP"
- variable: nodePort
label: "Node Port (Optional)"
description: "This port gets exposed to the node. Only considered when service type is NodePort, Simple or LoadBalancer"
schema:
type: int
min: 9000
max: 65535
- variable: targetPort
label: "Target Port"
description: "The internal(!) port on the container the Application runs on"
schema:
type: int
default: 4181
# Include{serviceExpertRoot}
default: false
# Include{serviceExpert}
# Include{serviceList}
# Include{ingressRoot}
- variable: main
label: "Main Ingress"
schema:
type: dict
attrs:
# Include{ingressDefault}
# Include{ingressTLS}
# Include{ingressTraefik}
# Include{ingressExpert}
# Include{ingressList}
- variable: advancedSecurity
label: "Show Advanced Security Settings"
group: "Security and Permissions"
schema:
type: boolean
default: false
show_subquestions_if: true
subquestions:
- variable: securityContext
label: "Security Context"
schema:
type: dict
attrs:
- variable: privileged
label: "Privileged mode"
schema:
type: boolean
default: false
- variable: readOnlyRootFilesystem
label: "ReadOnly Root Filesystem"
schema:
type: boolean
default: true
- variable: allowPrivilegeEscalation
label: "Allow Privilege Escalation"
schema:
type: boolean
default: false
- variable: runAsNonRoot
label: "runAsNonRoot"
schema:
type: boolean
default: true
# Include{securityContextAdvanced}
# Include{podSecurityContextRoot}
- variable: runAsUser
label: "runAsUser"
description: "The UserID of the user running the application"
schema:
type: int
default: 568
- variable: runAsGroup
label: "runAsGroup"
description: "The groupID this App of the user running the application"
schema:
type: int
default: 568
- variable: fsGroup
label: "fsGroup"
description: "The group that should own ALL storage."
schema:
type: int
default: 568
# Include{podSecurityContextAdvanced}
# Include{resources}
# Include{advanced}
# Include{addons}
# Include{codeserver}
# Include{promtail}
# Include{netshoot}
# Include{vpn}

View File

@ -0,0 +1,34 @@
{{- define "tfa.args" -}}
args:
- --log-level={{ .Values.tfaAppOptions.logLevel }}
- --log-format={{ .Values.tfaAppOptions.logFormat }}
{{- if .Values.tfaAuthOptions.authHost }}
- --auth-host={{ .Values.tfaAuthOptions.authHost }}
{{- end }}
{{- range .Values.tfaCookieOptions.cookieDomain }}
- --cookie-domain={{ . }}
{{- end }}
{{- if .Values.tfaCookieOptions.insecureCookie }}
- --insecure-cookie
{{- end }}
- --cookie-name={{ .Values.tfaCookieOptions.cookieName }}
- --csrf-cookie-name={{ .Values.tfaCookieOptions.csrfCookieName }}
- --default-action={{ .Values.tfaAuthOptions.defaultAction }}
- --default-provider={{ .Values.tfaAuthOptions.defaultProvider }}
{{- range .Values.tfaAuthOptions.domain }}
- --domain={{ . }}
{{- end }}
- --lifetime={{ .Values.tfaCookieOptions.lifetime }}
{{- if .Values.tfaAuthOptions.logoutRedirect }}
- --logout-redirect={{ .Values.tfaAuthOptions.logoutRedirect }}
{{- end }}
- --url-path={{ .Values.tfaAuthOptions.urlPath }}
- --secret={{ .Values.tfaAppOptions.secret }}
{{- range .Values.tfaAuthOptions.whitelist }}
- --whitelist={{ . }}
{{- end }}
- --port={{ .Values.tfaAppOptions.port }}
{{- range .Values.tfaAuthOptions.rules }}
- --{{ . }}
{{- end }}
{{- end -}}

View File

@ -0,0 +1,57 @@
{{/* Define the secret */}}
{{- define "tfa.secret" -}}
{{- $googleSecretName := printf "%s-google-secret" (include "tc.common.names.fullname" .) }}
{{- $oidcSecretName := printf "%s-oidc-secret" (include "tc.common.names.fullname" .) }}
{{- $oauthSecretName := printf "%s-oauth2-secret" (include "tc.common.names.fullname" .) }}
---
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: {{ $googleSecretName }}
labels:
{{- include "tc.common.labels" . | nindent 4 }}
data:
PROVIDERS_GOOGLE_CLIENT_ID: {{ .Values.tfaGoogleOptions.clientId | trimAll "\"" | b64enc }}
PROVIDERS_GOOGLE_CLIENT_SECRET: {{ .Values.tfaGoogleOptions.clientSecret | trimAll "\"" | b64enc }}
PROVIDERS_GOOGLE_PROMPT: {{ .Values.tfaGoogleOptions.prompt | trimAll "\"" | b64enc }}
---
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: {{ $oidcSecretName }}
labels:
{{- include "tc.common.labels" . | nindent 4 }}
data:
PROVIDERS_OIDC_ISSUER_URL: {{ .Values.tfaOidcOptions.issuerUrl | trimAll "\"" | b64enc }}
PROVIDERS_OIDC_CLIENT_ID: {{ .Values.tfaOidcOptions.clientId | trimAll "\"" | b64enc }}
PROVIDERS_OIDC_CLIENT_SECRET: {{ .Values.tfaOidcOptions.clientSecret | trimAll "\"" | b64enc }}
PROVIDERS_OIDC_RESOURCE: {{ .Values.tfaOidcOptions.resource | trimAll "\"" | b64enc }}
---
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: {{ $oauthSecretName }}
labels:
{{- include "tc.common.labels" . | nindent 4 }}
data:
PROVIDERS_GENERIC_OAUTH_AUTH_URL: {{ .Values.tfaOauthOptions.authUrl | trimAll "\"" | b64enc }}
PROVIDERS_GENERIC_OAUTH_TOKEN_URL: {{ .Values.tfaOauthOptions.tokenUrl | trimAll "\"" | b64enc }}
PROVIDERS_GENERIC_OAUTH_USER_URL: {{ .Values.tfaOauthOptions.userUrl | trimAll "\"" | b64enc }}
PROVIDERS_GENERIC_OAUTH_CLIENT_ID: {{ .Values.tfaOauthOptions.clientId | trimAll "\"" | b64enc }}
PROVIDERS_GENERIC_OAUTH_CLIENT_SECRET: {{ .Values.tfaOauthOptions.clientSecret | trimAll "\"" | b64enc }}
PROVIDERS_GENERIC_OAUTH_TOKEN_STYLE: {{ .Values.tfaOauthOptions.tokenStyle | trimAll "\"" | b64enc }}
PROVIDERS_GENERIC_OAUTH_RESOURCE: {{ .Values.tfaOauthOptions.resource | trimAll "\"" | b64enc }}
---
{{- end }}

View File

@ -0,0 +1,13 @@
{{/* Make sure all variables are set properly */}}
{{- include "tc.common.loader.init" . }}
{{/* Render secret */}}
{{- include "tfa.secret" . }}
{{- $tplArgs := (include "tfa.args" . | fromYaml) }}
{{- $_ := set .Values "tplArgs" $tplArgs -}}
{{- $args := concat .Values.args .Values.tplArgs.args }}
{{- $_ := set .Values "args" $args -}}
{{/* Render the templates */}}
{{ include "tc.common.loader.apply" . }}

View File

@ -0,0 +1,62 @@
image:
repository: tccr.io/truecharts/traefik-forward-auth
pullPolicy: IfNotPresent
tag: latest@sha256:edd7eb812cb38e59d32b5a00398b57a78506db2390cbe295f5df590a38a5d44e
envFrom:
- secretRef:
name: '{{ include "tc.common.names.fullname" . }}-google-secret'
- secretRef:
name: '{{ include "tc.common.names.fullname" . }}-oidc-secret'
- secretRef:
name: '{{ include "tc.common.names.fullname" . }}-oauth2-secret'
service:
main:
ports:
main:
targetPort: 4181
port: 4181
tfaAppOptions:
secret: something-random
port: 4181
logLevel: warn
logFormat: text
tfaAuthOptions:
authHost:
urlPath: /_oauth
defaultAction: auth
defaultProvider: google
domain: []
whitelist: []
rules: []
tfaCookieOptions:
cookieDomain: []
cookieName: _forward_auth
csrfCookieName: _forward_auth_csrf
lifetime: 43200
insecureCookie: false
tfaGoogleOptions:
clientId: "changeme"
clientSecret: "changeme"
prompt: "changeme"
tfaOidcOptions:
issuerUrl: "changeme"
clientId: "changeme"
clientSecret: "changeme"
resource: "changeme"
tfaOauthOptions:
authUrl: "changeme"
tokenUrl: "changeme"
userUrl: "changeme"
clientId: "changeme"
clientSecret: "changeme"
scopes: "changeme"
tokenStyle: header
resource: "changeme"