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