VYPR
Moderate severityNVD Advisory· Published Sep 23, 2020· Updated Aug 4, 2024

CVE-2020-14365

CVE-2020-14365

Description

A flaw was found in the Ansible Engine, in ansible-engine 2.8.x before 2.8.15 and ansible-engine 2.9.x before 2.9.13, when installing packages using the dnf module. GPG signatures are ignored during installation even when disable_gpg_check is set to False, which is the default behavior. This flaw leads to malicious packages being installed on the system and arbitrary code executed via package installation scripts. The highest threat from this vulnerability is to integrity and system availability.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Ansible Engine dnf module ignores GPG signature checks, allowing unverified package installation and arbitrary code execution.

Vulnerability

CVE-2020-14365 is a flaw in the Ansible Engine's dnf module, present in versions 2.8.x before 2.8.15 and 2.9.x before 2.9.13. The module was not actually validating GPG signatures on packages during installation, even though the disable_gpg_check option defaulted to False. The commit messages confirm that 'regardless of the `disable_gpg_check` option, packages were not GPG validated' [2][3][4]. This means that even when the user expected signature verification to occur, no such check was performed.

Exploitation

An attacker who can control a package repository or intercept the package distribution channel (e.g., man-in-the-middle) can deliver a malicious RPM package. Because the Ansible dnf module did not implement the GPG validation logic—the commit adds a new code block that explicitly calls self.base._sig_check_pkg() for each package in the transaction set—the malicious package would be installed without any signature verification [2][3][4]. No special authentication or elevated privileges beyond those normally used to run Ansible playbooks are required; the attack merely needs to compromise the package source.

Impact

Successful exploitation allows the attacker to install arbitrary packages on the target system. Since RPM packages can contain pre- and post-installation scripts, this effectively grants the attacker arbitrary code execution on the managed host. The official description notes that the highest threat is to integrity and system availability [1]. A compromised system could be used for lateral movement, data exfiltration, or as a persistent foothold.

Mitigation

The vulnerability is fixed in ansible-engine 2.8.15 and 2.9.13 [1]. Users should upgrade to these or later versions. The fix adds explicit GPG signature checking for all packages, including those from repositories and local files [2]. No workaround is available—relying on the disable_gpg_check option is ineffective in vulnerable versions.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
ansiblePyPI
>= 2.8.0a1, < 2.8.152.8.15
ansiblePyPI
>= 2.9.0a1, < 2.9.132.9.13

Affected products

173

Patches

3
1fa2d5fd6b76

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

https://github.com/ansible/ansibleRick ElrodAug 31, 2020via ghsa
7 files changed · +108 0
  • changelogs/fragments/dnf_gpg.yml+2 0 added
    @@ -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)
    
  • lib/ansible/modules/packaging/os/dnf.py+22 0 modified
    @@ -65,6 +65,8 @@
         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 @@ def ensure(self):
                             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))
    
  • test/integration/targets/dnf/meta/main.yml+1 0 modified
    @@ -1,3 +1,4 @@
     dependencies:
       - prepare_tests
       - setup_rpm_repo
    +  - setup_remote_tmp_dir
    
  • test/integration/targets/dnf/tasks/dnf.yml+2 0 modified
    @@ -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
    
  • test/integration/targets/dnf/tasks/gpg.yml+72 0 added
    @@ -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
    
  • test/integration/targets/dnf/tasks/main.yml+4 0 modified
    @@ -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', '>='))
    
  • test/integration/targets/dnf/tasks/repo.yml+5 0 modified
    @@ -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
    
1d043e082b3b

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

https://github.com/ansible/ansibleRick ElrodAug 31, 2020via ghsa
6 files changed · +107 0
  • changelogs/fragments/dnf_gpg.yml+2 0 added
    @@ -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)
    
  • lib/ansible/modules/packaging/os/dnf.py+22 0 modified
    @@ -67,6 +67,8 @@
         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'
     
    @@ -1193,6 +1195,26 @@ def ensure(self):
                             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))
    
  • test/integration/targets/dnf/tasks/dnf.yml+2 0 modified
    @@ -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
    
  • test/integration/targets/dnf/tasks/gpg.yml+72 0 added
    @@ -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
    
  • test/integration/targets/dnf/tasks/main.yml+4 0 modified
    @@ -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', '>='))
    
  • test/integration/targets/dnf/tasks/repo.yml+5 0 modified
    @@ -106,6 +106,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
    @@ -132,6 +133,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
    @@ -153,6 +155,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
    @@ -169,6 +172,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
    @@ -190,6 +194,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
    
9bea33ffa3be

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

https://github.com/ansible/ansibleRick ElrodAug 31, 2020via ghsa
6 files changed · +107 0
  • changelogs/fragments/dnf_gpg.yml+2 0 added
    @@ -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)
    
  • lib/ansible/modules/dnf.py+22 0 modified
    @@ -62,6 +62,8 @@
         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'
     
    @@ -1189,6 +1191,26 @@ def ensure(self):
                             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:
                         # No further work left to do, and the results were already updated above.
                         # Just return them.
    
  • test/integration/targets/dnf/tasks/dnf.yml+2 0 modified
    @@ -617,6 +617,7 @@
       dnf:
         name: "/tmp/{{ pkg_name }}.rpm"
         state: present
    +    disable_gpg_check: true
       register: dnf_result
     
     - name: verify installation
    @@ -646,6 +647,7 @@
       dnf:
         name: "{{ pkg_url }}"
         state: present
    +    disable_gpg_check: true
       register: dnf_result
     
     - name: verify installation
    
  • test/integration/targets/dnf/tasks/gpg.yml+72 0 added
    @@ -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
    
  • test/integration/targets/dnf/tasks/main.yml+4 0 modified
    @@ -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', '>='))
    
  • test/integration/targets/dnf/tasks/repo.yml+5 0 modified
    @@ -106,6 +106,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
    @@ -132,6 +133,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
    @@ -153,6 +155,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
    @@ -169,6 +172,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
    @@ -190,6 +194,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
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.