Merge branch 'dev-authelia' into 'main'

Rolle | Authelia

See merge request misc/ansible-base!1
This commit is contained in:
Christian Fraß 2023-12-17 22:49:06 +00:00
commit 54f749c4de
15 changed files with 829 additions and 0 deletions

View file

@ -0,0 +1,3 @@
{
"var_authelia_and_nginx_domain": "authelia.example.org"
}

View file

@ -0,0 +1,3 @@
## Verweise
- [Authelia-Dokumentation](https://www.authelia.com/integration/proxies/nginx/)

View file

@ -0,0 +1,35 @@
[
{
"name": "deactivate default site",
"become": true,
"ansible.builtin.file": {
"state": "absent",
"dest": "/etc/nginx/sites-enabled/default"
}
},
{
"name": "emplace configuration | data",
"become": true,
"ansible.builtin.template": {
"src": "conf.j2",
"dest": "/etc/nginx/sites-available/{{var_authelia_and_nginx_domain}}"
}
},
{
"name": "emplace configuration | link",
"become": true,
"ansible.builtin.file": {
"state": "link",
"src": "/etc/nginx/sites-available/{{var_authelia_and_nginx_domain}}",
"dest": "/etc/nginx/sites-enabled/{{var_authelia_and_nginx_domain}}"
}
},
{
"name": "restart nginx",
"become": true,
"ansible.builtin.systemd_service": {
"state": "restarted",
"name": "nginx"
}
}
]

View file

@ -0,0 +1,62 @@
server {
server_name {{var_authelia_and_nginx_domain}};
listen [::]:80;
listen 80;
return 301 https://$server_name$request_uri;
}
server {
server_name {{var_authelia_and_nginx_domain}};
listen [::]:443 ssl http2;
listen 443 ssl http2;
ssl_certificate /etc/ssl/fullchains/{{var_authelia_and_nginx_domain}}.pem;
ssl_certificate_key /etc/ssl/private/{{var_authelia_and_nginx_domain}}.pem;
location / {
## Headers
proxy_set_header Host $host;
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Uri $request_uri;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Connection "";
## Basic Proxy Configuration
client_body_buffer_size 128k;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; ## Timeout if the real server is dead.
proxy_redirect http:// $scheme://;
proxy_http_version 1.1;
proxy_cache_bypass $cookie_session;
proxy_no_cache $cookie_session;
proxy_buffers 64 256k;
## Trusted Proxies Configuration
## Please read the following documentation before configuring this:
## https://www.authelia.com/integration/proxies/nginx/#trusted-proxies
# set_real_ip_from 10.0.0.0/8;
# set_real_ip_from 172.16.0.0/12;
# set_real_ip_from 192.168.0.0/16;
# set_real_ip_from fc00::/7;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
## Advanced Proxy Configuration
send_timeout 5m;
proxy_read_timeout 360;
proxy_send_timeout 360;
proxy_connect_timeout 360;
proxy_pass http://localhost:9091;
}
location /api/verify {
proxy_pass http://localhost:9091;
}
}

View file

@ -0,0 +1,5 @@
{
"var_authelia_for_synapse_synapse_url_base": "https://matrix.example.org",
"var_authelia_for_synapse_client_id": "synapse",
"var_authelia_for_synapse_client_secret": "REPLACE_ME"
}

View file

@ -0,0 +1,9 @@
## Beschreibung
Um [Synapse](../synapse) gegen [Authelia](../authelia) authentifizieren zu lassen
## Verweise
- [Authelia-Dokumentation | Synapse Integration](https://www.authelia.com/integration/openid-connect/synapse/)
- [Synapse-Dokumentation | OpenID Connect](https://matrix-org.github.io/synapse/latest/openid.html)

View file

@ -0,0 +1,25 @@
[
{
"name": "configuration | emplace",
"become": true,
"ansible.builtin.template": {
"src": "authelia-client-conf.json.j2",
"dest": "/etc/authelia/conf.d/clients/synapse.json"
}
},
{
"name": "configuration | apply",
"become": true,
"ansible.builtin.command": {
"cmd": "/usr/bin/authelia-conf-compose"
}
},
{
"name": "restart service",
"become": true,
"ansible.builtin.systemd_service": {
"state": "restarted",
"name": "authelia"
}
}
]

View file

@ -0,0 +1,16 @@
{
"id": "{{var_authelia_for_synapse_client_id}}",
"description": "Synapse",
"secret": "{{var_authelia_for_synapse_client_secret}}",
"public": false,
"authorization_policy": "one_factor",
"redirect_uris": [
"{{var_authelia_for_synapse_synapse_url_base}}/_synapse/client/oidc/callback"
],
"scopes": [
"openid",
"email",
"profile"
],
"userinfo_signing_algorithm": "none"
}

View file

@ -0,0 +1,21 @@
{
"var_authelia_version": "4.37.5",
"var_authelia_architecture": "amd64",
"var_authelia_listen_address": "0.0.0.0",
"var_authelia_jwt_secret": "REPLACE_ME",
"var_authelia_users_file_path": "/var/authelia/users.yml",
"var_authelia_log_file_path": "/var/authelia/log.jsonl",
"var_authelia_session_domain": "example.org",
"var_authelia_session_secret": "REPLACE_ME",
"var_authelia_storage_path": "/var/authelia/state.db",
"var_authelia_storage_encryption_key": "storage_encryption_key",
"var_authelia_ntp_server": "time.cloudflare.com:123",
"var_authelia_notification_mode": "smtp",
"var_authelia_notification_file_path": "/var/authelia/notifications",
"var_authelia_notification_smtp_host": "smtp.example.org",
"var_authelia_notification_smtp_port": "465",
"var_authelia_notification_smtp_username": "authelia",
"var_authelia_notification_smtp_password": "smtp_password",
"var_authelia_notification_smtp_sender": "Authelia",
"var_authelia_oidc_hmac_secret": "oidc_hmac_secret"
}

View file

@ -0,0 +1,150 @@
#!/usr/bin/env python3
import sys as _sys
import os as _os
import yaml as _yaml
import json as _json
import argparse as _argparse
def file_read(path):
handle = open(path, "r")
content = handle.read()
handle.close()
return content
def file_write(path, content):
directory = _os.path.dirname(path)
if (not _os.path.exists(directory)):
_os.makedirs(directory, exist_ok = True)
else:
pass
handle = open(path, "w")
handle.write(content)
handle.close()
return content
def dict_merge(core, mantle, path = None):
if (path is None):
path = []
result = {}
for source in [core, mantle]:
for (key, value_new, ) in source.items():
path_ = (path + [key])
type_new = type(value_new)
if (not (key in result)):
result[key] = value_new
else:
value_old = result[key]
type_old = type(value_old)
if (value_old is None):
result[key] = value_new
else:
if (not (type_old == type_new)):
raise ValueError(
"type mismatch at path %s: %s vs. %s"
% (
".".join(path),
str(type_old),
str(type_new),
)
)
else:
if (type_old == dict):
result[key] = dict_merge(value_old, value_new, path_)
elif (type_old == list):
result[key] = (value_old + value_new)
else:
result[key] = value_new
return result
def main():
## args
argument_parser = _argparse.ArgumentParser()
argument_parser.add_argument(
"-m",
"--main-file-path",
type = str,
dest = "main_file_path",
default = "/etc/authelia/conf.d/main.json",
metavar = "<main-file-path>",
)
argument_parser.add_argument(
"-c",
"--clients-directory-path",
type = str,
dest = "clients_directory_path",
default = "/etc/authelia/conf.d/clients",
metavar = "<clients-directory-path>",
)
argument_parser.add_argument(
"-f",
"--output-format",
type = str,
choices = ["json", "yaml"],
dest = "output_format",
default = "yaml",
metavar = "<output-format>",
)
argument_parser.add_argument(
"-o",
"--output-path",
type = str,
dest = "output_path",
default = "/etc/authelia/configuration.yml",
metavar = "<output-path>",
)
args = argument_parser.parse_args()
## exec
data = {}
### main
if True:
data_ = _json.loads(file_read(args.main_file_path))
data = dict_merge(data, data_)
### clients
if True:
for name in _os.listdir(args.clients_directory_path):
data__ = _json.loads(file_read(_os.path.join(args.clients_directory_path, name)))
data_ = {
"identity_providers": {
"oidc": {
"clients": [data__]
}
}
}
data = dict_merge(data, data_)
### postprocess
if True:
if (len(data["identity_providers"]["oidc"]["clients"]) <= 0):
data["identity_providers"]["oidc"]["clients"].append(
{
"public": False,
"id": "_dummy",
"description": "not a real client; just here to make Authelia run",
"authorization_policy": "one_factor",
"secret": "",
"scopes": [],
"redirect_uris": [],
"grant_types": [],
"response_types": [],
"response_modes": [],
}
)
else:
pass
## output
if True:
if (args.output_format == "json"):
output_content = _json.dumps(data, indent = "\t")
elif (args.output_format == "yaml"):
output_content = _yaml.dump(data)
else:
raise ValueError("invalid output format")
file_write(args.output_path, output_content)
main()

View file

@ -0,0 +1,188 @@
#!/usr/bin/env python3
import sys as _sys
import os as _os
import subprocess as _subprocess
import argparse as _argparse
import yaml as _yaml
import string as _string
import random as _random
def file_read(path):
handle = open(path, "r")
content = handle.read()
handle.close()
return content
def file_write(path, content):
directory = _os.path.dirname(path)
if (not _os.path.exists(directory)):
_os.makedirs(directory, exist_ok = True)
else:
pass
handle = open(path, "w")
handle.write(content)
handle.close()
return content
def get_password_hash(binary_file_path, conf_file_path, name):
output = _subprocess.check_output([
binary_file_path,
"--config=%s" % conf_file_path,
"hash-password",
name
])
return output.decode("utf-8").split("\n")[0][8:]
def postprocess(binary_file_path, conf_file_path, data):
password = "".join(map(lambda x: _random.choice(_string.ascii_lowercase), range(16)))
if (len(data["users"]) <= 0):
data["users"]["_dummy"] = {
"displayname": "(Dummy)",
"password": get_password_hash(binary_file_path, conf_file_path, password),
"email": "dummy@nowhere.net",
"groups": [],
}
else:
pass
return data
def data_read(user_file_path):
data = _yaml.safe_load(file_read(user_file_path))
data["users"].pop("_dummy", None)
return data
def data_write(binary_file_path, conf_file_path, user_file_path, data):
file_write(user_file_path, _yaml.dump(postprocess(binary_file_path, conf_file_path, data)))
def main():
## args
argument_parser = _argparse.ArgumentParser()
argument_parser.add_argument(
"command",
type = str,
choices = ["list", "show", "add", "change", "remove"],
default = "list",
help = "command to execute",
)
argument_parser.add_argument(
"-b",
"--binary-file-path",
type = str,
default = "/usr/bin/authelia",
help = "path to the Authelia binary file",
)
argument_parser.add_argument(
"-c",
"--conf-file-path",
type = str,
default = "/etc/authelia/configuration.yml",
help = "path to the Authelia configuration file",
)
argument_parser.add_argument(
"-u",
"--user-file-path",
type = str,
default = "/var/authelia/users.yml",
help = "path to the user database file",
)
argument_parser.add_argument(
"-n",
"--login-name",
type = str,
default = None,
help = "login name of the user; has to be unique",
)
argument_parser.add_argument(
"-p",
"--password",
type = str,
default = None,
help = "password of the user",
)
argument_parser.add_argument(
"-d",
"--display-name",
type = str,
default = None,
help = "display name of the user",
)
argument_parser.add_argument(
"-e",
"--email",
type = str,
default = None,
help = "e-mail address of the user",
)
args = argument_parser.parse_args()
## exec
data = data_read(args.user_file_path)
if (args.command == "list"):
names = sorted(data["users"].keys())
for name in names:
_sys.stdout.write("%s\n" % name)
elif (args.command == "show"):
if (args.login_name is None):
raise ValueError("name required")
else:
if (not (args.login_name in data["users"])):
raise ValueError("no such user")
else:
entry = data["users"][args.login_name]
_sys.stdout.write("%s\n" % _yaml.dump(entry))
elif (args.command == "add"):
if (args.login_name is None):
raise ValueError("name required")
else:
if (args.password is None):
raise ValueError("password required")
else:
entry = {
"displayname": (args.display_name or args.login_name),
"password": get_password_hash(args.binary_file_path, args.conf_file_path, args.login_name),
"email": args.email,
"groups": [],
}
data["users"][args.login_name] = entry
data_write(args.binary_file_path, args.conf_file_path, args.user_file_path, data)
elif (args.command == "change"):
if (args.login_name is None):
raise ValueError("name required")
else:
entry = data["users"][args.login_name]
if (args.password is None):
pass
else:
entry["password"] = get_password_hash(args.binary_file_path, args.conf_file_path, args.login_name)
if (args.display_name is None):
pass
else:
entry["displayname"] = args.display_name
if (args.email is None):
pass
else:
entry["email"] = args.email
data["users"][args.login_name] = entry
data_write(args.binary_file_path, args.conf_file_path, args.user_file_path, data)
elif (args.command == "remove"):
if (args.login_name is None):
raise ValueError("name required")
else:
if (not (args.login_name in data["users"])):
raise ValueError("no such user")
else:
del data["users"][args.login_name]
data_write(args.binary_file_path, args.conf_file_path, args.user_file_path, data)
else:
raise NotImplementedError()
main()

View file

@ -0,0 +1,17 @@
## Beschreibung
- zum Aufsetzen des Authentifizierungs-Dienstes [Authelia](https://www.authelia.com/)
- um einen Clients hinzuzfügen, erstellt man eine entsprechende JSON-Datei in `/etc/authelia/conf.d/clients` und führt aus `authelia-conf-compose`
- zum Verwalten der Nutzer kann man `authelia-user-manage` verwenden (`-h` anhängen für Erklärung)
## Verweise
- [GitHub-Seite](https://github.com/authelia/authelia)
- [Installations-Anleitung](https://www.authelia.com/integration/deployment/bare-metal/)
- [Dokumentation | Konfiguration](https://www.authelia.com/configuration/)
## ToDo
- Nutzer-Verwaltung verbessern (evtl. externalisieren)

View file

@ -0,0 +1,128 @@
[
{
"name": "packages | prerequisites",
"become": true,
"ansible.builtin.apt": {
"pkg": [
"apt-transport-https",
"gpg"
]
}
},
{
"name": "packages | keys",
"become": true,
"ansible.builtin.apt_key": {
"url": "https://apt.authelia.com/organization/signing.asc"
}
},
{
"name": "packages | repository",
"become": true,
"ansible.builtin.apt_repository": {
"repo": "deb https://apt.authelia.com/stable/debian/debian/ all main"
}
},
{
"name": "packages | installation",
"become": true,
"ansible.builtin.apt": {
"update_cache": true,
"pkg": [
"openssl",
"python3-cryptography",
"python3-yaml",
"authelia"
]
}
},
{
"name": "generate private key for signing OIDC JWTs",
"become": true,
"community.crypto.openssl_privatekey": {
"type": "RSA",
"size": 4096,
"path": "/etc/ssl/private/authelia-key.pem",
"return_content": true
},
"register": "temp_tls_result"
},
{
"name": "configuration | compose script",
"become": true,
"ansible.builtin.copy": {
"src": "conf-compose.py",
"dest": "/usr/bin/authelia-conf-compose",
"mode": "0700"
}
},
{
"name": "configuration | directories",
"become": true,
"loop": [
"/etc/authelia/conf.d",
"/etc/authelia/conf.d/clients"
],
"ansible.builtin.file": {
"state": "directory",
"path": "{{item}}"
}
},
{
"name": "configuration | main",
"become": true,
"ansible.builtin.template": {
"src": "conf-main.json.j2",
"dest": "/etc/authelia/conf.d/main.json"
}
},
{
"name": "configuration | compose",
"become": true,
"ansible.builtin.command": {
"cmd": "/usr/bin/authelia-conf-compose --main-file-path=/etc/authelia/conf.d/main.json --clients-directory-path=/etc/authelia/conf.d/clients --output-format=yaml --output-path=/etc/authelia/configuration.yml"
}
},
{
"name": "setup log directory",
"become": true,
"ansible.builtin.file": {
"state": "directory",
"path": "{{var_authelia_log_file_path | dirname}}"
}
},
{
"name": "users | directory",
"become": true,
"ansible.builtin.file": {
"state": "directory",
"path": "{{var_authelia_users_file_path | dirname}}"
}
},
{
"name": "users | initial file",
"become": true,
"ansible.builtin.template": {
"src": "users.yml.j2",
"dest": "{{var_authelia_users_file_path}}"
}
},
{
"name": "users | management script",
"become": true,
"ansible.builtin.copy": {
"src": "user-manage.py",
"dest": "/usr/bin/authelia-user-manage",
"mode": "0700"
}
},
{
"name": "apply",
"become": true,
"ansible.builtin.systemd_service": {
"state": "restarted",
"name": "authelia"
}
}
]

View file

@ -0,0 +1,166 @@
{
"theme": "auto",
"jwt_secret": "{{var_authelia_jwt_secret}}",
"default_2fa_method": "totp",
"server": {
"host": "{{var_authelia_listen_address}}",
"port": 9091,
"path": "",
"enable_pprof": false,
"enable_expvars": false,
"disable_healthcheck": false
},
"log": {
"level": "info",
"format": "json",
"file_path": "{{var_authelia_log_file_path}}",
"keep_stdout": false
},
"telemetry": {
"metrics": {
"enabled": false,
"address": "tcp://0.0.0.0:9959"
}
},
"totp": {
"disable": false,
"issuer": "authelia.com",
"algorithm": "sha1",
"digits": 6,
"period": 30,
"skew": 1,
"secret_size": 32
},
"webauthn": {
"disable": true,
"timeout": "60s",
"display_name": "Authelia",
"attestation_conveyance_preference": "indirect",
"user_verification": "preferred"
},
"ntp": {
"address": "{{var_authelia_ntp_server}}",
"version": 4,
"max_desync": "3s",
"disable_startup_check": false,
"disable_failure": false
},
"authentication_backend": {
"password_reset": {
"disable": true,
"custom_url": ""
},
"refresh_interval": "5m",
"file": {
"path": "{{var_authelia_users_file_path}}",
"watch": true,
"search": {
"email": false,
"case_insensitive": false
},
"password": {
"algorithm": "argon2",
"argon2": {
"variant": "argon2id",
"iterations": 3,
"memory": 65536,
"parallelism": 4,
"key_length": 32,
"salt_length": 16
},
"scrypt": {
"iterations": 16,
"block_size": 8,
"parallelism": 1,
"key_length": 32,
"salt_length": 16
},
"pbkdf2": {
"variant": "sha512",
"iterations": 310000,
"salt_length": 16
},
"sha2crypt": {
"variant": "sha512",
"iterations": 50000,
"salt_length": 16
},
"bcrypt": {
"variant": "standard",
"cost": 12
}
}
}
},
"password_policy": {
"standard": {
"enabled": false,
"min_length": 8,
"max_length": 0,
"require_uppercase": true,
"require_lowercase": true,
"require_number": true,
"require_special": true
},
"zxcvbn": {
"enabled": false,
"min_score": 3
}
},
"access_control": {
"default_policy": "one_factor"
},
"session": {
"name": "authelia_session",
"domain": "{{var_authelia_session_domain}}",
"same_site": "lax",
"secret": "{{var_authelia_session_secret}}",
"expiration": "1h",
"inactivity": "5m",
"remember_me_duration": "1M"
},
"regulation": {
"max_retries": 3,
"find_time": "2m",
"ban_time": "5m"
},
"storage": {
"encryption_key": "{{var_authelia_storage_encryption_key}}",
"local": {
"path": "{{var_authelia_storage_path}}"
}
},
"notifier": {
"disable_startup_check": true,
{% if var_authelia_notification_mode == "file" %}
"filesystem": {
"filename": "{{var_authelia_notification_file_path}}"
}
{% endif %}
{% if var_authelia_notification_mode == "smtp" %}
"smtp": {
"host": "{{var_authelia_notification_smtp_host}}",
"port": {{var_authelia_notification_smtp_port}},
"username": "{{var_authelia_notification_smtp_username}}",
"password": "{{var_authelia_notification_smtp_password}}",
"sender": "{{var_authelia_notification_smtp_sender}}",
"disable_require_tls": false,
"disable_html_emails": false,
"tls": {
"skip_verify": false
}
}
{% endif %}
},
"identity_providers": {
"oidc": {
"hmac_secret": "{{var_authelia_oidc_hmac_secret}}",
"issuer_private_key": "{{temp_tls_result.privatekey | replace('\n', '\\n')}}",
"cors": {
"allowed_origins_from_client_redirect_uris": true
},
"clients": [
]
}
}
}

View file

@ -0,0 +1 @@
users: {}