Bug fix: resultant-acl + wildcard parsing (#9449) (#9584)

* an attempt to add parsing of wilcards

* test coverage and changelog

* fix policy path

* fix failing tesT

* fix comments

* add in go doc comment

* update second internal ui resultant acl test

Co-authored-by: Angel Garbarino <Monkeychip@users.noreply.github.com>
Co-authored-by: Tony Wittinger <anwittin@users.noreply.github.com>
This commit is contained in:
Vault Automation 2025-09-29 14:19:56 -04:00 committed by GitHub
parent a48469ef13
commit ca48dcda5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 97 additions and 4 deletions

3
changelog/_9449.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
core: resultant-acl now merges segment-wildcard (`+`) paths with existing prefix rules in `glob_paths`, so clients receive a complete view of glob-style permissions. This unblocks UI sidebar navigation checks and namespace access banners.
```

View File

@ -5337,16 +5337,18 @@ func (b *SystemBackend) pathInternalUIResultantACL(ctx context.Context, req *log
walkFn(exact, s, v)
return false
}
acl.exactRules.Walk(exactWalkFn)
resp.Data["exact_paths"] = exact
// Combine glob (prefix) and segment wildcard (+) paths into glob_paths
globWalkFn := func(s string, v interface{}) bool {
walkFn(glob, s, v)
return false
}
acl.exactRules.Walk(exactWalkFn)
acl.prefixRules.Walk(globWalkFn)
resp.Data["exact_paths"] = exact
for s, v := range acl.segmentWildcardPaths {
walkFn(glob, s, v)
}
resp.Data["glob_paths"] = glob
return resp, nil

View File

@ -138,6 +138,9 @@ func TestSystemBackend_InternalUIResultantACL(t *testing.T) {
"update",
},
},
"identity/oidc/provider/+/authorize": map[string]interface{}{
"capabilities": []interface{}{"read", "update"},
},
},
"root": false,
"chroot_namespace": "",

View File

@ -4738,6 +4738,91 @@ func TestSystemBackend_InternalUIMount(t *testing.T) {
}
}
// TestSystemBackend_InternalUIResultantACL verifies that segment wildcard and prefix glob ACLs are emitted correctly in the internal UI resultant-acl endpoint.
func TestSystemBackend_InternalUIResultantACL(t *testing.T) {
ctx := namespace.RootContext(nil)
core, b, rootToken := testCoreSystemBackend(t)
// Define a policy that includes a segment wildcard and a prefix glob.
rules := `
name = "ui-res-acl-test"
path "+/auth/*" {
capabilities = ["read"]
}
path "sys/*" {
capabilities = ["update"]
}`
pol, err := ParseACLPolicy(namespace.RootNamespace, rules)
require.NoError(t, err)
require.NoError(t, core.policyStore.SetPolicy(ctx, pol))
// Create a non-root token that has this policy attached
testMakeServiceTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"ui-res-acl-test"})
// Call the endpoint as the non-root token; this endpoint evaluates the caller.
req := logical.TestRequest(t, logical.ReadOperation, "internal/ui/resultant-acl")
req.ClientToken = "tokenid"
resp, err := b.HandleRequest(ctx, req)
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
// Validate response shape.
schema.ValidateResponse(
t,
schema.GetResponseSchema(t, b.(*SystemBackend).Route(req.Path), req.Operation),
resp,
true,
)
// Basic flags we expect back for a non-root token were inspecting.
if v, ok := resp.Data["root"].(bool); ok {
require.False(t, v, "expected non-root token")
}
// Extract glob paths and ensure both entries are present with expected caps.
globPaths, ok := resp.Data["glob_paths"].(map[string]interface{})
require.True(t, ok, "glob_paths missing or wrong type")
getCaps := func(m map[string]interface{}) []string {
raw := m["capabilities"]
switch a := raw.(type) {
case []string:
return a
case []interface{}:
out := make([]string, 0, len(a))
for _, x := range a {
if s, ok := x.(string); ok {
out = append(out, s)
}
}
return out
default:
return nil
}
}
// 1) segment wildcard preserved
segRaw, ok := globPaths["+/auth/*"]
require.True(t, ok, "segment wildcard path not found")
seg, ok := segRaw.(map[string]interface{})
require.True(t, ok, "segment wildcard value wrong type")
require.Equal(t, []string{"read"}, getCaps(seg))
// 2) prefix glob preserved (the backend may normalize to "sys/*" or "sys/")
prefixKey := "sys/*"
if _, ok := globPaths["sys/"]; ok {
prefixKey = "sys/"
}
prefRaw, ok := globPaths[prefixKey]
require.True(t, ok, "prefix glob path not found")
pref, ok := prefRaw.(map[string]interface{})
require.True(t, ok, "prefix glob value wrong type")
require.Contains(t, getCaps(pref), "update")
}
func TestSystemBackend_OpenAPI(t *testing.T) {
coreConfig := &CoreConfig{
LogicalBackends: map[string]logical.Factory{