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:
Vanessasaurus 2021-05-29 00:32:57 -06:00 committed by GitHub
parent f6febd2ef5
commit 6f534acbef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 117 additions and 14 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1010,7 +1010,7 @@ _spack_gpg_sign() {
_spack_gpg_create() {
if $list_options
then
SPACK_COMPREPLY="-h --help --comment --expires --export"
SPACK_COMPREPLY="-h --help --comment --expires --export --export-secret"
else
SPACK_COMPREPLY=""
fi
@ -1027,7 +1027,7 @@ _spack_gpg_init() {
_spack_gpg_export() {
if $list_options
then
SPACK_COMPREPLY="-h --help"
SPACK_COMPREPLY="-h --help --secret"
else
_keys
fi