summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRick Elrod <rick@elrod.me>2020-08-31 10:05:42 -0500
committerGitHub <noreply@github.com>2020-08-31 10:05:42 -0500
commit1fa2d5fd6b768120b76a77929e27302b06accc0c (patch)
tree7b1757c910a997cf0baefd0d056bbf3b07fed0cf
parent53be4cc03dca58d8e4a42af65e697d6b2c7d8845 (diff)
downloadansible-1fa2d5fd6b768120b76a77929e27302b06accc0c.tar.gz
[dnf] ensure packages are gpg-verified (#71541)
Change: - By default the dnf API does not gpg-verify packages. This is a feature that is executed in its CLI code. It never made it into Ansible's usage of the API, so packages were previously not verified. - This fixes CVE-2020-14365. Test Plan: - New integration tests Signed-off-by: Rick Elrod <rick@elrod.me>
-rw-r--r--changelogs/fragments/dnf_gpg.yml2
-rw-r--r--lib/ansible/modules/packaging/os/dnf.py22
-rw-r--r--test/integration/targets/dnf/meta/main.yml1
-rw-r--r--test/integration/targets/dnf/tasks/dnf.yml2
-rw-r--r--test/integration/targets/dnf/tasks/gpg.yml72
-rw-r--r--test/integration/targets/dnf/tasks/main.yml4
-rw-r--r--test/integration/targets/dnf/tasks/repo.yml5
7 files changed, 108 insertions, 0 deletions
diff --git a/changelogs/fragments/dnf_gpg.yml b/changelogs/fragments/dnf_gpg.yml
new file mode 100644
index 0000000000..2e156c509b
--- /dev/null
+++ b/changelogs/fragments/dnf_gpg.yml
@@ -0,0 +1,2 @@
+security_fixes:
+ - dnf - Previously, regardless of the ``disable_gpg_check`` option, packages were not GPG validated. They are now. (CVE-2020-14365)
diff --git a/lib/ansible/modules/packaging/os/dnf.py b/lib/ansible/modules/packaging/os/dnf.py
index 509ee76568..66912593fa 100644
--- a/lib/ansible/modules/packaging/os/dnf.py
+++ b/lib/ansible/modules/packaging/os/dnf.py
@@ -65,6 +65,8 @@ options:
description:
- Whether to disable the GPG checking of signatures of packages being
installed. Has an effect only if state is I(present) or I(latest).
+ - This setting affects packages installed from a repository as well as
+ "local" packages installed from the filesystem or a URL.
type: bool
default: 'no'
@@ -1203,6 +1205,26 @@ class DnfModule(YumDnf):
results=[],
)
+ # Validate GPG. This is NOT done in dnf.Base (it's done in the
+ # upstream CLI subclass of dnf.Base)
+ if not self.disable_gpg_check:
+ for package in self.base.transaction.install_set:
+ fail = False
+ gpgres, gpgerr = self.base._sig_check_pkg(package)
+ if gpgres == 0: # validated successfully
+ continue
+ elif gpgres == 1: # validation failed, install cert?
+ try:
+ self.base._get_key_for_package(package)
+ except dnf.exceptions.Error as e:
+ fail = True
+ else: # fatal error
+ fail = True
+
+ if fail:
+ msg = 'Failed to validate GPG signature for {0}'.format(package)
+ self.module.fail_json(msg=msg)
+
if self.download_only:
for package in self.base.transaction.install_set:
response['results'].append("Downloaded: {0}".format(package))
diff --git a/test/integration/targets/dnf/meta/main.yml b/test/integration/targets/dnf/meta/main.yml
index 73076d3562..34d812616b 100644
--- a/test/integration/targets/dnf/meta/main.yml
+++ b/test/integration/targets/dnf/meta/main.yml
@@ -1,3 +1,4 @@
dependencies:
- prepare_tests
- setup_rpm_repo
+ - setup_remote_tmp_dir
diff --git a/test/integration/targets/dnf/tasks/dnf.yml b/test/integration/targets/dnf/tasks/dnf.yml
index abe7068d24..24e7b8073c 100644
--- a/test/integration/targets/dnf/tasks/dnf.yml
+++ b/test/integration/targets/dnf/tasks/dnf.yml
@@ -559,6 +559,7 @@
dnf:
name: "/tmp/{{ pkg_name }}.rpm"
state: present
+ disable_gpg_check: true
register: dnf_result
- name: verify installation
@@ -588,6 +589,7 @@
dnf:
name: "{{ pkg_url }}"
state: present
+ disable_gpg_check: true
register: dnf_result
- name: verify installation
diff --git a/test/integration/targets/dnf/tasks/gpg.yml b/test/integration/targets/dnf/tasks/gpg.yml
new file mode 100644
index 0000000000..2b6f4079a9
--- /dev/null
+++ b/test/integration/targets/dnf/tasks/gpg.yml
@@ -0,0 +1,72 @@
+# Set up a repo of unsigned rpms
+- block:
+ - name: Ensure our test package isn't already installed
+ dnf:
+ name:
+ - fpaste
+ state: absent
+
+ - name: Install rpm-sign
+ dnf:
+ name:
+ - rpm-sign
+ state: present
+
+ - name: Create directory to use as local repo
+ file:
+ path: "{{ remote_tmp_dir }}/unsigned"
+ state: directory
+
+ - name: Download an RPM
+ get_url:
+ url: https://s3.amazonaws.com/ansible-ci-files/test/integration/targets/dnf/fpaste-0.3.9.1-1.fc27.noarch.rpm
+ dest: "{{ remote_tmp_dir }}/unsigned/fpaste-0.3.9.1-1.fc27.noarch.rpm"
+ mode: 0644
+
+ - name: Unsign the RPM
+ command: rpmsign --delsign "{{ remote_tmp_dir }}/unsigned/fpaste-0.3.9.1-1.fc27.noarch.rpm"
+
+ - name: createrepo
+ command: createrepo .
+ args:
+ chdir: "{{ remote_tmp_dir }}/unsigned"
+
+ - name: Add the repo
+ yum_repository:
+ name: unsigned
+ description: unsigned rpms
+ baseurl: "file://{{ remote_tmp_dir }}/unsigned/"
+ # we want to ensure that signing is verified
+ gpgcheck: true
+
+ - name: Install fpaste from above
+ dnf:
+ name:
+ - fpaste
+ disablerepo: '*'
+ enablerepo: unsigned
+ register: res
+ ignore_errors: yes
+
+ - assert:
+ that:
+ - res is failed
+ - "'Failed to validate GPG signature' in res.msg"
+
+ always:
+ - name: Remove rpm-sign (and fpaste if it got installed)
+ dnf:
+ name:
+ - rpm-sign
+ - fpaste
+ state: absent
+
+ - name: Remove test repo
+ yum_repository:
+ name: unsigned
+ state: absent
+
+ - name: Remove repo dir
+ file:
+ path: "{{ remote_tmp_dir }}/unsigned"
+ state: absent
diff --git a/test/integration/targets/dnf/tasks/main.yml b/test/integration/targets/dnf/tasks/main.yml
index 9369b5b0cd..fbfddadf7b 100644
--- a/test/integration/targets/dnf/tasks/main.yml
+++ b/test/integration/targets/dnf/tasks/main.yml
@@ -23,6 +23,10 @@
when: (ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('23', '>=')) or
(ansible_distribution in ['RedHat', 'CentOS'] and ansible_distribution_major_version is version('8', '>='))
+- include_tasks: gpg.yml
+ when: (ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('23', '>=')) or
+ (ansible_distribution in ['RedHat', 'CentOS'] and ansible_distribution_major_version is version('8', '>='))
+
- include_tasks: repo.yml
when: (ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('23', '>=')) or
(ansible_distribution in ['RedHat', 'CentOS'] and ansible_distribution_major_version is version('8', '>='))
diff --git a/test/integration/targets/dnf/tasks/repo.yml b/test/integration/targets/dnf/tasks/repo.yml
index 718eae10a5..b8a84f2dc7 100644
--- a/test/integration/targets/dnf/tasks/repo.yml
+++ b/test/integration/targets/dnf/tasks/repo.yml
@@ -88,6 +88,7 @@
name: "{{ repodir }}/dinginessentail-1.0-1.{{ ansible_architecture }}.rpm"
state: present
allow_downgrade: True
+ disable_gpg_check: True
register: dnf_result
- name: Check dinginessentail with rpm
@@ -114,6 +115,7 @@
dnf:
name: "{{ repodir }}/dinginessentail-1.0-1.{{ ansible_architecture }}.rpm"
state: present
+ disable_gpg_check: True
register: dnf_result
- name: Check dinginessentail with rpm
@@ -135,6 +137,7 @@
dnf:
name: "{{ repodir }}/dinginessentail-1.0-1.{{ ansible_architecture }}.rpm"
state: present
+ disable_gpg_check: True
register: dnf_result
- name: Check dinginessentail with rpm
@@ -151,6 +154,7 @@
dnf:
name: "{{ repodir }}/dinginessentail-1.0-2.{{ ansible_architecture }}.rpm"
state: present
+ disable_gpg_check: True
register: dnf_result
- name: Check dinginessentail with rpm
@@ -172,6 +176,7 @@
dnf:
name: "{{ repodir }}/dinginessentail-1.0-2.{{ ansible_architecture }}.rpm"
state: present
+ disable_gpg_check: True
register: dnf_result
- name: Check dinginessentail with rpm