Merge "Document signing key management"
This commit is contained in:
commit
b17b6f744b
@ -80,8 +80,440 @@ completely, the revocation certificate generated at key creation
|
||||
time should be used as a last resort.
|
||||
|
||||
|
||||
Management
|
||||
==========
|
||||
Key Management Process
|
||||
======================
|
||||
|
||||
As process is solidified, this section will be updated with specific
|
||||
commands and examples.
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
This is the content of the ``/root/signing.gnupg/gpg.conf`` file on
|
||||
our management bastion host::
|
||||
|
||||
# A basic gpg.conf using secure keyserver transport and some more
|
||||
# verbose display options. This configuration assumes you have
|
||||
# installed both the gnupg and gnupg-curl packages. Set your umask
|
||||
# to 077, create a /root/signing.gnupg directory and place this
|
||||
# configuration file in it.
|
||||
#
|
||||
# Retrieve and validate the HKPS key for the SKS keyservers this way:
|
||||
#
|
||||
# wget -P ~/signing.gnupg/ \
|
||||
# https://sks-keyservers.net/sks-keyservers.netCA.pem{,.asc}
|
||||
# gpg --homedir signing.gnupg --recv-key \
|
||||
# 0x94CBAFDD30345109561835AA0B7F8B60E3EDFAE3
|
||||
# gpg --homedir signing.gnupg --verify \
|
||||
# ~/signing.gnupg/sks-keyservers.netCA.pem{.asc,}
|
||||
|
||||
# Receive, send and search for keys in the SKS keyservers pool using
|
||||
# HKPS (OpenPGP HTTP Keyserver Protocol via TLS/SSL).
|
||||
keyserver hkps://hkps.pool.sks-keyservers.net
|
||||
|
||||
# Set the path to the public certificate for the
|
||||
# sks-keyservers.net CA used to verify connections to servers in
|
||||
# the pool above.
|
||||
keyserver-options ca-cert-file=/root/signing.gnupg/sks-keyservers.netCA.pem
|
||||
|
||||
# Ignore keyserver URLs specified in retrieved/refreshed keys
|
||||
# so they don't direct you to update from non-HKPS sources.
|
||||
keyserver-options no-honor-keyserver-url
|
||||
|
||||
# Display key IDs in a more accurate 16-digit hexidecimal format
|
||||
# and add 0x at the beginning for clarity.
|
||||
keyid-format 0xlong
|
||||
|
||||
# Display the calculated validity of user IDs when listing keys or
|
||||
# showing signatures.
|
||||
list-options show-uid-validity
|
||||
verify-options show-uid-validity
|
||||
|
||||
|
||||
Generation
|
||||
----------
|
||||
|
||||
Key generation should happen reasonably far in advance of expiration
|
||||
of the old key (at least a month), so as to provide ample time for a
|
||||
majority of our root sysadmins to attest to the key and provide
|
||||
warning to the rest of the community of the upcoming transition. Of
|
||||
course, if this is being done to replace a revoked key, this
|
||||
timeline should be accelerated as much as possible to provide
|
||||
continuity of service so use your best judgement on a balance of
|
||||
sufficient attestation and warning (same-day turnaround is
|
||||
preferred).
|
||||
|
||||
Make sure we start with a restrictive umask so that files and
|
||||
directories we write from this point forward are only accessible by
|
||||
the root user:
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
root@puppetmaster:~# umask 077
|
||||
|
||||
Now create a master key for the coming development cycle, taking
|
||||
mostly the GnuPG recommended default values. Set a validity period
|
||||
sufficient to last through the release process at the conclusion of
|
||||
the cycle. Use a sufficiently long, randomly-generated passphrase
|
||||
string (it's fine to reuse the one stored in our passwords list for
|
||||
earlier keys unless we know it to have been compromised):
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
root@puppetmaster:~# gpg --homedir signing.gnupg --gen-key
|
||||
gpg (GnuPG) 1.4.16; Copyright (C) 2013 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.
|
||||
|
||||
Please select what kind of key you want:
|
||||
(1) RSA and RSA (default)
|
||||
(2) DSA and Elgamal
|
||||
(3) DSA (sign only)
|
||||
(4) RSA (sign only)
|
||||
Your selection?
|
||||
RSA keys may be between 1024 and 4096 bits long.
|
||||
What keysize do you want? (2048)
|
||||
Requested keysize is 2048 bits
|
||||
Please specify how long the key should be valid.
|
||||
0 = key does not expire
|
||||
<n> = key expires in n days
|
||||
<n>w = key expires in n weeks
|
||||
<n>m = key expires in n months
|
||||
<n>y = key expires in n years
|
||||
Key is valid for? (0) 7m
|
||||
Key expires at Thu 02 Feb 2017 08:41:39 PM UTC
|
||||
Is this correct? (y/N) y
|
||||
|
||||
You need a user ID to identify your key; the software constructs the user ID
|
||||
from the Real Name, Comment and Email Address in this form:
|
||||
"Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"
|
||||
|
||||
Real name: OpenStack Infra
|
||||
Email address: infra-root@openstack.org
|
||||
Comment: Some Cycle
|
||||
You selected this USER-ID:
|
||||
"OpenStack Infra (Some Cycle) <infra-root@openstack.org>"
|
||||
|
||||
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
|
||||
You need a Passphrase to protect your secret key.
|
||||
|
||||
Enter passphrase: ********************************
|
||||
Repeat passphrase: ********************************
|
||||
|
||||
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: key 0x120D3C23C6D5584D marked as ultimately trusted
|
||||
public and secret key created and signed.
|
||||
|
||||
gpg: checking the trustdb
|
||||
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
|
||||
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
|
||||
gpg: next trustdb check due at 2017-02-02
|
||||
pub 2048R/0x120D3C23C6D5584D 2016-07-07 [expires: 2017-02-02]
|
||||
Key fingerprint = 7222 E5A0 5730 B767 0F93 035A 120D 3C23 C6D5 584D
|
||||
uid [ultimate] OpenStack Infra (Some Cycle) <infra-root@openstack.org>
|
||||
sub 2048R/0x1F215B56867C5D9A 2016-07-07 [expires: 2017-02-02]
|
||||
|
||||
Create a revocation certificate for the master key, for use in the
|
||||
case extreme case that this master key itself becomes inaccessible,
|
||||
for example because the decryption passphrase is lost (under any
|
||||
other circumstances, a revocation certificate with a more detailed
|
||||
description can be generated using the master key on an as-needed
|
||||
basis):
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
root@puppetmaster:~# gpg --homedir signing.gnupg --output \
|
||||
> signing.gnupg/revoke.asc --gen-revoke 0x120D3C23C6D5584D
|
||||
sec 2048R/0x120D3C23C6D5584D 2016-07-07 OpenStack Infra (Some Cycle) <infra-root@openstack.org>
|
||||
|
||||
Create a revocation certificate for this key? (y/N) y
|
||||
Please select the reason for the revocation:
|
||||
0 = No reason specified
|
||||
1 = Key has been compromised
|
||||
2 = Key is superseded
|
||||
3 = Key is no longer used
|
||||
Q = Cancel
|
||||
(Probably you want to select 1 here)
|
||||
Your decision? 1
|
||||
Enter an optional description; end it with an empty line:
|
||||
> This revocation is to be used in the event the key cannot be recovered.
|
||||
>
|
||||
Reason for revocation: Key has been compromised
|
||||
This revocation is to be used in the event the key cannot be recovered.
|
||||
Is this okay? (y/N) y
|
||||
|
||||
You need a passphrase to unlock the secret key for
|
||||
user: "OpenStack Infra (Some Cycle) <infra-root@openstack.org>"
|
||||
2048-bit RSA key, ID 0x120D3C23C6D5584D, created 2016-07-07
|
||||
|
||||
Enter passphrase: ********************************
|
||||
|
||||
ASCII armored output forced.
|
||||
Revocation certificate created.
|
||||
|
||||
Please move it to a medium which you can hide away; if Mallory gets
|
||||
access to this certificate he can use it to make your key unusable.
|
||||
It is smart to print this certificate and store it away, just in case
|
||||
your media become unreadable. But have some caution: The print system of
|
||||
your machine might store the data and make it available to others!
|
||||
|
||||
Use the interactive key editor to add a subkey constrained to
|
||||
signing purposes only. It does not need an expiration since it will
|
||||
be valid only for as long as its associated master key is valid:
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
root@puppetmaster:~# gpg --homedir signing.gnupg --edit-key 0x120D3C23C6D5584D
|
||||
gpg (GnuPG) 1.4.16; Copyright (C) 2013 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.
|
||||
|
||||
Secret key is available.
|
||||
|
||||
pub 2048R/0x120D3C23C6D5584D created: 2016-07-07 expires: 2017-02-02 usage: SC
|
||||
trust: ultimate validity: ultimate
|
||||
sub 2048R/0x1F215B56867C5D9A created: 2016-07-07 expires: 2017-02-02 usage: E
|
||||
[ultimate] (1). OpenStack Infra (Some Cycle) <infra-root@openstack.org>
|
||||
|
||||
gpg> addkey
|
||||
Key is protected.
|
||||
|
||||
You need a passphrase to unlock the secret key for
|
||||
user: "OpenStack Infra (Some Cycle) <infra-root@openstack.org>"
|
||||
2048-bit RSA key, ID 0x120D3C23C6D5584D, created 2016-07-07
|
||||
|
||||
Enter passphrase: ********************************
|
||||
|
||||
Please select what kind of key you want:
|
||||
(3) DSA (sign only)
|
||||
(4) RSA (sign only)
|
||||
(5) Elgamal (encrypt only)
|
||||
(6) RSA (encrypt only)
|
||||
Your selection? 4
|
||||
RSA keys may be between 1024 and 4096 bits long.
|
||||
What keysize do you want? (2048)
|
||||
Requested keysize is 2048 bits
|
||||
Please specify how long the key should be valid.
|
||||
0 = key does not expire
|
||||
<n> = key expires in n days
|
||||
<n>w = key expires in n weeks
|
||||
<n>m = key expires in n months
|
||||
<n>y = key expires in n years
|
||||
Key is valid for? (0)
|
||||
Key does not expire at all
|
||||
Is this correct? (y/N) y
|
||||
Really create? (y/N) y
|
||||
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.
|
||||
+++++
|
||||
........+++++
|
||||
|
||||
pub 2048R/0x120D3C23C6D5584D created: 2016-07-07 expires: 2017-02-02 usage: SC
|
||||
trust: ultimate validity: ultimate
|
||||
sub 2048R/0x1F215B56867C5D9A created: 2016-07-07 expires: 2017-02-02 usage: E
|
||||
sub 2048R/0xC0224DB5F541FB68 created: 2016-07-07 expires: never usage: S
|
||||
[ultimate] (1). OpenStack Infra (Some Cycle) <infra-root@openstack.org>
|
||||
|
||||
gpg> save
|
||||
|
||||
Now send the master key to the keyserver network. The subkeys are
|
||||
all submitted along with it, so do not need to be specified
|
||||
separately:
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
root@puppetmaster:~# gpg --homedir signing.gnupg --send-keys 0x120D3C23C6D5584D
|
||||
sending key 0x120D3C23C6D5584D to hkps server hkps.pool.sks-keyservers.net
|
||||
|
||||
The rest of this process shouldn't happen until we're ready for the
|
||||
signing system to transition to our new key. In a typical,
|
||||
non-emergency rotation this should not happen until release
|
||||
activities for the previous cycle have concluded so that we don't
|
||||
inadvertently sign their artifacts with the new key.
|
||||
|
||||
Create a new GnuPG keychain by exporting a copy of just the signing
|
||||
subkey to a file and then importing that (and only that) in a new
|
||||
GnuPG directory:
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
root@puppetmaster:~# mkdir temporary.gnupg
|
||||
root@puppetmaster:~# gpg --homedir signing.gnupg --output \
|
||||
> temporary.gnupg/secret-subkeys --export-secret-subkeys 0xC0224DB5F541FB68\!
|
||||
root@puppetmaster:~# gpg --homedir temporary.gnupg --import \
|
||||
> temporary.gnupg/secret-subkeys
|
||||
gpg: keyring `temporary.gnupg/secring.gpg' created
|
||||
gpg: keyring `temporary.gnupg/pubring.gpg' created
|
||||
gpg: key C6D5584D: secret key imported
|
||||
gpg: temporary.gnupg/trustdb.gpg: trustdb created
|
||||
gpg: key C6D5584D: public key "OpenStack Infra (Some Cycle) <infra-root@openstack.org>" imported
|
||||
gpg: Total number processed: 1
|
||||
gpg: imported: 1 (RSA: 1)
|
||||
gpg: secret keys read: 1
|
||||
gpg: secret keys imported: 1
|
||||
|
||||
So that our CI jobs will be able to make use of this subkey without
|
||||
interactively supplying a passphrase, the old passphrase (exported
|
||||
from the master key) must be reset to an empty string in the new
|
||||
temporary copy. This is again done using an interactive key editor
|
||||
session:
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
root@puppetmaster:~# gpg --homedir temporary.gnupg --edit-key 0xC0224DB5F541FB68
|
||||
gpg (GnuPG) 1.4.16; Copyright (C) 2013 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.
|
||||
|
||||
Secret key is available.
|
||||
|
||||
pub 2048R/C6D5584D created: 2016-07-07 expires: 2017-02-02 usage: SC
|
||||
trust: unknown validity: unknown
|
||||
sub 2048R/F541FB68 created: 2016-07-07 expires: never usage: S
|
||||
[ unknown] (1). OpenStack Infra (Some Cycle) <infra-root@openstack.org>
|
||||
|
||||
gpg> passwd
|
||||
Secret parts of primary key are not available.
|
||||
|
||||
You need a passphrase to unlock the secret key for
|
||||
user: "OpenStack Infra (Some Cycle) <infra-root@openstack.org>"
|
||||
2048-bit RSA key, ID F541FB68, created 2016-07-07
|
||||
|
||||
Enter passphrase: ********************************
|
||||
|
||||
Enter the new passphrase for this secret key.
|
||||
|
||||
Enter passphrase:
|
||||
Repeat passphrase:
|
||||
|
||||
You don't want a passphrase - this is probably a *bad* idea!
|
||||
|
||||
Do you really want to do this? (y/N) y
|
||||
|
||||
gpg> save
|
||||
|
||||
This leaves us with a temporary keyring containing only an
|
||||
unencrypted copy of the signing subkey. Push this into private hiera
|
||||
so that it will be installed onto the signing system by our
|
||||
configuration management:
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
root@puppetmaster:~# /opt/system-config/production/tools/hieraedit.py --yaml \
|
||||
> /opt/system-config/hieradata/production/group/signing.yaml -f \
|
||||
> temporary.gnupg/pubring.gpg pubring
|
||||
root@puppetmaster:~# /opt/system-config/production/tools/hieraedit.py --yaml \
|
||||
> /opt/system-config/hieradata/production/group/signing.yaml -f \
|
||||
> temporary.gnupg/secring.gpg secring
|
||||
|
||||
Finally, do your best to securely remove the temporary copy of the
|
||||
unencrypted signing subkey and any associated files:
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
root@puppetmaster:~# shred temporary.gnupg/*
|
||||
root@puppetmaster:~# rm -rf temporary.gnupg
|
||||
|
||||
|
||||
Attestation
|
||||
-----------
|
||||
|
||||
We need a majority (if not all) of our current root sysadmins to
|
||||
verify and attest to the authenticity of our artifact signing key,
|
||||
because it represents a system maintained by our team rather than
|
||||
representing some particular individual and so anyone else attesting
|
||||
to this key can really only do so transitively through us. This
|
||||
should be done soon after a new key is minted (preferably the same
|
||||
week) so that others in the community who wish to extend the web of
|
||||
trust around the key based on our attestations (for example, release
|
||||
managers or team leads) have an opportunity to do so before it's put
|
||||
into production.
|
||||
|
||||
Start by logging into the management bastion and examining the
|
||||
fingerprint of the key as it exists on disk:
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
me@puppetmaster:~$ sudo gpg --homedir /root/signing.gnupg --fingerprint \
|
||||
> --list-keys "OpenStack Infra (Some Cycle)"
|
||||
pub 2048R/0x120D3C23C6D5584D 2016-07-07 [expires: 2017-02-02]
|
||||
Key fingerprint = 120D 3C23 C6D5 584D 6FC2 4646 64DB B05A CC5E 7C28
|
||||
uid [ultimate] OpenStack Infra (Some Cycle) <infra-root@openstack.org>
|
||||
sub 2048R/0x1F215B56867C5D9A 2016-07-07 [expires: 2017-02-02]
|
||||
sub 2048R/0xC0224DB5F541FB68 2016-07-07
|
||||
|
||||
Now on your own system where your OpenPGP key resides, retrieve the
|
||||
key, compare the fingerprint from above, and if they match, sign it
|
||||
and push the signature back to the keyserver network:
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
me@home:~$ gpg2 --recv-keys 0x120D3C23C6D5584D
|
||||
gpg: requesting key 0x120D3C23C6D5584D from hkps server hkps.pool.sks-keyservers.net
|
||||
gpg: key 0x120D3C23C6D5584D: public key "OpenStack Infra (Some Cycle) <infra-root@openstack.org>" imported
|
||||
gpg: 3 marginal(s) needed, 1 complete(s) needed, classic trust model
|
||||
gpg: depth: 0 valid: 3 signed: 31 trust: 0-, 0q, 0n, 0m, 0f, 3u
|
||||
gpg: depth: 1 valid: 31 signed: 46 trust: 30-, 0q, 0n, 0m, 1f, 0u
|
||||
gpg: next trustdb check due at 2016-11-30
|
||||
gpg: Total number processed: 1
|
||||
gpg: imported: 1 (RSA: 1)
|
||||
me@home:~$ gpg2 --fingerprint 0x120D3C23C6D5584D
|
||||
pub 2048R/0x120D3C23C6D5584D 2016-07-07 [expires: 2017-02-02]
|
||||
Key fingerprint = 120D 3C23 C6D5 584D 6FC2 4646 64DB B05A CC5E 7C28
|
||||
uid [ full ] OpenStack Infra (Some Cycle) <infra-root@openstack.org>
|
||||
sub 2048R/0x1F215B56867C5D9A 2016-07-07 [expires: 2017-02-02]
|
||||
sub 2048R/0xC0224DB5F541FB68 2016-07-07
|
||||
me@home:~$ gpg2 --sign-key 0x120D3C23C6D5584D
|
||||
|
||||
pub 2048R/0x120D3C23C6D5584D created: 2016-07-07 expires: 2017-02-02 usage: SC
|
||||
trust: unknown validity: full
|
||||
sub 2048R/0x1F215B56867C5D9A created: 2016-07-07 expires: 2017-02-02 usage: E
|
||||
sub 2048R/0xC0224DB5F541FB68 created: 2016-07-07 expires: never usage: S
|
||||
[ full ] (1). OpenStack Infra (Some Cycle) <infra-root@openstack.org>
|
||||
|
||||
|
||||
pub 2048R/0x120D3C23C6D5584D created: 2016-07-07 expires: 2017-02-02 usage: SC
|
||||
trust: unknown validity: full
|
||||
Primary key fingerprint: 120D 3C23 C6D5 584D 6FC2 4646 64DB B05A CC5E 7C28
|
||||
|
||||
OpenStack Infra (Some Cycle) <infra-root@openstack.org>
|
||||
|
||||
This key is due to expire on 2017-02-02.
|
||||
Are you sure that you want to sign this key with your
|
||||
key "My Name <me@example.org>" (0xAB54A98CEB1F0AD2)
|
||||
|
||||
Really sign? (y/N) y
|
||||
|
||||
+-----------------------------------------------------------------------+
|
||||
| Please enter the passphrase to unlock the secret key for the OpenPGP |
|
||||
| certificate: |
|
||||
| "My Name <me@example.org>" |
|
||||
| 2048-bit RSA key, ID 0xAB54A98CEB1F0AD2, |
|
||||
| created 2008-09-10. |
|
||||
| |
|
||||
| |
|
||||
| Passphrase **********************____________________________________ |
|
||||
| |
|
||||
| <OK> <Cancel> |
|
||||
+-----------------------------------------------------------------------+
|
||||
|
||||
me@home:~$ gpg2 --send-keys 0x120D3C23C6D5584D
|
||||
gpg: sending key 0x120D3C23C6D5584D to hkps server hkps.pool.sks-keyservers.net
|
||||
|
||||
Also, please retrieve a copy of the
|
||||
``/root/signing.gnupg/revoke.asc`` fallback revocation certificate
|
||||
from the management bastion and keep it stashed somewhere secure,
|
||||
for emergency use in the (hopefully very unlikely) event that our
|
||||
OpenPGP master private key is completely lost to us (for example, if
|
||||
we lose the file containing its decryption passphrase and all
|
||||
backups thereof).
|
||||
|
Loading…
x
Reference in New Issue
Block a user