NAME
App::FuguWeb::Rotate - write the key directory of a site
SYNOPSIS
use App::FuguWeb::Rotate;
my $rotate = App::FuguWeb::Rotate->new(
config => $config,
org => 'fugubsd',
url => 'https://www.fugubsd.org/keys',
);
my $facts = $rotate->mint(
purpose => 'release',
secret => '/tmp/new.sec',
signer => '/tmp/current.sec',
) or die $rotate->error;
my $facts = $rotate->promote(
purpose => 'release',
secret => '/tmp/next.sec',
) or die $rotate->error;
DESCRIPTION
App::FuguWeb::Keys reads the key directory, and this class writes it. One class holds both steps of a rotation, so the reader and the writer share one implementation of the name pattern, the status vocabulary and the manifest.
A consumer verifies a release against the copy of the public key that it holds. A release that a new key signs therefore fails in each consumer that holds the old copy. One rotation runs in two steps, and the trust order carries the gap.
mint makes the next key of a purpose. The current key of that purpose signs the manifest that names the new one. A consumer with the old copy can therefore still verify. promote makes the new key current and retires the old one.
The first mint of a purpose finds no current key. That key is current at once, and it signs its own manifest.
This class signs, and a site build does not. It is the one part of FuguWeb that needs signify(1).
THE TRUST RULES
Each rule below answers a way to publish a key directory that no consumer can use.
- The description holds the status
-
The status of a key comes from its
keyblock, and no step assumes one. A key file with no block, and a block with no file, each fail the step. A step that read the first key of a purpose would take a retired key for the current one. - The current key signs
-
A mint of a purpose that holds a current key takes the private half of that key as
signer. It never signs with the key that it generated. A manifest that the new key signed would fail in every consumer. - The signature verifies before it lands
-
Each step stages the manifest and the signature in a temporary directory, and it verifies the signature against the published public key of the signer. Only a verified pair reaches the key directory.
- One mint waits for the promote
-
A mint refuses a purpose that holds a
nextkey already, and it refuses before it generates a pair. A caller holds one place for the private half of a mint, so a second key would lose the private half of the first. - A step lands whole, or not at all
-
Every byte of a step is ready before the first write. Each file lands through a temporary file in the same directory, so a reader sees the old bytes or the new ones. A write that fails, and a check that fails after the writes, both put every file back.
The private half of a new key lands last, after the check passes. A step that fails therefore leaves no key that the site does not publish.
A process that dies inside a step leaves the checkout dirty. A caller runs a step and commits after it, so it commits nothing then.
- One directory holds one purpose
-
One manifest covers the whole directory, and one key signs it. A second purpose would leave the current key of the first unable to verify the pair, and no check would report it. A mint of another purpose therefore fails.
- The reader decides
-
Each step ends with App::FuguWeb::Keys. A directory that the reader rejects is a directory that
fuguweb checkrejects, so the step fails and the caller commits nothing.
METHODS
- App::FuguWeb::Rotate->new(config => $config, ...)
-
The rotation over one description.
configis an App::FuguWeb::Config.A site that publishes its first key holds no
keysblock, because such a block cannot load until akeyblock stands beside it. The caller then namesorg, and it can namedirandurl. One commit carries thekeysblock, the firstkeyblock and the key itself. - $rotate->mint(purpose => $word, secret => $path, signer => $path)
-
Generate the next key of the purpose. The public half goes into the key directory, and the private half goes to
secretwith no group mode and no other mode.signeris the private half of the current key of the purpose, and a first mint takes none.secretmust name no file that stands, and it must not namesigner. The private half of a key is the one thing that a rotation cannot make again, and a caller holds each one in one place.The method answers a hash reference with
name,stem,serialandstatus, or undef with a reason inerror. - $rotate->promote(purpose => $word, secret => $path)
-
Make the
nextkey of the purpose current, and retire the key that was current with anuntildate.secretis the private half of the key that this step makes current, and that key signs the manifest.The retired key keeps its file and its published URL, so a release that it signed still verifies.
The method answers a hash reference with
name,stem,serial,statusandretired, or undef with a reason inerror. - $rotate->config
-
The description. A step loads it again after it writes, so a caller that holds this object reads the current state.
- $rotate->error
-
The reason of the most recent failure, or undef.
WHAT THIS CLASS DOES NOT DO
It writes no secret store and it opens no pull request. It reaches no network and it holds no token. A caller stores the private key and declares the public one.
SEE ALSO
App::FuguWeb::Keys, Fugu::KeyDir, Fugu::Signify, App::FuguWeb::Config, signify(1)
AUTHORS
Dick Olsson <hi@senzilla.io>