diff --git a/lib/ansible/modules/apt_repository.py b/lib/ansible/modules/apt_repository.py index c46aefea3ce..0b9371ac77a 100644 --- a/lib/ansible/modules/apt_repository.py +++ b/lib/ansible/modules/apt_repository.py @@ -179,11 +179,13 @@ import sys import tempfile import time +from urllib.parse import urlencode + from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.file import S_IRWU_RG_RO as DEFAULT_SOURCES_PERM from ansible.module_utils.common.respawn import has_respawned, probe_interpreters_for_module, respawn_module from ansible.module_utils.common.text.converters import to_native -from ansible.module_utils.urls import fetch_url +from ansible.module_utils.urls import fetch_file, fetch_url from ansible.module_utils.common.locale import get_best_parsable_locale @@ -472,10 +474,9 @@ class UbuntuSourcesList(SourcesList): self.codename = module.params['codename'] or distro.codename super(UbuntuSourcesList, self).__init__(module) - self.apt_key_bin = self.module.get_bin_path('apt-key', required=False) self.gpg_bin = self.module.get_bin_path('gpg', required=False) - if not self.apt_key_bin and not self.gpg_bin: - self.module.fail_json(msg='Either apt-key or gpg binary is required, but neither could be found') + if not self.gpg_bin: + self.module.fail_json(msg='gpg binary is required, but this could be found') def __deepcopy__(self, memo=None): return UbuntuSourcesList(self.module) @@ -497,25 +498,13 @@ class UbuntuSourcesList(SourcesList): except IndexError: ppa_name = 'ppa' - line = 'deb %s/%s/%s/ubuntu %s main' % (self.PPA_URI, ppa_owner, ppa_name, self.codename) + keypath = self._get_ppa_keyfile("ubuntu-%s-main" % self.codename, ppa_owner, ppa_name) + line = 'deb [signed-by=%s] %s/%s/%s/ubuntu %s main' % (keypath, self.PPA_URI, ppa_owner, ppa_name, self.codename) return line, ppa_owner, ppa_name - def _key_already_exists(self, key_fingerprint): - - if self.apt_key_bin: - locale = get_best_parsable_locale(self.module) - APT_ENV = dict(LANG=locale, LC_ALL=locale, LC_MESSAGES=locale, LC_CTYPE=locale, LANGUAGE=locale) - self.module.run_command_environ_update = APT_ENV - rc, out, err = self.module.run_command([self.apt_key_bin, 'export', key_fingerprint], check_rc=True) - found = bool(not err or 'nothing exported' not in err) - else: - found = self._gpg_key_exists(key_fingerprint) - - return found - - def _gpg_key_exists(self, key_fingerprint): - - found = False + def _existing_gpg_filepath(self, key_fingerprint): + """ Returns the path to an existing GPG key, or "" if one cannot be found """ + found_keyfile = "" keyfiles = ['/etc/apt/trusted.gpg'] # main gpg repo for apt for other_dir in APT_KEY_DIRS: # add other known sources of gpg sigs for apt, skip hidden files @@ -531,10 +520,37 @@ class UbuntuSourcesList(SourcesList): continue if key_fingerprint in out: - found = True + found_keyfile = key_file break - return found + return found_keyfile + + def _retreive_gpg_key(self, keyfile, info): + # TODO: report file that would have been added if not check_mode + if not self.module.check_mode: + # use first available key dir, in order of preference + + + keyserver_url = "https://keyserver.ubuntu.com/pks/lookup?{}".format( + urlencode({"op": "get", "search": "0x" + info['signing_key_fingerprint']}) + ) + armored_keyfile = fetch_file(self.module, keyserver_url) + + command = [self.gpg_bin, '--no-tty', '-o', keyfile, '--dearmor', armored_keyfile] + rc, stdout, stderr = self.module.run_command(command, check_rc=True, encoding=None) + + if rc != 0 or len(stderr) != 0: + self.module.fail_json(msg='Unable to get required signing key', rc=rc, stderr=stderr, command=command) + self.module.log('Added repo key "%s" for apt to file "%s"' % (info['signing_key_fingerprint'], keyfile)) + + def _get_ppa_keyfile(self, source, ppa_owner, ppa_name): + for keydir in APT_KEY_DIRS: + if os.path.exists(keydir): + break + else: + self.module.fail_json("Unable to find any existing apt gpgp repo directories, tried the following: %s" % ', '.join(APT_KEY_DIRS)) + + return '%s/%s-%s-%s.gpg' % (keydir, os.path.basename(source).replace(' ', '-'), ppa_owner, ppa_name) # https://www.linuxuprising.com/2021/01/apt-key-is-deprecated-how-to-add.html def add_source(self, line, comment='', file=None): @@ -548,37 +564,16 @@ class UbuntuSourcesList(SourcesList): info = self._get_ppa_info(ppa_owner, ppa_name) # add gpg sig if needed - if not self._key_already_exists(info['signing_key_fingerprint']): - - # TODO: report file that would have been added if not check_mode - keyfile = '' - if not self.module.check_mode: - if self.apt_key_bin: - command = [self.apt_key_bin, 'adv', '--recv-keys', '--no-tty', '--keyserver', 'hkps://keyserver.ubuntu.com:443', - info['signing_key_fingerprint']] - else: - # use first available key dir, in order of preference - for keydir in APT_KEY_DIRS: - if os.path.exists(keydir): - break - else: - self.module.fail_json("Unable to find any existing apt gpgp repo directories, tried the following: %s" % ', '.join(APT_KEY_DIRS)) - - keyfile = '%s/%s-%s-%s.gpg' % (keydir, os.path.basename(source).replace(' ', '-'), ppa_owner, ppa_name) - command = [self.gpg_bin, '--no-tty', '--keyserver', 'hkps://keyserver.ubuntu.com:443', '--export', info['signing_key_fingerprint']] - - rc, stdout, stderr = self.module.run_command(command, check_rc=True, encoding=None) - if keyfile: - # using gpg we must write keyfile ourselves - if len(stdout) == 0: - self.module.fail_json(msg='Unable to get required signing key', rc=rc, stderr=stderr, command=command) - try: - with open(keyfile, 'wb') as f: - f.write(stdout) - self.module.log('Added repo key "%s" for apt to file "%s"' % (info['signing_key_fingerprint'], keyfile)) - except (OSError, IOError) as e: - self.module.fail_json(msg='Unable to add required signing key for%s ', rc=rc, stderr=stderr, error=to_native(e)) + existing_keyfile = self._existing_gpg_filepath(info["signing_key_fingerprint"]) + if not existing_keyfile: + for keydir in APT_KEY_DIRS: + if os.path.exists(keydir): + break + else: + self.module.fail_json("Unable to find any existing apt gpgp repo directories, tried the following: %s" % ', '.join(APT_KEY_DIRS)) + keyfile = self._get_ppa_keyfile(source, ppa_owner, ppa_name) + self._retreive_gpg_key(keyfile, info) # apt source file file = file or self._suggest_filename('%s_%s' % (line, self.codename)) else: