Solved bug existed due to difference between mailu domain and hostname difference. also refactored during this to find the bug

This commit is contained in:
Kevin Veen-Birkenbach 2025-08-16 14:29:07 +02:00
parent 1bed83078e
commit 0de26fa6c7
No known key found for this signature in database
GPG Key ID: 44D8F11FD62F878E
76 changed files with 543 additions and 487 deletions

View File

@ -0,0 +1,19 @@
# filter_plugins/domain_tools.py
# Returns the DNS zone (SLD.TLD) from a hostname.
# Pure-Python, no external deps; handles simple cases. For exotic TLDs use tldextract (see note).
from ansible.errors import AnsibleFilterError
def to_zone(hostname: str) -> str:
if not isinstance(hostname, str) or not hostname.strip():
raise AnsibleFilterError("to_zone: hostname must be a non-empty string")
parts = hostname.strip(".").split(".")
if len(parts) < 2:
raise AnsibleFilterError(f"to_zone: '{hostname}' has no TLD part")
# naive default: last two labels -> SLD.TLD
return ".".join(parts[-2:])
class FilterModule(object):
def filters(self):
return {
"to_zone": to_zone,
}

View File

@ -44,7 +44,7 @@ defaults_networks:
subnet: 192.168.102.0/28 subnet: 192.168.102.0/28
web-app-mailu: web-app-mailu:
# Use one of the last container ips for dns resolving so that it isn't used # Use one of the last container ips for dns resolving so that it isn't used
dns: 192.168.102.29 dns_resolver: 192.168.102.29
subnet: 192.168.102.16/28 subnet: 192.168.102.16/28
web-app-moodle: web-app-moodle:
subnet: 192.168.102.32/28 subnet: 192.168.102.32/28

View File

@ -8,37 +8,37 @@
# @see https://en.wikipedia.org/wiki/OpenID_Connect # @see https://en.wikipedia.org/wiki/OpenID_Connect
## Helper Variables: ## Helper Variables:
_oidc_client_realm: "{{ oidc.client.realm if oidc.client is defined and oidc.client.realm is defined else SOFTWARE_NAME | lower }}" _oidc_client_realm: "{{ OIDC.CLIENT.ISSUER_URL if OIDC.CLIENT is defined and OIDC.CLIENT.ISSUER_URL is defined else SOFTWARE_NAME | lower }}"
_oidc_url: "{{ _oidc_url: "{{
(oidc.url (OIDC.URL
if (oidc is defined and oidc.url is defined) if (oidc is defined and OIDC.URL is defined)
else WEB_PROTOCOL ~ '://' ~ (domains | get_domain('web-app-keycloak')) else WEB_PROTOCOL ~ '://' ~ (domains | get_domain('web-app-keycloak'))
) )
}}" }}"
_oidc_client_issuer_url: "{{ _oidc_url }}/realms/{{_oidc_client_realm}}" _oidc_client_issuer_url: "{{ _oidc_url }}/realms/{{_oidc_client_realm}}"
_oidc_client_id: "{{ oidc.client.id if oidc.client is defined and oidc.client.id is defined else SOFTWARE_NAME | lower }}" _oidc_client_id: "{{ OIDC.CLIENT.ID if OIDC.CLIENT is defined and OIDC.CLIENT.ID is defined else SOFTWARE_NAME | lower }}"
defaults_oidc: defaults_oidc:
url: "{{ _oidc_url }}" URL: "{{ _oidc_url }}"
client: CLIENT:
id: "{{ _oidc_client_id }}" # Client identifier, typically matching your primary domain ID: "{{ _oidc_client_id }}" # Client identifier, typically matching your primary domain
# secret: # Client secret for authenticating with the OIDC provider (set in the inventory file). Recommend greater then 32 characters # secret: # Client secret for authenticating with the OIDC provider (set in the inventory file). Recommend greater then 32 characters
realm: "{{_oidc_client_realm}}" # The realm to which the client belongs in the OIDC provider REALM: "{{_oidc_client_realm}}" # The realm to which the client belongs in the OIDC provider
issuer_url: "{{_oidc_client_issuer_url}}" # Base URL of the OIDC provider (issuer) ISSUER_URL: "{{_oidc_client_issuer_url}}" # Base URL of the OIDC provider (issuer)
discovery_document: "{{_oidc_client_issuer_url}}/.well-known/openid-configuration" # URL for fetching the provider's configuration details DISCOVERY_DOCUMENT: "{{_oidc_client_issuer_url}}/.well-known/openid-configuration" # URL for fetching the provider's configuration details
authorize_url: "{{_oidc_client_issuer_url}}/protocol/openid-connect/auth" # Endpoint to start the authorization process AUTHORIZE_URL: "{{_oidc_client_issuer_url}}/protocol/openid-connect/auth" # Endpoint to start the authorization process
token_url: "{{_oidc_client_issuer_url}}/protocol/openid-connect/token" # Endpoint to exchange authorization codes for tokens (note: 'token_url' may be a typo for 'token_url') TOKEN_URL: "{{_oidc_client_issuer_url}}/protocol/openid-connect/token" # Endpoint to exchange authorization codes for tokens (note: 'token_url' may be a typo for 'token_url')
user_info_url: "{{_oidc_client_issuer_url}}/protocol/openid-connect/userinfo" # Endpoint to retrieve user information USER_INFO_URL: "{{_oidc_client_issuer_url}}/protocol/openid-connect/userinfo" # Endpoint to retrieve user information
logout_url: "{{_oidc_client_issuer_url}}/protocol/openid-connect/logout" # Endpoint to log out the user LOGOUT_URL: "{{_oidc_client_issuer_url}}/protocol/openid-connect/logout" # Endpoint to log out the user
change_credentials: "{{_oidc_client_issuer_url}}account/account-security/signing-in" # URL for managing or changing user credentials CHANGE_CREDENTIALS: "{{_oidc_client_issuer_url}}account/account-security/signing-in" # URL for managing or changing user credentials
certs: "{{_oidc_client_issuer_url}}/protocol/openid-connect/certs" # JSON Web Key Set (JWKS) CERTS: "{{_oidc_client_issuer_url}}/protocol/openid-connect/certs" # JSON Web Key Set (JWKS)
reset_credentials: "{{_oidc_client_issuer_url}}/login-actions/reset-credentials?client_id={{ _oidc_client_id }}" # Password reset url RESET_CREDENTIALS: "{{_oidc_client_issuer_url}}/login-actions/reset-credentials?client_id={{ _oidc_client_id }}" # Password reset url
button_text: "SSO Login ({{ PRIMARY_DOMAIN | upper }})" # Default button text BUTTON_TEXT: "SSO Login ({{ PRIMARY_DOMAIN | upper }})" # Default button text
attributes: ATTRIBUTES:
# Attribut to identify the user # Attribut to identify the user
username: "preferred_username" USERNAME: "preferred_username"
given_name: "givenName" GIVEN_NAME: "givenName"
family_name: "surname" FAMILY_NAME: "surname"
email: "email" EMAIL: "email"
claims: CLAIMS:
groups: "groups" GROUPS: "groups"

View File

@ -30,4 +30,4 @@ defaults_service_provider:
legal: legal:
editorial_responsible: "Johannes Gutenberg" editorial_responsible: "Johannes Gutenberg"
source_code: "https://s.{{ SOFTWARE_NAME | lower }}/code" source_code: "https://s.{{ SOFTWARE_NAME | lower }}/code"
imprint: "{{WEB_PROTOCOL}}://{{ domains | get_domain('web-svc-html') }}/imprint.html" imprint: "{{ domains | get_url('web-svc-html', WEB_PROTOCOL) }}/imprint.html"

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Keyboard Color Service Description=Keyboard Color Service
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,67 +0,0 @@
# Nginx Docker Cert Deploy Role
🎉 **Author**: [Kevin Veen-Birkenbach](https://www.veen.world)
This Ansible role simplifies the deployment of **Let's Encrypt certificates** into **Docker Compose** setups with Nginx. It supports both **individual certificates per subdomain** and a **single wildcard certificate** for all subdomains.
---
## 🚀 **Features**
- Automatically deploys **Let's Encrypt certificates** to Docker Compose setups.
- Supports both **single-domain certificates** and **one wildcard certificate** for all subdomains.
- **Copies certificates** to the target directory inside the container.
- Automatically **reloads or restarts Nginx services** when certificates are updated.
- **Configures and manages a `systemd` service** for automated certificate deployment.
- **Includes a `systemd` timer** for scheduled renewals.
- **Handles dependent services** like `sys-alm-compose`.
---
## 🔧 **Tasks Overview**
### **1⃣ Main Tasks**
1. **Add Deployment Script**
- Copies `srv-proxy-6-6-tls-deploy.sh` to the administrator scripts directory.
2. **Create Certificate Directory**
- Ensures `cert_mount_directory` exists with proper permissions.
3. **Configure `systemd` Service**
- Deploys a `systemd` service file for the deployment process.
4. **Include `sys-timer` Role**
- Schedules automatic certificate deployment using a `systemd` timer.
### **2⃣ Handlers**
- **Restart Nginx Service**
- Restarts `srv-proxy-6-6-tls-deploy` whenever a certificate update occurs.
---
## **🔧 Deploying Certificates into Docker Containers**
The role **automates copying certificates** into Docker Compose setups.
### **1⃣ Deployment Script (`srv-proxy-6-6-tls-deploy.sh`)**
This script:
- **Copies certificates** to the correct container directory.
- **Reloads Nginx** inside all running containers.
- **Restarts containers if needed**.
**Usage:**
```sh
sh srv-proxy-6-6-tls-deploy.sh PRIMARY_DOMAIN /path/to/docker/compose
```
---
## 🎯 **Summary**
| Feature | Description |
|---------|------------|
| **Single-domain & wildcard support** | Use individual certs or a wildcard certificate |
| **Automated renewal** | Cronjob or systemd timer ensures auto-renewals |
| **Docker-ready** | Deploys certificates directly into Docker containers |
| **Supports Nginx & Mailu** | Compatible with multiple services |
| **Systemd integration** | Automates deployment via `systemd` |
🚀 **Now your Nginx setup is fully automated and secured with Let's Encrypt!** 🎉
```

View File

@ -1,7 +0,0 @@
---
- name: "restart srv-proxy-6-6-tls-deploy service"
systemd:
name: srv-proxy-6-6-tls-deploy.{{ application_id }}{{ SYS_SERVICE_SUFFIX }}
state: restarted
enabled: yes
daemon_reload: yes

View File

@ -1,10 +0,0 @@
- name: Include dependency 'sys-alm-compose'
include_role:
name: sys-alm-compose
when: run_once_sys_alm_compose is not defined
- name: add srv-proxy-6-6-tls-deploy.sh
template:
src: "srv-proxy-6-6-tls-deploy.sh.j2"
dest: "{{nginx_docker_cert_deploy_script}}"
notify: restart srv-proxy-6-6-tls-deploy service

View File

@ -1,27 +0,0 @@
- block:
- include_tasks: 01_core.yml
- set_fact:
run_once_srv_proxy_6_6_tls_deploy: true
when: run_once_srv_proxy_6_6_tls_deploy is not defined
- name: "create {{cert_mount_directory}}"
file:
path: "{{cert_mount_directory}}"
state: directory
mode: "0755"
notify: restart srv-proxy-6-6-tls-deploy service
- name: configure srv-proxy-6-6-tls-deploy service
template:
src: "srv-proxy-6-6-tls-deploy.service.j2"
dest: "/etc/systemd/system/srv-proxy-6-6-tls-deploy.{{ application_id }}{{ SYS_SERVICE_SUFFIX }}"
notify: restart srv-proxy-6-6-tls-deploy service
- name: "include role for sys-timer for {{ service_name }}"
include_role:
name: sys-timer
vars:
on_calendar: "{{on_calendar_deploy_certificates}}"
service_name: "srv-proxy-6-6-tls-deploy.{{ application_id }}"
persistent: "true"

View File

@ -1,7 +0,0 @@
[Unit]
Description=Let's Encrypt deploy to {{ docker_compose.directories.instance }}
OnFailure=sys-alm-compose.infinito@%n.service
[Service]
Type=oneshot
ExecStart=/usr/bin/bash {{ PATH_ADMINISTRATOR_SCRIPTS }}/srv-proxy-6-6-tls-deploy.sh {{ssl_cert_folder}} {{ docker_compose.directories.instance }}

View File

@ -1 +0,0 @@
nginx_docker_cert_deploy_script: "{{ PATH_ADMINISTRATOR_SCRIPTS }}srv-proxy-6-6-tls-deploy.sh"

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Let's Encrypt renewal Description=Let's Encrypt renewal
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,7 +1,7 @@
[Unit] [Unit]
Description=Backup to USB when mounted to {{ backup_to_usb_mount }} Description=Backup to USB when mounted to {{ backup_to_usb_mount }}
Wants={{systemctl_mount_service_name}} Wants={{systemctl_mount_service_name}}
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=pull remote backups Description=pull remote backups
OnFailure=sys-alm-compose.infinito@%n.service sys-cln-faild-bkps{{ SYS_SERVICE_SUFFIX }} OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service sys-cln-faild-bkps{{ SYS_SERVICE_SUFFIX }}
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Optimize storage paths Description=Optimize storage paths
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=backup all docker volumes to local folder Description=backup all docker volumes to local folder
OnFailure=sys-alm-compose.infinito@%n.service sys-cln-faild-bkps{{ SYS_SERVICE_SUFFIX }} OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service sys-cln-faild-bkps{{ SYS_SERVICE_SUFFIX }}
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=backup docker volumes to local folder Description=backup docker volumes to local folder
OnFailure=sys-alm-compose.infinito@%n.service sys-cln-faild-bkps{{ SYS_SERVICE_SUFFIX }} OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service sys-cln-faild-bkps{{ SYS_SERVICE_SUFFIX }}
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=delete old backups Description=delete old backups
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Detect, revoke, and delete unused Let's Encrypt certificates based on active NGINX configuration files. Description=Detect, revoke, and delete unused Let's Encrypt certificates based on active NGINX configuration files.
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=freeing disc space Description=freeing disc space
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Cleaning up failed docker volume backups Description=Cleaning up failed docker volume backups
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Check btrfs status Description=Check btrfs status
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Check for CSP-blocked resources via Puppeteer Description=Check for CSP-blocked resources via Puppeteer
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=checking disc space Description=checking disc space
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Checking docker health Description=Checking docker health
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Checking docker health Description=Checking docker health
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=checking journalctl health Description=checking journalctl health
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Check msmtp liveliness Description=Check msmtp liveliness
OnFailure=sys-alm-telegram.infinito@%n.service OnFailure=sys-alm-telegram.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Check nginx configuration status Description=Check nginx configuration status
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=auto balance btrfs Description=auto balance btrfs
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Restart Docker Instances Description=Restart Docker Instances
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=restart unhealthy docker containers Description=restart unhealthy docker containers
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -1,6 +1,6 @@
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
initLogoutPatch( initLogoutPatch(
'{{ oidc.client.logout_url }}', '{{ OIDC.CLIENT.LOGOUT_URL }}',
'{{ WEB_PROTOCOL }}', '{{ WEB_PROTOCOL }}',
'{{ PRIMARY_DOMAIN }}' '{{ PRIMARY_DOMAIN }}'
); );

View File

@ -0,0 +1,18 @@
# Docker Compose Certificate Sync Service
## Description
Keeps Docker Compose services updated with fresh Lets Encrypt certificates via a systemd oneshot service and timer.
## Overview
Installs a small script and a systemd unit that copy certificates into your Compose project and trigger an Nginx hot-reload (fallback: restart) to minimize downtime.
## Features
- Automatic certificate sync into the Compose project
- Mailu-friendly filenames (`key.pem`, `cert.pem`)
- Nginx hot-reload if available, otherwise restart
- Runs on a schedule you define
## Further Resources
- [Wildcard Certificate Setup (SETUP.md)](./SETUP.md)
- [Role Documentation](https://s.infinito.nexus/code/tree/main/roles/sys-svc-cert-sync-docker)
- [Issue Tracker](https://s.infinito.nexus/issues)

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# Check if the necessary parameters are provided # Check if the necessary parameters are provided
if [ "$#" -ne 2 ]; then if [ "$#" -ne 3 ]; then
echo "Usage: $0 <ssl_cert_folder> <docker_compose_instance_directory>" echo "Usage: $0 <ssl_cert_folder> <docker_compose_instance_directory>"
exit 1 exit 1
fi fi
@ -9,14 +9,15 @@ fi
# Assign parameters # Assign parameters
ssl_cert_folder="$1" ssl_cert_folder="$1"
docker_compose_instance_directory="$2" docker_compose_instance_directory="$2"
letsencrypt_live_path="$3"
docker_compose_cert_directory="$docker_compose_instance_directory/volumes/certs" docker_compose_cert_directory="$docker_compose_instance_directory/volumes/certs"
# Copy certificates # Copy certificates
cp -RvL "{{ LETSENCRYPT_LIVE_PATH }}/$ssl_cert_folder/"* "$docker_compose_cert_directory" || exit 1 cp -RvL "$letsencrypt_live_path/$ssl_cert_folder/"* "$docker_compose_cert_directory" || exit 1
# This code is optimized for mailu # This code is optimized for mailu
cp -v "{{ LETSENCRYPT_LIVE_PATH }}/$ssl_cert_folder/privkey.pem" "$docker_compose_cert_directory/key.pem" || exit 1 cp -v "$letsencrypt_live_path/$ssl_cert_folder/privkey.pem" "$docker_compose_cert_directory/key.pem" || exit 1
cp -v "{{ LETSENCRYPT_LIVE_PATH }}/$ssl_cert_folder/fullchain.pem" "$docker_compose_cert_directory/cert.pem" || exit 1 cp -v "$letsencrypt_live_path/$ssl_cert_folder/fullchain.pem" "$docker_compose_cert_directory/cert.pem" || exit 1
# Set correct reading rights # Set correct reading rights
chmod a+r -v "$docker_compose_cert_directory/"* chmod a+r -v "$docker_compose_cert_directory/"*

View File

@ -0,0 +1,7 @@
---
- name: "restart tls deploy to docker service"
systemd:
name: "{{ CERT_SYNC_DOCKER_SERVICE_NAME }}"
state: restarted
enabled: yes
daemon_reload: yes

View File

@ -14,7 +14,7 @@ galaxy_info:
- systemd - systemd
repository: "https://s.infinito.nexus/code" repository: "https://s.infinito.nexus/code"
issue_tracker_url: "https://s.infinito.nexus/issues" issue_tracker_url: "https://s.infinito.nexus/issues"
documentation: "https://s.infinito.nexus/code/tree/main/roles/srv-proxy-6-6-tls-deploy" documentation: "https://s.infinito.nexus/code/tree/main/roles/sys-svc-cert-sync-docker"
min_ansible_version: "2.9" min_ansible_version: "2.9"
platforms: platforms:
- name: Any - name: Any

View File

@ -0,0 +1,13 @@
- name: Include dependency 'sys-alm-compose'
include_role:
name: sys-alm-compose
when: run_once_sys_alm_compose is not defined
- name: "Install '{{ CERT_SYNC_DOCKER_SCRIPT_FILE }}'"
ansible.builtin.copy:
src: "{{ CERT_SYNC_DOCKER_SCRIPT_FILE }}"
dest: "{{ CERT_SYNC_DOCKER_SCRIPT_PATH }}"
mode: "0755"
owner: root
group: root
notify: restart tls deploy to docker service

View File

@ -0,0 +1,27 @@
- block:
- include_tasks: 01_core.yml
- set_fact:
run_once_sys_svc_cert_sync_docker: true
when: run_once_sys_svc_cert_sync_docker is not defined
- name: "create {{ cert_mount_directory }}"
file:
path: "{{ cert_mount_directory }}"
state: directory
mode: "0755"
notify: restart tls deploy to docker service
- name: configure sys-svc-cert-sync-docker service
template:
src: "sys-svc-cert-sync-docker.service.j2"
dest: "/etc/systemd/system/{{ CERT_SYNC_DOCKER_SERVICE_NAME }}"
notify: restart tls deploy to docker service
- name: "include role for sys-timer for {{ service_name }}"
include_role:
name: sys-timer
vars:
on_calendar: "{{ on_calendar_deploy_certificates }}"
service_name: "{{ CERT_SYNC_DOCKER_SERVICE_NAME }}"
persistent: "true"

View File

@ -0,0 +1,7 @@
[Unit]
Description=Let's Encrypt deploy to {{ docker_compose.directories.instance }}
OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service]
Type=oneshot
ExecStart={{ PATH_ADMINISTRATOR_SCRIPTS }}/{{ CERT_SYNC_DOCKER_SCRIPT_FILE }} {{ ssl_cert_folder }} {{ docker_compose.directories.instance }} {{ LETSENCRYPT_LIVE_PATH }}

View File

@ -0,0 +1,3 @@
CERT_SYNC_DOCKER_SCRIPT_FILE: "sys-svc-cert-sync-docker.sh"
CERT_SYNC_DOCKER_SCRIPT_PATH: "{{ PATH_ADMINISTRATOR_SCRIPTS }}{{ CERT_SYNC_DOCKER_SCRIPT_FILE }}"
CERT_SYNC_DOCKER_SERVICE_NAME: "sys-svc-cert-sync-docker.{{ application_id }}{{ SYS_SERVICE_SUFFIX }}"

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Updates Docker Instances Description=Updates Docker Instances
OnFailure=sys-alm-compose.infinito@%n.service OnFailure=sys-alm-compose.{{ SOFTWARE_NAME }}@%n.service
[Service] [Service]
Type=oneshot Type=oneshot

View File

@ -287,9 +287,9 @@ DEFAULT_REGISTRATION=invite
### EXTERNAL AUTHENTICATION METHODS ### EXTERNAL AUTHENTICATION METHODS
# @See https://docs.bigbluebutton.org/greenlight/v3/external-authentication/ # @See https://docs.bigbluebutton.org/greenlight/v3/external-authentication/
# #
OPENID_CONNECT_CLIENT_ID={{ oidc.client.id }} OPENID_CONNECT_CLIENT_ID={{ OIDC.CLIENT.ID }}
OPENID_CONNECT_CLIENT_SECRET={{ oidc.client.secret }} OPENID_CONNECT_CLIENT_SECRET={{ OIDC.CLIENT.SECRET }}
OPENID_CONNECT_ISSUER={{ oidc.client.issuer_url }} OPENID_CONNECT_ISSUER={{ OIDC.CLIENT.ISSUER_URL }}
OPENID_CONNECT_REDIRECT={{ domains | get_url(application_id, WEB_PROTOCOL) }} OPENID_CONNECT_REDIRECT={{ domains | get_url(application_id, WEB_PROTOCOL) }}
# OPENID_CONNECT_UID_FIELD=sub default # OPENID_CONNECT_UID_FIELD=sub default
{% endif %} {% endif %}

View File

@ -136,9 +136,9 @@ run:
# OIDC Activation # OIDC Activation
- exec: rails r "SiteSetting.openid_connect_enabled = true" - exec: rails r "SiteSetting.openid_connect_enabled = true"
- exec: rails r "SiteSetting.openid_connect_discovery_document = '{{oidc.client.discovery_document}}'" - exec: rails r "SiteSetting.openid_connect_discovery_document = '{{OIDC.CLIENT.DISCOVERY_DOCUMENT}}'"
- exec: rails r "SiteSetting.openid_connect_client_id = '{{ oidc.client.id }}'" - exec: rails r "SiteSetting.openid_connect_client_id = '{{ OIDC.CLIENT.ID }}'"
- exec: rails r "SiteSetting.openid_connect_client_secret = '{{ oidc.client.secret }}'" - exec: rails r "SiteSetting.openid_connect_client_secret = '{{ OIDC.CLIENT.SECRET }}'"
- exec: rails r "SiteSetting.openid_connect_rp_initiated_logout_redirect = 'https://{{ domains | get_domain(application_id) }}'" - exec: rails r "SiteSetting.openid_connect_rp_initiated_logout_redirect = 'https://{{ domains | get_domain(application_id) }}'"
- exec: rails r "SiteSetting.openid_connect_allow_association_change = false" - exec: rails r "SiteSetting.openid_connect_allow_association_change = false"
- exec: rails r "SiteSetting.openid_connect_rp_initiated_logout = true" - exec: rails r "SiteSetting.openid_connect_rp_initiated_logout = true"

View File

@ -86,20 +86,20 @@ ESPOCRM_CONFIG_OIDC_ALLOW_ADMIN_USER=true
ESPOCRM_CONFIG_AUTHENTICATION_METHOD=Oidc ESPOCRM_CONFIG_AUTHENTICATION_METHOD=Oidc
ESPOCRM_CONFIG_OIDC_FALLBACK=false # set true if you want LDAP as fallback ESPOCRM_CONFIG_OIDC_FALLBACK=false # set true if you want LDAP as fallback
ESPOCRM_CONFIG_OIDC_CLIENT_ID={{ oidc.client.id }} ESPOCRM_CONFIG_OIDC_CLIENT_ID={{ OIDC.CLIENT.ID }}
ESPOCRM_CONFIG_OIDC_CLIENT_SECRET={{ oidc.client.secret }} ESPOCRM_CONFIG_OIDC_CLIENT_SECRET={{ OIDC.CLIENT.SECRET }}
ESPOCRM_CONFIG_OIDC_AUTHORIZATION_ENDPOINT={{ oidc.client.authorize_url }} ESPOCRM_CONFIG_OIDC_AUTHORIZATION_ENDPOINT={{ OIDC.CLIENT.AUTHORIZE_URL }}
ESPOCRM_CONFIG_OIDC_TOKEN_ENDPOINT={{ oidc.client.token_url }} ESPOCRM_CONFIG_OIDC_TOKEN_ENDPOINT={{ OIDC.CLIENT.TOKEN_URL }}
ESPOCRM_CONFIG_OIDC_USER_INFO_ENDPOINT={{ oidc.client.user_info_url }} ESPOCRM_CONFIG_OIDC_USER_INFO_ENDPOINT={{ OIDC.CLIENT.USER_INFO_URL }}
ESPOCRM_CONFIG_OIDC_JWKS_ENDPOINT={{ oidc.client.certs }} ESPOCRM_CONFIG_OIDC_JWKS_ENDPOINT={{ OIDC.CLIENT.CERTS }}
ESPOCRM_CONFIG_OIDC_AUTHORIZATION_REDIRECT_URI={{ espocrm_url }}/oidc/callback ESPOCRM_CONFIG_OIDC_AUTHORIZATION_REDIRECT_URI={{ espocrm_url }}/oidc/callback
#ESPOCRM_CONFIG_OIDC_SCOPES=openid,profile,email # Defined in main.yml #ESPOCRM_CONFIG_OIDC_SCOPES=openid,profile,email # Defined in main.yml
ESPOCRM_CONFIG_OIDC_CREATE_USER=true ESPOCRM_CONFIG_OIDC_CREATE_USER=true
ESPOCRM_CONFIG_OIDC_SYNC=true ESPOCRM_CONFIG_OIDC_SYNC=true
ESPOCRM_CONFIG_OIDC_USERNAME_CLAIM={{oidc.attributes.username}} ESPOCRM_CONFIG_OIDC_USERNAME_CLAIM={{OIDC.ATTRIBUTES.USERNAME}}
# ESPOCRM_CONFIG_OIDC_SYNC_TEAMS=true # ESPOCRM_CONFIG_OIDC_SYNC_TEAMS=true
# ESPOCRM_CONFIG_OIDC_GROUP_CLAIM=group # ESPOCRM_CONFIG_OIDC_GROUP_CLAIM=group
{% endif %} {% endif %}

View File

@ -3,7 +3,7 @@
shell: | shell: |
docker exec -i --user {{ gitea_user }} {{ gitea_container }} \ docker exec -i --user {{ gitea_user }} {{ gitea_container }} \
gitea admin auth list \ gitea admin auth list \
| awk -v name="{{ oidc.button_text }}" '$0 ~ name {print $1; exit}' | awk -v name="{{ OIDC.BUTTON_TEXT }}" '$0 ~ name {print $1; exit}'
args: args:
chdir: "{{ docker_compose.directories.instance }}" chdir: "{{ docker_compose.directories.instance }}"
register: oidc_source_id_raw register: oidc_source_id_raw

View File

@ -3,10 +3,10 @@
docker exec -i --user {{ gitea_user }} {{ gitea_container }} \ docker exec -i --user {{ gitea_user }} {{ gitea_container }} \
gitea admin auth add-oauth \ gitea admin auth add-oauth \
--provider openidConnect \ --provider openidConnect \
--name "{{ oidc.button_text }}" \ --name "{{ OIDC.BUTTON_TEXT }}" \
--key "{{ oidc.client.id }}" \ --key "{{ OIDC.CLIENT.ID }}" \
--secret "{{ oidc.client.secret }}" \ --secret "{{ OIDC.CLIENT.SECRET }}" \
--auto-discover-url "{{ oidc.client.discovery_document }}" \ --auto-discover-url "{{ OIDC.CLIENT.DISCOVERY_DOCUMENT }}" \
--scopes "openid profile email" --scopes "openid profile email"
args: args:
chdir: "{{ docker_compose.directories.instance }}" chdir: "{{ docker_compose.directories.instance }}"
@ -18,7 +18,7 @@
docker exec -i --user {{ gitea_user }} {{ gitea_container }} \ docker exec -i --user {{ gitea_user }} {{ gitea_container }} \
/app/gitea/gitea admin auth list \ /app/gitea/gitea admin auth list \
| tail -n +2 \ | tail -n +2 \
| grep -F "{{ oidc.button_text }}" \ | grep -F "{{ OIDC.BUTTON_TEXT }}" \
| awk '{print $1; exit}' | awk '{print $1; exit}'
args: args:
chdir: "{{ docker_compose.directories.instance }}" chdir: "{{ docker_compose.directories.instance }}"
@ -38,10 +38,10 @@
gitea admin auth update-oauth \ gitea admin auth update-oauth \
--id {{ oidc_source_id }}\ --id {{ oidc_source_id }}\
--provider openidConnect \ --provider openidConnect \
--name "{{ oidc.button_text }}" \ --name "{{ OIDC.BUTTON_TEXT }}" \
--key "{{ oidc.client.id }}" \ --key "{{ OIDC.CLIENT.ID }}" \
--secret "{{ oidc.client.secret }}" \ --secret "{{ OIDC.CLIENT.SECRET }}" \
--auto-discover-url "{{ oidc.client.discovery_document }}" \ --auto-discover-url "{{ OIDC.CLIENT.DISCOVERY_DOCUMENT }}" \
--scopes "openid profile email" --scopes "openid profile email"
args: args:
chdir: "{{ docker_compose.directories.instance }}" chdir: "{{ docker_compose.directories.instance }}"

View File

@ -832,7 +832,7 @@
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "desktop-secret", "clientAuthenticatorType": "desktop-secret",
"secret": "{{ oidc.client.secret }}", "secret": "{{ OIDC.CLIENT.SECRET }}",
{# The following line should be covered by 02_update_client_redirects.yml #} {# The following line should be covered by 02_update_client_redirects.yml #}
"redirectUris": {{ domains | redirect_uris(applications, WEB_PROTOCOL) | tojson }}, "redirectUris": {{ domains | redirect_uris(applications, WEB_PROTOCOL) | tojson }},
"webOrigins": [ "webOrigins": [
@ -1261,7 +1261,7 @@
"id.token.claim": "true", "id.token.claim": "true",
"lightweight.claim": "false", "lightweight.claim": "false",
"access.token.claim": "true", "access.token.claim": "true",
"claim.name": "{{ oidc.claims.groups }}" "claim.name": "{{ OIDC.CLAIMS.GROUPS }}"
} }
} }
] ]
@ -1520,7 +1520,7 @@
"user.attribute": "username", "user.attribute": "username",
"id.token.claim": "true", "id.token.claim": "true",
"access.token.claim": "true", "access.token.claim": "true",
"claim.name": "{{oidc.attributes.username}}", "claim.name": "{{OIDC.ATTRIBUTES.USERNAME}}",
"jsonType.label": "String" "jsonType.label": "String"
} }
}, },

View File

@ -5,7 +5,7 @@ database_type: "postgres"
# Keycloak # Keycloak
keycloak_container: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.name') }}" # Name of the keycloak docker container keycloak_container: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.name') }}" # Name of the keycloak docker container
keycloak_docker_import_directory: "/opt/keycloak/data/import/" # Directory in which keycloak import files are placed in the running docker container keycloak_docker_import_directory: "/opt/keycloak/data/import/" # Directory in which keycloak import files are placed in the running docker container
keycloak_realm: "{{ oidc.client.realm }}" # This is the name of the default realm which is used by the applications keycloak_realm: "{{ OIDC.CLIENT.ISSUER_URL }}" # This is the name of the default realm which is used by the applications
keycloak_master_api_user: "{{ applications | get_app_conf(application_id, 'users.administrator') }}" # Master Administrator keycloak_master_api_user: "{{ applications | get_app_conf(application_id, 'users.administrator') }}" # Master Administrator
keycloak_master_api_user_name: "{{ keycloak_master_api_user.username }}" # Master Administrator Username keycloak_master_api_user_name: "{{ keycloak_master_api_user.username }}" # Master Administrator Username
keycloak_master_api_user_password: "{{ keycloak_master_api_user.password }}" # Master Administrator Password keycloak_master_api_user_password: "{{ keycloak_master_api_user.password }}" # Master Administrator Password
@ -17,7 +17,7 @@ keycloak_image: "{{ applications | get_app_conf(application_
keycloak_version: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.version') }}" # Keycloak docker version keycloak_version: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.version') }}" # Keycloak docker version
keycloak_debug_enabled: "{{ MODE_DEBUG }}" keycloak_debug_enabled: "{{ MODE_DEBUG }}"
keycloak_redirect_features: ["features.oauth2","features.oidc"] keycloak_redirect_features: ["features.oauth2","features.oidc"]
keycloak_client_id: "{{ oidc.client.id }}" keycloak_client_id: "{{ OIDC.CLIENT.ID }}"
keycloak_ldap_component_name: "{{ ldap.server.domain }}" # Name of the LDAP User Federation component in Keycloak (as shown in UI) keycloak_ldap_component_name: "{{ ldap.server.domain }}" # Name of the LDAP User Federation component in Keycloak (as shown in UI)
keycloak_import_realm: "{{ applications | get_app_conf(application_id, 'actions.import_realm') }}" # Activate realm import keycloak_import_realm: "{{ applications | get_app_conf(application_id, 'actions.import_realm') }}" # Activate realm import
keycloak_update_ldap_bind: "{{ applications | get_app_conf(application_id, 'actions.update_ldap_bind') }}" # Toggle the LDAP bind update step keycloak_update_ldap_bind: "{{ applications | get_app_conf(application_id, 'actions.update_ldap_bind') }}" # Toggle the LDAP bind update step

View File

@ -35,9 +35,9 @@
oidc_settings_json: >- oidc_settings_json: >-
{{ { {{ {
"enabled": True, "enabled": True,
"client_id": oidc.client.id, "client_id": OIDC.CLIENT.ID,
"provider_url": oidc.client.issuer_url, "provider_url": OIDC.CLIENT.ISSUER_URL,
"client_secret": oidc.client.secret "client_secret": OIDC.CLIENT.SECRET
} | to_json }} } | to_json }}
- name: Update administrator email and password login in Listmonk - name: Update administrator email and password login in Listmonk

View File

@ -24,9 +24,9 @@ LISTMONK_SETTINGS:
value: >- value: >-
{{ { {{ {
"enabled": True, "enabled": True,
"client_id": oidc.client.id, "client_id": OIDC.CLIENT.ID,
"provider_url": oidc.client.issuer_url, "provider_url": OIDC.CLIENT.ISSUER_URL,
"client_secret": oidc.client.secret "client_secret": OIDC.CLIENT.SECRET
} | to_json }} } | to_json }}
when: applications | get_app_conf(application_id, 'features.oidc', False) when: applications | get_app_conf(application_id, 'features.oidc', False)

View File

@ -1,10 +1,23 @@
- name: Ensure MAILU_HOSTNAMES is a list with max 1 entry
ansible.builtin.assert:
that:
- MAILU_HOSTNAMES is iterable
- MAILU_HOSTNAMES is sequence
- MAILU_HOSTNAMES | length <= 1
fail_msg: "MAILU_HOSTNAMES must be a list with at most one entry (only one host is supported). You can set the other ones as alias."
success_msg: "MAILU_HOSTNAMES is valid."
- name: "Mailu Docker and Webserver Setup"
block:
- name: "load docker, db and proxy for {{ application_id }}" - name: "load docker, db and proxy for {{ application_id }}"
include_role: include_role:
name: cmp-db-docker-proxy name: cmp-db-docker-proxy
- name: "Include the srv-proxy-6-6-tls-deploy role" - name: "Include the sys-svc-cert-sync-docker role"
include_role: include_role:
name: srv-proxy-6-6-tls-deploy name: sys-svc-cert-sync-docker
vars:
domain: "{{ MAILU_HOSTNAME }}"
- name: Flush docker service handlers - name: Flush docker service handlers
meta: flush_handlers meta: flush_handlers
@ -12,10 +25,8 @@
- name: "Create Mailu accounts" - name: "Create Mailu accounts"
include_tasks: 02_create-mailu-user.yml include_tasks: 02_create-mailu-user.yml
vars: vars:
mailu_compose_dir: "{{ docker_compose.directories.instance }}" MAILU_DOCKER_DIR: "{{ docker_compose.directories.instance }}"
mailu_domain: "{{ PRIMARY_DOMAIN }}"
mailu_api_base_url: "http://127.0.0.1:8080/api/v1" mailu_api_base_url: "http://127.0.0.1:8080/api/v1"
mailu_global_api_token: "{{ applications | get_app_conf(application_id, 'credentials.api_token') }}"
mailu_action: >- mailu_action: >-
{{ {{
( (

View File

@ -1,9 +1,9 @@
- name: "Ensure Mailu user '{{ mailu_user_key }};{{ mailu_user_name }}@{{ mailu_domain }}'' exists" - name: "Ensure Mailu user '{{ mailu_user_key }};{{ mailu_user_name }}@{{ MAILU_DOMAIN }}'' exists"
command: > command: >
docker compose exec admin flask mailu {{ mailu_action }} docker compose exec admin flask mailu {{ mailu_action }}
{{ mailu_user_name }} {{ mailu_domain }} '{{ mailu_password }}' {{ mailu_user_name }} {{ MAILU_DOMAIN }} '{{ mailu_password }}'
args: args:
chdir: "{{ mailu_compose_dir }}" chdir: "{{ MAILU_DOCKER_DIR }}"
register: mailu_user_result register: mailu_user_result
failed_when: > failed_when: >
mailu_user_result.rc != 0 and mailu_user_result.rc != 0 and
@ -15,12 +15,12 @@
when: "'mail-bot' in item.value.roles or 'administrator' in item.value.roles" when: "'mail-bot' in item.value.roles or 'administrator' in item.value.roles"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "Change password for user '{{ mailu_user_key }};{{ mailu_user_name }}@{{ mailu_domain }}'" - name: "Change password for user '{{ mailu_user_key }};{{ mailu_user_name }}@{{ MAILU_DOMAIN }}'"
command: > command: >
docker compose exec admin flask mailu password docker compose exec admin flask mailu password
{{ mailu_user_name }} {{ mailu_domain }} '{{ mailu_password }}' {{ mailu_user_name }} {{ MAILU_DOMAIN }} '{{ mailu_password }}'
args: args:
chdir: "{{ mailu_compose_dir }}" chdir: "{{ MAILU_DOCKER_DIR }}"
when: "'mail-bot' in item.value.roles or 'administrator' in item.value.roles" when: "'mail-bot' in item.value.roles or 'administrator' in item.value.roles"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

View File

@ -3,9 +3,9 @@
command: >- command: >-
docker compose exec -T admin \ docker compose exec -T admin \
curl -s -X GET {{ mailu_api_base_url }}/token \ curl -s -X GET {{ mailu_api_base_url }}/token \
-H "Authorization: Bearer {{ mailu_global_api_token }}" -H "Authorization: Bearer {{ MAILU_API_TOKEN }}"
args: args:
chdir: "{{ mailu_compose_dir }}" chdir: "{{ MAILU_DOCKER_DIR }}"
register: mailu_tokens_cli register: mailu_tokens_cli
changed_when: false changed_when: false
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
@ -25,9 +25,9 @@
command: >- command: >-
docker compose exec -T admin \ docker compose exec -T admin \
curl -s -X DELETE {{ mailu_api_base_url }}/token/{{ mailu_user_existing_token.id }} \ curl -s -X DELETE {{ mailu_api_base_url }}/token/{{ mailu_user_existing_token.id }} \
-H "Authorization: Bearer {{ mailu_global_api_token }}" -H "Authorization: Bearer {{ MAILU_API_TOKEN }}"
args: args:
chdir: "{{ mailu_compose_dir }}" chdir: "{{ MAILU_DOCKER_DIR }}"
when: when:
- users[mailu_user_key].mailu_token is not defined - users[mailu_user_key].mailu_token is not defined
- mailu_user_existing_token is not none - mailu_user_existing_token is not none
@ -40,7 +40,7 @@
command: >- command: >-
docker compose exec -T admin \ docker compose exec -T admin \
curl -s -X POST {{ mailu_api_base_url }}/token \ curl -s -X POST {{ mailu_api_base_url }}/token \
-H "Authorization: Bearer {{ mailu_global_api_token }}" \ -H "Authorization: Bearer {{ MAILU_API_TOKEN }}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{{ { -d '{{ {
"comment": mailu_user_key ~ " - ansible.infinito", "comment": mailu_user_key ~ " - ansible.infinito",
@ -48,7 +48,7 @@
"ip": mailu_token_ip "ip": mailu_token_ip
} | to_json }}' } | to_json }}'
args: args:
chdir: "{{ mailu_compose_dir }}" chdir: "{{ MAILU_DOCKER_DIR }}"
when: users[mailu_user_key].mailu_token is not defined when: users[mailu_user_key].mailu_token is not defined
register: mailu_token_creation register: mailu_token_creation
changed_when: mailu_token_creation.rc == 0 changed_when: mailu_token_creation.rc == 0

View File

@ -1,16 +1,14 @@
- name: "Load Mailu DNS variables"
include_vars: vars/mailu-dns.yml
- name: Generate DKIM public key - name: Generate DKIM public key
include_tasks: 05_generate-and-read-dkim.yml include_tasks: 05_generate-and-read-dkim.yml
- name: "Set A record for mail server" # A/AAAA record for the mail host in the **Hostname Zone**
- name: "Set A record for Mailu host"
community.general.cloudflare_dns: community.general.cloudflare_dns:
api_token: "{{ cloudflare_record_api_token }}" api_token: "{{ MAILU_CLOUDFLARE_API_TOKEN }}"
zone: "{{ mailu_dns_zone }}" zone: "{{ MAILU_HOSTNAME_DNS_ZONE }}"
type: A type: A
name: "{{ domain }}" name: "{{ MAILU_HOSTNAME }}" # Fully Qualified Domain Name of the mail host
content: "{{ mailu_dns_ip }}" content: "{{ MAILU_IP4_PUBLIC }}"
proxied: false proxied: false
ttl: 1 ttl: 1
state: present state: present
@ -18,13 +16,29 @@
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}" poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "Set AAAA record for Mailu host"
community.general.cloudflare_dns:
api_token: "{{ MAILU_CLOUDFLARE_API_TOKEN }}"
zone: "{{ MAILU_HOSTNAME_DNS_ZONE }}"
type: AAAA
name: "{{ MAILU_HOSTNAME }}"
content: "{{ MAILU_IP6_PUBLIC }}"
proxied: false
ttl: 1
state: present
when: MAILU_IP6_PUBLIC is defined and MAILU_IP6_PUBLIC | length > 0
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
# Autoconfig CNAME record in the **Mail Domain Zone**
- name: "Set CNAME record for autoconfig" - name: "Set CNAME record for autoconfig"
community.general.cloudflare_dns: community.general.cloudflare_dns:
api_token: "{{ cloudflare_record_api_token }}" api_token: "{{ MAILU_CLOUDFLARE_API_TOKEN }}"
zone: "{{ mailu_dns_zone }}" zone: "{{ MAILU_DOMAIN_DNS_ZONE }}"
type: CNAME type: CNAME
name: "autoconfig.{{ mailu_dns_zone }}" name: "autoconfig.{{ MAILU_DOMAIN_DNS_ZONE }}"
value: "{{ domain }}" value: "{{ MAILU_HOSTNAME }}" # Points to the Mailu host FQDN
proxied: false proxied: false
ttl: 1 ttl: 1
state: present state: present
@ -32,13 +46,14 @@
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}" poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
# MX record in the **Mail Domain Zone**
- name: "Set MX record" - name: "Set MX record"
community.general.cloudflare_dns: community.general.cloudflare_dns:
api_token: "{{ cloudflare_record_api_token }}" api_token: "{{ MAILU_CLOUDFLARE_API_TOKEN }}"
zone: "{{ mailu_dns_zone }}" zone: "{{ MAILU_DOMAIN_DNS_ZONE }}"
type: MX type: MX
name: "{{ mailu_dns_zone }}" name: "{{ MAILU_DOMAIN }}" # Root mail domain
value: "{{ domain }}" value: "{{ MAILU_HOSTNAME }}" # Points to the Mailu host
priority: 10 priority: 10
ttl: 1 ttl: 1
state: present state: present
@ -46,60 +61,62 @@
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}" poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
# SRV records in the **Mail Domain Zone**
- name: "Set SRV records" - name: "Set SRV records"
community.general.cloudflare_dns: community.general.cloudflare_dns:
api_token: "{{ cloudflare_record_api_token }}" api_token: "{{ MAILU_CLOUDFLARE_API_TOKEN }}"
zone: "{{ mailu_dns_zone }}" zone: "{{ MAILU_DOMAIN_DNS_ZONE }}"
type: SRV type: SRV
service: "_{{ item.key }}" service: "_{{ item.key }}"
proto: "_tcp" proto: "_tcp"
priority: "{{ item.value.priority }}" priority: "{{ item.value.priority }}"
weight: "{{ item.value.weight }}" weight: "{{ item.value.weight }}"
port: "{{ item.value.port }}" port: "{{ item.value.port }}"
value: "{{ domain }}" value: "{{ MAILU_HOSTNAME }}" # Target = Mailu host FQDN
ttl: 1 ttl: 1
state: present state: present
loop: "{{ mailu_dns_srv_records | dict2items }}" name: "{{ MAILU_DOMAIN }}"
loop: "{{ MAILU_DNS_SRV_RECORDS | dict2items }}"
ignore_errors: true ignore_errors: true
#register: srv_result
#failed_when: srv_result.rc != 0 and ("An identical record already exists" not in srv_result.stdout)
#changed_when: srv_result.rc == 0 and ("An identical record already exists" not in srv_result.stdout)
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}" async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}" poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
# SPF TXT record in the **Mail Domain Zone**
- name: "Set SPF TXT record" - name: "Set SPF TXT record"
community.general.cloudflare_dns: community.general.cloudflare_dns:
api_token: "{{ cloudflare_record_api_token }}" api_token: "{{ MAILU_CLOUDFLARE_API_TOKEN }}"
zone: "{{ mailu_dns_zone }}" zone: "{{ MAILU_DOMAIN_DNS_ZONE }}"
type: TXT type: TXT
name: "{{ mailu_dns_zone }}" name: "{{ MAILU_DOMAIN }}"
value: "v=spf1 mx a:{{ domain }} ~all" value: "v=spf1 mx a:{{ MAILU_HOSTNAME }} ~all"
ttl: 1 ttl: 1
state: present state: present
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}" async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}" poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
# DMARC TXT record in the **Mail Domain Zone**
- name: "Set DMARC TXT record" - name: "Set DMARC TXT record"
community.general.cloudflare_dns: community.general.cloudflare_dns:
api_token: "{{ cloudflare_record_api_token }}" api_token: "{{ MAILU_CLOUDFLARE_API_TOKEN }}"
zone: "{{ mailu_dns_zone }}" zone: "{{ MAILU_DOMAIN_DNS_ZONE }}"
type: TXT type: TXT
name: "_dmarc.{{ mailu_dns_zone }}" name: "_dmarc.{{ MAILU_DOMAIN_DNS_ZONE }}"
value: "v=DMARC1; p=reject; ruf=mailto:{{ mailu_dmarc_ruf }}; adkim=s; aspf=s" value: "v=DMARC1; p=reject; ruf=mailto:{{ MAILU_DMARC_RUF }}; adkim=s; aspf=s"
ttl: 1 ttl: 1
state: present state: present
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}" async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}" poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
# DKIM TXT record in the **Mail Domain Zone**
- name: "Set DKIM TXT record" - name: "Set DKIM TXT record"
community.general.cloudflare_dns: community.general.cloudflare_dns:
api_token: "{{ cloudflare_record_api_token }}" api_token: "{{ MAILU_CLOUDFLARE_API_TOKEN }}"
zone: "{{ mailu_dns_zone }}" zone: "{{ MAILU_DOMAIN_DNS_ZONE }}"
type: TXT type: TXT
name: "dkim._domainkey.{{ mailu_dns_zone }}" name: "dkim._domainkey.{{ MAILU_DOMAIN_DNS_ZONE }}"
value: "{{ mailu_dkim_public_key }}" value: "{{ mailu_dkim_public_key }}"
ttl: 1 ttl: 1
state: present state: present

View File

@ -1,7 +1,7 @@
- name: Check if DKIM private key file exists in the antispam container - name: Check if DKIM private key file exists in the antispam container
command: > command: >
docker compose exec -T antispam docker compose exec -T antispam
test -f {{ mailu_dkim_key_path }} test -f {{ MAILU_DKIM_KEY_PATH }}
register: dkim_key_file_stat register: dkim_key_file_stat
failed_when: false failed_when: false
changed_when: false changed_when: false
@ -11,7 +11,7 @@
- name: Generate DKIM key - name: Generate DKIM key
command: > command: >
docker compose exec -T antispam docker compose exec -T antispam
rspamadm dkim_keygen -s dkim -d {{ applications | get_app_conf(application_id, 'domain', True) }} -k {{ mailu_dkim_key_path }} rspamadm dkim_keygen -s dkim -d {{ MAILU_DOMAIN }} -k {{ MAILU_DKIM_KEY_PATH }}
register: dkim_keygen_output register: dkim_keygen_output
when: dkim_key_file_stat.rc != 0 when: dkim_key_file_stat.rc != 0
args: args:
@ -21,7 +21,7 @@
- name: Fetch DKIM private key from antispam container - name: Fetch DKIM private key from antispam container
shell: > shell: >
docker compose exec -T antispam docker compose exec -T antispam
cat {{ mailu_dkim_key_path }} cat {{ MAILU_DKIM_KEY_PATH }}
args: args:
chdir: "{{ docker_compose.directories.instance }}" chdir: "{{ docker_compose.directories.instance }}"
register: dkim_priv_content register: dkim_priv_content

View File

@ -2,26 +2,26 @@
# Core services # Core services
resolver: resolver:
image: {{docker_source}}/unbound:{{ mailu_version }} image: {{ MAILU_DOCKER_FLAVOR }}/unbound:{{ MAILU_VERSION }}
container_name: {{mailu_name}}_resolver container_name: {{ MAILU_CONTAINER }}_resolver
{% include 'roles/docker-container/templates/base.yml.j2' %} {% include 'roles/docker-container/templates/base.yml.j2' %}
{% include 'roles/docker-container/templates/networks.yml.j2' %} {% include 'roles/docker-container/templates/networks.yml.j2' %}
ipv4_address: {{networks.local['web-app-mailu'].dns}} ipv4_address: {{ MAILU_DNS_RESOLVER }}
front: front:
container_name: {{mailu_name}}_front container_name: {{ MAILU_CONTAINER }}_front
image: {{docker_source}}/nginx:{{ mailu_version }} image: {{ MAILU_DOCKER_FLAVOR }}/nginx:{{ MAILU_VERSION }}
{% include 'roles/docker-container/templates/base.yml.j2' %} {% include 'roles/docker-container/templates/base.yml.j2' %}
ports: ports:
- "127.0.0.1:{{ports.localhost.http[application_id]}}:80" - "127.0.0.1:{{ports.localhost.http[application_id]}}:80"
- "{{ networks.internet.ip4 }}:25:25" - "{{ MAILU_IP4_PUBLIC }}:25:25"
- "{{ networks.internet.ip4 }}:465:465" - "{{ MAILU_IP4_PUBLIC }}:465:465"
- "{{ networks.internet.ip4 }}:587:587" - "{{ MAILU_IP4_PUBLIC }}:587:587"
- "{{ networks.internet.ip4 }}:110:110" - "{{ MAILU_IP4_PUBLIC }}:110:110"
- "{{ networks.internet.ip4 }}:995:995" - "{{ MAILU_IP4_PUBLIC }}:995:995"
- "{{ networks.internet.ip4 }}:143:143" - "{{ MAILU_IP4_PUBLIC }}:143:143"
- "{{ networks.internet.ip4 }}:993:993" - "{{ MAILU_IP4_PUBLIC }}:993:993"
- "{{ networks.internet.ip4 }}:4190:4190" - "{{ MAILU_IP4_PUBLIC }}:4190:4190"
volumes: volumes:
- "{{docker_compose.directories.volumes}}overrides/nginx:/overrides:ro" - "{{docker_compose.directories.volumes}}overrides/nginx:/overrides:ro"
- "{{ cert_mount_directory }}:/certs:ro" - "{{ cert_mount_directory }}:/certs:ro"
@ -32,11 +32,11 @@
webmail: webmail:
radicale: radicale:
dns: dns:
- {{networks.local['web-app-mailu'].dns}} - {{ MAILU_DNS_RESOLVER }}
admin: admin:
container_name: {{mailu_name}}_admin container_name: {{ MAILU_CONTAINER }}_admin
image: {{docker_source}}/admin:{{ mailu_version }} image: {{ MAILU_DOCKER_FLAVOR }}/admin:{{ MAILU_VERSION }}
{% include 'roles/docker-container/templates/base.yml.j2' %} {% include 'roles/docker-container/templates/base.yml.j2' %}
volumes: volumes:
- "admin_data:/data" - "admin_data:/data"
@ -47,12 +47,12 @@
front: front:
condition: service_started condition: service_started
dns: dns:
- {{networks.local['web-app-mailu'].dns}} - {{ MAILU_DNS_RESOLVER }}
{% include 'roles/docker-container/templates/networks.yml.j2' %} {% include 'roles/docker-container/templates/networks.yml.j2' %}
imap: imap:
container_name: {{mailu_name}}_imap container_name: {{ MAILU_CONTAINER }}_imap
image: {{docker_source}}/dovecot:{{ mailu_version }} image: {{ MAILU_DOCKER_FLAVOR }}/dovecot:{{ MAILU_VERSION }}
{% include 'roles/docker-container/templates/base.yml.j2' %} {% include 'roles/docker-container/templates/base.yml.j2' %}
volumes: volumes:
- "dovecot_mail:/mail" - "dovecot_mail:/mail"
@ -61,12 +61,12 @@
- front - front
- resolver - resolver
dns: dns:
- {{networks.local['web-app-mailu'].dns}} - {{ MAILU_DNS_RESOLVER }}
{% include 'roles/docker-container/templates/networks.yml.j2' %} {% include 'roles/docker-container/templates/networks.yml.j2' %}
smtp: smtp:
container_name: {{mailu_name}}_smtp container_name: {{ MAILU_CONTAINER }}_smtp
image: {{docker_source}}/postfix:{{ mailu_version }} image: {{ MAILU_DOCKER_FLAVOR }}/postfix:{{ MAILU_VERSION }}
{% include 'roles/docker-container/templates/base.yml.j2' %} {% include 'roles/docker-container/templates/base.yml.j2' %}
volumes: volumes:
- "{{docker_compose.directories.volumes}}overrides:/overrides:ro" - "{{docker_compose.directories.volumes}}overrides:/overrides:ro"
@ -75,24 +75,24 @@
- front - front
- resolver - resolver
dns: dns:
- {{networks.local['web-app-mailu'].dns}} - {{ MAILU_DNS_RESOLVER }}
{% include 'roles/docker-container/templates/networks.yml.j2' %} {% include 'roles/docker-container/templates/networks.yml.j2' %}
oletools: oletools:
container_name: {{mailu_name}}_oletools container_name: {{ MAILU_CONTAINER }}_oletools
image: {{docker_source}}/oletools:{{ mailu_version }} image: {{ MAILU_DOCKER_FLAVOR }}/oletools:{{ MAILU_VERSION }}
hostname: oletools hostname: oletools
restart: {{ DOCKER_RESTART_POLICY }} restart: {{ DOCKER_RESTART_POLICY }}
depends_on: depends_on:
- resolver - resolver
dns: dns:
- {{networks.local['web-app-mailu'].dns}} - {{ MAILU_DNS_RESOLVER }}
{% include 'roles/docker-container/templates/networks.yml.j2' %} {% include 'roles/docker-container/templates/networks.yml.j2' %}
noinet: noinet:
antispam: antispam:
container_name: {{mailu_name}}_antispam container_name: {{ MAILU_CONTAINER }}_antispam
image: {{docker_source}}/rspamd:{{ mailu_version }} image: {{ MAILU_DOCKER_FLAVOR }}/rspamd:{{ MAILU_VERSION }}
{% include 'roles/docker-container/templates/base.yml.j2' %} {% include 'roles/docker-container/templates/base.yml.j2' %}
volumes: volumes:
- "filter:/var/lib/rspamd" - "filter:/var/lib/rspamd"
@ -104,14 +104,14 @@
- antivirus - antivirus
- resolver - resolver
dns: dns:
- {{networks.local['web-app-mailu'].dns}} - {{ MAILU_DNS_RESOLVER }}
{% include 'roles/docker-container/templates/networks.yml.j2' %} {% include 'roles/docker-container/templates/networks.yml.j2' %}
noinet: noinet:
# Optional services # Optional services
antivirus: antivirus:
container_name: {{mailu_name}}_antivirus container_name: {{ MAILU_CONTAINER }}_antivirus
image: clamav/clamav-debian:latest image: clamav/clamav-debian:latest
{% include 'roles/docker-container/templates/base.yml.j2' %} {% include 'roles/docker-container/templates/base.yml.j2' %}
volumes: volumes:
@ -119,25 +119,25 @@
depends_on: depends_on:
- resolver - resolver
dns: dns:
- {{networks.local['web-app-mailu'].dns}} - {{ MAILU_DNS_RESOLVER }}
{% include 'roles/docker-container/templates/networks.yml.j2' %} {% include 'roles/docker-container/templates/networks.yml.j2' %}
webdav: webdav:
container_name: {{mailu_name}}_webdav container_name: {{ MAILU_CONTAINER }}_webdav
image: {{docker_source}}/radicale:{{ mailu_version }} image: {{ MAILU_DOCKER_FLAVOR }}/radicale:{{ MAILU_VERSION }}
{% include 'roles/docker-container/templates/base.yml.j2' %} {% include 'roles/docker-container/templates/base.yml.j2' %}
volumes: volumes:
- "webdav_data:/data" - "webdav_data:/data"
depends_on: depends_on:
- resolver - resolver
dns: dns:
- {{networks.local['web-app-mailu'].dns}} - {{ MAILU_DNS_RESOLVER }}
{% include 'roles/docker-container/templates/networks.yml.j2' %} {% include 'roles/docker-container/templates/networks.yml.j2' %}
radicale: radicale:
fetchmail: fetchmail:
container_name: {{mailu_name}}_fetchmail container_name: {{ MAILU_CONTAINER }}_fetchmail
image: {{docker_source}}/fetchmail:{{ mailu_version }} image: {{ MAILU_DOCKER_FLAVOR }}/fetchmail:{{ MAILU_VERSION }}
volumes: volumes:
- "admin_data:/data" - "admin_data:/data"
{% include 'roles/docker-container/templates/base.yml.j2' %} {% include 'roles/docker-container/templates/base.yml.j2' %}
@ -147,12 +147,12 @@
- imap - imap
- resolver - resolver
dns: dns:
- {{networks.local['web-app-mailu'].dns}} - {{ MAILU_DNS_RESOLVER }}
{% include 'roles/docker-container/templates/networks.yml.j2' %} {% include 'roles/docker-container/templates/networks.yml.j2' %}
webmail: webmail:
container_name: {{mailu_name}}_webmail container_name: {{ MAILU_CONTAINER }}_webmail
image: {{docker_source}}/webmail:{{ mailu_version }} image: {{ MAILU_DOCKER_FLAVOR }}/webmail:{{ MAILU_VERSION }}
{% include 'roles/docker-container/templates/base.yml.j2' %} {% include 'roles/docker-container/templates/base.yml.j2' %}
volumes: volumes:
- "webmail_data:/data" - "webmail_data:/data"
@ -162,25 +162,25 @@
- front - front
- resolver - resolver
dns: dns:
- {{networks.local['web-app-mailu'].dns}} - {{ MAILU_DNS_RESOLVER }}
{% include 'roles/docker-container/templates/networks.yml.j2' %} {% include 'roles/docker-container/templates/networks.yml.j2' %}
webmail: webmail:
{% include 'roles/docker-compose/templates/volumes.yml.j2' %} {% include 'roles/docker-compose/templates/volumes.yml.j2' %}
smtp_queue: smtp_queue:
name: {{ mailu_smtp_queue }} name: {{ MAILU_SMTP_QUEUE_VOLUME }}
admin_data: admin_data:
name: {{ mailu_admin_data }} name: {{ MAILU_ADMIN_DATA_VOLUME }}
webdav_data: webdav_data:
name: {{ mailu_webdav_data }} name: {{ MAILU_WEBDAV_DATA }}
webmail_data: webmail_data:
name: {{ mailu_webmail_data }} name: {{ MAILU_WEBMAIL_DATA }}
filter: filter:
name: {{ mailu_filter }} name: {{ MAILU_FILTER_VOLUME }}
dkim: dkim:
name: {{ mailu_dkim }} name: {{ MAILU_DKIM_VOLUME }}
dovecot_mail: dovecot_mail:
name: {{ mailu_dovecot_mail }} name: {{ MAILU_DOVECOT_MAIL_VOLUME }}
{% include 'roles/docker-compose/templates/networks.yml.j2' %} {% include 'roles/docker-compose/templates/networks.yml.j2' %}
radicale: radicale:

View File

@ -11,16 +11,16 @@
LD_PRELOAD=/usr/lib/libhardened_malloc.so LD_PRELOAD=/usr/lib/libhardened_malloc.so
# Set to a randomly generated 16 bytes string # Set to a randomly generated 16 bytes string
SECRET_KEY={{applications | get_app_conf(application_id,'credentials.secret_key')}} SECRET_KEY={{ MAILU_SECRET_KEY }}
# Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!) # Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!)
SUBNET={{networks.local['web-app-mailu'].subnet}} SUBNET={{ MAILU_SUBNET }}
# Main mail domain # Main mail domain
DOMAIN={{ applications | get_app_conf(application_id,'domain') }} DOMAIN={{ MAILU_DOMAIN }}
# Hostnames for this server, separated with comas # Hostnames for this server, separated with comas
HOSTNAMES={{ domains[application_id] | join(',') }} HOSTNAMES={{ MAILU_HOSTNAMES | join(',') }}
# Postmaster local part (will append the main mail domain) # Postmaster local part (will append the main mail domain)
POSTMASTER=admin POSTMASTER=admin
@ -105,7 +105,7 @@ WEB_WEBMAIL=/webmail
SITENAME=Mailservices SITENAME=Mailservices
# Linked Website URL # Linked Website URL
WEBSITE={{ domains | get_url(application_id, WEB_PROTOCOL) }} WEBSITE={{ MAILU_WEBSITE }}
@ -151,34 +151,34 @@ SQLALCHEMY_DATABASE_URI=mysql+mysqlconnector://{{ database_username }}:{{ databa
API=true API=true
WEB_API=/api WEB_API=/api
# Configures the authentication token. The minimum length is 3 characters. This token must be passed as request header to the API as authentication token. This is a mandatory setting for using the RESTful API. # Configures the authentication token. The minimum length is 3 characters. This token must be passed as request header to the API as authentication token. This is a mandatory setting for using the RESTful API.
API_TOKEN={{ applications | get_app_conf(application_id, 'credentials.api_token')}} API_TOKEN={{ MAILU_API_TOKEN}}
# Activated https://mailu.io/master/configuration.html#advanced-settings # Activated https://mailu.io/master/configuration.html#advanced-settings
AUTH_REQUIRE_TOKENS=True AUTH_REQUIRE_TOKENS=True
{% if applications | get_app_conf(application_id, 'features.oidc', False) %} {% if MAILU_OIDC_ENABLED | bool %}
################################### ###################################
# OpenID Connect settings # OpenID Connect settings
################################### ###################################
# @see https://github.com/heviat/Mailu-OIDC/tree/master # @see https://github.com/heviat/Mailu-OIDC/tree/master
# Enable OpenID Connect. Possible values: True, False # Enable OpenID Connect. Possible values: True, False
OIDC_ENABLED={{ applications | get_app_conf(application_id, 'features.oidc', False) | string | capitalize }} OIDC_ENABLED={{ MAILU_OIDC_ENABLED | string | capitalize }}
# OpenID Connect provider configuration URL # OpenID Connect provider configuration URL
OIDC_PROVIDER_INFO_URL={{ oidc.client.issuer_url }} OIDC_PROVIDER_INFO_URL={{ OIDC.CLIENT.ISSUER_URL }}
# OpenID Connect Client ID for Mailu # OpenID Connect Client ID for Mailu
OIDC_CLIENT_ID={{ oidc.client.id }} OIDC_CLIENT_ID={{ OIDC.CLIENT.ID }}
# OpenID Connect Client secret for Mailu # OpenID Connect Client secret for Mailu
OIDC_CLIENT_SECRET={{ oidc.client.secret }} OIDC_CLIENT_SECRET={{ OIDC.CLIENT.SECRET }}
# Label text for OpenID Connect login button. Default: OpenID Connect # Label text for OpenID Connect login button. Default: OpenID Connect
OIDC_BUTTON_NAME={{ oidc.button_text }} OIDC_BUTTON_NAME={{ OIDC.BUTTON_TEXT }}
# Disable TLS certificate verification for the OIDC client. Possible values: True, False # Disable TLS certificate verification for the OIDC client. Possible values: True, False
OIDC_VERIFY_SSL=True OIDC_VERIFY_SSL=True
@ -187,17 +187,17 @@ OIDC_VERIFY_SSL=True
OIDC_CHANGE_PASSWORD_REDIRECT_ENABLED=True OIDC_CHANGE_PASSWORD_REDIRECT_ENABLED=True
# Redirect URL for password change. Defaults to provider issuer url appended by /.well-known/change-password # Redirect URL for password change. Defaults to provider issuer url appended by /.well-known/change-password
OIDC_CHANGE_PASSWORD_REDIRECT_URL={{oidc.client.change_credentials}} OIDC_CHANGE_PASSWORD_REDIRECT_URL={{ OIDC.CLIENT.CHANGE_CREDENTIALS }}
{% if applications | get_app_conf(application_id, 'oidc.email_by_username', True) | bool %} {% if MAILU_OIDC_EMAIL_BY_USERNAME_ENABLED | bool %}
# The OIDC claim used as the username. If the selected claim contains an email address, it will be used as is. If it is not an email (e.g., sub), the email address will be constructed as <OIDC_USERNAME_CLAIM>@<OIDC_USER_DOMAIN>. Defaults to email. # The OIDC claim used as the username. If the selected claim contains an email address, it will be used as is. If it is not an email (e.g., sub), the email address will be constructed as <OIDC_USERNAME_CLAIM>@<OIDC_USER_DOMAIN>. Defaults to email.
OIDC_USERNAME_CLAIM={{oidc.attributes.username}} OIDC_USERNAME_CLAIM={{ OIDC.ATTRIBUTES.USERNAME }}
# The domain used when constructing an email from a non-email username (e.g., when OIDC_USERNAME_CLAIM=sub). Ignored if OIDC_USERNAME_CLAIM is already an email. Defaults to the value of DOMAIN. # The domain used when constructing an email from a non-email username (e.g., when OIDC_USERNAME_CLAIM=sub). Ignored if OIDC_USERNAME_CLAIM is already an email. Defaults to the value of DOMAIN.
OIDC_USER_DOMAIN={{ PRIMARY_DOMAIN }} OIDC_USER_DOMAIN={{ MAILU_DOMAIN }}
{% endif %} {% endif %}
# If enabled, users who authenticate successfully but do not yet have an account will have one created for them. If disabled, only existing users can log in, and authentication will fail for users without a pre-existing account. Defaults to True. # If enabled, users who authenticate successfully but do not yet have an account will have one created for them. If disabled, only existing users can log in, and authentication will fail for users without a pre-existing account. Defaults to True.
OIDC_ENABLE_USER_CREATION={{ applications | get_app_conf(application_id, 'oidc.enable_user_creation', True) | string | capitalize }} OIDC_ENABLE_USER_CREATION={{ MAILU_OIDC_ENABLE_USER_CREATION }}
{% endif %} {% endif %}

View File

@ -1,41 +0,0 @@
# vars/mailu-dns.yml
mailu_dns_zone: "{{ applications | get_app_conf(application_id, 'domain', True) }}"
mailu_dns_ip: "{{ networks.internet.ip4 }}"
cloudflare_record_api_token: "{{ CERTBOT_DNS_API_TOKEN }}"
mailu_dmarc_ruf: "{{ applications | get_app_conf(application_id, 'users.administrator.email', True) }}"
mailu_dkim_key_file: "{{ applications | get_app_conf(application_id, 'domain', True) }}.dkim.key"
mailu_dkim_key_path: "/dkim/{{ mailu_dkim_key_file }}"
mailu_dns_srv_records:
submission:
port: 587
priority: 20
weight: 1
submissions:
port: 465
priority: 20
weight: 1
imaps:
port: 993
priority: 20
weight: 1
imap:
port: 143
priority: 20
weight: 1
pop3s:
port: 995
priority: 20
weight: 1
pop3:
port: 110
priority: 20
weight: 1
autodiscover:
port: "{{ WEB_PORT }}"
priority: 20
weight: 1

View File

@ -1,6 +1,5 @@
# General # General
application_id: "web-app-mailu" application_id: "web-app-mailu"
domain: "{{ domains | get_domain(application_id) }}"
http_port: "{{ ports.localhost.http[application_id] }}" http_port: "{{ ports.localhost.http[application_id] }}"
proxy_extra_configuration: "client_max_body_size 31M;" proxy_extra_configuration: "client_max_body_size 31M;"
@ -8,19 +7,64 @@ proxy_extra_configuration: "client_max_body_size 31M;"
database_password: "{{ applications | get_app_conf(application_id, 'credentials.database_password') }}" database_password: "{{ applications | get_app_conf(application_id, 'credentials.database_password') }}"
database_type: "mariadb" database_type: "mariadb"
# Cert Mount
cert_mount_directory: "{{ docker_compose.directories.volumes }}certs/" cert_mount_directory: "{{ docker_compose.directories.volumes }}certs/"
# Mailu
## Meta
MAILU_WEBSITE: "{{ domains | get_url(application_id, WEB_PROTOCOL) }}"
## Domains
MAILU_DOMAIN: "{{ applications | get_app_conf(application_id, 'domain') }}"
MAILU_DOMAIN_DNS_ZONE: "{{ MAILU_DOMAIN | to_zone }}"
MAILU_HOSTNAMES: "{{ domains[application_id] }}"
MAILU_HOSTNAME: "{{ domains | get_domain(application_id) }}"
MAILU_HOSTNAME_DNS_ZONE: "{{ MAILU_HOSTNAME | to_zone }}"
## Docker
MAILU_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.mailu.version') }}"
MAILU_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.mailu.name') }}"
## Volumes
MAILU_SMTP_QUEUE_VOLUME: "mailu_smtp_queue"
MAILU_ADMIN_DATA_VOLUME: "mailu_admin_data"
MAILU_WEBDAV_DATA: "mailu_webdav_data"
MAILU_WEBMAIL_DATA: "mailu_webmail_data"
MAILU_FILTER_VOLUME: "mailu_filter"
MAILU_DKIM_VOLUME: "mailu_dkim"
MAILU_DOVECOT_MAIL_VOLUME: "mailu_dovecot_mail"
## Network
MAILU_DNS_RESOLVER: "{{ networks.local['web-app-mailu'].dns_resolver }}"
MAILU_IP4_PUBLIC: "{{ networks.internet.ip4 }}"
MAILU_IP6_PUBLIC: false #Deactivated atm. but cloudflare logic present
MAILU_SUBNET: "{{ networks.local['web-app-mailu'].subnet }}"
## Credentials
MAILU_SECRET_KEY: "{{ applications | get_app_conf(application_id,'credentials.secret_key') }}"
MAILU_CLOUDFLARE_API_TOKEN: "{{ CERTBOT_DNS_API_TOKEN }}"
MAILU_API_TOKEN: "{{ applications | get_app_conf(application_id, 'credentials.api_token') }}"
## OIDC
MAILU_OIDC_ENABLED: "{{ applications | get_app_conf(application_id, 'features.oidc', False) }}"
MAILU_OIDC_EMAIL_BY_USERNAME_ENABLED: "{{ applications | get_app_conf(application_id, 'oidc.email_by_username') }}"
MAILU_OIDC_ENABLE_USER_CREATION: "{{ applications | get_app_conf(application_id, 'oidc.enable_user_creation') | string | capitalize }}"
# Use dedicated source for oidc if activated # Use dedicated source for oidc if activated
# @see https://github.com/heviat/Mailu-OIDC/tree/2024.06 # @see https://github.com/heviat/Mailu-OIDC/tree/2024.06
docker_source: "{{ 'ghcr.io/heviat' if applications | get_app_conf(application_id, 'features.oidc', False) else 'ghcr.io/mailu' }}" MAILU_DOCKER_FLAVOR: "{{ 'ghcr.io/heviat' if MAILU_OIDC_ENABLED | bool else 'ghcr.io/mailu' }}"
# Mailu Specific MAILU_DMARC_RUF: "{{ applications | get_app_conf(application_id, 'users.administrator.email') }}"
mailu_version: "{{ applications | get_app_conf(application_id, 'docker.services.mailu.version', True) }}"
mailu_name: "{{ applications | get_app_conf(application_id, 'docker.services.mailu.name', True) }}" MAILU_DKIM_KEY_FILE: "{{ MAILU_DOMAIN }}.dkim.key"
mailu_smtp_queue: "mailu_smtp_queue" MAILU_DKIM_KEY_PATH: "/dkim/{{ MAILU_DKIM_KEY_FILE }}"
mailu_admin_data: "mailu_admin_data"
mailu_webdav_data: "mailu_webdav_data" MAILU_DNS_SRV_RECORDS:
mailu_webmail_data: "mailu_webmail_data" submission: { port: 587, priority: 20, weight: 1 }
mailu_filter: "mailu_filter" submissions: { port: 465, priority: 20, weight: 1 }
mailu_dkim: "mailu_dkim" imaps: { port: 993, priority: 20, weight: 1 }
mailu_dovecot_mail: "mailu_dovecot_mail" imap: { port: 143, priority: 20, weight: 1 }
pop3s: { port: 995, priority: 20, weight: 1 }
pop3: { port: 110, priority: 20, weight: 1 }
autodiscover: { port: 443, priority: 20, weight: 1 }

View File

@ -60,16 +60,16 @@ SMTP_FROM_ADDRESS=Mastodon <{{ users['no-reply'].email }}>
# @see https://stackoverflow.com/questions/72081776/how-mastodon-configured-login-using-sso # @see https://stackoverflow.com/questions/72081776/how-mastodon-configured-login-using-sso
OIDC_ENABLED={{ applications | get_app_conf(application_id, 'features.oidc', False) | string | lower }} OIDC_ENABLED={{ applications | get_app_conf(application_id, 'features.oidc', False) | string | lower }}
OIDC_DISPLAY_NAME="{{ oidc.button_text }}" OIDC_DISPLAY_NAME="{{ OIDC.BUTTON_TEXT }}"
OIDC_ISSUER={{ oidc.client.issuer_url }} OIDC_ISSUER={{ OIDC.CLIENT.ISSUER_URL }}
OIDC_DISCOVERY=true OIDC_DISCOVERY=true
OIDC_SCOPE="openid,profile,email" OIDC_SCOPE="openid,profile,email"
# @see https://stackoverflow.com/questions/72108087/how-to-set-the-username-of-mastodon-by-log-in-via-keycloak # @see https://stackoverflow.com/questions/72108087/how-to-set-the-username-of-mastodon-by-log-in-via-keycloak
OIDC_UID_FIELD={{oidc.attributes.username}} OIDC_UID_FIELD={{OIDC.ATTRIBUTES.USERNAME}}
OIDC_CLIENT_ID={{ oidc.client.id }} OIDC_CLIENT_ID={{ OIDC.CLIENT.ID }}
OIDC_REDIRECT_URI=https://{{ domains | get_domain(application_id) }}/auth/auth/openid_connect/callback OIDC_REDIRECT_URI=https://{{ domains | get_domain(application_id) }}/auth/auth/openid_connect/callback
OIDC_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true OIDC_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
OIDC_CLIENT_SECRET={{ oidc.client.secret }} OIDC_CLIENT_SECRET={{ OIDC.CLIENT.SECRET }}
# uncomment to only use OIDC for login / registration buttons # uncomment to only use OIDC for login / registration buttons
OMNIAUTH_ONLY=true OMNIAUTH_ONLY=true
ALLOW_UNSAFE_AUTH_PROVIDER_REATTACH=true ALLOW_UNSAFE_AUTH_PROVIDER_REATTACH=true

View File

@ -50,14 +50,14 @@ email:
# @See https://matrix-org.github.io/synapse/latest/openid.html # @See https://matrix-org.github.io/synapse/latest/openid.html
oidc_providers: oidc_providers:
- idp_id: keycloak - idp_id: keycloak
idp_name: "{{ oidc.button_text }}" idp_name: "{{ OIDC.BUTTON_TEXT }}"
issuer: "{{ oidc.client.issuer_url }}" issuer: "{{ OIDC.CLIENT.ISSUER_URL }}"
client_id: "{{ oidc.client.id }}" client_id: "{{ OIDC.CLIENT.ID }}"
client_secret: "{{ oidc.client.secret }}" client_secret: "{{ OIDC.CLIENT.SECRET }}"
scopes: ["openid", "profile"] scopes: ["openid", "profile"]
user_mapping_provider: user_mapping_provider:
config: config:
localpart_template: "{% raw %}{{ user.{% endraw %}{{oidc.attributes.username}}{% raw %}}}{% endraw %}" localpart_template: "{% raw %}{{ user.{% endraw %}{{OIDC.ATTRIBUTES.USERNAME}}{% raw %}}}{% endraw %}"
display_name_template: "{% raw %}{{ user.name }}{% endraw %}" display_name_template: "{% raw %}{{ user.name }}{% endraw %}"
backchannel_logout_enabled: true backchannel_logout_enabled: true
{% endif %} {% endif %}

View File

@ -264,15 +264,15 @@ config :ueberauth,
config :mobilizon, :auth, config :mobilizon, :auth,
oauth_consumer_strategies: [ oauth_consumer_strategies: [
{:keycloak, "{{ oidc.button_text }}"} {:keycloak, "{{ OIDC.BUTTON_TEXT }}"}
] ]
config :ueberauth, Ueberauth.Strategy.Keycloak.OAuth, config :ueberauth, Ueberauth.Strategy.Keycloak.OAuth,
client_id: "{{ oidc.client.id }}", client_id: "{{ OIDC.CLIENT.ID }}",
client_secret: "{{ oidc.client.secret }}", client_secret: "{{ OIDC.CLIENT.SECRET }}",
site: "{{ oidc.url }}", site: "{{ OIDC.URL }}",
authorize_url: "{{ oidc.client.authorize_url }}", authorize_url: "{{ OIDC.CLIENT.AUTHORIZE_URL }}",
token_url: "{{ oidc.client.token_url }}", token_url: "{{ OIDC.CLIENT.TOKEN_URL }}",
userinfo_url: "{{ oidc.client.user_info_url }}", userinfo_url: "{{ OIDC.CLIENT.USER_INFO_URL }}",
token_method: :post token_method: :post
{% endif %} {% endif %}

View File

@ -23,19 +23,19 @@
loop: loop:
- { name: "idptype", value: 3 } - { name: "idptype", value: 3 }
- { name: "clientauthmethod", value: 1 } - { name: "clientauthmethod", value: 1 }
- { name: "clientid", value: "{{ oidc.client.id }}" } - { name: "clientid", value: "{{ OIDC.CLIENT.ID }}" }
- { name: "clientsecret", value: "{{ oidc.client.secret }}" } - { name: "clientsecret", value: "{{ OIDC.CLIENT.SECRET }}" }
- { name: "opname", value: "{{ oidc.button_text }}" } - { name: "opname", value: "{{ OIDC.BUTTON_TEXT }}" }
- { name: "oidcscope", value: "openid profile email" } - { name: "oidcscope", value: "openid profile email" }
- { name: "authendpoint", value: "{{ oidc.client.authorize_url }}" } - { name: "authendpoint", value: "{{ OIDC.CLIENT.AUTHORIZE_URL }}" }
- { name: "tokenendpoint", value: "{{ oidc.client.token_url }}" } - { name: "tokenendpoint", value: "{{ OIDC.CLIENT.TOKEN_URL }}" }
- { name: "bindingusernameclaim", value: "{{ oidc.attributes.username }}" } - { name: "bindingusernameclaim", value: "{{ OIDC.ATTRIBUTES.USERNAME }}" }
- { name: "single_sign_off", value: 1 } # Logs the user out from the IDP - { name: "single_sign_off", value: 1 } # Logs the user out from the IDP
- { name: "logouturi", value: "{{ oidc.client.logout_url }}" } - { name: "logouturi", value: "{{ OIDC.CLIENT.LOGOUT_URL }}" }
- { name: "icon", value: "moodle:t/lock" } - { name: "icon", value: "moodle:t/lock" }
- { name: "field_map_firstname", value: "{{ oidc.attributes.given_name }}" } - { name: "field_map_firstname", value: "{{ OIDC.ATTRIBUTES.GIVEN_NAME }}" }
- { name: "field_lock_firstname", value: "locked" } - { name: "field_lock_firstname", value: "locked" }
- { name: "field_map_lastname", value: "{{ oidc.attributes.family_name }}" } - { name: "field_map_lastname", value: "{{ OIDC.ATTRIBUTES.FAMILY_NAME }}" }
- { name: "field_lock_lastname", value: "locked" } - { name: "field_lock_lastname", value: "locked" }
- { name: "field_map_email", value: "locked" } - { name: "field_map_email", value: "locked" }
#- { name: "showloginform", value: 0 } # Deactivate if OIDC is active #- { name: "showloginform", value: 0 } # Deactivate if OIDC is active

View File

@ -11,11 +11,11 @@ return array (
'lost_password_link' => 'disabled', 'lost_password_link' => 'disabled',
// URL of provider. All other URLs are auto-discovered from .well-known // URL of provider. All other URLs are auto-discovered from .well-known
'oidc_login_provider_url' => '{{ oidc.client.issuer_url }}', 'oidc_login_provider_url' => '{{ OIDC.CLIENT.ISSUER_URL }}',
// Client ID and secret registered with the provider // Client ID and secret registered with the provider
'oidc_login_client_id' => '{{ oidc.client.id }}', 'oidc_login_client_id' => '{{ OIDC.CLIENT.ID }}',
'oidc_login_client_secret' => '{{ oidc.client.secret }}', 'oidc_login_client_secret' => '{{ OIDC.CLIENT.SECRET }}',
// Automatically redirect the login page to the provider // Automatically redirect the login page to the provider
'oidc_login_auto_redirect' => true, 'oidc_login_auto_redirect' => true,
@ -36,7 +36,7 @@ return array (
'oidc_login_default_quota' => '{{applications | get_app_conf(application_id, 'default_quota', True)}}', 'oidc_login_default_quota' => '{{applications | get_app_conf(application_id, 'default_quota', True)}}',
// Login button text // Login button text
'oidc_login_button_text' => '{{ oidc.button_text }}', 'oidc_login_button_text' => '{{ OIDC.BUTTON_TEXT }}',
// Hide the NextCloud password change form. // Hide the NextCloud password change form.
'oidc_login_hide_password_form' => true, 'oidc_login_hide_password_form' => true,
@ -102,7 +102,7 @@ return array (
'mail' => 'email', 'mail' => 'email',
'quota' => '{{ ldap.user.attributes.nextcloud_quota }}', 'quota' => '{{ ldap.user.attributes.nextcloud_quota }}',
# 'home' => 'homeDirectory', # Not implemented yet # 'home' => 'homeDirectory', # Not implemented yet
'ldap_uid' => '{{oidc.attributes.username}}', 'ldap_uid' => '{{OIDC.ATTRIBUTES.USERNAME}}',
# 'groups' => 'ownCloudGroups', # Not implemented yet # 'groups' => 'ownCloudGroups', # Not implemented yet
# 'login_filter' => 'realm_access_roles', # 'login_filter' => 'realm_access_roles',
// 'photoURL' => 'picture', // 'photoURL' => 'picture',

View File

@ -28,13 +28,13 @@ plugin_configuration:
- name: "{{ domains | get_domain('web-app-keycloak') }}" - name: "{{ domains | get_domain('web-app-keycloak') }}"
title: "keycloak" title: "keycloak"
style: "keycloak" style: "keycloak"
authorizeUrl: "{{ oidc.client.authorize_url }}" authorizeUrl: "{{ OIDC.CLIENT.AUTHORIZE_URL }}"
tokenUrl: "{{ oidc.client.token_url }}" tokenUrl: "{{ OIDC.CLIENT.TOKEN_URL }}"
displayNameClaim: "" displayNameClaim: ""
userInfoUrl: "{{ oidc.client.user_info_url }}" userInfoUrl: "{{ OIDC.CLIENT.USER_INFO_URL }}"
logoutUrl: "{{ oidc.client.logout_url }}" logoutUrl: "{{ OIDC.CLIENT.LOGOUT_URL }}"
clientId: "{{ oidc.client.id }}" clientId: "{{ OIDC.CLIENT.ID }}"
clientSecret: "{{ oidc.client.secret }}" clientSecret: "{{ OIDC.CLIENT.SECRET }}"
scope: "openid" scope: "openid"
groupsClaim: "" groupsClaim: ""
defaultGroup: "" defaultGroup: ""

View File

@ -6,17 +6,17 @@ cookie_domains = ["{{ domains | get_domain(oauth2_proxy_application_i
whitelist_domains = [".{{ PRIMARY_DOMAIN }}"] # Required to allow redirection back to original requested target. whitelist_domains = [".{{ PRIMARY_DOMAIN }}"] # Required to allow redirection back to original requested target.
# keycloak provider # keycloak provider
client_secret = "{{ oidc.client.secret }}" client_secret = "{{ OIDC.CLIENT.SECRET }}"
client_id = "{{ oidc.client.id }}" client_id = "{{ OIDC.CLIENT.ID }}"
redirect_url = "{{ WEB_PROTOCOL }}://{{ domains | get_domain(oauth2_proxy_application_id) }}/oauth2/callback" redirect_url = "{{ WEB_PROTOCOL }}://{{ domains | get_domain(oauth2_proxy_application_id) }}/oauth2/callback"
oidc_issuer_url = "{{ oidc.client.issuer_url }}" oidc_issuer_url = "{{ OIDC.CLIENT.ISSUER_URL }}"
provider = "oidc" provider = "oidc"
provider_display_name = "{{ oidc.button_text }}" provider_display_name = "{{ OIDC.BUTTON_TEXT }}"
{% if applications | get_app_conf(oauth2_proxy_application_id, 'oauth2_proxy.allowed_groups', False) %} {% if applications | get_app_conf(oauth2_proxy_application_id, 'oauth2_proxy.allowed_groups', False) %}
{# role based restrictions #} {# role based restrictions #}
scope = "openid email profile {{ oidc.claims.groups }}" scope = "openid email profile {{ OIDC.CLAIMS.GROUPS }}"
oidc_groups_claim = "{{ oidc.claims.groups }}" oidc_groups_claim = "{{ OIDC.CLAIMS.GROUPS }}"
allowed_groups = {{ applications | get_app_conf(oauth2_proxy_application_id, 'oauth2_proxy.allowed_groups', True) | tojson }} allowed_groups = {{ applications | get_app_conf(oauth2_proxy_application_id, 'oauth2_proxy.allowed_groups', True) | tojson }}
email_domains = ["*"] email_domains = ["*"]
{% else %} {% else %}

View File

@ -1,12 +1,12 @@
oidc_settings: | oidc_settings: |
{ {
"scope": "openid email profile", "scope": "openid email profile",
"desk-id": "{{ oidc.client.id }}", "desk-id": "{{ OIDC.CLIENT.ID }}",
"discover-url": "{{ oidc.client.discovery_document }}", "discover-url": "{{ OIDC.CLIENT.DISCOVERY_DOCUMENT }}",
"desk-secret": "{{ oidc.client.secret }}", "desk-secret": "{{ OIDC.CLIENT.SECRET }}",
"mail-property": "email", "mail-property": "email",
"auth-display-name": "{{ oidc.button_text }}", "auth-display-name": "{{ OIDC.BUTTON_TEXT }}",
"username-property": "{{ oidc.attributes.username }}", "username-property": "{{ OIDC.ATTRIBUTES.USERNAME }}",
"signature-algorithm": "RS256", "signature-algorithm": "RS256",
"display-name-property": "{{ oidc.attributes.username }}" "display-name-property": "{{ OIDC.ATTRIBUTES.USERNAME }}"
} }

View File

@ -141,14 +141,14 @@ ENABLE_CONFIG_CACHE=true
# @see https://github.com/pixelfed/pixelfed/commit/b3c27815788e4b47e7eb3fca727d817512cf26c2#diff-66e408190a301e81b5f1c079463487c54a6452c4944dc5ae80770f50101283ff # @see https://github.com/pixelfed/pixelfed/commit/b3c27815788e4b47e7eb3fca727d817512cf26c2#diff-66e408190a301e81b5f1c079463487c54a6452c4944dc5ae80770f50101283ff
PF_OIDC_ENABLED={{ applications | get_app_conf(application_id, 'features.oidc', False) | string | lower }} PF_OIDC_ENABLED={{ applications | get_app_conf(application_id, 'features.oidc', False) | string | lower }}
PF_OIDC_AUTHORIZE_URL="{{oidc.client.authorize_url}}" PF_OIDC_AUTHORIZE_URL="{{ OIDC.CLIENT.AUTHORIZE_URL }}"
PF_OIDC_TOKEN_URL="{{oidc.client.token_url}}" PF_OIDC_TOKEN_URL="{{OIDC.CLIENT.TOKEN_URL}}"
PF_OIDC_PROFILE_URL="{{ oidc.client.user_info_url }}" PF_OIDC_PROFILE_URL="{{ OIDC.CLIENT.USER_INFO_URL }}"
PF_OIDC_LOGOUT_URL="{{oidc.client.logout_url}}" PF_OIDC_LOGOUT_URL="{{OIDC.CLIENT.LOGOUT_URL}}"
PF_OIDC_USERNAME_FIELD="{{oidc.attributes.username}}" PF_OIDC_USERNAME_FIELD="{{OIDC.ATTRIBUTES.USERNAME}}"
PF_OIDC_FIELD_ID="{{oidc.attributes.username}}" PF_OIDC_FIELD_ID="{{OIDC.ATTRIBUTES.USERNAME}}"
PF_OIDC_CLIENT_SECRET={{ oidc.client.secret }} PF_OIDC_CLIENT_SECRET={{ OIDC.CLIENT.SECRET }}
PF_OIDC_CLIENT_ID={{ oidc.client.id }} PF_OIDC_CLIENT_ID={{ OIDC.CLIENT.ID }}
PF_OIDC_SCOPES="openid profile email" PF_OIDC_SCOPES="openid profile email"
{% endif %} {% endif %}

View File

@ -38,13 +38,13 @@ applications:
description: Update your personal admin settings description: Update your personal admin settings
icon: icon:
class: fa-solid fa-user-gear class: fa-solid fa-user-gear
url: {{ keycloak_url }}/realms/{{ oidc.client.id }}/account url: {{ keycloak_url }}/realms/{{ OIDC.CLIENT.ID }}/account
iframe: {{ applications | get_app_conf( 'web-app-keycloak', 'features.desktop', False) }} iframe: {{ applications | get_app_conf( 'web-app-keycloak', 'features.desktop', False) }}
- name: Logout - name: Logout
description: End your admin session securely description: End your admin session securely
icon: icon:
class: fa-solid fa-right-from-bracket class: fa-solid fa-right-from-bracket
url: {{ keycloak_url }}/realms/{{ oidc.client.id }}/protocol/openid-connect/logout url: {{ keycloak_url }}/realms/{{ OIDC.CLIENT.ID }}/protocol/openid-connect/logout
iframe: false iframe: false
{% endif %} {% endif %}

View File

@ -36,7 +36,7 @@
$s->ad_domain = ""; $s->ad_domain = "";
$s->ldap_default_group = ""; $s->ldap_default_group = "";
$s->ldap_email = "{{ ldap.user.attributes.mail }}"; $s->ldap_email = "{{ ldap.user.attributes.mail }}";
$s->custom_forgot_pass_url = "{{ oidc.client.reset_credentials }}"; $s->custom_forgot_pass_url = "{{ OIDC.CLIENT.RESET_CREDENTIALS }}";
$s->save(); $s->save();
EOF' EOF'
args: args:

View File

@ -54,14 +54,14 @@ ENABLE_TELEMETRY = True
# OIDC via taigaio official contrib # OIDC via taigaio official contrib
# @See https://github.com/taigaio/taiga-contrib-oidc-auth # @See https://github.com/taigaio/taiga-contrib-oidc-auth
OIDC_RP_CLIENT_ID="{{ oidc.client.id }}" OIDC_RP_CLIENT_ID="{{ OIDC.CLIENT.ID }}"
OIDC_RP_CLIENT_SECRET="{{ oidc.client.secret }}" OIDC_RP_CLIENT_SECRET="{{ OIDC.CLIENT.SECRET }}"
OIDC_OP_AUTHORIZATION_ENDPOINT="{{ oidc.client.authorize_url }}" OIDC_OP_AUTHORIZATION_ENDPOINT="{{ OIDC.CLIENT.AUTHORIZE_URL }}"
OIDC_OP_TOKEN_ENDPOINT="{{ oidc.client.token_url }}" OIDC_OP_TOKEN_ENDPOINT="{{ OIDC.CLIENT.TOKEN_URL }}"
OIDC_OP_USER_ENDPOINT="{{ oidc.client.user_info_url }}" OIDC_OP_USER_ENDPOINT="{{ OIDC.CLIENT.USER_INFO_URL }}"
OIDC_RP_SIGN_ALGO="RS256" OIDC_RP_SIGN_ALGO="RS256"
OIDC_RP_SCOPES="openid profile email" OIDC_RP_SCOPES="openid profile email"
OIDC_OP_JWKS_ENDPOINT="{{ oidc.client.certs }}" OIDC_OP_JWKS_ENDPOINT="{{ OIDC.CLIENT.CERTS }}"
{% endif %} {% endif %}
@ -70,13 +70,13 @@ OIDC_OP_JWKS_ENDPOINT="{{ oidc.client.certs }}"
# OIDC via robrotheram # OIDC via robrotheram
# @see https://github.com/robrotheram/taiga-contrib-openid-auth # @see https://github.com/robrotheram/taiga-contrib-openid-auth
ENABLE_OPENID=True ENABLE_OPENID=True
OPENID_URL="{{oidc.client.authorize_url}}" OPENID_URL="{{ OIDC.CLIENT.AUTHORIZE_URL }}"
OPENID_USER_URL="{{oidc.client.user_info_url}}" OPENID_USER_URL="{{OIDC.CLIENT.USER_INFO_URL}}"
OPENID_TOKEN_URL="{{oidc.client.token_url}}" OPENID_TOKEN_URL="{{OIDC.CLIENT.TOKEN_URL}}"
OPENID_CLIENT_ID="{{ oidc.client.id }}" OPENID_CLIENT_ID="{{ OIDC.CLIENT.ID }}"
OPENID_CLIENT_SECRET="{{ oidc.client.secret }}" OPENID_CLIENT_SECRET="{{ OIDC.CLIENT.SECRET }}"
OPENID_NAME="{{ oidc.button_text }}" OPENID_NAME="{{ OIDC.BUTTON_TEXT }}"
OPENID_USERNAME_FIELD="{{oidc.attributes.username}}" OPENID_USERNAME_FIELD="{{OIDC.ATTRIBUTES.USERNAME}}"
# Optional: # Optional:
# OPENID_ID_FIELD="sub" # OPENID_ID_FIELD="sub"
# OPENID_FULLNAME_FIELD="name" # OPENID_FULLNAME_FIELD="name"

View File

@ -3,12 +3,12 @@
# @see https://github.com/oidc-wp/openid-connect-generic/blob/develop/includes/openid-connect-dev-option-settings.php # @see https://github.com/oidc-wp/openid-connect-generic/blob/develop/includes/openid-connect-dev-option-settings.php
oidc_settings: oidc_settings:
client_id: "{{ oidc.client.id }}" # The client ID that identifies WordPress as the OIDC client. client_id: "{{ OIDC.CLIENT.ID }}" # The client ID that identifies WordPress as the OIDC client.
client_secret: "{{ oidc.client.secret }}" # The secret key used by WordPress to authenticate to the OIDC provider. client_secret: "{{ OIDC.CLIENT.SECRET }}" # The secret key used by WordPress to authenticate to the OIDC provider.
endpoint_login: "{{ oidc.client.authorize_url }}" # URL of the authorization endpoint to initiate the login flow. endpoint_login: "{{ OIDC.CLIENT.AUTHORIZE_URL }}" # URL of the authorization endpoint to initiate the login flow.
endpoint_token: "{{ oidc.client.token_url }}" # URL of the token endpoint for exchanging authorization codes for tokens. endpoint_token: "{{ OIDC.CLIENT.TOKEN_URL }}" # URL of the token endpoint for exchanging authorization codes for tokens.
endpoint_userinfo: "{{ oidc.client.user_info_url }}" # URL of the userinfo endpoint to retrieve user profile data. endpoint_userinfo: "{{ OIDC.CLIENT.USER_INFO_URL }}" # URL of the userinfo endpoint to retrieve user profile data.
endpoint_end_session: "{{ oidc.client.logout_url }}" # URL of the end-session endpoint to log users out of the IDP. endpoint_end_session: "{{ OIDC.CLIENT.LOGOUT_URL }}" # URL of the end-session endpoint to log users out of the IDP.
login_type: "auto" # Determines how the login interface is rendered (e.g., button or form). login_type: "auto" # Determines how the login interface is rendered (e.g., button or form).
scope: "openid profile email" # Scopes requested from the OIDC provider during authentication. scope: "openid profile email" # Scopes requested from the OIDC provider during authentication.
create_if_does_not_exist: true # Auto-create a new WP user if one doesnt exist. create_if_does_not_exist: true # Auto-create a new WP user if one doesnt exist.
@ -16,14 +16,14 @@ oidc_settings:
link_existing_users: true # Link OIDC login to existing WP users by matching email. link_existing_users: true # Link OIDC login to existing WP users by matching email.
redirect_on_logout: true # Redirect users after logout to the login screen or homepage. redirect_on_logout: true # Redirect users after logout to the login screen or homepage.
redirect_user_back: true # Return users to their original URL after successful login. redirect_user_back: true # Return users to their original URL after successful login.
#acr_values: "{{ oidc.client.acr_values | default('') }}" # ACR values defining required authentication context (e.g., MFA level). #acr_values: "{{ OIDC.CLIENT.acr_values | default('') }}" # ACR values defining required authentication context (e.g., MFA level).
enable_logging: "{{ MODE_DEBUG }}" # Enable detailed plugin logging for debugging and auditing. enable_logging: "{{ MODE_DEBUG }}" # Enable detailed plugin logging for debugging and auditing.
# log_limit: "{{ oidc.client.log_limit | default('') }}" # Maximum number of log entries to retain before pruning. # log_limit: "{{ OIDC.CLIENT.log_limit | default('') }}" # Maximum number of log entries to retain before pruning.
no_sslverify: false # The flag to enable/disable SSL verification during authorization. no_sslverify: false # The flag to enable/disable SSL verification during authorization.
http_request_timeout: 5 # The timeout for requests made to the IDP. Default value is 5. http_request_timeout: 5 # The timeout for requests made to the IDP. Default value is 5.
identity_key: "{{ oidc.attributes.username }}" # The key in the user claim array to find the user's identification data. identity_key: "{{ OIDC.ATTRIBUTES.USERNAME }}" # The key in the user claim array to find the user's identification data.
nickname_key: "{{ oidc.attributes.username }}" # The key in the user claim array to find the user's nickname. nickname_key: "{{ OIDC.ATTRIBUTES.USERNAME }}" # The key in the user claim array to find the user's nickname.
email_format: "{{ oidc.attributes.email }}" # The key(s) in the user claim array to formulate the user's email address. email_format: "{{ OIDC.ATTRIBUTES.EMAIL }}" # The key(s) in the user claim array to formulate the user's email address.
displayname_format: "{{ oidc.attributes.given_name }} {{ oidc.attributes.family_name }}" # The key(s) in the user claim array to formulate the user's display name. displayname_format: "{{ OIDC.ATTRIBUTES.GIVEN_NAME }} {{ OIDC.ATTRIBUTES.FAMILY_NAME }}" # The key(s) in the user claim array to formulate the user's display name.
identify_with_username: true # The flag which indicates how the user's identity will be determined. identify_with_username: true # The flag which indicates how the user's identity will be determined.
state_time_limit: 180 # The valid time limit of the state, in seconds. Defaults to 180 seconds. state_time_limit: 180 # The valid time limit of the state, in seconds. Defaults to 180 seconds.

View File

@ -83,9 +83,9 @@
set_fact: set_fact:
networks: "{{ defaults_networks | combine(networks | default({}, true), recursive=True) }}" networks: "{{ defaults_networks | combine(networks | default({}, true), recursive=True) }}"
- name: Merge oidc configuration - name: Merge OIDC configuration
set_fact: set_fact:
oidc: "{{ defaults_oidc | combine(oidc | default({}, true), recursive=True) }}" OIDC: "{{ defaults_oidc | combine(OIDC | default({}, true), recursive=True) }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: Merge design configuration - name: Merge design configuration

View File

@ -0,0 +1,49 @@
import os
import sys
import unittest
# Make project root importable so "filter_plugins.domain_tools" works no matter where tests run from
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_ROOT = os.path.abspath(os.path.join(THIS_DIR, '..', '..', '..'))
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
from ansible.errors import AnsibleFilterError # noqa: E402
from filter_plugins.domain_tools import to_zone, FilterModule # noqa: E402
class TestDomainTools(unittest.TestCase):
def test_to_zone_basic(self):
self.assertEqual(to_zone("example.com"), "example.com")
self.assertEqual(to_zone("mail.example.com"), "example.com")
self.assertEqual(to_zone("a.b.c.example.com"), "example.com")
def test_to_zone_trailing_and_leading_dots(self):
self.assertEqual(to_zone("example.com."), "example.com")
self.assertEqual(to_zone(".mail.example.com."), "example.com")
def test_to_zone_keeps_two_last_labels(self):
# Naive behavior by design: last two labels only
self.assertEqual(to_zone("service.co.uk"), "co.uk")
self.assertEqual(to_zone("mx.mail.service.co.uk"), "co.uk")
self.assertEqual(to_zone("uni.edu.pl"), "edu.pl")
def test_to_zone_invalid_inputs(self):
with self.assertRaises(AnsibleFilterError):
to_zone("") # empty
with self.assertRaises(AnsibleFilterError):
to_zone(" ") # whitespace
with self.assertRaises(AnsibleFilterError):
to_zone("localhost") # no TLD part
with self.assertRaises(AnsibleFilterError):
to_zone(None) # type: ignore
def test_filtermodule_exports(self):
fm = FilterModule()
filters = fm.filters()
self.assertIn("to_zone", filters)
self.assertIs(filters["to_zone"], to_zone)
if __name__ == "__main__":
unittest.main()