mirror of
https://github.com/alibaba/higress.git
synced 2026-03-19 01:37:28 +08:00
feature: support secret reference for Redis password in MCP Server (#3006)
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
This commit is contained in:
@@ -39,10 +39,22 @@ type RedisConfig struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
// The password for Redis authentication
|
||||
Password string `json:"password,omitempty"`
|
||||
// Reference to a secret containing the password
|
||||
PasswordSecret *SecretKeyReference `json:"passwordSecret,omitempty"`
|
||||
// The database index to use
|
||||
DB int `json:"db,omitempty"`
|
||||
}
|
||||
|
||||
// SecretKeyReference defines a reference to a key within a Kubernetes secret
|
||||
type SecretKeyReference struct {
|
||||
// The namespace of the secret. Defaults to the higress system namespace.
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
// The name of the secret
|
||||
Name string `json:"name,omitempty"`
|
||||
// The key within the secret data
|
||||
Key string `json:"key,omitempty"`
|
||||
}
|
||||
|
||||
// MCPRatelimitConfig defines the configuration for rate limit
|
||||
type MCPRatelimitConfig struct {
|
||||
// The limit of the rate limit
|
||||
@@ -119,6 +131,15 @@ func validMcpServer(m *McpServer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if m.Redis != nil && m.Redis.PasswordSecret != nil {
|
||||
if m.Redis.PasswordSecret.Name == "" {
|
||||
return errors.New("redis passwordSecret.name cannot be empty")
|
||||
}
|
||||
if m.Redis.PasswordSecret.Key == "" {
|
||||
return errors.New("redis passwordSecret.key cannot be empty")
|
||||
}
|
||||
}
|
||||
|
||||
if m.EnableUserLevelServer && m.Redis == nil {
|
||||
return errors.New("redis config cannot be empty when user level server is enabled")
|
||||
}
|
||||
@@ -184,6 +205,13 @@ func deepCopyMcpServer(mcp *McpServer) (*McpServer, error) {
|
||||
Password: mcp.Redis.Password,
|
||||
DB: mcp.Redis.DB,
|
||||
}
|
||||
if mcp.Redis.PasswordSecret != nil {
|
||||
newMcp.Redis.PasswordSecret = &SecretKeyReference{
|
||||
Namespace: mcp.Redis.PasswordSecret.Namespace,
|
||||
Name: mcp.Redis.PasswordSecret.Name,
|
||||
Key: mcp.Redis.PasswordSecret.Key,
|
||||
}
|
||||
}
|
||||
}
|
||||
if mcp.Ratelimit != nil {
|
||||
newMcp.Ratelimit = &MCPRatelimitConfig{
|
||||
@@ -504,12 +532,24 @@ func (m *McpServerController) constructMcpSessionStruct(mcp *McpServer) string {
|
||||
// Build redis configuration
|
||||
redisConfig := "null"
|
||||
if mcp.Redis != nil {
|
||||
passwordValue := mcp.Redis.Password
|
||||
if mcp.Redis.PasswordSecret != nil && mcp.Redis.PasswordSecret.Name != "" && mcp.Redis.PasswordSecret.Key != "" {
|
||||
ns := mcp.Redis.PasswordSecret.Namespace
|
||||
if ns == "" {
|
||||
ns = m.Namespace
|
||||
}
|
||||
if ns != "" {
|
||||
passwordValue = fmt.Sprintf("${secret.%s/%s.%s}", ns, mcp.Redis.PasswordSecret.Name, mcp.Redis.PasswordSecret.Key)
|
||||
} else {
|
||||
passwordValue = fmt.Sprintf("${secret.%s.%s}", mcp.Redis.PasswordSecret.Name, mcp.Redis.PasswordSecret.Key)
|
||||
}
|
||||
}
|
||||
redisConfig = fmt.Sprintf(`{
|
||||
"address": "%s",
|
||||
"username": "%s",
|
||||
"password": "%s",
|
||||
"db": %d
|
||||
}`, mcp.Redis.Address, mcp.Redis.Username, mcp.Redis.Password, mcp.Redis.DB)
|
||||
}`, mcp.Redis.Address, mcp.Redis.Username, passwordValue, mcp.Redis.DB)
|
||||
}
|
||||
|
||||
// Build rate limit configuration
|
||||
|
||||
@@ -120,6 +120,30 @@ func Test_validMcpServer(t *testing.T) {
|
||||
},
|
||||
wantErr: errors.New("redis config cannot be empty when user level server is enabled"),
|
||||
},
|
||||
{
|
||||
name: "redis config with password secret missing name",
|
||||
mcp: &McpServer{
|
||||
Enable: true,
|
||||
Redis: &RedisConfig{
|
||||
PasswordSecret: &SecretKeyReference{
|
||||
Key: "password",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: errors.New("redis passwordSecret.name cannot be empty"),
|
||||
},
|
||||
{
|
||||
name: "redis config with password secret missing key",
|
||||
mcp: &McpServer{
|
||||
Enable: true,
|
||||
Redis: &RedisConfig{
|
||||
PasswordSecret: &SecretKeyReference{
|
||||
Name: "redis-credentials",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: errors.New("redis passwordSecret.key cannot be empty"),
|
||||
},
|
||||
{
|
||||
name: "valid config with redis",
|
||||
mcp: &McpServer{
|
||||
@@ -152,6 +176,20 @@ func Test_validMcpServer(t *testing.T) {
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "valid config with redis password secret",
|
||||
mcp: &McpServer{
|
||||
Enable: true,
|
||||
Redis: &RedisConfig{
|
||||
Address: "localhost:6379",
|
||||
PasswordSecret: &SecretKeyReference{
|
||||
Name: "redis-credentials",
|
||||
Key: "password",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -265,7 +303,11 @@ func Test_deepCopyMcpServer(t *testing.T) {
|
||||
Address: "localhost:6379",
|
||||
Username: "default",
|
||||
Password: "password",
|
||||
DB: 0,
|
||||
PasswordSecret: &SecretKeyReference{
|
||||
Name: "redis-credentials",
|
||||
Key: "password",
|
||||
},
|
||||
DB: 0,
|
||||
},
|
||||
MatchList: []*MatchRule{},
|
||||
Servers: []*SSEServer{},
|
||||
@@ -276,7 +318,11 @@ func Test_deepCopyMcpServer(t *testing.T) {
|
||||
Address: "localhost:6379",
|
||||
Username: "default",
|
||||
Password: "password",
|
||||
DB: 0,
|
||||
PasswordSecret: &SecretKeyReference{
|
||||
Name: "redis-credentials",
|
||||
Key: "password",
|
||||
},
|
||||
DB: 0,
|
||||
},
|
||||
MatchList: []*MatchRule{},
|
||||
Servers: []*SSEServer{},
|
||||
@@ -291,7 +337,12 @@ func Test_deepCopyMcpServer(t *testing.T) {
|
||||
Address: "localhost:6379",
|
||||
Username: "default",
|
||||
Password: "password",
|
||||
DB: 0,
|
||||
PasswordSecret: &SecretKeyReference{
|
||||
Name: "redis-credentials",
|
||||
Namespace: "custom-ns",
|
||||
Key: "password",
|
||||
},
|
||||
DB: 0,
|
||||
},
|
||||
SSEPathSuffix: "/sse",
|
||||
MatchList: []*MatchRule{
|
||||
@@ -318,7 +369,12 @@ func Test_deepCopyMcpServer(t *testing.T) {
|
||||
Address: "localhost:6379",
|
||||
Username: "default",
|
||||
Password: "password",
|
||||
DB: 0,
|
||||
PasswordSecret: &SecretKeyReference{
|
||||
Name: "redis-credentials",
|
||||
Namespace: "custom-ns",
|
||||
Key: "password",
|
||||
},
|
||||
DB: 0,
|
||||
},
|
||||
SSEPathSuffix: "/sse",
|
||||
MatchList: []*MatchRule{
|
||||
@@ -706,6 +762,80 @@ func TestMcpServerController_constructMcpSessionStruct(t *testing.T) {
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "config with password secret",
|
||||
mcp: &McpServer{
|
||||
Enable: true,
|
||||
Redis: &RedisConfig{
|
||||
Address: "localhost:6379",
|
||||
Password: "ignored",
|
||||
PasswordSecret: &SecretKeyReference{
|
||||
Name: "redis-credentials",
|
||||
Key: "password",
|
||||
},
|
||||
},
|
||||
MatchList: []*MatchRule{},
|
||||
Servers: []*SSEServer{},
|
||||
},
|
||||
wantJSON: `{
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config",
|
||||
"library_id": "mcp-session",
|
||||
"library_path": "/var/lib/istio/envoy/golang-filter.so",
|
||||
"plugin_name": "mcp-session",
|
||||
"plugin_config": {
|
||||
"@type": "type.googleapis.com/xds.type.v3.TypedStruct",
|
||||
"value": {
|
||||
"redis": {
|
||||
"address": "localhost:6379",
|
||||
"username": "",
|
||||
"password": "${secret.test-namespace/redis-credentials.password}",
|
||||
"db": 0
|
||||
},
|
||||
"rate_limit": null,
|
||||
"sse_path_suffix": "",
|
||||
"match_list": [],
|
||||
"enable_user_level_server": false
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "config with password secret and namespace",
|
||||
mcp: &McpServer{
|
||||
Enable: true,
|
||||
Redis: &RedisConfig{
|
||||
Address: "localhost:6379",
|
||||
PasswordSecret: &SecretKeyReference{
|
||||
Namespace: "other-ns",
|
||||
Name: "redis-credentials",
|
||||
Key: "password",
|
||||
},
|
||||
},
|
||||
MatchList: []*MatchRule{},
|
||||
Servers: []*SSEServer{},
|
||||
},
|
||||
wantJSON: `{
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config",
|
||||
"library_id": "mcp-session",
|
||||
"library_path": "/var/lib/istio/envoy/golang-filter.so",
|
||||
"plugin_name": "mcp-session",
|
||||
"plugin_config": {
|
||||
"@type": "type.googleapis.com/xds.type.v3.TypedStruct",
|
||||
"value": {
|
||||
"redis": {
|
||||
"address": "localhost:6379",
|
||||
"username": "",
|
||||
"password": "${secret.other-ns/redis-credentials.password}",
|
||||
"db": 0
|
||||
},
|
||||
"rate_limit": null,
|
||||
"sse_path_suffix": "",
|
||||
"match_list": [],
|
||||
"enable_user_level_server": false
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
Reference in New Issue
Block a user