diff --git a/roles/authelia-for-mas/defaults/main.json b/roles/authelia-for-mas/defaults/main.json new file mode 100644 index 0000000..636a5cb --- /dev/null +++ b/roles/authelia-for-mas/defaults/main.json @@ -0,0 +1,6 @@ +{ + "var_authelia_for_mas_mas_url_base": "https://mas.example.org", + "var_authelia_for_mas_id": "mas", + "var_authelia_for_mas_client_id": "mas", + "var_authelia_for_mas_client_secret": "REPLACE_ME" +} diff --git a/roles/authelia-for-mas/info.md b/roles/authelia-for-mas/info.md new file mode 100644 index 0000000..281cfd0 --- /dev/null +++ b/roles/authelia-for-mas/info.md @@ -0,0 +1,8 @@ +## Beschreibung + +Um [MAS](../mas) gegen [Authelia](../authelia) authentifizieren zu lassen + + +## Verweise + +- [MAS-Dokumentation | Configure an upstream SSO provider](https://element-hq.github.io/matrix-authentication-service/setup/sso.html) diff --git a/roles/authelia-for-mas/tasks/main.json b/roles/authelia-for-mas/tasks/main.json new file mode 100644 index 0000000..3067d3d --- /dev/null +++ b/roles/authelia-for-mas/tasks/main.json @@ -0,0 +1,25 @@ +[ + { + "name": "configuration | emplace", + "become": true, + "ansible.builtin.template": { + "src": "authelia-client-conf.json.j2", + "dest": "/etc/authelia/conf.d/clients/vikunja.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" + } + } +] diff --git a/roles/authelia-for-mas/templates/authelia-client-conf.json.j2 b/roles/authelia-for-mas/templates/authelia-client-conf.json.j2 new file mode 100644 index 0000000..1310283 --- /dev/null +++ b/roles/authelia-for-mas/templates/authelia-client-conf.json.j2 @@ -0,0 +1,16 @@ +{ + "client_id": "{{var_authelia_for_mas_client_id}}", + "client_secret": "{{var_authelia_for_mas_client_secret}}", + "client_name": "MAS", + "public": false, + "authorization_policy": "one_factor", + "redirect_uris": [ + "{{var_authelia_for_mas_mas_url_base}}/upstream/callback/{{var_authelia_for_mas_id}}" + ], + "scopes": [ + "openid", + "email", + "profile" + ], + "token_endpoint_auth_method": "client_secret_basic" +} diff --git a/roles/mas-and-nginx/defaults/main.json b/roles/mas-and-nginx/defaults/main.json new file mode 100644 index 0000000..f3be49f --- /dev/null +++ b/roles/mas-and-nginx/defaults/main.json @@ -0,0 +1,5 @@ +{ + "var_mas_and_nginx_server_port": 2839, + "var_mas_and_nginx_domain": "REPLACE_ME", + "var_mas_and_nginx_tls_mode": "force" +} diff --git a/roles/mas-and-nginx/info.md b/roles/mas-and-nginx/info.md new file mode 100644 index 0000000..117fbee --- /dev/null +++ b/roles/mas-and-nginx/info.md @@ -0,0 +1,8 @@ +## Beschreibung + +- zur Einrichtung von [nginx](../nginx) als Reverse-Proxy für [MAS](../mas) + + +## Verweise + +- [MAS-Dokumentation | Configuring a reverse proxy](https://element-hq.github.io/matrix-authentication-service/setup/reverse-proxy.html) diff --git a/roles/mas-and-nginx/tasks/main.json b/roles/mas-and-nginx/tasks/main.json new file mode 100644 index 0000000..63a1213 --- /dev/null +++ b/roles/mas-and-nginx/tasks/main.json @@ -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_mas_and_nginx_domain}}" + } + }, + { + "name": "emplace configuration | link", + "become": true, + "ansible.builtin.file": { + "state": "link", + "src": "/etc/nginx/sites-available/{{var_mas_and_nginx_domain}}", + "dest": "/etc/nginx/sites-enabled/{{var_mas_and_nginx_domain}}" + } + }, + { + "name": "restart nginx", + "become": true, + "ansible.builtin.systemd_service": { + "state": "restarted", + "name": "nginx" + } + } +] diff --git a/roles/mas-and-nginx/templates/conf.j2 b/roles/mas-and-nginx/templates/conf.j2 new file mode 100644 index 0000000..779f527 --- /dev/null +++ b/roles/mas-and-nginx/templates/conf.j2 @@ -0,0 +1,36 @@ +{% macro mas_common() %} + location / { + proxy_http_version 1.1; + proxy_pass http://localhost:{{var_mas_and_nginx_server_port | string}}; + + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +{% endmacro %} + +server { + server_name {{var_mas_and_nginx_domain}}; + + listen 80; + listen [::]:80; + +{% if (var_mas_and_nginx_tls_mode == 'force') %} + return 301 https://$http_host$request_uri; +{% else %} +{{ mas_common() }} +{% endif %} +} + +{% if (var_mas_and_nginx_tls_mode != 'disable') %} +server { + server_name {{var_mas_and_nginx_domain}}; + + listen 443 ssl http2; + listen [::]:443 ssl http2; + + ssl_certificate_key /etc/ssl/private/{{var_mas_and_nginx_domain}}.pem; + ssl_certificate /etc/ssl/fullchains/{{var_mas_and_nginx_domain}}.pem; + include /etc/nginx/ssl-hardening.conf; + +{{ mas_common() }} +} +{% endif %} diff --git a/roles/mas-and-nginx/vardef.json b/roles/mas-and-nginx/vardef.json new file mode 100644 index 0000000..2ae6020 --- /dev/null +++ b/roles/mas-and-nginx/vardef.json @@ -0,0 +1,19 @@ +{ + "port": { + "type": "integer", + "mandatory": false + }, + "domain": { + "type": "string", + "mandatory": false + }, + "tls_mode": { + "type": "string", + "options": [ + "disable", + "enable", + "force" + ], + "mandatory": false + } +} diff --git a/roles/mas-for-synapse/defaults/main.json b/roles/mas-for-synapse/defaults/main.json new file mode 100644 index 0000000..c90cf80 --- /dev/null +++ b/roles/mas-for-synapse/defaults/main.json @@ -0,0 +1,5 @@ +{ + "var_mas_for_synapse_synapse_url_base": "https://synapse.example.org", + "var_mas_for_synapse_client_id": "synapse", + "var_mas_for_synapse_client_secret": "REPLACE_ME" +} diff --git a/roles/mas-for-synapse/info.md b/roles/mas-for-synapse/info.md new file mode 100644 index 0000000..f22001b --- /dev/null +++ b/roles/mas-for-synapse/info.md @@ -0,0 +1,9 @@ +## Beschreibung + +Um [Synapse](../synapse) gegen [MAS](../mas) authentifizieren zu lassen + + +## Verweise + +- [Synapse-Dokumentation | OpenID Connect](https://matrix-org.github.io/synapse/latest/openid.html) +- [MAS-Dokumentation | Homeserver configuration](https://element-hq.github.io/matrix-authentication-service/setup/homeserver.html) diff --git a/roles/mas-for-synapse/tasks/main.json b/roles/mas-for-synapse/tasks/main.json new file mode 100644 index 0000000..5eeb79b --- /dev/null +++ b/roles/mas-for-synapse/tasks/main.json @@ -0,0 +1,25 @@ +[ + { + "name": "configuration | emplace", + "become": true, + "ansible.builtin.template": { + "src": "mas-client-conf.json.j2", + "dest": "/opt/mas/conf.d/clients/synapse.json" + } + }, + { + "name": "configuration | apply", + "become": true, + "ansible.builtin.command": { + "cmd": "/usr/bin/mas-conf-compose" + } + }, + { + "name": "restart service", + "become": true, + "ansible.builtin.systemd_service": { + "state": "restarted", + "name": "mas" + } + } +] diff --git a/roles/mas-for-synapse/templates/mas-client-conf.json.j2 b/roles/mas-for-synapse/templates/mas-client-conf.json.j2 new file mode 100644 index 0000000..0fa50eb --- /dev/null +++ b/roles/mas-for-synapse/templates/mas-client-conf.json.j2 @@ -0,0 +1,5 @@ +{ + "client_id": "{{var_mas_for_synapse_client_id}}", + "client_secret": "{{var_mas_for_synapse_client_secret}}", + "client_auth_method": "client_secret_basic" +} diff --git a/roles/mas/defaults/main.json b/roles/mas/defaults/main.json new file mode 100644 index 0000000..e402ce6 --- /dev/null +++ b/roles/mas/defaults/main.json @@ -0,0 +1,24 @@ +{ + "var_mas_user": "mas", + "var_mas_directory": "/opt/mas", + "var_mas_server_address": "::", + "var_mas_server_port": 2839, + "var_mas_database_host": "postgresql.example.org", + "var_mas_database_port": 5432, + "var_mas_database_username": "mas_user", + "var_mas_database_password": "REPLACE_ME", + "var_mas_database_schema": "mas", + "var_mas_matrix_server": "localhost:8008", + "var_mas_matrix_secret": "REPLACE_ME", + "var_mas_matrix_endpoint": "http://localhost:8008/", + "var_mas_encryption_key": "REPLACE_ME", + "var_mas_authentication_upstream_active": false, + "var_mas_authentication_upstream_id": "default_upstream", + "var_mas_authentication_upstream_issuer": "https://auth.example.org", + "var_mas_authentication_upstream_client_id": "mas", + "var_mas_authentication_upstream_client_secret": "REPLACE_ME", + "var_mas_authentication_upstream_token_endpoint_auth_method": "client_secret_post" + "var_mas_authentication_upstream_scope": "openid email profile", + "var_mas_authentication_upstream_authorization_endpoint": "https://auth.example.org/authorize", + "var_mas_authentication_upstream_token_endpoint": "https://auth.example.org/token" +} diff --git a/roles/mas/files/conf-compose.py b/roles/mas/files/conf-compose.py new file mode 100644 index 0000000..59434c3 --- /dev/null +++ b/roles/mas/files/conf-compose.py @@ -0,0 +1,127 @@ +#!/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( + "-b", + "--base-file-path", + type = str, + dest = "base_file_path", + default = "/opt/mas/conf.d/base.json", + metavar = "", + ) + argument_parser.add_argument( + "-c", + "--clients-directory-path", + type = str, + dest = "clients_directory_path", + default = "/opt/mas/conf.d/clients", + metavar = "", + ) + argument_parser.add_argument( + "-f", + "--output-format", + type = str, + choices = ["json", "yaml"], + dest = "output_format", + default = "yaml", + metavar = "", + ) + argument_parser.add_argument( + "-o", + "--output-path", + type = str, + dest = "output_path", + default = "/opt/mas/config.yaml", + metavar = "", + ) + args = argument_parser.parse_args() + + ## exec + data = {} + ### base + if True: + data_ = _json.loads(file_read(args.base_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_ = { + "clients": data__ + } + data = dict_merge(data, data_) + ## 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() diff --git a/roles/mas/info.md b/roles/mas/info.md new file mode 100644 index 0000000..1eaeb83 --- /dev/null +++ b/roles/mas/info.md @@ -0,0 +1,8 @@ +## Beschreibung + +für [Matrix Authentication Service](https://github.com/element-hq/matrix-authentication-service), was eine OIDC-Portal für [Synapse](../synapse) ist + + +## Verweise + +- [Dokumentation](https://element-hq.github.io/matrix-authentication-service/index.html) diff --git a/roles/mas/tasks/main.json b/roles/mas/tasks/main.json new file mode 100644 index 0000000..b3598a3 --- /dev/null +++ b/roles/mas/tasks/main.json @@ -0,0 +1,87 @@ +[ + { + "name": "user", + "become": true, + "ansible.builtin.user": { + "name": "{{var_mas_user}}", + "create_home": true, + "home": "{{var_mas_directory}}" + } + }, + { + "name": "directories", + "become": true, + "loop": [ + "{{var_mas_directory}}/conf.d", + "{{var_mas_directory}}/conf.d/providers", + "{{var_mas_directory}}/conf.d/clients" + ], + "ansible.builtin.file": { + "state": "directory", + "owner": "{{var_mas_user}}", + "path": "{{item}}" + } + }, + { + "name": "download", + "become": true, + "become_user": "{{var_mas_user}}", + "ansible.builtin.get_url": { + "url": "https://github.com/element-hq/matrix-authentication-service/releases/latest/download/mas-cli-x86_64-linux.tar.gz", + "dest": "/tmp/mas.tar.gz" + } + }, + { + "name": "extract", + "become": true, + "become_user": "{{var_mas_user}}", + "ansible.builtin.unarchive": { + "remote_src": true, + "src": "/tmp/mas.tar.gz", + "dest": "{{var_mas_directory}}", + "owner": "{{var_mas_user}}" + } + }, + { + "name": "configuration | compose script", + "become": true, + "ansible.builtin.copy": { + "src": "conf-compose.py", + "dest": "/usr/bin/mas-conf-compose", + "mode": "0700" + } + }, + { + "name": "configuration | base", + "become": true, + "become_user": "{{var_mas_user}}", + "ansible.builtin.template": { + "src": "config-main.yaml.j2", + "dest": "{{var_mas_directory}}/conf.d/base.yaml" + } + }, + { + "name": "configuration | apply", + "become": true, + "ansible.builtin.command": { + "cmd": "/usr/bin/mas-conf-compose" + } + }, + { + "name": "systemd unit", + "become": true, + "ansible.builtin.template": { + "src": "systemd_unit.j2", + "dest": "/etc/systemd/system/mas.service" + } + }, + { + "name": "run", + "become": true, + "ansible.builtin.systemd_service": { + "name": "mas", + "enabled": true, + "state": "restarted" + } + } +] diff --git a/roles/mas/templates/config-base.json.j2 b/roles/mas/templates/config-base.json.j2 new file mode 100644 index 0000000..1ba74d4 --- /dev/null +++ b/roles/mas/templates/config-base.json.j2 @@ -0,0 +1,109 @@ +{ + "database": { + "host": "{{var_mas_database_host}}", + "port": "{{var_mas_database_port | string}}", + "username": "{{var_mas_database_username}}", + "password": "{{var_mas_database_password}}", + "database": "{{var_mas_database_schema}}" + }, + "http": { + "listeners": [ + { + "name": "web", + "resources": [ + { + "name": "discovery" + }, + { + "name": "human" + }, + { + "name": "oauth" + }, + { + "name": "compat" + }, + { + "name": "graphql" + }, + { + "name": "assets" + }, + ], + "binds": [ + { + "address": "[{{var_mas_server_address}}]:{{var_mas_server_port | string}}" + } + ], + "proxy_protocol": false + }, + { + "name": "internal", + "resources": [ + { + "name": "health" + } + ], + "binds": [ + { + "host": "localhost", + "port": 8081 + } + ], + "proxy_protocol": false + } + ], + "trusted_proxies": [ + "192.168.0.0/16", + "172.16.0.0/12", + "10.0.0.0/10", + "127.0.0.1/8", + "fd00::/8", + "::1/128", + ], + "public_base": "http://{{var_mas_server_address}}]:{{var_mas_server_port | string}}/", + "issuer": "http://{{var_mas_server_address}}]:{{var_mas_server_port | string}}/" + }, + "matrix": { + "homeserver": "{{var_mas_matrix_server}}", + "secret": "{{var_mas_matrix_secret}}", + "endpoint": "{{var_mas_matrix_endpoint}}" + }, + "secrets": { + "encryption": "{{var_mas_encryption_key}}", + "keys": [ + "__TODO__" + ] + }, + "passwords": { + "enabled": true, + "schemas": [ + { + "version": 1, + "algorithm": "argon2id" + } + ], + "minimum_complexity": 3 + }, +{% if var_mas_authentication_upstream_active %} + "upstream_oauth2": { + "providers": [ + { + "id": "{{var_mas_authentication_upstream_id}}", + "issuer": "{{var_mas_authentication_upstream_issuer}}", + "client_id": "{{var_mas_authentication_upstream_client_id}}", + "client_secret": "{{var_mas_authentication_upstream_client_secret}}", + "token_endpoint_auth_method": "{{var_mas_authentication_upstream_token_endpoint_auth_method}}", + "scope": "{{var_mas_authentication_upstream_scope}}", + "authorization_endpoint": "{{var_mas_authentication_upstream_authorization_endpoint}}", + "token_endpoint": "{{var_mas_authentication_upstream_token_endpoint}}" + } + ] + }, +{% endif %} + "email": { + "from": "'\"Authentication Service\" '", + "reply_to": "'\"Authentication Service\" '", + "transport": "blackhole" + } +} diff --git a/roles/mas/templates/systemd_unit.j2 b/roles/mas/templates/systemd_unit.j2 new file mode 100644 index 0000000..f68219f --- /dev/null +++ b/roles/mas/templates/systemd_unit.j2 @@ -0,0 +1,15 @@ +[Unit] +Description=MAS +After=network.target + +[Service] +WorkingDirectory={{var_mas_directory}} +ExecStart={{var_mas_directory}}/mas-cli server +Type=simple +Restart=always +User={{var_mas_user}} + +[Install] +WantedBy=default.target +RequiredBy=network.target + diff --git a/roles/mas/vardef.json b/roles/mas/vardef.json new file mode 100644 index 0000000..dc301b8 --- /dev/null +++ b/roles/mas/vardef.json @@ -0,0 +1,82 @@ +{ + "user": { + "type": "string", + "mandatory": false + }, + "directory": { + "type": "string", + "mandatory": false + }, + "database_host": { + "type": "string", + "mandatory": false + }, + "database_port": { + "type": "integer", + "mandatory": false + }, + "database_username": { + "type": "string", + "mandatory": false + }, + "database_password": { + "type": "string", + "mandatory": false + }, + "database_schema": { + "type": "string", + "mandatory": false + }, + "matrix_server": { + "type": "string", + "mandatory": false + }, + "matrix_secret": { + "type": "string", + "mandatory": false + }, + "matrix_endpoint": { + "type": "string", + "mandatory": false + }, + "encryption_key": { + "type": "string", + "mandatory": false + }, + "authentication_upstream_active": { + "nullable": false, + "type": "boolean" + }, + "authentication_upstream_id": { + "nullable": false, + "type": "string" + }, + "authentication_upstream_issuer": { + "nullable": false, + "type": "string" + }, + "authentication_upstream_client_id": { + "nullable": false, + "type": "string" + }, + "authentication_upstream_client_secret": { + "nullable": false, + "type": "string" + }, + "authentication_upstream_token_endpoint_auth_method": { + "nullable": false, + "type": "string" + }, + "authentication_upstream_scope": { + "nullable": false, + "type": "string" + }, + "authentication_upstream_authorization_endpoint": { + "nullable": false, + "type": "string" + }, + "authentication_upstream_token_endpoint": { + "nullable": false, + "type": "string" + } +} diff --git a/roles/postgresql-for-mas/defaults/main.json b/roles/postgresql-for-mas/defaults/main.json new file mode 100644 index 0000000..eac9bc3 --- /dev/null +++ b/roles/postgresql-for-mas/defaults/main.json @@ -0,0 +1,5 @@ +{ + "var_postgresql_for_vikunja_username": "vikunja_user", + "var_postgresql_for_vikunja_password": "REPLACE_ME", + "var_postgresql_for_vikunja_schema": "vikunja" +} diff --git a/roles/postgresql-for-mas/tasks/main.json b/roles/postgresql-for-mas/tasks/main.json new file mode 100644 index 0000000..fb5f787 --- /dev/null +++ b/roles/postgresql-for-mas/tasks/main.json @@ -0,0 +1,49 @@ +[ + { + "name": "packages", + "become": true, + "ansible.builtin.apt": { + "update_cache": true, + "pkg": [ + "acl", + "python3-psycopg2" + ] + } + }, + { + "name": "user", + "become": true, + "become_user": "postgres", + "community.postgresql.postgresql_user": { + "state": "present", + "name": "{{var_postgresql_for_vikunja_username}}", + "password": "{{var_postgresql_for_vikunja_password}}" + }, + "environment": { + "PGOPTIONS": "-c password_encryption=scram-sha-256" + } + }, + { + "name": "schema", + "become": true, + "become_user": "postgres", + "community.postgresql.postgresql_db": { + "state": "present", + "name": "{{var_postgresql_for_vikunja_schema}}", + "owner": "{{var_postgresql_for_vikunja_username}}" + } + }, + { + "name": "rights", + "become": true, + "become_user": "postgres", + "community.postgresql.postgresql_privs": { + "state": "present", + "db": "{{var_postgresql_for_vikunja_schema}}", + "objs": "ALL_IN_SCHEMA", + "roles": "{{var_postgresql_for_vikunja_username}}", + "privs": "ALL", + "grant_option": true + } + } +]