Backport Fix cert auth role quotas into ce/main (#9246)

This commit is contained in:
Vault Automation 2025-09-10 10:55:10 -06:00 committed by GitHub
parent 9eb24c2eb2
commit a70bc7c3cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 48 additions and 38 deletions

View file

@ -69,6 +69,13 @@ func (b *backend) loginPathWrapper(wrappedOp func(ctx context.Context, req *logi
}
func (b *backend) pathLoginResolveRole(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// Quota role rule creates send a probe to test if the backend returns
// ErrUnsupportedOperation for ResolveRole, and there's no req.Storage populated
// for these. So just return a non-ErrUnsupportedOperation error.
if req.Storage == nil {
return logical.ErrorResponse("no storage"), logical.ErrMissingRequiredState
}
config, err := b.Config(ctx, req.Storage)
if err != nil {
return nil, err

3
changelog/_9201.txt Normal file
View file

@ -0,0 +1,3 @@
```release-note:bug
core: Role based quotas now work for cert auth
```

View file

@ -153,7 +153,7 @@ func rateLimitQuotaWrapping(handler http.Handler, core *vault.Core) http.Handler
if requiresResolveRole {
buf := bytes.Buffer{}
teeReader := io.TeeReader(r.Body, &buf)
role := core.DetermineRoleFromLoginRequestFromReader(r.Context(), mountPath, teeReader)
role := core.DetermineRoleFromLoginRequestFromReader(r.Context(), mountPath, teeReader, getConnection(r), r.Header)
// Reset the body if it was read
if buf.Len() > 0 {

View file

@ -4422,48 +4422,53 @@ func (c *Core) LoadNodeID() (string, error) {
// DetermineRoleFromLoginRequest will determine the role that should be applied to a quota for a given
// login request
func (c *Core) DetermineRoleFromLoginRequest(ctx context.Context, mountPoint string, data map[string]interface{}) string {
func (c *Core) DetermineRoleFromLoginRequest(ctx context.Context, mountPoint string, data map[string]interface{}, conn *logical.Connection, headers map[string][]string) string {
c.authLock.RLock()
defer c.authLock.RUnlock()
matchingBackend := c.router.MatchingBackend(ctx, mountPoint)
if matchingBackend == nil || matchingBackend.Type() != logical.TypeCredential {
// Role based quotas do not apply to this request
return ""
}
return c.doResolveRoleLocked(ctx, mountPoint, matchingBackend, data)
return c.doResolveRoleLocked(ctx, mountPoint, data, conn, headers)
}
// DetermineRoleFromLoginRequestFromReader will determine the role that should
// be applied to a quota for a given login request. The reader will only be
// consumed if the matching backend for the mount point exists and is a secret
// backend
func (c *Core) DetermineRoleFromLoginRequestFromReader(ctx context.Context, mountPoint string, reader io.Reader) string {
func (c *Core) DetermineRoleFromLoginRequestFromReader(ctx context.Context, mountPoint string, reader io.Reader, conn *logical.Connection, header http.Header) string {
c.authLock.RLock()
defer c.authLock.RUnlock()
matchingBackend := c.router.MatchingBackend(ctx, mountPoint)
if matchingBackend == nil || matchingBackend.Type() != logical.TypeCredential {
// Role based quotas do not apply to this request
return ""
}
data := make(map[string]interface{})
err := jsonutil.DecodeJSONFromReader(reader, &data)
if err != nil {
return ""
}
return c.doResolveRoleLocked(ctx, mountPoint, matchingBackend, data)
return c.doResolveRoleLocked(ctx, mountPoint, data, conn, header)
}
// doResolveRoleLocked does a login and resolve role request on the matching
// backend. Callers should have a read lock on c.authLock
func (c *Core) doResolveRoleLocked(ctx context.Context, mountPoint string, matchingBackend logical.Backend, data map[string]interface{}) string {
resp, err := matchingBackend.HandleRequest(ctx, &logical.Request{
// doResolveRoleLocked does a resolve role request on the matching backend.
// Callers should have a read lock on c.authLock.
func (c *Core) doResolveRoleLocked(ctx context.Context, mountPoint string, data map[string]interface{}, conn *logical.Connection, headers http.Header) string {
be, me := c.router.MatchingBackendAndMountEntry(ctx, mountPoint)
if be == nil || be.Type() != logical.TypeCredential {
// Role based quotas do not apply to this request
return ""
}
var passthroughRequestHeaders []string
if rawVal, ok := me.synthesizedConfigCache.Load("passthrough_request_headers"); ok {
passthroughRequestHeaders = rawVal.([]string)
}
req := &logical.Request{
MountPoint: mountPoint,
Path: "login",
Operation: logical.ResolveRoleOperation,
Data: data,
Storage: c.router.MatchingStorageByAPIPath(ctx, mountPoint+"login"),
})
Connection: conn,
}
if len(passthroughRequestHeaders) > 0 {
req.Headers = filteredHeaders(headers, passthroughRequestHeaders, deniedPassthroughRequestHeaders)
}
resp, err := be.HandleRequest(ctx, req)
if err != nil || resp.Data["role"] == nil {
return ""
}

View file

@ -1928,7 +1928,7 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re
// for new role-based quotas upon creation, rather than counting old leases toward
// the total.
if reqRole == nil && requiresLease && !c.impreciseLeaseRoleTracking {
role = c.DetermineRoleFromLoginRequest(ctx, req.MountPoint, req.Data)
role = c.DetermineRoleFromLoginRequest(ctx, req.MountPoint, req.Data, req.Connection, req.Headers)
}
leaseGen, respTokenCreate, errCreateToken := c.LoginCreateToken(ctx, ns, req.Path, source, role, resp)

View file

@ -474,26 +474,20 @@ func (r *Router) matchingStorage(ctx context.Context, path string, apiPath bool)
// MatchingMountEntry returns the MountEntry used for a path
func (r *Router) MatchingMountEntry(ctx context.Context, path string) *MountEntry {
ns, err := namespace.FromContext(ctx)
if err != nil {
return nil
}
path = ns.Path + path
r.l.RLock()
_, raw, ok := r.root.LongestPrefix(path)
r.l.RUnlock()
if !ok {
return nil
}
return raw.(*routeEntry).mountEntry
_, mountEntry := r.MatchingBackendAndMountEntry(ctx, path)
return mountEntry
}
// MatchingBackend returns the backend used for a path
func (r *Router) MatchingBackend(ctx context.Context, path string) logical.Backend {
be, _ := r.MatchingBackendAndMountEntry(ctx, path)
return be
}
func (r *Router) MatchingBackendAndMountEntry(ctx context.Context, path string) (logical.Backend, *MountEntry) {
ns, err := namespace.FromContext(ctx)
if err != nil {
return nil
return nil, nil
}
path = ns.Path + path
@ -501,14 +495,15 @@ func (r *Router) MatchingBackend(ctx context.Context, path string) logical.Backe
_, raw, ok := r.root.LongestPrefix(path)
r.l.RUnlock()
if !ok {
return nil
return nil, nil
}
re := raw.(*routeEntry)
re.l.RLock()
defer re.l.RUnlock()
return re.backend
return re.backend, re.mountEntry
}
// MatchingSystemView returns the SystemView used for a path