diff options
Diffstat (limited to 'molecule')
| -rw-r--r-- | molecule/default/converge.yml | 62 | ||||
| -rw-r--r-- | molecule/default/molecule.yml | 8 | ||||
| -rw-r--r-- | molecule/default/tests/test_client_setup.py | 103 | ||||
| -rw-r--r-- | molecule/default/tests/test_server_setup.py | 2 | ||||
| -rw-r--r-- | molecule/default/tests/test_systemd.py | 57 |
5 files changed, 183 insertions, 49 deletions
diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml index c77202c..d90f33a 100644 --- a/molecule/default/converge.yml +++ b/molecule/default/converge.yml @@ -4,6 +4,7 @@ - borg-client - borg-client-2 - borg-client-multi + - borg-client-nonroot vars: borg_server_host: borg-server @@ -12,29 +13,35 @@ # This would usually be set by the user globally on their ansible # repository and can be a security risk to do automatically. We will # however set the variable here in the pre_tasks since it is for testing. - - name: Set borg server openssh key variable + - name: Start ssh + ansible.builtin.systemd: + name: sshd + state: started become: true - block: - - name: Start ssh - ansible.builtin.systemd: - name: sshd - state: started - become: true - delegate_to: "{{ borg_server_host }}" + delegate_to: "{{ borg_server_host }}" - - name: Fetch ssh_key - ansible.builtin.command: >- - ssh-keyscan -t rsa {{ borg_server_host }} | sed "s/^[^ ]* //" - register: borg_server_ssh_keyscan - changed_when: false + - name: Fetch ssh_key + ansible.builtin.command: >- + ssh-keyscan -t rsa {{ borg_server_host }} | sed "s/^[^ ]* //" + register: borg_server_ssh_keyscan + changed_when: false - - name: Set ssh_key - ansible.builtin.set_fact: - borg_server_host_ssh_key: >- - {{ borg_server_ssh_keyscan.stdout - | split(" ") - | reject("search", borg_server_host) - | join(" ") }} + - name: Set ssh_key + ansible.builtin.set_fact: + borg_server_host_ssh_key: >- + {{ borg_server_ssh_keyscan.stdout + | split(" ") + | reject("search", borg_server_host) + | join(" ") }} + + - name: Create backup user for non-root test + ansible.builtin.user: + name: backupuser + home: /home/backupuser + shell: /bin/bash + state: present + become: true + when: inventory_hostname == 'borg-client-nonroot' - name: Converge - Default borg-client hosts: borg-client @@ -62,6 +69,7 @@ borg_server_host: borg-server borg_server_user_home: /opt/borg borg_decryption_keys_yaml_path: "{{ playbook_dir }}/decryption_keys.yml" + borg_ssh_key_type: ed25519 borg_backup_service_successful_exit_status: - 1 - TEMPFAIL @@ -105,3 +113,17 @@ borg_excluded_dirs: - /home/*/.cache borg_systemd_oncalendar: "*-*-* 04:00:00" + +- name: Converge - Non-root backup user + hosts: borg-client-nonroot + + roles: + - role: kliwniloc.borgbackup + vars: + borg_server_host: borg-server + borg_server_user_home: /opt/borg + borg_client_user: backupuser + borg_decryption_keys_yaml_path: "{{ playbook_dir }}/decryption_keys.yml" + borg_included_dirs: + - /etc + borg_excluded_dirs: [] diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml index 0d25587..b4f68cb 100644 --- a/molecule/default/molecule.yml +++ b/molecule/default/molecule.yml @@ -35,6 +35,14 @@ platforms: networks: - name: molecule-container-net + - name: borg-client-nonroot + image: ${MOLECULE_DISTRO_CLIENT:-debian:12} + dockerfile: Dockerfile.j2 + pre_build_image: false + privileged: true + networks: + - name: molecule-container-net + - name: borg-server image: ${MOLECULE_DISTRO_SERVER:-debian:12} dockerfile: Dockerfile.j2 diff --git a/molecule/default/tests/test_client_setup.py b/molecule/default/tests/test_client_setup.py index 25bf8e2..290e3dc 100644 --- a/molecule/default/tests/test_client_setup.py +++ b/molecule/default/tests/test_client_setup.py @@ -1,33 +1,72 @@ """Tests for client setup configuration""" import pytest -testinfra_hosts = ['borg-client', 'borg-client-2', 'borg-client-multi'] +testinfra_hosts = ['borg-client', 'borg-client-2', 'borg-client-multi', 'borg-client-nonroot'] + + +CLIENT_USER_MAP = { + 'borg-client': 'root', + 'borg-client-2': 'root', + 'borg-client-multi': 'root', + 'borg-client-nonroot': 'backupuser', +} + +CLIENT_SSH_KEY_TYPE_MAP = { + 'borg-client': 'rsa', + 'borg-client-2': 'ed25519', + 'borg-client-multi': 'rsa', + 'borg-client-nonroot': 'rsa', +} + + +def get_client_user(host): + hostname = host.backend.get_hostname() + return CLIENT_USER_MAP.get(hostname, 'root') + + +def get_client_ssh_key_type(host): + hostname = host.backend.get_hostname() + return CLIENT_SSH_KEY_TYPE_MAP.get(hostname, 'rsa') + + +def get_client_home(host): + user = get_client_user(host) + return f'/home/{user}' if user != 'root' else '/root' class TestSSHSetup: def test_ssh_directory_exists(self, host): - ssh_dir = host.file('/root/.ssh') + client_home = get_client_home(host) + client_user = get_client_user(host) + ssh_dir = host.file(f'{client_home}/.ssh') assert ssh_dir.exists assert ssh_dir.is_directory - assert ssh_dir.user == 'root' - assert ssh_dir.group == 'root' - assert ssh_dir.mode == 0o640 + assert ssh_dir.user == client_user + assert ssh_dir.group == client_user + assert ssh_dir.mode == 0o700 def test_ssh_private_key_exists(self, host): - key = host.file('/root/.ssh/id_rsa') + client_home = get_client_home(host) + client_user = get_client_user(host) + key_type = get_client_ssh_key_type(host) + key = host.file(f'{client_home}/.ssh/id_{key_type}') assert key.exists - assert key.user == 'root' - assert key.group == 'root' + assert key.user == client_user + assert key.group == client_user assert key.mode == 0o600 def test_ssh_public_key_exists(self, host): - key = host.file('/root/.ssh/id_rsa.pub') + client_home = get_client_home(host) + client_user = get_client_user(host) + key_type = get_client_ssh_key_type(host) + key = host.file(f'{client_home}/.ssh/id_{key_type}.pub') assert key.exists - assert key.user == 'root' - assert key.group == 'root' + assert key.user == client_user + assert key.group == client_user def test_known_hosts_contains_borg_server(self, host): - known_hosts = host.file('/root/.ssh/known_hosts') + client_home = get_client_home(host) + known_hosts = host.file(f'{client_home}/.ssh/known_hosts') assert known_hosts.exists assert known_hosts.contains('borg-server') @@ -35,23 +74,30 @@ class TestSSHSetup: class TestBackupScript: def test_backup_script_exists(self, host): hostname = host.backend.get_hostname() + client_user = get_client_user(host) if hostname == 'borg-client-multi': script1 = host.file('/usr/local/bin/run_borg_backup@configs') script2 = host.file('/usr/local/bin/run_borg_backup@home-data') assert script1.exists - assert script1.user == 'root' - assert script1.group == 'root' + assert script1.user == client_user + assert script1.group == client_user assert script1.mode == 0o711 assert script2.exists - assert script2.user == 'root' - assert script2.group == 'root' + assert script2.user == client_user + assert script2.group == client_user assert script2.mode == 0o711 + elif hostname == 'borg-client-nonroot': + script = host.file('/usr/local/bin/run_borg_backup@borg-server') + assert script.exists + assert script.user == client_user + assert script.group == client_user + assert script.mode == 0o711 elif hostname in ('borg-client', 'borg-client-2'): script = host.file('/usr/local/bin/run_borg_backup') assert script.exists - assert script.user == 'root' - assert script.group == 'root' + assert script.user == client_user + assert script.group == client_user assert script.mode == 0o711 else: pytest.fail(f"Unexpected hostname: {hostname}") @@ -64,6 +110,9 @@ class TestBackupScript: script2 = host.file('/usr/local/bin/run_borg_backup@home-data') assert script1.contains('borg create') assert script2.contains('borg create') + elif hostname == 'borg-client-nonroot': + script = host.file('/usr/local/bin/run_borg_backup@borg-server') + assert script.contains('borg create') elif hostname in ('borg-client', 'borg-client-2'): script = host.file('/usr/local/bin/run_borg_backup') assert script.contains('borg create') @@ -82,8 +131,8 @@ class TestBackupScript: script = host.file('/usr/local/bin/run_borg_backup') assert script.contains('-C') assert script.contains('lz4') - elif hostname == 'borg-client': - script = host.file('/usr/local/bin/run_borg_backup') + elif hostname in ('borg-client', 'borg-client-nonroot'): + script = host.file('/usr/local/bin/run_borg_backup') if hostname == 'borg-client' else host.file('/usr/local/bin/run_borg_backup@borg-server') assert script.contains('-C') assert script.contains('zstd') else: @@ -99,6 +148,10 @@ class TestBackupScript: assert script1.contains('/opt/borg/configs') assert script2.contains('borg@borg-server') assert script2.contains('/opt/borg/home-data') + elif hostname == 'borg-client-nonroot': + script = host.file('/usr/local/bin/run_borg_backup@borg-server') + assert script.contains('borg@borg-server') + assert script.contains('/opt/borg') elif hostname in ('borg-client', 'borg-client-2'): script = host.file('/usr/local/bin/run_borg_backup') assert script.contains('borg@borg-server') @@ -115,6 +168,10 @@ class TestBackupScript: assert '/etc' in script1.content_string assert '/home' in script2.content_string assert '--exclude' in script2.content_string + elif hostname == 'borg-client-nonroot': + script = host.file('/usr/local/bin/run_borg_backup@borg-server') + content = script.content_string + assert '/etc' in content elif hostname in ('borg-client', 'borg-client-2'): script = host.file('/usr/local/bin/run_borg_backup') content = script.content_string @@ -130,6 +187,9 @@ class TestBackupScript: script2 = host.file('/usr/local/bin/run_borg_backup@home-data') assert script1.mode == 0o711 assert script2.mode == 0o711 + elif hostname == 'borg-client-nonroot': + script = host.file('/usr/local/bin/run_borg_backup@borg-server') + assert script.mode == 0o711 elif hostname in ('borg-client', 'borg-client-2'): script = host.file('/usr/local/bin/run_borg_backup') assert script.mode == 0o711 @@ -143,9 +203,10 @@ class TestMultiInstanceBaseScript: if hostname != 'borg-client-multi': return + client_user = get_client_user(host) base_script = host.file('/usr/local/bin/run_borg_backup') assert base_script.exists - assert base_script.user == 'root' + assert base_script.user == client_user assert base_script.mode == 0o711 def test_base_script_contains_both_blocks(self, host): diff --git a/molecule/default/tests/test_server_setup.py b/molecule/default/tests/test_server_setup.py index cb644cb..3c494f7 100644 --- a/molecule/default/tests/test_server_setup.py +++ b/molecule/default/tests/test_server_setup.py @@ -75,7 +75,7 @@ class TestBorgSSHSetup: if not line.strip(): continue assert line.startswith('restrict,command="borg serve'), f"Line should start with restrict,command: {line[:50]}" - assert 'root@' in line, f"Line should contain root@ hostname marker: {line[-30:]}" + assert '@' in line and line.rstrip().endswith(('borg-client', 'borg-client-2', 'borg-client-multi', 'borg-client-nonroot')), f"Line should contain user@hostname marker: {line[-40:]}" assert '--restrict-to-repository' in line, f"Line should have repo restriction: {line[:80]}" def test_authorized_keys_multi_instance_no_cross_host_repos(self, host): diff --git a/molecule/default/tests/test_systemd.py b/molecule/default/tests/test_systemd.py index 9421959..7b7b2ab 100644 --- a/molecule/default/tests/test_systemd.py +++ b/molecule/default/tests/test_systemd.py @@ -2,7 +2,20 @@ import pytest -testinfra_hosts = ['borg-client', 'borg-client-2', 'borg-client-multi'] +testinfra_hosts = ['borg-client', 'borg-client-2', 'borg-client-multi', 'borg-client-nonroot'] + + +CLIENT_USER_MAP = { + 'borg-client': 'root', + 'borg-client-2': 'root', + 'borg-client-multi': 'root', + 'borg-client-nonroot': 'backupuser', +} + + +def get_client_user(host): + hostname = host.backend.get_hostname() + return CLIENT_USER_MAP.get(hostname, 'root') class TestSystemdServiceFile: @@ -18,6 +31,12 @@ class TestSystemdServiceFile: assert service2.exists assert service2.user == 'root' assert service2.group == 'root' + elif hostname == 'borg-client-nonroot': + service = host.file('/etc/systemd/system/borg_backup@borg-server.service') + assert service.exists + assert service.user == 'root' + assert service.group == 'root' + assert service.mode == 0o644 elif hostname in ('borg-client', 'borg-client-2'): service = host.file('/etc/systemd/system/borg_backup@borg-server.service') assert service.exists @@ -38,6 +57,13 @@ class TestSystemdServiceFile: assert service.contains('[Service]') assert service.contains('[Install]') assert service.contains('Type=oneshot') + elif hostname == 'borg-client-nonroot': + service = host.file('/etc/systemd/system/borg_backup@borg-server.service') + assert service.contains('[Unit]') + assert service.contains('[Service]') + assert service.contains('[Install]') + assert service.contains('Type=oneshot') + assert service.contains('ExecStart=/usr/local/bin/run_borg_backup') elif hostname in ('borg-client', 'borg-client-2'): service = host.file('/etc/systemd/system/borg_backup@borg-server.service') assert service.contains('[Unit]') @@ -48,9 +74,26 @@ class TestSystemdServiceFile: else: pytest.fail(f"Unexpected hostname: {hostname}") - def test_success_exit_status(self, host): + def test_service_user(self, host): hostname = host.backend.get_hostname() + client_user = get_client_user(host) + if hostname == 'borg-client-multi': + service1 = host.file('/etc/systemd/system/borg_backup@configs.service') + service2 = host.file('/etc/systemd/system/borg_backup@home-data.service') + for service in [service1, service2]: + assert service.contains(f'User={client_user}') + assert service.contains(f'Group={client_user}') + elif hostname in ('borg-client', 'borg-client-2', 'borg-client-nonroot'): + service = host.file('/etc/systemd/system/borg_backup@borg-server.service') + assert service.contains(f'User={client_user}') + assert service.contains(f'Group={client_user}') + else: + pytest.fail(f"Unexpected hostname: {hostname}") + + def test_success_exit_status(self, host): + hostname = host.backend.get_hostname() + if hostname in ('borg-client-multi', 'borg-client-nonroot'): return service = host.file('/etc/systemd/system/borg_backup@borg-server.service') @@ -80,7 +123,7 @@ class TestSystemdTimerFile: timer2 = host.file('/etc/systemd/system/borg_backup@home-data.timer') assert timer1.exists assert timer2.exists - elif hostname in ('borg-client', 'borg-client-2'): + elif hostname in ('borg-client', 'borg-client-2', 'borg-client-nonroot'): timer = host.file('/etc/systemd/system/borg_backup@borg-server.timer') assert timer.exists assert timer.user == 'root' @@ -101,7 +144,7 @@ class TestSystemdTimerFile: assert timer.contains('[Install]') assert timer.contains('OnCalendar=') assert timer.contains('AccuracySec=') - elif hostname in ('borg-client', 'borg-client-2'): + elif hostname in ('borg-client', 'borg-client-2', 'borg-client-nonroot'): timer = host.file('/etc/systemd/system/borg_backup@borg-server.timer') assert timer.contains('[Unit]') assert timer.contains('[Timer]') @@ -122,7 +165,7 @@ class TestSystemdTimerFile: elif hostname == 'borg-client-2': timer = host.file('/etc/systemd/system/borg_backup@borg-server.timer') assert 'OnCalendar=*-*-* 03:00:00' in timer.content_string - elif hostname == 'borg-client': + elif hostname in ('borg-client', 'borg-client-nonroot'): timer = host.file('/etc/systemd/system/borg_backup@borg-server.timer') assert 'OnCalendar=*-*-* 02:00:00' in timer.content_string else: @@ -140,7 +183,7 @@ class TestSystemdState: assert c1.stdout.strip() == 'enabled' assert c2.rc == 0 assert c2.stdout.strip() == 'enabled' - elif hostname in ('borg-client', 'borg-client-2'): + elif hostname in ('borg-client', 'borg-client-2', 'borg-client-nonroot'): timer_name = 'borg_backup@borg-server.timer' c = host.run(f"systemctl is-enabled {timer_name}") assert c.rc == 0 @@ -156,7 +199,7 @@ class TestSystemdState: c2 = host.run('systemctl is-active borg_backup@home-data.timer') assert c1.rc == 0 assert c2.rc == 0 - elif hostname in ('borg-client', 'borg-client-2'): + elif hostname in ('borg-client', 'borg-client-2', 'borg-client-nonroot'): timer_name = 'borg_backup@borg-server.timer' c = host.run(f"systemctl is-active {timer_name}") assert c.rc == 0 |