← Back to team overview

curtin-dev team mailing list archive

[Merge] ~dbungert/curtin:lp-1892494-apt-key-v2 into curtin:master

 

Dan Bungert has proposed merging ~dbungert/curtin:lp-1892494-apt-key-v2 into curtin:master.

Commit message:
commands/apt: stop using apt-key

re-apply e099e32 and update for the modified test codebase.
Drops using apt-key in favor of placing the file direct on disk.
Includes a fix for placing the key in the correct directory.


Requested reviews:
  curtin developers (curtin-dev)

For more details, see:
https://code.launchpad.net/~dbungert/curtin/+git/curtin/+merge/432335

Verified with unit tests and with a test build in Subiquity.

```
autoinstall:
    apt:
        sources:
            mykey:
                keyid: '491F9C0CCB70BB00D245C6FD433A228BEB109E69'
                keyserver: 'keyserver.ubuntu.com'
```

This snippet results in the key being placed at /etc/apt/trusted.gpg.d/mykey.asc.
-- 
Your team curtin developers is requested to review the proposed merge of ~dbungert/curtin:lp-1892494-apt-key-v2 into curtin:master.
diff --git a/curtin/commands/apt_config.py b/curtin/commands/apt_config.py
index 4f62a86..d625527 100644
--- a/curtin/commands/apt_config.py
+++ b/curtin/commands/apt_config.py
@@ -391,20 +391,29 @@ def apply_preserve_sources_list(target):
         raise
 
 
-def add_apt_key_raw(key, target=None):
+def add_apt_key_raw(filename, key, target=None):
     """
     actual adding of a key as defined in key argument
     to the system
     """
-    LOG.debug("Adding key:\n'%s'", key)
-    try:
-        util.subp(['apt-key', 'add', '-'], data=key.encode(), target=target)
-    except util.ProcessExecutionError:
-        LOG.exception("failed to add apt GPG Key to apt keyring")
-        raise
+    if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in str(key):
+        keyfile_ext = 'asc'
+        omode = 'w'
+        key = key.rstrip()
+    else:
+        keyfile_ext = 'gpg'
+        omode = 'wb'
+
+    # Store the key file in /etc/apt/trusted.gpg.d/
+    # The (sources.list) filename may be a full path
+    basename = os.path.basename(filename)
+    keyfile = f'/etc/apt/trusted.gpg.d/{basename}.{keyfile_ext}'
+    target_keyfile = paths.target_path(target, keyfile)
+    util.write_file(target_keyfile, key, mode=0o644, omode=omode)
+    LOG.debug("Adding key to '%s':\n'%s'", target_keyfile, key)
 
 
-def add_apt_key(ent, target=None):
+def add_apt_key(filename, ent, target=None):
     """
     Add key to the system as defined in ent (if any).
     Supports raw keys or keyid's
@@ -419,7 +428,7 @@ def add_apt_key(ent, target=None):
                                     retries=(1, 2, 5, 10))
 
     if 'key' in ent:
-        add_apt_key_raw(ent['key'], target)
+        add_apt_key_raw(filename, ent['key'], target)
 
 
 def add_apt_sources(srcdict, target=None, template_params=None,
@@ -443,7 +452,7 @@ def add_apt_sources(srcdict, target=None, template_params=None,
         if 'filename' not in ent:
             ent['filename'] = filename
 
-        add_apt_key(ent, target)
+        add_apt_key(ent['filename'], ent, target)
 
         if 'source' not in ent:
             continue
diff --git a/tests/data/test.gpg b/tests/data/test.gpg
new file mode 100644
index 0000000..f7ba778
Binary files /dev/null and b/tests/data/test.gpg differ
diff --git a/tests/unittests/test_apt_source.py b/tests/unittests/test_apt_source.py
index 18de832..9fb3fc5 100644
--- a/tests/unittests/test_apt_source.py
+++ b/tests/unittests/test_apt_source.py
@@ -35,15 +35,30 @@ S0ORP6HXET3+jC8BMG4tBWCTK/XEZw==
 =ACB2
 -----END PGP PUBLIC KEY BLOCK-----"""
 
+EXPECTEDKEY_NOVER = u"""-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mI0ESuZLUgEEAKkqq3idtFP7g9hzOu1a8+v8ImawQN4TrvlygfScMU1TIS1eC7UQ
+NUA8Qqgr9iUaGnejb0VciqftLrU9D6WYHSKz+EITefgdyJ6SoQxjoJdsCpJ7o9Jy
+8PQnpRttiFm4qHu6BVnKnBNxw/z3ST9YMqW5kbMQpfxbGe+obRox59NpABEBAAG0
+HUxhdW5jaHBhZCBQUEEgZm9yIFNjb3R0IE1vc2VyiLYEEwECACAFAkrmS1ICGwMG
+CwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRAGILvPA2g/d3aEA/9tVjc10HOZwV29
+OatVuTeERjjrIbxflO586GLA8cp0C9RQCwgod/R+cKYdQcHjbqVcP0HqxveLg0RZ
+FJpWLmWKamwkABErwQLGlM/Hwhjfade8VvEQutH5/0JgKHmzRsoqfR+LMO6OS+Sm
+S0ORP6HXET3+jC8BMG4tBWCTK/XEZw==
+=ACB2
+-----END PGP PUBLIC KEY BLOCK-----"""
+
+EXPECTED_BINKEY = util.load_file("tests/data/test.gpg", decode=False)
+
 ADD_APT_REPO_MATCH = r"^[\w-]+:\w"
 
 
-def load_tfile(filename):
+def load_tfile(filename, decode=True):
     """ load_tfile
     load file and return content after decoding
     """
     try:
-        content = util.load_file(filename, decode=True)
+        content = util.load_file(filename, decode=decode)
     except Exception as error:
         print('failed to load file content for test: %s' % error)
         raise
@@ -86,8 +101,6 @@ class TestAptSourceConfig(CiTestCase):
         self.aptlistfile2 = "single-deb2.list"
         self.aptlistfile3 = "single-deb3.list"
         self.matcher = re.compile(ADD_APT_REPO_MATCH).search
-        self.add_patch('curtin.util.subp', 'm_subp')
-        self.m_subp.return_value = ('s390x', '')
 
     def _sources_filepath(self, filename):
         # force the paths to be relative to the target
@@ -95,6 +108,11 @@ class TestAptSourceConfig(CiTestCase):
         against_target = os.path.join('etc/apt/sources.list.d', filename)
         return self.target + '/' + against_target
 
+    def _key_filepath(self, filename):
+        # always to trusted.gpg.d
+        basename = os.path.basename(filename)
+        return os.path.join(self.target, 'etc/apt/trusted.gpg.d', basename)
+
     @staticmethod
     def _add_apt_sources(*args, **kwargs):
         with mock.patch.object(distro, 'apt_update'):
@@ -229,23 +247,46 @@ class TestAptSourceConfig(CiTestCase):
                self.aptlistfile3: {'source': 'deb $MIRROR $RELEASE universe'}}
         self._apt_src_replace_tri(cfg)
 
-    def _apt_src_keyid(self, filename, cfg, keynum):
+    def _apt_src_keyid_txt(self, filename, cfg):
         """ _apt_src_keyid
         Test specification of a source + keyid
         """
         params = self._get_default_params()
 
-        with mock.patch("curtin.util.subp",
-                        return_value=('fakekey 1234', '')) as mockobj:
+        with mock.patch.object(gpg, 'getkeybyid',
+                               return_value=EXPECTEDKEY_NOVER):
             self._add_apt_sources(cfg, self.target, template_params=params,
                                   aa_repo_match=self.matcher)
 
-        # check if it added the right ammount of keys
-        calls = []
-        for _ in range(keynum):
-            calls.append(call(['apt-key', 'add', '-'], data=b'fakekey 1234',
-                              target=self.target))
-        mockobj.assert_has_calls(calls, any_order=True)
+        for ent in cfg:
+            key_filename = cfg[ent].get('filename', ent) + '.asc'
+            contents = load_tfile(self._key_filepath(key_filename))
+            self.assertMultiLineEqual(EXPECTEDKEY_NOVER, contents)
+
+    def _apt_src_keyid_bin(self, filename, cfg):
+        """ _apt_src_keyid
+        Test specification of a source + keyid
+        """
+        params = self._get_default_params()
+
+        with mock.patch.object(gpg, 'getkeybyid',
+                               return_value=EXPECTED_BINKEY):
+            self._add_apt_sources(cfg, self.target, template_params=params,
+                                  aa_repo_match=self.matcher)
+
+        for ent in cfg:
+            key_filename = cfg[ent].get('filename', ent) + '.gpg'
+            contents = load_tfile(self._key_filepath(key_filename), False)
+            self.assertEqual(EXPECTED_BINKEY, contents)
+
+    def _apt_src_keyid(self, filename, cfg, key_type="txt"):
+        """ _apt_src_keyid
+        Test specification of a source + keyid
+        """
+        if key_type == "txt":
+            self._apt_src_keyid_txt(filename, cfg)
+        else:
+            self._apt_src_keyid_bin(filename, cfg)
 
         contents = load_tfile(self._sources_filepath(filename))
         self.assertTrue(re.search(r"%s %s %s %s\n" %
@@ -263,7 +304,17 @@ class TestAptSourceConfig(CiTestCase):
                                              'smoser/cloud-init-test/ubuntu'
                                              ' xenial main'),
                                   'keyid': "03683F77"}}
-        self._apt_src_keyid(self.aptlistfile, cfg, 1)
+        self._apt_src_keyid(self.aptlistfile, cfg)
+
+    @mock.patch(ChrootableTargetStr, new=PseudoChrootableTarget)
+    def test_apt_src_keyid_bin(self):
+        """test_apt_src_keyid - Test source + keyid with filename being set"""
+        cfg = {self.aptlistfile: {'source': ('deb '
+                                             'http://ppa.launchpad.net/'
+                                             'smoser/cloud-init-test/ubuntu'
+                                             ' xenial main'),
+                                  'keyid': "03683F77"}}
+        self._apt_src_keyid(self.aptlistfile, cfg, key_type='bin')
 
     @mock.patch(ChrootableTargetStr, new=PseudoChrootableTarget)
     def test_apt_src_keyid_tri(self):
@@ -285,7 +336,7 @@ class TestAptSourceConfig(CiTestCase):
                                               ' xenial multiverse'),
                                    'keyid': "03683F77"}}
 
-        self._apt_src_keyid(self.aptlistfile, cfg, 3)
+        self._apt_src_keyid(self.aptlistfile, cfg)
         contents = load_tfile(self._sources_filepath(self.aptlistfile2))
         self.assertTrue(re.search(r"%s %s %s %s\n" %
                                   ("deb",
@@ -305,18 +356,20 @@ class TestAptSourceConfig(CiTestCase):
     def test_apt_src_key(self):
         """test_apt_src_key - Test source + key"""
         params = self._get_default_params()
+        fake_key = u"""-----BEGIN PGP PUBLIC KEY BLOCK-----
+                       fakekey 4321"""
+
         cfg = {self.aptlistfile: {'source': ('deb '
                                              'http://ppa.launchpad.net/'
                                              'smoser/cloud-init-test/ubuntu'
                                              ' xenial main'),
-                                  'key': "fakekey 4321"}}
+                                  'key': fake_key}}
 
-        with mock.patch.object(util, 'subp') as mockobj:
-            self._add_apt_sources(cfg, self.target, template_params=params,
-                                  aa_repo_match=self.matcher)
+        self._add_apt_sources(cfg, self.target, template_params=params,
+                              aa_repo_match=self.matcher)
 
-        mockobj.assert_any_call(['apt-key', 'add', '-'], data=b'fakekey 4321',
-                                target=self.target)
+        contents = load_tfile(self._key_filepath(self.aptlistfile + '.asc'))
+        self.assertMultiLineEqual(fake_key, contents)
 
         contents = load_tfile(self._sources_filepath(self.aptlistfile))
         self.assertTrue(re.search(r"%s %s %s %s\n" %
@@ -330,14 +383,15 @@ class TestAptSourceConfig(CiTestCase):
     def test_apt_src_keyonly(self):
         """test_apt_src_keyonly - Test key without source"""
         params = self._get_default_params()
-        cfg = {self.aptlistfile: {'key': "fakekey 4242"}}
+        fake_key = u"""-----BEGIN PGP PUBLIC KEY BLOCK-----
+                       fakekey 4321"""
+        cfg = {self.aptlistfile: {'key': fake_key}}
 
-        with mock.patch.object(util, 'subp') as mockobj:
-            self._add_apt_sources(cfg, self.target, template_params=params,
-                                  aa_repo_match=self.matcher)
+        self._add_apt_sources(cfg, self.target, template_params=params,
+                              aa_repo_match=self.matcher)
 
-        mockobj.assert_any_call(['apt-key', 'add', '-'], data=b'fakekey 4242',
-                                target=self.target)
+        contents = load_tfile(self._key_filepath(self.aptlistfile + '.asc'))
+        self.assertMultiLineEqual(fake_key, contents)
 
         # filename should be ignored on key only
         self.assertFalse(os.path.isfile(
@@ -349,13 +403,13 @@ class TestAptSourceConfig(CiTestCase):
         params = self._get_default_params()
         cfg = {self.aptlistfile: {'keyid': "03683F77"}}
 
-        with mock.patch.object(util, 'subp',
-                               return_value=('fakekey 1212', '')) as mockobj:
+        with mock.patch.object(gpg, 'getkeybyid',
+                               return_value=EXPECTEDKEY_NOVER):
             self._add_apt_sources(cfg, self.target, template_params=params,
                                   aa_repo_match=self.matcher)
 
-        mockobj.assert_any_call(['apt-key', 'add', '-'], data=b'fakekey 1212',
-                                target=self.target)
+        contents = load_tfile(self._key_filepath(self.aptlistfile + '.asc'))
+        self.assertMultiLineEqual(EXPECTEDKEY_NOVER, contents)
 
         # filename should be ignored on key only
         self.assertFalse(os.path.isfile(
@@ -380,7 +434,7 @@ class TestAptSourceConfig(CiTestCase):
                                       keycfg.get('keyserver',
                                                  'keyserver.ubuntu.com'),
                                       retries=(1, 2, 5, 10))
-        mockkey.assert_called_with(expectedkey, self.target)
+        mockkey.assert_called_with(self.aptlistfile, expectedkey, self.target)
 
         # filename should be ignored on key only
         self.assertFalse(os.path.isfile(
@@ -425,7 +479,7 @@ class TestAptSourceConfig(CiTestCase):
 
         mockgetkey.assert_called_with('03683F77', 'test.random.com',
                                       retries=(1, 2, 5, 10))
-        mockadd.assert_called_with('fakekey', self.target)
+        mockadd.assert_called_with(self.aptlistfile, 'fakekey', self.target)
 
         # filename should be ignored on key only
         self.assertFalse(os.path.isfile(

Follow ups