A module may hold several secrets from one provider, each a pair of its own
secrets: maps a requirement to several files under local names. Each local name is its own need, its own pair credential (the pair is keyed on it: migration 0027), its own file on the consumer, its own holder at the provider (the identity with the local name after it) and rotates apart from the others. The plain shape is unchanged and every existing row is the credential it was (novox/hq 04-ISSUES/069, ADR 0094).
This commit is contained in:
+12
@@ -0,0 +1,12 @@
|
||||
-- A module may need several values from one provider that gives one per pair
|
||||
-- (novox/hq 04-ISSUES/069, ADR 0094).
|
||||
--
|
||||
-- A pair credential was keyed on (provision, consumer node, consumer module, provider): one value
|
||||
-- per module per provider. Seven catalogue modules hold two to four independent secrets of their
|
||||
-- own -- a root certificate, its key and that key's password -- and the vault could serve each
|
||||
-- module one. The pair now carries the LOCAL name the credential goes by inside the module; empty
|
||||
-- for the ordinary one, so every existing row is the credential it was.
|
||||
|
||||
alter table secret add column local text not null default '';
|
||||
alter table secret drop constraint secret_pkey;
|
||||
alter table secret add primary key (name, local, consumer, consumer_module, provider);
|
||||
@@ -164,7 +164,7 @@ func TestAPairCredentialIsSealedToTheOperatorToo(t *testing.T) {
|
||||
if _, err := inv.SetOperatorKey(ctx, pub); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
made, err := inv.SecretFor(ctx, "secret", "consumer", "gitea", "provider")
|
||||
made, err := inv.SecretFor(ctx, "secret", "consumer", "gitea", "provider", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -191,7 +191,7 @@ func TestAPairCredentialIsSealedToTheOperatorToo(t *testing.T) {
|
||||
|
||||
// A second provider of the same provision: two rows, refused rather than the first one taken,
|
||||
// unless the provider is named. And replacing the key counts pair credentials as orphaned.
|
||||
if _, err := inv.SecretFor(ctx, "secret", "consumer", "gitea", "consumer"); err != nil {
|
||||
if _, err := inv.SecretFor(ctx, "secret", "consumer", "gitea", "consumer", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := inv.KeptSecret(ctx, "consumer", "gitea", "secret", ""); err == nil || !strings.Contains(err.Error(), "--provider") {
|
||||
|
||||
@@ -24,11 +24,14 @@ type Secret struct {
|
||||
// **Part of the key, not a label** (novox/hq 04-ISSUES/022). Two modules on one node wanting
|
||||
// the same provision are two consumers, and were one credential until this.
|
||||
ConsumerModule string
|
||||
Provider string
|
||||
ForConsumer string
|
||||
ForProvider string
|
||||
ConsumerKey string
|
||||
ProviderKey string
|
||||
// Local is the name the credential goes by inside the consumer where it keeps several for one
|
||||
// provision (novox/hq ADR 0094); empty for the ordinary one. Part of the key.
|
||||
Local string
|
||||
Provider string
|
||||
ForConsumer string
|
||||
ForProvider string
|
||||
ConsumerKey string
|
||||
ProviderKey string
|
||||
// Origin is `made` — the mesh generated it — or `accepted` — a person supplied it, for
|
||||
// something outside the mesh, and the mesh cannot make another (novox/hq 04-ISSUES/070).
|
||||
Origin string
|
||||
@@ -51,7 +54,7 @@ const (
|
||||
// can no longer open what was sealed to the old one, so keeping the blob would deliver something
|
||||
// unreadable for ever. The new secret reaches both ends in the same push, which is the only
|
||||
// moment they can be changed together.
|
||||
func (i *Inventory) SecretFor(ctx context.Context, name, consumer, consumerModule, provider string) (
|
||||
func (i *Inventory) SecretFor(ctx context.Context, name, consumer, consumerModule, provider, local string) (
|
||||
Secret, error) {
|
||||
consumerKey, err := i.SealingKeyOf(ctx, consumer)
|
||||
if err != nil {
|
||||
@@ -74,12 +77,12 @@ func (i *Inventory) SecretFor(ctx context.Context, name, consumer, consumerModul
|
||||
var held Secret
|
||||
err = i.store.Pool().QueryRow(ctx,
|
||||
`select for_consumer, for_provider, consumer_key, provider_key, origin from secret
|
||||
where name = $1 and consumer = $2 and consumer_module = $3 and provider = $4`,
|
||||
name, consumerNode.ID, consumerModule, providerNode.ID).
|
||||
where name = $1 and consumer = $2 and consumer_module = $3 and provider = $4 and local = $5`,
|
||||
name, consumerNode.ID, consumerModule, providerNode.ID, local).
|
||||
Scan(&held.ForConsumer, &held.ForProvider, &held.ConsumerKey, &held.ProviderKey, &held.Origin)
|
||||
if err == nil && held.ConsumerKey == consumerKey && held.ProviderKey == providerKey {
|
||||
held.Name, held.Consumer, held.Provider = name, consumer, provider
|
||||
held.ConsumerModule = consumerModule
|
||||
held.ConsumerModule, held.Local = consumerModule, local
|
||||
return held, nil
|
||||
}
|
||||
if err == nil && held.Origin == OriginAccepted {
|
||||
@@ -90,8 +93,8 @@ func (i *Inventory) SecretFor(ctx context.Context, name, consumer, consumerModul
|
||||
return Secret{}, fmt.Errorf(
|
||||
"%s's %q credential from %s was accepted from a person, and a sealing key at one end "+
|
||||
"has changed since. The mesh cannot re-seal a value it does not hold: accept it "+
|
||||
"again with `secret accept %s %s %s --provider %s`",
|
||||
consumerModule, name, provider, consumer, consumerModule, name, provider)
|
||||
"again with `secret accept %s %s %s --provider %s%s`",
|
||||
consumerModule, name, provider, consumer, consumerModule, name, provider, localFlag(local))
|
||||
}
|
||||
|
||||
// And to the operator, when the mesh has one (novox/hq ADR 0085, amended): the third copy that
|
||||
@@ -107,19 +110,19 @@ func (i *Inventory) SecretFor(ctx context.Context, name, consumer, consumerModul
|
||||
forOperator, operatorKey := operatorColumns(operator, blob)
|
||||
_, err = i.store.Pool().Exec(ctx,
|
||||
`insert into secret (name, consumer, consumer_module, provider, for_consumer, for_provider,
|
||||
consumer_key, provider_key, operator_sealed, operator_key)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
on conflict (name, consumer, consumer_module, provider) do update set
|
||||
consumer_key, provider_key, operator_sealed, operator_key, local)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
on conflict (name, local, consumer, consumer_module, provider) do update set
|
||||
for_consumer = excluded.for_consumer, for_provider = excluded.for_provider,
|
||||
consumer_key = excluded.consumer_key, provider_key = excluded.provider_key,
|
||||
created_at = now(),
|
||||
operator_sealed = excluded.operator_sealed, operator_key = excluded.operator_key`,
|
||||
name, consumerNode.ID, consumerModule, providerNode.ID,
|
||||
made.ForConsumer, made.ForProvider, made.ConsumerKey, made.ProviderKey, forOperator, operatorKey)
|
||||
made.ForConsumer, made.ForProvider, made.ConsumerKey, made.ProviderKey, forOperator, operatorKey, local)
|
||||
if err != nil {
|
||||
return Secret{}, err
|
||||
}
|
||||
return Secret{Name: name, Consumer: consumer, ConsumerModule: consumerModule,
|
||||
return Secret{Name: name, Consumer: consumer, ConsumerModule: consumerModule, Local: local,
|
||||
Provider: provider,
|
||||
ForConsumer: made.ForConsumer, ForProvider: made.ForProvider,
|
||||
ConsumerKey: made.ConsumerKey, ProviderKey: made.ProviderKey, Origin: OriginMade}, nil
|
||||
@@ -133,7 +136,7 @@ func (i *Inventory) SecretFor(ctx context.Context, name, consumer, consumerModul
|
||||
// person can supply. It is the counterpart to AcceptSecretForModule for a module's own secret;
|
||||
// what differs is that both ends of the pair are sealed to, and that the record says `accepted`
|
||||
// so a later read never replaces it with a minted one. The plaintext is discarded here.
|
||||
func (i *Inventory) AcceptSecretForPair(ctx context.Context, name, consumer, consumerModule, provider, value string) error {
|
||||
func (i *Inventory) AcceptSecretForPair(ctx context.Context, name, consumer, consumerModule, provider, local, value string) error {
|
||||
consumerKey, err := i.SealingKeyOf(ctx, consumer)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -165,16 +168,16 @@ func (i *Inventory) AcceptSecretForPair(ctx context.Context, name, consumer, con
|
||||
}
|
||||
_, err = i.store.Pool().Exec(ctx,
|
||||
`insert into secret (name, consumer, consumer_module, provider, for_consumer, for_provider,
|
||||
consumer_key, provider_key, operator_sealed, operator_key, origin)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
on conflict (name, consumer, consumer_module, provider) do update set
|
||||
consumer_key, provider_key, operator_sealed, operator_key, origin, local)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||
on conflict (name, local, consumer, consumer_module, provider) do update set
|
||||
for_consumer = excluded.for_consumer, for_provider = excluded.for_provider,
|
||||
consumer_key = excluded.consumer_key, provider_key = excluded.provider_key,
|
||||
created_at = now(), origin = excluded.origin,
|
||||
operator_sealed = excluded.operator_sealed, operator_key = excluded.operator_key`,
|
||||
name, consumerNode.ID, consumerModule, providerNode.ID,
|
||||
sealed.ForConsumer, sealed.ForProvider, sealed.ConsumerKey, sealed.ProviderKey,
|
||||
forOperator, operatorKey, OriginAccepted)
|
||||
forOperator, operatorKey, OriginAccepted, local)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -190,7 +193,7 @@ func (i *Inventory) AcceptSecretForPair(ctx context.Context, name, consumer, con
|
||||
// **An accepted credential is not rotated.** The mesh did not make it and cannot make its
|
||||
// replacement; deleting it would have the next read mint one, which is exactly the wrong value
|
||||
// delivered with the mesh insisting it was (novox/hq 04-ISSUES/070). Refused, and the remedy named.
|
||||
func (i *Inventory) RotateSecret(ctx context.Context, name, consumer, consumerModule, provider string) error {
|
||||
func (i *Inventory) RotateSecret(ctx context.Context, name, consumer, consumerModule, provider, local string) error {
|
||||
consumerNode, err := i.NodeByName(ctx, consumer)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -202,19 +205,19 @@ func (i *Inventory) RotateSecret(ctx context.Context, name, consumer, consumerMo
|
||||
var origin string
|
||||
err = i.store.Pool().QueryRow(ctx,
|
||||
`select origin from secret where name = $1 and consumer = $2 and consumer_module = $3
|
||||
and provider = $4`,
|
||||
name, consumerNode.ID, consumerModule, providerNode.ID).Scan(&origin)
|
||||
and provider = $4 and local = $5`,
|
||||
name, consumerNode.ID, consumerModule, providerNode.ID, local).Scan(&origin)
|
||||
if err == nil && origin == OriginAccepted {
|
||||
return fmt.Errorf(
|
||||
"%s's %q credential from %s was accepted from a person, and the mesh cannot make "+
|
||||
"its replacement. Accept the new value instead: `secret accept %s %s %s "+
|
||||
"--provider %s --from <file>`",
|
||||
consumerModule, name, provider, consumer, consumerModule, name, provider)
|
||||
"--provider %s%s --from <file>`",
|
||||
consumerModule, name, provider, consumer, consumerModule, name, provider, localFlag(local))
|
||||
}
|
||||
_, err = i.store.Pool().Exec(ctx,
|
||||
`delete from secret where name = $1 and consumer = $2 and consumer_module = $3
|
||||
and provider = $4`,
|
||||
name, consumerNode.ID, consumerModule, providerNode.ID)
|
||||
and provider = $4 and local = $5`,
|
||||
name, consumerNode.ID, consumerModule, providerNode.ID, local)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -225,9 +228,9 @@ func (i *Inventory) SecretsFrom(ctx context.Context, provider string) ([]Secret,
|
||||
return nil, err
|
||||
}
|
||||
rows, err := i.store.Pool().Query(ctx,
|
||||
`select s.name, c.name, s.consumer_module, s.for_provider from secret s
|
||||
`select s.name, c.name, s.consumer_module, s.local, s.for_provider from secret s
|
||||
join node c on c.id = s.consumer
|
||||
where s.provider = $1 order by s.name, c.name, s.consumer_module`, providerNode.ID)
|
||||
where s.provider = $1 order by s.name, c.name, s.consumer_module, s.local`, providerNode.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -236,7 +239,7 @@ func (i *Inventory) SecretsFrom(ctx context.Context, provider string) ([]Secret,
|
||||
var out []Secret
|
||||
for rows.Next() {
|
||||
s := Secret{Provider: provider}
|
||||
if err := rows.Scan(&s.Name, &s.Consumer, &s.ConsumerModule, &s.ForProvider); err != nil {
|
||||
if err := rows.Scan(&s.Name, &s.Consumer, &s.ConsumerModule, &s.Local, &s.ForProvider); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, s)
|
||||
@@ -401,7 +404,9 @@ type Holder struct {
|
||||
// ConsumerModule is which module on that machine holds it. Part of what identifies a
|
||||
// credential (novox/hq 04-ISSUES/022), so rotating one consumer's does not touch another's.
|
||||
ConsumerModule string
|
||||
Provider string
|
||||
// Local is the credential's name inside the consumer where it holds several (ADR 0094).
|
||||
Local string
|
||||
Provider string
|
||||
}
|
||||
|
||||
// HoldersOf is every pair sharing a credential for one provision.
|
||||
@@ -415,11 +420,11 @@ type Holder struct {
|
||||
// Empty consumer means all of them.
|
||||
func (i *Inventory) HoldersOf(ctx context.Context, provision, consumer string) ([]Holder, error) {
|
||||
rows, err := i.store.Pool().Query(ctx,
|
||||
`select s.name, c.name, s.consumer_module, p.name from secret s
|
||||
`select s.name, c.name, s.consumer_module, s.local, p.name from secret s
|
||||
join node c on c.id = s.consumer
|
||||
join node p on p.id = s.provider
|
||||
where s.name = $1 and ($2 = '' or c.name = $2)
|
||||
order by c.name, s.consumer_module, p.name`, provision, consumer)
|
||||
order by c.name, s.consumer_module, s.local, p.name`, provision, consumer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -428,10 +433,18 @@ func (i *Inventory) HoldersOf(ctx context.Context, provision, consumer string) (
|
||||
var out []Holder
|
||||
for rows.Next() {
|
||||
var h Holder
|
||||
if err := rows.Scan(&h.Provision, &h.Consumer, &h.ConsumerModule, &h.Provider); err != nil {
|
||||
if err := rows.Scan(&h.Provision, &h.Consumer, &h.ConsumerModule, &h.Local, &h.Provider); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, h)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// localFlag is the `--local` a remedy has to name where a credential has a local name.
|
||||
func localFlag(local string) string {
|
||||
if local == "" {
|
||||
return ""
|
||||
}
|
||||
return " --local " + local
|
||||
}
|
||||
|
||||
@@ -63,11 +63,11 @@ func TestASecretIsMadeOnceAndKept(t *testing.T) {
|
||||
// Regenerating on every declaration would restart both ends on every push, and — worse — the
|
||||
// password a provider was told to create would never be the one its consumer was given.
|
||||
inv, ctx := twoNodesWithKeys(t)
|
||||
first, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider")
|
||||
first, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
second, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider")
|
||||
second, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -81,7 +81,7 @@ func TestTheStoredSecretIsNotTheSecret(t *testing.T) {
|
||||
// what an encrypted column does not achieve, because whoever runs the control plane can read
|
||||
// through it.
|
||||
inv, ctx := twoNodesWithKeys(t)
|
||||
got, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider")
|
||||
got, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -114,7 +114,7 @@ func TestANewSealingKeyMeansANewSecret(t *testing.T) {
|
||||
// A node that rejoined generated a new key and can no longer open what was sealed to the old
|
||||
// one. Keeping the blob would deliver something unreadable for ever, reported as configured.
|
||||
inv, ctx := twoNodesWithKeys(t)
|
||||
before, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider")
|
||||
before, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -126,7 +126,7 @@ func TestANewSealingKeyMeansANewSecret(t *testing.T) {
|
||||
if err := inv.RecordSealingKey(ctx, node.ID, fresh); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
after, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider")
|
||||
after, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -142,14 +142,14 @@ func TestANewSealingKeyMeansANewSecret(t *testing.T) {
|
||||
|
||||
func TestRotatingReachesBothEnds(t *testing.T) {
|
||||
inv, ctx := twoNodesWithKeys(t)
|
||||
before, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider")
|
||||
before, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := inv.RotateSecret(ctx, "postgres-database", "consumer", "gitea", "provider"); err != nil {
|
||||
if err := inv.RotateSecret(ctx, "postgres-database", "consumer", "gitea", "provider", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
after, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider")
|
||||
after, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -184,7 +184,7 @@ func TestAProviderIsToldEveryCredentialItMustCreate(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, who := range []string{"consumer", "second-consumer"} {
|
||||
if _, err := inv.SecretFor(ctx, "postgres-database", who, "gitea", "provider"); err != nil {
|
||||
if _, err := inv.SecretFor(ctx, "postgres-database", who, "gitea", "provider", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -215,7 +215,7 @@ func TestANodeWithNoSealingKeyCannotBeGivenASecret(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
_, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider")
|
||||
_, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider", "")
|
||||
if err == nil {
|
||||
t.Fatal("a credential was made for nodes that cannot open one")
|
||||
}
|
||||
@@ -226,7 +226,7 @@ func TestANodeWithNoSealingKeyCannotBeGivenASecret(t *testing.T) {
|
||||
|
||||
func TestSecretsGoWhenANodeLeaves(t *testing.T) {
|
||||
inv, ctx := twoNodesWithKeys(t)
|
||||
if _, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider"); err != nil {
|
||||
if _, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := inv.store.Pool().Exec(ctx, `delete from node where name = 'consumer'`); err != nil {
|
||||
@@ -349,7 +349,7 @@ func TestACredentialGoesWhenTheConsumerStopsAskingForIt(t *testing.T) {
|
||||
if err := inv.Assign(ctx, "consumer", "meshboard"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider"); err != nil {
|
||||
if _, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ func TestACredentialGoesWhenEitherMachineDoes(t *testing.T) {
|
||||
// The case that must not leave a live login behind: a machine removed from the mesh. Its
|
||||
// credentials go with it, and the provider stops being told to keep them.
|
||||
inv, ctx := twoNodesWithKeys(t)
|
||||
if _, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider"); err != nil {
|
||||
if _, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := inv.store.Pool().Exec(ctx, `delete from node where name = 'consumer'`); err != nil {
|
||||
@@ -473,12 +473,12 @@ func TestEveryHolderOfACredentialCanBeNamed(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, consumer := range []string{"consumer", "third"} {
|
||||
if _, err := inv.SecretFor(ctx, "postgres-database", consumer, "gitea", "provider"); err != nil {
|
||||
if _, err := inv.SecretFor(ctx, "postgres-database", consumer, "gitea", "provider", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// And one for a different provision, which must not be swept up.
|
||||
if _, err := inv.SecretFor(ctx, "cache", "consumer", "gitea", "provider"); err != nil {
|
||||
if _, err := inv.SecretFor(ctx, "cache", "consumer", "gitea", "provider", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -509,14 +509,14 @@ func TestEveryHolderOfACredentialCanBeNamed(t *testing.T) {
|
||||
// And rotating gives both ends a new credential, together — the same one.
|
||||
func TestRotatingGivesBothEndsTheSameNewCredential(t *testing.T) {
|
||||
inv, ctx := twoNodesWithKeys(t)
|
||||
before, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider")
|
||||
before, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := inv.RotateSecret(ctx, "postgres-database", "consumer", "gitea", "provider"); err != nil {
|
||||
if err := inv.RotateSecret(ctx, "postgres-database", "consumer", "gitea", "provider", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
after, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider")
|
||||
after, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -543,14 +543,14 @@ func TestRotatingGivesBothEndsTheSameNewCredential(t *testing.T) {
|
||||
if err := inv.RecordSealingKey(ctx, third.ID, key); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
untouched, err := inv.SecretFor(ctx, "postgres-database", "third", "gitea", "provider")
|
||||
untouched, err := inv.SecretFor(ctx, "postgres-database", "third", "gitea", "provider", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := inv.RotateSecret(ctx, "postgres-database", "consumer", "gitea", "provider"); err != nil {
|
||||
if err := inv.RotateSecret(ctx, "postgres-database", "consumer", "gitea", "provider", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
again, err := inv.SecretFor(ctx, "postgres-database", "third", "gitea", "provider")
|
||||
again, err := inv.SecretFor(ctx, "postgres-database", "third", "gitea", "provider", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -565,17 +565,17 @@ func TestRotatingGivesBothEndsTheSameNewCredential(t *testing.T) {
|
||||
// because it cannot make the replacement.
|
||||
func TestAnAcceptedPairCredentialIsKeptAndNeverRemade(t *testing.T) {
|
||||
inv, ctx := twoNodesWithKeys(t)
|
||||
if err := inv.AcceptSecretForPair(ctx, "secret", "consumer", "gitea", "provider", "hunter2"); err != nil {
|
||||
if err := inv.AcceptSecretForPair(ctx, "secret", "consumer", "gitea", "provider", "", "hunter2"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := inv.SecretFor(ctx, "secret", "consumer", "gitea", "provider")
|
||||
got, err := inv.SecretFor(ctx, "secret", "consumer", "gitea", "provider", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.Origin != OriginAccepted {
|
||||
t.Fatalf("an accepted credential reads back as %q", got.Origin)
|
||||
}
|
||||
again, err := inv.SecretFor(ctx, "secret", "consumer", "gitea", "provider")
|
||||
again, err := inv.SecretFor(ctx, "secret", "consumer", "gitea", "provider", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -584,15 +584,15 @@ func TestAnAcceptedPairCredentialIsKeptAndNeverRemade(t *testing.T) {
|
||||
}
|
||||
|
||||
// Rotation is refused, and says what to do instead.
|
||||
err = inv.RotateSecret(ctx, "secret", "consumer", "gitea", "provider")
|
||||
err = inv.RotateSecret(ctx, "secret", "consumer", "gitea", "provider", "")
|
||||
if err == nil || !strings.Contains(err.Error(), "secret accept") {
|
||||
t.Fatalf("rotating an accepted credential was not refused with the remedy: %v", err)
|
||||
}
|
||||
// And a made one still rotates.
|
||||
if _, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider"); err != nil {
|
||||
if _, err := inv.SecretFor(ctx, "postgres-database", "consumer", "gitea", "provider", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := inv.RotateSecret(ctx, "postgres-database", "consumer", "gitea", "provider"); err != nil {
|
||||
if err := inv.RotateSecret(ctx, "postgres-database", "consumer", "gitea", "provider", ""); err != nil {
|
||||
t.Fatalf("a made credential no longer rotates: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -601,7 +601,7 @@ func TestAnAcceptedPairCredentialIsKeptAndNeverRemade(t *testing.T) {
|
||||
// re-seal what it does not hold: refused aloud, never quietly replaced by a minted one.
|
||||
func TestAnAcceptedPairCredentialIsNotRemadeWhenAKeyChanges(t *testing.T) {
|
||||
inv, ctx := twoNodesWithKeys(t)
|
||||
if err := inv.AcceptSecretForPair(ctx, "secret", "consumer", "gitea", "provider", "hunter2"); err != nil {
|
||||
if err := inv.AcceptSecretForPair(ctx, "secret", "consumer", "gitea", "provider", "", "hunter2"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
node, err := inv.NodeByName(ctx, "consumer")
|
||||
@@ -612,15 +612,64 @@ func TestAnAcceptedPairCredentialIsNotRemadeWhenAKeyChanges(t *testing.T) {
|
||||
if err := inv.RecordSealingKey(ctx, node.ID, fresh); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = inv.SecretFor(ctx, "secret", "consumer", "gitea", "provider")
|
||||
_, err = inv.SecretFor(ctx, "secret", "consumer", "gitea", "provider", "")
|
||||
if err == nil || !strings.Contains(err.Error(), "accept it again") {
|
||||
t.Fatalf("an accepted credential was remade, or refused without the remedy: %v", err)
|
||||
}
|
||||
// Accepting it again is the remedy, and it works.
|
||||
if err := inv.AcceptSecretForPair(ctx, "secret", "consumer", "gitea", "provider", "hunter3"); err != nil {
|
||||
if err := inv.AcceptSecretForPair(ctx, "secret", "consumer", "gitea", "provider", "", "hunter3"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := inv.SecretFor(ctx, "secret", "consumer", "gitea", "provider"); err != nil {
|
||||
if _, err := inv.SecretFor(ctx, "secret", "consumer", "gitea", "provider", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Two secrets from one provider to one module are two credentials (novox/hq 04-ISSUES/069, ADR
|
||||
// 0094): keyed on the local name, made and rotated apart, and listed apart for the provider.
|
||||
func TestTwoLocalNamesAreTwoCredentials(t *testing.T) {
|
||||
inv, ctx := twoNodesWithKeys(t)
|
||||
key, err := inv.SecretFor(ctx, "secret", "consumer", "gitea", "provider", "root-key")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pass, err := inv.SecretFor(ctx, "secret", "consumer", "gitea", "provider", "root-pass")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if key.ForConsumer == pass.ForConsumer {
|
||||
t.Fatal("two local names were given one credential")
|
||||
}
|
||||
if err := inv.RotateSecret(ctx, "secret", "consumer", "gitea", "provider", "root-key"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keyAgain, err := inv.SecretFor(ctx, "secret", "consumer", "gitea", "provider", "root-key")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
passAgain, err := inv.SecretFor(ctx, "secret", "consumer", "gitea", "provider", "root-pass")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if keyAgain.ForConsumer == key.ForConsumer || passAgain.ForConsumer != pass.ForConsumer {
|
||||
t.Fatal("rotating one local name touched the other, or neither")
|
||||
}
|
||||
holders, err := inv.HoldersOf(ctx, "secret", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var locals []string
|
||||
for _, h := range holders {
|
||||
locals = append(locals, h.Local)
|
||||
}
|
||||
if strings.Join(locals, ",") != "root-key,root-pass" {
|
||||
t.Fatalf("the holders are listed apart, by local name: %v", holders)
|
||||
}
|
||||
from, err := inv.SecretsFrom(ctx, "provider")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(from) != 2 || from[0].Local == from[1].Local {
|
||||
t.Fatalf("the provider is told two credentials to create: %+v", from)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user