Name the vault's credential under vault-name so the state guard accepts the bound entry (issue 945)
This commit is contained in:
@@ -105,8 +105,10 @@ type Entry struct {
|
||||
User string `json:"user,omitempty"`
|
||||
// Values are the served values, composed for this consumer: what `${bound:<provision>:<key>}` reads.
|
||||
Values map[string]any `json:"values,omitempty"`
|
||||
// Credential is the name of the credential in the vault, which the consumer's client asks the vault for.
|
||||
Credential string `json:"credential,omitempty"`
|
||||
// VaultName is the name under which the vault keeps this consumer's credential, which the consumer's client asks
|
||||
// the vault for: a name, never the credential. Its key is not "credential" because the runtime's state guard
|
||||
// refuses any key ending in that word, whatever the value (issue 945).
|
||||
VaultName string `json:"vault-name,omitempty"`
|
||||
// Refused, when set, is why the consumer is not served, naming both ends.
|
||||
Refused string `json:"refused,omitempty"`
|
||||
// Contributes is what the consumer's module tells its provider about this provision (a database's name, its
|
||||
@@ -419,15 +421,15 @@ func (p *Provider) answer(ctx context.Context, e Entry, have []byte) Outcome {
|
||||
switch offer.Credential {
|
||||
case Shared:
|
||||
e.User = offer.User
|
||||
e.Credential = SharedName(e.Provider, e.Provision)
|
||||
e.VaultName = SharedName(e.Provider, e.Provision)
|
||||
case PerConsumer:
|
||||
e.User = e.As
|
||||
e.Credential = PairName(e.Provider, e.Provision, e.Consumer, e.Module, e.Local)
|
||||
e.VaultName = PairName(e.Provider, e.Provision, e.Consumer, e.Module, e.Local)
|
||||
}
|
||||
if e.Credential != "" {
|
||||
credential, err = p.opts.Vault.Value(ctx, e.Credential)
|
||||
if e.VaultName != "" {
|
||||
credential, err = p.opts.Vault.Value(ctx, e.VaultName)
|
||||
if err != nil {
|
||||
return Outcome{Key: key, Status: Waiting, Why: fmt.Sprintf("the vault's %s: %v", e.Credential, err)}
|
||||
return Outcome{Key: key, Status: Waiting, Why: fmt.Sprintf("the vault's %s: %v", e.VaultName, err)}
|
||||
}
|
||||
}
|
||||
if p.opts.Make != nil {
|
||||
|
||||
@@ -224,7 +224,7 @@ func TestEachConsumerGetsAnEntryOfItsOwnAndTheVaultsValue(t *testing.T) {
|
||||
e := entryOf(t, bus, "ace.letta.postgres-database")
|
||||
if e.Provider != "novox" || e.ProviderModule != "postgres" || e.Consumer != "ace" || e.Module != "letta" ||
|
||||
e.As != "mesh_ace_letta" || e.User != "mesh_ace_letta" ||
|
||||
e.Credential != "pair/novox/postgres-database/ace/letta" || e.Refused != "" {
|
||||
e.VaultName != "pair/novox/postgres-database/ace/letta" || e.Refused != "" {
|
||||
t.Fatalf("entry %+v", e)
|
||||
}
|
||||
if e.Values["database"] != "mesh_ace_letta" || e.Values["host"] != "postgres.novox.mesh" ||
|
||||
@@ -263,7 +263,7 @@ func TestASharedCredentialIsTheVaultsOneValueAndTheProvidersOneUser(t *testing.T
|
||||
if err := json.Unmarshal(bus.get(BucketOf("sonarr"), key), &e); err != nil {
|
||||
t.Fatalf("%s: %v", key, err)
|
||||
}
|
||||
if e.Credential != "provision/novox/sonarr-api" || e.User != "sonarr" || e.Values["url"] == nil {
|
||||
if e.VaultName != "provision/novox/sonarr-api" || e.User != "sonarr" || e.Values["url"] == nil {
|
||||
t.Fatalf("%s: %+v", key, e)
|
||||
}
|
||||
if m.made[key] != "one-value" {
|
||||
@@ -291,7 +291,7 @@ func TestAMultiHolderConsumerIsRefusedADerivedValueNamingBothEnds(t *testing.T)
|
||||
t.Fatalf("the refusal does not name %q: %s", says, e.Refused)
|
||||
}
|
||||
}
|
||||
if e.Values != nil || e.Credential != "" {
|
||||
if e.Values != nil || e.VaultName != "" {
|
||||
t.Fatalf("a refused entry carries values: %+v", e)
|
||||
}
|
||||
if m.calls != 0 {
|
||||
@@ -317,7 +317,7 @@ func TestALocalNameIsServedWhenNothingIsDerived(t *testing.T) {
|
||||
if err := json.Unmarshal(bus.get(BucketOf("redis"), "ace.letta.redis-cache.queue"), &e); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if e.Refused != "" || e.Credential != "pair/novox/redis-cache/ace/letta/queue" || e.Local != "queue" {
|
||||
if e.Refused != "" || e.VaultName != "pair/novox/redis-cache/ace/letta/queue" || e.Local != "queue" {
|
||||
t.Fatalf("entry %+v", e)
|
||||
}
|
||||
if m.made["ace.letta.redis-cache.queue"] != "q-secret" {
|
||||
@@ -588,3 +588,62 @@ func TestAConsumersEntryCarriesTheNamesOfItsPlan(t *testing.T) {
|
||||
t.Fatalf("an entry of a plan without names changed: %+v", plain)
|
||||
}
|
||||
}
|
||||
|
||||
// refusedByTheStateGuard mirrors the key rule of the runtime's state guard (mesh-tools node-tools internal/bus
|
||||
// state.go): a JSON key whose lowercased name, with `-`, `_` and spaces taken out, ends in one of these is refused.
|
||||
func refusedByTheStateGuard(key string) bool {
|
||||
k := strings.NewReplacer("-", "", "_", "", " ", "").Replace(strings.ToLower(key))
|
||||
for _, end := range []string{"password", "passwd", "secret", "token", "credential", "credentials",
|
||||
"authorization", "apikey", "privatekey", "accesskey", "cookie"} {
|
||||
if strings.HasSuffix(k, end) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func keysOf(v any, into *[]string) {
|
||||
switch x := v.(type) {
|
||||
case map[string]any:
|
||||
for k, c := range x {
|
||||
*into = append(*into, k)
|
||||
keysOf(c, into)
|
||||
}
|
||||
case []any:
|
||||
for _, c := range x {
|
||||
keysOf(c, into)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Every entry the helper writes passes the state guard's key rule (issue 945), and names where the vault keeps the
|
||||
// credential under `vault-name`, for a login per consumer and for a shared one.
|
||||
func TestAnEntryHasNoKeyTheStateGuardRefuses(t *testing.T) {
|
||||
for name, form := range map[string]CredentialForm{"pair": PerConsumer, "shared": Shared} {
|
||||
bus, m := newStandIn(), &maker{}
|
||||
v := &vault{values: map[string]string{"pair/novox/postgres-database/ace/letta": "s1",
|
||||
"provision/novox/postgres-database": "s2"}}
|
||||
assign(t, bus, "ace", "letta", bind("postgres-database", "", "novox", "postgres"))
|
||||
p := New(Options{Node: "novox", Module: "postgres", Bus: bus, Vault: v, Make: m.Make, Logf: func(string, ...any) {},
|
||||
Offers: map[string]Offer{"postgres-database": {Credential: form, User: "shared-user"}}})
|
||||
if _, err := p.Pass(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var doc map[string]any
|
||||
if err := json.Unmarshal(bus.get(BucketOf("postgres"), "ace.letta.postgres-database"), &doc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var keys []string
|
||||
keysOf(doc, &keys)
|
||||
for _, k := range keys {
|
||||
if refusedByTheStateGuard(k) {
|
||||
t.Fatalf("%s: the entry carries the key %q, which the state guard refuses", name, k)
|
||||
}
|
||||
}
|
||||
want := map[string]string{"pair": "pair/novox/postgres-database/ace/letta",
|
||||
"shared": "provision/novox/postgres-database"}[name]
|
||||
if doc["vault-name"] != want {
|
||||
t.Fatalf("%s: vault-name is %v, want %s", name, doc["vault-name"], want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user