mirror of
https://github.com/trailofbits/algo.git
synced 2025-08-14 08:43:01 +02:00
Fix IPv6 address selection on BSD systems (#14786)
* fix: Fix IPv6 address selection on BSD systems (#1843) BSD systems return IPv6 addresses in the order they were added to the interface, not sorted by scope like Linux. This causes ansible_default_ipv6 to contain link-local addresses (fe80::) with interface suffixes (%em0) instead of global addresses, breaking certificate generation. This fix: - Adds a new task file to properly select global IPv6 addresses on BSD - Filters out link-local addresses and interface suffixes - Falls back to ansible_all_ipv6_addresses when needed - Ensures certificates are generated with valid global IPv6 addresses The workaround is implemented in Algo rather than waiting for the upstream Ansible issue (#16977) to be fixed, which has been open since 2016. Fixes #1843 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * chore: Remove duplicate condition in BSD IPv6 facts Removed redundant 'global_ipv6_address is not defined' condition that was checked twice in the same when clause. * improve: simplify regex for IPv6 interface suffix removal Change regex from '(.*)%.*' to '%.*' for better readability and performance when stripping interface suffixes from IPv6 addresses. The simplified regex is equivalent but more concise and easier to understand. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: resolve yamllint trailing spaces in BSD IPv6 test Remove trailing spaces from test_bsd_ipv6.yml to ensure CI passes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: resolve yamllint issues across repository - Remove trailing spaces from server.yml, WireGuard test files, and keys.yml - Add missing newlines at end of test files - Ensure all YAML files pass yamllint validation for CI 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
358d50314e
commit
146e2dcf24
8 changed files with 127 additions and 8 deletions
56
roles/common/tasks/bsd_ipv6_facts.yml
Normal file
56
roles/common/tasks/bsd_ipv6_facts.yml
Normal file
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
# BSD systems return IPv6 addresses in the order they were added to the interface,
|
||||
# not sorted by scope like Linux does. This means ansible_default_ipv6 often contains
|
||||
# a link-local address (fe80::) instead of a global address, which breaks certificate
|
||||
# generation due to the %interface suffix.
|
||||
#
|
||||
# This task file creates a fact with the first global IPv6 address found.
|
||||
|
||||
- name: Initialize all_ipv6_addresses as empty list
|
||||
set_fact:
|
||||
all_ipv6_addresses: []
|
||||
|
||||
- name: Get all IPv6 addresses for the default interface
|
||||
set_fact:
|
||||
all_ipv6_addresses: "{{ ansible_facts[ansible_default_ipv6.interface]['ipv6'] | default([]) }}"
|
||||
when:
|
||||
- ansible_default_ipv6 is defined
|
||||
- ansible_default_ipv6.interface is defined
|
||||
- ansible_facts[ansible_default_ipv6.interface] is defined
|
||||
|
||||
- name: Find first global IPv6 address from interface-specific addresses
|
||||
set_fact:
|
||||
global_ipv6_address: "{{ item.address }}"
|
||||
global_ipv6_prefix: "{{ item.prefix }}"
|
||||
loop: "{{ all_ipv6_addresses }}"
|
||||
when:
|
||||
- all_ipv6_addresses | length > 0
|
||||
- item.address is defined
|
||||
- not item.address.startswith('fe80:') # Filter out link-local addresses
|
||||
- "'%' not in item.address" # Ensure no interface suffix
|
||||
- global_ipv6_address is not defined # Only set once
|
||||
loop_control:
|
||||
label: "{{ item.address | default('no address') }}"
|
||||
|
||||
- name: Find first global IPv6 address from ansible_all_ipv6_addresses
|
||||
set_fact:
|
||||
global_ipv6_address: "{{ item | regex_replace('%.*', '') }}"
|
||||
global_ipv6_prefix: "128" # Assume /128 for addresses from this list
|
||||
loop: "{{ ansible_all_ipv6_addresses | default([]) }}"
|
||||
when:
|
||||
- global_ipv6_address is not defined
|
||||
- ansible_all_ipv6_addresses is defined
|
||||
- not item.startswith('fe80:')
|
||||
|
||||
- name: Override ansible_default_ipv6 with global address on BSD
|
||||
set_fact:
|
||||
ansible_default_ipv6: "{{ ansible_default_ipv6 | combine({'address': global_ipv6_address, 'prefix': global_ipv6_prefix}) }}"
|
||||
when:
|
||||
- global_ipv6_address is defined
|
||||
- ansible_default_ipv6 is defined
|
||||
- ansible_default_ipv6.address.startswith('fe80:') or '%' in ansible_default_ipv6.address
|
||||
|
||||
- name: Debug IPv6 address selection
|
||||
debug:
|
||||
msg: "Selected IPv6 address: {{ ansible_default_ipv6.address | default('none') }}"
|
||||
when: algo_debug | default(false) | bool
|
|
@ -16,6 +16,10 @@
|
|||
- name: Gather additional facts
|
||||
import_tasks: facts.yml
|
||||
|
||||
- name: Fix IPv6 address selection on BSD
|
||||
import_tasks: bsd_ipv6_facts.yml
|
||||
when: ipv6_support | default(false) | bool
|
||||
|
||||
- name: Set OS specific facts
|
||||
set_fact:
|
||||
config_prefix: /usr/local/
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# Access pattern: item.item.item where:
|
||||
# - First 'item' = current async_status result
|
||||
# - Second 'item' = original async job object
|
||||
# - Second 'item' = original async job object
|
||||
# - Third 'item' = actual username from original loop
|
||||
#
|
||||
# Reference: https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_loops.html
|
||||
|
@ -92,7 +92,7 @@
|
|||
become: false
|
||||
# DATA STRUCTURE EXPLANATION:
|
||||
# item = current result from wg_genkey_results.results
|
||||
# item.item = original job object from wg_genkey.results
|
||||
# item.item = original job object from wg_genkey.results
|
||||
# item.item.item = actual username from original loop
|
||||
# item.stdout = the generated private key content
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
- block:
|
||||
- name: Wait until the cloud-init completed
|
||||
wait_for:
|
||||
path: /var/lib/cloud/data/result.json
|
||||
path: /var/lib/cloud/data/result.json
|
||||
delay: 10 # Conservative 10 second initial delay
|
||||
timeout: 480 # Reduce from 600 to 480 seconds (8 minutes)
|
||||
sleep: 10 # Check every 10 seconds (less aggressive)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
rc: 0
|
||||
failed: false
|
||||
finished: true
|
||||
- item: "10.10.10.1" # This comes from the original wg_genkey.results item
|
||||
- item: "10.10.10.1" # This comes from the original wg_genkey.results item
|
||||
stdout: "mock_private_key_2" # This is the command output
|
||||
changed: true
|
||||
rc: 0
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
register: file_check
|
||||
loop:
|
||||
- "testuser1"
|
||||
- "testuser2"
|
||||
- "testuser2"
|
||||
- "127.0.0.1"
|
||||
|
||||
- name: Assert all files exist
|
||||
|
@ -63,4 +63,4 @@
|
|||
state: absent
|
||||
|
||||
- debug:
|
||||
msg: "✅ WireGuard async fix test PASSED - item.item.item is the correct pattern!"
|
||||
msg: "✅ WireGuard async fix test PASSED - item.item.item is the correct pattern!"
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
- name: Debug - Show wg_genkey structure
|
||||
debug:
|
||||
var: wg_genkey
|
||||
|
||||
|
||||
- name: Simulate the actual async pattern - Wait for completion
|
||||
async_status:
|
||||
jid: "{{ item.ansible_job_id }}"
|
||||
|
@ -62,4 +62,4 @@
|
|||
- name: Cleanup
|
||||
file:
|
||||
path: "{{ wireguard_pki_path }}"
|
||||
state: absent
|
||||
state: absent
|
||||
|
|
59
tests/test_bsd_ipv6.yml
Normal file
59
tests/test_bsd_ipv6.yml
Normal file
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
# Test playbook for BSD IPv6 address selection
|
||||
# Run with: ansible-playbook tests/test_bsd_ipv6.yml -e algo_debug=true
|
||||
|
||||
- name: Test BSD IPv6 address selection logic
|
||||
hosts: localhost
|
||||
gather_facts: no
|
||||
vars:
|
||||
# Simulate BSD system facts with link-local as default
|
||||
ansible_default_ipv6:
|
||||
address: "fe80::1%em0"
|
||||
interface: "em0"
|
||||
prefix: "64"
|
||||
gateway: "fe80::1"
|
||||
|
||||
# Simulate interface facts with multiple IPv6 addresses
|
||||
ansible_facts:
|
||||
em0:
|
||||
ipv6:
|
||||
- address: "fe80::1%em0"
|
||||
prefix: "64"
|
||||
- address: "2001:db8::1"
|
||||
prefix: "64"
|
||||
- address: "2001:db8::2"
|
||||
prefix: "64"
|
||||
|
||||
# Simulate all_ipv6_addresses fact
|
||||
ansible_all_ipv6_addresses:
|
||||
- "fe80::1%em0"
|
||||
- "2001:db8::1"
|
||||
- "2001:db8::2"
|
||||
|
||||
ipv6_support: true
|
||||
algo_debug: true
|
||||
|
||||
tasks:
|
||||
- name: Show initial IPv6 facts
|
||||
debug:
|
||||
msg: "Initial ansible_default_ipv6: {{ ansible_default_ipv6 }}"
|
||||
|
||||
- name: Include BSD IPv6 fix tasks
|
||||
include_tasks: ../roles/common/tasks/bsd_ipv6_facts.yml
|
||||
|
||||
- name: Show fixed IPv6 facts
|
||||
debug:
|
||||
msg: |
|
||||
Fixed ansible_default_ipv6: {{ ansible_default_ipv6 }}
|
||||
Global IPv6 address: {{ global_ipv6_address | default('not found') }}
|
||||
Global IPv6 prefix: {{ global_ipv6_prefix | default('not found') }}
|
||||
|
||||
- name: Verify fix worked
|
||||
assert:
|
||||
that:
|
||||
- ansible_default_ipv6.address == "2001:db8::1"
|
||||
- global_ipv6_address == "2001:db8::1"
|
||||
- "'%' not in ansible_default_ipv6.address"
|
||||
- not ansible_default_ipv6.address.startswith('fe80:')
|
||||
fail_msg: "BSD IPv6 address selection failed"
|
||||
success_msg: "BSD IPv6 address selection successful"
|
Loading…
Add table
Reference in a new issue