mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 11:38:02 +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))
|
r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||||
|
|
||||||
role := core.DetermineRoleFromLoginRequestFromBytes(mountPath, bodyBytes, r.Context())
|
quotaReq := "as.Request{
|
||||||
|
|
||||||
// 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{
|
|
||||||
Type: quotas.TypeRateLimit,
|
Type: quotas.TypeRateLimit,
|
||||||
Path: path,
|
Path: path,
|
||||||
MountPath: mountPath,
|
MountPath: mountPath,
|
||||||
Role: role,
|
|
||||||
NamespacePath: ns.Path,
|
NamespacePath: ns.Path,
|
||||||
ClientAddress: parseRemoteIPAddress(r),
|
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 {
|
if err != nil {
|
||||||
core.Logger().Error("failed to apply quota", "path", path, "error", err)
|
core.Logger().Error("failed to apply quota", "path", path, "error", err)
|
||||||
respondError(w, http.StatusUnprocessableEntity, err)
|
respondError(w, http.StatusInternalServerError, err)
|
||||||
return
|
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
|
// DetermineRoleFromLoginRequestFromBytes will determine the role that should be applied to a quota for a given
|
||||||
// login request, accepting a byte payload
|
// 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{})
|
data := make(map[string]interface{})
|
||||||
err := jsonutil.DecodeJSON(payload, &data)
|
err := jsonutil.DecodeJSON(payload, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -3947,12 +3947,12 @@ func (c *Core) DetermineRoleFromLoginRequestFromBytes(mountPoint string, payload
|
|||||||
return ""
|
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
|
// DetermineRoleFromLoginRequest will determine the role that should be applied to a quota for a given
|
||||||
// login request
|
// 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()
|
c.authLock.RLock()
|
||||||
defer c.authLock.RUnlock()
|
defer c.authLock.RUnlock()
|
||||||
matchingBackend := c.router.MatchingBackend(ctx, mountPoint)
|
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 {
|
if err != nil || resp.Data["role"] == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.Data["role"].(string)
|
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
|
// aliasNameFromLoginRequest will determine the aliasName from the login Request
|
||||||
func (c *Core) aliasNameFromLoginRequest(ctx context.Context, req *logical.Request) (string, error) {
|
func (c *Core) aliasNameFromLoginRequest(ctx context.Context, req *logical.Request) (string, error) {
|
||||||
c.authLock.RLock()
|
c.authLock.RLock()
|
||||||
|
|||||||
@@ -617,6 +617,37 @@ func (m *Manager) queryQuota(txn *memdb.Txn, req *Request) (Quota, error) {
|
|||||||
return nil, nil
|
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
|
// DeleteQuota removes a quota rule from the db for a given name
|
||||||
func (m *Manager) DeleteQuota(ctx context.Context, qType string, name string) error {
|
func (m *Manager) DeleteQuota(ctx context.Context, qType string, name string) error {
|
||||||
m.quotaLock.Lock()
|
m.quotaLock.Lock()
|
||||||
|
|||||||
@@ -135,3 +135,48 @@ func TestQuotas_Precedence(t *testing.T) {
|
|||||||
checkQuotaFunc(t, "testns/nested2/nested3/", "", "", "", rateLimitMultiNestedNsInheritableQuota)
|
checkQuotaFunc(t, "testns/nested2/nested3/", "", "", "", rateLimitMultiNestedNsInheritableQuota)
|
||||||
checkQuotaFunc(t, "testns/nested2/nested3/nested4/nested5", "", "", "", 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
|
// Check for request role in context to role based quotas
|
||||||
var role string
|
var role string
|
||||||
if reqRole := ctx.Value(logical.CtxKeyRequestRole{}); reqRole != nil {
|
reqRole := ctx.Value(logical.CtxKeyRequestRole{})
|
||||||
|
if reqRole != nil {
|
||||||
role = reqRole.(string)
|
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
|
// Attach the display name, might be used by audit backends
|
||||||
req.DisplayName = auth.DisplayName
|
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)
|
leaseGen, respTokenCreate, errCreateToken := c.LoginCreateToken(ctx, ns, req.Path, source, role, resp)
|
||||||
leaseGenerated = leaseGen
|
leaseGenerated = leaseGen
|
||||||
if errCreateToken != nil {
|
if errCreateToken != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user