From 330a871979b01510eec4105ff8c74aafe040b9f0 Mon Sep 17 00:00:00 2001 From: Jean-Francois Weber-Marx Date: Tue, 2 Sep 2025 03:57:52 +0000 Subject: [PATCH] enhance: add configuration to allow custom characters in names (#42417) (#44063) related: #42417 - Add NameValidationAllowedChars and RoleNameValidationAllowedChars configuration parameters to specify additional characters allowed respectively in (generic) names and a role names - All validations in validateName method is moved to a the new method validateNameWithCustomChars which is called by both validateName and ValidateRoleName while specifying characters allowed Signed-off-by: Jean-Francois Weber-Marx Signed-off-by: Jean-Francois Weber-Marx --- configs/milvus.yaml | 4 ++ internal/proxy/util.go | 10 ++- internal/proxy/util_test.go | 17 +++++ pkg/util/paramtable/component_param.go | 86 ++++++++++++++++---------- 4 files changed, 80 insertions(+), 37 deletions(-) diff --git a/configs/milvus.yaml b/configs/milvus.yaml index 8c56bdf69b..03f47d88d3 100644 --- a/configs/milvus.yaml +++ b/configs/milvus.yaml @@ -316,6 +316,10 @@ proxy: # please adjust in embedded Milvus: false ginLogging: true ginLogSkipPaths: / # skip url path for gin log + nameValidation: + allowedChars: $ # Additional characters allowed in names beyond underscores, letters and numbers. To allow hyphens in names, add '-' here. + roleNameValidation: + allowedChars: $ # Additional characters allowed in role names beyond underscores, letters and numbers. Add '-' to allow hyphens in role names. maxTaskNum: 1024 # The maximum number of tasks in the task queue of the proxy. ddlConcurrency: 16 # The concurrent execution number of DDL at proxy. dclConcurrency: 16 # The concurrent execution number of DCL at proxy. diff --git a/internal/proxy/util.go b/internal/proxy/util.go index 7cdd13ef50..43db708f47 100644 --- a/internal/proxy/util.go +++ b/internal/proxy/util.go @@ -1283,6 +1283,10 @@ func getMaxMvccTsFromChannels(channelsTs map[string]uint64, beginTs typeutil.Tim } func validateName(entity string, nameType string) error { + return validateNameWithCustomChars(entity, nameType, Params.ProxyCfg.NameValidationAllowedChars.GetValue()) +} + +func validateNameWithCustomChars(entity string, nameType string, allowedChars string) error { entity = strings.TrimSpace(entity) if entity == "" { @@ -1305,15 +1309,15 @@ func validateName(entity string, nameType string) error { for i := 1; i < len(entity); i++ { c := entity[i] - if c != '_' && c != '$' && !isAlpha(c) && !isNumber(c) { - return merr.WrapErrParameterInvalidMsg("%s can only contain numbers, letters, dollars and underscores, found %c at %d", nameType, c, i) + if c != '_' && !isAlpha(c) && !isNumber(c) && !strings.ContainsRune(allowedChars, rune(c)) { + return merr.WrapErrParameterInvalidMsg("%s can only contain numbers, letters, underscores, and allowed characters (%s), found %c at %d", nameType, allowedChars, c, i) } } return nil } func ValidateRoleName(entity string) error { - return validateName(entity, "role name") + return validateNameWithCustomChars(entity, "role name", Params.ProxyCfg.RoleNameValidationAllowedChars.GetValue()) } func IsDefaultRole(roleName string) bool { diff --git a/internal/proxy/util_test.go b/internal/proxy/util_test.go index ffeec73f59..4df7e9b7c4 100644 --- a/internal/proxy/util_test.go +++ b/internal/proxy/util_test.go @@ -708,6 +708,23 @@ func TestValidateName(t *testing.T) { assert.Nil(t, ValidateObjectName("*")) } +func TestValidateRoleName_HyphenToggle(t *testing.T) { + pt := paramtable.Get() + + pt.ProxyCfg.RoleNameValidationAllowedChars.SwapTempValue("$-") + assert.Nil(t, ValidateRoleName("Admin-1")) + assert.Nil(t, ValidateRoleName("_a-bc$1")) + assert.NotNil(t, ValidateRoleName("-bad")) + assert.NotNil(t, ValidateRoleName("1leading")) + assert.NotNil(t, ValidateRoleName("")) + assert.NotNil(t, ValidateRoleName("*")) + + pt.ProxyCfg.RoleNameValidationAllowedChars.SwapTempValue("$") + assert.Nil(t, ValidateRoleName("Admin_1")) + assert.Nil(t, ValidateRoleName("Admin$1")) + assert.NotNil(t, ValidateRoleName("Admin-1")) +} + func TestIsDefaultRole(t *testing.T) { assert.Equal(t, true, IsDefaultRole(util.RoleAdmin)) assert.Equal(t, true, IsDefaultRole(util.RolePublic)) diff --git a/pkg/util/paramtable/component_param.go b/pkg/util/paramtable/component_param.go index 37c21bf328..6ecf547195 100644 --- a/pkg/util/paramtable/component_param.go +++ b/pkg/util/paramtable/component_param.go @@ -1627,40 +1627,42 @@ type proxyConfig struct { // Alias string SoPath ParamItem `refreshable:"false"` - TimeTickInterval ParamItem `refreshable:"false"` - HealthCheckTimeout ParamItem `refreshable:"true"` - MsgStreamTimeTickBufSize ParamItem `refreshable:"true"` - MaxNameLength ParamItem `refreshable:"true"` - MaxUsernameLength ParamItem `refreshable:"true"` - MinPasswordLength ParamItem `refreshable:"true"` - MaxPasswordLength ParamItem `refreshable:"true"` - MaxFieldNum ParamItem `refreshable:"true"` - MaxVectorFieldNum ParamItem `refreshable:"true"` - MaxShardNum ParamItem `refreshable:"true"` - MaxDimension ParamItem `refreshable:"true"` - GinLogging ParamItem `refreshable:"false"` - GinLogSkipPaths ParamItem `refreshable:"false"` - MaxUserNum ParamItem `refreshable:"true"` - MaxRoleNum ParamItem `refreshable:"true"` - MaxTaskNum ParamItem `refreshable:"false"` - DDLConcurrency ParamItem `refreshable:"true"` - DCLConcurrency ParamItem `refreshable:"true"` - ShardLeaderCacheInterval ParamItem `refreshable:"false"` - ReplicaSelectionPolicy ParamItem `refreshable:"false"` - CheckQueryNodeHealthInterval ParamItem `refreshable:"false"` - CostMetricsExpireTime ParamItem `refreshable:"false"` - CheckWorkloadRequestNum ParamItem `refreshable:"false"` - WorkloadToleranceFactor ParamItem `refreshable:"false"` - RetryTimesOnReplica ParamItem `refreshable:"true"` - RetryTimesOnHealthCheck ParamItem `refreshable:"true"` - PartitionNameRegexp ParamItem `refreshable:"true"` - MustUsePartitionKey ParamItem `refreshable:"true"` - SkipAutoIDCheck ParamItem `refreshable:"true"` - SkipPartitionKeyCheck ParamItem `refreshable:"true"` - MaxVarCharLength ParamItem `refreshable:"false"` - MaxTextLength ParamItem `refreshable:"false"` - MaxResultEntries ParamItem `refreshable:"true"` - EnableCachedServiceProvider ParamItem `refreshable:"true"` + TimeTickInterval ParamItem `refreshable:"false"` + HealthCheckTimeout ParamItem `refreshable:"true"` + MsgStreamTimeTickBufSize ParamItem `refreshable:"true"` + MaxNameLength ParamItem `refreshable:"true"` + MaxUsernameLength ParamItem `refreshable:"true"` + MinPasswordLength ParamItem `refreshable:"true"` + MaxPasswordLength ParamItem `refreshable:"true"` + MaxFieldNum ParamItem `refreshable:"true"` + MaxVectorFieldNum ParamItem `refreshable:"true"` + MaxShardNum ParamItem `refreshable:"true"` + MaxDimension ParamItem `refreshable:"true"` + GinLogging ParamItem `refreshable:"false"` + GinLogSkipPaths ParamItem `refreshable:"false"` + MaxUserNum ParamItem `refreshable:"true"` + MaxRoleNum ParamItem `refreshable:"true"` + NameValidationAllowedChars ParamItem `refreshable:"true"` + RoleNameValidationAllowedChars ParamItem `refreshable:"true"` + MaxTaskNum ParamItem `refreshable:"false"` + DDLConcurrency ParamItem `refreshable:"true"` + DCLConcurrency ParamItem `refreshable:"true"` + ShardLeaderCacheInterval ParamItem `refreshable:"false"` + ReplicaSelectionPolicy ParamItem `refreshable:"false"` + CheckQueryNodeHealthInterval ParamItem `refreshable:"false"` + CostMetricsExpireTime ParamItem `refreshable:"false"` + CheckWorkloadRequestNum ParamItem `refreshable:"false"` + WorkloadToleranceFactor ParamItem `refreshable:"false"` + RetryTimesOnReplica ParamItem `refreshable:"true"` + RetryTimesOnHealthCheck ParamItem `refreshable:"true"` + PartitionNameRegexp ParamItem `refreshable:"true"` + MustUsePartitionKey ParamItem `refreshable:"true"` + SkipAutoIDCheck ParamItem `refreshable:"true"` + SkipPartitionKeyCheck ParamItem `refreshable:"true"` + MaxVarCharLength ParamItem `refreshable:"false"` + MaxTextLength ParamItem `refreshable:"false"` + MaxResultEntries ParamItem `refreshable:"true"` + EnableCachedServiceProvider ParamItem `refreshable:"true"` AccessLog AccessLogConfig @@ -1855,6 +1857,22 @@ please adjust in embedded Milvus: false`, } p.MaxRoleNum.Init(base.mgr) + p.NameValidationAllowedChars = ParamItem{ + Key: "proxy.nameValidation.allowedChars", + DefaultValue: "$", + Doc: "Additional characters allowed in names beyond underscores, letters and numbers. To allow hyphens in names, add '-' here.", + Export: true, + } + p.NameValidationAllowedChars.Init(base.mgr) + + p.RoleNameValidationAllowedChars = ParamItem{ + Key: "proxy.roleNameValidation.allowedChars", + DefaultValue: "$", + Doc: "Additional characters allowed in role names beyond underscores, letters and numbers. Add '-' to allow hyphens in role names.", + Export: true, + } + p.RoleNameValidationAllowedChars.Init(base.mgr) + p.SoPath = ParamItem{ Key: "proxy.soPath", Version: "2.2.0",