From ab258cb6ddf468dd4cbd8786269f33d5fc2d26e9 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Mon, 24 Feb 2025 23:53:12 +0100 Subject: [PATCH] Added LDAP integration for Nextcloud and optimized CSS --- group_vars/all/11_iam.yml | 4 +- roles/docker-funkwhale/templates/env.j2 | 2 +- .../templates/import/realm.json.j2 | 2 +- roles/docker-ldap/handlers/main.yml | 2 +- .../templates/docker-compose.yml.j2 | 2 +- roles/docker-ldap/templates/env.j2 | 2 +- roles/docker-ldap/templates/lam.env.j2 | 2 +- roles/docker-nextcloud/tasks/ldap.yml | 14 +- roles/docker-nextcloud/vars/ldap.yml | 183 ++++++++++++++++++ .../templates/global.css.j2 | 108 +++++------ 10 files changed, 252 insertions(+), 69 deletions(-) create mode 100644 roles/docker-nextcloud/vars/ldap.yml diff --git a/group_vars/all/11_iam.yml b/group_vars/all/11_iam.yml index 7f81a08f..f6a2bed5 100644 --- a/group_vars/all/11_iam.yml +++ b/group_vars/all/11_iam.yml @@ -39,9 +39,11 @@ ldap: # Defines the base Distinguished Name (DN) for the LDAP directory, constructed from the second-level domain (SLD) and top-level domain (TLD). root: "{{_ldap_dn_base}}" # Specifies the Distinguished Name (DN) of the LDAP administrator, combining the admin's username with the LDAP root domain. - bind: "cn={{applications.ldap.administrator_username}},{{_ldap_dn_base}}" + administrator: "cn={{applications.ldap.administrator_username}},{{_ldap_dn_base}}" # Dn from which the users should be read users: "ou=users,{{_ldap_dn_base}}" + # Dn from which the groups should be read + groups: "ou=groups,{{_ldap_dn_base}}" # Dn for all application roles of the users application_roles: "ou=application_roles,{{_ldap_dn_base}}" # Password to access dn.bind diff --git a/roles/docker-funkwhale/templates/env.j2 b/roles/docker-funkwhale/templates/env.j2 index fd4b3ac2..d34add61 100644 --- a/roles/docker-funkwhale/templates/env.j2 +++ b/roles/docker-funkwhale/templates/env.j2 @@ -109,7 +109,7 @@ DJANGO_SECRET_KEY={{funkwhale_django_secret}} LDAP_ENABLED = True LDAP_SERVER_URI = "{{ldap.server.uri}}" -LDAP_BIND_DN = "{{ldap.dn.bind}}" +LDAP_BIND_DN = "{{ldap.dn.administrator}}" LDAP_BIND_PASSWORD = "{{ldap.bind_credential}}" LDAP_SEARCH_FILTER = "(|(cn={0})(mail={0}))" LDAP_START_TLS = False diff --git a/roles/docker-keycloak/templates/import/realm.json.j2 b/roles/docker-keycloak/templates/import/realm.json.j2 index 5496cbba..0e43919b 100644 --- a/roles/docker-keycloak/templates/import/realm.json.j2 +++ b/roles/docker-keycloak/templates/import/realm.json.j2 @@ -1948,7 +1948,7 @@ "uid" ], "bindDn": [ - "{{ldap.dn.bind}}" + "{{ldap.dn.administrator}}" ], "lastSync": [ "1737578007" diff --git a/roles/docker-ldap/handlers/main.yml b/roles/docker-ldap/handlers/main.yml index fa395db4..947a2eeb 100644 --- a/roles/docker-ldap/handlers/main.yml +++ b/roles/docker-ldap/handlers/main.yml @@ -22,7 +22,7 @@ - name: "Import users, groups, etc. to LDAP" shell: > - docker exec -i openldap ldapadd -x -D "{{ldap.dn.bind}}" -w "{{ldap.bind_credential}}" -c -f "{{ldif_docker_path}}import/{{ item | basename | regex_replace(r'\.j2$', '') }}" + docker exec -i openldap ldapadd -x -D "{{ldap.dn.administrator}}" -w "{{ldap.bind_credential}}" -c -f "{{ldif_docker_path}}import/{{ item | basename | regex_replace(r'\.j2$', '') }}" register: ldapadd_result changed_when: "'adding new entry' in ldapadd_result.stdout" # Allow return code 0 (all entries added) or 68 (entry already exists) diff --git a/roles/docker-ldap/templates/docker-compose.yml.j2 b/roles/docker-ldap/templates/docker-compose.yml.j2 index 12c35b8b..6a35bac8 100644 --- a/roles/docker-ldap/templates/docker-compose.yml.j2 +++ b/roles/docker-ldap/templates/docker-compose.yml.j2 @@ -34,7 +34,7 @@ services: - '{{ldif_host_path}}:{{ldif_docker_path}}:ro' # Mounting all ldif files for import healthcheck: test: > - ldapsearch -x -H ldap://localhost:{{ldap_docker_port}} -b "{{ldap.dn.root}}" -D "{{ldap.dn.bind}}" -w "{{ldap.bind_credential}}" + ldapsearch -x -H ldap://localhost:{{ldap_docker_port}} -b "{{ldap.dn.root}}" -D "{{ldap.dn.administrator}}" -w "{{ldap.bind_credential}}" interval: 30s timeout: 10s retries: 3 diff --git a/roles/docker-ldap/templates/env.j2 b/roles/docker-ldap/templates/env.j2 index 917ec692..961ec4ff 100644 --- a/roles/docker-ldap/templates/env.j2 +++ b/roles/docker-ldap/templates/env.j2 @@ -12,7 +12,7 @@ LDAP_PASSWORDS= ' ' # Comma separated li LDAP_ROOT= {{ldap.dn.root}} # LDAP baseDN (or suffix) of the LDAP tree. Default: dc=example,dc=org ## Admin -LDAP_ADMIN_DN= {{ldap.dn.bind}} +LDAP_ADMIN_DN= {{ldap.dn.administrator}} LDAP_CONFIG_ADMIN_ENABLED= yes LDAP_CONFIG_ADMIN_USERNAME= {{applications.ldap.administrator_username}} LDAP_CONFIG_ADMIN_PASSWORD= {{applications.ldap.administrator_password}} diff --git a/roles/docker-ldap/templates/lam.env.j2 b/roles/docker-ldap/templates/lam.env.j2 index 5ccb745b..f5d4b75a 100644 --- a/roles/docker-ldap/templates/lam.env.j2 +++ b/roles/docker-ldap/templates/lam.env.j2 @@ -9,5 +9,5 @@ LAM_CONFIGURATION_DATABASE= files # LDAP Configuration LDAP_SERVER= {{ldap.server.domain}} # domain of LDAP database root entry LDAP_BASE_DN= {{ldap.dn.root}} # LDAP base DN to overwrite value generated by LDAP_DOMAIN -LDAP_USER= {{ldap.dn.bind}} # LDAP admin user (set as login user for LAM) +LDAP_USER= {{ldap.dn.administrator}} # LDAP admin user (set as login user for LAM) LDAP_ADMIN_PASSWORD= {{ldap.bind_credential}} # LDAP admin password \ No newline at end of file diff --git a/roles/docker-nextcloud/tasks/ldap.yml b/roles/docker-nextcloud/tasks/ldap.yml index 44d67a91..640588cf 100644 --- a/roles/docker-nextcloud/tasks/ldap.yml +++ b/roles/docker-nextcloud/tasks/ldap.yml @@ -1,6 +1,16 @@ # @See https://docs.nextcloud.com/server/latest/admin_manual/configuration_user/user_auth_ldap.html # @See https://chatgpt.com/c/67aa2d21-cb4c-800f-b1be-8629b6bd3f55 # @todo implement -#docker compose exec -u www-data application php occ app:enable user_ldap -occ config:app:set user_ldap installed_version --value "1.21.0" \ No newline at end of file +- name: Activate Nextcloud LDAP App + command: "docker exec -u www-data {{ nextcloud_application_container_name }} php occ app:enable user_ldap" + +- name: Load LDAP Nextcloud configuration variables + include_vars: + file: ldap.yml + +- name: Set Nextcloud LDAP config + loop: "{{ nextcloud_ldap_configuration }}" + command: > + docker exec -u www-data {{ nextcloud_application_container_name }} + php occ config:app:set {{ item.appid }} {{ item.configkey }} --value "{{ item.configvalue }}" diff --git a/roles/docker-nextcloud/vars/ldap.yml b/roles/docker-nextcloud/vars/ldap.yml new file mode 100644 index 00000000..4bfc60a7 --- /dev/null +++ b/roles/docker-nextcloud/vars/ldap.yml @@ -0,0 +1,183 @@ +nextcloud_ldap_configuration: + - + appid: "user_ldap" + configkey: "background_sync_interval" + configvalue: 43200 + + - + appid: "user_ldap" + configkey: "background_sync_offset" + configvalue: 0 + + - + appid: "user_ldap" + configkey: "background_sync_prefix" + configvalue: "s01" + + - + appid: "user_ldap" + configkey: "enabled" + configvalue: "yes" + + - + appid: "user_ldap" + configkey: "s01last_jpegPhoto_lookup" + configvalue: 0 + + - + appid: "user_ldap" + configkey: "s01ldap_agent_password" + configvalue: "{{ldap.bind_credential}}" + + - + appid: "user_ldap" + configkey: "s01ldap_backup_port" + configvalue: "{{ ports.localhost.ldap.openldap }}" # This is just optimized for local port @todo implement for external ports as well + + - + appid: "user_ldap" + configkey: "s01ldap_base" + configvalue: "{{ldap.dn.root}}" + + - + appid: "user_ldap" + configkey: "s01ldap_base_groups" + configvalue: "{{ldap.dn.groups}}" + + - + appid: "user_ldap" + configkey: "s01ldap_base_users" + configvalue: "{{ldap.dn.users}}" + + - + appid: "user_ldap" + configkey: "s01ldap_cache_ttl" + configvalue: 600 + + - + appid: "user_ldap" + configkey: "s01ldap_configuration_active" + configvalue: 1 + + - + appid: "user_ldap" + configkey: "s01ldap_connection_timeout" + configvalue: 15 + + - + appid: "user_ldap" + configkey: "s01ldap_display_name" + configvalue: "cn" + + - + appid: "user_ldap" + configkey: "s01ldap_dn" + configvalue: "{{ldap.dn.administrator}}" + - + appid: "user_ldap" + configkey: "s01ldap_email_attr" + configvalue: "mail" + - + appid: "user_ldap" + configkey: "s01ldap_experienced_admin" + configvalue: 0 + - + appid: "user_ldap" + configkey: "s01ldap_gid_number" + configvalue: "gidNumber" + - + appid: "user_ldap" + configkey: "s01ldap_group_display_name" + configvalue: "cn" + - + appid: "user_ldap" + configkey: "s01ldap_group_filter" + configvalue: "(&(|(objectclass=groupOfUniqueNames)(objectclass=posixGroup)))" + - + appid: "user_ldap" + configkey: "s01ldap_group_filter_mode" + configvalue: 0 + - + appid: "user_ldap" + configkey: "s01ldap_group_member_assoc_attribute" + configvalue: "uniqueMember" + - + appid: "user_ldap" + configkey: "s01ldap_groupfilter_objectclass" + configvalue: "groupOfUniqueNames\nposixGroup" + - + appid: "user_ldap" + configkey: "s01ldap_host" + configvalue: "openldap" + - + appid: "user_ldap" + configkey: "s01ldap_login_filter" + configvalue: "(&(|(objectclass=inetOrgPerson))(uid=%uid))" + - + appid: "user_ldap" + configkey: "s01ldap_login_filter_mode" + configvalue: 0 + - + appid: "user_ldap" + configkey: "s01ldap_loginfilter_email" + configvalue: 0 + - + appid: "user_ldap" + configkey: "s01ldap_loginfilter_username" + configvalue: 1 + - + appid: "user_ldap" + configkey: "s01ldap_mark_remnants_as_disabled" + configvalue: 0 + - + appid: "user_ldap" + configkey: "s01ldap_matching_rule_in_chain_state" + configvalue: "unknown" + - + appid: "user_ldap" + configkey: "s01ldap_nested_groups" + configvalue: 0 + - + appid: "user_ldap" + configkey: "s01ldap_paging_size" + configvalue: 500 + - + appid: "user_ldap" + configkey: "s01ldap_port" + configvalue: 389 + - + appid: "user_ldap" + configkey: "s01ldap_turn_off_cert_check" + configvalue: 0 + - + appid: "user_ldap" + configkey: "s01ldap_turn_on_pwd_change" + configvalue: 0 + - + appid: "user_ldap" + configkey: "s01ldap_user_avatar_rule" + configvalue: "default" + - + appid: "user_ldap" + configkey: "s01ldap_user_filter_mode" + configvalue: 0 + - + appid: "user_ldap" + configkey: "s01ldap_userfilter_objectclass" + configvalue: "inetOrgPerson" + - + appid: "user_ldap" + configkey: "s01ldap_userlist_filter" + configvalue: "(|(objectclass=inetOrgPerson))" + - + appid: "user_ldap" + configkey: "s01use_memberof_to_detect_membership" + configvalue: 1 + - + appid: "user_ldap" + configkey: "types" + configvalue: "authentication" + - + appid: "user_ldap" + configkey: "s01ldap_expert_username_attr" + configvalue: "uid" \ No newline at end of file diff --git a/roles/nginx-modifier-css/templates/global.css.j2 b/roles/nginx-modifier-css/templates/global.css.j2 index 67b7603a..618bcac3 100644 --- a/roles/nginx-modifier-css/templates/global.css.j2 +++ b/roles/nginx-modifier-css/templates/global.css.j2 @@ -54,15 +54,12 @@ HINT: --bs-white: var(--color-99); /* Original tone: White (#fff) */ --bs-gray: var(--color-50); /* Original tone: Gray (#6c757d) */ --bs-gray-dark: var(--color-20); /* Original tone: Dark Gray (#343a40) */ - --bs-gray-100: var(--color-95); /* Original tone: Very Light Gray (#f8f9fa) */ - --bs-gray-200: var(--color-90); /* Original tone: Lighter Gray (#e9ecef) */ - --bs-gray-300: var(--color-85); /* Original tone: Light Gray (#dee2e6) */ - --bs-gray-400: var(--color-80); /* Original tone: Gray (#ced4da) */ - --bs-gray-500: var(--color-70); /* Original tone: Medium Gray (#adb5bd) */ - --bs-gray-600: var(--color-50); /* Original tone: Gray (#6c757d) */ - --bs-gray-700: var(--color-40); /* Original tone: Darker Gray (#495057) */ - --bs-gray-800: var(--color-20); /* Original tone: Dark Gray (#343a40) */ - --bs-gray-900: var(--color-10); /* Original tone: Very Dark Gray (#212529) */ +{% for i in range(1, 10) %} +{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #} + {% set gray = i * 100 %} + {% set color = 100 - i * 10 %} + --bs-gray-{{ gray }}: var(--color-{{ "%02d" % color }}); +{% endfor %} --bs-primary: var(--color-65); /* Original tone: Blue (#0d6efd) */ --bs-light: var(--color-95); /* Original tone: Light (#f8f9fa) */ --bs-dark: var(--color-10); /* Original tone: Dark (#212529) */ @@ -147,15 +144,12 @@ HINT: --primary-high: var(--color-40); /* originally #487096 */ --primary-very-high: var(--color-20); /* originally #34516d */ --primary-50: var(--color-99); /* originally #f7f9fb */ - --primary-100: var(--color-95); /* originally #eef3f7 */ - --primary-200: var(--color-90); /* originally #e3ebf2 */ - --primary-300: var(--color-80); /* originally #c7d6e4 */ - --primary-400: var(--color-75); /* originally #acc2d7 */ - --primary-500: var(--color-70); /* originally #90aeca */ - --primary-600: var(--color-60); /* originally #7499bd */ - --primary-700: var(--color-50); /* originally #5381ad */ - --primary-800: var(--color-40); /* originally #487096 */ - --primary-900: var(--color-20); /* originally #34516d */ +{% for i in range(1, 10) %} +{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #} + {% set primary = i * 100 %} + {% set color = 100 - i * 8 %} + --primary-{{ primary }}: var(--color-{{ "%02d" % color }}); +{% endfor %} /* Header Primary Scale */ --header_primary-low: rgb(var(--color-rgb-75)); /* rgb(128, 180, 209) */ @@ -177,15 +171,12 @@ HINT: --tertiary-high: var(--color-40); /* originally #5886a0 */ --tertiary-hover: var(--color-20); /* originally #314a59 */ --tertiary-50: var(--color-99); /* originally #eaf0f3 */ - --tertiary-100: var(--color-95); /* originally #e6edf1 */ - --tertiary-200: var(--color-90); /* originally #e4ebf0 */ - --tertiary-300: var(--color-85); /* originally #dfe8ee */ - --tertiary-400: var(--color-75); /* originally #c8d8e1 */ - --tertiary-500: var(--color-65); /* originally #b1c7d4 */ - --tertiary-600: var(--color-60); /* originally #96b4c5 */ - --tertiary-700: var(--color-55); /* originally #80a5b9 */ - --tertiary-800: var(--color-50); /* originally #6b96ae */ - --tertiary-900: var(--color-40); /* originally #5886a0 */ +{% for i in range(1, 10) %} +{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #} + {% set tertiary = i * 100 %} + {% set color = 100 - i * 5 %} + --tertiary-{{ tertiary }}: var(--color-{{ "%02d" % color }}); +{% endfor %} /* Quaternary */ --quaternary-low: var(--color-80); /* originally #cfe0ea */ @@ -237,15 +228,11 @@ HINT: --color-primary-light-7: var(--color-61); /* Alpha variants reference the base RGB variable */ - --color-primary-alpha-10: rgba(var(--color-rgb-50), 0.10); - --color-primary-alpha-20: rgba(var(--color-rgb-50), 0.20); - --color-primary-alpha-30: rgba(var(--color-rgb-50), 0.30); - --color-primary-alpha-40: rgba(var(--color-rgb-50), 0.40); - --color-primary-alpha-50: rgba(var(--color-rgb-50), 0.50); - --color-primary-alpha-60: rgba(var(--color-rgb-50), 0.60); - --color-primary-alpha-70: rgba(var(--color-rgb-50), 0.70); - --color-primary-alpha-80: rgba(var(--color-rgb-50), 0.80); - --color-primary-alpha-90: rgba(var(--color-rgb-50), 0.90); +{% for i in range(1, 10) %} +{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #} + {% set alpha = i * 10 %} + --color-primary-alpha-{{ alpha }}: rgba(var(--color-rgb-50), 0.{{ alpha }}); +{% endfor %} --color-primary-hover: var(--color-primary-dark-1); --color-primary-active: var(--color-primary-dark-2); @@ -270,15 +257,11 @@ HINT: --color-secondary-light-3: var(--color-94); --color-secondary-light-4: var(--color-95); - --color-secondary-alpha-10: rgba(var(--color-rgb-80), 0.10); - --color-secondary-alpha-20: rgba(var(--color-rgb-80), 0.20); - --color-secondary-alpha-30: rgba(var(--color-rgb-80), 0.30); - --color-secondary-alpha-40: rgba(var(--color-rgb-80), 0.40); - --color-secondary-alpha-50: rgba(var(--color-rgb-80), 0.50); - --color-secondary-alpha-60: rgba(var(--color-rgb-80), 0.60); - --color-secondary-alpha-70: rgba(var(--color-rgb-80), 0.70); - --color-secondary-alpha-80: rgba(var(--color-rgb-80), 0.80); - --color-secondary-alpha-90: rgba(var(--color-rgb-80), 0.90); +{% for i in range(1, 10) %} +{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #} + {% set alpha = i * 10 %} + --color-secondary-alpha-{{ alpha }}: rgba(var(--color-rgb-80), 0.{{ alpha }}); +{% endfor %} --color-secondary-button: var(--color-secondary-dark-4); --color-secondary-hover: var(--color-secondary-dark-5); @@ -370,18 +353,12 @@ HINT: /* Keycloak */ :root{ /* --- Palette Black (Graustufen) --- */ - --pf-v5-global--palette--black-100: var(--color-95); /* #fafafa */ - --pf-v5-global--palette--black-150: var(--color-90); /* #f5f5f5 */ - --pf-v5-global--palette--black-200: var(--color-85); /* #f0f0f0 */ - --pf-v5-global--palette--black-300: var(--color-75); /* #d2d2d2 */ - --pf-v5-global--palette--black-400: var(--color-65); /* #b8bbbe */ - --pf-v5-global--palette--black-500: var(--color-50); /* #8a8d90 */ - --pf-v5-global--palette--black-600: var(--color-40); /* #6a6e73 */ - --pf-v5-global--palette--black-700: var(--color-30); /* #4f5255 */ - --pf-v5-global--palette--black-800: var(--color-25); /* #3c3f42 */ - --pf-v5-global--palette--black-850: var(--color-20); /* #212427 */ - --pf-v5-global--palette--black-900: var(--color-10); /* #151515 */ - --pf-v5-global--palette--black-1000: var(--color-05); /* #030303 */ +{% for i in range(1, 21) %} +{# @see https://chatgpt.com/share/67bcd94e-bb44-800f-bf63-06d1ae0f5096 #} + {% set black = i * 50 %} + {% set color = 100 - i * 5 %} + --pf-v5-global--palette--black-{{ black }}: var(--color-{{ "%02d" % color }}); +{% endfor %} /* --- White --- */ --pf-v5-global--palette--white: var(--color-99); @@ -463,9 +440,12 @@ HINT: --pf-v5-global--icon--Color--light--dark: var(--color-40); --pf-v5-global--icon--Color--dark--dark: var(--color-10); - {# Additional Configuration #} - --pf-v5-c-button--m-tertiary--BackgroundColor: var(--color-62); +} +.pf-v5-c-button.pf-m-tertiary { + --pf-v5-c-button--m-tertiary--BackgroundColor: var(--color-70); + {# Assume that the following line is necessary due to load order #} + background-color: var(--pf-v5-c-button--m-tertiary--BackgroundColor); } /* Additional Keykloak Configuration */ @@ -863,6 +843,10 @@ html[native-dark-active] .btn, .btn { color: var(--color-40); } +.navbar-nav { + --bs-nav-link-hover-color: var(--color-17); +} + .dropdown-item { color: var(--color-40); background-color: var(--color-80); @@ -960,7 +944,7 @@ html.ng-csp header#header{ background-color: var(--color-80); /* New Gradient based on original background (80 -5, 80, 80 +1, 80 +5) */ background: linear-gradient({{ range(0, 361) | random }}deg, var(--color-75), var(--color-80), var(--color-81), var(--color-85)); - color: var(--color-90); + color: var(--color-17); } .files-list__row-name button, button.button-vue{ @@ -1038,6 +1022,10 @@ div.page-wrapper{ background-color: none; } +.card-header-title { + color: var(--color-37); +} + /* PHP MyAdmin */ #pma_navigation { background: linear-gradient(to right, var(--color-95), var(--color-85));