summaryrefslogtreecommitdiff
path: root/tests/integration_tests/modules/test_apt.py
blob: 2c388047e9ba656ee058e30272e1fe8c3a25ff51 (plain)
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
"""Series of integration tests covering apt functionality."""
import re

import pytest

from cloudinit.config import cc_apt_configure
from cloudinit import gpg
from tests.integration_tests.clouds import ImageSpecification
from tests.integration_tests.instances import IntegrationInstance


USER_DATA = """\
#cloud-config
apt:
  conf: |
    APT {
        Get {
            Assume-Yes "true";
            Fix-Broken "true";
        }
    }
  proxy: "http://proxy.internal:3128"
  http_proxy: "http://squid.internal:3128"
  ftp_proxy: "ftp://squid.internal:3128"
  https_proxy: "https://squid.internal:3128"
  primary:
    - arches: [default]
      uri: http://badarchive.ubuntu.com/ubuntu
  security:
    - arches: [default]
      uri: http://badsecurity.ubuntu.com/ubuntu
  sources_list: |
    deb $MIRROR $RELEASE main restricted
    deb-src $MIRROR $RELEASE main restricted
    deb $PRIMARY $RELEASE universe restricted
    deb-src $PRIMARY $RELEASE universe restricted
    deb $SECURITY $RELEASE-security multiverse
    deb-src $SECURITY $RELEASE-security multiverse
  sources:
    test_keyserver:
        keyid: 72600DB15B8E4C8B1964B868038ACC97C660A937
        keyserver: keyserver.ubuntu.com
        source: "deb http://ppa.launchpad.net/cloud-init-raharper/curtin-dev/ubuntu $RELEASE main"
    test_ppa:
      keyid: 441614D8
      keyserver: keyserver.ubuntu.com
      source: "ppa:simplestreams-dev/trunk"
    test_signed_by:
      keyid: A2EB2DEC0BD7519B7B38BE38376A290EC8068B11
      keyserver: keyserver.ubuntu.com
      source: "deb [signed-by=$KEY_FILE] http://ppa.launchpad.net/juju/stable/ubuntu $RELEASE main"
    test_bad_key:
      key: ""
      source: "deb $MIRROR $RELEASE main"
    test_key:
      source: "deb http://ppa.launchpad.net/cloud-init-dev/test-archive/ubuntu $RELEASE main"
      key: |
        -----BEGIN PGP PUBLIC KEY BLOCK-----
        Version: SKS 1.1.6
        Comment: Hostname: keyserver.ubuntu.com

        mQINBFbZRUIBEAC+A0PIKYBP9kLC4hQtRrffRS11uLo8/BdtmOdrlW0hpPHzCfKnjR3tvSEI
        lqPHG1QrrjAXKZDnZMRz+h/px7lUztvytGzHPSJd5ARUzAyjyRezUhoJ3VSCxrPqx62avuWf
        RfoJaIeHfDehL5/dTVkyiWxfVZ369ZX6JN2AgLsQTeybTQ75+2z0xPrrhnGmgh6g0qTYcAaq
        M5ONOGiqeSBX/Smjh6ALy5XkhUiFGLsI7Yluf6XSICY/x7gd6RAfgSIQrUTNMoS1sqhT4aot
        +xvOfQy8ySkfAK4NddXql6E/+ZqTmBY/Lr0YklFBy8jGT+UysfiIznPMIwbmgq5Li7BtDDtX
        b8Uyi4edPpjtextezfXYn4NVIpPL5dPZS/FXh4HpzyH0pYCfrH4QDGA7i52AGmhpiOFjJMo6
        N33sdjZHOH/2Vyp+QZaQnsdUAi1N4M6c33tQbpIScn1SY+El8z5JDA4PBzkw8HpLCi1gGoa6
        V4kfbWqXXbGAJFkLkP/vc4+pY9axOlmCkJg7xCPwhI75y1cONgovhz+BEXOzolh5KZuGbGbj
        xe0wva5DLBeIg7EQFf+99pOS7Syby3Xpm6ZbswEFV0cllK4jf/QMjtfInxobuMoI0GV0bE5l
        WlRtPCK5FnbHwxi0wPNzB/5fwzJ77r6HgPrR0OkT0lWmbUyoOQARAQABtC1MYXVuY2hwYWQg
        UFBBIGZvciBjbG91ZCBpbml0IGRldmVsb3BtZW50IHRlYW2JAjgEEwECACIFAlbZRUICGwMG
        CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEAg9Bvvk0wTfHfcP/REK5N2s1JYc69qEa9ZN
        o6oi+A7l6AYw+ZY88O5TJe7F9otv5VXCIKSUT0Vsepjgf0mtXAgf/sb2lsJn/jp7tzgov3YH
        vSrkTkRydz8xcA87gwQKePuvTLxQpftF4flrBxgSueIn5O/tPrBOxLz7EVYBc78SKg9aj9L2
        yUp+YuNevlwfZCTYeBb9r3FHaab2HcgkwqYch66+nKYfwiLuQ9NzXXm0Wn0JcEQ6pWvJscbj
        C9BdawWovfvMK5/YLfI6Btm7F4mIpQBdhSOUp/YXKmdvHpmwxMCN2QhqYK49SM7qE9aUDbJL
        arppSEBtlCLWhRBZYLTUna+BkuQ1bHz4St++XTR49Qd7vDERALpApDjB2dxPfMiBzCMwQQyq
        uy13exU8o2ETLg+dZSLfDTzrBNsBFmXlw8WW17nTISYdKeGKL+QdlUjpzdwUMMzHhAO8SmMH
        zjeSlDSRMXBJFAFSbCl7EwmMKa3yVX0zInT91fNllZ3iatAmtVdqVH/BFQfTIMH2ET7A8WzJ
        ZzVSuMRhqoKdr5AMcHuJGPUoVkVJHQA+NNvEiXSysF3faL7jmKapmUwrhpYYX2H8pf+VMu2e
        cLflKTI28dl+ZQ4Pl/aVsxrti/pzhdYy05Sn5ddtySyIkvo8L1cU5MWpbvSlFPkTstBUDLBf
        pb0uBy+g0oxJQg15
        =uy53
        -----END PGP PUBLIC KEY BLOCK-----
apt_pipelining: os
"""  # noqa: E501

EXPECTED_REGEXES = [
    r"deb http://badarchive.ubuntu.com/ubuntu [a-z]+ main restricted",
    r"deb-src http://badarchive.ubuntu.com/ubuntu [a-z]+ main restricted",
    r"deb http://badarchive.ubuntu.com/ubuntu [a-z]+ universe restricted",
    r"deb-src http://badarchive.ubuntu.com/ubuntu [a-z]+ universe restricted",
    r"deb http://badsecurity.ubuntu.com/ubuntu [a-z]+-security multiverse",
    r"deb-src http://badsecurity.ubuntu.com/ubuntu [a-z]+-security multiverse",
]

TEST_KEYSERVER_KEY = "7260 0DB1 5B8E 4C8B 1964  B868 038A CC97 C660 A937"

TEST_PPA_KEY = "3552 C902 B4DD F7BD 3842  1821 015D 28D7 4416 14D8"

TEST_KEY = "1FF0 D853 5EF7 E719 E5C8  1B9C 083D 06FB E4D3 04DF"
TEST_SIGNED_BY_KEY = "A2EB 2DEC 0BD7 519B 7B38  BE38 376A 290E C806 8B11"


@pytest.mark.ci
@pytest.mark.ubuntu
@pytest.mark.user_data(USER_DATA)
class TestApt:
    def get_keys(self, class_client: IntegrationInstance):
        """Return all keys in /etc/apt/trusted.gpg.d/ and /etc/apt/trusted.gpg
        in human readable format. Mimics the output of apt-key finger
        """
        list_cmd = ' '.join(gpg.GPG_LIST) + ' '
        keys = class_client.execute(list_cmd + cc_apt_configure.APT_LOCAL_KEYS)
        print(keys)
        files = class_client.execute(
            'ls ' + cc_apt_configure.APT_TRUSTED_GPG_DIR)
        for file in files.split():
            path = cc_apt_configure.APT_TRUSTED_GPG_DIR + file
            keys += class_client.execute(list_cmd + path) or ''
        return keys

    def test_sources_list(self, class_client: IntegrationInstance):
        """Integration test for the apt module's `sources_list` functionality.

        This test specifies a ``sources_list`` and then checks that (a) the
        expected number of sources.list entries is present, and (b) that each
        expected line appears in the file.

        (This is ported from
        `tests/cloud_tests/testcases/modules/apt_configure_sources_list.yaml`.)
        """
        sources_list = class_client.read_from_file('/etc/apt/sources.list')
        assert 6 == len(sources_list.rstrip().split('\n'))

        for expected_re in EXPECTED_REGEXES:
            assert re.search(expected_re, sources_list) is not None

    def test_apt_conf(self, class_client: IntegrationInstance):
        """Test the apt conf functionality.

        Ported from tests/cloud_tests/testcases/modules/apt_configure_conf.py
        """
        apt_config = class_client.read_from_file(
            '/etc/apt/apt.conf.d/94cloud-init-config'
        )
        assert 'Assume-Yes "true";' in apt_config
        assert 'Fix-Broken "true";' in apt_config

    def test_apt_proxy(self, class_client: IntegrationInstance):
        """Test the apt proxy functionality.

        Ported from tests/cloud_tests/testcases/modules/apt_configure_proxy.py
        """
        out = class_client.read_from_file(
            '/etc/apt/apt.conf.d/90cloud-init-aptproxy')
        assert 'Acquire::http::Proxy "http://proxy.internal:3128";' in out
        assert 'Acquire::http::Proxy "http://squid.internal:3128";' in out
        assert 'Acquire::ftp::Proxy "ftp://squid.internal:3128";' in out
        assert 'Acquire::https::Proxy "https://squid.internal:3128";' in out

    def test_ppa_source(self, class_client: IntegrationInstance):
        """Test the apt ppa functionality.

        Ported from
        tests/cloud_tests/testcases/modules/apt_configure_sources_ppa.py
        """
        release = ImageSpecification.from_os_image().release
        ppa_path_contents = class_client.read_from_file(
            '/etc/apt/sources.list.d/'
            'simplestreams-dev-ubuntu-trunk-{}.list'.format(release)
        )

        assert (
            'http://ppa.launchpad.net/simplestreams-dev/trunk/ubuntu'
        ) in ppa_path_contents

        assert TEST_PPA_KEY in self.get_keys(class_client)

    def test_signed_by(self, class_client: IntegrationInstance):
        """Test the apt signed-by functionality.
        """
        release = ImageSpecification.from_os_image().release
        source = (
            "deb [signed-by=/etc/apt/cloud-init.gpg.d/test_signed_by.gpg] "
            "http://ppa.launchpad.net/juju/stable/ubuntu"
            " {} main".format(release))
        print(class_client.execute('cat /var/log/cloud-init.log'))
        path_contents = class_client.read_from_file(
            '/etc/apt/sources.list.d/test_signed_by.list')
        assert path_contents == source

        key = class_client.execute(
            'gpg --no-default-keyring --with-fingerprint --list-keys '
            '--keyring /etc/apt/cloud-init.gpg.d/test_signed_by.gpg')

        assert TEST_SIGNED_BY_KEY in key

    def test_bad_key(self, class_client: IntegrationInstance):
        """Test the apt signed-by functionality.
        """
        with pytest.raises(OSError):
            class_client.read_from_file(
                '/etc/apt/trusted.list.d/test_bad_key.gpg')

    def test_key(self, class_client: IntegrationInstance):
        """Test the apt key functionality.

        Ported from
        tests/cloud_tests/testcases/modules/apt_configure_sources_key.py
        """
        test_archive_contents = class_client.read_from_file(
            '/etc/apt/sources.list.d/test_key.list'
        )

        assert (
            'http://ppa.launchpad.net/cloud-init-dev/test-archive/ubuntu'
        ) in test_archive_contents
        assert TEST_KEY in self.get_keys(class_client)

    def test_keyserver(self, class_client: IntegrationInstance):
        """Test the apt keyserver functionality.

        Ported from
        tests/cloud_tests/testcases/modules/apt_configure_sources_keyserver.py
        """
        test_keyserver_contents = class_client.read_from_file(
            '/etc/apt/sources.list.d/test_keyserver.list'
        )

        assert (
            'http://ppa.launchpad.net/cloud-init-raharper/curtin-dev/ubuntu'
        ) in test_keyserver_contents

        assert TEST_KEYSERVER_KEY in self.get_keys(class_client)

    def test_os_pipelining(self, class_client: IntegrationInstance):
        """Test 'os' settings does not write apt config file.

        Ported from tests/cloud_tests/testcases/modules/apt_pipelining_os.py
        """
        conf_exists = class_client.execute(
            'test -f /etc/apt/apt.conf.d/90cloud-init-pipelining'
        ).ok
        assert conf_exists is False


_DEFAULT_DATA = """\
#cloud-config
apt:
  primary:
    - arches:
      - default
      {uri}
  security:
    - arches:
      - default
"""
DEFAULT_DATA = _DEFAULT_DATA.format(uri='')


@pytest.mark.ubuntu
@pytest.mark.user_data(DEFAULT_DATA)
class TestDefaults:
    @pytest.mark.openstack
    def test_primary_on_openstack(self, class_client: IntegrationInstance):
        """Test apt default primary source on openstack.

        When no uri is provided.
        """
        zone = class_client.execute('cloud-init query v1.availability_zone')
        sources_list = class_client.read_from_file('/etc/apt/sources.list')
        assert '{}.clouds.archive.ubuntu.com'.format(zone) in sources_list

    def test_security(self, class_client: IntegrationInstance):
        """Test apt default security sources.

        Ported from
        tests/cloud_tests/testcases/modules/apt_configure_security.py
        """
        sources_list = class_client.read_from_file('/etc/apt/sources.list')

        # 3 lines from main, universe, and multiverse
        assert 3 == sources_list.count('deb http://security.ubuntu.com/ubuntu')
        assert 3 == sources_list.count(
            '# deb-src http://security.ubuntu.com/ubuntu'
        )


DEFAULT_DATA_WITH_URI = _DEFAULT_DATA.format(
    uri='uri: "http://something.random.invalid/ubuntu"'
)


@pytest.mark.user_data(DEFAULT_DATA_WITH_URI)
def test_default_primary_with_uri(client: IntegrationInstance):
    """Test apt default primary sources.

    Ported from
    tests/cloud_tests/testcases/modules/apt_configure_primary.py
    """
    sources_list = client.read_from_file('/etc/apt/sources.list')
    assert 'archive.ubuntu.com' not in sources_list

    assert 'something.random.invalid' in sources_list


DISABLED_DATA = """\
#cloud-config
apt:
  disable_suites:
  - $RELEASE
  - $RELEASE-updates
  - $RELEASE-backports
  - $RELEASE-security
apt_pipelining: false
"""


@pytest.mark.ubuntu
@pytest.mark.user_data(DISABLED_DATA)
class TestDisabled:
    def test_disable_suites(self, class_client: IntegrationInstance):
        """Test disabling of apt suites.

        Ported from
        tests/cloud_tests/testcases/modules/apt_configure_disable_suites.py
        """
        sources_list = class_client.execute(
            "cat /etc/apt/sources.list | grep -v '^#'"
        ).strip()
        assert '' == sources_list

    def test_disable_apt_pipelining(self, class_client: IntegrationInstance):
        """Test disabling of apt pipelining.

        Ported from
        tests/cloud_tests/testcases/modules/apt_pipelining_disable.py
        """
        conf = class_client.read_from_file(
            '/etc/apt/apt.conf.d/90cloud-init-pipelining'
        )
        assert 'Acquire::http::Pipeline-Depth "0";' in conf