From 14a758a28a63cd01722d6548770a22b9b41756f5 Mon Sep 17 00:00:00 2001 From: windyboy Date: Sat, 8 Aug 2026 09:15:49 +0800 Subject: [PATCH] feat: manage UniFi SSO login setting via Ansible (W1N-51) --- AGENTS.md | 1 + ansible/inventory/hosts.yml | 11 +++ ansible/playbooks/unifi-sso.yml | 25 +++++ ansible/roles/unifi_sso/defaults/main.yml | 11 +++ ansible/roles/unifi_sso/tasks/main.yml | 110 ++++++++++++++++++++++ 5 files changed, 158 insertions(+) create mode 100644 ansible/playbooks/unifi-sso.yml create mode 100644 ansible/roles/unifi_sso/defaults/main.yml create mode 100644 ansible/roles/unifi_sso/tasks/main.yml diff --git a/AGENTS.md b/AGENTS.md index d3aa6ad..c3f6e4d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -75,6 +75,7 @@ Also readable as `agent.md` (symlink → this file). | Matrix upstream refs | [docs/matrix-upstream.md](docs/matrix-upstream.md) | | Hermes Agent Matrix channel | [docs/hermes-matrix.md](docs/hermes-matrix.md) | | UniFi local-service proxy bypass | [docs/unifi-openclash-localhost.md](docs/unifi-openclash-localhost.md) | +| UniFi SSO login setting (Ansible) | `cd ansible && ansible-playbook playbooks/unifi-sso.yml --limit unifi` | | Routine Ansible operations | [runbooks/ansible-operations.md](runbooks/ansible-operations.md) | Routine mailcow health: `cd ansible && ansible-playbook playbooks/health-report.yml --limit mailcow`. The local stub resolver is flaky; DNS probes use `1.1.1.1` / `8.8.8.8`. diff --git a/ansible/inventory/hosts.yml b/ansible/inventory/hosts.yml index c063c03..2b27372 100644 --- a/ansible/inventory/hosts.yml +++ b/ansible/inventory/hosts.yml @@ -80,6 +80,16 @@ all: adguardhome: hosts: dns_windy_lan: + unifi: + hosts: + ubnt: + ansible_host: 192.168.66.46 + ansible_host_ipv4: 192.168.66.46 + vars: + service_role: unifi + compose_project_dir: /home/windy/unifi-9 + unifi_container: unifi-controller + unifi_mongo_port: 27117 docker_hosts: children: mailcow: @@ -87,6 +97,7 @@ all: powerdns: wireguard: adguardhome: + unifi: # Matrix is a dedicated K3s node and intentionally remains outside the # Docker-oriented managed group. k3s_servers: diff --git a/ansible/playbooks/unifi-sso.yml b/ansible/playbooks/unifi-sso.yml new file mode 100644 index 0000000..70f9606 --- /dev/null +++ b/ansible/playbooks/unifi-sso.yml @@ -0,0 +1,25 @@ +--- +# Reconcile the UniFi controller SSO login setting +# (super_sdn.sso_login_enabled = "Sync Local Admin with Ubiquiti SSO"). +# +# Safe by default: run with --check for a read-only status report, or supply +# unifi_sso_confirm=true to apply the declared target value. +# +# # Read-only status +# ansible-playbook playbooks/unifi-sso.yml --limit ubnt --check +# +# # Apply (disable SSO login; local accounts use local passwords, no MFA) +# ansible-playbook playbooks/unifi-sso.yml --limit ubnt \ +# -e '{"unifi_sso_confirm": true, "unifi_sso_target_value": false}' +# +# # Rollback (re-enable SSO login) +# ansible-playbook playbooks/unifi-sso.yml --limit ubnt \ +# -e '{"unifi_sso_confirm": true, "unifi_sso_target_value": true}' +- name: Reconcile UniFi SSO login setting + hosts: unifi + become: true + gather_facts: false + serial: 1 + roles: + - role: unifi_sso + tags: [unifi, sso, mutating] \ No newline at end of file diff --git a/ansible/roles/unifi_sso/defaults/main.yml b/ansible/roles/unifi_sso/defaults/main.yml new file mode 100644 index 0000000..57266f1 --- /dev/null +++ b/ansible/roles/unifi_sso/defaults/main.yml @@ -0,0 +1,11 @@ +--- +# Reconcile super_sdn.sso_login_enabled on the UniFi Network Controller DB. +# Safe by default: without unifi_sso_confirm=true the role only reports the +# current state and refuses to change anything. +unifi_sso_confirm: false +# Declared desired state (true = Sync Local Admin with Ubiquiti SSO ON). +unifi_sso_target_value: false +# Restart the controller container after applying a change. Only needed when +# the setting was changed directly in the DB while the controller was running +# (the UI path applies it immediately without a restart). +unifi_sso_restart_controller: false diff --git a/ansible/roles/unifi_sso/tasks/main.yml b/ansible/roles/unifi_sso/tasks/main.yml new file mode 100644 index 0000000..a4c6eec --- /dev/null +++ b/ansible/roles/unifi_sso/tasks/main.yml @@ -0,0 +1,110 @@ +--- +# Reconcile super_sdn.sso_login_enabled on the UniFi controller DB. +# Idempotent: reads the current value, reports compliance when it already +# matches the target, and only mutates with explicit confirmation. + +- name: Require a boolean target value + ansible.builtin.assert: + that: + - unifi_sso_target_value | type_debug == 'bool' + fail_msg: "unifi_sso_target_value must be a boolean (true or false)." + +- name: Read current SSO login setting + ansible.builtin.command: + argv: + - docker + - exec + - "{{ unifi_container }}" + - mongo + - --port + - "{{ unifi_mongo_port }}" + - --quiet + - ace + - --eval + - 'db.setting.findOne({key:"super_sdn"}).sso_login_enabled' + register: unifi_sso_current + changed_when: false + failed_when: unifi_sso_current.rc != 0 + check_mode: false + +- name: Report current setting + ansible.builtin.debug: + msg: >- + sso_login_enabled={{ unifi_sso_current.stdout | trim }} + (target={{ unifi_sso_target_value | string | lower }}) + +- name: Report already compliant + ansible.builtin.debug: + msg: "Already compliant; no change needed." + when: unifi_sso_current.stdout | trim | lower == unifi_sso_target_value | string | lower + +- name: Refuse to change without explicit confirmation + ansible.builtin.fail: + msg: >- + Change required (sso_login_enabled={{ unifi_sso_current.stdout | trim }} + != target {{ unifi_sso_target_value | string | lower }}) but + unifi_sso_confirm is not true. Supply unifi_sso_confirm=true to apply. + when: + - unifi_sso_current.stdout | trim | lower != unifi_sso_target_value | string | lower + - not (unifi_sso_confirm | bool) + +- name: Apply SSO login setting change + ansible.builtin.command: + argv: + - docker + - exec + - "{{ unifi_container }}" + - mongo + - --port + - "{{ unifi_mongo_port }}" + - --quiet + - ace + - --eval + - >- + db.setting.updateOne({key:"super_sdn"}, + {$set:{sso_login_enabled: {{ unifi_sso_target_value | string | lower }}}}) + when: + - unifi_sso_confirm | bool + - unifi_sso_current.stdout | trim | lower != unifi_sso_target_value | string | lower + changed_when: true + register: unifi_sso_update + +- name: Restart controller to apply setting when requested + ansible.builtin.command: + argv: [docker, restart, "{{ unifi_container }}"] + when: + - unifi_sso_restart_controller | bool + - unifi_sso_current.stdout | trim | lower != unifi_sso_target_value | string | lower + changed_when: true + +- name: Verify setting after change + ansible.builtin.command: + argv: + - docker + - exec + - "{{ unifi_container }}" + - mongo + - --port + - "{{ unifi_mongo_port }}" + - --quiet + - ace + - --eval + - 'db.setting({key:"super_sdn"}).sso_login_enabled' + register: unifi_sso_verify + changed_when: false + check_mode: false + when: + - unifi_sso_current.stdout | trim | lower != unifi_sso_target_value | string | lower + - not ansible_check_mode + +- name: Assert verified value matches target + ansible.builtin.assert: + that: + - unifi_sso_verify.stdout | trim | lower == unifi_sso_target_value | string | lower + fail_msg: >- + Verification failed: sso_login_enabled is + {{ unifi_sso_verify.stdout | trim }} but expected + {{ unifi_sso_target_value | string | lower }}. + when: + - unifi_sso_current.stdout | trim | lower != unifi_sso_target_value | string | lower + - not ansible_check_mode \ No newline at end of file