From c1c2921ce5c1626b24e066532efce06d951d11b9 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Fri, 20 Jun 2025 00:43:43 +0200 Subject: [PATCH 1/4] Added correct username policy, compatible with all oidc services --- roles/docker-keycloak/templates/import/realm.json.j2 | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/roles/docker-keycloak/templates/import/realm.json.j2 b/roles/docker-keycloak/templates/import/realm.json.j2 index 8420cf0f..24929abd 100644 --- a/roles/docker-keycloak/templates/import/realm.json.j2 +++ b/roles/docker-keycloak/templates/import/realm.json.j2 @@ -1785,6 +1785,18 @@ "config": {} } ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "47f273d5-f2b9-47b7-8026-a521e797afcd", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": { + "kc.user.profile.config": [ + "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"pattern\":{\"pattern\":\"^[a-z0-9]+$\",\"error-message\":\"\"}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}" + ] + } + } + ], "org.keycloak.storage.UserStorageProvider": [ { "id": "bBD2l6kVRMaSABbfOJVRaw", From 7abfe8502178e7fb6b14b90c1c9e81a579be7ace Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Fri, 20 Jun 2025 03:00:35 +0200 Subject: [PATCH 2/4] Solved https bug --- roles/docker-discourse/templates/discourse_application.yml.j2 | 1 + 1 file changed, 1 insertion(+) diff --git a/roles/docker-discourse/templates/discourse_application.yml.j2 b/roles/docker-discourse/templates/discourse_application.yml.j2 index bfe5c066..aad39d9c 100644 --- a/roles/docker-discourse/templates/discourse_application.yml.j2 +++ b/roles/docker-discourse/templates/discourse_application.yml.j2 @@ -115,6 +115,7 @@ hooks: ## Any custom commands to run after building run: - exec: echo "Beginning of custom commands" + - exec: rails r "SiteSetting.force_https = true" ## If you want to set the 'From' email address for your first registration, uncomment and change: ## After getting the first signup email, re-comment the line. It only needs to run once. #- exec: rails r "SiteSetting.notification_email='info@unconfigured.discourse.org'" From 13141ac7d6f39ed10cf8ce8a78c368c9786dfd66 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Fri, 20 Jun 2025 03:55:51 +0200 Subject: [PATCH 3/4] Solved discourse mail bug --- roles/docker-discourse/Debug.md | 106 ++++++++++++++++++ .../templates/discourse_application.yml.j2 | 3 +- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 roles/docker-discourse/Debug.md diff --git a/roles/docker-discourse/Debug.md b/roles/docker-discourse/Debug.md new file mode 100644 index 00000000..c0713f9b --- /dev/null +++ b/roles/docker-discourse/Debug.md @@ -0,0 +1,106 @@ +## Discourse Debugging Guide for Docker Role + +This document explains how to locate and use key log file paths on both the host and inside the container for a Docker-based Discourse installation deployed via the `docker-discourse` role. + +### 1. Host Paths + +Discourse log files are stored in the Docker volume named `discourse_data`. On the host, you can find them at: + +* **Rails Production Log**: + + ```bash + cat /var/lib/docker/volumes/discourse_data/_data/log/rails/production.log | grep -i mail + ``` + + Filters for email-related entries: + + * **Queued emails**: `Email::Sender: queued mail to user@example.com` + * **Errors**: e.g. `Net::SMTPAuthenticationError`, `SMTPConnectionError` + +* **Sidekiq Log**: + + ```bash + cat /var/lib/docker/volumes/discourse_data/_data/log/sidekiq.log | grep -i mail + ``` + + Shows asynchronous mail job executions, retries, and failures. + +### 2. Inside the Container + +To inspect logs within the container, enter it: + +```bash +cd /var/discourse +./launcher enter app +``` + +Logs are mounted under `/var/log` inside the container: + +* **Rails Production Log**: + + ```bash + tail -n 200 /var/log/rails/production.log | grep -i mail + ``` + + * **Info**: `I, [timestamp] INFO -- : Email::Sender: queued mail to ...` + * **Error**: `E, [timestamp] ERROR -- : Net::SMTPSyntaxError ...` + +* **Sidekiq Log**: + + ```bash + tail -n 200 /var/log/sidekiq.log | grep -i mail + ``` + + * **Execution**: `Mail::MessageJob JID-...` + * **Retries/Exceptions** on delivery failure. + +### 3. Live Streaming Logs + +For real-time monitoring while reproducing an issue: + +```bash +# On host: + +tail -f \ + /var/lib/docker/volumes/discourse_data/_data/log/rails/production.log \ + /var/lib/docker/volumes/discourse_data/_data/log/sidekiq.log | grep -i mail + +# Or inside container: +tail -f /var/log/rails/production.log /var/log/sidekiq.log | grep -i mail +``` + +### 4. Enabling Verbose Email Debugging + +For detailed SMTP conversation logging: + +```bash +# Inside container +rails c +> Discourse.debug_email = true +``` + +Send a test email: + +```bash +rails c +> UserMailer.test_email("you@example.com").deliver_now +``` + +Then check logs for the full SMTP handshake details. + +### 5. Flushing Redis Cache + +After configuration changes, clear Redis to remove stale session or cached data: + +```bash +# Inside container +rails r "Redis.new.flushall" +``` + +### 6. Sidekiq Web UI + +In the Admin UI under **Plugins → Sidekiq**, monitor queues, retries, and failed jobs for additional context. + +--- + +Use this guide to quickly locate and interpret Discourse logs on both host and container, enabling efficient debugging of email delivery and background job issues in a Docker deployment managed by the `docker-discourse` role. diff --git a/roles/docker-discourse/templates/discourse_application.yml.j2 b/roles/docker-discourse/templates/discourse_application.yml.j2 index aad39d9c..bf8fa903 100644 --- a/roles/docker-discourse/templates/discourse_application.yml.j2 +++ b/roles/docker-discourse/templates/discourse_application.yml.j2 @@ -62,7 +62,8 @@ env: DISCOURSE_SMTP_PORT: {{ system_email.port }} DISCOURSE_SMTP_USER_NAME: {{ users['no-reply'].email }} DISCOURSE_SMTP_PASSWORD: {{ users['no-reply'].mailu_token }} - DISCOURSE_SMTP_ENABLE_START_TLS: {{ system_email.start_tls | upper }} + DISCOURSE_SMTP_ENABLE_START_TLS: {{ system_email.start_tls }} + DISCOURSE_SMTP_FORCE_TLS: {{ system_email.tls }} DISCOURSE_SMTP_DOMAIN: {{ system_email.domain }} DISCOURSE_NOTIFICATION_EMAIL: {{ users['no-reply'].email }} From 1ed26ab706ac6ea37822d9c7de4043ee075e0fdb Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Fri, 20 Jun 2025 05:45:43 +0200 Subject: [PATCH 4/4] Implemented discourse LDAP draft --- group_vars/all/12_iam.yml | 13 +++-- roles/docker-compose/handlers/main.yml | 8 +-- roles/docker-discourse/Todo.md | 2 + roles/docker-discourse/handlers/main.yml | 4 +- .../templates/discourse_application.yml.j2 | 49 +++++++++++++++---- roles/docker-discourse/vars/configuration.yml | 3 +- .../vars/plugins/user_ldap.yml | 2 +- 7 files changed, 55 insertions(+), 26 deletions(-) create mode 100644 roles/docker-discourse/Todo.md diff --git a/group_vars/all/12_iam.yml b/group_vars/all/12_iam.yml index 90866c9c..00288b8f 100644 --- a/group_vars/all/12_iam.yml +++ b/group_vars/all/12_iam.yml @@ -40,6 +40,7 @@ defaults_oidc: # Keep in mind to mapp this variables if there is ever the possibility for the user to define them in the inventory _ldap_dn_base: "dc={{primary_domain_sld}},dc={{primary_domain_tld}}" _ldap_server_port: "{% if applications.ldap.network.docker | bool %}{{ ports.localhost.ldap.ldap }}{% else %}{{ ports.localhost.ldaps.ldap }}{% endif %}" +_ldap_user_id: "uid" ldap: # Distinguished Names (DN) @@ -56,7 +57,7 @@ ldap: application_roles: "ou=application_roles,{{_ldap_dn_base}}" attributes: # Attribut to identify the user - user_id: "uid" + user_id: "{{ _ldap_user_id }}" # Password to access dn.bind bind_credential: "{{applications.ldap.credentials.administrator_database_password}}" server: @@ -66,7 +67,9 @@ ldap: network: local: "{{applications.ldap.network.docker}}" # Uses the application configuration to define if local network should be available or not user_objects: - - person # Basic person attributes (sn, cn …) – RFC 4519 - - inetOrgPerson # Extended Internet / intranet person – RFC 2798 - - posixAccount # POSIX/UNIX login attributes (uidNumber, gidNumber …) – RFC 2307 - - nextcloudUser # Nextcloud-specific auxiliary attributes (nextcloudQuota, nextcloudEnabled) – Nextcloud schema + - person # Basic person attributes (sn, cn …) – RFC 4519 + - inetOrgPerson # Extended Internet / intranet person – RFC 2798 + - posixAccount # POSIX/UNIX login attributes (uidNumber, gidNumber …) – RFC 2307 + - nextcloudUser # Nextcloud-specific auxiliary attributes (nextcloudQuota, nextcloudEnabled) – Nextcloud schema + filters: + user_filter: "(&(|(objectclass=inetOrgPerson))({{_ldap_user_id}}=%{{_ldap_user_id}}))" \ No newline at end of file diff --git a/roles/docker-compose/handlers/main.yml b/roles/docker-compose/handlers/main.yml index 61b33246..c68dd9a8 100644 --- a/roles/docker-compose/handlers/main.yml +++ b/roles/docker-compose/handlers/main.yml @@ -10,13 +10,7 @@ # default setup for docker compose files - name: docker compose up - shell: > - docker-compose -p {{ application_id }} up -d --force-recreate --remove-orphans - 2>&1 | tee >(systemd-cat -t docker-compose-{{ application_id }}) - shell: > - docker-compose -p {{ application_id }} up -d - --force-recreate --remove-orphans --build - 2>&1 | tee >(systemd-cat -t docker-compose-{{ application_id }}) + shell: docker-compose -p {{ application_id }} up -d --force-recreate --remove-orphans --build args: chdir: "{{ docker_compose.directories.instance }}" executable: /bin/bash diff --git a/roles/docker-discourse/Todo.md b/roles/docker-discourse/Todo.md new file mode 100644 index 00000000..26940718 --- /dev/null +++ b/roles/docker-discourse/Todo.md @@ -0,0 +1,2 @@ +# Todo +- Finish LDAP implementation \ No newline at end of file diff --git a/roles/docker-discourse/handlers/main.yml b/roles/docker-discourse/handlers/main.yml index a8e61e22..393e3bcc 100644 --- a/roles/docker-discourse/handlers/main.yml +++ b/roles/docker-discourse/handlers/main.yml @@ -17,9 +17,7 @@ listen: recreate discourse - name: rebuild discourse - shell: > - ./launcher rebuild {{applications[application_id].container}} - 2>&1 | tee >(systemd-cat -t rebuild-{{ application_id }}) + shell: ./launcher rebuild {{applications[application_id].container}} args: executable: /bin/bash chdir: "{{docker_repository_directory }}" diff --git a/roles/docker-discourse/templates/discourse_application.yml.j2 b/roles/docker-discourse/templates/discourse_application.yml.j2 index bf8fa903..31bdf22d 100644 --- a/roles/docker-discourse/templates/discourse_application.yml.j2 +++ b/roles/docker-discourse/templates/discourse_application.yml.j2 @@ -103,15 +103,22 @@ hooks: - exec: cd: $home/plugins cmd: - - git clone --depth=1 https://github.com/discourse/docker_manager.git - - git clone --depth=1 https://github.com/discourse/discourse-activity-pub.git - - git clone --depth=1 https://github.com/discourse/discourse-calendar.git - - git clone --depth=1 https://github.com/discourse/discourse-akismet.git - - git clone --depth=1 https://github.com/discourse/discourse-cakeday.git - - git clone --depth=1 https://github.com/discourse/discourse-solved.git - - git clone --depth=1 https://github.com/discourse/discourse-voting.git - - git clone --depth=1 https://github.com/discourse/discourse-oauth2-basic.git - - git clone --depth=1 https://github.com/discourse/discourse-openid-connect.git + - git clone --depth=1 https://github.com/discourse/docker_manager.git + - git clone --depth=1 https://github.com/discourse/discourse-activity-pub.git + - git clone --depth=1 https://github.com/discourse/discourse-calendar.git + - git clone --depth=1 https://github.com/discourse/discourse-akismet.git + - git clone --depth=1 https://github.com/discourse/discourse-cakeday.git + - git clone --depth=1 https://github.com/discourse/discourse-solved.git + - git clone --depth=1 https://github.com/discourse/discourse-voting.git + - git clone --depth=1 https://github.com/discourse/discourse-oauth2-basic.git + +{% if applications | is_feature_enabled('oidc',application_id) %} + - git clone --depth=1 https://github.com/discourse/discourse-openid-connect.git +{% endif %} + +{% if applications | is_feature_enabled('ldap',application_id) %} + - git clone --depth=1 https://github.com/jonmbake/discourse-ldap-auth.git +{% endif %} ## Any custom commands to run after building run: @@ -143,6 +150,30 @@ run: - exec: rails r "SiteSetting.openid_connect_allow_association_change = false" - exec: rails r "SiteSetting.openid_connect_rp_initiated_logout = true" {% endif %} + +{% if applications | is_feature_enabled('ldap',application_id) %} + # Enable LDAP authentication + - exec: rails r "SiteSetting.ldap_auth_enabled = true" + - exec: rails r "SiteSetting.ldap_sync_enabled = true" + + # LDAP connection settings + - exec: rails r "SiteSetting.ldap_sync_host = '{{ ldap.server.domain }}'" + - exec: rails r "SiteSetting.ldap_sync_port = {{ ldap.server.port }}" + - exec: rails r "SiteSetting.ldap_encryption = 'simple_tls'" + - exec: rails r "SiteSetting.ldap_base_dn = '{{ ldap.dn.root }}'" + - exec: rails r "SiteSetting.ldap_bind_dn = '{{ ldap.dn.administrator }}'" + - exec: rails r "SiteSetting.ldap_bind_password = '{{ ldap.bind_credential }}'" + + # LDAP additional configuration + - exec: rails r "SiteSetting.ldap_user_filter = '{{ ldap.filters.user_filter }}'" + - exec: rails r "SiteSetting.ldap_group_base_dn = '{{ ldap.dn.groups }}'" + - exec: rails r "SiteSetting.ldap_group_member_check = 'memberUid'" + + - exec: rails r "SiteSetting.ldap_sync_period = 1" + - exec: rails r "SiteSetting.ldap_sync_unit = 'hours'" + +{% endif %} + - exec: echo "End of custom commands" docker_args: diff --git a/roles/docker-discourse/vars/configuration.yml b/roles/docker-discourse/vars/configuration.yml index c5b19c84..d41f6a1a 100644 --- a/roles/docker-discourse/vars/configuration.yml +++ b/roles/docker-discourse/vars/configuration.yml @@ -5,9 +5,10 @@ credentials: features: matomo: true css: true - portfolio_iframe: false + portfolio_iframe: false oidc: true central_database: true + ldap: false # @todo implement and activate csp: flags: style-src: diff --git a/roles/docker-nextcloud/vars/plugins/user_ldap.yml b/roles/docker-nextcloud/vars/plugins/user_ldap.yml index 36ac3c1f..ac2bfb4f 100644 --- a/roles/docker-nextcloud/vars/plugins/user_ldap.yml +++ b/roles/docker-nextcloud/vars/plugins/user_ldap.yml @@ -107,7 +107,7 @@ plugin_configuration: - appid: "user_ldap" configkey: "s01ldap_login_filter" - configvalue: "(&(|(objectclass=inetOrgPerson))({{ldap.attributes.user_id}}=%{{ldap.attributes.user_id}}))" + configvalue: "{{ ldap.filters.user_filter }}" - appid: "user_ldap" configkey: "s01ldap_login_filter_mode"