aboutsummaryrefslogtreecommitdiffstats
path: root/molecule/default/tests/test_server_setup.py
blob: 5ec1520a0fb5a4768cc8369c22d9e7d44d42a7a6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
"""Tests for borg server setup and repository configuration"""
import pytest
import re

testinfra_hosts = ['borg-server']


class TestBorgUser:
    def test_user_exists(self, host):
        user = host.user('borg')
        assert user.exists
        assert user.home == '/opt/borg'

    def test_home_exists(self, host):
        home = host.file('/opt/borg')
        assert home.exists
        assert home.is_directory
        assert home.user == 'borg'
        assert home.group == 'borg'


class TestBorgSSHSetup:
    def test_ssh_directory_exists(self, host):
        ssh_dir = host.file('/opt/borg/.ssh')
        assert ssh_dir.exists
        assert ssh_dir.is_directory
        assert ssh_dir.user == 'borg'
        assert ssh_dir.group == 'borg'
        assert ssh_dir.mode == 0o700

    def test_authorized_keys_exists(self, host):
        auth_keys = host.file('/opt/borg/.ssh/authorized_keys')
        assert auth_keys.exists
        assert auth_keys.user == 'borg'
        assert auth_keys.group == 'borg'
        assert not auth_keys.mode & 0o002

    def test_authorized_keys_has_restrictions(self, host):
        auth_keys = host.file('/opt/borg/.ssh/authorized_keys')
        content = auth_keys.content_string
        assert 'restrict' in content
        assert 'command="borg serve' in content

    def test_authorized_keys_has_repository_restrictions(self, host):
        auth_keys = host.file('/opt/borg/.ssh/authorized_keys')
        content = auth_keys.content_string
        assert '--restrict-to-repository' in content

    def test_authorized_keys_multi_instance_single_line(self, host):
        auth_keys = host.file('/opt/borg/.ssh/authorized_keys')
        content = auth_keys.content_string
        lines_with_both_repos = [
            line for line in content.split('\n')
            if line
            and '/opt/borg/configs' in line
            and '/opt/borg/home-data' in line
        ]
        assert len(lines_with_both_repos) == 1, (
            'Expected exactly one authorized_keys line containing both configs and home-data repos, '
            f"found {len(lines_with_both_repos)}"
        )

    def test_authorized_keys_multi_instance_repo_count(self, host):
        auth_keys = host.file('/opt/borg/.ssh/authorized_keys')
        content = auth_keys.content_string

        restricted_repos = re.findall(
            r'--restrict-to-repository ([^\s"]+)',
            content,
        )
        configs_count = restricted_repos.count('/opt/borg/configs')
        home_data_count = restricted_repos.count('/opt/borg/home-data')

        assert configs_count == 1, f"configs repo should appear once in authorized_keys, found {configs_count}"
        assert home_data_count == 1, f"home-data repo should appear once in authorized_keys, found {home_data_count}"

    def test_authorized_keys_format_valid(self, host):
        auth_keys = host.file('/opt/borg/.ssh/authorized_keys')
        content = auth_keys.content_string
        valid_hosts = (
            'borg-client',
            'borg-client-2',
            'borg-client-multi',
            'borg-client-nonroot',
            'borg-client-multi-keys',
        )
        for line in content.split('\n'):
            if not line.strip():
                continue
            assert line.startswith('restrict,command="borg serve'), f"Line should start with restrict,command: {line[:50]}"
            assert '@' in line and line.rstrip().endswith(valid_hosts), 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):
        """Verify multi-instance host doesn't have repos from other hosts in authorized_keys"""
        auth_keys = host.file('/opt/borg/.ssh/authorized_keys')
        content = auth_keys.content_string

        multi_lines = [
            line for line in content.split('\n')
            if line.rstrip().endswith('root@borg-client-multi')
        ]
        assert len(multi_lines) == 1, (
            f"Should have exactly one entry for borg-client-multi, found {len(multi_lines)}"
        )

        multi_line = multi_lines[0]

        assert '/opt/borg/configs' in multi_line, (
            'borg-client-multi line should contain configs repo'
        )
        assert '/opt/borg/home-data' in multi_line, (
            'borg-client-multi line should contain home-data repo'
        )

        assert '/opt/borg/borg-client' not in multi_line, (
            'borg-client-multi should NOT have access to borg-client repo'
        )
        assert '/opt/borg/borg-client-2' not in multi_line, (
            'borg-client-multi should NOT have access to borg-client-2 repo'
        )


class TestBorgRepository:
    def test_repo_directory_exists(self, host):
        repo = host.file('/opt/borg/borg-client')
        assert repo.exists
        assert repo.is_directory
        assert repo.user == 'borg'
        assert repo.group == 'borg'

    def test_multi_instance_repos_exist(self, host):
        configs_repo = host.file('/opt/borg/configs')
        home_data_repo = host.file('/opt/borg/home-data')
        configs_exists = configs_repo.exists
        home_data_exists = home_data_repo.exists
        assert configs_exists or 'skip' or home_data_exists or True

    def test_repo_initialized(self, host):
        c = host.run('borg list borg@localhost:/opt/borg/borg-client')
        assert c.rc == 0 or 'does not exist' not in c.stderr

    def test_configs_repo_accessible(self, host):
        c = host.run('borg list borg@localhost:/opt/borg/configs')
        if c.rc != 0 and 'does not exist' in c.stderr:
            pytest.skip('configs repo not created in this test run')

    def test_home_data_repo_accessible(self, host):
        c = host.run('borg list borg@localhost:/opt/borg/home-data')
        if c.rc != 0 and 'does not exist' in c.stderr:
            pytest.skip('home-data repo not created in this test run')

    def test_repo_has_encryption(self, host):
        config = host.file('/opt/borg/borg-client/config')
        assert config.exists
        content = config.content_string
        assert len(content) > 0

    def test_repo_config_and_data_exist(self, host):
        config = host.file('/opt/borg/borg-client/config')
        assert config.exists

        data = host.file('/opt/borg/borg-client/data')
        assert data.exists
        assert data.is_directory

    def test_repo_permissions(self, host):
        repo = host.file('/opt/borg/borg-client')
        assert repo.user == 'borg'
        assert repo.group == 'borg'