From ffe972d79b58349df626ca519b1582b9035513eb Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Wed, 12 Jun 2024 18:33:24 -0500 Subject: [PATCH] r/samba-cert: Obtain LDAP/TLS cert via ACME The *samba-cert* role configures `lego` and HAProxy to obtain an X.509 certificate via the ACME HTTP-01 challenge. HAProxy is necessary because LDAP server certificates need to have the apex domain in their SAN field, and the ACME server may contact *any* domain controller server with an A record for that name. HAProxy will forward the challenge request on to the first available host on port 5000, where `lego` is listening to provide validation. Issuing certificates this way has a couple of advantages: 1. No need for the wildcard certificate for the *pyrocufflink.blue* domain any more 2. Renewals are automatic and handled by the server itself rather than Ansible via scheduled Jenkins job Item (2) is particularly interesting because it avoids the bi-monthly issue where replacing the LDAP server certificate and restarting Samba causes the Jenkins job to fail. Naturally, for this to work correctly, all LDAP client applications need to trust the certificates issued by the ACME server, in this case *DCH Root CA R2*. --- group_vars/samba-dc.yml | 16 ++- roles/samba-cert/handlers/main.yml | 5 + roles/samba-cert/meta/main.yml | 3 + roles/samba-cert/tasks/main.yml | 122 ++++++++++++++++++ .../templates/samba-cert-renew.service.j2 | 18 +++ .../templates/samba-cert-renew.timer.j2 | 9 ++ .../samba-cert/templates/samba-dc.haproxy.cfg | 30 +++++ samba-dc.yml | 2 + 8 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 roles/samba-cert/handlers/main.yml create mode 100644 roles/samba-cert/meta/main.yml create mode 100644 roles/samba-cert/tasks/main.yml create mode 100644 roles/samba-cert/templates/samba-cert-renew.service.j2 create mode 100644 roles/samba-cert/templates/samba-cert-renew.timer.j2 create mode 100644 roles/samba-cert/templates/samba-dc.haproxy.cfg diff --git a/group_vars/samba-dc.yml b/group_vars/samba-dc.yml index 6a61a0d..ecc0c92 100644 --- a/group_vars/samba-dc.yml +++ b/group_vars/samba-dc.yml @@ -14,8 +14,9 @@ samba_shares: read_only: no samba_tls_enabled: true -samba_tls_keyfile: /etc/pki/tls/private/samba.key -samba_tls_certfile: /etc/pki/tls/certs/samba.cer +samba_tls_keyfile: /etc/samba/server.key +samba_tls_certfile: /etc/samba/server.cer +samba_tls_cafile: /etc/samba/ca.crt collectd_processes: - name: samba @@ -26,3 +27,14 @@ collectd_processes: admin_users: - 'PYROCUFFLINK\dustin' - 'PYROCUFFLINK\jenkins' + +haproxy_resolvers: +- name: local + nameservers: + - name: local + address: 127.0.0.1:53 + options: + accepted_payload_size: 8192 + +samba_cert_acme_server: https://ca.pyrocufflink.blue:32599/acme/acme/directory +samba_cert_acme_email: '{{ ansible_hostname }}@pyrocufflink.net' diff --git a/roles/samba-cert/handlers/main.yml b/roles/samba-cert/handlers/main.yml new file mode 100644 index 0000000..6bea0db --- /dev/null +++ b/roles/samba-cert/handlers/main.yml @@ -0,0 +1,5 @@ +- name: restart samba-cert-renew.timer + systemd: + name: samba-cert-renew.timer + state: restarted + diff --git a/roles/samba-cert/meta/main.yml b/roles/samba-cert/meta/main.yml new file mode 100644 index 0000000..8ba9711 --- /dev/null +++ b/roles/samba-cert/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: +- role: haproxy + tags: haproxy diff --git a/roles/samba-cert/tasks/main.yml b/roles/samba-cert/tasks/main.yml new file mode 100644 index 0000000..9310a35 --- /dev/null +++ b/roles/samba-cert/tasks/main.yml @@ -0,0 +1,122 @@ +- name: ensure lego is installed + package: + name: golang-github-acme-lego + state: present + tags: + - install + +- name: ensure haproxy is configured for domain controllers + template: + src: samba-dc.haproxy.cfg + dest: /etc/haproxy/conf.d/40-samba-dc.cfg + owner: root + group: root + mode: u=rw,go=r + notify: + - reload haproxy + tags: + - haproxy + +- name: flush handlers + meta: flush_handlers + +- name: ensure acme/http port is allowed in firewall + firewalld: + port: '{{ item }}' + state: enabled + loop: + - 80/tcp + - 5000/tcp + when: host_uses_firewalld|d(true) + tags: + - firewalld + +- name: wait for dns records to propagate + delegate_to: localhost + become: false + command: 'true' + until: >- + ansible_default_ipv4.address in lookup("dig", krb5_realm | lower) and + ansible_default_ipv4.address in lookup("dig", ansible_fqdn) + delay: 60 + retries: 15 + changed_when: false + tags: + - wait-for-dns + +- name: ensure samba server certificate exists + command: + lego + --path /var/lib/samba/.lego + --accept-tos + --server {{ samba_cert_acme_server }} + --http --http.port :5000 + --domains {{ ansible_fqdn }} + --domains {{ krb5_realm | lower }} + --email {{ samba_cert_acme_email }} + run + args: + creates: /var/lib/samba/.lego/certificates/{{ ansible_fqdn }}.json + notify: + - restart samba + tags: + - cert + +- name: ensure samba server certificate renewal service is installed + template: + src: samba-cert-renew.service.j2 + dest: /etc/systemd/system/samba-cert-renew.service + owner: root + group: root + mode: u=rw,go=r + notify: + - reload systemd + tags: + - systemd + +- name: ensure samba server certificate renewal timer is installed + template: + src: samba-cert-renew.timer.j2 + dest: /etc/systemd/system/samba-cert-renew.timer + owner: root + group: root + mode: u=rw,go=r + notify: + - reload systemd + - restart samba-cert-renew.timer + tags: + - systemd + +- name: flush handlers + meta: flush_handlers + +- name: ensure samba-cert-renew timer is running + systemd: + name: samba-cert-renew.timer + state: started + tags: + - service +- name: ensure samba-cert-renew timer starts at boot + systemd: + name: samba-cert-renew.timer + enabled: true + tags: + - service + +- name: ensure samba certificate files are linked + file: + path: /etc/samba/{{ item.path }} + src: '{{ item.dest }}' + force: true + state: link + loop: + - path: server.cer + dest: /var/lib/samba/.lego/certificates/{{ ansible_fqdn }}.crt + - path: server.key + dest: /var/lib/samba/.lego/certificates/{{ ansible_fqdn }}.key + - path: ca.crt + dest: /dev/null + notify: + - restart samba + tags: + - cert diff --git a/roles/samba-cert/templates/samba-cert-renew.service.j2 b/roles/samba-cert/templates/samba-cert-renew.service.j2 new file mode 100644 index 0000000..e09015d --- /dev/null +++ b/roles/samba-cert/templates/samba-cert-renew.service.j2 @@ -0,0 +1,18 @@ +[Unit] +Description=Renew Samba LDAP server certificate +Wants=network-online.target +After=network-online.target + +[Service] +Type=oneshot +ExecStart=/usr/bin/lego \ + --path /var/lib/samba/.lego \ + --accept-tos \ + --server {{ samba_cert_acme_server }} \ + --http --http.port :5000 \ + --domains {{ ansible_fqdn }} \ + --domains {{ krb5_realm | lower }} \ + --email {{ samba_cert_acme_email }} \ + renew \ + --renew-hook 'systemctl restart samba' +CapabilityBoundingSet= diff --git a/roles/samba-cert/templates/samba-cert-renew.timer.j2 b/roles/samba-cert/templates/samba-cert-renew.timer.j2 new file mode 100644 index 0000000..f498f6d --- /dev/null +++ b/roles/samba-cert/templates/samba-cert-renew.timer.j2 @@ -0,0 +1,9 @@ +[Unit] +Description=Samba LDAP server certificate renewal + +[Timer] +OnCalendar=daily +RandomizedDelaySec=12h + +[Install] +WantedBy=timers.target diff --git a/roles/samba-cert/templates/samba-dc.haproxy.cfg b/roles/samba-cert/templates/samba-dc.haproxy.cfg new file mode 100644 index 0000000..b6df62e --- /dev/null +++ b/roles/samba-cert/templates/samba-dc.haproxy.cfg @@ -0,0 +1,30 @@ +frontend http + bind *:80 + + acl acme_challenge path_beg /.well-known/acme-challenge + + # Proxy ACME challenge requests to Lego + use_backend lego if acme_challenge + #default_backend web + + +# Lego listens on port 5000 when it is requresting a certificate via +# ACME. Only one DC can be requesting a certificate at a time, or +# requests may be forwarded to the wrong machine. +# +# It is imperative that the `check` option is NOT enabled for any +# server/template in this back-end, or challenge requests may get lost +# if they are initiated between HAProxy health check intervals. +backend lego + balance roundrobin + server-template dc {{ groups["samba-dc"] | length + 3 }} {{ krb5_realm|lower }}:5000 resolvers local init-addr none + retries 10 + option redispatch + retry-on all-retryable-errors 404 + + +# Although there is not currently a use case for it, it is possible to run a +# web server on the apex domain, but it has to listen on an alternate port. +#backend web +# balance roundrobin +# server-template dc 5 {{ krb5_realm|lower }}:8080 resolvers local init-addr none diff --git a/samba-dc.yml b/samba-dc.yml index 6c64348..83234c4 100644 --- a/samba-dc.yml +++ b/samba-dc.yml @@ -4,6 +4,8 @@ - kerberos - dch-selinux - samba-dc + - role: samba-cert + tags: samba-cert tasks: - name: set samba configuration facts set_fact: