Sanitized mirror from private repository - 2026-04-18 11:19:59 UTC
This commit is contained in:
187
ansible/playbooks/ssh_mesh.yml
Normal file
187
ansible/playbooks/ssh_mesh.yml
Normal file
@@ -0,0 +1,187 @@
|
||||
---
|
||||
# SSH Mesh Key Distribution & Verification
|
||||
#
|
||||
# Distributes SSH public keys across all managed hosts so every host can SSH
|
||||
# to every other host. Also verifies connectivity.
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook -i inventory.yml playbooks/ssh_mesh.yml
|
||||
# ansible-playbook -i inventory.yml playbooks/ssh_mesh.yml --tags verify
|
||||
# ansible-playbook -i inventory.yml playbooks/ssh_mesh.yml --tags distribute
|
||||
# ansible-playbook -i inventory.yml playbooks/ssh_mesh.yml -e "generate_missing=true"
|
||||
|
||||
- name: SSH Mesh — Collect Keys
|
||||
hosts: ssh_mesh
|
||||
gather_facts: false
|
||||
tags: [collect, distribute]
|
||||
|
||||
tasks:
|
||||
- name: Check if ed25519 key exists
|
||||
stat:
|
||||
path: "~/.ssh/id_ed25519.pub"
|
||||
register: ed25519_key
|
||||
|
||||
- name: Check if RSA key exists (fallback)
|
||||
stat:
|
||||
path: "~/.ssh/id_rsa.pub"
|
||||
register: rsa_key
|
||||
when: not ed25519_key.stat.exists
|
||||
|
||||
- name: Generate ed25519 key if missing
|
||||
command: ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N "" -C "{{ ansible_user }}@{{ inventory_hostname }}"
|
||||
args:
|
||||
creates: ~/.ssh/id_ed25519
|
||||
when:
|
||||
- not ed25519_key.stat.exists
|
||||
- not (rsa_key.stat.exists | default(false))
|
||||
- generate_missing | default(false) | bool
|
||||
|
||||
- name: Re-check for ed25519 key after generation
|
||||
stat:
|
||||
path: "~/.ssh/id_ed25519.pub"
|
||||
register: ed25519_key_recheck
|
||||
when:
|
||||
- not ed25519_key.stat.exists
|
||||
- generate_missing | default(false) | bool
|
||||
|
||||
- name: Read ed25519 public key
|
||||
slurp:
|
||||
src: "~/.ssh/id_ed25519.pub"
|
||||
register: pubkey_ed25519
|
||||
when: ed25519_key.stat.exists or (ed25519_key_recheck.stat.exists | default(false))
|
||||
|
||||
- name: Read RSA public key (fallback)
|
||||
slurp:
|
||||
src: "~/.ssh/id_rsa.pub"
|
||||
register: pubkey_rsa
|
||||
when:
|
||||
- not ed25519_key.stat.exists
|
||||
- not (ed25519_key_recheck.stat.exists | default(false))
|
||||
- rsa_key.stat.exists | default(false)
|
||||
|
||||
- name: Set public key fact
|
||||
set_fact:
|
||||
ssh_pubkey: >-
|
||||
{{ (pubkey_ed25519.content | default(pubkey_rsa.content) | b64decode | trim) }}
|
||||
ssh_key_comment: "{{ inventory_hostname }}"
|
||||
when: pubkey_ed25519 is not skipped or pubkey_rsa is not skipped
|
||||
|
||||
- name: Warn if no key found
|
||||
debug:
|
||||
msg: "WARNING: No SSH key on {{ inventory_hostname }}. Run with -e generate_missing=true to create one."
|
||||
when: ssh_pubkey is not defined
|
||||
|
||||
- name: SSH Mesh — Distribute Keys
|
||||
hosts: ssh_mesh
|
||||
gather_facts: false
|
||||
tags: [distribute]
|
||||
|
||||
tasks:
|
||||
- name: Build list of all mesh public keys
|
||||
set_fact:
|
||||
all_mesh_keys: >-
|
||||
{{ groups['ssh_mesh']
|
||||
| map('extract', hostvars)
|
||||
| selectattr('ssh_pubkey', 'defined')
|
||||
| map(attribute='ssh_pubkey')
|
||||
| list }}
|
||||
|
||||
- name: Include admin key
|
||||
set_fact:
|
||||
all_mesh_keys: >-
|
||||
{{ all_mesh_keys + [admin_key] }}
|
||||
when: admin_key is defined
|
||||
|
||||
- name: Ensure .ssh directory exists
|
||||
file:
|
||||
path: "~/.ssh"
|
||||
state: directory
|
||||
mode: "0700"
|
||||
|
||||
- name: Ensure authorized_keys exists
|
||||
file:
|
||||
path: "~/.ssh/authorized_keys"
|
||||
state: touch
|
||||
mode: "0600"
|
||||
changed_when: false
|
||||
|
||||
- name: Add missing keys to authorized_keys
|
||||
lineinfile:
|
||||
path: "~/.ssh/authorized_keys"
|
||||
line: "{{ item }}"
|
||||
state: present
|
||||
loop: "{{ all_mesh_keys }}"
|
||||
loop_control:
|
||||
label: "{{ item.split()[-1] | default('unknown') }}"
|
||||
|
||||
- name: SSH Mesh — Verify Connectivity
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
connection: local
|
||||
tags: [verify]
|
||||
|
||||
tasks:
|
||||
- name: Build mesh host list
|
||||
set_fact:
|
||||
mesh_hosts: >-
|
||||
{{ groups['ssh_mesh']
|
||||
| map('extract', hostvars)
|
||||
| list }}
|
||||
|
||||
- name: Test SSH from localhost to each mesh host
|
||||
shell: |
|
||||
ssh -o BatchMode=yes \
|
||||
-o ConnectTimeout=5 \
|
||||
-o StrictHostKeyChecking=accept-new \
|
||||
-i ~/.ssh/id_ed25519 \
|
||||
-p {{ item.ansible_port | default(22) }} \
|
||||
{{ item.ansible_user }}@{{ item.ansible_host }} \
|
||||
"echo ok" 2>&1
|
||||
register: ssh_tests
|
||||
loop: "{{ mesh_hosts }}"
|
||||
loop_control:
|
||||
label: "localhost -> {{ item.inventory_hostname | default(item.ansible_host) }}"
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Display connectivity matrix
|
||||
debug:
|
||||
msg: |
|
||||
SSH Mesh Verification (from localhost):
|
||||
{% for result in ssh_tests.results %}
|
||||
{{ '✓' if result.rc == 0 and 'ok' in (result.stdout | default('')) else '✗' }} -> {{ result.item.inventory_hostname | default(result.item.ansible_host) }}{% if result.rc != 0 or 'ok' not in (result.stdout | default('')) %} ({{ result.stdout_lines[-1] | default('unknown error') }}){% endif %}
|
||||
|
||||
{% endfor %}
|
||||
{{ ssh_tests.results | selectattr('rc', 'equalto', 0) | list | length }}/{{ ssh_tests.results | length }} hosts reachable
|
||||
|
||||
- name: Test cross-host SSH (sample pairs)
|
||||
shell: |
|
||||
results=""
|
||||
{% for pair in cross_test_pairs | default([]) %}
|
||||
src_user="{{ pair.src_user }}"
|
||||
src_host="{{ pair.src_host }}"
|
||||
src_port="{{ pair.src_port | default(22) }}"
|
||||
dst_user="{{ pair.dst_user }}"
|
||||
dst_host="{{ pair.dst_host }}"
|
||||
dst_port="{{ pair.dst_port | default(22) }}"
|
||||
out=$(ssh -o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=no \
|
||||
-p ${src_port} ${src_user}@${src_host} \
|
||||
"ssh -o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new \
|
||||
-i ~/.ssh/id_ed25519 -p ${dst_port} ${dst_user}@${dst_host} 'echo ok'" 2>&1)
|
||||
if echo "$out" | grep -q "ok"; then
|
||||
results="${results}✓ {{ pair.label }}\n"
|
||||
else
|
||||
results="${results}✗ {{ pair.label }} ($(echo "$out" | tail -1))\n"
|
||||
fi
|
||||
{% endfor %}
|
||||
echo -e "$results"
|
||||
register: cross_tests
|
||||
when: cross_test_pairs is defined
|
||||
changed_when: false
|
||||
|
||||
- name: Display cross-host results
|
||||
debug:
|
||||
msg: |
|
||||
Cross-Host SSH Tests:
|
||||
{{ cross_tests.stdout }}
|
||||
when: cross_tests is not skipped and cross_tests.stdout is defined
|
||||
Reference in New Issue
Block a user