blob: 965d6a3eb589d6cd458ce4ec801c87b048175534 [file] [log] [blame]
// Copyright Istio Authors
//
// 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 xds_test
import (
"reflect"
"testing"
"time"
)
import (
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
extensionsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/wasm/v3"
discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
extensions "istio.io/api/extensions/v1alpha1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/model"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/xds"
v3 "github.com/apache/dubbo-go-pixiu/pilot/pkg/xds/v3"
"github.com/apache/dubbo-go-pixiu/pkg/config"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/gvk"
"github.com/apache/dubbo-go-pixiu/pkg/spiffe"
"github.com/apache/dubbo-go-pixiu/pkg/util/sets"
)
func TestECDS(t *testing.T) {
s := xds.NewFakeDiscoveryServer(t, xds.FakeOptions{
ConfigString: mustReadFile(t, "./testdata/ecds.yaml"),
})
ads := s.ConnectADS().WithType(v3.ExtensionConfigurationType)
wantExtensionConfigName := "extension-config"
md := model.NodeMetadata{
ClusterID: "Kubernetes",
}
res := ads.RequestResponseAck(t, &discovery.DiscoveryRequest{
Node: &corev3.Node{
Id: ads.ID,
Metadata: md.ToStruct(),
},
ResourceNames: []string{wantExtensionConfigName},
})
var ec corev3.TypedExtensionConfig
err := res.Resources[0].UnmarshalTo(&ec)
if err != nil {
t.Fatal("Failed to unmarshal extension config", err)
return
}
if ec.Name != wantExtensionConfigName {
t.Errorf("extension config name got %v want %v", ec.Name, wantExtensionConfigName)
}
}
func makeDockerCredentials(name, namespace string, data map[string]string, secretType corev1.SecretType) *corev1.Secret {
bdata := map[string][]byte{}
for k, v := range data {
bdata[k] = []byte(v)
}
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Data: bdata,
Type: secretType,
}
}
func makeWasmPlugin(name, namespace, secret string) config.Config {
spec := &extensions.WasmPlugin{
Phase: extensions.PluginPhase_AUTHN,
}
if secret != "" {
spec.ImagePullSecret = secret
}
return config.Config{
Meta: config.Meta{
Name: name,
Namespace: namespace,
GroupVersionKind: gvk.WasmPlugin,
},
Spec: spec,
}
}
var (
// Secrets
defaultPullSecret = makeDockerCredentials("default-pull-secret", "default", map[string]string{
corev1.DockerConfigJsonKey: "default-docker-credential",
}, corev1.SecretTypeDockerConfigJson)
rootPullSecret = makeDockerCredentials("root-pull-secret", "dubbo-system", map[string]string{
corev1.DockerConfigJsonKey: "root-docker-credential",
}, corev1.SecretTypeDockerConfigJson)
wrongTypeSecret = makeDockerCredentials("wrong-type-pull-secret", "default", map[string]string{
corev1.DockerConfigJsonKey: "wrong-type-docker-credential",
}, corev1.SecretTypeTLS)
wasmPlugin = makeWasmPlugin("default-plugin", "default", "")
wasmPluginWithSec = makeWasmPlugin("default-plugin-with-sec", "default", "default-pull-secret")
wasmPluginWrongSec = makeWasmPlugin("default-plugin-wrong-sec", "default", "wrong-secret")
wasmPluginWrongSecType = makeWasmPlugin("default-plugin-wrong-sec-type", "default", "wrong-type-pull-secret")
rootWasmPluginWithSec = makeWasmPlugin("root-plugin", "dubbo-system", "root-pull-secret")
)
func TestECDSGenerate(t *testing.T) {
cases := []struct {
name string
proxyNamespace string
request *model.PushRequest
watchedResources []string
wantExtensions sets.Set
wantSecrets sets.Set
}{
{
name: "simple",
proxyNamespace: "default",
request: &model.PushRequest{Full: true},
watchedResources: []string{"default.default-plugin"},
wantExtensions: sets.Set{"default.default-plugin": {}},
wantSecrets: sets.Set{},
},
{
name: "simple_with_secret",
proxyNamespace: "default",
request: &model.PushRequest{Full: true},
watchedResources: []string{"default.default-plugin-with-sec"},
wantExtensions: sets.Set{"default.default-plugin-with-sec": {}},
wantSecrets: sets.Set{"default-docker-credential": {}},
},
{
name: "miss_secret",
proxyNamespace: "default",
request: &model.PushRequest{Full: true},
watchedResources: []string{"default.default-plugin-wrong-sec"},
wantExtensions: sets.Set{"default.default-plugin-wrong-sec": {}},
wantSecrets: sets.Set{},
},
{
name: "wrong_secret_type",
proxyNamespace: "default",
request: &model.PushRequest{Full: true},
watchedResources: []string{"default.default-plugin-wrong-sec-type"},
wantExtensions: sets.Set{"default.default-plugin-wrong-sec-type": {}},
wantSecrets: sets.Set{},
},
{
name: "root_and_default",
proxyNamespace: "default",
request: &model.PushRequest{Full: true},
watchedResources: []string{"default.default-plugin-with-sec", "dubbo-system.root-plugin"},
wantExtensions: sets.Set{"default.default-plugin-with-sec": {}, "dubbo-system.root-plugin": {}},
wantSecrets: sets.Set{"default-docker-credential": {}, "root-docker-credential": {}},
},
{
name: "only_root",
proxyNamespace: "somenamespace",
request: &model.PushRequest{Full: true},
watchedResources: []string{"dubbo-system.root-plugin"},
wantExtensions: sets.Set{"dubbo-system.root-plugin": {}},
wantSecrets: sets.Set{"root-docker-credential": {}},
},
{
name: "no_relevant_config_update",
proxyNamespace: "default",
request: &model.PushRequest{
Full: true,
ConfigsUpdated: map[model.ConfigKey]struct{}{
{Kind: gvk.AuthorizationPolicy}: {},
},
},
watchedResources: []string{"default.default-plugin-with-sec", "dubbo-system.root-plugin"},
wantExtensions: sets.Set{},
wantSecrets: sets.Set{},
},
{
name: "has_relevant_config_update",
proxyNamespace: "default",
request: &model.PushRequest{
Full: true,
ConfigsUpdated: map[model.ConfigKey]struct{}{
{Kind: gvk.AuthorizationPolicy}: {},
{Kind: gvk.WasmPlugin}: {},
},
},
watchedResources: []string{"default.default-plugin-with-sec"},
wantExtensions: sets.Set{"default.default-plugin-with-sec": {}},
wantSecrets: sets.Set{"default-docker-credential": {}},
},
{
name: "non_relevant_secret_update",
proxyNamespace: "default",
request: &model.PushRequest{
Full: true,
ConfigsUpdated: map[model.ConfigKey]struct{}{
{Kind: gvk.AuthorizationPolicy}: {},
{Kind: gvk.Secret}: {},
},
},
watchedResources: []string{"default.default-plugin-with-sec"},
wantExtensions: sets.Set{},
wantSecrets: sets.Set{},
},
{
name: "relevant_secret_update",
proxyNamespace: "default",
request: &model.PushRequest{
Full: true,
ConfigsUpdated: map[model.ConfigKey]struct{}{
{Kind: gvk.Secret, Name: "default-pull-secret", Namespace: "default"}: {},
},
},
watchedResources: []string{"default.default-plugin-with-sec"},
wantExtensions: sets.Set{"default.default-plugin-with-sec": {}},
wantSecrets: sets.Set{"default-docker-credential": {}},
},
{
name: "relevant_secret_update_non_full_push",
proxyNamespace: "default",
request: &model.PushRequest{
Full: false,
ConfigsUpdated: map[model.ConfigKey]struct{}{
{Kind: gvk.Secret, Name: "default-pull-secret", Namespace: "default"}: {},
},
},
watchedResources: []string{"default.default-plugin-with-sec"},
wantExtensions: sets.Set{"default.default-plugin-with-sec": {}},
wantSecrets: sets.Set{"default-docker-credential": {}},
},
// All the credentials should be sent to istio-agent even if one of them is only updated,
// because `istio-agent` does not keep the credentials.
{
name: "multi_wasmplugin_update_secret",
proxyNamespace: "default",
request: &model.PushRequest{
Full: false,
ConfigsUpdated: map[model.ConfigKey]struct{}{
{Kind: gvk.Secret, Name: "default-pull-secret", Namespace: "default"}: {},
},
},
watchedResources: []string{"default.default-plugin-with-sec", "dubbo-system.root-plugin"},
wantExtensions: sets.Set{"default.default-plugin-with-sec": {}, "dubbo-system.root-plugin": {}},
wantSecrets: sets.Set{"default-docker-credential": {}, "root-docker-credential": {}},
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
s := xds.NewFakeDiscoveryServer(t, xds.FakeOptions{
KubernetesObjects: []runtime.Object{defaultPullSecret, rootPullSecret, wrongTypeSecret},
Configs: []config.Config{wasmPlugin, wasmPluginWithSec, wasmPluginWrongSec, wasmPluginWrongSecType, rootWasmPluginWithSec},
})
gen := s.Discovery.Generators[v3.ExtensionConfigurationType]
tt.request.Start = time.Now()
proxy := &model.Proxy{
VerifiedIdentity: &spiffe.Identity{Namespace: tt.proxyNamespace},
Type: model.Router,
Metadata: &model.NodeMetadata{
ClusterID: "Kubernetes",
},
}
tt.request.Push = s.PushContext()
tt.request.Push.Mesh.RootNamespace = "dubbo-system"
resources, _, _ := gen.Generate(s.SetupProxy(proxy),
&model.WatchedResource{ResourceNames: tt.watchedResources}, tt.request)
gotExtensions := sets.Set{}
gotSecrets := sets.Set{}
for _, res := range resources {
gotExtensions.Insert(res.Name)
ec := &corev3.TypedExtensionConfig{}
res.Resource.UnmarshalTo(ec)
wasm := &extensionsv3.Wasm{}
ec.TypedConfig.UnmarshalTo(wasm)
gotsecret := wasm.GetConfig().GetVmConfig().GetEnvironmentVariables().GetKeyValues()[model.WasmSecretEnv]
if gotsecret != "" {
gotSecrets.Insert(gotsecret)
}
}
if !reflect.DeepEqual(gotSecrets, tt.wantSecrets) {
t.Errorf("got secrets %v, want secrets %v", gotSecrets, tt.wantSecrets)
}
if !reflect.DeepEqual(gotExtensions, tt.wantExtensions) {
t.Errorf("got extensions %v, want extensions %v", gotExtensions, tt.wantExtensions)
}
})
}
}