licences: deliver the refresh token by the ordinary sealed path, not a bespoke envelope
The refreshable-grant refresh token no longer rides a custom at-rest envelope that a
module opens with a node private key. A module is never given a node's private sealing
key, so that path could not exist -- the gap Phase C hit.
Instead the refresh token is a credential sealed to the MANAGER holder with the same
anonymous box (secrets.Seal / crypto_box_seal) every credential uses, stored as one
sealed blob, and delivered by the existing host-unseal-and-mount: the host opens it with
the node's real key and mounts the cleartext at the manager module's bound path, exactly
as a consumer's db password is delivered.
- refresh_grant now stores { sealed, manager_key }, dropping the AtRest token/wrapped_key
columns; internal/secrets/atrest.go is retired (nothing else used it).
- the licence records its manager as (node, module); KeyFor delivers the refresh token to
the manager holder and the access token to consumers, disambiguated by module so the two
can co-locate. Accept and the reseal skip the manager holder.
- the manager holder is delivered the node's PUBLIC sealing key in its bound facts, so the
module can re-seal a rotated refresh token with no private key of its own; the
declaration tolerates its empty pre-adoption secret rather than refusing.
- SubmitRefresh / set-grant take a sealed blob, never a refresh token in the clear.
The invariant holds unchanged: the control plane never reads the refresh token, and no node
but the manager holds it. A committed cross-language test proves the TypeScript module seal
opens under Go box.OpenAnonymous (the host's Unseal) -- both are NaCl crypto_box_seal.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
@@ -4,8 +4,6 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-control/internal/secrets"
|
||||
)
|
||||
|
||||
func TestTheTwoShapesAreSelectedByVendor(t *testing.T) {
|
||||
@@ -63,8 +61,8 @@ func (f *fakeVendor) Refresh(_ context.Context, in RefreshInput) (RefreshResult,
|
||||
return f.result, nil
|
||||
}
|
||||
|
||||
// A plugged-in refresher is dispatched to, and is handed the at-rest envelope (never a plaintext
|
||||
// refresh token) plus which node is the manager.
|
||||
// A plugged-in refresher is dispatched to, and is handed the sealed refresh token (never a plaintext
|
||||
// one) plus which node is the manager.
|
||||
func TestAPluggedInRefresherIsDispatchedTo(t *testing.T) {
|
||||
fake := &fakeVendor{result: RefreshResult{AccessToken: "at-new"}}
|
||||
RegisterRefresher("anthropic", fake)
|
||||
@@ -74,7 +72,7 @@ func TestAPluggedInRefresherIsDispatchedTo(t *testing.T) {
|
||||
r := grant.(Refresher)
|
||||
in := RefreshInput{
|
||||
Licence: "personal", Manager: "workstation",
|
||||
AtRest: secrets.AtRest{Token: "tok", WrappedKey: "wk", ManagerKey: "mk"},
|
||||
Sealed: "sealed-box", ManagerKey: "mk",
|
||||
}
|
||||
out, err := r.Refresh(context.Background(), in)
|
||||
if err != nil {
|
||||
@@ -83,7 +81,7 @@ func TestAPluggedInRefresherIsDispatchedTo(t *testing.T) {
|
||||
if out.AccessToken != "at-new" {
|
||||
t.Fatalf("the dispatched result did not come back: %q", out.AccessToken)
|
||||
}
|
||||
if fake.got.Manager != "workstation" || fake.got.AtRest.Token != "tok" {
|
||||
if fake.got.Manager != "workstation" || fake.got.Sealed != "sealed-box" {
|
||||
t.Fatalf("the refresher was handed the wrong input: %+v", fake.got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user