Runtime state guard: a credential-shaped name passes only holding an exact provider reference, node and module, so a placement's binding is kept and nothing else gets through (issue 1126)
This commit is contained in:
@@ -391,9 +391,10 @@ func valueOf(raw []byte) json.RawMessage {
|
||||
var credentialEndings = []string{"password", "passwd", "secret", "token", "credential", "credentials",
|
||||
"authorization", "apikey", "privatekey", "accesskey", "cookie"}
|
||||
|
||||
// credentialField is the first field anywhere in a JSON value whose name says it is a credential, or "". A credential is
|
||||
// a value, so a name counts only where it holds one or a list of them: a name holding an object is data naming a structure (a
|
||||
// placement's binding of the provision "secret" to the vault, issue 1126), whose own fields are looked at in turn.
|
||||
// credentialField is the first field anywhere in a JSON value whose name says it is a credential, or "". The one shape a
|
||||
// credential-shaped name may hold is a provider reference, an object of exactly `node` and `module`, both strings: a
|
||||
// placement's binding of the provision "secret" to the vault (issue 1126) names who provides it, never its value. Any
|
||||
// other shape under such a name is refused.
|
||||
func credentialField(value json.RawMessage) string {
|
||||
var v any
|
||||
if json.Unmarshal(value, &v) != nil {
|
||||
@@ -410,9 +411,8 @@ func credentialField(value json.RawMessage) string {
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
norm := strings.NewReplacer("-", "", "_", "", " ", "").Replace(strings.ToLower(k))
|
||||
_, object := t[k].(map[string]any)
|
||||
for _, end := range credentialEndings {
|
||||
if strings.HasSuffix(norm, end) && !object {
|
||||
if strings.HasSuffix(norm, end) && !providerReference(t[k]) {
|
||||
return k
|
||||
}
|
||||
}
|
||||
@@ -431,3 +431,14 @@ func credentialField(value json.RawMessage) string {
|
||||
}
|
||||
return walk(v)
|
||||
}
|
||||
|
||||
// providerReference says whether v is exactly {"node": <string>, "module": <string>}.
|
||||
func providerReference(v any) bool {
|
||||
m, ok := v.(map[string]any)
|
||||
if !ok || len(m) != 2 {
|
||||
return false
|
||||
}
|
||||
node, okN := m["node"].(string)
|
||||
module, okM := m["module"].(string)
|
||||
return okN && okM && node != "" && module != ""
|
||||
}
|
||||
|
||||
@@ -15,11 +15,13 @@ func TestACredentialNamedFieldIsFoundAnywhereInAValue(t *testing.T) {
|
||||
`{"clientSecret":"s"}`: "clientSecret",
|
||||
`{"maxTokens":4096,"name":"a","generation":3}`: "",
|
||||
`"just a string"`: "",
|
||||
// Issue 1126: a placement binds the provision "secret" to the vault; the name holds a structure, not a value.
|
||||
// Issue 1126: a placement binds the provision "secret" to the vault; only an exact provider reference passes.
|
||||
`{"bindings":{"secret":{"node":"anchor","module":"mesh-vault"}}}`: "",
|
||||
`{"secret":{"value":"s"}}`: "",
|
||||
`{"secret":["s"]}`: "secret",
|
||||
`{"bindings":{"secret":{"token":"s"}}}`: "token",
|
||||
`{"secret":{"value":"s"}}`: "secret",
|
||||
`{"secret":{"node":"anchor","module":"mesh-vault","value":"s"}}`: "secret",
|
||||
`{"secret":{"node":"anchor","module":7}}`: "secret",
|
||||
`{"secret":["s"]}`: "secret",
|
||||
`{"bindings":{"secret":{"token":"s"}}}`: "secret",
|
||||
} {
|
||||
if got := credentialField(json.RawMessage(value)); got != want {
|
||||
t.Errorf("%s: found %q, want %q", value, got, want)
|
||||
|
||||
Reference in New Issue
Block a user