From 580b0138371c406ab3ce1706f9cf7ab4dd83049d Mon Sep 17 00:00:00 2001 From: Tim Rupp Date: Mon, 1 Jul 2019 06:57:45 -0700 Subject: [PATCH] rpm_key - add fingerprint parameter (#58373) This parameter can be used to verify keys before they are imported by providing the long form fingerprint of the key. --- lib/ansible/modules/packaging/os/rpm_key.py | 54 ++++++++++++++++--- .../targets/rpm_key/tasks/rpm_key.yaml | 38 +++++++++++++ 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/lib/ansible/modules/packaging/os/rpm_key.py b/lib/ansible/modules/packaging/os/rpm_key.py index 14acdde3c41..f64992e9446 100644 --- a/lib/ansible/modules/packaging/os/rpm_key.py +++ b/lib/ansible/modules/packaging/os/rpm_key.py @@ -17,27 +17,34 @@ DOCUMENTATION = ''' --- module: rpm_key author: -- Hector Acosta (@hacosta) + - Hector Acosta (@hacosta) short_description: Adds or removes a gpg key from the rpm db description: - - Adds or removes (rpm --import) a gpg key to your rpm database. + - Adds or removes (rpm --import) a gpg key to your rpm database. version_added: "1.3" options: key: description: - - Key that will be modified. Can be a url, a file, or a keyid if the key already exists in the database. + - Key that will be modified. Can be a url, a file on the managed node, or a keyid if the key + already exists in the database. required: true state: description: - - If the key will be imported or removed from the rpm db. + - If the key will be imported or removed from the rpm db. default: present choices: [ absent, present ] validate_certs: description: - - If C(no) and the C(key) is a url starting with https, SSL certificates will not be validated. This should only be used - on personally controlled sites using self-signed certificates. + - If C(no) and the C(key) is a url starting with https, SSL certificates will not be validated. + - This should only be used on personally controlled sites using self-signed certificates. type: bool default: 'yes' + fingerprint: + description: + - The long-form fingerprint of the key being imported. + - This will be used to verify the specified key. + type: str + version_added: 2.9 ''' EXAMPLES = ''' @@ -55,6 +62,11 @@ EXAMPLES = ''' - rpm_key: state: absent key: DEADB33F + +# Verify the key, using a fingerprint, before import +- rpm_key: + key: /path/to/RPM-GPG-KEY.dag.txt + fingerprint: EBC6 E12C 62B1 C734 026B 2122 A20E 5214 6B8D 79E6 ''' import re import os.path @@ -83,6 +95,9 @@ class RpmKey(object): self.rpm = self.module.get_bin_path('rpm', True) state = module.params['state'] key = module.params['key'] + fingerprint = module.params['fingerprint'] + if fingerprint: + fingerprint = fingerprint.replace(' ', '').upper() self.gpg = self.module.get_bin_path('gpg') if not self.gpg: @@ -107,6 +122,12 @@ class RpmKey(object): else: if not keyfile: self.module.fail_json(msg="When importing a key, a valid file must be given") + if fingerprint: + has_fingerprint = self.getfingerprint(keyfile) + if fingerprint != has_fingerprint: + self.module.fail_json( + msg="The specified fingerprint, '%s', does not match the key fingerprint '%s'" % (fingerprint, has_fingerprint) + ) self.import_key(keyfile) if should_cleanup_keyfile: self.module.cleanup(keyfile) @@ -153,6 +174,26 @@ class RpmKey(object): self.module.fail_json(msg="Unexpected gpg output") + def getfingerprint(self, keyfile): + stdout, stderr = self.execute_command([ + self.gpg, '--no-tty', '--batch', '--with-colons', + '--fixed-list-mode', '--with-fingerprint', keyfile + ]) + for line in stdout.splitlines(): + line = line.strip() + if line.startswith('fpr:'): + # As mentioned here, + # + # https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob_plain;f=doc/DETAILS + # + # The description of the `fpr` field says + # + # "fpr :: Fingerprint (fingerprint is in field 10)" + # + return line.split(':')[9] + + self.module.fail_json(msg="Unexpected gpg output") + def is_keyid(self, keystr): """Verifies if a key, as provided by the user is a keyid""" return re.match('(0x)?[0-9a-f]{8}', keystr, flags=re.IGNORECASE) @@ -189,6 +230,7 @@ def main(): argument_spec=dict( state=dict(type='str', default='present', choices=['absent', 'present']), key=dict(type='str', required=True), + fingerprint=dict(type='str'), validate_certs=dict(type='bool', default=True), ), supports_check_mode=True, diff --git a/test/integration/targets/rpm_key/tasks/rpm_key.yaml b/test/integration/targets/rpm_key/tasks/rpm_key.yaml index 4c0672d2d96..58020f4880b 100644 --- a/test/integration/targets/rpm_key/tasks/rpm_key.yaml +++ b/test/integration/targets/rpm_key/tasks/rpm_key.yaml @@ -138,6 +138,44 @@ assert: that: "'rsa sha1 (md5) pgp md5 OK' in sl_check.stdout or 'digests signatures OK' in sl_check.stdout" +- name: Issue 20325 - Verify fingerprint of key, invalid fingerprint - EXPECTED FAILURE + rpm_key: + key: https://ansible-ci-files.s3.amazonaws.com/test/integration/targets/rpm_key/RPM-GPG-KEY.dag + fingerprint: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 + register: result + failed_when: result is success + +- name: Issue 20325 - Assert Verify fingerprint of key, invalid fingerprint + assert: + that: + - result is success + - result is not changed + - "'does not match the key fingerprint' in result.msg" + +- name: Issue 20325 - Verify fingerprint of key, valid fingerprint + rpm_key: + key: https://ansible-ci-files.s3.amazonaws.com/test/integration/targets/rpm_key/RPM-GPG-KEY.dag + fingerprint: EBC6 E12C 62B1 C734 026B 2122 A20E 5214 6B8D 79E6 + register: result + +- name: Issue 20325 - Assert Verify fingerprint of key, valid fingerprint + assert: + that: + - result is success + - result is changed + +- name: Issue 20325 - Verify fingerprint of key, valid fingerprint - Idempotent check + rpm_key: + key: https://ansible-ci-files.s3.amazonaws.com/test/integration/targets/rpm_key/RPM-GPG-KEY.dag + fingerprint: EBC6 E12C 62B1 C734 026B 2122 A20E 5214 6B8D 79E6 + register: result + +- name: Issue 20325 - Assert Verify fingerprint of key, valid fingerprint - Idempotent check + assert: + that: + - result is success + - result is not changed + # # Cleanup #