mirror of
https://github.com/alibaba/higress.git
synced 2026-06-09 04:37:31 +08:00
add variable from secret when applying istio cr (#1877)
This commit is contained in:
@@ -151,6 +151,33 @@ type IngressConfig struct {
|
|||||||
clusterId cluster.ID
|
clusterId cluster.ID
|
||||||
|
|
||||||
httpsConfigMgr *cert.ConfigMgr
|
httpsConfigMgr *cert.ConfigMgr
|
||||||
|
|
||||||
|
// templateProcessor processes template variables in config
|
||||||
|
templateProcessor *TemplateProcessor
|
||||||
|
|
||||||
|
// secretConfigMgr manages secret dependencies
|
||||||
|
secretConfigMgr *SecretConfigMgr
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSecretValue implements the getValue function for secret references
|
||||||
|
func (m *IngressConfig) getSecretValue(valueType, namespace, name, key string) (string, error) {
|
||||||
|
if valueType != "secret" {
|
||||||
|
return "", fmt.Errorf("unsupported value type: %s", valueType)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.mutex.RLock()
|
||||||
|
defer m.mutex.RUnlock()
|
||||||
|
|
||||||
|
for _, controller := range m.remoteIngressControllers {
|
||||||
|
secret, err := controller.SecretLister().Secrets(namespace).Get(name)
|
||||||
|
if err == nil {
|
||||||
|
if value, exists := secret.Data[key]; exists {
|
||||||
|
return string(value), nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("key %s not found in secret %s/%s", key, namespace, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("secret %s/%s not found", namespace, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIngressConfig(localKubeClient kube.Client, xdsUpdater istiomodel.XDSUpdater, namespace string, options common.Options) *IngressConfig {
|
func NewIngressConfig(localKubeClient kube.Client, xdsUpdater istiomodel.XDSUpdater, namespace string, options common.Options) *IngressConfig {
|
||||||
@@ -171,6 +198,13 @@ func NewIngressConfig(localKubeClient kube.Client, xdsUpdater istiomodel.XDSUpda
|
|||||||
wasmPlugins: make(map[string]*extensions.WasmPlugin),
|
wasmPlugins: make(map[string]*extensions.WasmPlugin),
|
||||||
http2rpcs: make(map[string]*higressv1.Http2Rpc),
|
http2rpcs: make(map[string]*higressv1.Http2Rpc),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize secret config manager
|
||||||
|
config.secretConfigMgr = NewSecretConfigMgr(xdsUpdater)
|
||||||
|
|
||||||
|
// Initialize template processor with value getter function
|
||||||
|
config.templateProcessor = NewTemplateProcessor(config.getSecretValue, namespace, config.secretConfigMgr)
|
||||||
|
|
||||||
mcpbridgeController := mcpbridge.NewController(localKubeClient, options)
|
mcpbridgeController := mcpbridge.NewController(localKubeClient, options)
|
||||||
mcpbridgeController.AddEventHandler(config.AddOrUpdateMcpBridge, config.DeleteMcpBridge)
|
mcpbridgeController.AddEventHandler(config.AddOrUpdateMcpBridge, config.DeleteMcpBridge)
|
||||||
config.mcpbridgeController = mcpbridgeController
|
config.mcpbridgeController = mcpbridgeController
|
||||||
@@ -228,6 +262,7 @@ func (m *IngressConfig) RegisterEventHandler(kind config.GroupVersionKind, f ist
|
|||||||
func (m *IngressConfig) AddLocalCluster(options common.Options) {
|
func (m *IngressConfig) AddLocalCluster(options common.Options) {
|
||||||
secretController := secret.NewController(m.localKubeClient, options)
|
secretController := secret.NewController(m.localKubeClient, options)
|
||||||
secretController.AddEventHandler(m.ReflectSecretChanges)
|
secretController.AddEventHandler(m.ReflectSecretChanges)
|
||||||
|
secretController.AddEventHandler(m.secretConfigMgr.HandleSecretChange)
|
||||||
|
|
||||||
var ingressController common.IngressController
|
var ingressController common.IngressController
|
||||||
v1 := common.V1Available(m.localKubeClient)
|
v1 := common.V1Available(m.localKubeClient)
|
||||||
@@ -254,10 +289,24 @@ func (m *IngressConfig) List(typ config.GroupVersionKind, namespace string) []co
|
|||||||
var configs = make([]config.Config, 0)
|
var configs = make([]config.Config, 0)
|
||||||
|
|
||||||
if configsFromIngress := m.listFromIngressControllers(typ, namespace); configsFromIngress != nil {
|
if configsFromIngress := m.listFromIngressControllers(typ, namespace); configsFromIngress != nil {
|
||||||
|
// Process templates for ingress configs
|
||||||
|
for i := range configsFromIngress {
|
||||||
|
if err := m.templateProcessor.ProcessConfig(&configsFromIngress[i]); err != nil {
|
||||||
|
IngressLog.Errorf("Failed to process template for config %s/%s: %v",
|
||||||
|
configsFromIngress[i].Namespace, configsFromIngress[i].Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
configs = append(configs, configsFromIngress...)
|
configs = append(configs, configsFromIngress...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if configsFromGateway := m.listFromGatewayControllers(typ, namespace); configsFromGateway != nil {
|
if configsFromGateway := m.listFromGatewayControllers(typ, namespace); configsFromGateway != nil {
|
||||||
|
// Process templates for gateway configs
|
||||||
|
for i := range configsFromGateway {
|
||||||
|
if err := m.templateProcessor.ProcessConfig(&configsFromGateway[i]); err != nil {
|
||||||
|
IngressLog.Errorf("Failed to process template for config %s/%s: %v",
|
||||||
|
configsFromGateway[i].Namespace, configsFromGateway[i].Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
configs = append(configs, configsFromGateway...)
|
configs = append(configs, configsFromGateway...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -987,7 +1036,6 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isBoolValueTrue(b *wrappers.BoolValue) bool {
|
func isBoolValueTrue(b *wrappers.BoolValue) bool {
|
||||||
|
|||||||
119
pkg/ingress/config/ingress_template.go
Normal file
119
pkg/ingress/config/ingress_template.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
. "github.com/alibaba/higress/pkg/ingress/log"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
"istio.io/istio/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TemplateProcessor handles template substitution in configs
|
||||||
|
type TemplateProcessor struct {
|
||||||
|
// getValue is a function that retrieves values by type, namespace, name and key
|
||||||
|
getValue func(valueType, namespace, name, key string) (string, error)
|
||||||
|
namespace string
|
||||||
|
secretConfigMgr *SecretConfigMgr
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTemplateProcessor creates a new TemplateProcessor with the given value getter function
|
||||||
|
func NewTemplateProcessor(getValue func(valueType, namespace, name, key string) (string, error), namespace string, secretConfigMgr *SecretConfigMgr) *TemplateProcessor {
|
||||||
|
return &TemplateProcessor{
|
||||||
|
getValue: getValue,
|
||||||
|
namespace: namespace,
|
||||||
|
secretConfigMgr: secretConfigMgr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessConfig processes a config and substitutes any template variables
|
||||||
|
func (p *TemplateProcessor) ProcessConfig(cfg *config.Config) error {
|
||||||
|
// Convert spec to JSON string to process substitutions
|
||||||
|
jsonBytes, err := json.Marshal(cfg.Spec)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal config spec: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
configStr := string(jsonBytes)
|
||||||
|
// Find all value references in format:
|
||||||
|
// ${type.name.key} or ${type.namespace/name.key}
|
||||||
|
valueRegex := regexp.MustCompile(`\$\{([^.}]+)\.(?:([^/]+)/)?([^.}]+)\.([^}]+)\}`)
|
||||||
|
matches := valueRegex.FindAllStringSubmatch(configStr, -1)
|
||||||
|
// If there are no value references, return immediately
|
||||||
|
if len(matches) == 0 {
|
||||||
|
if p.secretConfigMgr != nil {
|
||||||
|
if err := p.secretConfigMgr.DeleteConfig(cfg); err != nil {
|
||||||
|
IngressLog.Errorf("failed to delete secret dependency: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
foundSecretSource := false
|
||||||
|
IngressLog.Infof("start to apply config %s/%s with %d variables", cfg.Namespace, cfg.Name, len(matches))
|
||||||
|
for _, match := range matches {
|
||||||
|
valueType := match[1]
|
||||||
|
var namespace, name, key string
|
||||||
|
if match[2] != "" {
|
||||||
|
// Format: ${type.namespace/name.key}
|
||||||
|
namespace = match[2]
|
||||||
|
} else {
|
||||||
|
// Format: ${type.name.key} - use default namespace
|
||||||
|
namespace = p.namespace
|
||||||
|
}
|
||||||
|
name = match[3]
|
||||||
|
key = match[4]
|
||||||
|
|
||||||
|
// Get value using the provided getter function
|
||||||
|
value, err := p.getValue(valueType, namespace, name, key)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get %s value for %s/%s.%s: %v", valueType, namespace, name, key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add secret dependency if this is a secret reference
|
||||||
|
if valueType == "secret" && p.secretConfigMgr != nil {
|
||||||
|
foundSecretSource = true
|
||||||
|
secretKey := fmt.Sprintf("%s/%s", namespace, name)
|
||||||
|
if err := p.secretConfigMgr.AddConfig(secretKey, cfg); err != nil {
|
||||||
|
IngressLog.Errorf("failed to add secret dependency: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Replace placeholder with actual value
|
||||||
|
configStr = strings.Replace(configStr, match[0], value, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new instance of the same type as cfg.Spec
|
||||||
|
newSpec := proto.Clone(cfg.Spec.(proto.Message))
|
||||||
|
if err := json.Unmarshal([]byte(configStr), newSpec); err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal substituted config: %v", err)
|
||||||
|
}
|
||||||
|
cfg.Spec = newSpec
|
||||||
|
|
||||||
|
// Delete secret dependency if no secret reference is found
|
||||||
|
if !foundSecretSource {
|
||||||
|
if p.secretConfigMgr != nil {
|
||||||
|
if err := p.secretConfigMgr.DeleteConfig(cfg); err != nil {
|
||||||
|
IngressLog.Errorf("failed to delete secret dependency: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IngressLog.Infof("end to process config %s/%s", cfg.Namespace, cfg.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
166
pkg/ingress/config/ingress_template_test.go
Normal file
166
pkg/ingress/config/ingress_template_test.go
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
extensions "istio.io/api/extensions/v1alpha1"
|
||||||
|
"istio.io/istio/pkg/config"
|
||||||
|
"istio.io/istio/pkg/config/schema/gvk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTemplateProcessor_ProcessConfig(t *testing.T) {
|
||||||
|
// Create test values map
|
||||||
|
values := map[string]string{
|
||||||
|
"secret.default/test-secret.api_key": "test-api-key",
|
||||||
|
"secret.default/test-secret.plugin_conf.timeout": "5000",
|
||||||
|
"secret.default/test-secret.plugin_conf.max_retries": "3",
|
||||||
|
"secret.higress-system/auth-secret.auth_config.type": "basic",
|
||||||
|
"secret.higress-system/auth-secret.auth_config.credentials": "base64-encoded",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock value getter function
|
||||||
|
getValue := func(valueType, namespace, name, key string) (string, error) {
|
||||||
|
fullKey := fmt.Sprintf("%s.%s/%s.%s", valueType, namespace, name, key)
|
||||||
|
fmt.Printf("Getting value for %s", fullKey)
|
||||||
|
if value, exists := values[fullKey]; exists {
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("value not found for %s", fullKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create template processor
|
||||||
|
processor := NewTemplateProcessor(getValue, "higress-system", nil)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
wasmPlugin *extensions.WasmPlugin
|
||||||
|
expected *extensions.WasmPlugin
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple api key reference",
|
||||||
|
wasmPlugin: &extensions.WasmPlugin{
|
||||||
|
PluginName: "test-plugin",
|
||||||
|
PluginConfig: makeStructValue(t, map[string]interface{}{
|
||||||
|
"api_key": "${secret.default/test-secret.api_key}",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
expected: &extensions.WasmPlugin{
|
||||||
|
PluginName: "test-plugin",
|
||||||
|
PluginConfig: makeStructValue(t, map[string]interface{}{
|
||||||
|
"api_key": "test-api-key",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config with multiple fields",
|
||||||
|
wasmPlugin: &extensions.WasmPlugin{
|
||||||
|
PluginName: "test-plugin",
|
||||||
|
PluginConfig: makeStructValue(t, map[string]interface{}{
|
||||||
|
"config": map[string]interface{}{
|
||||||
|
"timeout": "${secret.default/test-secret.plugin_conf.timeout}",
|
||||||
|
"max_retries": "${secret.default/test-secret.plugin_conf.max_retries}",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
expected: &extensions.WasmPlugin{
|
||||||
|
PluginName: "test-plugin",
|
||||||
|
PluginConfig: makeStructValue(t, map[string]interface{}{
|
||||||
|
"config": map[string]interface{}{
|
||||||
|
"timeout": "5000",
|
||||||
|
"max_retries": "3",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "auth config with default namespace",
|
||||||
|
wasmPlugin: &extensions.WasmPlugin{
|
||||||
|
PluginName: "test-plugin",
|
||||||
|
PluginConfig: makeStructValue(t, map[string]interface{}{
|
||||||
|
"auth": map[string]interface{}{
|
||||||
|
"type": "${secret.auth-secret.auth_config.type}",
|
||||||
|
"credentials": "${secret.auth-secret.auth_config.credentials}",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
expected: &extensions.WasmPlugin{
|
||||||
|
PluginName: "test-plugin",
|
||||||
|
PluginConfig: makeStructValue(t, map[string]interface{}{
|
||||||
|
"auth": map[string]interface{}{
|
||||||
|
"type": "basic",
|
||||||
|
"credentials": "base64-encoded",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-existent secret",
|
||||||
|
wasmPlugin: &extensions.WasmPlugin{
|
||||||
|
PluginName: "test-plugin",
|
||||||
|
PluginConfig: makeStructValue(t, map[string]interface{}{
|
||||||
|
"api_key": "${secret.default/non-existent.api_key}",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
cfg := &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
GroupVersionKind: gvk.WasmPlugin,
|
||||||
|
Name: "test-plugin",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
Spec: tt.wasmPlugin,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := processor.ProcessConfig(cfg)
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
processedPlugin := cfg.Spec.(*extensions.WasmPlugin)
|
||||||
|
|
||||||
|
// Compare plugin name
|
||||||
|
assert.Equal(t, tt.expected.PluginName, processedPlugin.PluginName)
|
||||||
|
|
||||||
|
// Compare plugin configs
|
||||||
|
if tt.expected.PluginConfig != nil {
|
||||||
|
assert.NotNil(t, processedPlugin.PluginConfig)
|
||||||
|
assert.Equal(t, tt.expected.PluginConfig.AsMap(), processedPlugin.PluginConfig.AsMap())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to create structpb.Struct from map
|
||||||
|
func makeStructValue(t *testing.T, m map[string]interface{}) *structpb.Struct {
|
||||||
|
s, err := structpb.NewStruct(m)
|
||||||
|
assert.NoError(t, err, "Failed to create struct value")
|
||||||
|
return s
|
||||||
|
}
|
||||||
157
pkg/ingress/config/secret_config_mgr.go
Normal file
157
pkg/ingress/config/secret_config_mgr.go
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/pkg/ingress/kube/util"
|
||||||
|
. "github.com/alibaba/higress/pkg/ingress/log"
|
||||||
|
istiomodel "istio.io/istio/pilot/pkg/model"
|
||||||
|
"istio.io/istio/pkg/config"
|
||||||
|
"istio.io/istio/pkg/config/schema/kind"
|
||||||
|
"istio.io/istio/pkg/util/sets"
|
||||||
|
)
|
||||||
|
|
||||||
|
// toConfigKey converts config.Config to istiomodel.ConfigKey
|
||||||
|
func toConfigKey(cfg *config.Config) (istiomodel.ConfigKey, error) {
|
||||||
|
return istiomodel.ConfigKey{
|
||||||
|
Kind: kind.MustFromGVK(cfg.GroupVersionKind),
|
||||||
|
Name: cfg.Name,
|
||||||
|
Namespace: cfg.Namespace,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecretConfigMgr maintains the mapping between secrets and configs
|
||||||
|
type SecretConfigMgr struct {
|
||||||
|
mutex sync.RWMutex
|
||||||
|
|
||||||
|
// configSet tracks all configs that have been added
|
||||||
|
// key format: namespace/name
|
||||||
|
configSet sets.Set[string]
|
||||||
|
|
||||||
|
// secretToConfigs maps secret key to dependent configs
|
||||||
|
// key format: namespace/name
|
||||||
|
secretToConfigs map[string]sets.Set[istiomodel.ConfigKey]
|
||||||
|
|
||||||
|
// watchedSecrets tracks which secrets are being watched
|
||||||
|
watchedSecrets sets.Set[string]
|
||||||
|
|
||||||
|
// xdsUpdater is used to push config updates
|
||||||
|
xdsUpdater istiomodel.XDSUpdater
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSecretConfigMgr creates a new SecretConfigMgr
|
||||||
|
func NewSecretConfigMgr(xdsUpdater istiomodel.XDSUpdater) *SecretConfigMgr {
|
||||||
|
return &SecretConfigMgr{
|
||||||
|
secretToConfigs: make(map[string]sets.Set[istiomodel.ConfigKey]),
|
||||||
|
watchedSecrets: sets.New[string](),
|
||||||
|
configSet: sets.New[string](),
|
||||||
|
xdsUpdater: xdsUpdater,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddConfig adds a config and its secret dependencies
|
||||||
|
func (m *SecretConfigMgr) AddConfig(secretKey string, cfg *config.Config) error {
|
||||||
|
configKey, _ := toConfigKey(cfg)
|
||||||
|
|
||||||
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
configId := fmt.Sprintf("%s/%s", cfg.Namespace, cfg.Name)
|
||||||
|
m.configSet.Insert(configId)
|
||||||
|
|
||||||
|
if configs, exists := m.secretToConfigs[secretKey]; exists {
|
||||||
|
configs.Insert(configKey)
|
||||||
|
} else {
|
||||||
|
m.secretToConfigs[secretKey] = sets.New(configKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to watched secrets
|
||||||
|
m.watchedSecrets.Insert(secretKey)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteConfig removes a config from all secret dependencies
|
||||||
|
func (m *SecretConfigMgr) DeleteConfig(cfg *config.Config) error {
|
||||||
|
configKey, _ := toConfigKey(cfg)
|
||||||
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
|
||||||
|
configId := fmt.Sprintf("%s/%s", cfg.Namespace, cfg.Name)
|
||||||
|
if !m.configSet.Contains(configId) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
removeKeys := make([]string, 0)
|
||||||
|
// Find and remove the config from all secrets
|
||||||
|
for secretKey, configs := range m.secretToConfigs {
|
||||||
|
if configs.Contains(configKey) {
|
||||||
|
configs.Delete(configKey)
|
||||||
|
// If no more configs depend on this secret, remove it
|
||||||
|
if configs.Len() == 0 {
|
||||||
|
removeKeys = append(removeKeys, secretKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the secrets from the secretToConfigs map
|
||||||
|
for _, secretKey := range removeKeys {
|
||||||
|
delete(m.secretToConfigs, secretKey)
|
||||||
|
m.watchedSecrets.Delete(secretKey)
|
||||||
|
}
|
||||||
|
// Remove the config from the config set
|
||||||
|
m.configSet.Delete(configId)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfigsForSecret returns all configs that depend on the given secret
|
||||||
|
func (m *SecretConfigMgr) GetConfigsForSecret(secretKey string) []istiomodel.ConfigKey {
|
||||||
|
m.mutex.RLock()
|
||||||
|
defer m.mutex.RUnlock()
|
||||||
|
|
||||||
|
if configs, exists := m.secretToConfigs[secretKey]; exists {
|
||||||
|
return configs.UnsortedList()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSecretWatched checks if a secret is being watched
|
||||||
|
func (m *SecretConfigMgr) IsSecretWatched(secretKey string) bool {
|
||||||
|
m.mutex.RLock()
|
||||||
|
defer m.mutex.RUnlock()
|
||||||
|
return m.watchedSecrets.Contains(secretKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleSecretChange handles secret changes and updates affected configs
|
||||||
|
func (m *SecretConfigMgr) HandleSecretChange(name util.ClusterNamespacedName) {
|
||||||
|
secretKey := fmt.Sprintf("%s/%s", name.Namespace, name.Name)
|
||||||
|
// Check if this secret is being watched
|
||||||
|
if !m.IsSecretWatched(secretKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get affected configs
|
||||||
|
configKeys := m.GetConfigsForSecret(secretKey)
|
||||||
|
if len(configKeys) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
IngressLog.Infof("SecretConfigMgr Secret %s changed, updating %d dependent configs and push", secretKey, len(configKeys))
|
||||||
|
m.xdsUpdater.ConfigUpdate(&istiomodel.PushRequest{
|
||||||
|
Full: true,
|
||||||
|
Reason: istiomodel.NewReasonStats(istiomodel.SecretTrigger),
|
||||||
|
})
|
||||||
|
}
|
||||||
155
pkg/ingress/config/secret_config_mgr_test.go
Normal file
155
pkg/ingress/config/secret_config_mgr_test.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/pkg/ingress/kube/util"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
istiomodel "istio.io/istio/pilot/pkg/model"
|
||||||
|
"istio.io/istio/pkg/cluster"
|
||||||
|
"istio.io/istio/pkg/config"
|
||||||
|
"istio.io/istio/pkg/config/schema/gvk"
|
||||||
|
"istio.io/istio/pkg/config/schema/kind"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockXdsUpdater struct {
|
||||||
|
lastPushRequest *istiomodel.PushRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockXdsUpdater) EDSUpdate(shard istiomodel.ShardKey, hostname string, namespace string, entry []*istiomodel.IstioEndpoint) {
|
||||||
|
//TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockXdsUpdater) EDSCacheUpdate(shard istiomodel.ShardKey, hostname string, namespace string, entry []*istiomodel.IstioEndpoint) {
|
||||||
|
//TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockXdsUpdater) SvcUpdate(shard istiomodel.ShardKey, hostname string, namespace string, event istiomodel.Event) {
|
||||||
|
//TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockXdsUpdater) ProxyUpdate(clusterID cluster.ID, ip string) {
|
||||||
|
//TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockXdsUpdater) RemoveShard(shardKey istiomodel.ShardKey) {
|
||||||
|
//TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockXdsUpdater) ConfigUpdate(req *istiomodel.PushRequest) {
|
||||||
|
m.lastPushRequest = req
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecretConfigMgr(t *testing.T) {
|
||||||
|
updater := &mockXdsUpdater{}
|
||||||
|
mgr := NewSecretConfigMgr(updater)
|
||||||
|
|
||||||
|
// Test AddConfig
|
||||||
|
t.Run("AddConfig", func(t *testing.T) {
|
||||||
|
wasmPlugin := &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
GroupVersionKind: gvk.WasmPlugin,
|
||||||
|
Name: "test-plugin",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mgr.AddConfig("default/test-secret", wasmPlugin)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, mgr.IsSecretWatched("default/test-secret"))
|
||||||
|
|
||||||
|
configs := mgr.GetConfigsForSecret("default/test-secret")
|
||||||
|
assert.Len(t, configs, 1)
|
||||||
|
assert.Equal(t, kind.WasmPlugin, configs[0].Kind)
|
||||||
|
assert.Equal(t, "test-plugin", configs[0].Name)
|
||||||
|
assert.Equal(t, "default", configs[0].Namespace)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test DeleteConfig
|
||||||
|
t.Run("DeleteConfig", func(t *testing.T) {
|
||||||
|
wasmPlugin := &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
GroupVersionKind: gvk.WasmPlugin,
|
||||||
|
Name: "test-plugin",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mgr.DeleteConfig(wasmPlugin)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, mgr.IsSecretWatched("default/test-secret"))
|
||||||
|
assert.Empty(t, mgr.GetConfigsForSecret("default/test-secret"))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test HandleSecretChange
|
||||||
|
t.Run("HandleSecretChange", func(t *testing.T) {
|
||||||
|
// Add a config first
|
||||||
|
wasmPlugin := &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
GroupVersionKind: gvk.WasmPlugin,
|
||||||
|
Name: "test-plugin",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := mgr.AddConfig("default/test-secret", wasmPlugin)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Test secret change
|
||||||
|
secretName := util.ClusterNamespacedName{
|
||||||
|
NamespacedName: types.NamespacedName{
|
||||||
|
Name: "test-secret",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mgr.HandleSecretChange(secretName)
|
||||||
|
assert.NotNil(t, updater.lastPushRequest)
|
||||||
|
assert.True(t, updater.lastPushRequest.Full)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test full push for secret update
|
||||||
|
t.Run("FullPushForSecretUpdate", func(t *testing.T) {
|
||||||
|
// Add a secret config
|
||||||
|
secretConfig := &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
GroupVersionKind: gvk.Secret,
|
||||||
|
Name: "test-secret",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := mgr.AddConfig("default/test-secret", secretConfig)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Update the secret
|
||||||
|
secretName := util.ClusterNamespacedName{
|
||||||
|
NamespacedName: types.NamespacedName{
|
||||||
|
Name: "test-secret",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mgr.HandleSecretChange(secretName)
|
||||||
|
assert.NotNil(t, updater.lastPushRequest)
|
||||||
|
assert.True(t, updater.lastPushRequest.Full)
|
||||||
|
})
|
||||||
|
}
|
||||||
192
test/e2e/conformance/tests/go-wasm-basic-auth-template.go
Normal file
192
test/e2e/conformance/tests/go-wasm-basic-auth-template.go
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alibaba/higress/test/e2e/conformance/utils/kubernetes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/test/e2e/conformance/utils/http"
|
||||||
|
"github.com/alibaba/higress/test/e2e/conformance/utils/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(WasmPluginsBasicAuthTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
var WasmPluginsBasicAuthTemplate = suite.ConformanceTest{
|
||||||
|
ShortName: "WasmPluginsBasicAuthTemplate",
|
||||||
|
Description: "The Ingress in the higress-conformance-infra namespace test the basic-auth WASM plugin.",
|
||||||
|
Manifests: []string{"tests/go-wasm-basic-auth-template.yaml"},
|
||||||
|
Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature},
|
||||||
|
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||||
|
testcases := []http.Assertion{
|
||||||
|
{
|
||||||
|
Meta: http.AssertionMeta{
|
||||||
|
TestCaseName: "case 1: Successful authentication",
|
||||||
|
TargetBackend: "infra-backend-v1",
|
||||||
|
TargetNamespace: "higress-conformance-infra",
|
||||||
|
},
|
||||||
|
Request: http.AssertionRequest{
|
||||||
|
ActualRequest: http.Request{
|
||||||
|
Host: "foo.com",
|
||||||
|
Path: "/foo",
|
||||||
|
Headers: map[string]string{"Authorization": "Basic YWRtaW46MTIzNDU2"}, // base64("admin:123456")
|
||||||
|
},
|
||||||
|
ExpectedRequest: &http.ExpectedRequest{
|
||||||
|
Request: http.Request{
|
||||||
|
Host: "foo.com",
|
||||||
|
Path: "/foo",
|
||||||
|
Headers: map[string]string{"X-Mse-Consumer": "consumer1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Response: http.AssertionResponse{
|
||||||
|
ExpectedResponse: http.Response{
|
||||||
|
StatusCode: 200,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Meta: http.AssertionMeta{
|
||||||
|
TestCaseName: "case 2: No Basic Authentication information found",
|
||||||
|
TargetBackend: "infra-backend-v1",
|
||||||
|
TargetNamespace: "higress-conformance-infra",
|
||||||
|
},
|
||||||
|
Request: http.AssertionRequest{
|
||||||
|
ActualRequest: http.Request{
|
||||||
|
Host: "foo.com",
|
||||||
|
Path: "/foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Response: http.AssertionResponse{
|
||||||
|
ExpectedResponse: http.Response{
|
||||||
|
StatusCode: 401,
|
||||||
|
},
|
||||||
|
AdditionalResponseHeaders: map[string]string{
|
||||||
|
"WWW-Authenticate": "Basic realm=MSE Gateway",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Meta: http.AssertionMeta{
|
||||||
|
TestCaseName: "case 3: Invalid username and/or password",
|
||||||
|
TargetBackend: "infra-backend-v1",
|
||||||
|
TargetNamespace: "higress-conformance-infra",
|
||||||
|
},
|
||||||
|
Request: http.AssertionRequest{
|
||||||
|
ActualRequest: http.Request{
|
||||||
|
Host: "foo.com",
|
||||||
|
Path: "/foo",
|
||||||
|
Headers: map[string]string{"Authorization": "Basic YWRtaW46cXdlcg=="}, // base64("admin:qwer")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Response: http.AssertionResponse{
|
||||||
|
ExpectedResponse: http.Response{
|
||||||
|
StatusCode: 401,
|
||||||
|
},
|
||||||
|
AdditionalResponseHeaders: map[string]string{
|
||||||
|
"WWW-Authenticate": "Basic realm=MSE Gateway",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Meta: http.AssertionMeta{
|
||||||
|
TestCaseName: "case 4: Unauthorized consumer",
|
||||||
|
TargetBackend: "infra-backend-v1",
|
||||||
|
TargetNamespace: "higress-conformance-infra",
|
||||||
|
},
|
||||||
|
Request: http.AssertionRequest{
|
||||||
|
ActualRequest: http.Request{
|
||||||
|
Host: "foo.com",
|
||||||
|
Path: "/foo",
|
||||||
|
Headers: map[string]string{"Authorization": "Basic Z3Vlc3Q6YWJj"}, // base64("guest:abc")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Response: http.AssertionResponse{
|
||||||
|
ExpectedResponse: http.Response{
|
||||||
|
StatusCode: 403,
|
||||||
|
},
|
||||||
|
AdditionalResponseHeaders: map[string]string{
|
||||||
|
"WWW-Authenticate": "Basic realm=MSE Gateway",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testcases2 := []http.Assertion{
|
||||||
|
{
|
||||||
|
Meta: http.AssertionMeta{
|
||||||
|
TestCaseName: "case 5: Invalid username and/or password",
|
||||||
|
TargetBackend: "infra-backend-v1",
|
||||||
|
TargetNamespace: "higress-conformance-infra",
|
||||||
|
},
|
||||||
|
Request: http.AssertionRequest{
|
||||||
|
ActualRequest: http.Request{
|
||||||
|
Host: "foo.com",
|
||||||
|
Path: "/foo",
|
||||||
|
Headers: map[string]string{"Authorization": "Basic YWRtaW46MTIzNDU2"}, // base64("admin:123456")
|
||||||
|
},
|
||||||
|
ExpectedRequest: &http.ExpectedRequest{
|
||||||
|
Request: http.Request{
|
||||||
|
Host: "foo.com",
|
||||||
|
Path: "/foo",
|
||||||
|
Headers: map[string]string{"X-Mse-Consumer": "consumer1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Response: http.AssertionResponse{
|
||||||
|
ExpectedResponse: http.Response{
|
||||||
|
StatusCode: 401,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Meta: http.AssertionMeta{
|
||||||
|
TestCaseName: "case 6: Successful authentication",
|
||||||
|
TargetBackend: "infra-backend-v1",
|
||||||
|
TargetNamespace: "higress-conformance-infra",
|
||||||
|
},
|
||||||
|
Request: http.AssertionRequest{
|
||||||
|
ActualRequest: http.Request{
|
||||||
|
Host: "foo.com",
|
||||||
|
Path: "/foo",
|
||||||
|
Headers: map[string]string{"Authorization": "Basic YWRtaW46cXdlcg=="}, // base64("admin:qwer")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Response: http.AssertionResponse{
|
||||||
|
ExpectedResponse: http.Response{
|
||||||
|
StatusCode: 200,
|
||||||
|
},
|
||||||
|
AdditionalResponseHeaders: map[string]string{
|
||||||
|
"WWW-Authenticate": "Basic realm=MSE Gateway",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
t.Run("WasmPlugins basic-auth", func(t *testing.T) {
|
||||||
|
for _, testcase := range testcases {
|
||||||
|
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
|
||||||
|
}
|
||||||
|
err := kubernetes.ApplySecret(t, suite.Client, "higress-conformance-infra", "auth-secret", "auth.credential1", "admin:qwer")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("can't apply secret %s in namespace %s for data key %s", "auth-secret", "higress-conformance-infra", "auth.credential1")
|
||||||
|
}
|
||||||
|
for _, testcase := range testcases2 {
|
||||||
|
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
77
test/e2e/conformance/tests/go-wasm-basic-auth-template.yaml
Normal file
77
test/e2e/conformance/tests/go-wasm-basic-auth-template.yaml
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: auth-secret
|
||||||
|
namespace: higress-conformance-infra
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
auth.credential1: "admin:123456"
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: auth-secret
|
||||||
|
namespace: higress-system
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
auth.credential2: "guest:abc"
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
name: wasmplugin-basic-auth
|
||||||
|
namespace: higress-conformance-infra
|
||||||
|
spec:
|
||||||
|
ingressClassName: higress
|
||||||
|
rules:
|
||||||
|
- host: "foo.com"
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- pathType: Prefix
|
||||||
|
path: "/foo"
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: infra-backend-v1
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
|
---
|
||||||
|
apiVersion: extensions.higress.io/v1alpha1
|
||||||
|
kind: WasmPlugin
|
||||||
|
metadata:
|
||||||
|
name: basic-auth
|
||||||
|
namespace: higress-system
|
||||||
|
spec:
|
||||||
|
defaultConfig:
|
||||||
|
consumers:
|
||||||
|
- credential: ${secret.higress-conformance-infra/auth-secret.auth.credential1}
|
||||||
|
name: consumer1
|
||||||
|
- credential: ${secret.auth-secret.auth.credential2}
|
||||||
|
name: consumer2
|
||||||
|
global_auth: false
|
||||||
|
defaultConfigDisable: false
|
||||||
|
matchRules:
|
||||||
|
- config:
|
||||||
|
allow:
|
||||||
|
- consumer1
|
||||||
|
configDisable: false
|
||||||
|
ingress:
|
||||||
|
- higress-conformance-infra/wasmplugin-basic-auth
|
||||||
|
url: oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/go-basic-auth:1.0.0
|
||||||
@@ -144,3 +144,15 @@ func ApplyConfigmapDataWithYaml(t *testing.T, c client.Client, namespace string,
|
|||||||
t.Logf("🏗 Updating %s %s", name, namespace)
|
t.Logf("🏗 Updating %s %s", name, namespace)
|
||||||
return c.Update(ctx, cm)
|
return c.Update(ctx, cm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ApplySecret(t *testing.T, c client.Client, namespace string, name string, key string, val string) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
cm := &v1.Secret{}
|
||||||
|
if err := c.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, cm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cm.Data[key] = []byte(val)
|
||||||
|
t.Logf("🏗 Updating Secret %s %s", name, namespace)
|
||||||
|
return c.Update(ctx, cm)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user