diff --git a/cli/generate_users.py b/cli/generate_users.py index 3df8e6f9..006a2370 100644 --- a/cli/generate_users.py +++ b/cli/generate_users.py @@ -50,6 +50,7 @@ def build_users(defs, primary_domain, start_id, become_pwd): username = overrides.get('username', key) email = overrides.get('email', f"{username}@{primary_domain}") description = overrides.get('description') + roles = overrides.get('roles',[]) password = overrides.get('password',become_pwd) # UID assignment if 'uid' in overrides: @@ -63,12 +64,11 @@ def build_users(defs, primary_domain, start_id, become_pwd): 'email': email, 'password': password, 'uid': uid, - 'gid': gid + 'gid': gid, + 'roles': roles } if description is not None: entry['description'] = description - if overrides.get('is_admin', False): - entry['is_admin'] = True users[key] = entry diff --git a/group_vars/all/13_ldap.yml b/group_vars/all/13_ldap.yml index 2ba52010..b235a8f2 100644 --- a/group_vars/all/13_ldap.yml +++ b/group_vars/all/13_ldap.yml @@ -35,16 +35,17 @@ ldap: # Typically: “cn=admin,cn=config” configuration: "cn={{ applications.ldap.users.administrator.username }},cn=config" - - # ------------------------------------------------------------------------- - # Organizational Units (OUs) - # Pre-created containers in the data tree to organize entries. - # – users: Where all person/posixAccount entries live. - # – groups: Where you define your application or business groups. - # – roles: A flat container for application-role entries (e.g. “cn=app1-user”). - users: "ou=users,{{ _ldap_dn_base }}" - groups: "ou=groups,{{ _ldap_dn_base }}" - application_roles: "ou=application_roles,{{ _ldap_dn_base }}" + ou: + # ------------------------------------------------------------------------- + # Organizational Units (OUs) + # Pre-created containers in the directory tree to logically separate entries: + # – users: Contains all user objects (person/posixAccount entries). + # – groups: Contains organizational or business groups (e.g., departments, teams). + # – roles: Contains application-specific RBAC roles + # (e.g., "cn=app1-user", "cn=yourls-admin"). + users: "ou=users,{{ _ldap_dn_base }}" + groups: "ou=groups,{{ _ldap_dn_base }}" + roles: "ou=roles,{{ _ldap_dn_base }}" # ------------------------------------------------------------------------- # Additional Notes diff --git a/roles/docker-discourse/templates/discourse_application.yml.j2 b/roles/docker-discourse/templates/discourse_application.yml.j2 index acecd68d..4e64eab0 100644 --- a/roles/docker-discourse/templates/discourse_application.yml.j2 +++ b/roles/docker-discourse/templates/discourse_application.yml.j2 @@ -166,7 +166,7 @@ run: # LDAP additional configuration - exec: rails r "SiteSetting.ldap_user_filter = '{{ ldap.filters.users.login }}'" - - exec: rails r "SiteSetting.ldap_group_base_dn = '{{ ldap.dn.groups }}'" + - exec: rails r "SiteSetting.ldap_group_base_dn = '{{ ldap.dn.ou.groups }}'" - exec: rails r "SiteSetting.ldap_group_member_check = 'memberUid'" - exec: rails r "SiteSetting.ldap_sync_period = 1" diff --git a/roles/docker-espocrm/templates/env.j2 b/roles/docker-espocrm/templates/env.j2 index 0b88fe5d..225c4127 100644 --- a/roles/docker-espocrm/templates/env.j2 +++ b/roles/docker-espocrm/templates/env.j2 @@ -69,7 +69,7 @@ ESPOCRM_CONFIG_LDAP_PORT={{ ldap.server.port }} ESPOCRM_CONFIG_LDAP_SECURITY={{ ldap.server.security }} ESPOCRM_CONFIG_LDAP_USERNAME={{ ldap.dn.administrator.data }} ESPOCRM_CONFIG_LDAP_PASSWORD={{ ldap.bind_credential }} -ESPOCRM_CONFIG_LDAP_BASE_DN={{ ldap.dn.users }} +ESPOCRM_CONFIG_LDAP_BASE_DN={{ ldap.dn.ou.users }} ESPOCRM_CONFIG_LDAP_USER_LOGIN_FILTER=(sAMAccountName=%USERNAME%) {% endif %} diff --git a/roles/docker-gitea/vars/main.yml b/roles/docker-gitea/vars/main.yml index 799e9649..9c485d76 100644 --- a/roles/docker-gitea/vars/main.yml +++ b/roles/docker-gitea/vars/main.yml @@ -7,7 +7,7 @@ gitea_ldap_auth_args: - '--security-protocol "{{ ldap.server.security | trim or "unencrypted" }}"' - '--bind-dn "{{ ldap.dn.administrator.data }}"' - '--bind-password "{{ ldap.bind_credential }}"' - - '--user-search-base "{{ ldap.dn.users }}"' + - '--user-search-base "{{ ldap.dn.ou.users }}"' - '--user-filter "(&(objectClass=inetOrgPerson)(uid=%s))"' - '--username-attribute "{{ ldap.attributes.user_id }}"' - '--firstname-attribute "{{ ldap.attributes.firstname }}"' diff --git a/roles/docker-keycloak/templates/import/realm.json.j2 b/roles/docker-keycloak/templates/import/realm.json.j2 index 53f2c0ea..aaf0e2d7 100644 --- a/roles/docker-keycloak/templates/import/realm.json.j2 +++ b/roles/docker-keycloak/templates/import/realm.json.j2 @@ -2045,7 +2045,7 @@ "false" ], "usersDn": [ - "{{ldap.dn.users}}" + "{{ldap.dn.ou.users}}" ], "connectionPooling": [ "true" diff --git a/roles/docker-ldap/tasks/add_user_objects.yml b/roles/docker-ldap/tasks/add_user_objects.yml index abe12019..459b053c 100644 --- a/roles/docker-ldap/tasks/add_user_objects.yml +++ b/roles/docker-ldap/tasks/add_user_objects.yml @@ -3,7 +3,7 @@ server_uri: "{{ ldap_server_uri }}" bind_dn: "{{ ldap.dn.administrator.data }}" bind_pw: "{{ ldap.bind_credential }}" - dn: "{{ ldap.dn.users }}" + dn: "{{ ldap.dn.ou.users }}" scope: subordinate filter: "{{ ldap.filters.users.all }}" attrs: diff --git a/roles/docker-ldap/tasks/main.yml b/roles/docker-ldap/tasks/main.yml index 3d930274..ed43bd20 100644 --- a/roles/docker-ldap/tasks/main.yml +++ b/roles/docker-ldap/tasks/main.yml @@ -63,7 +63,7 @@ ############################################################################### - name: Ensure LDAP users exist community.general.ldap_entry: - dn: "{{ ldap.attributes.user_id }}={{ item.key }},{{ ldap.dn.users }}" + dn: "{{ ldap.attributes.user_id }}={{ item.key }},{{ ldap.dn.ou.users }}" server_uri: "{{ ldap_server_uri }}" bind_dn: "{{ ldap.dn.administrator.data }}" bind_pw: "{{ ldap.bind_credential }}" @@ -87,7 +87,7 @@ ############################################################################### - name: Ensure required objectClass values and mail address are present community.general.ldap_attrs: - dn: "{{ ldap.attributes.user_id }}={{ item.key }},{{ ldap.dn.users }}" + dn: "{{ ldap.attributes.user_id }}={{ item.key }},{{ ldap.dn.ou.users }}" server_uri: "{{ ldap_server_uri }}" bind_dn: "{{ ldap.dn.administrator.data }}" bind_pw: "{{ ldap.bind_credential }}" @@ -101,7 +101,7 @@ - name: "Ensure container for application roles exists" community.general.ldap_entry: - dn: "{{ ldap.dn.application_roles }}" + dn: "{{ ldap.dn.ou.roles }}" server_uri: "{{ ldap_server_uri }}" bind_dn: "{{ ldap.dn.administrator.data }}" bind_pw: "{{ ldap.bind_credential }}" diff --git a/roles/docker-ldap/templates/ldif/data/01_application_roles.ldif.j2 b/roles/docker-ldap/templates/ldif/data/01_application_roles.ldif.j2 index 953a842e..fb8dc432 100644 --- a/roles/docker-ldap/templates/ldif/data/01_application_roles.ldif.j2 +++ b/roles/docker-ldap/templates/ldif/data/01_application_roles.ldif.j2 @@ -1,42 +1,34 @@ -{% for app, config in applications.items() %} -dn: cn={{ app }}-administrator,{{ldap.dn.application_roles}} +{% for application_id, application_config in applications.items() %} + + {# 1. Build up roles dict, defaulting to {} if rbac oder roles fehlt, then ensure administrator immer dabei ist #} + {% set base_roles = application_config.rbac.roles | default({}) %} + {% set roles = base_roles | combine({ + 'administrator': { + 'description': 'Has full administrative access: manage themes, plugins, settings, and users' + } + }) + %} + + {# 2. Emit role definitions #} + {% for role_name, role_conf in roles.items() %} +dn: cn={{ application_id }}-{{ role_name }},{{ ldap.dn.ou.roles }} objectClass: top objectClass: organizationalRole -cn: {{ app }}-administrator -description: Administrator role for {{ app }} (automatically generated) +cn: {{ application_id }}-{{ role_name }} +description: {{ role_conf.description }} -dn: cn={{ app }}-user,{{ldap.dn.application_roles}} -objectClass: top -objectClass: organizationalRole -cn: {{ app }}-user -description: Standard user role for {{ app }} (automatically generated) - -{% endfor %} - -{% for username, user in users.items() %} - -####################################################################### -# Assign {{ username }} to application user roles -####################################################################### -{% for app, config in applications.items() %} - -# Assign {{ username }} to {{ app }}-users - -dn: cn={{ app }}-user,{{ ldap.dn.application_roles }} + {# 3. Assign only if user has that role #} + {% for username, user_config in users.items() %} + {% set user_roles = user_config.roles | default([]) %} + {% if role_name in user_roles %} +dn: cn={{ application_id }}-{{ role_name }},{{ ldap.dn.ou.roles }} changetype: modify add: roleOccupant -roleOccupant: {{ ldap.attributes.user_id }}={{ username }},{{ ldap.dn.users }} +roleOccupant: {{ ldap.attributes.user_id }}={{ username }},{{ ldap.dn.ou.users }} -{% if users.is_admin | default(false) | bool %} + {% endif %} + {% endfor %} -# Assign {{ username }} to {{ app }}-administrator -dn: cn={{ app }}-administrator,{{ ldap.dn.application_roles }} -changetype: modify -add: roleOccupant -roleOccupant: {{ ldap.attributes.user_id }}={{ users.administrator.username }},{{ ldap.dn.users }} - -{% endif %} + {% endfor %} {% endfor %} - -{% endfor %} \ No newline at end of file diff --git a/roles/docker-mailu/tasks/main.yml b/roles/docker-mailu/tasks/main.yml index 0d48112a..b6710d62 100644 --- a/roles/docker-mailu/tasks/main.yml +++ b/roles/docker-mailu/tasks/main.yml @@ -32,7 +32,13 @@ mailu_domain: "{{ primary_domain }}" mailu_api_base_url: "http://127.0.0.1:8080/api/v1" mailu_global_api_token: "{{ applications.mailu.credentials.api_token }}" - mailu_action: "{{ item.value.is_admin | default(false) | ternary('admin','user') }}" + mailu_action: >- + {{ + ( + 'administrator' in (item.value.get('roles', [])) + ) + | ternary('admin','user') + }} mailu_user_key: "{{ item.key }}" mailu_user_name: "{{ item.value.username }}" mailu_password: "{{ item.value.password }}" diff --git a/roles/docker-mastodon/tasks/create-administrator.yml b/roles/docker-mastodon/tasks/create-administrator.yml index efafaca8..da621d2c 100644 --- a/roles/docker-mastodon/tasks/create-administrator.yml +++ b/roles/docker-mastodon/tasks/create-administrator.yml @@ -1,5 +1,24 @@ # Routines to create the administrator account -# @see https://chatgpt.com/share/67b9b12c-064c-800f-9354-8e42e6459764 +# @see https://chatgpt.com/share/67b9b12c-064c-800f-9354-8e42e6459764 + +- name: Check health status of {{ item }} container + shell: | + cid=$(docker compose ps -q {{ item }}) + docker inspect \ + --format '{{ "{{.State.Health.Status}}" }}' \ + $cid + args: + chdir: "{{ docker_compose.directories.instance }}" + register: healthcheck + retries: 60 + delay: 5 + until: healthcheck.stdout == "healthy" + loop: + - web + - streaming + - sidekiq + loop_control: + label: "{{ item }}" - name: Remove line containing "- administrator" from config/settings.yml to allow creating administrator account command: diff --git a/roles/docker-nextcloud/vars/plugins/user_ldap.yml b/roles/docker-nextcloud/vars/plugins/user_ldap.yml index 3fbcf164..a2e193eb 100644 --- a/roles/docker-nextcloud/vars/plugins/user_ldap.yml +++ b/roles/docker-nextcloud/vars/plugins/user_ldap.yml @@ -42,7 +42,7 @@ plugin_configuration: - appid: "user_ldap" configkey: "s01ldap_base_users" - configvalue: "{{ldap.dn.users}}" + configvalue: "{{ldap.dn.ou.users}}" - appid: "user_ldap" diff --git a/roles/docker-oauth2-proxy/templates/oauth2-proxy-keycloak.cfg.j2 b/roles/docker-oauth2-proxy/templates/oauth2-proxy-keycloak.cfg.j2 index 1e23d193..de2b44f7 100644 --- a/roles/docker-oauth2-proxy/templates/oauth2-proxy-keycloak.cfg.j2 +++ b/roles/docker-oauth2-proxy/templates/oauth2-proxy-keycloak.cfg.j2 @@ -1,6 +1,5 @@ http_address = "0.0.0.0:4180" cookie_secret = "{{ applications[oauth2_proxy_application_id].credentials.oauth2_proxy_cookie_secret }}" -email_domains = "{{ primary_domain }}" cookie_secure = "true" # True is necessary to force the cookie set via https upstreams = "http://{{ applications[oauth2_proxy_application_id].oauth2_proxy.application }}:{{ applications[oauth2_proxy_application_id].oauth2_proxy.port }}" cookie_domains = ["{{ domains | get_domain(oauth2_proxy_application_id) }}", "{{ domains | get_domain('keycloak') }}"] # Required so cookie can be read on all subdomains. @@ -14,7 +13,11 @@ oidc_issuer_url = "{{ oidc.client.issuer_url }}" provider = "oidc" provider_display_name = "Keycloak" -# role restrictions -#cookie_roles = "realm_access.roles" -#allowed_groups = "{{ applications[application_id].allowed_roles }}" # This is not correct here. needs to be placed in applications @todo move there when implementing -# @see https://chatgpt.com/share/67f42607-bf68-800f-b587-bd56fe9067b5 \ No newline at end of file +{% if applications[oauth2_proxy_application_id].oauth2_proxy.allowed_groups is defined %} +{# role based restrictions #} +scope = "openid email profile groups" +oidc_groups_claim = "realm_access.roles" +allowed_groups = {{ applications[oauth2_proxy_application_id].oauth2_proxy.allowed_groups | tojson }} +{% else %} +email_domains = "{{ primary_domain }}" +{% endif %} \ No newline at end of file diff --git a/roles/docker-openproject/vars/ldap.yml b/roles/docker-openproject/vars/ldap.yml index 02975c2f..f8fcf7e2 100644 --- a/roles/docker-openproject/vars/ldap.yml +++ b/roles/docker-openproject/vars/ldap.yml @@ -4,7 +4,7 @@ openproject_ldap: port: "{{ ldap.server.port }}" # LDAP server port (typically 389 or 636) account: "{{ ldap.dn.administrator.data }}" # Bind DN (used for authentication) account_password: "{{ ldap.bind_credential }}" # Bind password - base_dn: "{{ ldap.dn.users }}" # Base DN for user search + base_dn: "{{ ldap.dn.ou.users }}" # Base DN for user search attr_login: "{{ ldap.attributes.user_id }}" # LDAP attribute used for login attr_firstname: "givenName" # LDAP attribute for first name attr_lastname: "{{ ldap.attributes.surname }}" # LDAP attribute for last name diff --git a/roles/docker-openproject/vars/main.yml b/roles/docker-openproject/vars/main.yml index 6ba0d86e..b9eebf94 100644 --- a/roles/docker-openproject/vars/main.yml +++ b/roles/docker-openproject/vars/main.yml @@ -19,9 +19,9 @@ openproject_rails_settings: openproject_filters: administrators: >- - {{ '(memberOf=cn=openproject-admins,' ~ ldap.dn.application_roles ~ ')' + {{ '(memberOf=cn=openproject-admins,' ~ ldap.dn.ou.roles ~ ')' if applications[application_id].ldap.filters.administrators else '' }} users: >- - {{ '(memberOf=cn=openproject-users,' ~ ldap.dn.application_roles ~ ')' + {{ '(memberOf=cn=openproject-users,' ~ ldap.dn.ou.roles ~ ')' if applications[application_id].ldap.filters.users else '' }} diff --git a/roles/docker-snipe-it/tasks/ldap.yml b/roles/docker-snipe-it/tasks/ldap.yml index 5a4bfd02..618404ed 100644 --- a/roles/docker-snipe-it/tasks/ldap.yml +++ b/roles/docker-snipe-it/tasks/ldap.yml @@ -45,7 +45,7 @@ $s->ldap_server = "{{ ldap.server.uri }}"; $s->ldap_port = {{ ldap.server.port }}; $s->ldap_uname = "{{ ldap.dn.administrator.data }}"; - $s->ldap_basedn = "{{ ldap.dn.users }}"; + $s->ldap_basedn = "{{ ldap.dn.ou.users }}"; $s->ldap_filter = "&(objectClass=inetOrgPerson)"; $s->ldap_username_field = "{{ ldap.attributes.user_id }}"; $s->ldap_fname_field = "{{ ldap.attributes.firstname }}"; diff --git a/roles/docker-wordpress/vars/configuration.yml b/roles/docker-wordpress/vars/configuration.yml index dd7af1e6..21de4380 100644 --- a/roles/docker-wordpress/vars/configuration.yml +++ b/roles/docker-wordpress/vars/configuration.yml @@ -39,3 +39,15 @@ csp: domains: canonical: - "blog.{{ primary_domain }}" +rbac: + roles: + subscriber: + description: "Can read posts and leave comments but cannot write or manage content" + author: + description: "Can write and manage own posts" + contributor: + description: "Can write and submit posts for review but cannot publish" + editor: + description: "Can publish and manage all posts, including those by other users" + administrator: + description: "Has full administrative access: manage themes, plugins, settings, and users" diff --git a/roles/docker-yourls/templates/env.j2 b/roles/docker-yourls/templates/env.j2 index 5d6d8a3e..957e0aa9 100644 --- a/roles/docker-yourls/templates/env.j2 +++ b/roles/docker-yourls/templates/env.j2 @@ -4,4 +4,6 @@ YOURLS_DB_PASS: "{{database_password}}" YOURLS_DB_NAME: "{{database_name}}" YOURLS_SITE: "{{ web_protocol }}://{{domains | get_domain(application_id)}}" YOURLS_USER: "{{applications.yourls.users.administrator.username}}" -YOURLS_PASS: "{{applications[application_id].credentials.administrator_password}}" \ No newline at end of file +YOURLS_PASS: "{{applications[application_id].credentials.administrator_password}}" +# The following deactivates the login mask for admins, if the oauth2 proxy is activated +YOURLS_PRIVATE: "{{not (applications | is_feature_enabled('oauth2', application_id))}}" \ No newline at end of file diff --git a/roles/docker-yourls/vars/configuration.yml b/roles/docker-yourls/vars/configuration.yml index 6541fafb..ffbd2385 100644 --- a/roles/docker-yourls/vars/configuration.yml +++ b/roles/docker-yourls/vars/configuration.yml @@ -2,6 +2,8 @@ version: "latest" oauth2_proxy: application: "application" port: "80" + allowed_groups: + - "yourls-administrator" acl: blacklist: - "/admin/" # Protects the admin area diff --git a/roles/user-administrator/meta/users.yml b/roles/user-administrator/meta/users.yml index 947b591d..4e098d18 100644 --- a/roles/user-administrator/meta/users.yml +++ b/roles/user-administrator/meta/users.yml @@ -6,4 +6,5 @@ users: password: "{{ ansible_become_password }}" uid: 1001 gid: 1001 - is_admin: true + roles: + - administrator