Updated
This commit is contained in:
parent
1c6c25f442
commit
63d79f7f76
|
|
@ -284,7 +284,7 @@ interpreter_python=auto_silent
|
||||||
;verbosity=0
|
;verbosity=0
|
||||||
|
|
||||||
# (boolean) Toggle to control the showing of deprecation warnings
|
# (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.
|
# (boolean) Toggle to control showing warnings related to running devel.
|
||||||
;devel_warning=True
|
;devel_warning=True
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,21 @@
|
||||||
---
|
---
|
||||||
- name: Deploy Docker Compose stack with pull and restart
|
- name: Deploy Docker Compose stack with pull and restart
|
||||||
hosts: ki5bhv.com
|
hosts: killer2.ki5bhv.com
|
||||||
become: true
|
become: true
|
||||||
tasks:
|
tasks:
|
||||||
|
|
||||||
- name: Qbits
|
- name: Qbits
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
project_src: /home/justin/docker/qbits/
|
project_src: /home/justin/docker/qbits
|
||||||
pull: always
|
pull: always
|
||||||
recreate: auto
|
register: result
|
||||||
|
retries: 3
|
||||||
|
delay: 3
|
||||||
|
until: result is not failed
|
||||||
|
|
||||||
|
|
||||||
- name: Pause
|
- name: Pause
|
||||||
ansible.builtin.pause:
|
ansible.builtin.pause:
|
||||||
seconds: 10
|
seconds: 1
|
||||||
|
|
||||||
- name: homarr
|
- name: homarr
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|
@ -22,7 +25,7 @@
|
||||||
|
|
||||||
- name: Pause
|
- name: Pause
|
||||||
ansible.builtin.pause:
|
ansible.builtin.pause:
|
||||||
seconds: 10
|
seconds: 1
|
||||||
|
|
||||||
- name: wireguard
|
- name: wireguard
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|
@ -32,7 +35,7 @@
|
||||||
|
|
||||||
- name: Pause
|
- name: Pause
|
||||||
ansible.builtin.pause:
|
ansible.builtin.pause:
|
||||||
seconds: 10
|
seconds: 1
|
||||||
|
|
||||||
- name: beszel
|
- name: beszel
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|
@ -42,7 +45,7 @@
|
||||||
|
|
||||||
- name: Pause
|
- name: Pause
|
||||||
ansible.builtin.pause:
|
ansible.builtin.pause:
|
||||||
seconds: 10
|
seconds: 1
|
||||||
|
|
||||||
- name: cup
|
- name: cup
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|
@ -52,17 +55,7 @@
|
||||||
|
|
||||||
- name: Pause
|
- name: Pause
|
||||||
ansible.builtin.pause:
|
ansible.builtin.pause:
|
||||||
seconds: 10
|
seconds: 1
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
- name: ntfy
|
- name: ntfy
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|
@ -72,7 +65,7 @@
|
||||||
|
|
||||||
- name: Pause
|
- name: Pause
|
||||||
ansible.builtin.pause:
|
ansible.builtin.pause:
|
||||||
seconds: 10
|
seconds: 1
|
||||||
|
|
||||||
- name: vaultwarden
|
- name: vaultwarden
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|
@ -82,7 +75,7 @@
|
||||||
|
|
||||||
- name: Pause
|
- name: Pause
|
||||||
ansible.builtin.pause:
|
ansible.builtin.pause:
|
||||||
seconds: 10
|
seconds: 1
|
||||||
|
|
||||||
- name: uptime-kuma
|
- name: uptime-kuma
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|
@ -92,7 +85,7 @@
|
||||||
|
|
||||||
- name: Pause
|
- name: Pause
|
||||||
ansible.builtin.pause:
|
ansible.builtin.pause:
|
||||||
seconds: 10
|
seconds: 1
|
||||||
|
|
||||||
- name: overseerr
|
- name: overseerr
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|
@ -102,7 +95,7 @@
|
||||||
|
|
||||||
- name: Pause
|
- name: Pause
|
||||||
ansible.builtin.pause:
|
ansible.builtin.pause:
|
||||||
seconds: 10
|
seconds: 1
|
||||||
|
|
||||||
- name: portainer
|
- name: portainer
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|
@ -112,7 +105,7 @@
|
||||||
|
|
||||||
- name: Pause
|
- name: Pause
|
||||||
ansible.builtin.pause:
|
ansible.builtin.pause:
|
||||||
seconds: 10
|
seconds: 1
|
||||||
|
|
||||||
- name: it-tools
|
- name: it-tools
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|
@ -122,7 +115,7 @@
|
||||||
|
|
||||||
- name: Pause
|
- name: Pause
|
||||||
ansible.builtin.pause:
|
ansible.builtin.pause:
|
||||||
seconds: 10
|
seconds: 1
|
||||||
|
|
||||||
- name: termix
|
- name: termix
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|
@ -132,7 +125,7 @@
|
||||||
|
|
||||||
- name: Pause
|
- name: Pause
|
||||||
ansible.builtin.pause:
|
ansible.builtin.pause:
|
||||||
seconds: 10
|
seconds: 1
|
||||||
|
|
||||||
- name: ntfy
|
- name: ntfy
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|
@ -140,6 +133,218 @@
|
||||||
pull: always
|
pull: always
|
||||||
recreate: auto
|
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
|
- name: Prune non-dangling images
|
||||||
community.docker.docker_prune:
|
community.docker.docker_prune:
|
||||||
containers: false
|
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
|
- name: Update Plex Sever
|
||||||
hosts: media.ki5bhv.com
|
hosts: mediaserver.ki5bhv.com
|
||||||
become: yes
|
become: yes
|
||||||
|
|
||||||
tasks:
|
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
|
- name: Proxmox Update and upgrade apt packages
|
||||||
hosts: proxmox
|
hosts: proxmox
|
||||||
become: yes
|
become: yes
|
||||||
serial: 1
|
serial: 1
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
|
||||||
- name: Update packages with apt
|
- name: Update packages with apt
|
||||||
when: ansible_facts['pkg_mgr'] == 'apt'
|
when: ansible_facts['pkg_mgr'] == 'apt'
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
update_cache: true
|
update_cache: true
|
||||||
|
cache_valid_time: 3600
|
||||||
|
lock_timeout: 600
|
||||||
|
|
||||||
- name: Installing proxmox guest agent
|
- name: Installing proxmox guest agent
|
||||||
when: ansible_facts['pkg_mgr'] == 'apt'
|
when: ansible_facts['pkg_mgr'] == 'apt'
|
||||||
|
|
@ -45,10 +40,13 @@
|
||||||
become: yes
|
become: yes
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
|
||||||
- name: Update packages with apt
|
- name: Update packages with apt
|
||||||
when: ansible_facts['pkg_mgr'] == 'apt'
|
when: ansible_facts['pkg_mgr'] == 'apt'
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
update_cache: true
|
update_cache: true
|
||||||
|
cache_valid_time: 3600
|
||||||
|
lock_timeout: 600
|
||||||
|
|
||||||
- name: Upgrade packages with apt
|
- name: Upgrade packages with apt
|
||||||
when: ansible_facts['pkg_mgr'] == 'apt'
|
when: ansible_facts['pkg_mgr'] == 'apt'
|
||||||
|
|
@ -72,10 +70,3 @@
|
||||||
name: net-tools
|
name: net-tools
|
||||||
state: latest
|
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