Repository navigation
This release hardens decern-serve's startup and caller configuration. Configurations 0.5.0
started with now refuse to start: --sharded without --key; a shard store whose records
another key signed, or that holds a shard file with records and no head cursor; --sharded with
a scheme other than postgres://, or POSTGRES://… on a build without the Postgres store;
--signed-audience, --spiffe-audience or --aauth-audience without their posture; the same
--signed-agent-key given twice for one agent; an --aauth-provider that is not https:// and
a lowercase host alone; and an AAuth --pep that is not an agent identifier in a configured
provider's domain. Requests 0.5.0 accepted are refused when an AAuth token's sub is not
aauth:local@domain in the issuing provider's domain, its ps is not https:// and a lowercase
host alone, or its parent_agent is not an agent identifier. Each is described below.
If you ran --sharded without --key, 0.5.0 and earlier signed the shared store under a fresh
key on every restart, so only the current run's records verified under the key /pubkey named.
No key you can supply extends such a store: move it aside and start with --key. Nothing in
the kernel, the Cedar model, the proofs or the SDKs changes.
Changed
- An AAuth agent is an agent identifier of the provider that issued its token. A token's
sub
must beaauth:local@domainas draft-hardt-oauth-aauth-protocol-11 §5.2 spells it, withdomain
the host of the provider named iniss; any othersubis refused, as is aparent_agentthat
is not an agent identifier. Before this,subwas taken as sent, so with two providers configured
either could present thesubof the other's agents, including one listed in--pep. Under
--aauth-provider, a--pepid must be an agent identifier in a configured provider's domain,
and a provider is named ashttps://and a lowercase host alone (§11.1.1), as is apsclaim
when a token carries one (§5.3.3 step 2), wherehttps://with any path was accepted. Authored by
@anivar. decern-servechecks every flag before it writes anything. A boot refused for a bad
--public-url, a malformed--standing-issuer-key, an unknown--authzen-type-aliasmodel type
or a port already taken used to leave the key, the ledger and the Mission registry behind, and
one refused for an unsupported--shardedtarget the key; it now leaves nothing, so a failed
deployment can be retried as it stands.--missionsnaming a directory, the shard directory, the ledger file, a path under a file
or a file that is not JSON, and--ledgeror--keyunder a directory that does not exist, are
refused before anything is written;--ledgerthere used to be refused after the key was created,
and a--missionsparent directory that does not exist is still created when the registry opens,
as before. A registry file that is JSON of another shape is refused only when opened, after the
key and the ledger exist, and leaves its lock file; a failure opening the ledger or the head store
itself can still leave the key.--shardedmatches its scheme regardless of case, so
POSTGRES://…reaches the Postgres store instead of becoming a directory named after the URL with
the password printed in the startup line; any other scheme is refused, and no message repeats the
URL.--signed-audience,--spiffe-audienceand--aauth-audiencegiven without their posture
are refused instead of ignored. The startup line names the address actually bound. Authored by
@anivar.
Fixed
decern-servekeeps running when it cannot install its Ctrl-C handler. The failed install
resolved the shutdown signal at once, so the server stopped as soon as it started; on Unix it now
waits for SIGTERM alone, as it already did when the SIGTERM handler failed, and elsewhere, with no
SIGTERM handler, it runs until it is killed. Authored by @anivar.decern-servenames all five caller postures where it lists them. The refusal to start
with no posture, and the--trust-proxyand--pephelp, named only some of them; they now
include SPIFFE JWT-SVIDs (--spiffe-trust-domain) and AAuth agent tokens (--aauth-provider).
Authored by @anivar.- A shard file holding records but no head cursor is refused. The file head store listed a shard
only when its cursor was set, so a shard file whose cursor had been removed was skipped by
decern verify --shardedand not counted as history: the store verified as if the file were
absent, and the next append started the chain again at sequence 0 beside the old records. Such a file is now refused wherever it is read,
bydecern-serveat boot before anything is written and bydecern verify --sharded, as an
altered store. Authored by @anivar. - A second
--signed-agent-keyfor the same agent adds a key instead of replacing it. The
flag's help,docs/CLI.mdand the signed-request module all said a key rollover is a second
entry for the same id with the old one still accepted, but the second entry silently replaced
the first, so an agent mid-rollover was refused under its old key. Both keys now verify until
the old one is dropped from the command line: the bound token'scnf.jwkmust name one of the
agent's configured keys, and the request signature must verify under that key. The same key
given twice for one id is refused at startup. Authored by @anivar.
Security
- A ledger is extended only by the key that signed it.
--shardedwithout--keyis refused:
the shared head store was signed under a fresh ephemeral key on every restart, so each replica's
records verified under a different key and/pubkeyonly ever named the latest. At boot every
shard is verified under the key--keyholds, chain and every signature, one signature check per
record, as a single-file open already was; a record another key signed refuses the boot, naming
both key ids where the record names its key. The check runs at boot only: a store with no shards
boots under any key, so two replicas started at once on an empty store under different keys both
boot and the store then verifies under neither; start the first replica alone, or give every
replica the same key before any starts. A shard another replica creates later is checked at the
next restart. A single-file ledger with records already refused a different key; the refusal now
names both key ids, and an ephemeral key, or a--keyfile that does not exist, is refused before
anything is written. An empty ledger file is a new ledger. The startup line marks an ephemeral key
(ephemeral). Where a Postgres head store already holds shards, a--keyfile that does not
exist is still created before the refusal. Authored by @anivar. - A segment created at rollover is owner-only. A segmented ledger (
Ledger::open_segmented)
created each new segment with the process's default mode,0644under the common umask, so
the records it collected were readable by group and other until the next rollover sealed it
or the ledger was reopened. Every segment is now created0600on Unix; the next rollover
still seals it0400, and a reopen tightens an active segment created before this change,
as it already did. The manifest beside the segments, which names them with their sequence
ranges and open times and holds no records, keeps the default mode. Only a segmented ledger
creates segments:decern-servenever opens one (a single file, or a head store under
--sharded), anddecern verifyanddecern explainread a segmented directory without
writing to it, so neither was affected. On
Windows the ledger's files are not written owner-only. Authored by @anivar.
Known limits in this release
Unchanged from 0.5.0: the AAuth posture does not meet draft-hardt-oauth-aauth-protocol-11 (the
agent token's alg must be EdDSA, which -11 replaces with Ed25519, and the token is taken
from the Signature-Key member labelled sig while the signature labelled sig1 is verified, so
no conformant agent passes; this release tightens what the posture checks of a token it does
accept and closes none of those gaps); bearer validation verifies EdDSA only; a Mission's
approver is not checked to differ from its agent; the single-file ledger takes no lock; keys,
trust bundles and provider key sets load only at startup; no posture keeps a replay cache; and
what the proofs cover. Stated now: the ledger key check runs at boot only, so two replicas
started at once on an empty store under different keys both boot and fork it. The standards
registry lists each gap.