mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	Only resolve roles for role quotas and leases (#22597)
This commit is contained in:
		
							
								
								
									
										3
									
								
								changelog/22597.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/22597.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| ```release-note:bug | ||||
| core/quotas: Only perform ResolveRoleOperation for role-based quotas and lease creation. | ||||
| ``` | ||||
							
								
								
									
										29
									
								
								http/util.go
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								http/util.go
									
									
									
									
									
								
							| @@ -60,22 +60,33 @@ func rateLimitQuotaWrapping(handler http.Handler, core *vault.Core) http.Handler | ||||
| 		} | ||||
| 		r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) | ||||
|  | ||||
| 		role := core.DetermineRoleFromLoginRequestFromBytes(mountPath, bodyBytes, r.Context()) | ||||
|  | ||||
| 		// add an entry to the context to prevent recalculating request role unnecessarily | ||||
| 		r = r.WithContext(context.WithValue(r.Context(), logical.CtxKeyRequestRole{}, role)) | ||||
|  | ||||
| 		quotaResp, err := core.ApplyRateLimitQuota(r.Context(), "as.Request{ | ||||
| 		quotaReq := "as.Request{ | ||||
| 			Type:          quotas.TypeRateLimit, | ||||
| 			Path:          path, | ||||
| 			MountPath:     mountPath, | ||||
| 			Role:          role, | ||||
| 			NamespacePath: ns.Path, | ||||
| 			ClientAddress: parseRemoteIPAddress(r), | ||||
| 		}) | ||||
| 		} | ||||
| 		requiresResolveRole, err := core.ResolveRoleForQuotas(r.Context(), quotaReq) | ||||
| 		if err != nil { | ||||
| 			core.Logger().Error("failed to lookup quotas", "path", path, "error", err) | ||||
| 			respondError(w, http.StatusInternalServerError, err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// If any role-based quotas are enabled for this namespace/mount, just | ||||
| 		// do the role resolution once here. | ||||
| 		if requiresResolveRole { | ||||
| 			role := core.DetermineRoleFromLoginRequestFromBytes(r.Context(), mountPath, bodyBytes) | ||||
| 			// add an entry to the context to prevent recalculating request role unnecessarily | ||||
| 			r = r.WithContext(context.WithValue(r.Context(), logical.CtxKeyRequestRole{}, role)) | ||||
| 			quotaReq.Role = role | ||||
| 		} | ||||
|  | ||||
| 		quotaResp, err := core.ApplyRateLimitQuota(r.Context(), quotaReq) | ||||
| 		if err != nil { | ||||
| 			core.Logger().Error("failed to apply quota", "path", path, "error", err) | ||||
| 			respondError(w, http.StatusUnprocessableEntity, err) | ||||
| 			respondError(w, http.StatusInternalServerError, err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -3939,7 +3939,7 @@ func (c *Core) LoadNodeID() (string, error) { | ||||
|  | ||||
| // DetermineRoleFromLoginRequestFromBytes will determine the role that should be applied to a quota for a given | ||||
| // login request, accepting a byte payload | ||||
| func (c *Core) DetermineRoleFromLoginRequestFromBytes(mountPoint string, payload []byte, ctx context.Context) string { | ||||
| func (c *Core) DetermineRoleFromLoginRequestFromBytes(ctx context.Context, mountPoint string, payload []byte) string { | ||||
| 	data := make(map[string]interface{}) | ||||
| 	err := jsonutil.DecodeJSON(payload, &data) | ||||
| 	if err != nil { | ||||
| @@ -3947,12 +3947,12 @@ func (c *Core) DetermineRoleFromLoginRequestFromBytes(mountPoint string, payload | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return c.DetermineRoleFromLoginRequest(mountPoint, data, ctx) | ||||
| 	return c.DetermineRoleFromLoginRequest(ctx, mountPoint, data) | ||||
| } | ||||
|  | ||||
| // DetermineRoleFromLoginRequest will determine the role that should be applied to a quota for a given | ||||
| // login request | ||||
| func (c *Core) DetermineRoleFromLoginRequest(mountPoint string, data map[string]interface{}, ctx context.Context) string { | ||||
| func (c *Core) DetermineRoleFromLoginRequest(ctx context.Context, mountPoint string, data map[string]interface{}) string { | ||||
| 	c.authLock.RLock() | ||||
| 	defer c.authLock.RUnlock() | ||||
| 	matchingBackend := c.router.MatchingBackend(ctx, mountPoint) | ||||
| @@ -3971,9 +3971,19 @@ func (c *Core) DetermineRoleFromLoginRequest(mountPoint string, data map[string] | ||||
| 	if err != nil || resp.Data["role"] == nil { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return resp.Data["role"].(string) | ||||
| } | ||||
|  | ||||
| // ResolveRoleForQuotas looks for any quotas requiring a role for early | ||||
| // computation in the RateLimitQuotaWrapping handler. | ||||
| func (c *Core) ResolveRoleForQuotas(ctx context.Context, req *quotas.Request) (bool, error) { | ||||
| 	if c.quotaManager == nil { | ||||
| 		return false, nil | ||||
| 	} | ||||
| 	return c.quotaManager.QueryResolveRoleQuotas(req) | ||||
| } | ||||
|  | ||||
| // aliasNameFromLoginRequest will determine the aliasName from the login Request | ||||
| func (c *Core) aliasNameFromLoginRequest(ctx context.Context, req *logical.Request) (string, error) { | ||||
| 	c.authLock.RLock() | ||||
|   | ||||
| @@ -617,6 +617,37 @@ func (m *Manager) queryQuota(txn *memdb.Txn, req *Request) (Quota, error) { | ||||
| 	return nil, nil | ||||
| } | ||||
|  | ||||
| // QueryResolveRoleQuotas checks if there's a quota for the request mount path | ||||
| // which requires ResolveRoleOperation. | ||||
| func (m *Manager) QueryResolveRoleQuotas(req *Request) (bool, error) { | ||||
| 	m.dbAndCacheLock.RLock() | ||||
| 	defer m.dbAndCacheLock.RUnlock() | ||||
|  | ||||
| 	txn := m.db.Txn(false) | ||||
|  | ||||
| 	// ns would have been made non-empty during insertion. Use non-empty | ||||
| 	// value during query as well. | ||||
| 	if req.NamespacePath == "" { | ||||
| 		req.NamespacePath = "root" | ||||
| 	} | ||||
|  | ||||
| 	// Check for any role-based quotas on the request namespaces/mount path. | ||||
| 	for _, qType := range quotaTypes() { | ||||
| 		// Use the namespace and mount as indexes and find all matches with a | ||||
| 		// set Role field (see: 'true' as the last argument). We can't use | ||||
| 		// indexNamespaceMountRole for this, because Role is a StringFieldIndex, | ||||
| 		// which won't match on an empty string. | ||||
| 		quota, err := txn.First(qType, indexNamespaceMount, req.NamespacePath, req.MountPath, false, true) | ||||
| 		if err != nil { | ||||
| 			return false, err | ||||
| 		} | ||||
| 		if quota != nil { | ||||
| 			return true, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return false, nil | ||||
| } | ||||
|  | ||||
| // DeleteQuota removes a quota rule from the db for a given name | ||||
| func (m *Manager) DeleteQuota(ctx context.Context, qType string, name string) error { | ||||
| 	m.quotaLock.Lock() | ||||
|   | ||||
| @@ -135,3 +135,48 @@ func TestQuotas_Precedence(t *testing.T) { | ||||
| 	checkQuotaFunc(t, "testns/nested2/nested3/", "", "", "", rateLimitMultiNestedNsInheritableQuota) | ||||
| 	checkQuotaFunc(t, "testns/nested2/nested3/nested4/nested5", "", "", "", rateLimitMultiNestedNsInheritableQuota) | ||||
| } | ||||
|  | ||||
| // TestQuotas_QueryRoleQuotas checks to see if quota creation on a mount | ||||
| // requires a call to ResolveRoleOperation. | ||||
| func TestQuotas_QueryResolveRole_RateLimitQuotas(t *testing.T) { | ||||
| 	leaseWalkFunc := func(context.Context, func(request *Request) bool) error { | ||||
| 		return nil | ||||
| 	} | ||||
| 	qm, err := NewManager(logging.NewVaultLogger(log.Trace), leaseWalkFunc, metricsutil.BlackholeSink()) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	rlqReq := &Request{ | ||||
| 		Type:          TypeRateLimit, | ||||
| 		Path:          "", | ||||
| 		MountPath:     "mount1/", | ||||
| 		NamespacePath: "", | ||||
| 		ClientAddress: "127.0.0.1", | ||||
| 	} | ||||
| 	// Check that we have no quotas requiring role resolution on mount1/ | ||||
| 	required, err := qm.QueryResolveRoleQuotas(rlqReq) | ||||
| 	require.NoError(t, err) | ||||
| 	require.False(t, required) | ||||
|  | ||||
| 	// Create a non-role-based RLQ on mount1/ and make sure it doesn't require role resolution | ||||
| 	rlq := NewRateLimitQuota("tq", rlqReq.NamespacePath, rlqReq.MountPath, rlqReq.Path, rlqReq.Role, false, 1*time.Minute, 10*time.Second, 10) | ||||
| 	require.NoError(t, qm.SetQuota(context.Background(), TypeRateLimit.String(), rlq, false)) | ||||
|  | ||||
| 	required, err = qm.QueryResolveRoleQuotas(rlqReq) | ||||
| 	require.NoError(t, err) | ||||
| 	require.False(t, required) | ||||
|  | ||||
| 	// Create a role-based RLQ on mount1/ and make sure it requires role resolution | ||||
| 	rlqReq.Role = "test" | ||||
| 	rlq = NewRateLimitQuota("tq", rlqReq.NamespacePath, rlqReq.MountPath, rlqReq.Path, rlqReq.Role, false, 1*time.Minute, 10*time.Second, 10) | ||||
| 	require.NoError(t, qm.SetQuota(context.Background(), TypeRateLimit.String(), rlq, false)) | ||||
|  | ||||
| 	required, err = qm.QueryResolveRoleQuotas(rlqReq) | ||||
| 	require.NoError(t, err) | ||||
| 	require.True(t, required) | ||||
|  | ||||
| 	// Check that we have no quotas requiring role resolution on mount2/ | ||||
| 	rlqReq.MountPath = "mount2/" | ||||
| 	required, err = qm.QueryResolveRoleQuotas(rlqReq) | ||||
| 	require.NoError(t, err) | ||||
| 	require.False(t, required) | ||||
| } | ||||
|   | ||||
| @@ -1485,7 +1485,8 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re | ||||
|  | ||||
| 		// Check for request role in context to role based quotas | ||||
| 		var role string | ||||
| 		if reqRole := ctx.Value(logical.CtxKeyRequestRole{}); reqRole != nil { | ||||
| 		reqRole := ctx.Value(logical.CtxKeyRequestRole{}) | ||||
| 		if reqRole != nil { | ||||
| 			role = reqRole.(string) | ||||
| 		} | ||||
|  | ||||
| @@ -1686,6 +1687,13 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re | ||||
| 		// Attach the display name, might be used by audit backends | ||||
| 		req.DisplayName = auth.DisplayName | ||||
|  | ||||
| 		// If this is not a role-based quota, we still need to associate the | ||||
| 		// login role with this lease for later lease-count quotas to be | ||||
| 		// accurate. | ||||
| 		if reqRole == nil && resp.Auth.TokenType != logical.TokenTypeBatch { | ||||
| 			role = c.DetermineRoleFromLoginRequest(ctx, req.MountPoint, req.Data) | ||||
| 		} | ||||
|  | ||||
| 		leaseGen, respTokenCreate, errCreateToken := c.LoginCreateToken(ctx, ns, req.Path, source, role, resp) | ||||
| 		leaseGenerated = leaseGen | ||||
| 		if errCreateToken != nil { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Mike Palmiotto
					Mike Palmiotto