Add 2FAuth to community train (#1571)

* initial commit

* add some templates

* fix config

* add metadata and ci values

* expose more config

* add values

* fix lint

* add ui

* add validation

* whops

* update description

* formatting

* rename app
This commit is contained in:
Stavros Kois
2023-09-28 18:37:44 +03:00
committed by GitHub
parent 6116eb71e3
commit f780a02ed7
22 changed files with 670 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
dependencies:
- name: common
repository: file://../../../common
version: 1.1.1
digest: sha256:a7dbe3e4d42dbcd4325776e5e01a1d630c7f185f79e7ebf22b1b9cc80f56eed7
generated: "2023-09-25T15:07:14.962982648+03:00"

View File

@@ -0,0 +1,27 @@
name: twofactor-auth
description: 2FAuth is a web based self-hosted alternative to One Time Passcode (OTP) generators like Google Authenticator, designed for both mobile and desktop.
annotations:
title: 2FAuth
type: application
version: 1.0.0
apiVersion: v2
appVersion: 4.2.2
kubeVersion: '>=1.16.0-0'
maintainers:
- name: truenas
url: https://www.truenas.com/
email: dev@ixsystems.com
dependencies:
- name: common
repository: file://../../../common
version: 1.1.1
home: https://docs.2fauth.app/
icon: https://docs.2fauth.app/static/2fauth_light.png
sources:
- https://github.com/Bubka/2FAuth
- https://github.com/truenas/charts/tree/master/library/ix-dev/community/2fauth
- https://hub.docker.com/r/2fauth/2fauth/
keywords:
- security
- 2fa
- otp

View File

@@ -0,0 +1,8 @@
# 2FAuth
[2FAuth](https://docs.2fauth.app/) is a web based self-hosted alternative to One Time Passcode (OTP) generators like Google Authenticator, designed for both mobile and desktop.
> When application is installed, a container will be launched with **root** privileges.
> This is required in order to apply the correct permissions to the `2FAuth` directories.
> Afterward, the `2FAuth` container will run as a **non**-root user (`1000`).
> All mounted storage(s) will be `chown`ed only if the parent directory does not match the configured user.

View File

@@ -0,0 +1,8 @@
# 2FAuth
[2FAuth](https://docs.2fauth.app/) is a web based self-hosted alternative to One Time Passcode (OTP) generators like Google Authenticator, designed for both mobile and desktop.
> When application is installed, a container will be launched with **root** privileges.
> This is required in order to apply the correct permissions to the `2FAuth` directories.
> Afterward, the `2FAuth` container will run as a **non**-root user (`1000`).
> All mounted storage(s) will be `chown`ed only if the parent directory does not match the configured user.

View File

@@ -0,0 +1,7 @@
twofauthNetwork:
webPort: 31000
twofauthStorage:
config:
type: hostPath
hostPath: /mnt/{{ .Release.Namespace }}/config

View File

@@ -0,0 +1,28 @@
twofauthConfig:
authenticationGuard: reverse-proxy-guard
authProxyHeaderUser: X-Forwarded-User
authProxyHeaderEmail: X-Forwarded-Email
webauthnUserVerification: required
trustedProxies:
- "*"
additionalEnvs:
- name: LOG_LEVEL
value: notice
- name: IS_DEMO_APP
value: "true"
twofauthNetwork:
webPort: 31000
twofauthStorage:
config:
type: hostPath
hostPath: /mnt/{{ .Release.Namespace }}/config
additionalStorages:
- type: hostPath
hostPath: /mnt/{{ .Release.Namespace }}/data1
mountPath: /data1
- type: hostPath
hostPath: /mnt/{{ .Release.Namespace }}/data2
mountPath: /data2

View File

@@ -0,0 +1,8 @@
twofauthNetwork:
webPort: 30000
hostNetwork: true
twofauthStorage:
config:
type: hostPath
hostPath: /mnt/{{ .Release.Namespace }}/config

View File

@@ -0,0 +1,8 @@
icon_url: https://docs.2fauth.app/static/2fauth_light.png
categories:
- security
screenshots:
- https://docs.2fauth.app/static/2fauth_screenshots_dark.png
tags:
- 2fa
- otp

View File

@@ -0,0 +1,8 @@
runAsContext:
- userName: twofauth
groupName: twofauthreadarr
gid: 1000
uid: 1000
description: 2FAuth runs as a non-root user.
capabilities: []
hostMounts: []

View File

@@ -0,0 +1,298 @@
groups:
- name: 2FAuth Configuration
description: Configure 2FAuth
- name: Network Configuration
description: Configure Network for 2FAuth
- name: Storage Configuration
description: Configure Storage for 2FAuth
- name: Resources Configuration
description: Configure Resources for 2FAuth
portals:
web_portal:
protocols:
- "$kubernetes-resource_configmap_portal_protocol"
host:
- "$kubernetes-resource_configmap_portal_host"
ports:
- "$kubernetes-resource_configmap_portal_port"
path: "$kubernetes-resource_configmap_portal_path"
questions:
- variable: twofauthConfig
label: ""
group: 2FAuth Configuration
schema:
type: dict
attrs:
- variable: appName
label: App Name
description: The name of the 2FAuth.
schema:
type: string
default: "2FAuth"
required: true
- variable: appUrl
label: App URL
description: |
The URL that 2FAuth will be accessible from.</br>
Example: </br>
http://server.ip:30081</br>
https://2fauth.example.com
schema:
type: uri
default: ""
required: true
- variable: siteOwnerEmail
label: Site Owner Email
description: The email address of the site owner.
schema:
type: string
default: ""
required: true
- variable: authenticationGuard
label: Authentication Guard
description: |
When using 'reverse-proxy-guard' 2FAuth only look for the dedicated headers and skip all
other built-in authentication checks. That means your proxy is fully responsible of the
authentication process, 2FAuth will trust him as long as headers are presents.
schema:
type: string
default: "web-guard"
required: true
enum:
- value: "web-guard"
description: Web Guard
- value: "reverse-proxy-guard"
description: Reverse Proxy Guard
- variable: authProxyHeaderUser
label: Authentication Proxy Header User
description: |
Name of the HTTP headers sent by the reverse proxy that identifies the authenticated
user at proxy level. Check your proxy documentation to find out how these headers are named.
schema:
type: string
default: ""
show_if: [["authenticationGuard", "=", "reverse-proxy-guard"]]
required: true
- variable: authProxyHeaderEmail
label: Authentication Proxy Header Email
description: |
Name of the HTTP headers sent by the reverse proxy that identifies the authenticated
user at proxy level. Check your proxy documentation to find out how these headers are named.
schema:
type: string
default: ""
show_if: [["authenticationGuard", "=", "reverse-proxy-guard"]]
required: true
- variable: webauthnUserVerification
label: WebAuthn User Verification
description: |
Most authenticators and smartphones will ask the user to actively verify
themselves for log in. For example, through a touch plus pin code,
password entry, or biometric recognition (e.g., presenting a fingerprint).
The intent is to distinguish one user from any other.
schema:
type: string
default: "preferred"
required: true
enum:
- value: "preferred"
description: Preferred
- value: "required"
description: Required
- value: "discouraged"
description: Discouraged
- variable: trustedProxies
label: Trusted Proxies
description: The list of proxies IP to trust
schema:
type: list
default: []
items:
- variable: trustedProxy
label: Trusted Proxy
schema:
type: string
required: true
- variable: additionalEnvs
label: Additional Environment Variables
description: Configure additional environment variables for 2FAuth.
schema:
type: list
default: []
items:
- variable: env
label: Environment Variable
schema:
type: dict
attrs:
- variable: name
label: Name
schema:
type: string
required: true
- variable: value
label: Value
schema:
type: string
required: true
- variable: twofauthNetwork
label: ""
group: Network Configuration
schema:
type: dict
attrs:
- variable: webPort
label: Web Port
description: The port for the 2FAuth Web UI.
schema:
type: int
default: 30081
min: 9000
max: 65535
required: true
- variable: hostNetwork
label: Host Network
description: |
Bind to the host network. It's recommended to keep this disabled.</br>
schema:
type: boolean
default: false
- variable: twofauthStorage
label: ""
group: Storage Configuration
schema:
type: dict
attrs:
- variable: config
label: 2FAuth Config Storage
description: The path to store 2FAuth Configuration.
schema:
type: dict
attrs:
- variable: type
label: Type
description: |
ixVolume: Is dataset created automatically by the system.</br>
Host Path: Is a path that already exists on the system.
schema:
type: string
required: true
default: "ixVolume"
enum:
- value: "hostPath"
description: Host Path (Path that already exists on the system)
- value: "ixVolume"
description: ixVolume (Dataset created automatically by the system)
- variable: datasetName
label: Dataset Name
schema:
type: string
show_if: [["type", "=", "ixVolume"]]
required: true
hidden: true
immutable: true
default: "config"
$ref:
- "normalize/ixVolume"
- variable: hostPath
label: Host Path
schema:
type: hostpath
show_if: [["type", "=", "hostPath"]]
immutable: true
required: true
- variable: additionalStorages
label: Additional Storage
description: Additional storage for 2FAuth.
schema:
type: list
default: []
items:
- variable: storageEntry
label: Storage Entry
schema:
type: dict
attrs:
- variable: type
label: Type
description: |
ixVolume: Is dataset created automatically by the system.</br>
Host Path: Is a path that already exists on the system.
schema:
type: string
required: true
default: "ixVolume"
enum:
- value: "hostPath"
description: Host Path (Path that already exists on the system)
- value: "ixVolume"
description: ixVolume (Dataset created automatically by the system)
- variable: mountPath
label: Mount Path
description: The path inside the container to mount the storage.
schema:
type: path
required: true
- variable: hostPath
label: Host Path
description: The host path to use for storage.
schema:
type: hostpath
show_if: [["type", "=", "hostPath"]]
required: true
- variable: datasetName
label: Dataset Name
description: The name of the dataset to use for storage.
schema:
type: string
show_if: [["type", "=", "ixVolume"]]
required: true
immutable: true
default: "storage_entry"
$ref:
- "normalize/ixVolume"
- variable: resources
group: Resources Configuration
label: ""
schema:
type: dict
attrs:
- variable: limits
label: Limits
schema:
type: dict
attrs:
- variable: cpu
label: CPU
description: CPU limit for 2FAuth.
schema:
type: string
max_length: 6
valid_chars: '^(0\.[1-9]|[1-9][0-9]*)(\.[0-9]|m?)$'
valid_chars_error: |
Valid CPU limit formats are</br>
- Plain Integer - eg. 1</br>
- Float - eg. 0.5</br>
- Milicpu - eg. 500m
default: "4000m"
required: true
- variable: memory
label: Memory
description: Memory limit for 2FAuth.
schema:
type: string
max_length: 12
valid_chars: '^[1-9][0-9]*([EPTGMK]i?|e[0-9]+)?$'
valid_chars_error: |
Valid Memory limit formats are</br>
- Suffixed with E/P/T/G/M/K - eg. 1G</br>
- Suffixed with Ei/Pi/Ti/Gi/Mi/Ki - eg. 1Gi</br>
- Plain Integer in bytes - eg. 1024</br>
- Exponent - eg. 134e6
default: "8Gi"
required: true

View File

@@ -0,0 +1 @@
{{ include "ix.v1.common.lib.chart.notes" $ }}

View File

@@ -0,0 +1,52 @@
{{- define "twofauth.workload" -}}
workload:
twofauth:
enabled: true
primary: true
type: Deployment
podSpec:
hostNetwork: {{ .Values.twofauthNetwork.hostNetwork }}
containers:
twofauth:
enabled: true
primary: true
imageSelector: image
securityContext:
runAsUser: 1000
runAsGroup: 1000
readOnlyRootFilesystem: false
envFrom:
- secretRef:
name: twofauth-creds
- configMapRef:
name: twofauth-config
{{ with .Values.twofauthConfig.additionalEnvs }}
envList:
{{ range $env := . }}
- name: {{ $env.name }}
value: {{ $env.value }}
{{ end }}
{{ end }}
probes:
liveness:
enabled: true
type: http
port: 8000
path: /infos
readiness:
enabled: true
type: http
port: 8000
path: /infos
startup:
enabled: true
type: http
port: 8000
path: /infos
initContainers:
{{- include "ix.v1.common.app.permissions" (dict "containerName" "01-permissions"
"UID" 1000
"GID" 1000
"mode" "check"
"type" "init") | nindent 8 }}
{{- end -}}

View File

@@ -0,0 +1,37 @@
{{- define "twofauth.configuration" -}}
{{- $fullname := (include "ix.v1.common.lib.chart.names.fullname" $) -}}
{{- $appKey := (randAlphaNum 32) -}}
{{- with (lookup "v1" "Secret" .Release.Namespace (printf "%s-twofauth-creds" $fullname)) -}}
{{- $appKey = ((index .data "APP_KEY") | b64dec) -}}
{{- end }}
secret:
twofauth-creds:
enabled: true
data:
APP_KEY: {{ $appKey }}
configmap:
twofauth-config:
enabled: true
data:
# When this is set to production, it initialize automatically
# Because it waits for user input in the console.
APP_ENV: local
# It is symlinked to /2fauth/database.sqlite
DB_DATABASE: /srv/database/database.sqlite
APP_NAME: {{ .Values.twofauthConfig.appName }}
APP_URL: {{ .Values.twofauthConfig.appUrl }}
SITE_OWNER: {{ .Values.twofauthConfig.siteOwnerEmail }}
AUTHENTICATION_GUARD: {{ .Values.twofauthConfig.authenticationGuard }}
{{- if eq .Values.twofauthConfig.authenticationGuard "reverse-proxy-guard" }}
AUTH_PROXY_HEADER_FOR_USER: {{ .Values.twofauthConfig.authProxyHeaderUser }}
AUTH_PROXY_HEADER_FOR_EMAIL: {{ .Values.twofauthConfig.authProxyHeaderEmail }}
{{- end }}
WEBAUTHN_USER_VERIFICATION: {{ .Values.twofauthConfig.webauthnUserVerification }}
{{- with .Values.twofauthConfig.trustedProxies }}
TRUSTED_PROXIES: {{ join "," . | quote }}
{{- end -}}
{{- end -}}

View File

@@ -0,0 +1,34 @@
{{- define "twofauth.persistence" -}}
persistence:
config:
enabled: true
type: {{ .Values.twofauthStorage.config.type }}
datasetName: {{ .Values.twofauthStorage.config.datasetName | default "" }}
hostPath: {{ .Values.twofauthStorage.config.hostPath | default "" }}
targetSelector:
twofauth:
twofauth:
mountPath: /2fauth
01-permissions:
mountPath: /mnt/directories/2fauth
tmp:
enabled: true
type: emptyDir
targetSelector:
twofauth:
twofauth:
mountPath: /tmp
{{- range $idx, $storage := .Values.twofauthStorage.additionalStorages }}
{{ printf "twofauth-%v" (int $idx) }}:
enabled: true
type: {{ $storage.type }}
datasetName: {{ $storage.datasetName | default "" }}
hostPath: {{ $storage.hostPath | default "" }}
targetSelector:
twofauth:
twofauth:
mountPath: {{ $storage.mountPath }}
01-permissions:
mountPath: /mnt/directories{{ $storage.mountPath }}
{{- end }}
{{- end -}}

View File

@@ -0,0 +1,35 @@
{{- define "twofauth.portal" -}}
{{- $host := "$node_ip" -}}
{{- $port := "" -}}
{{- $protocol := "http" -}}
{{- if hasPrefix "https://" .Values.twofauthConfig.appUrl -}}
{{- $protocol = "https" -}}
{{- end -}}
{{- with .Values.twofauthConfig.appUrl -}} {{/* Trim protocol and trailing slash */}}
{{- $host = . | trimPrefix "https://" | trimPrefix "http://" | trimSuffix "/" -}}
{{- if contains ":" $host -}}
{{- $port = (split ":" $host)._1 -}}
{{- $host = (split ":" $host)._0 -}}
{{- end -}}
{{- if not $port -}}
{{- if eq $protocol "https" -}}
{{- $port = "443" -}}
{{- else -}}
{{- $port = "80" -}}
{{- end -}}
{{- end -}}
{{- end }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: portal
data:
path: "/"
port: {{ $port | quote }}
protocol: {{ $protocol }}
host: {{ $host }}
{{- end -}}

View File

@@ -0,0 +1,16 @@
{{- define "twofauth.service" -}}
service:
twofauth:
enabled: true
primary: true
type: NodePort
targetSelector: twofauth
ports:
webui:
enabled: true
primary: true
port: {{ .Values.twofauthNetwork.webPort }}
nodePort: {{ .Values.twofauthNetwork.webPort }}
targetPort: 8000
targetSelector: twofauth
{{- end -}}

View File

@@ -0,0 +1,13 @@
{{- define "twofauth.validation" -}}
{{- if eq .Values.twofauthConfig.authenticationGuard "reverse-proxy-guard" -}}
{{- if not .Values.twofauthConfig.authProxyHeaderUser -}}
{{- fail "[Auth Proxy Header User] is required when using reverse-proxy-guard" -}}
{{- end -}}
{{- if not .Values.twofauthConfig.authProxyHeaderEmail -}}
{{- fail "[Auth Proxy Header Email] is required when using reverse-proxy-guard" -}}
{{- end -}}
{{- end }}
{{- end -}}

View File

@@ -0,0 +1,14 @@
{{- include "ix.v1.common.loader.init" . -}}
{{- include "twofauth.validation" $ -}}
{{/* Merge the templates with Values */}}
{{- $_ := mustMergeOverwrite .Values (include "twofauth.configuration" $ | fromYaml) -}}
{{- $_ := mustMergeOverwrite .Values (include "twofauth.persistence" $ | fromYaml) -}}
{{- $_ := mustMergeOverwrite .Values (include "twofauth.service" $ | fromYaml) -}}
{{- $_ := mustMergeOverwrite .Values (include "twofauth.workload" $ | fromYaml) -}}
{{/* Create the configmap for portal manually*/}}
{{- include "twofauth.portal" $ -}}
{{- include "ix.v1.common.loader.apply" . -}}

View File

@@ -0,0 +1 @@
{"filename": "values.yaml", "keys": ["image"]}

View File

@@ -0,0 +1,31 @@
#!/usr/bin/python3
import json
import re
import sys
from catalog_update.upgrade_strategy import semantic_versioning
RE_STABLE_VERSION = re.compile(r'\d+\.\d+\.\d+')
def newer_mapping(image_tags):
key = list(image_tags.keys())[0]
tags = {t: t for t in image_tags[key] if RE_STABLE_VERSION.fullmatch(t)}
version = semantic_versioning(list(tags))
if not version:
return {}
return {
'tags': {key: tags[version]},
'app_version': version,
}
if __name__ == '__main__':
try:
versions_json = json.loads(sys.stdin.read())
except ValueError:
raise ValueError('Invalid json specified')
print(json.dumps(newer_mapping(versions_json)))

View File

@@ -0,0 +1,30 @@
image:
repository: 2fauth/2fauth
pullPolicy: IfNotPresent
tag: 4.2.2
resources:
limits:
cpu: 4000m
memory: 8Gi
twofauthConfig:
appName: 2FAuth
appUrl: http://localhost:30081
siteOwnerEmail: 'admin@example.com'
authenticationGuard: web-guard
authProxyHeaderUser: ''
authProxyHeaderEmail: ''
webauthnUserVerification: preferred
trustedProxies: []
additionalEnvs: []
twofauthNetwork:
webPort: 30081
hostNetwork: false
twofauthStorage:
config:
type: ixVolume
datasetName: config
additionalStorages: []