Establish clean homelab infrastructure baseline
Reorganize the brownfield repository, remove retired and generated artifacts, harden ignore rules, and record the GitOps/IaC redesign.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
---
|
||||
# LDAPS certificate for the DC, auto-renewed from OpenBao's internal ACME.
|
||||
#
|
||||
# WHY: the first bao-issued LDAPS cert (2026-07-25) was placed by hand and expires
|
||||
# 2027-07-25 with nothing to renew it. If it lapses, Authelia loses its LDAPS
|
||||
# backend and every SSO consumer (Gitea, Grafana, OpenBao's own OIDC login) fails
|
||||
# at once — a year later, with no memory of how it got there.
|
||||
|
||||
# --- lego (ACME client). Same pinned release as openbao_acme, deliberately. ---
|
||||
samba_ad_acme_version: "5.3.1"
|
||||
samba_ad_acme_checksum: "sha256:b3c71b122ee1947eacfe0b809b955647f6377239fe4bfc49f73b1a091ae1252a"
|
||||
samba_ad_acme_url: "https://github.com/go-acme/lego/releases/download/v{{ samba_ad_acme_version }}/lego_v{{ samba_ad_acme_version }}_linux_amd64.tar.gz"
|
||||
samba_ad_acme_bin: "/usr/local/bin/lego"
|
||||
|
||||
# --- Paths ---
|
||||
samba_ad_acme_dir: "/etc/samba/acme" # lego state (account + certs)
|
||||
samba_ad_acme_tls_dir: "/var/lib/samba/private/tls" # where Samba reads cert/key/ca
|
||||
|
||||
# --- Identity ---
|
||||
samba_ad_acme_domain: "dc1.ad.ddupan.top"
|
||||
samba_ad_acme_email: "[email protected]"
|
||||
|
||||
# OpenBao's ACME directory, pinned to the ROLE-scoped path so issuance is capped by
|
||||
# the bao-server role (allowed_domains=ad.ddupan.top) rather than sign-verbatim.
|
||||
samba_ad_acme_server: "https://bao.ad.ddupan.top:8200/v1/pki/roles/bao-server/acme/directory"
|
||||
|
||||
# http-01: lego binds this address only while validating, then releases it. Verified nothing
|
||||
# else listens on :80 on the DC, and bao (192.168.10.8) can reach it.
|
||||
# NOTE: this yields a cert with a DNS SAN ONLY — no IP SAN, unlike the hand-issued
|
||||
# one it replaces. Clients MUST connect as dc1.ad.ddupan.top, not 192.168.10.5.
|
||||
# NOTE: lego v5 calls this --http.address (NOT --http.port, which is a v4-ism
|
||||
# and fails with "flag provided but not defined"). Mirrors --tls.address.
|
||||
samba_ad_acme_http_address: ":80"
|
||||
|
||||
# OpenBao's ACME caps certificate lifetime (issued cert is ~32 days, NOT the
|
||||
# role's 1y max_ttl — ACME deliberately issues short-lived certs). A 30-day
|
||||
# threshold against a 32-day cert would try to renew on almost every run, so keep
|
||||
# the window well inside the lifetime: renew with ~10 days of headroom.
|
||||
samba_ad_acme_renew_days: 10
|
||||
samba_ad_acme_renew_oncalendar: "*-*-* 03:42:00"
|
||||
|
||||
# lego defaults to an EC (P-256) key, but the bao-server PKI role pins
|
||||
# key_type=rsa / key_bits=2048, so an EC CSR is rejected at finalize with
|
||||
# "badCSR :: refusing to sign CSR: role requires keys of type rsa".
|
||||
# Match the role rather than loosening it — the role is what caps ACME issuance.
|
||||
samba_ad_acme_key_type: "rsa2048"
|
||||
@@ -0,0 +1,2 @@
|
||||
---
|
||||
dependencies: []
|
||||
@@ -0,0 +1,128 @@
|
||||
---
|
||||
# Install lego, obtain the DC's LDAPS cert from OpenBao's ACME, deploy it into
|
||||
# Samba's TLS dir, and enable a renewal timer. Idempotent.
|
||||
|
||||
- name: Check installed lego version
|
||||
ansible.builtin.command: "{{ samba_ad_acme_bin }} --version"
|
||||
register: lego_installed
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Install lego when missing or version mismatch
|
||||
when: samba_ad_acme_version not in (lego_installed.stdout | default(''))
|
||||
block:
|
||||
- name: Download lego release tarball (checksum-verified)
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ samba_ad_acme_url }}"
|
||||
dest: "/tmp/lego_{{ samba_ad_acme_version }}.tar.gz"
|
||||
checksum: "{{ samba_ad_acme_checksum }}"
|
||||
mode: "0644"
|
||||
retries: 3
|
||||
delay: 10
|
||||
|
||||
- name: Create lego staging dir
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/lego_{{ samba_ad_acme_version }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Extract lego
|
||||
ansible.builtin.unarchive:
|
||||
src: "/tmp/lego_{{ samba_ad_acme_version }}.tar.gz"
|
||||
dest: "/tmp/lego_{{ samba_ad_acme_version }}"
|
||||
remote_src: true
|
||||
|
||||
- name: Install lego binary
|
||||
ansible.builtin.copy:
|
||||
src: "/tmp/lego_{{ samba_ad_acme_version }}/lego"
|
||||
dest: "{{ samba_ad_acme_bin }}"
|
||||
remote_src: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Create ACME state directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ samba_ad_acme_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0700"
|
||||
|
||||
- name: Install the obtain/renew wrapper and deploy hook
|
||||
ansible.builtin.template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
loop:
|
||||
- { src: samba-acme.sh.j2, dest: /usr/local/bin/samba-acme.sh }
|
||||
- { src: samba-acme-deploy.sh.j2, dest: /usr/local/bin/samba-acme-deploy.sh }
|
||||
|
||||
- name: Install the renewal systemd service + timer
|
||||
ansible.builtin.template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop:
|
||||
- { src: samba-acme.service.j2, dest: /etc/systemd/system/samba-acme.service }
|
||||
- { src: samba-acme.timer.j2, dest: /etc/systemd/system/samba-acme.timer }
|
||||
register: acme_units
|
||||
|
||||
- name: Reload systemd
|
||||
ansible.builtin.systemd_service:
|
||||
daemon_reload: true
|
||||
when: acme_units is changed
|
||||
|
||||
# --- Preflight: the two things that actually make http-01 fail here ------------
|
||||
- name: Confirm nothing else is bound to port 80
|
||||
# lego binds :80 for the duration of validation. Anything already holding it
|
||||
# makes issuance fail with a bind error rather than anything ACME-shaped.
|
||||
ansible.builtin.shell:
|
||||
cmd: "ss -ltn '( sport = :80 )' | tail -n +2 | wc -l"
|
||||
register: port80
|
||||
changed_when: false
|
||||
|
||||
- name: Fail if port 80 is occupied
|
||||
ansible.builtin.fail:
|
||||
msg: "Port 80 is in use on {{ inventory_hostname }}; lego's http-01 cannot bind it."
|
||||
when: port80.stdout | trim | int > 0
|
||||
|
||||
- name: Confirm the OpenBao ACME directory is reachable
|
||||
ansible.builtin.uri:
|
||||
url: "{{ samba_ad_acme_server }}"
|
||||
return_content: false
|
||||
validate_certs: true
|
||||
register: acme_dir
|
||||
retries: 3
|
||||
delay: 10
|
||||
until: acme_dir is succeeded
|
||||
changed_when: false
|
||||
|
||||
- name: Obtain/renew the certificate now
|
||||
# Safe to run every time: lego only acts when the cert is missing or within
|
||||
# --renew-days of expiry, and only then fires the deploy hook.
|
||||
ansible.builtin.command: /usr/local/bin/samba-acme.sh
|
||||
register: lego_run
|
||||
changed_when: "'Server responded with a certificate' in (lego_run.stdout | default('') + lego_run.stderr | default(''))"
|
||||
|
||||
- name: Enable and start the renewal timer
|
||||
ansible.builtin.systemd_service:
|
||||
name: samba-acme.timer
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Report the live LDAPS certificate
|
||||
ansible.builtin.shell:
|
||||
cmd: >-
|
||||
echo | timeout 10 openssl s_client -connect 127.0.0.1:636 2>/dev/null
|
||||
| openssl x509 -noout -subject -issuer -dates
|
||||
register: live_cert
|
||||
changed_when: false
|
||||
|
||||
- name: Show it
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ live_cert.stdout_lines }}"
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
# {{ ansible_managed }}
|
||||
# Install a freshly issued/renewed cert into Samba's TLS dir.
|
||||
# lego passes the paths in LEGO_CERT_PATH / LEGO_CERT_KEY_PATH.
|
||||
set -euo pipefail
|
||||
|
||||
TLS="{{ samba_ad_acme_tls_dir }}"
|
||||
CRT="${LEGO_CERT_PATH:-{{ samba_ad_acme_dir }}/certificates/{{ samba_ad_acme_domain }}.crt}"
|
||||
KEY="${LEGO_CERT_KEY_PATH:-{{ samba_ad_acme_dir }}/certificates/{{ samba_ad_acme_domain }}.key}"
|
||||
ISS="${CRT%.crt}.issuer.crt"
|
||||
|
||||
install -o root -g root -m 0644 "${CRT}" "${TLS}/cert.pem"
|
||||
install -o root -g root -m 0600 "${KEY}" "${TLS}/key.pem"
|
||||
[ -s "${ISS}" ] && install -o root -g root -m 0644 "${ISS}" "${TLS}/ca.pem"
|
||||
|
||||
# Samba re-reads its TLS credentials PER CONNECTION, so a renewal normally goes
|
||||
# live with no restart and no LDAPS downtime (observed 2026-07-25). Do not assume
|
||||
# it though: if the served cert does not match what we just installed, the old one
|
||||
# is still being handed out and would eventually expire in place. Verify, and only
|
||||
# restart if we must — that keeps the common path at zero downtime while making
|
||||
# the failure mode loud instead of silent.
|
||||
new="$(openssl x509 -noout -fingerprint -sha256 -in "${TLS}/cert.pem" | cut -d= -f2)"
|
||||
served="$(echo | timeout 10 openssl s_client -connect 127.0.0.1:636 2>/dev/null \
|
||||
| openssl x509 -noout -fingerprint -sha256 2>/dev/null | cut -d= -f2 || true)"
|
||||
|
||||
if [ "${new}" != "${served}" ]; then
|
||||
echo "served cert != installed cert; restarting samba-ad-dc to load it"
|
||||
systemctl restart samba-ad-dc
|
||||
else
|
||||
echo "samba already serving the new cert; no restart needed"
|
||||
fi
|
||||
@@ -0,0 +1,9 @@
|
||||
# {{ ansible_managed }}
|
||||
[Unit]
|
||||
Description=Samba AD DC LDAPS certificate (lego, OpenBao ACME http-01)
|
||||
After=network-online.target samba-ad-dc.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/samba-acme.sh
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# {{ ansible_managed }}
|
||||
# Obtain or renew the DC's LDAPS cert from OpenBao's internal ACME (http-01).
|
||||
# lego's `run` does BOTH: it renews only when due (--renew-days) and fires
|
||||
# --deploy-hook on any actual create/renew. There is no separate `renew` command
|
||||
# in lego v5, and every flag must come AFTER `run`.
|
||||
set -euo pipefail
|
||||
|
||||
exec {{ samba_ad_acme_bin }} run \
|
||||
--accept-tos \
|
||||
--email "{{ samba_ad_acme_email }}" \
|
||||
--server "{{ samba_ad_acme_server }}" \
|
||||
--http \
|
||||
--http.address "{{ samba_ad_acme_http_address }}" \
|
||||
--domains "{{ samba_ad_acme_domain }}" \
|
||||
--key-type "{{ samba_ad_acme_key_type }}" \
|
||||
--path "{{ samba_ad_acme_dir }}" \
|
||||
--renew-days {{ samba_ad_acme_renew_days }} \
|
||||
--deploy-hook /usr/local/bin/samba-acme-deploy.sh
|
||||
@@ -0,0 +1,11 @@
|
||||
# {{ ansible_managed }}
|
||||
[Unit]
|
||||
Description=Samba AD DC LDAPS certificate renewal timer
|
||||
|
||||
[Timer]
|
||||
OnCalendar={{ samba_ad_acme_renew_oncalendar }}
|
||||
RandomizedDelaySec=3600
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Reference in New Issue
Block a user