[dnf] ensure packages are gpg-verified (#71539)

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>
This commit is contained in:
Rick Elrod 2020-08-31 10:05:30 -05:00 committed by GitHub
parent d3e0cb4320
commit dc97027453
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 107 additions and 0 deletions

View file

@ -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)

View file

@ -62,6 +62,8 @@ options:
description: description:
- Whether to disable the GPG checking of signatures of packages being - Whether to disable the GPG checking of signatures of packages being
installed. Has an effect only if state is I(present) or I(latest). 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 type: bool
default: 'no' default: 'no'
@ -1165,6 +1167,26 @@ class DnfModule(YumDnf):
results=[], 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)
if self.download_only: if self.download_only:
for package in self.base.transaction.install_set: for package in self.base.transaction.install_set:
response['results'].append("Downloaded: {0}".format(package)) response['results'].append("Downloaded: {0}".format(package))

View file

@ -557,6 +557,7 @@
dnf: dnf:
name: "/tmp/{{ pkg_name }}.rpm" name: "/tmp/{{ pkg_name }}.rpm"
state: present state: present
disable_gpg_check: true
register: dnf_result register: dnf_result
- name: verify installation - name: verify installation
@ -586,6 +587,7 @@
dnf: dnf:
name: "{{ pkg_url }}" name: "{{ pkg_url }}"
state: present state: present
disable_gpg_check: true
register: dnf_result register: dnf_result
- name: verify installation - name: verify installation

View file

@ -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

View file

@ -23,6 +23,10 @@
when: (ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('23', '>=')) or 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', '>=')) (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 - include_tasks: repo.yml
when: (ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('23', '>=')) or 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', '>=')) (ansible_distribution in ['RedHat', 'CentOS'] and ansible_distribution_major_version is version('8', '>='))

View file

@ -106,6 +106,7 @@
name: "{{ repodir }}/dinginessentail-1.0-1.{{ ansible_architecture }}.rpm" name: "{{ repodir }}/dinginessentail-1.0-1.{{ ansible_architecture }}.rpm"
state: present state: present
allow_downgrade: True allow_downgrade: True
disable_gpg_check: True
register: dnf_result register: dnf_result
- name: Check dinginessentail with rpm - name: Check dinginessentail with rpm
@ -132,6 +133,7 @@
dnf: dnf:
name: "{{ repodir }}/dinginessentail-1.0-1.{{ ansible_architecture }}.rpm" name: "{{ repodir }}/dinginessentail-1.0-1.{{ ansible_architecture }}.rpm"
state: present state: present
disable_gpg_check: True
register: dnf_result register: dnf_result
- name: Check dinginessentail with rpm - name: Check dinginessentail with rpm
@ -153,6 +155,7 @@
dnf: dnf:
name: "{{ repodir }}/dinginessentail-1.0-1.{{ ansible_architecture }}.rpm" name: "{{ repodir }}/dinginessentail-1.0-1.{{ ansible_architecture }}.rpm"
state: present state: present
disable_gpg_check: True
register: dnf_result register: dnf_result
- name: Check dinginessentail with rpm - name: Check dinginessentail with rpm
@ -169,6 +172,7 @@
dnf: dnf:
name: "{{ repodir }}/dinginessentail-1.0-2.{{ ansible_architecture }}.rpm" name: "{{ repodir }}/dinginessentail-1.0-2.{{ ansible_architecture }}.rpm"
state: present state: present
disable_gpg_check: True
register: dnf_result register: dnf_result
- name: Check dinginessentail with rpm - name: Check dinginessentail with rpm
@ -190,6 +194,7 @@
dnf: dnf:
name: "{{ repodir }}/dinginessentail-1.0-2.{{ ansible_architecture }}.rpm" name: "{{ repodir }}/dinginessentail-1.0-2.{{ ansible_architecture }}.rpm"
state: present state: present
disable_gpg_check: True
register: dnf_result register: dnf_result
- name: Check dinginessentail with rpm - name: Check dinginessentail with rpm