istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/xds/ecds_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package xds_test 15 16 import ( 17 "reflect" 18 "testing" 19 "time" 20 21 core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 22 wasm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/wasm/v3" 23 discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 24 corev1 "k8s.io/api/core/v1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/runtime" 27 28 extensions "istio.io/api/extensions/v1alpha1" 29 "istio.io/istio/pilot/pkg/model" 30 v3 "istio.io/istio/pilot/pkg/xds/v3" 31 "istio.io/istio/pilot/test/xds" 32 "istio.io/istio/pkg/config" 33 "istio.io/istio/pkg/config/constants" 34 "istio.io/istio/pkg/config/schema/gvk" 35 "istio.io/istio/pkg/config/schema/kind" 36 "istio.io/istio/pkg/spiffe" 37 "istio.io/istio/pkg/util/sets" 38 ) 39 40 func TestECDS(t *testing.T) { 41 s := xds.NewFakeDiscoveryServer(t, xds.FakeOptions{ 42 ConfigString: mustReadFile(t, "./testdata/ecds.yaml"), 43 }) 44 45 ads := s.ConnectADS().WithType(v3.ExtensionConfigurationType) 46 wantExtensionConfigName := "extension-config" 47 md := model.NodeMetadata{ 48 ClusterID: constants.DefaultClusterName, 49 } 50 res := ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ 51 Node: &core.Node{ 52 Id: ads.ID, 53 Metadata: md.ToStruct(), 54 }, 55 ResourceNames: []string{wantExtensionConfigName}, 56 }) 57 58 var ec core.TypedExtensionConfig 59 err := res.Resources[0].UnmarshalTo(&ec) 60 if err != nil { 61 t.Fatal("Failed to unmarshal extension config", err) 62 return 63 } 64 if ec.Name != wantExtensionConfigName { 65 t.Errorf("extension config name got %v want %v", ec.Name, wantExtensionConfigName) 66 } 67 } 68 69 func makeDockerCredentials(name, namespace string, data map[string]string, secretType corev1.SecretType) *corev1.Secret { 70 bdata := map[string][]byte{} 71 for k, v := range data { 72 bdata[k] = []byte(v) 73 } 74 return &corev1.Secret{ 75 ObjectMeta: metav1.ObjectMeta{ 76 Name: name, 77 Namespace: namespace, 78 }, 79 Data: bdata, 80 Type: secretType, 81 } 82 } 83 84 func makeWasmPlugin(name, namespace, secret string) config.Config { 85 spec := &extensions.WasmPlugin{ 86 Phase: extensions.PluginPhase_AUTHN, 87 } 88 if secret != "" { 89 spec.ImagePullSecret = secret 90 } 91 return config.Config{ 92 Meta: config.Meta{ 93 Name: name, 94 Namespace: namespace, 95 GroupVersionKind: gvk.WasmPlugin, 96 }, 97 Spec: spec, 98 } 99 } 100 101 var ( 102 // Secrets 103 defaultPullSecret = makeDockerCredentials("default-pull-secret", "default", map[string]string{ 104 corev1.DockerConfigJsonKey: "default-docker-credential", 105 }, corev1.SecretTypeDockerConfigJson) 106 rootPullSecret = makeDockerCredentials("root-pull-secret", "istio-system", map[string]string{ 107 corev1.DockerConfigJsonKey: "root-docker-credential", 108 }, corev1.SecretTypeDockerConfigJson) 109 wrongTypeSecret = makeDockerCredentials("wrong-type-pull-secret", "default", map[string]string{ 110 corev1.DockerConfigJsonKey: "wrong-type-docker-credential", 111 }, corev1.SecretTypeTLS) 112 113 wasmPlugin = makeWasmPlugin("default-plugin", "default", "") 114 wasmPluginWithSec = makeWasmPlugin("default-plugin-with-sec", "default", "default-pull-secret") 115 wasmPluginWrongSec = makeWasmPlugin("default-plugin-wrong-sec", "default", "wrong-secret") 116 wasmPluginWrongSecType = makeWasmPlugin("default-plugin-wrong-sec-type", "default", "wrong-type-pull-secret") 117 rootWasmPluginWithSec = makeWasmPlugin("root-plugin", "istio-system", "root-pull-secret") 118 ) 119 120 func TestECDSGenerate(t *testing.T) { 121 cases := []struct { 122 name string 123 proxyNamespace string 124 request *model.PushRequest 125 watchedResources []string 126 wantExtensions sets.String 127 wantSecrets sets.String 128 }{ 129 { 130 name: "simple", 131 proxyNamespace: "default", 132 request: &model.PushRequest{Full: true}, 133 watchedResources: []string{"extenstions.istio.io/wasmplugin/default.default-plugin"}, 134 wantExtensions: sets.String{"extenstions.istio.io/wasmplugin/default.default-plugin": {}}, 135 wantSecrets: sets.String{}, 136 }, 137 { 138 name: "simple_with_secret", 139 proxyNamespace: "default", 140 request: &model.PushRequest{Full: true}, 141 watchedResources: []string{"extenstions.istio.io/wasmplugin/default.default-plugin-with-sec"}, 142 wantExtensions: sets.String{"extenstions.istio.io/wasmplugin/default.default-plugin-with-sec": {}}, 143 wantSecrets: sets.String{"default-docker-credential": {}}, 144 }, 145 { 146 name: "miss_secret", 147 proxyNamespace: "default", 148 request: &model.PushRequest{Full: true}, 149 watchedResources: []string{"extenstions.istio.io/wasmplugin/default.default-plugin-wrong-sec"}, 150 wantExtensions: sets.String{"extenstions.istio.io/wasmplugin/default.default-plugin-wrong-sec": {}}, 151 wantSecrets: sets.String{}, 152 }, 153 { 154 name: "wrong_secret_type", 155 proxyNamespace: "default", 156 request: &model.PushRequest{Full: true}, 157 watchedResources: []string{"extenstions.istio.io/wasmplugin/default.default-plugin-wrong-sec-type"}, 158 wantExtensions: sets.String{"extenstions.istio.io/wasmplugin/default.default-plugin-wrong-sec-type": {}}, 159 wantSecrets: sets.String{}, 160 }, 161 { 162 name: "root_and_default", 163 proxyNamespace: "default", 164 request: &model.PushRequest{Full: true}, 165 watchedResources: []string{ 166 "extenstions.istio.io/wasmplugin/default.default-plugin-with-sec", 167 "extenstions.istio.io/wasmplugin/istio-system.root-plugin", 168 }, 169 wantExtensions: sets.String{ 170 "extenstions.istio.io/wasmplugin/default.default-plugin-with-sec": {}, 171 "extenstions.istio.io/wasmplugin/istio-system.root-plugin": {}, 172 }, 173 wantSecrets: sets.String{"default-docker-credential": {}, "root-docker-credential": {}}, 174 }, 175 { 176 name: "only_root", 177 proxyNamespace: "somenamespace", 178 request: &model.PushRequest{Full: true}, 179 watchedResources: []string{"extenstions.istio.io/wasmplugin/istio-system.root-plugin"}, 180 wantExtensions: sets.String{"extenstions.istio.io/wasmplugin/istio-system.root-plugin": {}}, 181 wantSecrets: sets.String{"root-docker-credential": {}}, 182 }, 183 { 184 name: "no_relevant_config_update", 185 proxyNamespace: "default", 186 request: &model.PushRequest{ 187 Full: true, 188 ConfigsUpdated: sets.New(model.ConfigKey{Kind: kind.AuthorizationPolicy}), 189 }, 190 watchedResources: []string{"extenstions.istio.io/wasmplugin/default.default-plugin-with-sec", "extenstions.istio.io/wasmplugin/istio-system.root-plugin"}, 191 wantExtensions: sets.String{}, 192 wantSecrets: sets.String{}, 193 }, 194 { 195 name: "has_relevant_config_update", 196 proxyNamespace: "default", 197 request: &model.PushRequest{ 198 Full: true, 199 ConfigsUpdated: sets.New(model.ConfigKey{Kind: kind.AuthorizationPolicy}, model.ConfigKey{Kind: kind.WasmPlugin}), 200 }, 201 watchedResources: []string{"extenstions.istio.io/wasmplugin/default.default-plugin-with-sec"}, 202 wantExtensions: sets.String{"extenstions.istio.io/wasmplugin/default.default-plugin-with-sec": {}}, 203 wantSecrets: sets.String{"default-docker-credential": {}}, 204 }, 205 { 206 name: "non_relevant_secret_update", 207 proxyNamespace: "default", 208 request: &model.PushRequest{ 209 Full: true, 210 ConfigsUpdated: sets.New(model.ConfigKey{Kind: kind.AuthorizationPolicy}, model.ConfigKey{Kind: kind.Secret}), 211 }, 212 watchedResources: []string{"extenstions.istio.io/wasmplugin/default.default-plugin-with-sec"}, 213 wantExtensions: sets.String{}, 214 wantSecrets: sets.String{}, 215 }, 216 { 217 name: "non_relevant_secret_update_and_wasm_plugin", 218 proxyNamespace: "default", 219 request: &model.PushRequest{ 220 Full: true, 221 ConfigsUpdated: sets.New(model.ConfigKey{Kind: kind.WasmPlugin}, model.ConfigKey{Kind: kind.Secret}), 222 }, 223 watchedResources: []string{"extenstions.istio.io/wasmplugin/default.default-plugin-with-sec"}, 224 wantExtensions: sets.String{"extenstions.istio.io/wasmplugin/default.default-plugin-with-sec": {}}, 225 wantSecrets: sets.String{"default-docker-credential": {}}, 226 }, 227 { 228 name: "relevant_secret_update", 229 proxyNamespace: "default", 230 request: &model.PushRequest{ 231 Full: true, 232 ConfigsUpdated: sets.New(model.ConfigKey{Kind: kind.Secret, Name: "default-pull-secret", Namespace: "default"}), 233 }, 234 watchedResources: []string{"extenstions.istio.io/wasmplugin/default.default-plugin-with-sec"}, 235 wantExtensions: sets.String{"extenstions.istio.io/wasmplugin/default.default-plugin-with-sec": {}}, 236 wantSecrets: sets.String{"default-docker-credential": {}}, 237 }, 238 { 239 name: "relevant_secret_update_non_full_push", 240 proxyNamespace: "default", 241 request: &model.PushRequest{ 242 Full: false, 243 ConfigsUpdated: sets.New(model.ConfigKey{Kind: kind.Secret, Name: "default-pull-secret", Namespace: "default"}), 244 }, 245 watchedResources: []string{"extenstions.istio.io/wasmplugin/default.default-plugin-with-sec"}, 246 wantExtensions: sets.String{"extenstions.istio.io/wasmplugin/default.default-plugin-with-sec": {}}, 247 wantSecrets: sets.String{"default-docker-credential": {}}, 248 }, 249 // All the credentials should be sent to istio-agent even if one of them is only updated, 250 // because `istio-agent` does not keep the credentials. 251 { 252 name: "multi_wasmplugin_update_secret", 253 proxyNamespace: "default", 254 request: &model.PushRequest{ 255 Full: false, 256 ConfigsUpdated: sets.New(model.ConfigKey{Kind: kind.Secret, Name: "default-pull-secret", Namespace: "default"}), 257 }, 258 watchedResources: []string{ 259 "extenstions.istio.io/wasmplugin/default.default-plugin-with-sec", 260 "extenstions.istio.io/wasmplugin/istio-system.root-plugin", 261 }, 262 wantExtensions: sets.String{ 263 "extenstions.istio.io/wasmplugin/default.default-plugin-with-sec": {}, 264 "extenstions.istio.io/wasmplugin/istio-system.root-plugin": {}, 265 }, 266 wantSecrets: sets.String{"default-docker-credential": {}, "root-docker-credential": {}}, 267 }, 268 } 269 270 for _, tt := range cases { 271 t.Run(tt.name, func(t *testing.T) { 272 s := xds.NewFakeDiscoveryServer(t, xds.FakeOptions{ 273 KubernetesObjects: []runtime.Object{defaultPullSecret, rootPullSecret, wrongTypeSecret}, 274 Configs: []config.Config{wasmPlugin, wasmPluginWithSec, wasmPluginWrongSec, wasmPluginWrongSecType, rootWasmPluginWithSec}, 275 }) 276 277 gen := s.Discovery.Generators[v3.ExtensionConfigurationType] 278 tt.request.Start = time.Now() 279 proxy := &model.Proxy{ 280 VerifiedIdentity: &spiffe.Identity{Namespace: tt.proxyNamespace}, 281 Type: model.Router, 282 Metadata: &model.NodeMetadata{ 283 ClusterID: constants.DefaultClusterName, 284 }, 285 } 286 tt.request.Push = s.PushContext() 287 tt.request.Push.Mesh.RootNamespace = "istio-system" 288 resources, _, _ := gen.Generate(s.SetupProxy(proxy), 289 &model.WatchedResource{ResourceNames: tt.watchedResources}, tt.request) 290 gotExtensions := sets.String{} 291 gotSecrets := sets.String{} 292 for _, res := range resources { 293 gotExtensions.Insert(res.Name) 294 ec := &core.TypedExtensionConfig{} 295 res.Resource.UnmarshalTo(ec) 296 wasm := &wasm.Wasm{} 297 ec.TypedConfig.UnmarshalTo(wasm) 298 gotsecret := wasm.GetConfig().GetVmConfig().GetEnvironmentVariables().GetKeyValues()[model.WasmSecretEnv] 299 if gotsecret != "" { 300 gotSecrets.Insert(gotsecret) 301 } 302 } 303 if !reflect.DeepEqual(gotSecrets, tt.wantSecrets) { 304 t.Errorf("got secrets %v, want secrets %v", gotSecrets, tt.wantSecrets) 305 } 306 if !reflect.DeepEqual(gotExtensions, tt.wantExtensions) { 307 t.Errorf("got extensions %v, want extensions %v", gotExtensions, tt.wantExtensions) 308 } 309 }) 310 } 311 }