diff --git a/.gitignore b/.gitignore index 54a1384d5bc19a9903df10619e4d309c0e399d21..d0c8d0d68e4715defab9b533550a6ce55ce34c4d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,8 @@ testsite/preseed_files/ testsite/tls/*.pem testsite/tls/*.key testsite/tls/*.*_*.cfg -testsite/retry/* \ No newline at end of file +testsite/retry/* +testsite/ssh/*_key +testsite/ssh/*_key.pub +testsite/ssh/*.example.com +testsite/ssh/*.example.com.pub \ No newline at end of file diff --git a/docs/rolereference.rst b/docs/rolereference.rst index 147edd5f275fc71be8c4f22e5ab5d160d3c35075..e176c9d1aa7b22112ac4db6e7d1fa7e77568f0c1 100644 --- a/docs/rolereference.rst +++ b/docs/rolereference.rst @@ -1305,3 +1305,82 @@ website): - role: database db_name: phpinfo_example_com db_password: phpinfo_example_com + + +Backup Server +------------- + +The ``backup_server`` role can be used for setting-up a server to act as backup +storage for the backup clients. Storage is made available to the clients +exclusively via SFTP on a dedicated port and dedicated OpenSSH server +instance. This instance is specifically configured and tailored for this +purpose. + +The role is primarily aimed for use with `Duplicity +`_, but should be also usable for generic SFTP +uploads. + +The role implements the following: + +* Installs backup software (Duplicity, Duply). +* Creates a dedicated directory structure for backups with the following structure: + + * ``/srv/backups/`` - main directory under which all the backups reside. + * ``/srv/backups/SERVER_NAME/`` - home directory for the backup user, name + after the server. Backup users are confined to their respective home + directory via chroot. Backup users can't write to their own home directory, + though. + * ``/srv/backups/SERVER_NAME/duplicity/`` - directory where the Duplicity + backups are stored at. This directory is writable by the respective backup + user. + * ``SERVER_NAME/.ssh/`` - directory where authorized keys are stored. Backup + user is not allowed to make modifications to this directory and files + contained within (i.e. backup users can't add more keys to the + ``authorized_keys`` file). +* Creates dedicated operating system users for backup clients. These users will + be made members of the ``backup`` group as well (as an additional group). +* Sets-up ``authorized_keys`` for the backup clients. +* Makes sure the backup users can't log-in via regular OpenSSH server instance. +* Sets-up dedicated OpenSSH server instances to be used exclusively by backup + clients. The instance listens on TCP port ``2222``. +* Updates firewall to allow incoming TCP connections to port + ``2222``. Connections are allowed only from the configured IP addresses + associated with backup clients. + + +Parameters +~~~~~~~~~~ + +**backup_clients** (list, optional) + List of backup clients that are connecting to the backup server. This is + usually done on a per-server basis. Each item in the list is a dictionary + describing the backup client. The following keys are available: + + **server** (string, mandatory) + Name of the server that is backed up. It is highly recommended to use + server's FQDN for this purpose. The dedicated operating system user created + will have the name of format ``bak-ESCAPED_SERVER_NAME``, where + ``ESCAPED_SERVER_NAME`` is calculated by taking the passed-in server name + and replacing all dots (``.``) with undescores (``_``). For example, + ``web.example.com`` will be turned into ``bak-web_example_com``. + + **uid** (integer, optional, ``whatever OS picks``) + Uid for the operating system user. User's default group will have a GID + identical to the user's UID if specified. Otherwise user's default group + will have OS-determined GID. + + **ip** (IPv4 address, mandatory) + IPv4 address from which the backup client server is connecting to the backup + server. Used for introducing stricter firewall rules. + +**backup_host_ssh_private_keys** (dictionary, mandatory) + Defines host keys used for the dedicated OpenSSH server instance for + backup. Key values that must be provided are: **dsa**, **rsa**, **ed25519**, + and **ecdsa**, with values for each one of them corresponding to a private key + generated using the appropriate algorithm. Keys for this purpose can be easily + created via commands:: + + ssh-keygen -f backup_server_dsa_key -N '' -t dsa + ssh-keygen -f backup_server_rsa_key -N '' -t rsa + ssh-keygen -f backup_server_ed25519_key -N '' -t ed25519 + ssh-keygen -f backup_server_ecdsa_key -N '' -t ecdsa diff --git a/docs/testsite.rst b/docs/testsite.rst index 19c0459dcee66901adde593d663eb469921f5f61..8506974b8c9bd5af6e3b053ff73e064e698666d8 100644 --- a/docs/testsite.rst +++ b/docs/testsite.rst @@ -110,15 +110,29 @@ In order to deploy the test site, the following steps would normally be taken: location ``testsite/tls/ca.pem``. It is very important to include the full CA chain used for LDAP server. -6. Generate the preseed files: +6. Generate SSH keys to be used by the backup server and backup clients: + + .. code-block:: shell + + ssh-keygen -f ssh/backup_server_dsa_key -N '' -t dsa + ssh-keygen -f ssh/backup_server_rsa_key -N '' -t rsa + ssh-keygen -f ssh/backup_server_ed25519_key -N '' -t ed25519 + ssh-keygen -f ssh/backup_server_ecdsa_key -N '' -t ecdsa + ssh-keygen -f ssh/mail.example.com -N '' + ssh-keygen -f ssh/ldap.example.com -N '' + ssh-keygen -f ssh/xmpp.example.com -N '' + ssh-keygen -f ssh/web.example.com -N '' + ssh-keygen -f ssh/backup.example.com -N '' + +7. Generate the preseed files: .. code-block:: shell ansible-playbook playbooks/preseed.yml -7. Install all servers using the generated preseed files. +8. Install all servers using the generated preseed files. -8. Add the SSH host fingerprints to your ``known_hosts`` file (don't forget to +9. Add the SSH host fingerprints to your ``known_hosts`` file (don't forget to remove old entries if you are redoing the process). You can easily obtain all the necessary fingerprints with command (don't forget to modify domain if you need to): @@ -127,14 +141,14 @@ In order to deploy the test site, the following steps would normally be taken: ssh-keyscan -t ed25519 mail.example.com ldap.example.com xmpp.example.com web.example.com $(resolveip -s mail.example.com) $(resolveip -s ldap.example.com) $(resolveip -s xmpp.example.com) $(resolveip -s web.example.com) -9. Invoke the ``bootstrap.yml`` playbook in order to set-up some basic - environment for Ansible runs on all servers: +10. Invoke the ``bootstrap.yml`` playbook in order to set-up some basic + environment for Ansible runs on all servers: .. code-block:: shell ansible-playbook playbooks/bootstrap.yml -10. Finally, apply configuration on all servers: +11. Finally, apply configuration on all servers: .. code-block:: shell diff --git a/roles/backup_server/defaults/main.yml b/roles/backup_server/defaults/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..26b6a6710aa60252ce03e7dd6b0534c7fca31db1 --- /dev/null +++ b/roles/backup_server/defaults/main.yml @@ -0,0 +1,8 @@ +--- + +backup_clients: [] +backup_host_ssh_keys: + dsa: "{{ ssh_key_dir }}/{{ ansible_fqdn }}_dsa_key" + rsa: "{{ ssh_key_dir }}/{{ ansible_fqdn }}_rsa_key" + ed25519: "{{ ssh_key_dir }}/{{ ansible_fqdn }}_ed25519_key" + ecdsa: "{{ ssh_key_dir }}/{{ ansible_fqdn }}_ecdsa_key" diff --git a/roles/backup_server/files/backup-sshd_config b/roles/backup_server/files/backup-sshd_config new file mode 100644 index 0000000000000000000000000000000000000000..2f93b6a939c58376cdad4781221c2cecee85c034 --- /dev/null +++ b/roles/backup_server/files/backup-sshd_config @@ -0,0 +1,82 @@ +# Listen on separate port for backup purposes. +Port 2222 + +# Use the SSH protocol version 2 (which is safer). +Protocol 2 + +# Define dedicated host keys for backup SSH server. +HostKey /etc/ssh-backup/ssh_host_rsa_key +HostKey /etc/ssh-backup/ssh_host_dsa_key +HostKey /etc/ssh-backup/ssh_host_ecdsa_key +HostKey /etc/ssh-backup/ssh_host_ed25519_key + +# Use privilege separation for increased security. +UsePrivilegeSeparation yes + +# Configure logging. +SyslogFacility AUTH +LogLevel INFO + +# Users logging-in have 10 seconds to login upon established connection. +LoginGraceTime 10 + +# Don't allow root accounts logins. +PermitRootLogin no + +# Enforce strict checking of home directory mode. However, this is not used for +# the chroots (chroots must check mode). +StrictModes yes + +# Allow public key authentication. +PubkeyAuthentication yes + +# Don't read the user's ~/.rhosts and ~/.shosts files for eventual +# RhostsRSAAuthentication or HostbasedAuthentication. +IgnoreRhosts yes + +# Disable host-based authentication. +HostbasedAuthentication no + +# Do not allow logins with empty passwords. +PermitEmptyPasswords no + +# Don't allow challenge-response authentication. +ChallengeResponseAuthentication no + +# Disable password-based authentication. +PasswordAuthentication no + +# Disable X11 forwarding. +X11Forwarding no + +# Do not print motd to avoid eventual issues for clients. +PrintMotd no + +# Do not print the date and time of the last user login. +PrintLastLog no + +# Use TPC keepalives for detecting dead connections. +TCPKeepAlive yes + +# Use the internal SFTP so we can also easily utilise chroot. +Subsystem sftp internal-sftp + +# Use PAM. But thanks to PasswordAuthentication being set to "no", PAM will be +# used just for session stuff. +UsePAM yes + +# Specify a dedicated PID file for the backup SSH. +PidFile /var/run/sshd-backup.pid + +# Users logging-in are forced to use the SFTP server. +ForceCommand internal-sftp + +# Chroot logged-in users to their home directories. +ChrootDirectory %h + +# Do not allow any TCP forwarding. +AllowTCPForwarding no + +# Only allow the members of this group to log-in into this instance of OpenSSH +# server. +AllowGroups backup \ No newline at end of file diff --git a/roles/backup_server/files/ssh-backup.default b/roles/backup_server/files/ssh-backup.default new file mode 100644 index 0000000000000000000000000000000000000000..381ff1bfd17899e3a3a1bc00f083ac035ac0f4c5 --- /dev/null +++ b/roles/backup_server/files/ssh-backup.default @@ -0,0 +1,4 @@ +# Default settings for OpenSSH backup server. Used by systemd service. + +# Separate configuration file is used for backup instance. +SSHD_OPTS="-f /etc/ssh-backup/sshd_config" diff --git a/roles/backup_server/files/ssh-backup.service b/roles/backup_server/files/ssh-backup.service new file mode 100644 index 0000000000000000000000000000000000000000..bb7929bbc3d5046adfb1c9061c75d685a5c3c845 --- /dev/null +++ b/roles/backup_server/files/ssh-backup.service @@ -0,0 +1,14 @@ +[Unit] +Description=OpenBSD Secure Shell server for backup purposes +After=network.target auditd.service +ConditionPathExists=!/etc/ssh-backup/sshd_not_to_be_run + +[Service] +EnvironmentFile=-/etc/default/ssh-backup +ExecStart=/usr/sbin/sshd -D $SSHD_OPTS +ExecReload=/bin/kill -HUP $MAINPID +KillMode=process +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/roles/backup_server/files/ssh-backup.socket b/roles/backup_server/files/ssh-backup.socket new file mode 100644 index 0000000000000000000000000000000000000000..c411d252724e5c7a892768f384a9e7763359d93c --- /dev/null +++ b/roles/backup_server/files/ssh-backup.socket @@ -0,0 +1,12 @@ +[Unit] +Description=OpenBSD Secure Shell server socket for backup purposes +Before=ssh-backup.service +Conflicts=ssh-backup.service +ConditionPathExists=!/etc/ssh-backup/sshd_not_to_be_run + +[Socket] +ListenStream=2222 +Accept=yes + +[Install] +WantedBy=sockets.target diff --git a/roles/backup_server/handlers/main.yml b/roles/backup_server/handlers/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..bb255c8401e86481e4361cfd9fe66e725fee4b96 --- /dev/null +++ b/roles/backup_server/handlers/main.yml @@ -0,0 +1,4 @@ +--- + +- name: Restart backup SSH server + service: name=ssh-backup state=restarted diff --git a/roles/backup_server/tasks/main.yml b/roles/backup_server/tasks/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..610b56c9f789ff440d8ab594f2064f0f631f5abc --- /dev/null +++ b/roles/backup_server/tasks/main.yml @@ -0,0 +1,96 @@ +--- + +- name: Install backup software + apt: name="{{ item }}" state=installed + with_items: + - duplicity + - duply + +- name: Create directory for storing backups + file: path="/srv/backups" state=directory + owner="root" group="root" mode=751 + +- name: Create backup client groups + group: name="{{ item.server | replace('.', '_') | regex_replace('^', 'bak-') }}" + gid="{{ item.uid | default(omit) }}" system="yes" + with_items: backup_clients + +- name: Create backup client users + user: name="{{ item.server | replace('.', '_') | regex_replace('^', 'bak-') }}" + group="{{ item.server | replace('.', '_') | regex_replace('^', 'bak-') }}" + groups="backup" + uid="{{ item.uid | default(omit) }}" + system=yes createhome=no state=present home="/srv/backups/{{ item.server }}" + with_items: backup_clients + +- name: Create home directories for backup client users + file: path="/srv/backups/{{ item.server }}" state=directory + owner="root" group="{{ item.server | replace('.', '_') | regex_replace('^', 'bak-') }}" mode=750 + with_items: backup_clients + +- name: Create duplicity directories for backup client users + file: path="/srv/backups/{{ item.server }}/duplicity" state=directory + owner="{{ item.server | replace('.', '_') | regex_replace('^', 'bak-') }}" + group="{{ item.server | replace('.', '_') | regex_replace('^', 'bak-') }}" + mode=770 + with_items: backup_clients + +- name: Create SSH directory for backup client users + file: path="/srv/backups/{{ item.server }}/.ssh" state=directory + owner="root" group="root" mode=751 + with_items: backup_clients + +- name: Populate authorized keys for backup client users + authorized_key: user="{{ item.server | replace('.', '_') | regex_replace('^', 'bak-') }}" + key="{{ item.public_key }}" manage_dir="no" state="present" + with_items: backup_clients + +- name: Set-up authorized_keys file permissions for backup client users + file: path="/srv/backups/{{ item.server }}/.ssh/authorized_keys" state=file + owner="root" group="{{ item.server | replace('.', '_') | regex_replace('^', 'bak-') }}" + mode=640 + with_items: backup_clients + +- name: Deny the backup group login via regular SSH + lineinfile: dest="/etc/ssh/sshd_config" state=present line="DenyGroups backup" + notify: + - Restart SSH + +- name: Set-up directory for the backup OpenSSH server instance + file: path="/etc/ssh-backup/" state=directory + owner="root" group="root" mode="700" + +- name: Deploy configuration file for the backup OpenSSH server instance service + copy: src="ssh-backup.default" dest="/etc/default/ssh-backup" + owner="root" group="root" mode="644" + notify: + - Restart backup SSH server + +- name: Deploy configuration file for the backup OpenSSH server instance + copy: src="backup-sshd_config" dest="/etc/ssh-backup/sshd_config" + owner="root" group="root" mode="600" + notify: + - Restart backup SSH server + +- name: Deploy the private keys for backup OpenSSH server instance + copy: content="{{ item.value }}" dest="/etc/ssh-backup/ssh_host_{{ item.key }}_key" + owner="root" group="root" mode="600" + with_dict: backup_host_ssh_private_keys + no_log: True + notify: + - Restart backup SSH server + +- name: Deploy backup OpenSSH server systemd service file + copy: src="ssh-backup.service" dest="/etc/systemd/system/ssh-backup.service" + owner=root group=root mode=644 + notify: + - Reload systemd + - Restart backup SSH server + +- name: Start and enable OpenSSH backup service + service: name="ssh-backup" state="started" enabled="yes" + +- name: Deploy firewall configuration for backup server + template: src="ferm_backup.conf.j2" dest="/etc/ferm/conf.d/40-backup.conf" owner=root group=root mode=640 + notify: + - Restart ferm diff --git a/roles/backup_server/templates/ferm_backup.conf.j2 b/roles/backup_server/templates/ferm_backup.conf.j2 new file mode 100644 index 0000000000000000000000000000000000000000..360fe0013c8b77d4fd40822517b986571fe4aad2 --- /dev/null +++ b/roles/backup_server/templates/ferm_backup.conf.j2 @@ -0,0 +1,10 @@ +{% if backup_clients -%} +table filter { + chain INPUT { + saddr ({% for client in backup_clients %} {{ client.ip }}{% endfor %}) @subchain "backup_in" { + # SSH + proto tcp dport 2222 ACCEPT; + } + } +} +{%- endif %} diff --git a/testsite/group_vars/backup.yml b/testsite/group_vars/backup.yml new file mode 100644 index 0000000000000000000000000000000000000000..e8a5d20e8a0c5c899e3959274c5048404ca7813d --- /dev/null +++ b/testsite/group_vars/backup.yml @@ -0,0 +1,23 @@ +--- + +local_mail_aliases: + root: "root john.doe@{{ testsite_domain }}" + +smtp_relay_host: mail.{{ testsite_domain }} + +smtp_relay_truststore: /etc/ssl/certs/ca.pem + +backup_clients: + - server: web.example.com + uid: 3000 + public_key: "{{ lookup('file', inventory_dir + '/ssh/web.example.com.pub') }}" + ip: 10.32.64.1 + - server: mail.example.com + public_key: "{{ lookup('file', inventory_dir + '/ssh/mail.example.com.pub') }}" + ip: 10.32.64.1 + +backup_host_ssh_private_keys: + dsa: "{{ lookup('file', inventory_dir + '/ssh/backup_server_dsa_key') }}" + rsa: "{{ lookup('file', inventory_dir + '/ssh/backup_server_rsa_key') }}" + ed25519: "{{ lookup('file', inventory_dir + '/ssh/backup_server_ed25519_key') }}" + ecdsa: "{{ lookup('file', inventory_dir + '/ssh/backup_server_ecdsa_key') }}" diff --git a/testsite/hosts b/testsite/hosts index 242d1a6e2353cd3f7b8a46f02112d1bb1ce7508d..3e76a6334fe0e824ba4c5ec096ffd7c885a11502 100644 --- a/testsite/hosts +++ b/testsite/hosts @@ -13,8 +13,12 @@ mail.example.com [web] web.example.com +[backup] +backup.example.com + [testsite:children] ldap xmpp mail -web \ No newline at end of file +web +backup \ No newline at end of file diff --git a/testsite/playbooks/backup.yml b/testsite/playbooks/backup.yml new file mode 100644 index 0000000000000000000000000000000000000000..392894da2153e139c8399ae42dc90a484926443e --- /dev/null +++ b/testsite/playbooks/backup.yml @@ -0,0 +1,9 @@ +--- + +- hosts: backup + remote_user: ansible + sudo: yes + roles: + - common + - mail_forwarder + - backup_server \ No newline at end of file diff --git a/testsite/playbooks/site.yml b/testsite/playbooks/site.yml index 4d0e98a54b7878dab3b5a174e688d8c4657805ca..5f9a49da276af2b9a88c60f95bda8a084c29a17c 100644 --- a/testsite/playbooks/site.yml +++ b/testsite/playbooks/site.yml @@ -4,4 +4,5 @@ - include: ldap.yml - include: xmpp.yml - include: mail.yml -- include: web.yml \ No newline at end of file +- include: web.yml +- include: backup.yml \ No newline at end of file