diff --git a/go/buscompose/buscompose_test.go b/go/buscompose/buscompose_test.go index 86aa24e..c8f4c4a 100644 --- a/go/buscompose/buscompose_test.go +++ b/go/buscompose/buscompose_test.go @@ -6,6 +6,7 @@ import ( "time" "git.novox.be/novox/mesh-sdk/go/announce" + "git.novox.be/novox/mesh-sdk/go/delivery" "github.com/nats-io/nats.go/jetstream" "os" "path/filepath" @@ -431,6 +432,11 @@ func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) { if on, why := LeaseHandedAway([]byte(row.raw), row.found, row.err); on != row.on || why == "" { t.Errorf("%s: on %v (%s), want %v", row.name, on, why, row.on) } + // The assigner's grants source follows it: the grants part's bucket only for a handed-away lease (issue 774). + on, _ := LeaseHandedAway([]byte(row.raw), row.found, row.err) + if bucket, _ := delivery.GrantSource(on); (bucket == delivery.PartGrantsBucket) != row.on { + t.Errorf("%s: the grants source is %s", row.name, bucket) + } } announced := map[string]announce.States{ControllerSeat: ControllerAnnouncement("novox")} for reader, want := range map[string]bool{VaultSeat: true, GrantsModule: true, "mesh-delivery-view": false} { diff --git a/go/delivery/assigner_test.go b/go/delivery/assigner_test.go index 6b54e1d..1adb030 100644 --- a/go/delivery/assigner_test.go +++ b/go/delivery/assigner_test.go @@ -298,6 +298,25 @@ func TestControllerGrant(t *testing.T) { if _, err := EncodeControllerGrant(ControllerGrant{Accounts: []string{""}}); err == nil { t.Errorf("a grant naming an empty account was accepted") } + // The grants part's keys are the controller's: the same key round-trips; the source is the part's bucket only for a + // handed-away lease (issue 774). + if pk, err := PartGrantKey("mesh-delivery-assigner", fp); err != nil || pk != key { + t.Errorf("PartGrantKey = %q, %v, want the controller's %q", pk, err, key) + } + if m, f, err := ParsePartGrantKey(key); err != nil || m != "mesh-delivery-assigner" || f != fp { + t.Errorf("ParsePartGrantKey = %q, %q, %v", m, f, err) + } + for _, c := range []struct { + away bool + bucket, read string + }{ + {false, "mesh-controller_grants", "mesh-controller.grants"}, + {true, "mesh-grants_grants", "mesh-grants.grants"}, + } { + if b, r := GrantSource(c.away); b != c.bucket || r != c.read { + t.Errorf("GrantSource(%v) = %q, %q, want %q, %q", c.away, b, r, c.bucket, c.read) + } + } g := ControllerGrant{Accounts: []string{"ace.mesh-build-go", "shanks.mesh-mail.reader"}} for _, c := range []struct { g ControllerGrant diff --git a/go/delivery/grants.go b/go/delivery/grants.go index 884e4f1..9da49a0 100644 --- a/go/delivery/grants.go +++ b/go/delivery/grants.go @@ -90,3 +90,39 @@ func (g ControllerGrant) NamesNode(node, module string) bool { } return false } + +// **The grants part's grants** (novox/hq ADR 0547 §4, §5, ADR 0561 §2, §6; issue 774). The grants part writes the same +// keys, with the same value, into its own state `grants` (the bucket PartGrantsBucket), beside the controller's: first in +// shadow, which nobody reads, so a tool can compare the two before anything switches. The assigner reads the part's keys +// only once the controller's lease is handed away (GrantSource), and the controller's until then. +const ( + // PartGrantsState is the grants part's state of issued credentials by fingerprint, as its manifest names it. + PartGrantsState = "grants" + // PartGrantsBucket is that state's bucket, `_`. + PartGrantsBucket = "mesh-grants_" + PartGrantsState + // PartGrantsRead is that state as a reader names it in `reads`. + PartGrantsRead = "mesh-grants." + PartGrantsState + // PartGrantsHistory is the revisions the bucket keeps per key. + PartGrantsHistory = ControllerGrantsHistory +) + +// PartGrantKey is the key, in PartGrantsBucket, of a module's credential issued for a fingerprint: the layout of the +// controller's key (ControllerGrantKey), so a key of one is the key of the other and a compare is by key. +func PartGrantKey(module, fingerprint string) (string, error) { + return ControllerGrantKey(module, fingerprint) +} + +// ParsePartGrantKey is the inverse of PartGrantKey. +func ParsePartGrantKey(key string) (module, fingerprint string, err error) { + return ParseControllerGrantKey(key) +} + +// GrantSource names the bucket the assigner reads grants from, and the `reads` name that grants it: the grants part's +// once the controller's lease is handed away (buscompose.LeaseHandedAway, which refuses every other value of the lease, +// an unreadable one included), the controller's in every other case. +func GrantSource(leaseHandedAway bool) (bucket, read string) { + if leaseHandedAway { + return PartGrantsBucket, PartGrantsRead + } + return ControllerGrantsBucket, "mesh-controller.grants" +}