diff --git a/hosts b/hosts index be3908c..4e53a0a 100644 --- a/hosts +++ b/hosts @@ -37,6 +37,7 @@ zoneminder.universe.local [server:children] auth backup +bastionhost cluster database dhcpserver @@ -49,13 +50,17 @@ jitsimeet mailserver mastodon nameserver -printspooler +printspooler proxyserver -webservers +webserver [auth] freeradius.universe.local +[bastionhost] +bastion.universe.local +newbastion.universe.local + [backup] backup.universe.local @@ -143,10 +148,10 @@ endor.universe.local endorvm.universe.local tuxedo-book-xp1511.universe.local -[webservers] -nextcloud.universe.local -searx.universe.local -webserver.universe.local +[webserver] +nextcloud.universe.local +searx.universe.local +webserver.universe.local [workstation:children] development diff --git a/local.yml b/local.yml index fb83b88..9e8601a 100644 --- a/local.yml +++ b/local.yml @@ -42,18 +42,18 @@ portage: sync: yes when: ansible_distribution == 'Gentoo' - ignore_errors: True - hosts: all:!database pre_tasks: - name: pre-run | upgrade system (debian, ubuntu, etc.) apt: upgrade=dist #changed_when: false + notify: update aide database when: ansible_distribution in ["Debian", "Ubuntu", "Linux Mint"] - name: pre-run | upgrade system (arch) pacman: upgrade=true + notify: update aide database when: ansible_distribution == 'Archlinux' - ignore_errors: True # run roles - hosts: all @@ -80,11 +80,11 @@ roles: - nameserver -- hosts: webservers - tags: server,webservers +- hosts: webserver + tags: server,webserver become: true roles: - - webservers + - webserver - hosts: mailserver tags: server,mailserver diff --git a/roles/bastion/handlers/main.yml b/roles/bastion/handlers/main.yml new file mode 100644 index 0000000..52405e7 --- /dev/null +++ b/roles/bastion/handlers/main.yml @@ -0,0 +1,21 @@ +--- +- name: update aide database + listen: "update aide db" + tags: aide,hardening,system + block: + - name: system setup | aide | run aide --update to check for legitimate changes + command: aide --update + register: aide_update_result + changed_when: "'new database written to' in aide_update_result.stdout" + async: 1800 # Allow up to 30 minutes for update + poll: 15 + + - name: system setup | aide | activate updated database + copy: + src: /var/lib/aide/aide.db.new + dest: /var/lib/aide/aide.db + remote_src: true + owner: root + group: root + mode: '0600' + when: aide_update_result.changed \ No newline at end of file diff --git a/roles/bastion/tasks/aide.yml b/roles/bastion/tasks/aide.yml new file mode 100644 index 0000000..75ffdde --- /dev/null +++ b/roles/bastion/tasks/aide.yml @@ -0,0 +1,42 @@ +--- +- name: system setup | aide | install aide package + tags: aide,hardening,system + package: + name: aide + state: present + +- name: system setup | aide | check if aide database exists + tags: aide,hardening,system + stat: + path: /var/lib/aide/aide.db + register: aide_db + +- name: system setup | aide | initialize aide database if it does not exist + tags: aide,hardening,system + block: + - name: system setup | aide | run aide --init (this may take a while) + command: aide --init + register: aide_init_result + changed_when: "'AIDE, version' in aide_init_result.stdout" + async: 1800 # Allow up to 30 minutes for initialization + poll: 15 + + - name: system setup | aide | copy new database to be the active one + copy: + src: /var/lib/aide/aide.db.new + dest: /var/lib/aide/aide.db + remote_src: true + owner: root + group: root + mode: '0600' + when: aide_init_result.changed + when: not aide_db.stat.exists + +- name: system setup | aide | schedule daily check + tags: aide,hardening,system + cron: + name: "AIDE daily check" + minute: "0" + hour: "5" + job: "/usr/bin/aide --check" + cron_file: aide_check # Creates /etc/cron.d/aide_check \ No newline at end of file diff --git a/roles/bastion/tasks/firewall.yml b/roles/bastion/tasks/firewall.yml new file mode 100644 index 0000000..058cb7b --- /dev/null +++ b/roles/bastion/tasks/firewall.yml @@ -0,0 +1,27 @@ +--- +- name: system setup | firewall | install ufw + package: + name: ufw + state: present + +- name: system setup | firewall | deny all incoming traffic by default and enable firewall + community.general.ufw: + state: enabled + policy: deny + +- name: system setup | firewall | allow ssh from anywhere + community.general.ufw: + rule: allow + port: '22' + proto: tcp + src: 'any' + +- name: system setup | firewall | allow monitoring traffic from internal networks + community.general.ufw: + rule: allow + proto: "{{ item.proto }}" + port: "{{ item.port | default(omit) }}" + src: '192.168.1.0/24' # Passe dies an dein internes Netzwerk an + loop: + - { proto: 'icmp', comment: 'Allow Ping' } + - { proto: 'udp', port: '161', comment: 'Allow SNMP' } diff --git a/roles/bastion/tasks/main.yml b/roles/bastion/tasks/main.yml new file mode 100644 index 0000000..b2ae7ed --- /dev/null +++ b/roles/bastion/tasks/main.yml @@ -0,0 +1,19 @@ +# Load distro-specific variables +- include_vars: "{{ ansible_distribution | lower }}.yml" + tags: always + ignore_errors: True + +- block: + - debug: + msg: Debug + + # Perform remaining tasks: + - import_tasks: users.yml + - import_tasks: system_setup/openssh_hardening.yml + - import_tasks: system_setup/firewall.yml + - import_tasks: system_setup/package_hardening.yml + - import_tasks: system_setup/user_hardening.yml + - import_tasks: system_setup/aide.yml + + rescue: + - set_fact: task_failed=true diff --git a/roles/bastion/tasks/openssh_hardening.yml b/roles/bastion/tasks/openssh_hardening.yml new file mode 100644 index 0000000..b5611d9 --- /dev/null +++ b/roles/bastion/tasks/openssh_hardening.yml @@ -0,0 +1,38 @@ +--- +- name: system setup | openssh | copy hardened sshd config for bastion + tags: openssh,ssh,system,settings + copy: + dest: /etc/ssh/sshd_config.d/hardened.conf + owner: root + group: root + mode: '0644' + content: | + # This file is managed by Ansible for the bastion role + # It overwrites/complements settings from the base role. + LogLevel VERBOSE + MaxAuthTries 3 + PubkeyAuthentication yes + AuthorizedKeysFile .ssh/authorized_keys + PasswordAuthentication no + UsePAM yes + AllowAgentForwarding no + AllowTcpForwarding no + X11Forwarding no + PrintMotd no + + # Harden Ciphers and Algorithms + KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256 + Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr + MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com + HostKeyAlgorithms ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256 + + PrintLastLog no + TCPKeepAlive yes + Subsystem sftp /usr/lib/ssh/sftp-server + AcceptEnv * + AllowUsers lowpriv sshjumpuser + + Match User lowpriv,sshjumpuser + AllowAgentForwarding yes + AllowTcpForwarding yes + notify: [ "restart_sshd", "update aide database" ] \ No newline at end of file diff --git a/roles/bastion/tasks/package_hardening.yml b/roles/bastion/tasks/package_hardening.yml new file mode 100644 index 0000000..edaed77 --- /dev/null +++ b/roles/bastion/tasks/package_hardening.yml @@ -0,0 +1,42 @@ +--- +- name: system setup | package hardening | remove unnecessary packages (Debian family) + tags: packages,hardening,system + package: + name: + # Daemons not needed on a bastion host + - apache2* + - nginx* + - lighttpd* + - samba* + - nfs-kernel-server + - bind9 + - postfix + - cups* + - avahi-daemon + # Common utilities not required for a minimal system + - popularity-contest + - whoopsie + - command-not-found + # Games and fun stuff + - bsdgames + - fortune-mod + state: absent + purge: true # Also removes configuration files + notify: update aide database + when: ansible_os_family == "Debian" + +- name: system setup | package hardening | remove unnecessary packages (RedHat family) + tags: packages,hardening,system + package: + name: + - httpd* + - nginx* + - samba* + - nfs-utils + - named + - postfix + - cups* + - avahi + state: absent + notify: update aide database + when: ansible_os_family == "RedHat" \ No newline at end of file diff --git a/roles/bastion/tasks/user_hardening.yml b/roles/bastion/tasks/user_hardening.yml new file mode 100644 index 0000000..19a3e4d --- /dev/null +++ b/roles/bastion/tasks/user_hardening.yml @@ -0,0 +1,24 @@ +--- +- name: system setup | user hardening | remove unnecessary system accounts + tags: users,hardening,system + user: + name: "{{ item }}" + state: absent + remove: true # Also removes home directory and mail spool + loop: + # Legacy or unused service accounts + - lp + - sync + - shutdown + - halt + - mail + - news + - uucp + - proxy + - backup + - list + - irc + - gnats + - games + notify: update aide database + ignore_errors: true # Some users might not exist, which is fine \ No newline at end of file diff --git a/roles/bastion/tasks/users.yml b/roles/bastion/tasks/users.yml new file mode 100644 index 0000000..43cea15 --- /dev/null +++ b/roles/bastion/tasks/users.yml @@ -0,0 +1,7 @@ +# Configure users for the bastion host +- name: Manage bastion user accounts by including user-specific task files + include_tasks: "users/{{ item }}.yml" + loop: + - rene + - lowpriv + - sshjumpuser diff --git a/roles/bastion/tasks/users/lowpriv.yml b/roles/bastion/tasks/users/lowpriv.yml new file mode 100644 index 0000000..a1db0c0 --- /dev/null +++ b/roles/bastion/tasks/users/lowpriv.yml @@ -0,0 +1,28 @@ +- name: users | lowpriv | add user to system + user: + name: lowpriv + comment: Restricted user for interactive shell + shell: /usr/bin/rbash + state: present + create_home: True + generate_ssh_key: False + password_lock: True + +- name: users | lowpriv | getent user home directory + getent: + database: passwd + key: "lowpriv" + split: ":" + register: getent_passwd_lowpriv + changed_when: false + +- name: users | lowpriv | set home directory fact + set_fact: + user_home: "{{ getent_passwd_lowpriv.ansible_facts.getent_passwd['lowpriv'][4] }}" + user: "lowpriv" + +- name: users | lowpriv | import ssh configuration tasks from base role + import_tasks: ../../../base/tasks/users/install_public_keys.yml + +- name: users | lowpriv | import known_hosts task from base role + import_tasks: ../../../base/tasks/users/install_known_hosts.yml \ No newline at end of file diff --git a/roles/bastion/tasks/users/rene.yml b/roles/bastion/tasks/users/rene.yml new file mode 100644 index 0000000..23abe77 --- /dev/null +++ b/roles/bastion/tasks/users/rene.yml @@ -0,0 +1,7 @@ +- include_vars: 'users.yml' + +- name: users | rene | remove user from system + user: + name: rene + state: absent + remove: True \ No newline at end of file diff --git a/roles/bastion/tasks/users/sshjumpuser.yml b/roles/bastion/tasks/users/sshjumpuser.yml new file mode 100644 index 0000000..1e1aab4 --- /dev/null +++ b/roles/bastion/tasks/users/sshjumpuser.yml @@ -0,0 +1,28 @@ +- name: users | sshjumpuser | add user to system + user: + name: sshjumpuser + comment: SSH Jump User - no tty - no password + shell: /bin/false + state: present + create_home: True + generate_ssh_key: False + password_lock: True + +- name: users | sshjumpuser | getent user home directory + getent: + database: passwd + key: "sshjumpuser" + split: ":" + register: getent_passwd_sshjumpuser + changed_when: false + +- name: users | sshjumpuser | set home directory fact + set_fact: + user_home: "{{ getent_passwd_sshjumpuser.ansible_facts.getent_passwd['sshjumpuser'][4] }}" + user: "sshjumpuser" + +- name: users | sshjumpuser | import ssh configuration tasks from base role + import_tasks: ../../../base/tasks/users/install_public_keys.yml + +- name: users | sshjumpuser | import known_hosts task from base role + import_tasks: ../../../base/tasks/users/install_known_hosts.yml \ No newline at end of file