CINXE.COM

Signing System — OpenDev System Documentation documentation

<!DOCTYPE html> <html lang="en" data-content_root="./"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Signing System &#8212; OpenDev System Documentation documentation</title> <link rel="stylesheet" type="text/css" href="_static/pygments.css?v=03e43079" /> <link rel="stylesheet" type="text/css" href="_static/basic.css?v=b08954a9" /> <link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=27fed22d" /> <script src="_static/documentation_options.js?v=5929fcd5"></script> <script src="_static/doctools.js?v=9bcbadda"></script> <script src="_static/sphinx_highlight.js?v=dc90522c"></script> <link rel="index" title="Index" href="genindex.html" /> <link rel="search" title="Search" href="search.html" /> <link rel="next" title="GitHub" href="github.html" /> <link rel="prev" title="Code Search" href="codesearch.html" /> <link rel="stylesheet" href="_static/custom.css" type="text/css" /> </head><body> <div class="document"> <div class="documentwrapper"> <div class="bodywrapper"> <div class="body" role="main"> <section id="signing-system"> <span id="signing"></span><h1>Signing System<a class="headerlink" href="#signing-system" title="Link to this heading">露</a></h1> <p>Our standard signing automation model leverages an OpenPGP signing subkey, encrypted as a Zuul secret, to create detached signatures for release artifacts (tarballs, wheels, et cetera) and to sign and push Git tags as part of managed release automation. For OpenStack鈥檚 releases, the master key corresponding to this subkey is replaced near the start of each new development cycle and set to expire soon after the cycle is scheduled to conclude (with enough overlap to allow for graceful replacement).</p> <section id="at-a-glance"> <h2>At a Glance<a class="headerlink" href="#at-a-glance" title="Link to this heading">露</a></h2> <dl class="field-list simple"> <dt class="field-odd">Secrets<span class="colon">:</span></dt> <dd class="field-odd"><ul class="simple"> <li><p><a class="reference external" href="https://opendev.org/openstack/project-config/src/branch/master/zuul.d/secrets.yaml">gpg_key</a></p></li> </ul> </dd> <dt class="field-even">Roles<span class="colon">:</span></dt> <dd class="field-even"><ul class="simple"> <li><p><a class="reference external" href="https://docs.openstack.org/infra/zuul-jobs/general-roles.html#role-add-gpgkey">add-gpgkey</a></p></li> <li><p><a class="reference external" href="https://docs.openstack.org/infra/zuul-jobs/general-roles.html#role-sign-artifacts">sign-artifacts</a></p></li> </ul> </dd> </dl> </section> <section id="key-management-overview"> <h2>Key Management Overview<a class="headerlink" href="#key-management-overview" title="Link to this heading">露</a></h2> <p>The signing system is implemented as a set of Zuul jobs; these utilize the signing subkey via an encrypted Zuul secret imported into a job鈥檚 <code class="docutils literal notranslate"><span class="pre">~/.gnupg/secring.gpg</span></code> at runtime. It鈥檚 used by jobs to create detached signatures of release artifacts and to sign Git tags in release management automation.</p> <section id="storage"> <h3>Storage<a class="headerlink" href="#storage" title="Link to this heading">露</a></h3> <p>While the signing subkey is installed unencrypted on some job nodes, so that it can be used unattended by job automation, for OpenStack the corresponding master key is kept symmetrically encrypted in the root home directory of the Infra systems management bastion. At the time of key creation a revocation certificate is also generated, for which sysadmins are encouraged to retrieve and keep local copies in case control over or access to the original master key is lost.</p> </section> <section id="rotation"> <h3>Rotation<a class="headerlink" href="#rotation" title="Link to this heading">露</a></h3> <p>For OpenStack, the master key is rotated at the start of each development cycle (usually shortly after cycle-trailing deliverables are released), signed by the previous key before being put into service, and has an expiration date set for at least a month after the end of the targeted development cycle (or best guess, often longer for safety). New key fingerprints are also submitted to the openstack/releases repository, for publication on the releases.openstack.org Web site.</p> </section> <section id="revocation"> <h3>Revocation<a class="headerlink" href="#revocation" title="Link to this heading">露</a></h3> <p>Under normal circumstances, keys should be allowed to expire gracefully. If the key is compromised but still accessible, a revocation certificate can be generated and published to the key network at that time. If access to the private key is lost completely, the revocation certificate generated at key creation time should be used as a last resort.</p> </section> </section> <section id="key-management-process"> <h2>Key Management Process<a class="headerlink" href="#key-management-process" title="Link to this heading">露</a></h2> <section id="configuration"> <h3>Configuration<a class="headerlink" href="#configuration" title="Link to this heading">露</a></h3> <p>This is the content of the <code class="docutils literal notranslate"><span class="pre">/root/signing.gnupg/gpg.conf</span></code> file on our management bastion host:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># A basic gpg.conf using secure keyserver transport and some more</span> <span class="c1"># verbose display options. This configuration assumes you have</span> <span class="c1"># installed both the gnupg and gnupg-curl packages. Set your umask</span> <span class="c1"># to 077, create a /root/signing.gnupg directory and place this</span> <span class="c1"># configuration file in it.</span> <span class="c1"># Receive, send and search for keys in the SKS keyservers pool using</span> <span class="c1"># HKPS (OpenPGP HTTP Keyserver Protocol via TLS/SSL).</span> <span class="n">keyserver</span> <span class="n">hkps</span><span class="p">:</span><span class="o">//</span><span class="n">keys</span><span class="o">.</span><span class="n">openpgp</span><span class="o">.</span><span class="n">org</span> <span class="c1"># Display key IDs in a more accurate 16-digit hexidecimal format</span> <span class="c1"># and add 0x at the beginning for clarity.</span> <span class="n">keyid</span><span class="o">-</span><span class="nb">format</span> <span class="mi">0</span><span class="n">xlong</span> <span class="c1"># Display the calculated validity of user IDs when listing keys or</span> <span class="c1"># showing signatures.</span> <span class="nb">list</span><span class="o">-</span><span class="n">options</span> <span class="n">show</span><span class="o">-</span><span class="n">uid</span><span class="o">-</span><span class="n">validity</span> <span class="n">verify</span><span class="o">-</span><span class="n">options</span> <span class="n">show</span><span class="o">-</span><span class="n">uid</span><span class="o">-</span><span class="n">validity</span> </pre></div> </div> </section> <section id="generation"> <h3>Generation<a class="headerlink" href="#generation" title="Link to this heading">露</a></h3> <p>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:</p> <div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">root@bridge:~# </span><span class="nb">umask</span><span class="w"> </span><span class="m">077</span> </pre></div> </div> <p>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鈥檚 fine to reuse the one stored in our passwords list for earlier keys unless we know it to have been compromised):</p> <div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">root@bridge:~# </span>gpg<span class="w"> </span>--homedir<span class="w"> </span>signing.gnupg<span class="w"> </span>--full-generate-key<span class="w"> </span>--expert <span class="go">gpg (GnuPG) 2.2.4; Copyright (C) 2017 Free Software Foundation, Inc.</span> <span class="go">This is free software: you are free to change and redistribute it.</span> <span class="go">There is NO WARRANTY, to the extent permitted by law.</span> <span class="go">Please select what kind of key you want:</span> <span class="go"> (1) RSA and RSA (default)</span> <span class="go"> (2) DSA and Elgamal</span> <span class="go"> (3) DSA (sign only)</span> <span class="go"> (4) RSA (sign only)</span> <span class="go"> (7) DSA (set your own capabilities)</span> <span class="go"> (8) RSA (set your own capabilities)</span> <span class="go"> (9) ECC and ECC</span> <span class="go"> (10) ECC (sign only)</span> <span class="go"> (11) ECC (set your own capabilities)</span> <span class="go"> (13) Existing key</span> <span class="go">Your selection? 9</span> <span class="go">Please select which elliptic curve you want:</span> <span class="go"> (1) Curve 25519</span> <span class="go"> (3) NIST P-256</span> <span class="go"> (4) NIST P-384</span> <span class="go"> (5) NIST P-521</span> <span class="go"> (6) Brainpool P-256</span> <span class="go"> (7) Brainpool P-384</span> <span class="go"> (8) Brainpool P-512</span> <span class="go"> (9) secp256k1</span> <span class="go">Your selection? 1</span> <span class="go">Please specify how long the key should be valid.</span> <span class="go"> 0 = key does not expire</span> <span class="go"> &lt;n&gt; = key expires in n days</span> <span class="go"> &lt;n&gt;w = key expires in n weeks</span> <span class="go"> &lt;n&gt;m = key expires in n months</span> <span class="go"> &lt;n&gt;y = key expires in n years</span> <span class="go">Key is valid for? (0) 9m</span> <span class="go">Key expires at Thu 02 Feb 2017 08:41:39 PM UTC</span> <span class="go">Is this correct? (y/N) y</span> <span class="go">You need a user ID to identify your key; the software constructs the user ID</span> <span class="go">from the Real Name, Comment and Email Address in this form:</span> <span class="go"> &quot;Heinrich Heine (Der Dichter) &lt;heinrichh@duesseldorf.de&gt;&quot;</span> <span class="go">Real name: OpenStack Infra</span> <span class="go">Email address: infra-root@openstack.org</span> <span class="go">Comment: Some Cycle</span> <span class="go">You selected this USER-ID:</span> <span class="go"> &quot;OpenStack Infra (Some Cycle) &lt;infra-root@openstack.org&gt;&quot;</span> <span class="go">Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o</span> <span class="go">You need a Passphrase to protect your secret key.</span> <span class="go">Enter passphrase: ********************************</span> <span class="go">Repeat passphrase: ********************************</span> <span class="go">We need to generate a lot of random bytes. It is a good idea to perform</span> <span class="go">some other action (type on the keyboard, move the mouse, utilize the</span> <span class="go">disks) during the prime generation; this gives the random number</span> <span class="go">generator a better chance to gain enough entropy.</span> <span class="go">.+++++</span> <span class="go">......+++++</span> <span class="go">We need to generate a lot of random bytes. It is a good idea to perform</span> <span class="go">some other action (type on the keyboard, move the mouse, utilize the</span> <span class="go">disks) during the prime generation; this gives the random number</span> <span class="go">generator a better chance to gain enough entropy.</span> <span class="go">.+++++</span> <span class="go">+++++</span> <span class="go">gpg: key 0x120D3C23C6D5584D marked as ultimately trusted</span> <span class="go">gpg: revocation certificate stored as &#39;/root/signing.gnupg/openpgp-revocs.d/7222E5A05730B7670F93035A120D3C23C6D5584D.rev&#39;</span> <span class="go">public and secret key created and signed.</span> <span class="go">pub ed25519/0x120D3C23C6D5584D 2016-07-07 [expires: 2017-02-02]</span> <span class="go"> 7222E5A05730B7670F93035A120D3C23C6D5584D</span> <span class="go">uid OpenStack Infra (Some Cycle) &lt;infra-root@openstack.org&gt;</span> <span class="go">sub cv25519/0x1F215B56867C5D9A 2016-07-07 [E] [expires: 2017-02-02]</span> </pre></div> </div> <p>Save the 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). Replace <code class="docutils literal notranslate"><span class="pre">some</span></code> in the output filename with the lower-cased cycle name:</p> <div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">root@bridge:~# </span>mv<span class="w"> </span><span class="se">\</span> <span class="gp">&gt; </span>signing.gnupg/openpgp-revocs.d/7222E5A05730B7670F93035A120D3C23C6D5584D.rev <span class="go">&gt; signing.gnupg/some.revoke.asc</span> </pre></div> </div> <p>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:</p> <div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">root@bridge:~# </span>gpg<span class="w"> </span>--homedir<span class="w"> </span>signing.gnupg<span class="w"> </span>--expert<span class="w"> </span>--edit-key<span class="w"> </span>0x120D3C23C6D5584D <span class="go">gpg (GnuPG) 2.2.4; Copyright (C) 2017 Free Software Foundation, Inc.</span> <span class="go">This is free software: you are free to change and redistribute it.</span> <span class="go">There is NO WARRANTY, to the extent permitted by law.</span> <span class="go">Secret key is available.</span> <span class="go">sec ed25519/0x120D3C23C6D5584D</span> <span class="go"> created: 2016-07-07 expires: 2017-02-02 usage: SC</span> <span class="go"> trust: ultimate validity: ultimate</span> <span class="go">ssb cv25519/0x1F215B56867C5D9A</span> <span class="go"> created: 2016-07-07 expires: 2017-02-02 usage: E</span> <span class="go">[ultimate] (1). OpenStack Infra (Some Cycle) &lt;infra-root@openstack.org&gt;</span> <span class="go">gpg&gt; addkey</span> <span class="go">Please select what kind of key you want:</span> <span class="go"> (3) DSA (sign only)</span> <span class="go"> (4) RSA (sign only)</span> <span class="go"> (5) Elgamal (encrypt only)</span> <span class="go"> (6) RSA (encrypt only)</span> <span class="go"> (7) DSA (set your own capabilities)</span> <span class="go"> (8) RSA (set your own capabilities)</span> <span class="go"> (10) ECC (sign only)</span> <span class="go"> (11) ECC (set your own capabilities)</span> <span class="go"> (12) ECC (encrypt only)</span> <span class="go"> (13) Existing key</span> <span class="go">Your selection? 10</span> <span class="go">Please select which elliptic curve you want:</span> <span class="go"> (1) Curve 25519</span> <span class="go"> (3) NIST P-256</span> <span class="go"> (4) NIST P-384</span> <span class="go"> (5) NIST P-521</span> <span class="go"> (6) Brainpool P-256</span> <span class="go"> (7) Brainpool P-384</span> <span class="go"> (8) Brainpool P-512</span> <span class="go"> (9) secp256k1</span> <span class="go">Your selection? 1</span> <span class="go">Please specify how long the key should be valid.</span> <span class="go"> 0 = key does not expire</span> <span class="go"> &lt;n&gt; = key expires in n days</span> <span class="go"> &lt;n&gt;w = key expires in n weeks</span> <span class="go"> &lt;n&gt;m = key expires in n months</span> <span class="go"> &lt;n&gt;y = key expires in n years</span> <span class="go">Key is valid for? (0)</span> <span class="go">Key does not expire at all</span> <span class="go">Is this correct? (y/N) y</span> <span class="go">Really create? (y/N) y</span> <span class="go">Key is protected.</span> <span class="go">You need a passphrase to unlock the secret key for</span> <span class="go">user: &quot;OpenStack Infra (Some Cycle) &lt;infra-root@openstack.org&gt;&quot;</span> <span class="go">ID 0x120D3C23C6D5584D, created 2016-07-07</span> <span class="go">Enter passphrase: ********************************</span> <span class="go">We need to generate a lot of random bytes. It is a good idea to perform</span> <span class="go">some other action (type on the keyboard, move the mouse, utilize the</span> <span class="go">disks) during the prime generation; this gives the random number</span> <span class="go">generator a better chance to gain enough entropy.</span> <span class="go">+++++</span> <span class="go">........+++++</span> <span class="go">sec ed25519/0x120D3C23C6D5584D</span> <span class="go"> created: 2016-07-07 expires: 2017-02-02 usage: SC</span> <span class="go"> trust: ultimate validity: ultimate</span> <span class="go">ssb cv25519/0x1F215B56867C5D9A</span> <span class="go"> created: 2016-07-07 expires: 2017-02-02 usage: E</span> <span class="go">ssb ed25519/0xC0224DB5F541FB68</span> <span class="go"> created: 2016-07-07 expires: never usage: S</span> <span class="go">[ultimate] (1). OpenStack Infra (Some Cycle) &lt;infra-root@openstack.org&gt;</span> <span class="go">gpg&gt; save</span> </pre></div> </div> <p>Next, sign the new master key with the key from the previous cycle (specified with the <code class="docutils literal notranslate"><span class="pre">--default-key</span></code> option). This proves that the new key was created by a party with access to its predecessor, so provides some added assurance of its validity:</p> <div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">root@bridge:~# </span>gpg<span class="w"> </span>--homedir<span class="w"> </span>signing.gnupg<span class="w"> </span>--default-key<span class="w"> </span>0x70CA2E45DF30B1B8<span class="w"> </span>--sign-key<span class="w"> </span>0x120D3C23C6D5584D <span class="go">This key is due to expire on 2017-02-02.</span> <span class="go">Are you sure that you want to sign this key with your</span> <span class="go">key &quot;OpenStack Infra (Previous Cycle) &lt;infra-root@openstack.org&gt;&quot; (0x70CA2E45DF30B1B8)</span> <span class="go">Really sign? (y/N) y</span> <span class="go">You need a passphrase to unlock the secret key for</span> <span class="go">user: &quot;OpenStack Infra (Previous Cycle) &lt;infra-root@openstack.org&gt;&quot;</span> <span class="go">ID 0x70CA2E45DF30B1B8, created 2016-11-03</span> <span class="go">Enter passphrase: ********************************</span> </pre></div> </div> <p>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:</p> <div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">root@bridge:~# </span>gpg<span class="w"> </span>--homedir<span class="w"> </span>signing.gnupg<span class="w"> </span>--send-keys<span class="w"> </span>0x120D3C23C6D5584D <span class="go">gpg: sending key 0x120D3C23C6D5584D to hkps://keys.openpgp.org</span> </pre></div> </div> <p>Check the infra-root inbox (the address associated with the key) for a notification about the key upload to keys.openpgp.org and follow the URL within it. Once there, click the button to send a verification message. Now check the inbox again and follow the URL provided in the new message which should arrive. Once that鈥檚 done, the key will be searchable on the keyserver.</p> <p>The rest of this process shouldn鈥檛 happen until we鈥檙e 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鈥檛 inadvertently sign their artifacts with the new key.</p> <p>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:</p> <div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">root@bridge:~# </span><span class="nb">umask</span><span class="w"> </span><span class="m">077</span> <span class="gp">root@bridge:~# </span>mkdir<span class="w"> </span>temporary.gnupg <span class="gp">root@bridge:~# </span>gpg<span class="w"> </span>--homedir<span class="w"> </span>signing.gnupg<span class="w"> </span><span class="se">\</span> <span class="gp">&gt; </span>--output<span class="w"> </span>temporary.gnupg/secret-subkeys<span class="w"> </span><span class="se">\</span> <span class="gp">&gt; </span>--export-secret-subkeys<span class="w"> </span>0xC0224DB5F541FB68<span class="se">\!</span> <span class="gp">root@bridge:~# </span>gpg<span class="w"> </span>--homedir<span class="w"> </span>temporary.gnupg<span class="w"> </span><span class="se">\</span> <span class="gp">&gt; </span>--import<span class="w"> </span>temporary.gnupg/secret-subkeys <span class="go">gpg: keyring `temporary.gnupg/secring.gpg&#39; created</span> <span class="go">gpg: keyring `temporary.gnupg/pubring.gpg&#39; created</span> <span class="go">gpg: key C6D5584D: secret key imported</span> <span class="go">gpg: temporary.gnupg/trustdb.gpg: trustdb created</span> <span class="go">gpg: key C6D5584D: public key &quot;OpenStack Infra (Some Cycle) &lt;infra-root@openstack.org&gt;&quot; imported</span> <span class="go">gpg: Total number processed: 1</span> <span class="go">gpg: imported: 1</span> <span class="go">gpg: secret keys read: 1</span> <span class="go">gpg: secret keys imported: 1</span> </pre></div> </div> <p>Check that the exported version does not contain a usable primary secret key by listing all secret keys and looking for a <code class="docutils literal notranslate"><span class="pre">sec#</span></code> in front of it instead of just <code class="docutils literal notranslate"><span class="pre">sec</span></code>:</p> <div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">root@bridge:~# </span>gpg<span class="w"> </span>--homedir<span class="w"> </span>temporary.gnupg<span class="w"> </span>--list-secret-keys <span class="go">/root/temporary.gnupg/pubring.kbx</span> <span class="go">---------------------------------</span> <span class="go">sec# ed25519 2016-07-07 [SC] [expires: 2017-02-02]</span> <span class="go"> 120D3C23C6D5584D</span> <span class="go">uid [unknown] OpenStack Infra (Some Cycle) &lt;infra-root@openstack.org&gt;</span> <span class="go">ssb ed25519 2016-07-07 [S]</span> </pre></div> </div> <p>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. Here we override the default pinentry mode to loopback as a workaround for other pinentry frontends refusing to accept an empty passphrase (unfortunately the prompting and feedback from the loopback pinentry leaves something to be desired). Note that the first password prompt will be for the original key鈥檚 password, but the second password prompt should be left blank thus removing that password. This is again done using an interactive key editor session:</p> <div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">root@bridge:~# </span>gpg<span class="w"> </span>--homedir<span class="w"> </span>temporary.gnupg<span class="w"> </span>--pinentry-mode<span class="w"> </span>loopback<span class="w"> </span><span class="se">\</span> <span class="gp">&gt; </span>--edit-key<span class="w"> </span>0xC0224DB5F541FB68 <span class="go">gpg (GnuPG) 2.2.4; Copyright (C) 2017 Free Software Foundation, Inc.</span> <span class="go">This is free software: you are free to change and redistribute it.</span> <span class="go">There is NO WARRANTY, to the extent permitted by law.</span> <span class="go">Secret subkeys are available.</span> <span class="go">pub ed25519/0x120D3C23C6D5584D created: 2016-07-07 expires: 2017-02-02 usage: SC</span> <span class="go"> trust: unknown validity: unknown</span> <span class="go">sub ed25519/0xC0224DB5F541FB68 created: 2016-07-07 expires: never usage: S</span> <span class="go">[ unknown] (1). OpenStack Infra (Some Cycle) &lt;infra-root@openstack.org&gt;</span> <span class="go">gpg&gt; passwd</span> <span class="go">gpg: key 120D3C23C6D5584D/120D3C23C6D5584D: error changing passphrase: No secret key</span> <span class="go">Enter passphrase: ********************************</span> <span class="go">Enter passphrase:</span> <span class="go">gpg&gt; save</span> <span class="go">Key not changed so no update needed.</span> </pre></div> </div> <p>Test the subkey can be used without a passphrase:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">root</span><span class="nd">@bridge</span><span class="p">:</span><span class="o">~</span><span class="c1"># echo foo | gpg --homedir temporary.gnupg --sign --armor</span> <span class="o">-----</span><span class="n">BEGIN</span> <span class="n">PGP</span> <span class="n">MESSAGE</span><span class="o">-----</span> <span class="n">SR43vh1iK66BbmlsONWxII74fIPEDHDeCqVnkzxdhleDf7DOd9HhYmI8WNOKtTIU</span> <span class="mi">7</span><span class="n">hcy6cYqHBjEgVr5oViNiveiwGsKlOUhh8x1eYDIxEEoGQEHDJDKq9YOMMjRdsO8</span> <span class="n">fOw0TD</span><span class="o">/</span><span class="mi">1</span><span class="n">r8Lmi8QLkCfGvFdrSY6EoCHqCMx3</span><span class="o">+</span><span class="n">JmGUD</span><span class="o">+</span><span class="n">iFGp2rCOucw</span><span class="o">==</span> <span class="o">=</span><span class="n">LxND</span> <span class="o">-----</span><span class="n">END</span> <span class="n">PGP</span> <span class="n">MESSAGE</span><span class="o">-----</span> </pre></div> </div> <p>This leaves us with a temporary keyring containing only an unencrypted copy of the signing subkey. Export this keyring so that we can add it as a secret to Zuul for use by release jobs.</p> <div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">root@bridge:~# </span>gpg<span class="w"> </span>--homedir<span class="w"> </span>temporary.gnupg<span class="w"> </span><span class="se">\</span> <span class="gp">&gt; </span>--output<span class="w"> </span>temporary.gnupg/for-zuul<span class="w"> </span>--armor<span class="w"> </span><span class="se">\</span> <span class="gp">&gt; </span>--export-secret-subkeys<span class="w"> </span>0xC0224DB5F541FB68<span class="se">\!</span> <span class="gp">root@bridge:~# </span>wget<span class="w"> </span>https://opendev.org/zuul/zuul/raw/branch/master/tools/encrypt_secret.py <span class="gp">root@bridge:~# </span>python3<span class="w"> </span>encrypt_secret.py<span class="w"> </span>--tenant<span class="w"> </span>openstack<span class="w"> </span><span class="se">\</span> <span class="gp">&gt; </span>--infile<span class="w"> </span>temporary.gnupg/for-zuul<span class="w"> </span>--outfile<span class="w"> </span>temporary.gnupg/zuul.yaml<span class="w"> </span><span class="se">\</span> <span class="gp">&gt; </span>https://zuul.opendev.org<span class="w"> </span>openstack/project-config <span class="go">writing RSA key</span> <span class="go">Public key length: 4096 bits (512 bytes)</span> <span class="go">Max plaintext length per chunk: 470 bytes</span> <span class="go">Input plaintext length: 1490 bytes</span> <span class="go">Number of chunks: 4</span> </pre></div> </div> <p>Copy <code class="docutils literal notranslate"><span class="pre">temporary.gnupg/zuul.yaml</span></code> to your workstation and make a commit to <code class="docutils literal notranslate"><span class="pre">zuul.d/secrets.yaml</span></code> file in the <code class="docutils literal notranslate"><span class="pre">openstack/project-config</span></code> repo to update the <code class="docutils literal notranslate"><span class="pre">gpg_key</span></code> secret with its contents. Be sure to replace <code class="docutils literal notranslate"><span class="pre">&lt;name&gt;</span></code> and <code class="docutils literal notranslate"><span class="pre">&lt;fieldname&gt;</span></code> as appropriate.</p> <p>Safely clean up, doing your best to securely remove the temporary copy of the unencrypted signing subkey and any associated files:</p> <div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">root@bridge:~# </span>find<span class="w"> </span>temporary.gnupg/<span class="w"> </span>-type<span class="w"> </span>f<span class="w"> </span>-exec<span class="w"> </span>shred<span class="w"> </span><span class="o">{}</span><span class="w"> </span><span class="se">\;</span> <span class="gp">root@bridge:~# </span>rm<span class="w"> </span>-rf<span class="w"> </span>temporary.gnupg<span class="w"> </span>encrypt_secret.py </pre></div> </div> <p>To document this transition, export a minimal text version of the public master key:</p> <div class="highlight-shell-session notranslate"><div class="highlight"><pre><span></span><span class="gp">root@bridge:~# </span><span class="o">(</span><span class="w"> </span>gpg<span class="w"> </span>--fingerprint<span class="w"> </span>--list-sigs<span class="w"> </span><span class="se">\</span> <span class="gp">&gt; </span>0x120d3c23c6d5584d6fc2464664dbb05acc5e7c28<span class="w"> </span><span class="p">;</span><span class="w"> </span>gpg<span class="w"> </span>--armor<span class="w"> </span><span class="se">\</span> <span class="gp">&gt; </span>--export<span class="w"> </span>0x120d3c23c6d5584d6fc2464664dbb05acc5e7c28<span class="w"> </span><span class="o">)</span><span class="w"> </span>&gt;<span class="w"> </span><span class="se">\</span> <span class="gp">&gt; </span>0x120d3c23c6d5584d6fc2464664dbb05acc5e7c28.txt </pre></div> </div> <p>Add the file to a change for the <code class="docutils literal notranslate"><span class="pre">openstack/releases</span></code> repo placing it in the <code class="docutils literal notranslate"><span class="pre">doc/source/static</span></code> directory, and then link it similarly to other exported public keys are linked in the <cite>Cryptographic Signatures &lt;https://releases.openstack.org/#cryptographic-signatures&gt;</cite> section of <code class="docutils literal notranslate"><span class="pre">doc/source/index.rst</span></code> (noting the appropriate end date for use of the prior key as the start date for the new one).</p> </section> <section id="attestation"> <h3>Attestation<a class="headerlink" href="#attestation" title="Link to this heading">露</a></h3> <p>Since the collapse of the SKS keyserver network, no popular keyservers publish third-party key signatures any longer (the pollution thereof was a big part of SKS鈥檚 downfall). As such, until something like a caff-style signature approval mechanism is integrated into the OpenPGP keyserver infrastructure or some alternative comes along, there鈥檚 little point in generating our own individual key signatures we can鈥檛 easily distribute to users. For now, we simply make sure the signature made by the previous key is included in the export we publish from our own sites so that users can have some path to confirm new keys.</p> <p>Still, please retrieve a copy of the <code class="docutils literal notranslate"><span class="pre">/root/signing.gnupg/some.revoke.asc</span></code> fallback revocation certificate (<code class="docutils literal notranslate"><span class="pre">some</span></code> to be replaced with the lower-cased release name) 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).</p> </section> </section> </section> </div> </div> </div> <div class="sphinxsidebar" role="navigation" aria-label="Main"> <div class="sphinxsidebarwrapper"> <p class="logo"> <a href="index.html"> <img class="logo" src="_static/opendev.svg" alt="Logo" /> </a> </p> <search id="searchbox" style="display: none" role="search"> <div class="searchformwrapper"> <form class="search" action="search.html" method="get"> <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" placeholder="Search"/> <input type="submit" value="Go" /> </form> </div> </search> <script>document.getElementById('searchbox').style.display = "block"</script><h3>Navigation</h3> <ul class="current"> <li class="toctree-l1"><a class="reference internal" href="project.html">OpenDev Project</a></li> <li class="toctree-l1"><a class="reference internal" href="open-infrastructure.html">Open Infrastructure Technical Overview</a></li> <li class="toctree-l1"><a class="reference internal" href="test-infra-requirements.html">Test infrastructure Requirements</a></li> <li class="toctree-l1"><a class="reference internal" href="sysadmin.html">System Administration</a></li> <li class="toctree-l1"><a class="reference internal" href="sysadmin.html#root-only-information">Root only information</a></li> <li class="toctree-l1 current"><a class="reference internal" href="systems.html">Major Systems</a><ul class="current"> <li class="toctree-l2"><a class="reference internal" href="bridge.html">Bridge</a></li> <li class="toctree-l2"><a class="reference internal" href="cacti.html">Cacti</a></li> <li class="toctree-l2"><a class="reference internal" href="certificate_authority.html">Certificate Authority</a></li> <li class="toctree-l2"><a class="reference internal" href="dns.html">DNS</a></li> <li class="toctree-l2"><a class="reference internal" href="gerrit.html">Gerrit</a></li> <li class="toctree-l2"><a class="reference internal" href="gitea.html">Gitea</a></li> <li class="toctree-l2"><a class="reference internal" href="grafana.html">Grafana</a></li> <li class="toctree-l2"><a class="reference internal" href="grafyaml.html">Grafyaml</a></li> <li class="toctree-l2"><a class="reference internal" href="keycloak.html">Keycloak</a></li> <li class="toctree-l2"><a class="reference internal" href="zuul.html">Zuul</a></li> <li class="toctree-l2"><a class="reference internal" href="devstack-gate.html">Devstack Gate</a></li> <li class="toctree-l2"><a class="reference internal" href="nodepool.html">Nodepool</a></li> <li class="toctree-l2"><a class="reference internal" href="jeepyb.html">Jeepyb</a></li> <li class="toctree-l2"><a class="reference internal" href="irc.html">IRC Services</a></li> <li class="toctree-l2"><a class="reference internal" href="etherpad.html">Etherpad</a></li> <li class="toctree-l2"><a class="reference internal" href="paste.html">Paste</a></li> <li class="toctree-l2"><a class="reference internal" href="static.html">Static Web Hosting</a></li> <li class="toctree-l2"><a class="reference internal" href="mirrors.html">Mirror Servers</a></li> <li class="toctree-l2"><a class="reference internal" href="reprepro.html">Reprepro</a></li> <li class="toctree-l2"><a class="reference internal" href="lists.html">Mailing Lists</a></li> <li class="toctree-l2"><a class="reference internal" href="wiki.html">Wiki</a></li> <li class="toctree-l2"><a class="reference internal" href="storyboard.html">StoryBoard</a></li> <li class="toctree-l2"><a class="reference internal" href="kerberos.html">Kerberos</a></li> <li class="toctree-l2"><a class="reference internal" href="afs.html">OpenAFS</a></li> <li class="toctree-l2"><a class="reference internal" href="tracing.html">Tracing</a></li> <li class="toctree-l2"><a class="reference internal" href="translate.html">Translate</a></li> <li class="toctree-l2"><a class="reference internal" href="refstack.html">Refstack</a></li> <li class="toctree-l2"><a class="reference internal" href="codesearch.html">Code Search</a></li> <li class="toctree-l2 current"><a class="current reference internal" href="#">Signing System</a></li> <li class="toctree-l2"><a class="reference internal" href="github.html">GitHub</a></li> <li class="toctree-l2"><a class="reference internal" href="activity.html">Activity Board</a></li> <li class="toctree-l2"><a class="reference internal" href="letsencrypt.html">Let鈥檚 Encrypt Certificates</a></li> <li class="toctree-l2"><a class="reference internal" href="ppa.html">OpenDev PPA details</a></li> </ul> </li> <li class="toctree-l1"><a class="reference internal" href="roles.html">Ansible Roles</a></li> </ul> <ul> <li class="toctree-l1"><a class="reference internal" href="unofficial_project_hosting.html">Unofficial Project Hosting</a></li> <li class="toctree-l1"><a class="reference internal" href="third_party.html">Third Party Testing</a></li> <li class="toctree-l1"><a class="reference internal" href="contribute-cloud.html">Contributing Cloud Test Resources</a></li> </ul> <div class="relations"> <h3>Related Topics</h3> <ul> <li><a href="index.html">Documentation overview</a><ul> <li><a href="systems.html">Major Systems</a><ul> <li>Previous: <a href="codesearch.html" title="previous chapter">Code Search</a></li> <li>Next: <a href="github.html" title="next chapter">GitHub</a></li> </ul></li> </ul></li> </ul> </div> </div> </div> <div class="clearer"></div> </div> <div class="footer"> &#169;2025, OpenDev Contributors.. | Powered by <a href="https://www.sphinx-doc.org/">Sphinx 8.2.1</a> &amp; <a href="https://alabaster.readthedocs.io">Alabaster 1.0.0</a> | <a href="_sources/signing.rst.txt" rel="nofollow">Page source</a> </div> </body> </html>

Pages: 1 2 3 4 5 6 7 8 9 10