Updated
This commit is contained in:
parent
1c6c25f442
commit
63d79f7f76
|
|
@ -284,7 +284,7 @@ interpreter_python=auto_silent
|
|||
;verbosity=0
|
||||
|
||||
# (boolean) Toggle to control the showing of deprecation warnings
|
||||
;deprecation_warnings=True
|
||||
deprecation_warnings=False
|
||||
|
||||
# (boolean) Toggle to control showing warnings related to running devel.
|
||||
;devel_warning=True
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
---
|
||||
- name: Deploy Docker Compose stack with pull and restart
|
||||
hosts: ki5bhv.com
|
||||
hosts: killer2.ki5bhv.com
|
||||
become: true
|
||||
tasks:
|
||||
|
||||
- name: Qbits
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/qbits/
|
||||
project_src: /home/justin/docker/qbits
|
||||
pull: always
|
||||
recreate: auto
|
||||
register: result
|
||||
retries: 3
|
||||
delay: 3
|
||||
until: result is not failed
|
||||
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 10
|
||||
seconds: 1
|
||||
|
||||
- name: homarr
|
||||
community.docker.docker_compose_v2:
|
||||
|
|
@ -22,7 +25,7 @@
|
|||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 10
|
||||
seconds: 1
|
||||
|
||||
- name: wireguard
|
||||
community.docker.docker_compose_v2:
|
||||
|
|
@ -32,7 +35,7 @@
|
|||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 10
|
||||
seconds: 1
|
||||
|
||||
- name: beszel
|
||||
community.docker.docker_compose_v2:
|
||||
|
|
@ -42,7 +45,7 @@
|
|||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 10
|
||||
seconds: 1
|
||||
|
||||
- name: cup
|
||||
community.docker.docker_compose_v2:
|
||||
|
|
@ -52,17 +55,7 @@
|
|||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 10
|
||||
|
||||
- name: nginx-proxy
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/nginx-proxy/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 10
|
||||
seconds: 1
|
||||
|
||||
- name: ntfy
|
||||
community.docker.docker_compose_v2:
|
||||
|
|
@ -72,7 +65,7 @@
|
|||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 10
|
||||
seconds: 1
|
||||
|
||||
- name: vaultwarden
|
||||
community.docker.docker_compose_v2:
|
||||
|
|
@ -82,7 +75,7 @@
|
|||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 10
|
||||
seconds: 1
|
||||
|
||||
- name: uptime-kuma
|
||||
community.docker.docker_compose_v2:
|
||||
|
|
@ -92,7 +85,7 @@
|
|||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 10
|
||||
seconds: 1
|
||||
|
||||
- name: overseerr
|
||||
community.docker.docker_compose_v2:
|
||||
|
|
@ -102,7 +95,7 @@
|
|||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 10
|
||||
seconds: 1
|
||||
|
||||
- name: portainer
|
||||
community.docker.docker_compose_v2:
|
||||
|
|
@ -112,7 +105,7 @@
|
|||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 10
|
||||
seconds: 1
|
||||
|
||||
- name: it-tools
|
||||
community.docker.docker_compose_v2:
|
||||
|
|
@ -122,7 +115,7 @@
|
|||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 10
|
||||
seconds: 1
|
||||
|
||||
- name: termix
|
||||
community.docker.docker_compose_v2:
|
||||
|
|
@ -132,7 +125,7 @@
|
|||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 10
|
||||
seconds: 1
|
||||
|
||||
- name: ntfy
|
||||
community.docker.docker_compose_v2:
|
||||
|
|
@ -140,6 +133,218 @@
|
|||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
- name: netalertx
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/netalertx/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
- name: homarr
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/homarr/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
- name: wireguard
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/wireguard/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
- name: beszel
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/beszel/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
- name: cup
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/cup/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
|
||||
- name: ntfy
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/ntfy/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
- name: vaultwarden
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/vaultwarden/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
- name: uptime-kuma
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/uptime-kuma/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
- name: overseerr
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/overseerr/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
- name: portainer
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/portainer/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
- name: it-tools
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/it-tools/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
- name: termix
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/termix/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
- name: ntfy
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/ntfy/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
- name: netalertx
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/netalertx/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
- name: Wavelog
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/wavelog/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
- name: Kometa
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/kometa/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Pause
|
||||
ansible.builtin.pause:
|
||||
seconds: 1
|
||||
|
||||
- name: Tautulli
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/tautulli/
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Prune non-dangling images
|
||||
community.docker.docker_prune:
|
||||
containers: false
|
||||
images: true
|
||||
images_filters:
|
||||
dangling: false
|
||||
networks: false
|
||||
volumes: false
|
||||
builder_cache: false
|
||||
|
||||
- name: Deploy Docker Compose stack with pull and restart
|
||||
hosts: docker.ki5bhv.com
|
||||
become: true
|
||||
tasks:
|
||||
|
||||
- name: code
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/Docker/code-server
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Prune non-dangling images
|
||||
community.docker.docker_prune:
|
||||
containers: false
|
||||
images: true
|
||||
images_filters:
|
||||
dangling: false
|
||||
networks: false
|
||||
volumes: false
|
||||
builder_cache: false
|
||||
|
||||
- name: Deploy Docker Compose stack with pull and restart
|
||||
hosts: proxy-server.ki5bhv.com
|
||||
become: true
|
||||
tasks:
|
||||
- name: nginx proxy
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: /home/justin/docker/nginx-proxy
|
||||
pull: always
|
||||
recreate: auto
|
||||
|
||||
- name: Prune non-dangling images
|
||||
community.docker.docker_prune:
|
||||
containers: false
|
||||
|
|
|
|||
24
host
24
host
|
|
@ -1,24 +0,0 @@
|
|||
[linux]
|
||||
cloudflare-tunnel.ki5bhv.com
|
||||
pi-hole-server.ki5bhv.com
|
||||
mediaserver.ki5bhv.com
|
||||
webhost.ki5bhv.com
|
||||
gitserver.ki5bhv.com
|
||||
ansible.ki5bhv.com
|
||||
minecraft.ki5bhv.com
|
||||
ki5bhv.com
|
||||
coding.ki5bhv.com
|
||||
|
||||
[proxmox]
|
||||
cloudflare-tunnel.ki5bhv.com
|
||||
pi-hole-server.ki5bhv.com
|
||||
webhost.ki5bhv.com
|
||||
gitserver.ki5bhv.com
|
||||
ansible.ki5bhv.com
|
||||
minecraft.ki5bhv.com
|
||||
ki5bhv.com
|
||||
coding.ki5bhv.com
|
||||
|
||||
[hardware]
|
||||
mediaserver.ki5bhv.com
|
||||
|
||||
Binary file not shown.
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
# fix-ssh-host-keys.yaml
|
||||
#
|
||||
# Detects cloned VMs that share the same SSH host key and regenerates a
|
||||
# unique key set on each one, then refreshes the controller's known_hosts.
|
||||
#
|
||||
# Run with host key checking disabled for this one remediation pass,
|
||||
# since known_hosts is currently in a conflicted state:
|
||||
#
|
||||
# ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook fix-ssh-host-keys.yaml
|
||||
#
|
||||
# The `duplicate_fingerprint` guard means only hosts presenting the shared
|
||||
# key get touched. Hosts with an already-unique key are left alone.
|
||||
|
||||
- name: Regenerate shared SSH host keys on cloned guests
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: false
|
||||
vars:
|
||||
# The shared key every clone is currently presenting.
|
||||
duplicate_fingerprint: "SHA256:AZekUU+Wwn6S6sCjGD0SdkhVgj3WVqVdiVYa1AXYW8A"
|
||||
tasks:
|
||||
- name: Read current ED25519 host key fingerprint
|
||||
ansible.builtin.command:
|
||||
cmd: ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
|
||||
register: hostkey_fp
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Regenerate host keys if this host shares the duplicate key
|
||||
when: duplicate_fingerprint in (hostkey_fp.stdout | default(''))
|
||||
block:
|
||||
- name: Remove existing host keys
|
||||
ansible.builtin.shell:
|
||||
cmd: rm -f /etc/ssh/ssh_host_*
|
||||
# rm on a glob isn't idempotent-reportable; treat as changed.
|
||||
changed_when: true
|
||||
|
||||
- name: Generate a fresh unique set of host keys
|
||||
ansible.builtin.command:
|
||||
cmd: ssh-keygen -A
|
||||
changed_when: true
|
||||
|
||||
- name: Restart SSH so the new key is served
|
||||
ansible.builtin.service:
|
||||
name: ssh
|
||||
state: restarted
|
||||
|
||||
- name: Note that this host was remediated
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ inventory_hostname }} had the shared key and was regenerated."
|
||||
|
||||
- name: Refresh known_hosts on the Ansible controller
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
vars:
|
||||
known_hosts_path: "{{ lookup('env', 'HOME') }}/.ssh/known_hosts"
|
||||
target_hosts: "{{ groups['all'] | difference(['localhost']) }}"
|
||||
tasks:
|
||||
- name: Remove stale entries for every managed host
|
||||
ansible.builtin.command:
|
||||
cmd: "ssh-keygen -f {{ known_hosts_path }} -R {{ item }}"
|
||||
loop: "{{ target_hosts }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Rescan and add current keys
|
||||
ansible.builtin.shell:
|
||||
cmd: "ssh-keyscan -t ed25519,rsa,ecdsa {{ item }} >> {{ known_hosts_path }}"
|
||||
loop: "{{ target_hosts }}"
|
||||
changed_when: true
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
# open-all-ports.yml
|
||||
#
|
||||
# Opens all inbound ports on the target Ubuntu servers.
|
||||
#
|
||||
# Default behavior: UFW stays enabled but its default incoming
|
||||
# policy is set to allow, so every port is reachable.
|
||||
#
|
||||
# To turn the firewall OFF entirely instead, run with:
|
||||
# ansible-playbook -i inventory.ini open-all-ports.yml -e "disable_ufw=true"
|
||||
#
|
||||
# Uses the community.general.ufw module.
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook -i inventory.ini open-all-ports.yml
|
||||
|
||||
- name: Open all inbound ports
|
||||
hosts: ubuntu_servers
|
||||
become: true
|
||||
|
||||
vars:
|
||||
disable_ufw: false
|
||||
|
||||
tasks:
|
||||
- name: Set default incoming policy to allow (opens all ports)
|
||||
community.general.ufw:
|
||||
direction: incoming
|
||||
policy: allow
|
||||
|
||||
- name: Set default outgoing policy to allow
|
||||
community.general.ufw:
|
||||
direction: outgoing
|
||||
policy: allow
|
||||
|
||||
- name: Optionally disable UFW entirely
|
||||
community.general.ufw:
|
||||
state: disabled
|
||||
when: disable_ufw | bool
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
- name: Update Plex Sever
|
||||
hosts: media.ki5bhv.com
|
||||
hosts: mediaserver.ki5bhv.com
|
||||
become: yes
|
||||
|
||||
tasks:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
# restrict-ssh.yml
|
||||
#
|
||||
# Restricts SSH access on Ubuntu servers to a single IP range using UFW.
|
||||
# Uses the community.general.ufw module.
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook -i inventory.ini restrict-ssh.yml
|
||||
#
|
||||
# Override the range or port at runtime:
|
||||
# ansible-playbook -i inventory.ini restrict-ssh.yml -e "ssh_allowed_range=10.0.0.0/24 ssh_port=2222"
|
||||
|
||||
- name: Restrict SSH access to an allowed IP range
|
||||
hosts: ubuntu_servers
|
||||
become: true
|
||||
|
||||
vars:
|
||||
ssh_allowed_range: "192.168.1.0/24"
|
||||
ssh_port: 22
|
||||
|
||||
tasks:
|
||||
- name: Ensure UFW is installed
|
||||
ansible.builtin.apt:
|
||||
name: ufw
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Set default policy to deny incoming traffic
|
||||
community.general.ufw:
|
||||
direction: incoming
|
||||
policy: deny
|
||||
|
||||
- name: Set default policy to allow outgoing traffic
|
||||
community.general.ufw:
|
||||
direction: outgoing
|
||||
policy: allow
|
||||
|
||||
- name: Allow SSH only from the permitted range
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
from_ip: "{{ ssh_allowed_range }}"
|
||||
to_port: "{{ ssh_port }}"
|
||||
proto: tcp
|
||||
|
||||
- name: Remove any rule allowing SSH from anywhere (by port)
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
to_port: "{{ ssh_port }}"
|
||||
proto: tcp
|
||||
delete: true
|
||||
|
||||
- name: Remove any rule allowing the OpenSSH application profile from anywhere
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
name: OpenSSH
|
||||
delete: true
|
||||
|
||||
- name: Enable UFW
|
||||
community.general.ufw:
|
||||
state: enabled
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
# revert-ssh.yml
|
||||
#
|
||||
# Reverts the SSH IP-range restriction applied by restrict-ssh.yml.
|
||||
# Removes the range-limited SSH rule and re-opens SSH from anywhere so
|
||||
# you don't lose access. Optionally disables UFW entirely.
|
||||
#
|
||||
# Uses the community.general.ufw module.
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook -i inventory.ini revert-ssh.yml
|
||||
#
|
||||
# To also disable UFW completely:
|
||||
# ansible-playbook -i inventory.ini revert-ssh.yml -e "disable_ufw=true"
|
||||
#
|
||||
# Override the range or port to match what restrict-ssh.yml used:
|
||||
# ansible-playbook -i inventory.ini revert-ssh.yml -e "ssh_allowed_range=10.0.0.0/24 ssh_port=2222"
|
||||
|
||||
- name: Revert SSH IP-range restriction
|
||||
hosts: ubuntu_servers
|
||||
become: true
|
||||
|
||||
vars:
|
||||
ssh_allowed_range: "192.168.1.0/24"
|
||||
ssh_port: 22
|
||||
disable_ufw: false
|
||||
|
||||
tasks:
|
||||
- name: Remove the range-restricted SSH rule
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
from_ip: "{{ ssh_allowed_range }}"
|
||||
to_port: "{{ ssh_port }}"
|
||||
proto: tcp
|
||||
delete: true
|
||||
|
||||
- name: Re-open SSH from anywhere
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
to_port: "{{ ssh_port }}"
|
||||
proto: tcp
|
||||
when: not disable_ufw | bool
|
||||
|
||||
- name: Disable UFW entirely (optional)
|
||||
community.general.ufw:
|
||||
state: disabled
|
||||
when: disable_ufw | bool
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
---
|
||||
# ssh-hardening.yml
|
||||
# =============================================================================
|
||||
# Disable SSH password authentication and apply baseline hardening on Ubuntu.
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook -i inventory.ini ssh-hardening.yml
|
||||
# ansible-playbook -i inventory.ini ssh-hardening.yml --limit coding
|
||||
# ansible-playbook -i inventory.ini ssh-hardening.yml --check # dry run
|
||||
#
|
||||
# Example inventory.ini:
|
||||
# [servers]
|
||||
# coding ansible_host=192.168.1.21 ansible_user=justin
|
||||
#
|
||||
# SAFETY FEATURES:
|
||||
# * Refuses to run if the login user has no authorized_keys (no lockout).
|
||||
# * Fixes the cloud-init "first value wins" precedence trap automatically.
|
||||
# * Validates config with `sshd -t` BEFORE restarting.
|
||||
# * Verifies password auth is actually off at the end via `sshd -T`.
|
||||
#
|
||||
# Optional extras (fail2ban / UFW) use the community.general collection:
|
||||
# ansible-galaxy collection install community.general
|
||||
# =============================================================================
|
||||
|
||||
- name: Harden SSH (key-only authentication + baseline)
|
||||
hosts: all
|
||||
become: true
|
||||
|
||||
vars:
|
||||
# User whose key access is verified before passwords are disabled.
|
||||
# Defaults to whoever Ansible connects as.
|
||||
ssh_login_user: "{{ ansible_user | default(lookup('env', 'USER')) }}"
|
||||
|
||||
# ---- Core SSH settings -------------------------------------------------
|
||||
ssh_permit_root_login: "prohibit-password" # "no" to block root entirely
|
||||
ssh_allow_users: [] # e.g. ["justin", "deploy"]; [] = no restriction
|
||||
ssh_port: 22 # change to move SSH off 22
|
||||
|
||||
# ---- Optional hardening toggles ---------------------------------------
|
||||
manage_firewall: false # true = install + enable UFW
|
||||
install_fail2ban: false
|
||||
install_unattended_upgrades: false
|
||||
|
||||
handlers:
|
||||
# Handlers run in the order defined here, so validation always precedes restart.
|
||||
- name: Validate sshd config
|
||||
ansible.builtin.command: sshd -t
|
||||
changed_when: false
|
||||
listen: "restart ssh stack"
|
||||
|
||||
- name: Restart ssh
|
||||
ansible.builtin.systemd:
|
||||
name: ssh
|
||||
state: restarted
|
||||
listen: "restart ssh stack"
|
||||
|
||||
- name: Restart ssh.socket
|
||||
ansible.builtin.systemd:
|
||||
name: ssh.socket
|
||||
state: restarted
|
||||
when: ssh_socket_present | default(false) | bool
|
||||
listen: "restart ssh stack"
|
||||
|
||||
tasks:
|
||||
# ---- Pre-flight: prove we won't lock ourselves out --------------------
|
||||
- name: Resolve login user's home directory
|
||||
ansible.builtin.getent:
|
||||
database: passwd
|
||||
key: "{{ ssh_login_user }}"
|
||||
|
||||
- name: Set authorized_keys path
|
||||
ansible.builtin.set_fact:
|
||||
ssh_authorized_keys_path: "{{ getent_passwd[ssh_login_user][4] }}/.ssh/authorized_keys"
|
||||
|
||||
- name: Stat the login user's authorized_keys
|
||||
ansible.builtin.stat:
|
||||
path: "{{ ssh_authorized_keys_path }}"
|
||||
register: ak
|
||||
|
||||
- name: Refuse to continue if no SSH key is present
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ak.stat.exists
|
||||
- (ak.stat.size | default(0) | int) > 0
|
||||
fail_msg: >-
|
||||
{{ ssh_login_user }} has no authorized_keys at {{ ssh_authorized_keys_path }}.
|
||||
Aborting so you don't get locked out. Run `ssh-copy-id {{ ssh_login_user }}@<host>`
|
||||
first, verify key login works, then re-run this playbook.
|
||||
success_msg: "Key-based auth verified for {{ ssh_login_user }} - safe to proceed."
|
||||
|
||||
# ---- Detect socket activation (Ubuntu 22.10+ / 24.04 / 26.04) ---------
|
||||
- name: Check whether ssh.socket exists
|
||||
ansible.builtin.command: systemctl list-unit-files ssh.socket
|
||||
register: ssh_socket_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Record socket-activation state
|
||||
ansible.builtin.set_fact:
|
||||
ssh_socket_present: "{{ 'ssh.socket' in ssh_socket_check.stdout }}"
|
||||
|
||||
# ---- Fix the cloud-init precedence trap -------------------------------
|
||||
# sshd uses the FIRST value it sees for a directive. Files load in
|
||||
# alphanumeric order, so 50-cloud-init.conf (PasswordAuthentication yes)
|
||||
# beats a later 99-hardening.conf. Neutralize it at the source.
|
||||
- name: Check for the cloud-init SSH drop-in
|
||||
ansible.builtin.stat:
|
||||
path: /etc/ssh/sshd_config.d/50-cloud-init.conf
|
||||
register: cloud_init_conf
|
||||
|
||||
- name: Force PasswordAuthentication off in the cloud-init drop-in
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config.d/50-cloud-init.conf
|
||||
regexp: '^\s*PasswordAuthentication\s+'
|
||||
line: "PasswordAuthentication no"
|
||||
state: present
|
||||
when: cloud_init_conf.stat.exists
|
||||
notify: "restart ssh stack"
|
||||
|
||||
# ---- Authoritative hardening drop-in ----------------------------------
|
||||
- name: Deploy SSH hardening drop-in
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/ssh/sshd_config.d/99-hardening.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
validate: "sshd -t -f %s"
|
||||
content: |
|
||||
# Managed by Ansible - ssh-hardening.yml. Do not edit by hand.
|
||||
PasswordAuthentication no
|
||||
PubkeyAuthentication yes
|
||||
KbdInteractiveAuthentication no
|
||||
PermitRootLogin {{ ssh_permit_root_login }}
|
||||
{% if ssh_allow_users | length > 0 %}
|
||||
AllowUsers {{ ssh_allow_users | join(' ') }}
|
||||
{% endif %}
|
||||
{% if ssh_port != 22 %}
|
||||
Port {{ ssh_port }}
|
||||
{% endif %}
|
||||
notify: "restart ssh stack"
|
||||
|
||||
# ---- Stop cloud-init from re-enabling passwords on rebuild ------------
|
||||
- name: Check for cloud-init config directory
|
||||
ansible.builtin.stat:
|
||||
path: /etc/cloud/cloud.cfg.d
|
||||
register: cloud_init_dir
|
||||
|
||||
- name: Pin ssh_pwauth off for cloud-init
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/cloud/cloud.cfg.d/99-disable-password-auth.cfg
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
# Managed by Ansible - ssh-hardening.yml
|
||||
ssh_pwauth: false
|
||||
when: cloud_init_dir.stat.exists
|
||||
|
||||
# ---- Optional: UFW firewall -------------------------------------------
|
||||
- name: Configure UFW firewall
|
||||
when: manage_firewall | bool
|
||||
block:
|
||||
- name: Install UFW
|
||||
ansible.builtin.apt:
|
||||
name: ufw
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Allow the SSH port through UFW
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: "{{ ssh_port }}"
|
||||
proto: tcp
|
||||
|
||||
- name: Enable UFW with a default-deny inbound policy
|
||||
community.general.ufw:
|
||||
state: enabled
|
||||
policy: deny
|
||||
direction: incoming
|
||||
|
||||
# ---- Optional: fail2ban -----------------------------------------------
|
||||
- name: Install and enable fail2ban
|
||||
when: install_fail2ban | bool
|
||||
block:
|
||||
- name: Install fail2ban
|
||||
ansible.builtin.apt:
|
||||
name: fail2ban
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Enable and start fail2ban
|
||||
ansible.builtin.systemd:
|
||||
name: fail2ban
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
# ---- Optional: unattended security upgrades ---------------------------
|
||||
- name: Enable unattended security upgrades
|
||||
when: install_unattended_upgrades | bool
|
||||
block:
|
||||
- name: Install unattended-upgrades
|
||||
ansible.builtin.apt:
|
||||
name: unattended-upgrades
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Turn on periodic update + upgrade
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/apt/apt.conf.d/20auto-upgrades
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
APT::Periodic::Update-Package-Lists "1";
|
||||
APT::Periodic::Unattended-Upgrade "1";
|
||||
|
||||
# ---- Apply restarts, then verify the result ---------------------------
|
||||
- name: Apply any pending SSH restarts now
|
||||
ansible.builtin.meta: flush_handlers
|
||||
|
||||
- name: Read the effective sshd configuration
|
||||
ansible.builtin.command: sshd -T
|
||||
register: sshd_effective
|
||||
changed_when: false
|
||||
|
||||
- name: Confirm password authentication is actually disabled
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'passwordauthentication no' in sshd_effective.stdout"
|
||||
fail_msg: >-
|
||||
Password auth is STILL enabled in the effective config.
|
||||
Check drop-in precedence with: sudo sshd -T | grep -i passwordauth
|
||||
success_msg: "Confirmed: password authentication is disabled."
|
||||
|
|
@ -1,22 +1,17 @@
|
|||
---
|
||||
- name: Send Start update
|
||||
hosts: localhost
|
||||
|
||||
tasks:
|
||||
- name: send ntfy
|
||||
ansible.builtin.command:
|
||||
cmd: 'curl -d "Starting updating with ansible" ntfy.ki5bhv.com/server'
|
||||
|
||||
- name: Proxmox Update and upgrade apt packages
|
||||
hosts: proxmox
|
||||
become: yes
|
||||
serial: 1
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Update packages with apt
|
||||
when: ansible_facts['pkg_mgr'] == 'apt'
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
cache_valid_time: 3600
|
||||
lock_timeout: 600
|
||||
|
||||
- name: Installing proxmox guest agent
|
||||
when: ansible_facts['pkg_mgr'] == 'apt'
|
||||
|
|
@ -45,10 +40,13 @@
|
|||
become: yes
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Update packages with apt
|
||||
when: ansible_facts['pkg_mgr'] == 'apt'
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
cache_valid_time: 3600
|
||||
lock_timeout: 600
|
||||
|
||||
- name: Upgrade packages with apt
|
||||
when: ansible_facts['pkg_mgr'] == 'apt'
|
||||
|
|
@ -72,10 +70,3 @@
|
|||
name: net-tools
|
||||
state: latest
|
||||
|
||||
- name: Send completed update
|
||||
hosts: localhost
|
||||
|
||||
tasks:
|
||||
- name: send ntfy
|
||||
ansible.builtin.command:
|
||||
cmd: 'curl -d "Updated with ansible" ntfy.ki5bhv.com/server'
|
||||
|
|
|
|||
Loading…
Reference in New Issue