From 59e985eb3b9d3931597640e39ce01870e1d88893 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Thu, 24 Apr 2025 14:42:38 +0200 Subject: [PATCH] In between commit auto user creation before system email refactoring --- group_vars/all/00_general.yml | 36 ++++++++++++--- .../templates/discourse_application.yml.j2 | 6 +-- roles/docker-keycloak/templates/env.j2 | 2 +- .../templates/ldif/data/02_users.ldif.j2 | 43 ++++++++++++------ roles/docker-listmonk/meta/main.yml | 3 +- roles/docker-listmonk/vars/main.yml | 2 +- .../tasks/create-mailu-user-and-token.yml | 23 +++++----- roles/docker-mailu/tasks/main.yml | 45 +++++++++++++------ roles/docker-mailu/templates/env.j2 | 2 +- roles/docker-nextcloud/templates/env.j2 | 2 +- roles/user-administrator/tasks/main.yml | 2 +- templates/vars/applications.yml.j2 | 6 +-- 12 files changed, 119 insertions(+), 53 deletions(-) diff --git a/group_vars/all/00_general.yml b/group_vars/all/00_general.yml index a4e10875..564ae68a 100644 --- a/group_vars/all/00_general.yml +++ b/group_vars/all/00_general.yml @@ -15,17 +15,43 @@ primary_domain_sld: "cymais" # Second primary_domain: "{{primary_domain_sld}}.{{primary_domain_tld}}" # Primary Domain of the server # Helper Variables + +# Helper Variables for administrator _users_administrator_username: "{{ users.administrator.username | default('administrator') }}" _users_administrator_email: "{{ users.administrator.email | default(_users_administrator_username ~ '@' ~ primary_domain) }}" +# Helper Variables for bouncer +_users_bouncer_username: "{{ users.bouncer.username | default('bouncer') }}" +_users_bouncer_email: "{{ users.bouncer.email | default(_users_bouncer_username ~ '@' ~ primary_domain) }}" + +# Helper Variables for no-reply +_users_no_reply_username: "{{ users.no-reply.username | default('no-reply') }}" +_users_no_reply_email: "{{ users.no-reply.email | default(_users_no_reply_username ~ '@' ~ primary_domain) }}" + # Administrator default_users: administrator: - username: "{{_users_administrator_username}}" # Username of the administrator - email: "{{_users_administrator_email}}" # Email of the administrator -# initial_password: Null # Example initialisation password needs to be set in inventory file - uid: 1001 # Posix User ID - gid: 1001 # Posix Group ID + username: "{{_users_administrator_username}}" # Username of the administrator + email: "{{_users_administrator_email}}" # Email of the administrator + password: "{{ansible_become_password}}" # Example initialisation password needs to be set in inventory file + uid: 1001 # Posix User ID + gid: 1001 # Posix Group ID + is_admin: true # Define as admin user + + bouncer: + username: "{{ _users_bouncer_username }}" # Bounce-handler account username + email: "{{ _users_bouncer_email }}" # Email address for handling bounces + password: "{{ansible_become_password}}" # Example initialisation password needs to be set in inventory file + uid: 1002 # Posix User ID for bouncer + gid: 1002 # Posix Group ID for bouncer + + no-reply: + username: "{{ _users_no_reply_username }}" # No-reply account username + email: "{{ _users_no_reply_email }}" # Email address for outgoing no-reply mails + password: "{{ansible_become_password}}" # Example initialisation password needs to be set in inventory file + uid: 1003 # Posix User ID for no-reply + gid: 1003 # Posix Group ID for no-reply + # Test Email test_email: "test@{{primary_domain}}" diff --git a/roles/docker-discourse/templates/discourse_application.yml.j2 b/roles/docker-discourse/templates/discourse_application.yml.j2 index 99f0ac5d..285a8e61 100644 --- a/roles/docker-discourse/templates/discourse_application.yml.j2 +++ b/roles/docker-discourse/templates/discourse_application.yml.j2 @@ -51,8 +51,8 @@ env: DISCOURSE_DEVELOPER_EMAILS: {{users.administrator.email}} # Set Logo - DISCOURSE_LOGO_URL: "{{service_provider.platform.logo}}" - DISCOURSE_LOGO_SMALL_URL: "{{service_provider.platform.logo}}" + DISCOURSE_LOGO_URL: "{{ service_provider.platform.logo }}" + DISCOURSE_LOGO_SMALL_URL: "{{ service_provider.platform.logo }}" # SMTP ADDRESS, username, and password are required # WARNING the char '#' in SMTP password can cause problems! @@ -124,7 +124,7 @@ run: - exec: rails r "SiteSetting.username_change_period = 0" # Deactivate changing of username # Activate Administrator User - #- exec: printf '{{users.administrator.email}}\n{{users.administrator.initial_password}}\n{{users.administrator.initial_password}}\nY\n' | rake admin:create + #- exec: printf '{{users.administrator.email}}\n{{users.administrator.password}}\n{{users.administrator.password}}\nY\n' | rake admin:create #- exec: rails r "User.find_by_email('{{users.administrator.email}}').update(username: '{{users.administrator.username}}')" # The following code is just an inspiration, how to connect with the oidc account. as long as this is not set the admini account needs to be manually connected with oidc diff --git a/roles/docker-keycloak/templates/env.j2 b/roles/docker-keycloak/templates/env.j2 index c3aec2fe..87eabad0 100644 --- a/roles/docker-keycloak/templates/env.j2 +++ b/roles/docker-keycloak/templates/env.j2 @@ -19,4 +19,4 @@ KC_DB_PASSWORD= {{database_password}} # If the initial administrator already exists and the environment variables are still present at startup, an error message stating the failed creation of the initial administrator is shown in the logs. Keycloak ignores the values and starts up correctly. KC_BOOTSTRAP_ADMIN_USERNAME= {{users.administrator.username}} -KC_BOOTSTRAP_ADMIN_PASSWORD= {{users.administrator.initial_password}} \ No newline at end of file +KC_BOOTSTRAP_ADMIN_PASSWORD= {{users.administrator.password}} \ No newline at end of file diff --git a/roles/docker-ldap/templates/ldif/data/02_users.ldif.j2 b/roles/docker-ldap/templates/ldif/data/02_users.ldif.j2 index 35d1f684..5b210bf5 100644 --- a/roles/docker-ldap/templates/ldif/data/02_users.ldif.j2 +++ b/roles/docker-ldap/templates/ldif/data/02_users.ldif.j2 @@ -1,3 +1,8 @@ +{## +# Iterate over all users and create LDAP entries for each, then assign admin to application roles +# This template loops through a 'users' list variable where each user is a dict with keys: +# username, uid, gid, password (optional), sn (optional), cn (optional) +##} ####################################################################### # Container for Application Roles (if not already created) ####################################################################### @@ -6,36 +11,48 @@ objectClass: organizationalUnit ou: roles description: Container for application access profiles +{% for user in users %} ####################################################################### -# Create Admin User +# Create User {{ user.username }} ####################################################################### -dn: {{ldap.attributes.user_id}}={{users.administrator.username}},{{ldap.dn.users}} +dn: {{ ldap.attributes.user_id }}={{ user.username }},{{ ldap.dn.users }} objectClass: top objectClass: inetOrgPerson objectClass: posixAccount -{{ldap.attributes.user_id}}: {{users.administrator.username}} -sn: Administrator -cn: Administrator -userPassword: {SSHA}CHANGE_THIS_PASSWORD +{{ ldap.attributes.user_id }}: {{ user.username }} +sn: {{ user.username }} +cn: {{ user.username }} +userPassword: {{ user.password }} loginShell: /bin/bash -homeDirectory: /home/admin -uidNumber: {{users.administrator.uid}} -gidNumber: {{users.administrator.gid}} +homeDirectory: /home/{{ user.username }} +uidNumber: {{ user.uid }} +gidNumber: {{ user.gid }} ####################################################################### -# Add Admin User to All Application Role Groups +# Assign {{ user.username }} to application user roles ####################################################################### -{# Loop over each application defined in defaults_applications #} {% for app, config in defaults_applications.items() %} +dn: cn={{ app }}-user,{{ ldap.dn.application_roles }} +changetype: modify +add: roleOccupant +roleOccupant: {{ ldap.attributes.user_id }}={{ user.username }},{{ ldap.dn.users }} +{% endfor %} +{% endfor %} + +####################################################################### +# Add Admin User to All Application Role Groups (unchanged) +####################################################################### +{% for app, config in defaults_applications.items() %} dn: cn={{ app }}-administrator,{{ ldap.dn.application_roles }} changetype: modify add: roleOccupant -roleOccupant: {{ldap.attributes.user_id}}={{users.administrator.username}},{{ldap.dn.users}} +roleOccupant: {{ ldap.attributes.user_id }}={{ users.administrator.username }},{{ ldap.dn.users }} dn: cn={{ app }}-user,{{ ldap.dn.application_roles }} changetype: modify add: roleOccupant -roleOccupant: {{ldap.attributes.user_id}}={{users.administrator.username}},{{ldap.dn.users}} +roleOccupant: {{ ldap.attributes.user_id }}={{ users.administrator.username }},{{ ldap.dn.users }} {% endfor %} + diff --git a/roles/docker-listmonk/meta/main.yml b/roles/docker-listmonk/meta/main.yml index 4a6fe276..3895d735 100644 --- a/roles/docker-listmonk/meta/main.yml +++ b/roles/docker-listmonk/meta/main.yml @@ -19,4 +19,5 @@ galaxy_info: documentation: "https://s.veen.world/cymais" logo: class: "fa-solid fa-list" -dependencies: [] \ No newline at end of file +dependencies: + - docker-mailu \ No newline at end of file diff --git a/roles/docker-listmonk/vars/main.yml b/roles/docker-listmonk/vars/main.yml index a2f6f4c5..4ef477b6 100644 --- a/roles/docker-listmonk/vars/main.yml +++ b/roles/docker-listmonk/vars/main.yml @@ -70,7 +70,7 @@ listmonk_settings: value: '"{{ service_provider.platform.logo }}"' - key: "app.site_name" - value: '"Mailing list"' + value: '"{{ service_provider.company.titel }} Mailing list"' # - key: "bounce.enabled" # value: 'false' diff --git a/roles/docker-mailu/tasks/create-mailu-user-and-token.yml b/roles/docker-mailu/tasks/create-mailu-user-and-token.yml index 08d21ba0..07544978 100644 --- a/roles/docker-mailu/tasks/create-mailu-user-and-token.yml +++ b/roles/docker-mailu/tasks/create-mailu-user-and-token.yml @@ -1,7 +1,8 @@ --- # tasks/create-mailu-user-and-token.yml # -# Ensures a Mailu user exists and idempotently creates an API token for them. +# Ensures a Mailu user exists and idempotently creates an API token for them, +# storing tokens in a dictionary for targeted access. # # Required variables: # mailu_compose_dir: Path to your docker-compose.yml directory @@ -12,11 +13,11 @@ # mailu_global_api_token: Global API token (from API_TOKEN environment variable) # # Optional variable: -# mailu_user_token: Pre-existing API token for the user (if already created) +# mailu_user_tokens: Dictionary of existing tokens, e.g. { "alice": "secret" } - name: "Ensure Mailu user {{ mailu_user }}@{{ mailu_domain }} exists" command: > - docker compose exec admin flask mailu user {{ mailu_user }} {{ mailu_domain }} '{{ mailu_password }}' + docker compose exec admin flask mailu {{ mailu_action }} {{ mailu_user }} {{ mailu_domain }} '{{ mailu_password }}' args: chdir: "{{ mailu_compose_dir }}" register: mailu_user_creation @@ -51,19 +52,21 @@ body_format: json body: comment: "{{ mailu_user }}" - ip: "0.0.0.0/0" + ip: "{{ mailu_token_ip }}" status_code: 201 register: mailu_token_creation when: mailu_user_existing_token is not defined -- name: "Set mailu_user_token fact" +- name: "Set mailu_user_tokens dictionary" set_fact: - mailu_user_token: > - {{ (mailu_token_creation is defined) - ? mailu_token_creation.json.secret - : (mailu_user_existing_token.secret | default('')) }} + mailu_user_tokens: > + {{ (mailu_user_tokens | default({})) + | combine({ mailu_user: ((mailu_token_creation is defined) + | ternary(mailu_token_creation.json.secret, + mailu_user_existing_token.secret)) }) }} # Note: # - GET /tokens returns only metadata (id, comment, ip, created), not the secret itself. # - The secret is returned only by the POST request and must be captured when created. -# - Store mailu_user_token securely (e.g., in Ansible Vault) for future use. \ No newline at end of file +# - Tokens are stored in the mailu_user_tokens dictionary for targeted access. +# - Persist mailu_user_tokens securely (e.g., in Ansible Vault) for future use. \ No newline at end of file diff --git a/roles/docker-mailu/tasks/main.yml b/roles/docker-mailu/tasks/main.yml index 91975bc6..e4cc96dc 100644 --- a/roles/docker-mailu/tasks/main.yml +++ b/roles/docker-mailu/tasks/main.yml @@ -1,29 +1,48 @@ --- -- name: "include docker-central-database" - include_role: +- name: "Include docker-central-database" + include_role: name: docker-central-database + when: run_once_docker_mailu is not defined -- name: "include role nginx-domain-setup for {{application_id}}" +- name: "Include role nginx-domain-setup for {{ application_id }}" include_role: name: nginx-domain-setup vars: - domain: "{{ domains[application_id] }}" - http_port: "{{ ports.localhost.http[application_id] }}" - vars: + domain: "{{ domains[application_id] }}" + http_port: "{{ ports.localhost.http[application_id] }}" nginx_docker_reverse_proxy_extra_configuration: "client_max_body_size 31M;" + when: run_once_docker_mailu is not defined - name: "Include the nginx-docker-cert-deploy role" include_role: name: nginx-docker-cert-deploy + when: run_once_docker_mailu is not defined -- name: "copy docker-compose.yml and env file" +- name: "Copy docker-compose.yml and env file" include_tasks: copy-docker-compose-and-env.yml + when: run_once_docker_mailu is not defined -- name: flush docker service +- name: Flush docker service handlers meta: flush_handlers + when: run_once_docker_mailu is not defined -- name: create administrator account - command: - cmd: "docker compose -p mailu exec admin flask mailu admin {{users.administrator.username}} {{primary_domain}} {{applications.mailu.initial_administrator_password}}" - chdir: "{{docker_compose.directories.instance}}" - ignore_errors: true +- name: "Create Mailu accounts and API tokens" + include_tasks: create-mailu-user-and-token.yml + vars: + mailu_compose_dir: "{{ docker_compose.directories.instance }}" + mailu_domain: "{{ domain }}" + mailu_api_base_url: "{{ web_protocol }}://{{ domain }}/api/v1" + mailu_global_api_token: "{{ applications.mailu.credentials.api_token }}" + mailu_action: "{{ item.value.is_admin | default(false) | ternary('admin','user') }}" + mailu_user: "{{ item.key }}" + mailu_password: "{{ item.value.password }}" + mailu_token_ip: "{{ item.value.ip | default('') }}" + loop: "{{ users | dict2items }}" + loop_control: + loop_var: item + when: run_once_docker_mailu is not defined + +- name: Run the docker_mailu tasks once + set_fact: + run_once_docker_mailu: true + when: run_once_docker_mailu is not defined \ No newline at end of file diff --git a/roles/docker-mailu/templates/env.j2 b/roles/docker-mailu/templates/env.j2 index 6cb59888..0a842ad1 100644 --- a/roles/docker-mailu/templates/env.j2 +++ b/roles/docker-mailu/templates/env.j2 @@ -182,7 +182,7 @@ OIDC_CHANGE_PASSWORD_REDIRECT_ENABLED=True # 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}} -{% if applications[application_id].features.oidc | bool %} +{% if applications[application_id].oidc.email_by_username | 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 @. Defaults to email. OIDC_USERNAME_CLAIM={{oidc.attributes.username}} diff --git a/roles/docker-nextcloud/templates/env.j2 b/roles/docker-nextcloud/templates/env.j2 index ca488d99..e2e42190 100644 --- a/roles/docker-nextcloud/templates/env.j2 +++ b/roles/docker-nextcloud/templates/env.j2 @@ -25,7 +25,7 @@ MAIL_DOMAIN= "{{system_email.domain}}" # Initial Admin Data NEXTCLOUD_ADMIN_USER= "{{applications[application_id].users.administrator.username}}" -NEXTCLOUD_ADMIN_PASSWORD= "{{applications[application_id].users.administrator.initial_password}}" +NEXTCLOUD_ADMIN_PASSWORD= "{{applications[application_id].users.administrator.password}}" # Security diff --git a/roles/user-administrator/tasks/main.yml b/roles/user-administrator/tasks/main.yml index 4c125ebb..8cf7b338 100644 --- a/roles/user-administrator/tasks/main.yml +++ b/roles/user-administrator/tasks/main.yml @@ -2,7 +2,7 @@ user: name: administrator update_password: on_create - password: "{{ users.administrator.initial_password | password_hash('sha512') }}" + password: "{{ users.administrator.password | password_hash('sha512') }}" create_home: yes generate_ssh_key: yes ssh_key_type: rsa diff --git a/templates/vars/applications.yml.j2 b/templates/vars/applications.yml.j2 index 1fa6220a..58cb7e69 100644 --- a/templates/vars/applications.yml.j2 +++ b/templates/vars/applications.yml.j2 @@ -231,7 +231,7 @@ defaults_applications: # LDAP Account Manager lam: version: "latest" -# administrator_password: "{{users.administrator.initial_password}}" # CHANGE for security reasons +# administrator_password: "{{users.administrator.password}}" # CHANGE for security reasons oauth2_proxy: application: application # Needs to be the same as webinterface port: 80 # application port @@ -428,7 +428,7 @@ defaults_applications: users: administrator: username: "{{users.administrator.username}}" - initial_password: "{{users.administrator.initial_password}}" # Keep in mind to change the password fast after creation and activate 2FA + password: "{{users.administrator.password}}" # Keep in mind to change the password fast after creation and activate 2FA default_quota: '1000000000' # Quota to assign if no quota is specified in the OIDC response (bytes) legacy_login_mask: enabled: False # If true, then legacy login mask is shown. Otherwise just SSO @@ -680,7 +680,7 @@ defaults_applications: users: administrator: email: "{{ users.administrator.email }}" # Initial login email address - password: "{{ users.administrator.initial_password }}" # Initial login password – should be overridden in inventory for security + password: "{{ users.administrator.password }}" # Initial login password – should be overridden in inventory for security oauth2_proxy: application: "application" port: "80"