2023-03-15 16:00:52 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2020-06-26 21:13:16 +00:00
|
|
|
package quotas
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"testing"
|
2020-07-29 19:15:05 +00:00
|
|
|
"time"
|
2020-06-26 21:13:16 +00:00
|
|
|
|
|
|
|
"github.com/go-test/deep"
|
|
|
|
log "github.com/hashicorp/go-hclog"
|
2020-10-13 23:38:21 +00:00
|
|
|
"github.com/hashicorp/vault/helper/metricsutil"
|
2020-06-26 21:13:16 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/helper/logging"
|
2020-07-29 19:15:05 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2020-06-26 21:13:16 +00:00
|
|
|
)
|
|
|
|
|
2021-02-09 10:46:09 +00:00
|
|
|
func TestQuotas_MountPathOverwrite(t *testing.T) {
|
2023-10-30 17:21:47 +00:00
|
|
|
qm, err := NewManager(logging.NewVaultLogger(log.Trace), nil, metricsutil.BlackholeSink(), true)
|
2021-02-09 10:46:09 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-24 12:58:02 +00:00
|
|
|
quota := NewRateLimitQuota("tq", "", "kv1/", "", "", 10, time.Second, 0)
|
2021-02-09 10:46:09 +00:00
|
|
|
require.NoError(t, qm.SetQuota(context.Background(), TypeRateLimit.String(), quota, false))
|
2022-02-17 20:17:59 +00:00
|
|
|
quota = quota.Clone().(*RateLimitQuota)
|
2021-02-09 10:46:09 +00:00
|
|
|
quota.MountPath = "kv2/"
|
|
|
|
require.NoError(t, qm.SetQuota(context.Background(), TypeRateLimit.String(), quota, false))
|
|
|
|
|
|
|
|
q, err := qm.QueryQuota(&Request{
|
|
|
|
Type: TypeRateLimit,
|
|
|
|
MountPath: "kv1/",
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Nil(t, q)
|
|
|
|
|
|
|
|
require.NoError(t, qm.DeleteQuota(context.Background(), TypeRateLimit.String(), "tq"))
|
|
|
|
|
|
|
|
q, err = qm.QueryQuota(&Request{
|
|
|
|
Type: TypeRateLimit,
|
|
|
|
MountPath: "kv1/",
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Nil(t, q)
|
|
|
|
}
|
|
|
|
|
2020-06-26 21:13:16 +00:00
|
|
|
func TestQuotas_Precedence(t *testing.T) {
|
2023-10-30 17:21:47 +00:00
|
|
|
qm, err := NewManager(logging.NewVaultLogger(log.Trace), nil, metricsutil.BlackholeSink(), true)
|
2020-07-29 19:15:05 +00:00
|
|
|
require.NoError(t, err)
|
2020-06-26 21:13:16 +00:00
|
|
|
|
2022-06-24 12:58:02 +00:00
|
|
|
setQuotaFunc := func(t *testing.T, name, nsPath, mountPath, pathSuffix, role string) Quota {
|
2020-06-26 21:13:16 +00:00
|
|
|
t.Helper()
|
2022-06-24 12:58:02 +00:00
|
|
|
quota := NewRateLimitQuota(name, nsPath, mountPath, pathSuffix, role, 10, time.Second, 0)
|
2020-07-29 19:15:05 +00:00
|
|
|
require.NoError(t, qm.SetQuota(context.Background(), TypeRateLimit.String(), quota, true))
|
2020-06-26 21:13:16 +00:00
|
|
|
return quota
|
|
|
|
}
|
|
|
|
|
2022-06-24 12:58:02 +00:00
|
|
|
checkQuotaFunc := func(t *testing.T, nsPath, mountPath, pathSuffix, role string, expected Quota) {
|
2020-06-26 21:13:16 +00:00
|
|
|
t.Helper()
|
2020-07-13 18:42:39 +00:00
|
|
|
quota, err := qm.QueryQuota(&Request{
|
2020-06-26 21:13:16 +00:00
|
|
|
Type: TypeRateLimit,
|
|
|
|
NamespacePath: nsPath,
|
|
|
|
MountPath: mountPath,
|
2022-06-24 12:58:02 +00:00
|
|
|
Role: role,
|
2022-06-16 17:23:02 +00:00
|
|
|
Path: nsPath + mountPath + pathSuffix,
|
2020-06-26 21:13:16 +00:00
|
|
|
})
|
2020-07-29 19:15:05 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2020-06-26 21:13:16 +00:00
|
|
|
if diff := deep.Equal(expected, quota); len(diff) > 0 {
|
|
|
|
t.Fatal(diff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// No quota present. Expect nil.
|
2022-06-24 12:58:02 +00:00
|
|
|
checkQuotaFunc(t, "", "", "", "", nil)
|
2020-06-26 21:13:16 +00:00
|
|
|
|
|
|
|
// Define global quota and expect that to be returned.
|
2022-06-24 12:58:02 +00:00
|
|
|
rateLimitGlobalQuota := setQuotaFunc(t, "rateLimitGlobalQuota", "", "", "", "")
|
|
|
|
checkQuotaFunc(t, "", "", "", "", rateLimitGlobalQuota)
|
2020-06-26 21:13:16 +00:00
|
|
|
|
|
|
|
// Define a global mount specific quota and expect that to be returned.
|
2022-06-24 12:58:02 +00:00
|
|
|
rateLimitGlobalMountQuota := setQuotaFunc(t, "rateLimitGlobalMountQuota", "", "testmount/", "", "")
|
|
|
|
checkQuotaFunc(t, "", "testmount/", "", "", rateLimitGlobalMountQuota)
|
2022-06-16 17:23:02 +00:00
|
|
|
|
|
|
|
// Define a global mount + path specific quota and expect that to be returned.
|
2022-06-24 12:58:02 +00:00
|
|
|
rateLimitGlobalMountPathQuota := setQuotaFunc(t, "rateLimitGlobalMountPathQuota", "", "testmount/", "testpath", "")
|
|
|
|
checkQuotaFunc(t, "", "testmount/", "testpath", "", rateLimitGlobalMountPathQuota)
|
2020-06-26 21:13:16 +00:00
|
|
|
|
|
|
|
// Define a namespace quota and expect that to be returned.
|
2022-06-24 12:58:02 +00:00
|
|
|
rateLimitNSQuota := setQuotaFunc(t, "rateLimitNSQuota", "testns/", "", "", "")
|
|
|
|
checkQuotaFunc(t, "testns/", "", "", "", rateLimitNSQuota)
|
2020-06-26 21:13:16 +00:00
|
|
|
|
|
|
|
// Define a namespace mount specific quota and expect that to be returned.
|
2022-06-24 12:58:02 +00:00
|
|
|
rateLimitNSMountQuota := setQuotaFunc(t, "rateLimitNSMountQuota", "testns/", "testmount/", "", "")
|
2022-07-21 19:31:23 +00:00
|
|
|
checkQuotaFunc(t, "testns/", "testmount/", "testpath", "", rateLimitNSMountQuota)
|
|
|
|
|
|
|
|
// Define a namespace mount + glob and expect that to be returned.
|
|
|
|
rateLimitNSMountGlob := setQuotaFunc(t, "rateLimitNSMountGlob", "testns/", "testmount/", "*", "")
|
|
|
|
checkQuotaFunc(t, "testns/", "testmount/", "testpath", "", rateLimitNSMountGlob)
|
|
|
|
|
|
|
|
// Define a namespace mount + path specific quota with a glob and expect that to be returned.
|
|
|
|
rateLimitNSMountPathSuffixGlob := setQuotaFunc(t, "rateLimitNSMountPathSuffixGlob", "testns/", "testmount/", "test*", "")
|
|
|
|
checkQuotaFunc(t, "testns/", "testmount/", "testpath", "", rateLimitNSMountPathSuffixGlob)
|
|
|
|
|
|
|
|
// Define a namespace mount + path specific quota with a glob at the end of the path and expect that to be returned.
|
|
|
|
rateLimitNSMountPathSuffixGlobAfterPath := setQuotaFunc(t, "rateLimitNSMountPathSuffixGlobAfterPath", "testns/", "testmount/", "testpath*", "")
|
|
|
|
checkQuotaFunc(t, "testns/", "testmount/", "testpath", "", rateLimitNSMountPathSuffixGlobAfterPath)
|
2022-06-16 17:23:02 +00:00
|
|
|
|
|
|
|
// Define a namespace mount + path specific quota and expect that to be returned.
|
2022-06-24 12:58:02 +00:00
|
|
|
rateLimitNSMountPathQuota := setQuotaFunc(t, "rateLimitNSMountPathQuota", "testns/", "testmount/", "testpath", "")
|
|
|
|
checkQuotaFunc(t, "testns/", "testmount/", "testpath", "", rateLimitNSMountPathQuota)
|
|
|
|
|
|
|
|
// Define a namespace mount + role specific quota and expect that to be returned.
|
|
|
|
rateLimitNSMountRoleQuota := setQuotaFunc(t, "rateLimitNSMountPathQuota", "testns/", "testmount/", "", "role")
|
|
|
|
checkQuotaFunc(t, "testns/", "testmount/", "", "role", rateLimitNSMountRoleQuota)
|
2020-06-26 21:13:16 +00:00
|
|
|
|
|
|
|
// Now that many quota types are defined, verify that the most specific
|
|
|
|
// matches are returned per namespace.
|
2022-06-24 12:58:02 +00:00
|
|
|
checkQuotaFunc(t, "", "", "", "", rateLimitGlobalQuota)
|
|
|
|
checkQuotaFunc(t, "testns/", "", "", "", rateLimitNSQuota)
|
2020-06-26 21:13:16 +00:00
|
|
|
}
|
2023-08-30 15:28:32 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
2023-10-30 17:21:47 +00:00
|
|
|
qm, err := NewManager(logging.NewVaultLogger(log.Trace), leaseWalkFunc, metricsutil.BlackholeSink(), true)
|
2023-08-30 15:28:32 +00:00
|
|
|
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, 10, 1*time.Minute, 10*time.Second)
|
|
|
|
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, 10, 1*time.Minute, 10*time.Second)
|
|
|
|
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)
|
|
|
|
}
|