milvus/internal/kv/etcd/util_test.go
tinswzy c328fd3c6a
fix: etcd request context contamination by RBAC auth info (#44964)
#44892 fix etcd request context contamination by RBAC auth info
```
When RBAC is enabled, Milvus uses the gRPC metadata library to inject RBAC authentication information into the request context (ctx).
Since etcd’s authentication mechanism also relies on the same metadata library, if the same ctx is passed down to the etcd request, the RBAC auth info from Milvus contaminates the auth information used by etcd.
This causes the etcd server to report an invalid auth token error when RBAC is enabled but etcd auth is disabled.
```

#43638 upgrade wp to v0.1.10

Signed-off-by: tinswzy <zhenyuan.wei@zilliz.com>
2025-10-24 15:38:05 +08:00

181 lines
5.6 KiB
Go

package etcdkv
import (
"context"
"strings"
"testing"
"time"
"github.com/stretchr/testify/suite"
"google.golang.org/grpc/metadata"
"github.com/milvus-io/milvus/pkg/v2/kv/predicates"
"github.com/milvus-io/milvus/pkg/v2/util"
)
type EtcdKVUtilSuite struct {
suite.Suite
}
func (s *EtcdKVUtilSuite) TestParsePredicateType() {
type testCase struct {
tag string
pt predicates.PredicateType
expectResult string
expectSucceed bool
}
cases := []testCase{
{tag: "equal", pt: predicates.PredTypeEqual, expectResult: "=", expectSucceed: true},
{tag: "zero_value", pt: 0, expectResult: "", expectSucceed: false},
}
for _, tc := range cases {
s.Run(tc.tag, func() {
result, err := parsePredicateType(tc.pt)
if tc.expectSucceed {
s.NoError(err)
s.Equal(tc.expectResult, result)
} else {
s.Error(err)
}
})
}
}
func (s *EtcdKVUtilSuite) TestParsePredicates() {
type testCase struct {
tag string
input []predicates.Predicate
expectSucceed bool
}
badPredicate := predicates.NewMockPredicate(s.T())
badPredicate.EXPECT().Target().Return(0)
cases := []testCase{
{tag: "normal_value_equal", input: []predicates.Predicate{predicates.ValueEqual("a", "b")}, expectSucceed: true},
{tag: "empty_input", input: nil, expectSucceed: true},
{tag: "bad_predicates", input: []predicates.Predicate{badPredicate}, expectSucceed: false},
}
for _, tc := range cases {
s.Run(tc.tag, func() {
result, err := parsePredicates("", tc.input...)
if tc.expectSucceed {
s.NoError(err)
s.Equal(len(tc.input), len(result))
} else {
s.Error(err)
}
})
}
}
func (s *EtcdKVUtilSuite) TestGetContextWithTimeout() {
s.Run("remove_auth_from_metadata", func() {
// Create a parent context with metadata including auth information
parentMD := metadata.New(map[string]string{
strings.ToLower(util.HeaderAuthorize): "test-auth-token",
strings.ToLower(util.HeaderToken): "test-token",
"other-key": "other-value",
"tenant-id": "test-tenant",
})
parentCtx := metadata.NewIncomingContext(context.Background(), parentMD)
// Call getContextWithTimeout
newCtx, cancel := getContextWithTimeout(parentCtx, 5*time.Second)
defer cancel()
// Verify that the new context's metadata does not contain auth information
newMD, ok := metadata.FromIncomingContext(newCtx)
s.True(ok, "new context should have metadata")
// Auth keys should be removed
authValues := newMD.Get(strings.ToLower(util.HeaderAuthorize))
s.Empty(authValues, "authorization should be removed from new context")
tokenValues := newMD.Get(strings.ToLower(util.HeaderToken))
s.Empty(tokenValues, "token should be removed from new context")
// Other keys should be preserved
otherValues := newMD.Get("other-key")
s.Equal([]string{"other-value"}, otherValues, "other metadata should be preserved")
tenantValues := newMD.Get("tenant-id")
s.Equal([]string{"test-tenant"}, tenantValues, "tenant-id should be preserved")
// Verify that the parent context's metadata is not affected
parentMDAfter, ok := metadata.FromIncomingContext(parentCtx)
s.True(ok, "parent context should still have metadata")
parentAuthValues := parentMDAfter.Get(strings.ToLower(util.HeaderAuthorize))
s.Equal([]string{"test-auth-token"}, parentAuthValues, "parent context auth should not be modified")
parentTokenValues := parentMDAfter.Get(strings.ToLower(util.HeaderToken))
s.Equal([]string{"test-token"}, parentTokenValues, "parent context token should not be modified")
})
s.Run("context_without_metadata", func() {
// Create a context without metadata
parentCtx := context.Background()
// Call getContextWithTimeout
newCtx, cancel := getContextWithTimeout(parentCtx, 5*time.Second)
defer cancel()
// Verify that the new context is created successfully
s.NotNil(newCtx)
// Verify timeout is set
deadline, ok := newCtx.Deadline()
s.True(ok, "new context should have deadline")
s.True(time.Until(deadline) <= 5*time.Second, "deadline should be within 5 seconds")
})
s.Run("context_with_only_auth_metadata", func() {
// Create a context with only auth metadata
parentMD := metadata.New(map[string]string{
strings.ToLower(util.HeaderAuthorize): "test-auth-token",
strings.ToLower(util.HeaderToken): "test-token",
})
parentCtx := metadata.NewIncomingContext(context.Background(), parentMD)
// Call getContextWithTimeout
newCtx, cancel := getContextWithTimeout(parentCtx, 5*time.Second)
defer cancel()
// Verify that the new context's metadata is empty
newMD, ok := metadata.FromIncomingContext(newCtx)
s.True(ok, "new context should have metadata")
s.Empty(newMD.Get(strings.ToLower(util.HeaderAuthorize)), "authorization should be removed")
s.Empty(newMD.Get(strings.ToLower(util.HeaderToken)), "token should be removed")
})
s.Run("verify_timeout_works", func() {
// Create a context with metadata
parentMD := metadata.New(map[string]string{
"test-key": "test-value",
})
parentCtx := metadata.NewIncomingContext(context.Background(), parentMD)
// Call getContextWithTimeout with a very short timeout
timeout := 100 * time.Millisecond
newCtx, cancel := getContextWithTimeout(parentCtx, timeout)
defer cancel()
// Verify deadline is set correctly
deadline, ok := newCtx.Deadline()
s.True(ok, "context should have deadline")
s.True(time.Until(deadline) <= timeout, "deadline should be within timeout duration")
// Wait for timeout
<-newCtx.Done()
s.ErrorIs(newCtx.Err(), context.DeadlineExceeded, "context should be canceled due to timeout")
})
}
func TestEtcdKVUtil(t *testing.T) {
suite.Run(t, new(EtcdKVUtilSuite))
}