move to recommended file and directory structure

This commit is contained in:
Tobias Petrich 2025-12-26 19:59:39 +01:00
parent 8af8eafaf7
commit 7c2c5e06b2
No known key found for this signature in database
GPG Key ID: 220BE847F99B1B62
25 changed files with 222 additions and 298 deletions

3
.gitignore vendored
View File

@ -22,3 +22,6 @@ bookstack-db.container
bookstack-srv.container
sgnarva-srv.container
sgnarva-db.container
ansible/inventories/production/host_vars/*/vars.yml
ansible/inventories/production/host_vars/*/vault.yml

View File

@ -1,31 +1,34 @@
# Ansible MicroOS VM setup
<!--
Unfortunately, the devsec hardening role does not play well with MicroOS.
## Description
1. Install devsec hardening collection
```shell
ansible-galaxy collection install devsec.hardening
(Vaulted) Variables for accessing the host with specific credentials are stored in the host_vars,
they are handled as specific for how each user accesses a specific host.
(Vaulted) Variables for the services are stored in the group_vars,
they are shared between all administrators of the host.
## Requirements
Create vars and vault file for accessing the host following this structure.
Adjust the username, become method and password.
`inventories/production/host_vars/io/vars.yml`:
```yaml
---
ansible_user: tobias
ansible_become_method: sudo
```
2. Create the inventory.txt file for the server
3. Run the hardening playbook. Does not run completely through because of MicroOS immutability. At some point, a PR properly supporting MicroOS could be opened to https://github.com/dev-sec/ansible-os-hardening
```shell
ansible-playbook -i inventory.txt hardening.yml
`inventories/production/host_vars/io/vault.yml`:
(create through `ansible-vault create vault.yml` with a strong password)
```yaml
---
ansible_become_pass: EXAMPLE
```
-->
4. Run the custom_hardening playbook. This mostly sets SSH parameters to best practice values.
## Usage
```shell
ansible-playbook -i inventory.txt custom_hardening.yml
```
5. Run the allow_privileged_ports_rootless playbook. This allows a rootless traefik container to use ports 80 and 443.
```shell
ansible-playbook -i inventory.txt allow_privileged_ports_rootless.yml
```
6. Run the deploy_services playbook. This creates groups and users for each service, creates a btrfs subvolume for data and copies the quadlet files to the correct location, then activates the service.
```shell
ansible-playbook -i inventory.txt deploy_services.yml
```
7. Run the deploy_traefik_config playbool. This copies the traefik configuration to the correct location.
```shell
ansible-playbook -i inventory.txt deploy_traefik_config.yml
ansible-playbook main.yml
```

View File

@ -1,18 +0,0 @@
---
- name: Allow normal users to bind to port 80
hosts: all
become: yes
tasks:
- name: Set sysctl to allow normal users to bind to ports starting from 80
sysctl:
name: net.ipv4.ip_unprivileged_port_start
value: 80
state: present
reload: yes
- name: Verify the sysctl setting
command: sysctl net.ipv4.ip_unprivileged_port_start
register: sysctl_result
- debug:
msg: "net.ipv4.ip_unprivileged_port_start: {{ sysctl_result.stdout }}"

View File

@ -1,52 +0,0 @@
---
- name: Automate borg backup
hosts: all
become: yes
tasks:
- name: Check if Borg backup script exist
stat:
path: /usr/local/sbin/backup.bash
register: backup_status
- name: Check if Borg check script exists
stat:
path: /usr/local/sbin/check_backup.bash
register: check_status
- name: Synchronize Borg scripts
when: backup_status.stat.exists == False or check_status.stat.exists == False
synchronize:
src: ./borg_scripts/
dest: /usr/local/sbin/
rsync_opts:
- "--chown=root:root"
- "--chmod=0700"
- name: Create borg backup systemd service
copy:
content: |
[Unit]
Description=Borg backup
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/borg_backup.sh
User=root
Group=root
dest: /etc/systemd/system/borg_backup.service
- name: Create borg backup systemd timer
copy:
content: |
[Unit]
Description=Borg backup timer
[Timer]
OnCalendar=*-*-* 05:00:00
Persistent=true
[Install]
WantedBy=timers.target
dest: /etc/systemd/system/borg_backup.timer
- name: Start and enable borg backup timer
systemd:
name: borg_backup.timer
enabled: yes
state: started

View File

@ -1,17 +0,0 @@
---
- name: install commonly used programs
hosts: all
become: yes
tasks:
- name: install common programs with zypper and transactional-update
community.general.zypper:
pkg:
- borgbackup
- tmux
- lynis
- toolbox
state: present
register: zypper_result
- name: reboot if software needed to be installed
ansible.builtin.reboot:
when: zypper_result.changed

View File

@ -1,67 +0,0 @@
---
- name: Apply SSH best practices configuration
hosts: all
become: yes
tasks:
- name: Ensure the sshd_config.d directory exists
file:
path: /etc/ssh/sshd_config.d
state: directory
owner: root
group: root
mode: '0755'
- name: Create a configuration file to apply SSH best practices
copy:
content: |
# Disable password authentication
PasswordAuthentication no
# Disable challenge-response authentication
ChallengeResponseAuthentication no
# Allow root login
PermitRootLogin yes
# Disable empty passwords
PermitEmptyPasswords no
# Disable X11 forwarding
X11Forwarding no
# Use only protocol 2
Protocol 2
# Log more verbosely
LogLevel VERBOSE
# Keep-alive packets to ensure connection stability
TCPKeepAlive yes
ClientAliveInterval 60
ClientAliveCountMax 10
dest: /etc/ssh/sshd_config.d/best_practices.conf
owner: root
group: root
mode: '0644'
- name: Restart SSH service to apply changes
service:
name: sshd
state: restarted
- name: Verify SSH configuration settings
shell: "sshd -T"
register: ssh_config_result
- name: Check specific SSH settings
debug:
msg: "{{ ssh_config_result.stdout_lines | select('search', 'passwordauthentication no') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'challengeresponseauthentication no') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'permitrootlogin yes') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'permitemptypasswords no') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'x11forwarding no') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'protocol 2') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'loglevel verbose') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'clientaliveinterval 60') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'clientalivecountmax 3') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'tcpkeepalive yes') | list }}"

View File

@ -1,11 +0,0 @@
---
- name: Copy Traefik configuration files to the server
hosts: all
become: yes
tasks:
- name: Synchronize Traefik configuration files
synchronize:
src: ./traefik_config/
dest: /var/vol/traefik/
rsync_opts:
- "--chown=traefik:traefik"

View File

@ -1,89 +0,0 @@
- name: Apply DevSec hardening
hosts: all
become: yes
vars:
sysctl_overwrite:
# Enable IPv4 traffic forwarding. Needed for containers.
net.ipv4.ip_forward: 1
os_security_users_allow:
- "change_user" # Ensure this user is allowed to avoid modifying /bin/su (does not work on read-only filesystems)
os_family: "Suse"
os_release: "Tumbleweed" # Treat MicroOS as Tumbleweed
os_version: "{{ ansible_distribution_version }}"
os_vars:
packages:
- sudo
- openssh
ignore_fs_types:
- squashfs
- iso9660
- vfat
auth_pam:
- common-password
- common-auth
- common-account
- common-session
pam_passwords:
- password requisite pam_pwquality.so retry=3
- password required pam_unix.so use_authtok remember=5 sha512 shadow
securetty: [console, tty1, tty2, tty3, tty4, tty5, tty6]
sshd:
package: openssh
service: sshd
config: /etc/ssh/sshd_config
kernel_modules_disabled:
- cramfs
- freevxfs
- jffs2
- hfs
- hfsplus
- squashfs
- udf
- vfat
auditd_package: audit # This is the correct package name for auditd in openSUSE
os_env_umask: "027" # Setting a default umask value
os_auth_uid_min: "1000" # Setting the minimum user ID for non-system users
os_auth_uid_max: "60000" # Setting the maximum user ID for non-system users
os_auth_gid_min: 1000
os_auth_gid_max: 60000
os_auth_sys_uid_min: "100" # Setting the minimum user ID for system users
os_auth_sys_uid_max: "499" # Setting the maximum user ID for system users
os_auth_sys_gid_min: 100
os_auth_sys_gid_max: 499
os_auth_sub_uid_min: 100000
os_auth_sub_uid_max: 600100000
os_auth_sub_uid_count: 65536
os_auth_sub_gid_min: 100000
os_auth_sub_gid_max: 600100000
os_auth_sub_gid_count: 65536
os_shadow_perms:
owner: root
group: shadow
mode: "0640"
os_passwd_perms:
owner: root
group: root
mode: "0644"
hidepid_option: "2" # allowed values: 0, 1, 2
os_mnt_boot_group: 'root'
os_mnt_boot_owner: 'root'
os_mnt_dev_group: 'root'
os_mnt_dev_owner: 'root'
os_mnt_dev_shm_group: 'root'
os_mnt_dev_shm_owner: 'root'
os_mnt_home_group: 'root'
os_mnt_home_owner: 'root'
os_mnt_run_group: 'root'
os_mnt_run_owner: 'root'
os_mnt_tmp_group: 'root'
os_mnt_tmp_owner: 'root'
os_mnt_var_group: 'root'
os_mnt_var_owner: 'root'
os_mnt_var_log_group: 'root'
os_mnt_var_log_owner: 'root'
os_mnt_var_log_audit_group: 'root'
os_mnt_var_log_audit_owner: 'root'
os_mnt_var_tmp_group: 'root'
os_mnt_var_tmp_owner: 'root'
roles:
- devsec.hardening.os_hardening

View File

@ -0,0 +1,13 @@
$ANSIBLE_VAULT;1.1;AES256;podman_hosts
34396465333337346339653661356338373861326337663939616531623866336233653963353739
6264653732373635336563333732303735653532393865350a313135343361633034623239643339
31326238316333326166366638623963653635623533623833333862646462333363353066663434
6536343138643462320a393638643763633433363861326139326536663439633566386664393964
37366533323633376436396431383231346438643136326138643565343239393734643662386232
31373630376164623663333361323531626165646236393732353031313636623434323931633434
33343834356237376264383064393135663435323134373561306166306561356431656434633834
66653039633833643930303331323236633532343731613137383835643338373364633834653264
34643861366665336638656261373531343233353735353435643431303835376635356331373437
38306537643730646131666665396466366161303165303539663438666461623335366532376666
34356161383031353939343531333062623064353865636437633436613334663866346163316664
32383134333464663133

View File

@ -0,0 +1,4 @@
podman_hosts:
hosts:
io:
ansible_host: io.rohrschacht.de

View File

@ -1,13 +1,12 @@
---
- name: apply custom hardening for ssh
import_playbook: custom_hardening.yml
- name: install commonly used programs
import_playbook: common_programs.yml
- name: allow privileged ports for rootless containers
import_playbook: allow_privileged_ports_rootless.yml
- name: deploy services
import_playbook: deploy_services.yml
- name: deploy traefik configuration
import_playbook: deploy_traefik_config.yml
- name: automate backup
import_playbook: automate_backup.yml
- name: podman host setup
hosts: podman_hosts
roles:
- common
- hardening
- rootless_host
- traefik
- backup
#- name: deploy services
# import_playbook: deploy_services.yml

View File

@ -0,0 +1,48 @@
---
- name: Deploy backup.bash script
ansible.builtin.template:
src: backup.bash.j2
dest: /usr/local/sbin/backup.bash
owner: root
group: root
mode: '0700'
- name: Deploy check_backup.bash script
ansible.builtin.template:
src: check_backup.bash.j2
dest: /usr/local/sbin/check_backup.bash
owner: root
group: root
mode: '0700'
- name: Create borg backup systemd service
copy:
content: |
[Unit]
Description=Borg backup
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/borg_backup.sh
User=root
Group=root
dest: /etc/systemd/system/borg_backup.service
- name: Create borg backup systemd timer
copy:
content: |
[Unit]
Description=Borg backup timer
[Timer]
OnCalendar=*-*-* 05:00:00
Persistent=true
[Install]
WantedBy=timers.target
dest: /etc/systemd/system/borg_backup.timer
- name: Start and enable borg backup timer
systemd:
name: borg_backup.timer
enabled: yes
state: started

View File

@ -1,10 +1,10 @@
#!/bin/bash
# Configuration
BACKUP_USER="your_remote_user" # Remote SSH username
BACKUP_HOST="your_remote_host" # Remote SSH server
BACKUP_PATH="/path/to/remote/backup/folder" # Remote backup folder
BORG_PASSPHRASE="your_encryption_password" # Encryption password (in plain text)
BACKUP_USER="{{ backup.user }}" # Remote SSH username
BACKUP_HOST="{{ backup.host }}" # Remote SSH server
BACKUP_PATH="{{ backup.path }}" # Remote backup folder
BORG_PASSPHRASE="{{ backup.passphrase }}" # Encryption password (in plain text)
BACKUP_NAME="backup-$(date +'%Y-%m-%d')" # Name of the backup archive
BACKUP_REPO="ssh://$BACKUP_USER@$BACKUP_HOST/$BACKUP_PATH" # Borg repository location

View File

@ -1,10 +1,10 @@
#!/bin/bash
# Configuration
BACKUP_USER="your_remote_user" # Remote SSH username
BACKUP_HOST="your_remote_host" # Remote SSH server
BACKUP_PATH="/path/ro/remote/backup/folder" # Remote backup folder
BORG_PASSPHRASE="your_encryption_password" # Encryption password (in plain text)
BACKUP_USER="{{ backup.user }}" # Remote SSH username
BACKUP_HOST="{{ backup.host }}" # Remote SSH server
BACKUP_PATH="{{ backup.path }}" # Remote backup folder
BORG_PASSPHRASE="{{ backup.passphrase }}" # Encryption password (in plain text)
BACKUP_NAME="backup-$(date +'%Y-%m-%d')" # Name of the backup archive
BACKUP_REPO="ssh://$BACKUP_USER@$BACKUP_HOST/$BACKUP_PATH" # Borg repository location

View File

@ -0,0 +1,3 @@
---
- name: Install common programs
include_tasks: programs.yml

View File

@ -0,0 +1,13 @@
---
- name: install common programs with zypper and transactional-update
community.general.zypper:
pkg:
- borgbackup
- tmux
- lynis
- toolbox
state: present
register: zypper_result
- name: reboot if software needed to be installed
ansible.builtin.reboot:
when: zypper_result.changed

View File

@ -0,0 +1,6 @@
---
- name: Restart sshd
ansible.builtin.service:
name: sshd
state: restarted

View File

@ -0,0 +1,3 @@
---
- name: SSH hardening
include_tasks: ssh.yml

View File

@ -0,0 +1,59 @@
---
- name: Ensure the sshd_config.d directory exists
file:
path: /etc/ssh/sshd_config.d
state: directory
owner: root
group: root
mode: '0755'
- name: Create a configuration file to apply SSH best practices
copy:
content: |
# Disable password authentication
PasswordAuthentication no
# Disable challenge-response authentication
ChallengeResponseAuthentication no
# Allow root login
PermitRootLogin yes
# Disable empty passwords
PermitEmptyPasswords no
# Disable X11 forwarding
X11Forwarding no
# Use only protocol 2
Protocol 2
# Log more verbosely
LogLevel VERBOSE
# Keep-alive packets to ensure connection stability
TCPKeepAlive yes
ClientAliveInterval 60
ClientAliveCountMax 10
dest: /etc/ssh/sshd_config.d/best_practices.conf
owner: root
group: root
mode: '0644'
notify: Restart sshd
- name: Verify SSH configuration settings
shell: "sshd -T"
register: ssh_config_result
- name: Check specific SSH settings
debug:
msg: "{{ ssh_config_result.stdout_lines | select('search', 'passwordauthentication no') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'challengeresponseauthentication no') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'permitrootlogin yes') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'permitemptypasswords no') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'x11forwarding no') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'protocol 2') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'loglevel verbose') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'clientaliveinterval 60') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'clientalivecountmax 3') | list }}\n
{{ ssh_config_result.stdout_lines | select('search', 'tcpkeepalive yes') | list }}"

View File

@ -0,0 +1,3 @@
---
- name: Allow rootless ports for podman
include_tasks: rootless_ports.yml

View File

@ -0,0 +1,14 @@
---
- name: Set sysctl to allow normal users to bind to ports starting from 80
sysctl:
name: net.ipv4.ip_unprivileged_port_start
value: 80
state: present
reload: yes
- name: Verify the sysctl setting
command: sysctl net.ipv4.ip_unprivileged_port_start
register: sysctl_result
- debug:
msg: "net.ipv4.ip_unprivileged_port_start: {{ sysctl_result.stdout }}"

View File

@ -0,0 +1,7 @@
---
- name: Synchronize Traefik configuration files
synchronize:
src: ./
dest: /var/vol/traefik/
rsync_opts:
- "--chown=traefik:traefik"