From 64402d67326f2d79cd1faf2dd7b5fb3c23f3592e Mon Sep 17 00:00:00 2001 From: transcaffeine Date: Sat, 3 May 2025 23:48:16 +0200 Subject: [PATCH] feat(openldap): add ansible role for deployment --- galaxy.yml | 1 + playbooks/openldap.yml | 7 +++ roles/openldap/README.md | 3 + roles/openldap/defaults/main/container.yml | 61 +++++++++++++++++++++ roles/openldap/defaults/main/main.yml | 23 ++++++++ roles/openldap/defaults/main/openldap.yml | 56 +++++++++++++++++++ roles/openldap/tasks/configure.yml | 26 +++++++++ roles/openldap/tasks/deploy-docker.yml | 25 +++++++++ roles/openldap/tasks/initialize-docker.yml | 37 +++++++++++++ roles/openldap/tasks/initialize.yml | 47 ++++++++++++++++ roles/openldap/tasks/main.yml | 26 +++++++++ roles/openldap/tasks/prepare-docker.yml | 7 +++ roles/openldap/templates/slapd.ldif.j2 | 64 ++++++++++++++++++++++ roles/openldap/vars/main.yml | 6 ++ 14 files changed, 389 insertions(+) create mode 100644 playbooks/openldap.yml create mode 100644 roles/openldap/README.md create mode 100644 roles/openldap/defaults/main/container.yml create mode 100644 roles/openldap/defaults/main/main.yml create mode 100644 roles/openldap/defaults/main/openldap.yml create mode 100644 roles/openldap/tasks/configure.yml create mode 100644 roles/openldap/tasks/deploy-docker.yml create mode 100644 roles/openldap/tasks/initialize-docker.yml create mode 100644 roles/openldap/tasks/initialize.yml create mode 100644 roles/openldap/tasks/main.yml create mode 100644 roles/openldap/tasks/prepare-docker.yml create mode 100644 roles/openldap/templates/slapd.ldif.j2 create mode 100644 roles/openldap/vars/main.yml diff --git a/galaxy.yml b/galaxy.yml index ae7193a..26e1d92 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -25,3 +25,4 @@ tags: - nginx - restic - user_management + - openldap diff --git a/playbooks/openldap.yml b/playbooks/openldap.yml new file mode 100644 index 0000000..40f25f7 --- /dev/null +++ b/playbooks/openldap.yml @@ -0,0 +1,7 @@ +--- +- name: Deploy and configure openLDAP + hosts: "{{ openldap_hosts | default('openldap', true) }}" + become: "{{ openldap_become | default(true) }}" + gather_facts: "{{ openldap_playbook_gather_facts | default(false) }}" + roles: + - role: finallycoffee.base.openldap diff --git a/roles/openldap/README.md b/roles/openldap/README.md new file mode 100644 index 0000000..85ad817 --- /dev/null +++ b/roles/openldap/README.md @@ -0,0 +1,3 @@ +# `finallycoffee.base.openldap` ansible role + +Deploy and configure [OpenLDAP](https://www.openldap.org/). diff --git a/roles/openldap/defaults/main/container.yml b/roles/openldap/defaults/main/container.yml new file mode 100644 index 0000000..419513f --- /dev/null +++ b/roles/openldap/defaults/main/container.yml @@ -0,0 +1,61 @@ +--- +openldap_container_name: "openldap" +openldap_container_image_registry: docker.finally.coffee +openldap_container_image_namespace: containers +openldap_container_image_name: "openldap" +openldap_container_image_tag: ~ +openldap_container_image_source: "pull" +openldap_container_image_force_source: >-2 + {{ openldap_container_image_tag | default(false, true) }} +openldap_container_image_repository: >-2 + {{ + [ + openldap_container_image_registry | default([], true), + openldap_container_image_namespace | default([], true), + openldap_container_image_name + ] | flatten | join('/') + }} +openldap_container_image: >-2 + {{ + [ + openldap_container_image_repository, + openldap_container_image_tag + | default(openldap_alpine_package_version, true), + ] | join(':') + }} +openldap_container_env: ~ +openldap_container_user: ~ +openldap_container_ports: ~ +openldap_container_labels: ~ +openldap_container_volumes: ~ +openldap_container_networks: ~ +openldap_container_network_mode: ~ +openldap_container_dns_servers: ~ +openldap_container_etc_hosts: ~ +openldap_container_ulimits: + - "nofile:{{ openldap_fd_soft_limit }}:{{ openldap_fd_hard_limit }}" +openldap_container_memory: "256M" +openldap_container_memory_swap: ~ +openldap_container_memory_reservation: "128M" +openldap_container_restart_policy: "on-failure" +openldap_container_state: >-2 + {{ (openldap_state == 'present') | ternary('started', 'absent') }} + +openldap_container_data_path: "{{ openldap_data_path }}" +openldap_container_config_path: "{{ openldap_config_path }}" +openldap_container_socket_path: "{{ openldap_socket_path }}" +openldap_container_base_volumes: + - "{{ openldap_config_path }}:{{ openldap_container_config_path }}:Z" + - "{{ openldap_data_path }}:{{ openldap_container_data_path }}:rw" + - "{{ openldap_socket_path }}:{{ openldap_container_socket_path }}:rw" +openldap_container_all_volumes: >-2 + {{ openldap_container_base_volumes | default([], true) + + openldap_container_volumes | default([], true) }} +openldap_init_container_volumes: + - "{{ [openldap_slapd_path, openldap_slapd_path, 'ro'] | join(':') }}" + +openldap_container_healthcheck: + test: >-2 + [[ $(netstat -plnte | grep slapd | wc -l) -ge 1 ]] + && [[ $(ps aux | grep slapd | wc -l) -ge 1 ]] + || exit 1 diff --git a/roles/openldap/defaults/main/main.yml b/roles/openldap/defaults/main/main.yml new file mode 100644 index 0000000..b2a1677 --- /dev/null +++ b/roles/openldap/defaults/main/main.yml @@ -0,0 +1,23 @@ +--- +openldap_version: "2.6.8" +openldap_alpine_revision: "0" +openldap_alpine_package_version: >-2 + v{{ openldap_version }}-r{{ openldap_alpine_revision | string }} + +openldap_domain: ~ + +openldap_config_path: "/etc/openldap/" +openldap_olc_path: "{{ openldap_config_path }}/{0}config" +openldap_slapd_path: "{{ openldap_config_path }}/slapd.ldif" +openldap_schema_path: "{{ openldap_config_path }}/schema" +openldap_data_path: "/var/lib/openldap" +openldap_socket_path: "/run/openldap" +openldap_socket: "{{ openldap_socket_path }}/slapd.sock" +openldap_socket_url: >-2 + ldapi://{{ openldap_socket | urlencode | replace('/', '%2F') }} + +openldap_state: "present" +openldap_deployment_method: "docker" + +openldap_slapadd_init_command: >-2 + slapadd -v -F {{ openldap_olc_path }} -n 0 -l {{ openldap_slapd_path }} diff --git a/roles/openldap/defaults/main/openldap.yml b/roles/openldap/defaults/main/openldap.yml new file mode 100644 index 0000000..dcd323e --- /dev/null +++ b/roles/openldap/defaults/main/openldap.yml @@ -0,0 +1,56 @@ +--- +openldap_dn: >-2 + dc={{ openldap_domain | regex_replace('\\.', ',dc=') }} +openldap_root_username: "admin" +openldap_root_pw: ~ + +openldap_fd_soft_limit: "8192" +openldap_fd_hard_limit: "8192" + +openldap_module_path: "/usr/lib/openldap" +openldap_modules: + - "mdb" + - "hdb" + +openldap_core_schema_path: "{{ openldap_schema_path }}/core.ldif" +openldap_enabled_schemas: + - name: "cosine" + - name: "inetorgperson" +openldap_additional_schemas: [] +openldap_schemas: >-2 + {{ openldap_enabled_schemas + openldap_additional_schemas }} + +openldap_config_db: "cn=config" +openldap_config_db_olc_access: >-2 + to * + by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage + by * none +openldap_config_db_attributes: + olcAccess: "{{ openldap_config_db_olc_access }}" + +openldap_default_indices: + - "objectClass eq" +openldap_indices: [] + +openldap_default_database_name: "mdb" +openldap_default_database_object_class: "olcMdbConfig" +openldap_default_database_suffix: "{{ openldap_dn }}" +openldap_default_database_root_dn: >-2 + cn={{ openldap_root_username }},{{ openldap_default_database_suffix }} +openldap_default_database_root_pw: "{{ openldap_root_pw }}" +openldap_default_database_directory: >-2 + {{ openldap_data_path }}/{{ openldap_default_database_name }} +openldap_default_database_indices: >-2 + {{ openldap_default_indices + openldap_indices }} +openldap_default_database_config: >-2 + olcDatabase={1}{{ openldap_default_database_name }},{{ openldap_config_db }} +openldap_default_database: + name: "{{ openldap_default_database_name }}" + object_class: "{{ openldap_default_database_object_class }}" + suffix: "{{ openldap_default_database_suffix }}" + root_dn: "{{ openldap_default_database_root_dn }}" + root_pw: "{{ openldap_default_database_root_pw }}" + directory: "{{ openldap_default_database_directory }}" + indices: "{{ openldap_default_database_indices }}" +openldap_databases: + - "{{ openldap_default_database }}" diff --git a/roles/openldap/tasks/configure.yml b/roles/openldap/tasks/configure.yml new file mode 100644 index 0000000..75a7e2f --- /dev/null +++ b/roles/openldap/tasks/configure.yml @@ -0,0 +1,26 @@ +--- +- name: Ensure ACLs are configured + community.general.ldap_attrs: + dn: "{{ openldap_default_database_config }}" + attributes: + olcAccess: "{{ openldap_config_db_olc_access }}" + state: "exact" + server_uri: "{{ openldap_socket_url }}" + retries: 3 + delay: 3 + register: openldap_acl_result + until: openldap_acl_result is succeeded + +- name: Ensure rootDN + credentials are correct + community.general.ldap_attrs: + dn: "{{ openldap_default_database_config }}" + attributes: "{{ {entry.key: entry.value} }}" + state: "exact" + server_uri: "{{ openldap_socket_url }}" + no_log: "{{ entry.log is defined and not entry.log }}" + loop: + - key: "olcRootDN" + value: "{{ openldap_default_database_root_dn }}" + - key: "olcRootPW" + value: "{{ openldap_default_database_root_pw }" + log: false diff --git a/roles/openldap/tasks/deploy-docker.yml b/roles/openldap/tasks/deploy-docker.yml new file mode 100644 index 0000000..1ef6c40 --- /dev/null +++ b/roles/openldap/tasks/deploy-docker.yml @@ -0,0 +1,25 @@ +--- +- name: Ensure container '{{ openldap_container_name }}' is {{ openldap_container_state }} + community.docker.docker_container: + name: "{{ openldap_container_name }}" + image: "{{ openldap_container_image }}" + env: "{{ openldap_container_env | default(omit, true) }}" + user: "{{ openldap_container_user | default(omit, true) }}" + ports: "{{ openldap_container_ports | default(omit, true) }}" + labels: "{{ openldap_container_labels | default(omit, true) }}" + volumes: "{{ openldap_container_all_volumes | default(omit, true) }}" + networks: "{{ openldap_container_networks | default(omit, true) }}" + network_mode: "{{ openldap_container_network_mode | default(omit, true) }}" + dns_servers: "{{ openldap_container_dns_servers | default(omit, true) }}" + etc_hosts: "{{ openldap_container_etc_hosts | default(omit, true) }}" + command: "{{ openldap_container_command | default(omit, true) }}" + ulimits: "{{ openldap_container_ulimits | default(omit, true) }}" + memory: "{{ openldap_container_memory | default(omit, true) }}" + memory_swap: "{{ openldap_container_memory_swap | default(omit, true) }}" + memory_reservation: >-2 + {{ openldap_container_memory_reservation | default(omit, true) }} + restart_policy: >-2 + {{ openldap_container_restart_policy | default(omit, true) }} + healthcheck: "{{ openldap_container_healthcheck | default(omit, true) }}" + state: "{{ openldap_container_state }}" + diff --git a/roles/openldap/tasks/initialize-docker.yml b/roles/openldap/tasks/initialize-docker.yml new file mode 100644 index 0000000..60aca1f --- /dev/null +++ b/roles/openldap/tasks/initialize-docker.yml @@ -0,0 +1,37 @@ +--- +- name: Ensure additional schemas are mapped to container + ansible.builtin.set_fact: + openldap_init_container_volumes: >-2 + {{ openldap_init_container_volumes + [ schema_mount ] }} + vars: + schema_file: "{{ openldap_schema_path }}/{{ schema.name }}.ldif" + schema_mount: >-2 + {{ schema_file }}:{{ schema_file }}:ro + loop: "{{ openldap_additional_schemas }}" + loop_control: + loop_var: "schema" + label: "{{ schema.name }}" + +- name: Ensure ldap container is initialized + community.docker.docker_container: + name: "{{ openldap_container_name }}" + image: "{{ openldap_container_image }}" + env: "{{ openldap_container_env | default(omit, true) }}" + user: "{{ openldap_container_user | default(omit, true) }}" + ports: "{{ openldap_container_ports | default(omit, true) }}" + labels: "{{ openldap_container_labels | default(omit, true) }}" + volumes: "{{ openldap_container_merged_volumes | default(omit, true) }}" + networks: "{{ openldap_container_networks | default(omit, true) }}" + network_mode: "{{ openldap_container_network_mode | default(omit, true) }}" + dns_servers: "{{ openldap_container_dns_servers | default(omit, true) }}" + etc_hosts: "{{ openldap_container_etc_hosts | default(omit, true) }}" + memory: "{{ openldap_container_memory | default(omit, true) }}" + memory_swap: "{{ openldap_container_memory_swap | default(omit, true) }}" + memory_reservation: >-2 + {{ openldap_container_memory_reservation | default(omit, true) }} + command: "{{ openldap_slapadd_init_command }}" + detach: false + cleanup: true + vars: + openldap_container_merged_volumes: >-2 + {{ openldap_container_all_volumes + openldap_init_container_volumes }} diff --git a/roles/openldap/tasks/initialize.yml b/roles/openldap/tasks/initialize.yml new file mode 100644 index 0000000..b910d45 --- /dev/null +++ b/roles/openldap/tasks/initialize.yml @@ -0,0 +1,47 @@ +--- +- name: Determine if persisted OLC config exists + ansible.builtin.stat: + path: "{{ openldap_olc_path }}/cn=config" + register: openldap_olc_stat_info + +- name: Ensure openldap databases are initialized + when: not openldap_olc_stat_info.stat.exists + block: + - name: Ensure initial slapd.ldif is templated + ansible.builtin.template: + src: "slapd.ldif.j2" + dest: "{{ openldap_slapd_path }}" + mode: "0644" + - name: Ensure additional schemas to install are present + ansible.builtin.copy: + content: "{{ schema.content }}" + dest: "{{ openldap_schema_path }}/{{ schema.name }}.ldif" + mode: "0644" + loop: "{{ openldap_additional_schemas }}" + loop_control: + loop_var: "schema" + label: "{{ schema.name }}" + - name: Ensure db data directory exists + ansible.builtin.file: + path: "{{ openldap_default_database_directory }}" + state: directory + mode: "0750" + - name: Ensure container is initialized using {{ openldap_deployment_method }} + ansible.builtin.include_tasks: + file: "initialize-{{ openldap_deployment_method }}.yml" + rescue: + - name: Ensure temporary schema files are absent + ansible.builtin.file: + path: "{{ openldap_schema_path }}/{{ file.name }}.ldif" + state: absent + loop: >-2 + {{ openldap_additional_schemas }} + loop_control: + loop_var: "file" + label: "{{ file.name }}" + ignore_errors: true + - name: Ensure intial slapd.ldif file is absent + ansible.builtin.file: + path: "{{ openldap_slapd_path }}" + state: absent + ignore_errors: true diff --git a/roles/openldap/tasks/main.yml b/roles/openldap/tasks/main.yml new file mode 100644 index 0000000..520ae35 --- /dev/null +++ b/roles/openldap/tasks/main.yml @@ -0,0 +1,26 @@ +--- +- name: Check if 'openldap_state' is valid + ansible.builtin.fail: + msg: >-2 + Invalid state '{{ openldap_state }}'! + Supported states are {{ openldap_states | join(', ') }}. + when: openldap_state not in openldap_states + +- name: Check if 'openldap_deployment_method' is valid + ansible.builtin.fail: + msg: >-2 + Invalid state '{{ openldap_deployment_method }}'! + Supported states are {{ openldap_deployment_methods | join(', ') }}. + when: openldap_deployment_method not in openldap_deployment_methods + +- name: Ensure openldap deployment is prepared + ansible.builtin.include_tasks: + file: "prepare-{{ openldap_deployment_method }}.yml" + +- name: Ensure openldap is configured + ansible.builtin.include_tasks: + file: "configure.yml" + +- name: Ensure openldap is deployed using {{ openldap_deployment_method }} + ansible.builtin.include_tasks: + file: "deploy-{{ openldap_deployment_method }}.yml" diff --git a/roles/openldap/tasks/prepare-docker.yml b/roles/openldap/tasks/prepare-docker.yml new file mode 100644 index 0000000..24ddd73 --- /dev/null +++ b/roles/openldap/tasks/prepare-docker.yml @@ -0,0 +1,7 @@ +--- +- name: Ensure container image '{{ openldap_container_image }}' is {{ openldap_state }} + community.docker.docker_image: + name: "{{ openldap_container_image }}" + state: "{{ openldap_state }}" + source: "{{ openldap_container_image_source }}" + force_source: "{{ openldap_container_image_force_source }}" diff --git a/roles/openldap/templates/slapd.ldif.j2 b/roles/openldap/templates/slapd.ldif.j2 new file mode 100644 index 0000000..d038b73 --- /dev/null +++ b/roles/openldap/templates/slapd.ldif.j2 @@ -0,0 +1,64 @@ +dn: cn=config +objectClass: olcGlobal +cn: config +olcPidFile: /run/openldap/slapd.pid +olcArgsFile: /run/openldap/slapd.args + + +# Dynamic backend modules +dn: cn=module,cn=config +objectClass: olcModuleList +cn: module +olcModulepath: {{ openldap_module_path }} +{% for mod in openldap_modules | default([]) %} +olcModuleload: back_{{ mod }}.so +{% endfor %} + +# Schema config +dn: cn=schema,cn=config +objectClass: olcSchemaConfig +cn: schema + +include: file://{{ openldap_core_schema_path }} +{% for schema in openldap_schemas %} +include: file://{{ openldap_schema_path }}/{{ schema.name }}.ldif +{% endfor %} + +# Frontend settings +dn: olcDatabase=frontend,cn=config +objectClass: olcDatabaseConfig +objectClass: olcFrontendConfig +olcDatabase: frontend + + +# Config-DB settings +dn: olcDatabase=config,cn=config +objectClass: olcDatabaseConfig +olcDatabase: config +{% for attr in openldap_config_db_attributes | dict2items %} +{% if attr is string %} +{{ attr.key }}: {{ attr.value }} +{% else %} +{% for val in attr.value %} +{{ attr.key }}: {{ val }} +{% endfor %} +{% endif %} +{% endfor %} + + +# database settings +{% for db in openldap_databases %} +dn: olcDatabase={{ db.name }},cn=config +objectClass: olcDatabaseConfig +objectClass: {{ db.object_class }} +olcDatabase: {{ db.name }} +olcSuffix: {{ db.suffix }} +olcRootDN: {{ db.root_dn }} +olcRootPW: {{ db.root_pw }} +olcDbDirectory: {{ db.directory }} +{% for idx in db.indices %} +olcDbIndex: {{ idx }} +{% endfor %} + + +{% endfor %} diff --git a/roles/openldap/vars/main.yml b/roles/openldap/vars/main.yml new file mode 100644 index 0000000..d2e6bff --- /dev/null +++ b/roles/openldap/vars/main.yml @@ -0,0 +1,6 @@ +--- +openldap_states: + - "present" + - "absent" +openldap_deployment_methods: + - "docker"