adding support for export of private gpg key (#22557)
This PR allows users to `--export`, `--export-secret`, or both to export GPG keys from Spack. The docs are updated that include a warning that this usually does not need to be done. This addresses an issue brought up in slack, and also represented in #14721. Signed-off-by: vsoch <vsoch@users.noreply.github.com> Co-authored-by: vsoch <vsoch@users.noreply.github.com>
This commit is contained in:
		| @@ -1119,6 +1119,33 @@ Secret keys may also be later exported using the | ||||
|       <https://www.digitalocean.com/community/tutorials/how-to-setup-additional-entropy-for-cloud-servers-using-haveged>`_ | ||||
|       provides a good overview of sources of randomness. | ||||
|  | ||||
| Here is an example of creating a key. Note that we provide a name for the key first | ||||
| (which we can use to reference the key later) and an email address: | ||||
|  | ||||
| .. code-block:: console | ||||
|  | ||||
|     $ spack gpg create dinosaur dinosaur@thedinosaurthings.com | ||||
|  | ||||
|  | ||||
| If you want to export the key as you create it: | ||||
|  | ||||
|  | ||||
| .. code-block:: console | ||||
|  | ||||
|     $ spack gpg create --export key.pub dinosaur dinosaur@thedinosaurthings.com | ||||
|  | ||||
| Or the private key: | ||||
|  | ||||
|  | ||||
| .. code-block:: console | ||||
|  | ||||
|     $ spack gpg create --export-secret key.priv dinosaur dinosaur@thedinosaurthings.com | ||||
|  | ||||
|  | ||||
| You can include both ``--export`` and ``--export-secret``, each with | ||||
| an output file of choice, to export both. | ||||
|  | ||||
|  | ||||
| ^^^^^^^^^^^^ | ||||
| Listing keys | ||||
| ^^^^^^^^^^^^ | ||||
| @@ -1127,7 +1154,22 @@ In order to list the keys available in the keyring, the | ||||
| ``spack gpg list`` command will list trusted keys with the ``--trusted`` flag | ||||
| and keys available for signing using ``--signing``. If you would like to | ||||
| remove keys from your keyring, ``spack gpg untrust <keyid>``. Key IDs can be | ||||
| email addresses, names, or (best) fingerprints. | ||||
| email addresses, names, or (best) fingerprints. Here is an example of listing | ||||
| the key that we just created: | ||||
|  | ||||
| .. code-block:: console | ||||
|  | ||||
|     gpgconf: socketdir is '/run/user/1000/gnupg' | ||||
|     /home/spackuser/spack/opt/spack/gpg/pubring.kbx | ||||
|     ---------------------------------------------------------- | ||||
|     pub   rsa4096 2021-03-25 [SC] | ||||
|           60D2685DAB647AD4DB54125961E09BB6F2A0ADCB | ||||
|     uid           [ultimate] dinosaur (GPG created for Spack) <dinosaur@thedinosaurthings.com> | ||||
|  | ||||
|      | ||||
| Note that the name "dinosaur" can be seen under the uid, which is the unique | ||||
| id. We might need this reference if we want to export or otherwise reference the key. | ||||
|  | ||||
|  | ||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||
| Signing and Verifying Packages | ||||
| @@ -1142,6 +1184,38 @@ may also be used to create a signed file which contains the contents, but it | ||||
| is not recommended. Signed packages may be verified by using | ||||
| ``spack gpg verify <file>``. | ||||
|  | ||||
|  | ||||
| ^^^^^^^^^^^^^^ | ||||
| Exporting Keys | ||||
| ^^^^^^^^^^^^^^ | ||||
|  | ||||
| You likely might want to export a public key, and that looks like this. Let's | ||||
| use the previous example and ask spack to export the key with uid "dinosaur." | ||||
| We will provide an output location (typically a `*.pub` file) and the name of | ||||
| the key. | ||||
|  | ||||
| .. code-block:: console | ||||
|  | ||||
|     $ spack gpg export dinosaur.pub dinosaur | ||||
|  | ||||
| You can then look at the created file, `dinosaur.pub`, to see the exported key. | ||||
| If you want to include the private key, then just add `--secret`: | ||||
|  | ||||
| .. code-block:: console | ||||
|  | ||||
|     $ spack gpg export --secret dinosaur.priv dinosaur | ||||
|  | ||||
| This will write the private key to the file `dinosaur.priv`.  | ||||
|  | ||||
| .. warning:: | ||||
|  | ||||
|     You should be very careful about exporting private keys. You likely would | ||||
|     only want to do this in the context of moving your spack installation to | ||||
|     a different server, and wanting to preserve keys for a buildcache. If you | ||||
|     are unsure about exporting, you can ask your local system administrator | ||||
|     or for help on an issue or the Spack slack. | ||||
|  | ||||
|  | ||||
| .. _cray-support: | ||||
|  | ||||
| ------------- | ||||
|   | ||||
| @@ -1558,7 +1558,9 @@ def push_keys(*mirrors, **kwargs): | ||||
|                 filename = fingerprint + '.pub' | ||||
| 
 | ||||
|                 export_target = os.path.join(prefix, filename) | ||||
|                 spack.util.gpg.export_keys(export_target, fingerprint) | ||||
| 
 | ||||
|                 # Export public keys (private is set to False) | ||||
|                 spack.util.gpg.export_keys(export_target, [fingerprint]) | ||||
| 
 | ||||
|                 # If mirror is local, the above export writes directly to the | ||||
|                 # mirror (export_target points directly to the mirror). | ||||
|   | ||||
| @@ -60,6 +60,9 @@ def setup_parser(subparser): | ||||
|                         default='0', help='when the key should expire') | ||||
|     create.add_argument('--export', metavar='DEST', type=str, | ||||
|                         help='export the public key to a file') | ||||
|     create.add_argument('--export-secret', metavar="DEST", type=str, | ||||
|                         dest="secret", | ||||
|                         help='export the private key to a file.') | ||||
|     create.set_defaults(func=gpg_create) | ||||
| 
 | ||||
|     list = subparsers.add_parser('list', help=gpg_list.__doc__) | ||||
| @@ -79,7 +82,9 @@ def setup_parser(subparser): | ||||
|                         help='where to export keys') | ||||
|     export.add_argument('keys', nargs='*', | ||||
|                         help='the keys to export; ' | ||||
|                              'all secret keys if unspecified') | ||||
|                              'all public keys if unspecified') | ||||
|     export.add_argument('--secret', action='store_true', | ||||
|                         help='export secret keys') | ||||
|     export.set_defaults(func=gpg_export) | ||||
| 
 | ||||
|     publish = subparsers.add_parser('publish', help=gpg_publish.__doc__) | ||||
| @@ -112,22 +117,28 @@ def setup_parser(subparser): | ||||
| 
 | ||||
| def gpg_create(args): | ||||
|     """create a new key""" | ||||
|     if args.export: | ||||
|     if args.export or args.secret: | ||||
|         old_sec_keys = spack.util.gpg.signing_keys() | ||||
| 
 | ||||
|     # Create the new key | ||||
|     spack.util.gpg.create(name=args.name, email=args.email, | ||||
|                           comment=args.comment, expires=args.expires) | ||||
|     if args.export: | ||||
|     if args.export or args.secret: | ||||
|         new_sec_keys = set(spack.util.gpg.signing_keys()) | ||||
|         new_keys = new_sec_keys.difference(old_sec_keys) | ||||
|         spack.util.gpg.export_keys(args.export, *new_keys) | ||||
| 
 | ||||
|     if args.export: | ||||
|         spack.util.gpg.export_keys(args.export, new_keys) | ||||
|     if args.secret: | ||||
|         spack.util.gpg.export_keys(args.secret, new_keys, secret=True) | ||||
| 
 | ||||
| 
 | ||||
| def gpg_export(args): | ||||
|     """export a secret key""" | ||||
|     """export a gpg key, optionally including secret key.""" | ||||
|     keys = args.keys | ||||
|     if not keys: | ||||
|         keys = spack.util.gpg.signing_keys() | ||||
|     spack.util.gpg.export_keys(args.location, *keys) | ||||
|     spack.util.gpg.export_keys(args.location, keys, args.secret) | ||||
| 
 | ||||
| 
 | ||||
| def gpg_list(args): | ||||
|   | ||||
| @@ -117,6 +117,20 @@ def test_gpg(tmpdir, mock_gnupghome): | ||||
|     export_path = tmpdir.join('export.testing.key') | ||||
|     gpg('export', str(export_path)) | ||||
| 
 | ||||
|     # Test exporting the private key | ||||
|     private_export_path = tmpdir.join('export-secret.testing.key') | ||||
|     gpg('export', '--secret', str(private_export_path)) | ||||
| 
 | ||||
|     # Ensure we exported the right content! | ||||
|     with open(str(private_export_path), 'r') as fd: | ||||
|         content = fd.read() | ||||
|     assert "BEGIN PGP PRIVATE KEY BLOCK" in content | ||||
| 
 | ||||
|     # and for the public key | ||||
|     with open(str(export_path), 'r') as fd: | ||||
|         content = fd.read() | ||||
|     assert "BEGIN PGP PUBLIC KEY BLOCK" in content | ||||
| 
 | ||||
|     # Create a second key for use in the tests. | ||||
|     gpg('create', | ||||
|         '--comment', 'Spack testing key', | ||||
|   | ||||
| @@ -336,10 +336,12 @@ def public_keys(self, *args): | ||||
|                       *args, output=str) | ||||
|         return parse_public_keys_output(output) | ||||
| 
 | ||||
|     def export_keys(self, location, *keys): | ||||
|         self('--batch', '--yes', | ||||
|              '--armor', '--export', | ||||
|              '--output', location, *keys) | ||||
|     def export_keys(self, location, keys, secret=False): | ||||
|         if secret: | ||||
|             self("--export-secret-keys", "--armor", "--output", location, *keys) | ||||
|         else: | ||||
|             self('--batch', '--yes', '--armor', '--export', '--output', | ||||
|                  location, *keys) | ||||
| 
 | ||||
|     def trust(self, keyfile): | ||||
|         self('--import', keyfile) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Vanessasaurus
					Vanessasaurus