Fix update-users: DNS IP, reboots, key regeneration, and local permissions (#14883)

* Fix DNS IP changing after running update-users

The local_service_ip variable is generated using a deterministic random
formula seeded by algo_server_name + ansible_fqdn. While algo_server_name
is persisted in .config.yml, ansible_fqdn is a runtime fact gathered from
the target host that can change if:

- Server's hostname configuration changes
- DNS resolution changes
- User runs ansible from a different control machine

When ansible_fqdn differs between initial deployment and users.yml, a
different local_service_ip is generated, causing new client configs to
point to a DNS IP that doesn't exist on the server.

This fix persists local_service_ip and local_service_ipv6 in .config.yml
during initial deployment, ensuring users.yml uses the correct DNS IP.

Fixes #14614

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Skip apt upgrade and reboot during update-users

Previously, running `./algo update-users` would trigger a full apt upgrade
and potentially reboot the server because ubuntu.yml was tagged with
`update-users`. This caused unexpected disconnections for all VPN clients
(gaming, downloads, etc.) during what should be a simple user management
operation.

Now only facts.yml runs during update-users, which provides the necessary
password facts for IPsec certificate generation without running apt upgrade
or triggering reboots.

Fixes #14518

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Add keys_clean_all support to WireGuard (parity with IPsec)

Previously, WireGuard had no option to force credential regeneration for
existing users. The keys_clean_all option only affected IPsec certificates.

Now WireGuard respects keys_clean_all the same way IPsec does:
- keys_clean_all: false (default) - preserve existing keys, only generate
  for new users
- keys_clean_all: true - delete all keys and regenerate for all users

Also improved the config.cfg documentation to clarify this option affects
both WireGuard and IPsec credentials.

Fixes #14610

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Add permission check for local deployment update-users

For local deployments, file ownership must be consistent between initial
deployment and subsequent update-users runs. When there's a mismatch
(e.g., initial deployment without sudo, update with sudo), files get
mixed ownership causing permission errors.

This adds a pre-flight check that:
- Detects local deployments (localhost or algo_provider: local)
- Compares config directory owner with current user
- Displays a warning with guidance if there's a mismatch
- Provides the exact chown command to fix permissions

Addresses #14551

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Address review feedback: explicit directory modes and clearer docs

- Add explicit mode to WireGuard directory creation in main.yml:
  - PKI directories (preshared, private, public): 0700
  - Config directories (apple/ios, apple/macos): 0755

- Enhance config.cfg keys_clean_all comment to clarify:
  - When false: new users added (not just preserved)
  - When true: ALL CLIENTS MUST RECONFIGURE (explicit impact warning)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Address review: fail on permission mismatch, clean up directory creation

1. Permission check now FAILS instead of warning
   - Prevents continuing with mismatched permissions
   - Provides clear fix command: sudo chown -R <user> configs/<server>/
   - Simpler condition: just compare owner with current user

2. Clean separation of directory creation
   - main.yml: Only config directories (apple/ios, apple/macos) with 0755
   - keys.yml: Only PKI directories (preshared, private, public) with 0700
   - Eliminates duplication and clarifies responsibility

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Address review: add empty users validation and improve docs

1. Add empty users list validation
   - Fails early with clear message if no users defined in config.cfg
   - Prevents confusing downstream errors

2. Improve config.cfg keys_clean_all documentation
   - Add example use cases: key compromise, removing untrusted users, security audit

3. Rename block for clarity
   - "Check local deployment permissions" → "Local deployment permission validation"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix jinja2 spacing lint warning

Remove extra spaces inside brackets in Jinja2 expression per ansible-lint
jinja[spacing] rule.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Dan Guido
2025-11-28 01:19:36 -05:00
committed by GitHub
co-authored by Claude
parent 152440ff3c
commit a7b9b61265
6 changed files with 64 additions and 7 deletions
+4 -1
View File
@@ -88,7 +88,10 @@ dns_servers:
# Store PKI in RAM disk when not retaining (MacOS/Linux only)
pki_in_tmpfs: true
# Regenerate ALL user certs on update-users (not just new users)
# Regenerate ALL user credentials on update-users (not just new users)
# When false: existing WireGuard keys and IPsec certs are preserved, new users added
# When true: all credentials deleted and regenerated - ALL CLIENTS MUST RECONFIGURE
# Use true after: suspected key compromise, removing untrusted users, or security audit
keys_clean_all: false
### VPN Network Configuration ###
+3 -1
View File
@@ -11,10 +11,12 @@
- include_tasks: ubuntu.yml
when: '"Ubuntu" in OS.stdout or "Linux" in OS.stdout'
# Include facts separately for update-users (skips apt upgrade/reboot in ubuntu.yml)
- include_tasks: facts.yml
tags:
- update-users
- name: Sysctl tuning
sysctl: name="{{ item.item }}" value="{{ item.value }}"
when: item.item is defined and item.item != none
+17
View File
@@ -1,4 +1,21 @@
---
- name: Ensure the WireGuard pki directory does not exist
file:
dest: "{{ wireguard_pki_path }}"
state: absent
when: keys_clean_all | bool
- name: Ensure the WireGuard pki directories exist
file:
dest: "{{ wireguard_pki_path }}/{{ item }}"
state: directory
recurse: true
mode: "0700"
with_items:
- preshared
- private
- public
- name: Generate raw private keys
community.crypto.openssl_privatekey:
type: X25519
+2 -4
View File
@@ -1,13 +1,11 @@
---
- name: Ensure the required directories exist
- name: Ensure the required config directories exist
file:
dest: "{{ item }}"
state: directory
recurse: true
mode: "0755"
with_items:
- "{{ wireguard_pki_path }}/preshared"
- "{{ wireguard_pki_path }}/private"
- "{{ wireguard_pki_path }}/public"
- "{{ wireguard_config_path }}/apple/ios"
- "{{ wireguard_config_path }}/apple/macos"
delegate_to: localhost
+2
View File
@@ -205,6 +205,8 @@
IP_subject_alt_name: {{ IP_subject_alt_name }}
ipsec_enabled: {{ ipsec_enabled }}
wireguard_enabled: {{ wireguard_enabled }}
local_service_ip: {{ local_service_ip }}
local_service_ipv6: {{ local_service_ipv6 }}
{% if tests | default(false) | bool %}
ca_password: '{{ CA_password }}'
p12_password: '{{ p12_export_password }}'
+36 -1
View File
@@ -24,7 +24,7 @@
- name: Build list of installed servers
set_fact:
server_list: "{{ server_list | default([]) + [ {'server': config.server, 'IP_subject_alt_name': config.IP_subject_alt_name} ] }}"
server_list: "{{ server_list | default([]) + [{'server': config.server, 'IP_subject_alt_name': config.IP_subject_alt_name}] }}"
loop: "{{ _configs_list.files }}"
loop_control:
label: "{{ item.path }}"
@@ -51,6 +51,41 @@
include_vars:
file: configs/{{ algo_server }}/.config.yml
- name: Validate users list is not empty
fail:
msg: |
NO USERS DEFINED
The 'users' list in config.cfg is empty. At least one user is required.
Add users to config.cfg before running update-users.
when: users | default([]) | length == 0
- name: Local deployment permission validation
block:
- name: Get config directory owner
stat:
path: configs/{{ algo_server }}
register: config_dir_stat
- name: Fail on permission mismatch
fail:
msg: |
PERMISSION MISMATCH DETECTED
Config directory owner: {{ config_dir_stat.stat.pw_name }}
Current user: {{ ansible_user_id }}
Running update-users with mismatched permissions will create
files with inconsistent ownership, breaking future operations.
TO FIX: Run this command, then retry update-users:
sudo chown -R {{ ansible_user_id }} configs/{{ algo_server }}/
PREVENT: Always run update-users the same way as initial deployment
(both with sudo, or both without sudo).
when: config_dir_stat.stat.pw_name != ansible_user_id
when: algo_server == 'localhost' or algo_provider | default('') == 'local'
- name: Test SSH connectivity to server
wait_for:
host: "{{ algo_server }}"