How to sign an Ansible project?

From a non-signed to 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
  • GPG signature
  • command line

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, you sign a project using a GPG signature.

Join 50+ hours of courses in our exclusive community

Playbook

  • GPG sign a project

I’m going to show you how to sign 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 without any GPG signature files. By the end of this Playbook, you are obtaining a GPG-signed 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

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. create a MANIFEST.in file

When the MANIFEST.in file is not present, we obtain the following message on the screen:

$ ansible-sign project gpg-sign .
[ERROR] Could not find a MANIFEST.in file in the specified project.
[NOTE ] If you are attempting to sign a project, please create this file.
[NOTE ] See the ansible-sign documentation for more information.

When some parts of the MANIFEST.in file is not correct; we obtain the following error on the screen:

[ERROR] An error was encountered while parsing MANIFEST.in: 'recursive-include' expects <dir> <pattern1> <pattern2> ...
[NOTE ] You can use the --debug global flag to view the full traceback.

3. 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 ...

4. GPG key successfully created

When no GPG key is present the ansible-sign command shows the following error:

$ ansible-sign project gpg-sign .
[2023-01-13 19:39:18] WARNING:gnupg:potential problem: FAILURE: sign 17
[2023-01-13 19:39:18] WARNING:gnupg:gpg returned a non-zero error code: 2
[ERROR] GPG signing FAILED!
[NOTE ] Re-run with the global --debug flag for more information.
[NOTE ] Checksum manifest: ./.ansible-sign/sha256sum.txt
[NOTE ] GPG summary: None

We can specify a debugging feature for more information:

gpg: no default secret key: No secret key\n[GNUPG:] INV_SGNR 9

The full stack trace:

$ ansible-sign --debug project gpg-sign .
[2023-01-13 19:39:37] DEBUG:ansible_sign.cli:Running requested command/passing to function
[2023-01-13 19:39:37] DEBUG:ansible_sign.cli:Full calculated checksum manifest (.):
da18c88f14299362a2073b4f340ae5abec55e280af82b9e1b399dfabef079983  MANIFEST.in
49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763  inventory
1c666ccae8a05445d2c8b36341dec1671093999d995944e2ecdce671fc474f7c  playbooks/ping.yml

[2023-01-13 19:39:37] INFO:ansible_sign.cli:Wrote to file: ./.ansible-sign/sha256sum.txt
[2023-01-13 19:39:37] DEBUG:gnupg:96724: gpg --status-fd 2 --no-tty --no-verbose --fixed-list-mode --batch --with-colons --version
[2023-01-13 19:39:37] DEBUG:gnupg:stderr reader: <Thread(Thread-1 (_read_response), initial daemon)>
[2023-01-13 19:39:37] DEBUG:gnupg:stdout reader: <Thread(Thread-2 (_read_data), initial daemon)>
[2023-01-13 19:39:37] DEBUG:gnupg:sign_file: <_io.BufferedReader name='./.ansible-sign/sha256sum.txt'>
[2023-01-13 19:39:37] DEBUG:gnupg:96725: gpg --status-fd 2 --no-tty --no-verbose --fixed-list-mode --batch --with-colons -sa --detach-sign --output ./.ansible-sign/sha256sum.txt.sig
[2023-01-13 19:39:37] DEBUG:gnupg:data copier: <Thread(Thread-3 (_copy_data), initial daemon)>, <_io.BufferedReader name='./.ansible-sign/sha256sum.txt'>, <_io.BufferedWriter name=5>
[2023-01-13 19:39:37] DEBUG:gnupg:closed output, 239 bytes sent
[2023-01-13 19:39:37] DEBUG:gnupg:stderr reader: <Thread(Thread-4 (_read_response), initial daemon)>
[2023-01-13 19:39:37] DEBUG:gnupg:stdout reader: <Thread(Thread-5 (_read_data), initial daemon)>
[2023-01-13 19:39:37] DEBUG:gnupg:gpg: no default secret key: No secret key
[2023-01-13 19:39:37] DEBUG:gnupg:[GNUPG:] INV_SGNR 9
[2023-01-13 19:39:37] DEBUG:gnupg:message ignored: INV_SGNR, 9
[2023-01-13 19:39:37] DEBUG:gnupg:[GNUPG:] FAILURE sign 17
[2023-01-13 19:39:37] WARNING:gnupg:potential problem: FAILURE: sign 17
[2023-01-13 19:39:37] DEBUG:gnupg:gpg: signing failed: No secret key
[2023-01-13 19:39:37] WARNING:gnupg:gpg returned a non-zero error code: 2
[ERROR] GPG signing FAILED!
[NOTE ] Re-run with the global --debug flag for more information.
[NOTE ] Checksum manifest: ./.ansible-sign/sha256sum.txt
[NOTE ] GPG summary: None
[2023-01-13 19:39:37] DEBUG:ansible_sign.cli:GPG Details: {'stderr': 'gpg: no default secret key: No secret key\n[GNUPG:] INV_SGNR 9\n[GNUPG:] FAILURE sign 17\ngpg: signing failed: No secret key\n', 'fingerprint': None, 'hash_algo': None, 'returncode': 2}
[2023-01-13 19:39:37] INFO:ansible_sign.cli:Script ends here, rc=4

We can create our GPG key using the command line

$ gpg --generate-key
gpg (GnuPG) 2.4.0; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Note: Use "gpg --full-generate-key" for a full featured key generation dialog.

GnuPG needs to construct a user ID to identify your key.

Real name: Luca Berton
Email address: [email protected]
You selected this USER-ID:
    "Luca Berton <[email protected]>"

Change (N)ame, (E)mail, or (O)kay/(Q)uit? o
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: /Users/lberton/.gnupg/trustdb.gpg: trustdb created
gpg: directory '/Users/lberton/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/Users/lberton/.gnupg/openpgp-revocs.d/D5F7F6089E75CFE10DBF50ECC64CA1040CBCE82C.rev'
public and secret key created and signed.

pub   ed25519 2023-01-13 [SC] [expires: 2025-01-12]
      D5F7F6089E75CFE10DBF50ECC64CA1040CBCE82C
uid                      Luca Berton <[email protected]>
sub   cv25519 2023-01-13 [E] [expires: 2025-01-12]

5. Sign our project directory

When all the previous steps is satisfied, we obtain a successful execution:

$ ansible-sign project gpg-sign .        
[OK   ] GPG signing successful!
[NOTE ] Checksum manifest: ./.ansible-sign/sha256sum.txt
[NOTE ] GPG summary: signature created

Verification

The output is two files under the .ansible-sign directory:

  • sha256sum.txt
  • sha256sum.txt.sig

The content of the sha256sum.txt file:

8fda56fd3288141367f151fcaf8e3fca5d4b46cfe3ba7d8dfc66b17205284efd  MANIFEST.in
49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763  inventory
1c666ccae8a05445d2c8b36341dec1671093999d995944e2ecdce671fc474f7c  playbooks/ping.yml

The content of the sha256sum.txt.sig file:

-----BEGIN PGP SIGNATURE-----

iHUEABYKAB0WIQTV9/YInnXP4Q2/UOzGTKEEDLzoLAUCY8G45gAKCRDGTKEEDLzo
LOIxAP9G0zYcy4CvtkuxfDAjeG98+ok+iXpkV9Dejx6HxEz1iAD/R+yFvpPfNxvC
5HBzKa4EEkySXZpJ3yrah9MogBKX+QQ=
=zl60
-----END PGP SIGNATURE-----
Join 50+ hours of courses in our exclusive community

Conclusion

Now you know how to sign an Ansible project 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