2025-09-13 20:26:11 +02:00
|
|
|
---
|
|
|
|
|
- name: Ensure base packages
|
|
|
|
|
ansible.builtin.package:
|
|
|
|
|
name:
|
|
|
|
|
- apt-transport-https
|
|
|
|
|
- ca-certificates
|
|
|
|
|
- curl
|
|
|
|
|
- git
|
|
|
|
|
- vim
|
|
|
|
|
- ufw
|
2026-03-08 22:53:38 +00:00
|
|
|
- fail2ban
|
2025-09-13 20:26:11 +02:00
|
|
|
- python3
|
|
|
|
|
- python3-venv
|
|
|
|
|
state: present
|
|
|
|
|
update_cache: true
|
|
|
|
|
|
|
|
|
|
- name: Harden SSH
|
|
|
|
|
ansible.builtin.copy:
|
|
|
|
|
dest: /etc/ssh/sshd_config.d/10-hardening.conf
|
|
|
|
|
owner: root
|
|
|
|
|
group: root
|
|
|
|
|
mode: '0644'
|
|
|
|
|
content: |
|
|
|
|
|
PasswordAuthentication no
|
|
|
|
|
PermitRootLogin no
|
|
|
|
|
PubkeyAuthentication yes
|
|
|
|
|
|
|
|
|
|
- name: Restart sshd
|
|
|
|
|
ansible.builtin.service:
|
|
|
|
|
name: ssh
|
|
|
|
|
state: restarted
|
|
|
|
|
|
2026-03-27 23:52:54 +01:00
|
|
|
- name: Ensure .ssh directory exists for ops_bridge_user
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: "/home/{{ ops_bridge_user | default('tegwick') }}/.ssh"
|
|
|
|
|
state: directory
|
|
|
|
|
owner: "{{ ops_bridge_user | default('tegwick') }}"
|
|
|
|
|
group: "{{ ops_bridge_user | default('tegwick') }}"
|
|
|
|
|
mode: '0700'
|
|
|
|
|
|
|
|
|
|
- name: Inject ops-bridge public key into authorized_keys
|
|
|
|
|
ansible.posix.authorized_key:
|
|
|
|
|
user: "{{ ops_bridge_user | default('tegwick') }}"
|
|
|
|
|
key: "{{ ops_bridge_pubkey }}"
|
|
|
|
|
comment: "ops-bridge@{{ inventory_hostname }}"
|
|
|
|
|
state: present
|
|
|
|
|
when: ops_bridge_pubkey is defined and ops_bridge_pubkey | length > 0
|
|
|
|
|
|
2026-03-27 02:28:51 +01:00
|
|
|
- name: Configure UFW default incoming policy
|
2025-09-13 20:26:11 +02:00
|
|
|
ansible.builtin.ufw:
|
|
|
|
|
state: enabled
|
|
|
|
|
policy: deny
|
|
|
|
|
direction: incoming
|
|
|
|
|
|
2026-03-27 02:28:51 +01:00
|
|
|
- name: Allow UFW routing (required for k3s flannel pod networking)
|
|
|
|
|
ansible.builtin.ufw:
|
|
|
|
|
policy: allow
|
|
|
|
|
direction: routed
|
|
|
|
|
|
2025-09-13 20:26:11 +02:00
|
|
|
- name: Allow SSH in UFW
|
|
|
|
|
ansible.builtin.ufw:
|
|
|
|
|
rule: allow
|
|
|
|
|
name: OpenSSH
|
|
|
|
|
|
2026-08-11 23:56:28 +02:00
|
|
|
# k3s API access is source-restricted. See roles/base/defaults/main.yml for why
|
|
|
|
|
# this is declared rather than hand-applied. Order matters below: grants are
|
|
|
|
|
# added BEFORE the blanket rule is removed, so convergence never opens a window
|
|
|
|
|
# in which the operator cannot reach the API.
|
|
|
|
|
|
|
|
|
|
- name: Allow k3s API from approved operator sources only
|
|
|
|
|
ansible.builtin.ufw:
|
|
|
|
|
rule: allow
|
|
|
|
|
port: '6443'
|
|
|
|
|
proto: tcp
|
|
|
|
|
from_ip: "{{ item.address }}"
|
|
|
|
|
comment: "{{ item.comment | default('k3s-api-operator') }}"
|
|
|
|
|
loop: "{{ k3s_api_allowed_sources }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item.address }}"
|
|
|
|
|
|
|
|
|
|
- name: Remove blanket k3s API rule if present (must not be world-reachable)
|
2026-03-08 22:53:38 +00:00
|
|
|
ansible.builtin.ufw:
|
|
|
|
|
rule: allow
|
|
|
|
|
port: '6443'
|
|
|
|
|
proto: tcp
|
2026-08-11 23:56:28 +02:00
|
|
|
delete: true
|
|
|
|
|
|
|
|
|
|
- name: Revoke k3s API access for retired operator sources
|
|
|
|
|
ansible.builtin.ufw:
|
|
|
|
|
rule: allow
|
|
|
|
|
port: '6443'
|
|
|
|
|
proto: tcp
|
|
|
|
|
from_ip: "{{ item.address }}"
|
|
|
|
|
delete: true
|
|
|
|
|
loop: "{{ k3s_api_revoked_sources }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item.address }}"
|
|
|
|
|
|
|
|
|
|
- name: Warn when no operator source is allowed to reach the k3s API
|
|
|
|
|
ansible.builtin.debug:
|
|
|
|
|
msg: >-
|
|
|
|
|
k3s_api_allowed_sources is empty, so 6443/tcp is closed to all external
|
|
|
|
|
sources on this host. This is the safe default, not an error. SSH is
|
|
|
|
|
unaffected and the host remains recoverable. Set the allowlist in
|
|
|
|
|
inventory/group_vars/all.yaml to restore API access.
|
|
|
|
|
when: k3s_api_allowed_sources | length == 0
|
2026-03-08 22:53:38 +00:00
|
|
|
|
|
|
|
|
- name: Allow Flannel VXLAN in UFW
|
|
|
|
|
ansible.builtin.ufw:
|
|
|
|
|
rule: allow
|
|
|
|
|
port: '8472'
|
|
|
|
|
proto: udp
|
|
|
|
|
|
|
|
|
|
- name: Enable fail2ban
|
|
|
|
|
ansible.builtin.service:
|
|
|
|
|
name: fail2ban
|
|
|
|
|
state: started
|
|
|
|
|
enabled: true
|
|
|
|
|
|
|
|
|
|
- name: Configure fail2ban SSH jail
|
|
|
|
|
ansible.builtin.copy:
|
|
|
|
|
dest: /etc/fail2ban/jail.d/sshd.conf
|
|
|
|
|
owner: root
|
|
|
|
|
group: root
|
|
|
|
|
mode: '0644'
|
|
|
|
|
content: |
|
|
|
|
|
[sshd]
|
|
|
|
|
enabled = true
|
|
|
|
|
port = ssh
|
|
|
|
|
filter = sshd
|
|
|
|
|
maxretry = 5
|
|
|
|
|
bantime = 3600
|
|
|
|
|
findtime = 600
|
|
|
|
|
notify: Restart fail2ban
|
|
|
|
|
|
|
|
|
|
- name: Set HISTCONTROL to ignorespace
|
|
|
|
|
ansible.builtin.copy:
|
|
|
|
|
dest: /etc/profile.d/histcontrol.sh
|
|
|
|
|
owner: root
|
|
|
|
|
group: root
|
|
|
|
|
mode: '0644'
|
|
|
|
|
content: |
|
|
|
|
|
export HISTCONTROL=ignorespace
|
|
|
|
|
|
2025-09-13 20:26:11 +02:00
|
|
|
- name: Set timezone
|
|
|
|
|
community.general.timezone:
|
|
|
|
|
name: "{{ timezone | default('UTC') }}"
|