How to verify an Ansible project signature?

A step to step guide to verify the signature using the ansible-sign of a GPG-signed Ansible project. I’m going to show you a live Playbook with some simple Ansible code. I’m Luca Berton, and welcome to today’s episode of Ansible Pilot.

ansible-sign

  • available since 2022
  • command line
  • GPG signature

The ansible-sign command has been available since 2022 for installation in the most modern operating system. It is a command line tool so simplify the Project signing process using your terminal. Using the ansible-sign command, we can verify the GPG signature of an Ansible project.

Join 50+ hours of courses in our exclusive community

Playbook

  • GPG sign verification a project

I’m going to show you how to verify the signature of an Ansible project using the ansible-sign command line utility. At the beginning of this example, we start with a project with all our Ansible files already signed with a GPG signature. By the end of this Playbook, we will verify if the signature is correct for the current Ansible project directory.

Project directory files:

  • playbooks/ping.yml
---
- name: ping module Playbook
  hosts: all
  tasks:
    - name: test connection
      ansible.builtin.ping:
  • inventory
localhost ansible_connection=local
  • MANIFEST.in
recursive-exclude .git *
include inventory
recursive-include playbooks *.yml

Project signature files:

  • .ansible-sign/sha256sum.txt
8fda56fd3288141367f151fcaf8e3fca5d4b46cfe3ba7d8dfc66b17205284efd  MANIFEST.in
49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763  inventory
1c666ccae8a05445d2c8b36341dec1671093999d995944e2ecdce671fc474f7c  playbooks/ping.yml
  • .ansible-sign/sha256sum.txt.sig
-----BEGIN PGP SIGNATURE-----

iHUEABYKAB0WIQRFr0MY44L/nHcn7m7siNPNNZbCMAUCY8KpMQAKCRDsiNPNNZbC
MJvaAP97QUbnymXHaco5ZnF6vrReOc/7C7e8YDxSfh+6fsjbPAEAsnk4t4jTihkm
O15QNa+3EwelTLjjeZkHfFyY1YPD6QE=
=4rnz
-----END PGP SIGNATURE-----

1. install ansible-sign

Verify if the ansible-sign command is available in your terminal. When you obtain a command not found error, you should install it.

$ ansible-sign
command not found: ansible-sign

When the package is not available on our favorite package manager (apt, DNF, yum, zypper, brew, conda), we can rely on the PIP Python package manager:

$ pip3 install ansible-sign

Expected output:

$ pip3 install ansible-sign
Collecting ansible-sign
  Downloading ansible_sign-0.1.1-py3-none-any.whl (15 kB)
Requirement already satisfied: distlib in /opt/homebrew/lib/python3.10/site-packages (from ansible-sign) (0.3.6)
Requirement already satisfied: python-gnupg in /opt/homebrew/lib/python3.10/site-packages (from ansible-sign) (0.5.0)
Installing collected packages: ansible-sign
Successfully installed ansible-sign-0.1.1

By the end of this step, the command will be available with the following output:

$ ansible-sign
usage: ansible-sign [-h] [--version] [--debug] [--nocolor] CONTENT_TYPE ...
ansible-sign: error: the following arguments are required: CONTENT_TYPE

2. ensure the GPG utility is installed

When the GPG utility (gpg command) is not present in our system, we obtain the following message on the screen:

OSError: Unable to run gpg (gpg) - it may not be available.

The full error stack track is the following:

$ ansible-sign project gpg-sign .
[2023-01-13 19:38:25] ERROR:gnupg:Unable to run gpg (gpg) - it may not be available.
Traceback (most recent call last):
  File "/opt/homebrew/lib/python3.10/site-packages/gnupg.py", line 941, in __init__
    p = self._open_subprocess(['--version'])
  File "/opt/homebrew/lib/python3.10/site-packages/gnupg.py", line 1007, in _open_subprocess
    result = Popen(cmd, shell=False, stdin=PIPE, stdout=PIPE, stderr=PIPE, startupinfo=si, env=self.env)
  File "/opt/homebrew/Cellar/[email protected]/3.10.9/Frameworks/Python.framework/Versions/3.10/lib/python3.10/subprocess.py", line 971, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/opt/homebrew/Cellar/[email protected]/3.10.9/Frameworks/Python.framework/Versions/3.10/lib/python3.10/subprocess.py", line 1847, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'gpg'
Traceback (most recent call last):
  File "/opt/homebrew/lib/python3.10/site-packages/gnupg.py", line 941, in __init__
    p = self._open_subprocess(['--version'])
  File "/opt/homebrew/lib/python3.10/site-packages/gnupg.py", line 1007, in _open_subprocess
    result = Popen(cmd, shell=False, stdin=PIPE, stdout=PIPE, stderr=PIPE, startupinfo=si, env=self.env)
  File "/opt/homebrew/Cellar/[email protected]/3.10.9/Frameworks/Python.framework/Versions/3.10/lib/python3.10/subprocess.py", line 971, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/opt/homebrew/Cellar/[email protected]/3.10.9/Frameworks/Python.framework/Versions/3.10/lib/python3.10/subprocess.py", line 1847, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'gpg'During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "/opt/homebrew/bin/ansible-sign", line 8, in <module>
    sys.exit(run())
  File "/opt/homebrew/lib/python3.10/site-packages/ansible_sign/cli.py", line 374, in run
    return main(sys.argv[1:])
  File "/opt/homebrew/lib/python3.10/site-packages/ansible_sign/cli.py", line 364, in main
    exitcode = cli.run_command()
  File "/opt/homebrew/lib/python3.10/site-packages/ansible_sign/cli.py", line 46, in run_command
    return self.args.func()
  File "/opt/homebrew/lib/python3.10/site-packages/ansible_sign/cli.py", line 346, in gpg_sign
    result = signer.sign()
  File "/opt/homebrew/lib/python3.10/site-packages/ansible_sign/signing/gpg/signer.py", line 45, in sign
    gpg = gnupg.GPG(gnupghome=self.gpg_home)
  File "/opt/homebrew/lib/python3.10/site-packages/gnupg.py", line 945, in __init__
    raise OSError(msg)
OSError: Unable to run gpg (gpg) - it may not be available

We can install it using our favorite package manager (apt, DNF, yum, zypper, brew):

$ brew install gpg
[...]
==> Installing gnupg

A successful installation report makes the gpg command available and usable:

$ gpg             
gpg: directory '/Users/lberton/.gnupg' created
gpg: keybox '/Users/lberton/.gnupg/pubring.kbx' created
gpg: WARNING: no command supplied.  Trying to guess what you mean ...
gpg: Go ahead and type your message ...

3. GPG key successfully imported

Make sure that the key used by the Ansible project is successfully imported into the verification system. We can list all the GPG keys using the following command:

$ gpg --list-keys
/Users/lberton/.gnupg/pubring.kbx
---------------------------------
pub   ed25519 2023-01-14 [SC] [expires: 2025-01-13]
      45AF4318E382FF9C7727EE6EEC88D3CD3596C230
uid           [ultimate] Luca Berton <[email protected]>
sub   cv25519 2023-01-14 [E] [expires: 2025-01-13]

We can easily import a GPG public key using the command:

$ gpg --import luca.gpg
gpg: key 45AF4318: public key imported
gpg: Total number processed: 1
gpg:               imported: 1

The GPG public key was previously exported using the command:

$ gpg --armor --export [email protected]

4. Ansible Project verification

A successful Ansible project verification is like the following:

$ ansible-sign project gpg-verify .
[OK   ] GPG signature verification succeeded.
[OK   ] Checksum validation succeeded.

All our files are the same as the creator; the checksum matches, and the signature is valid.

File mismatch

When additional files are present or removed in the current directory the ansible-sign utility print the full list on the screen:

$ ansible-sign project gpg-verify .
[OK   ] GPG signature verification succeeded.
[ERROR] Checksum validation failed.
[ERROR] {'added': ['.DS_Store'], 'removed': []}

In the above output, the additional file .DS_Store is present in the current directory and not present in the Ansible checksum manifest.

Signature mismatch

When the signature is mismatched or invalid, we obtain the following message:

$ ansible-sign project gpg-verify .   
[2023-01-14 13:41:38] WARNING:gnupg:gpg returned a non-zero error code: 2
[ERROR] GPG signature verification failed.
[NOTE ] Re-run with the global --debug flag for more information.

The root cause might be a wrong GPG signature, a file tempered, or a mismatch. Something that we should definitely investigate.

Full debug output

[2023-01-14 14:13:31] DEBUG:ansible_sign.cli:Running requested command/passing to function
[2023-01-14 14:13:31] DEBUG:gnupg:20291: gpg --status-fd 2 --no-tty --no-verbose --fixed-list-mode --batch --with-colons --version
[2023-01-14 14:13:31] DEBUG:gnupg:stderr reader: <Thread(Thread-1 (_read_response), initial daemon)>
[2023-01-14 14:13:31] DEBUG:gnupg:stdout reader: <Thread(Thread-2 (_read_data), initial daemon)>
[2023-01-14 14:13:31] DEBUG:gnupg:verify_file: <_io.BufferedReader name='./.ansible-sign/sha256sum.txt.sig'>, './.ansible-sign/sha256sum.txt'
[2023-01-14 14:13:31] DEBUG:gnupg:Handling detached verification
[2023-01-14 14:13:31] DEBUG:gnupg:Wrote to temp file: b'-----BEGIN PGP SIGNATURE-----\n\nMJvaAP97QUbnymXHaco5ZnF6vrReOc/7C7e8YDxSfh+6fsjbPAEAsnk4t4jTihkm\nO15QNa+3EwelTLjjeZkHfFyY1YPD6QE=\n=4rnz\n-----END PGP SIGNATURE-----\n'
[2023-01-14 14:13:31] DEBUG:gnupg:20292: gpg --status-fd 2 --no-tty --no-verbose --fixed-list-mode --batch --with-colons --verify /var/folders/mp/q66z6zq57f18s7s6gvw6zkwh0000gn/T/pygpg-ab8lbsju ./.ansible-sign/sha256sum.txt
[2023-01-14 14:13:31] DEBUG:gnupg:stderr reader: <Thread(Thread-3 (_read_response), initial daemon)>
[2023-01-14 14:13:31] DEBUG:gnupg:stdout reader: <Thread(Thread-4 (_read_data), initial daemon)>
[2023-01-14 14:13:31] DEBUG:gnupg:gpg: CRC error; 4DD76C - E2B9F3
[2023-01-14 14:13:31] DEBUG:gnupg:gpg: [don't know]: invalid packet (ctb=30)
[2023-01-14 14:13:31] DEBUG:gnupg:[GNUPG:] NODATA 3
[2023-01-14 14:13:31] DEBUG:gnupg:[GNUPG:] NODATA 4
[2023-01-14 14:13:31] DEBUG:gnupg:gpg: no signature found
[2023-01-14 14:13:31] DEBUG:gnupg:gpg: the signature could not be verified.
[2023-01-14 14:13:31] DEBUG:gnupg:Please remember that the signature file (.sig or .asc)
[2023-01-14 14:13:31] DEBUG:gnupg:should be the first file given on the command line.
[2023-01-14 14:13:31] WARNING:gnupg:gpg returned a non-zero error code: 2
[ERROR] GPG signature verification failed.
[NOTE ] Re-run with the global --debug flag for more information.
[2023-01-14 14:13:31] DEBUG:ansible_sign.cli:{'stderr': "gpg: CRC error; 4DD76C - E2B9F3\ngpg: [don't know]: invalid packet (ctb=30)\n[GNUPG:] NODATA 3\n[GNUPG:] NODATA 4\ngpg: no signature found\ngpg: the signature could not be verified.\nPlease remember that the signature file (.sig or .asc)\nshould be the first file given on the command line.\n"}
[2023-01-14 14:13:31] INFO:ansible_sign.cli:Script ends here, rc=3
Join 50+ hours of courses in our exclusive community

Conclusion

Now you know how to verify an Ansible project signed with a GPG signature.

Subscribe to the YouTube channel, Medium, and Website, X (formerly Twitter) to not miss the next episode of the Ansible Pilot.

Academy

Learn the Ansible automation technology with some real-life examples in my Udemy 300+ Lessons Video Course.

BUY the Complete Udemy 300+ Lessons Video Course

My book Ansible By Examples: 200+ Automation Examples For Linux and Windows System Administrator and DevOps

BUY the Complete PDF BOOK to easily Copy and Paste the 250+ Ansible code

Want to keep this project going? Please donate

Patreon Buy me a Pizza