istio

Форк
0
/
builder_test.go 
475 строк · 13.9 Кб
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

15
package builder
16

17
import (
18
	"os"
19
	"testing"
20

21
	listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
22
	hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
23
	"google.golang.org/protobuf/proto"
24
	"google.golang.org/protobuf/types/known/durationpb"
25

26
	meshconfig "istio.io/api/mesh/v1alpha1"
27
	"istio.io/istio/pilot/pkg/config/kube/crd"
28
	"istio.io/istio/pilot/pkg/config/memory"
29
	"istio.io/istio/pilot/pkg/model"
30
	"istio.io/istio/pilot/pkg/security/trustdomain"
31
	"istio.io/istio/pilot/test/util"
32
	"istio.io/istio/pkg/config"
33
	"istio.io/istio/pkg/config/host"
34
	"istio.io/istio/pkg/config/schema/collections"
35
	"istio.io/istio/pkg/util/protomarshal"
36
)
37

38
const (
39
	basePath = "testdata/"
40
)
41

42
var (
43
	httpbin = map[string]string{
44
		"app":     "httpbin",
45
		"version": "v1",
46
	}
47
	meshConfigGRPCNoNamespace = &meshconfig.MeshConfig{
48
		ExtensionProviders: []*meshconfig.MeshConfig_ExtensionProvider{
49
			{
50
				Name: "default",
51
				Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExtAuthzGrpc{
52
					EnvoyExtAuthzGrpc: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationGrpcProvider{
53
						Service:       "my-custom-ext-authz.foo.svc.cluster.local",
54
						Port:          9000,
55
						FailOpen:      true,
56
						StatusOnError: "403",
57
					},
58
				},
59
			},
60
		},
61
	}
62
	meshConfigGRPC = &meshconfig.MeshConfig{
63
		ExtensionProviders: []*meshconfig.MeshConfig_ExtensionProvider{
64
			{
65
				Name: "default",
66
				Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExtAuthzGrpc{
67
					EnvoyExtAuthzGrpc: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationGrpcProvider{
68
						Service:       "foo/my-custom-ext-authz.foo.svc.cluster.local",
69
						Port:          9000,
70
						Timeout:       &durationpb.Duration{Nanos: 2000 * 1000},
71
						FailOpen:      true,
72
						StatusOnError: "403",
73
						IncludeRequestBodyInCheck: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationRequestBody{
74
							MaxRequestBytes:     4096,
75
							AllowPartialMessage: true,
76
						},
77
					},
78
				},
79
			},
80
		},
81
	}
82
	meshConfigHTTP = &meshconfig.MeshConfig{
83
		ExtensionProviders: []*meshconfig.MeshConfig_ExtensionProvider{
84
			{
85
				Name: "default",
86
				Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExtAuthzHttp{
87
					EnvoyExtAuthzHttp: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationHttpProvider{
88
						Service:                      "foo/my-custom-ext-authz.foo.svc.cluster.local",
89
						Port:                         9000,
90
						Timeout:                      &durationpb.Duration{Seconds: 10},
91
						FailOpen:                     true,
92
						StatusOnError:                "403",
93
						PathPrefix:                   "/check",
94
						IncludeRequestHeadersInCheck: []string{"x-custom-id", "x-prefix-*", "*-suffix"},
95
						//nolint: staticcheck
96
						IncludeHeadersInCheck:           []string{"should-not-include-when-IncludeRequestHeadersInCheck-is-set"},
97
						IncludeAdditionalHeadersInCheck: map[string]string{"x-header-1": "value-1", "x-header-2": "value-2"},
98
						IncludeRequestBodyInCheck: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationRequestBody{
99
							MaxRequestBytes:     2048,
100
							AllowPartialMessage: true,
101
							PackAsBytes:         true,
102
						},
103
						HeadersToUpstreamOnAllow:   []string{"Authorization", "x-prefix-*", "*-suffix"},
104
						HeadersToDownstreamOnDeny:  []string{"Set-cookie", "x-prefix-*", "*-suffix"},
105
						HeadersToDownstreamOnAllow: []string{"Set-cookie", "x-prefix-*", "*-suffix"},
106
					},
107
				},
108
			},
109
		},
110
	}
111
	meshConfigInvalid = &meshconfig.MeshConfig{
112
		ExtensionProviders: []*meshconfig.MeshConfig_ExtensionProvider{
113
			{
114
				Name: "default",
115
				Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExtAuthzHttp{
116
					EnvoyExtAuthzHttp: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationHttpProvider{
117
						Service:       "foo/my-custom-ext-authz",
118
						Port:          999999,
119
						PathPrefix:    "check",
120
						StatusOnError: "999",
121
					},
122
				},
123
			},
124
		},
125
	}
126
)
127

128
func TestGenerator_GenerateHTTP(t *testing.T) {
129
	testCases := []struct {
130
		name       string
131
		tdBundle   trustdomain.Bundle
132
		meshConfig *meshconfig.MeshConfig
133
		version    *model.IstioVersion
134
		input      string
135
		want       []string
136
	}{
137
		{
138
			name:  "allow-empty-rule",
139
			input: "allow-empty-rule-in.yaml",
140
			want:  []string{"allow-empty-rule-out.yaml"},
141
		},
142
		{
143
			name:  "allow-full-rule",
144
			input: "allow-full-rule-in.yaml",
145
			want:  []string{"allow-full-rule-out.yaml"},
146
		},
147
		{
148
			name:  "allow-nil-rule",
149
			input: "allow-nil-rule-in.yaml",
150
			want:  []string{"allow-nil-rule-out.yaml"},
151
		},
152
		{
153
			name:  "allow-path",
154
			input: "allow-path-in.yaml",
155
			want:  []string{"allow-path-out.yaml"},
156
		},
157
		{
158
			name:  "audit-full-rule",
159
			input: "audit-full-rule-in.yaml",
160
			want:  []string{"audit-full-rule-out.yaml"},
161
		},
162
		{
163
			name:       "custom-grpc-provider-no-namespace",
164
			meshConfig: meshConfigGRPCNoNamespace,
165
			input:      "custom-simple-http-in.yaml",
166
			want:       []string{"custom-grpc-provider-no-namespace-out1.yaml", "custom-grpc-provider-no-namespace-out2.yaml"},
167
		},
168
		{
169
			name:       "custom-grpc-provider",
170
			meshConfig: meshConfigGRPC,
171
			input:      "custom-simple-http-in.yaml",
172
			want:       []string{"custom-grpc-provider-out1.yaml", "custom-grpc-provider-out2.yaml"},
173
		},
174
		{
175
			name:       "custom-http-provider",
176
			meshConfig: meshConfigHTTP,
177
			input:      "custom-simple-http-in.yaml",
178
			want:       []string{"custom-http-provider-out1.yaml", "custom-http-provider-out2.yaml"},
179
		},
180
		{
181
			name:       "custom-bad-multiple-providers",
182
			meshConfig: meshConfigHTTP,
183
			input:      "custom-bad-multiple-providers-in.yaml",
184
			want:       []string{"custom-bad-out.yaml"},
185
		},
186
		{
187
			name:       "custom-bad-invalid-config",
188
			meshConfig: meshConfigInvalid,
189
			input:      "custom-simple-http-in.yaml",
190
			want:       []string{"custom-bad-out.yaml"},
191
		},
192
		{
193
			name:  "deny-and-allow",
194
			input: "deny-and-allow-in.yaml",
195
			want:  []string{"deny-and-allow-out1.yaml", "deny-and-allow-out2.yaml"},
196
		},
197
		{
198
			name:  "deny-empty-rule",
199
			input: "deny-empty-rule-in.yaml",
200
			want:  []string{"deny-empty-rule-out.yaml"},
201
		},
202
		{
203
			name:  "dry-run-allow-and-deny",
204
			input: "dry-run-allow-and-deny-in.yaml",
205
			want:  []string{"dry-run-allow-and-deny-out1.yaml", "dry-run-allow-and-deny-out2.yaml"},
206
		},
207
		{
208
			name:  "dry-run-allow",
209
			input: "dry-run-allow-in.yaml",
210
			want:  []string{"dry-run-allow-out.yaml"},
211
		},
212
		{
213
			name:  "dry-run-mix",
214
			input: "dry-run-mix-in.yaml",
215
			want:  []string{"dry-run-mix-out.yaml"},
216
		},
217
		{
218
			name:  "multiple-policies",
219
			input: "multiple-policies-in.yaml",
220
			want:  []string{"multiple-policies-out.yaml"},
221
		},
222
		{
223
			name:  "single-policy",
224
			input: "single-policy-in.yaml",
225
			want:  []string{"single-policy-out.yaml"},
226
		},
227
		{
228
			name:     "trust-domain-one-alias",
229
			tdBundle: trustdomain.NewBundle("td1", []string{"cluster.local"}),
230
			input:    "simple-policy-td-aliases-in.yaml",
231
			want:     []string{"simple-policy-td-aliases-out.yaml"},
232
		},
233
		{
234
			name:     "trust-domain-multiple-aliases",
235
			tdBundle: trustdomain.NewBundle("td1", []string{"cluster.local", "some-td"}),
236
			input:    "simple-policy-multiple-td-aliases-in.yaml",
237
			want:     []string{"simple-policy-multiple-td-aliases-out.yaml"},
238
		},
239
		{
240
			name:     "trust-domain-wildcard-in-principal",
241
			tdBundle: trustdomain.NewBundle("td1", []string{"foobar"}),
242
			input:    "simple-policy-principal-with-wildcard-in.yaml",
243
			want:     []string{"simple-policy-principal-with-wildcard-out.yaml"},
244
		},
245
		{
246
			name:     "trust-domain-aliases-in-source-principal",
247
			tdBundle: trustdomain.NewBundle("new-td", []string{"old-td", "some-trustdomain"}),
248
			input:    "td-aliases-source-principal-in.yaml",
249
			want:     []string{"td-aliases-source-principal-out.yaml"},
250
		},
251
	}
252

253
	baseDir := "http/"
254
	for _, extended := range []bool{false, true} {
255
		for _, tc := range testCases {
256
			t.Run(tc.name, func(t *testing.T) {
257
				option := Option{
258
					IsCustomBuilder: tc.meshConfig != nil,
259
					UseExtendedJwt:  extended,
260
				}
261
				push := push(t, baseDir+tc.input, tc.meshConfig)
262
				proxy := node(tc.version)
263
				selectionOpts := model.WorkloadSelectionOpts{
264
					Namespace:      proxy.ConfigNamespace,
265
					WorkloadLabels: proxy.Labels,
266
				}
267
				policies := push.AuthzPolicies.ListAuthorizationPolicies(selectionOpts)
268
				g := New(tc.tdBundle, push, policies, option)
269
				if g == nil {
270
					t.Fatalf("failed to create generator")
271
				}
272
				got := g.BuildHTTP()
273
				wants := tc.want
274
				if extended {
275
					for i := range wants {
276
						wants[i] = "extended-" + wants[i]
277
					}
278
				}
279
				verify(t, convertHTTP(got), baseDir, tc.want, false /* forTCP */)
280
			})
281
		}
282
	}
283
}
284

285
func TestGenerator_GenerateTCP(t *testing.T) {
286
	testCases := []struct {
287
		name       string
288
		tdBundle   trustdomain.Bundle
289
		meshConfig *meshconfig.MeshConfig
290
		input      string
291
		want       []string
292
	}{
293
		{
294
			name:  "allow-both-http-tcp",
295
			input: "allow-both-http-tcp-in.yaml",
296
			want:  []string{"allow-both-http-tcp-out.yaml"},
297
		},
298
		{
299
			name:  "allow-only-http",
300
			input: "allow-only-http-in.yaml",
301
			want:  []string{"allow-only-http-out.yaml"},
302
		},
303
		{
304
			name:  "audit-both-http-tcp",
305
			input: "audit-both-http-tcp-in.yaml",
306
			want:  []string{"audit-both-http-tcp-out.yaml"},
307
		},
308
		{
309
			name:       "custom-both-http-tcp",
310
			meshConfig: meshConfigGRPC,
311
			input:      "custom-both-http-tcp-in.yaml",
312
			want:       []string{"custom-both-http-tcp-out1.yaml", "custom-both-http-tcp-out2.yaml"},
313
		},
314
		{
315
			name:       "custom-only-http",
316
			meshConfig: meshConfigHTTP,
317
			input:      "custom-only-http-in.yaml",
318
			want:       []string{},
319
		},
320
		{
321
			name:  "deny-both-http-tcp",
322
			input: "deny-both-http-tcp-in.yaml",
323
			want:  []string{"deny-both-http-tcp-out.yaml"},
324
		},
325
		{
326
			name:  "dry-run-mix",
327
			input: "dry-run-mix-in.yaml",
328
			want:  []string{"dry-run-mix-out.yaml"},
329
		},
330
	}
331

332
	baseDir := "tcp/"
333
	for _, tc := range testCases {
334
		t.Run(tc.name, func(t *testing.T) {
335
			option := Option{
336
				IsCustomBuilder: tc.meshConfig != nil,
337
			}
338
			push := push(t, baseDir+tc.input, tc.meshConfig)
339
			proxy := node(nil)
340
			selectionOpts := model.WorkloadSelectionOpts{
341
				Namespace:      proxy.ConfigNamespace,
342
				WorkloadLabels: proxy.Labels,
343
			}
344
			policies := push.AuthzPolicies.ListAuthorizationPolicies(selectionOpts)
345
			g := New(tc.tdBundle, push, policies, option)
346
			if g == nil {
347
				t.Fatalf("failed to create generator")
348
			}
349
			got := g.BuildTCP()
350
			verify(t, convertTCP(got), baseDir, tc.want, true /* forTCP */)
351
		})
352
	}
353
}
354

355
func verify(t *testing.T, gots []proto.Message, baseDir string, wants []string, forTCP bool) {
356
	t.Helper()
357

358
	if len(gots) != len(wants) {
359
		t.Fatalf("got %d configs but want %d", len(gots), len(wants))
360
	}
361
	for i, got := range gots {
362
		gotYaml, err := protomarshal.ToYAML(got)
363
		if err != nil {
364
			t.Fatalf("failed to convert to YAML: %v", err)
365
		}
366

367
		wantFile := basePath + baseDir + wants[i]
368
		util.RefreshGoldenFile(t, []byte(gotYaml), wantFile)
369
		want := yamlConfig(t, wantFile, forTCP)
370
		wantYaml, err := protomarshal.ToYAML(want)
371
		if err != nil {
372
			t.Fatalf("failed to convert to YAML: %v", err)
373
		}
374

375
		if err := util.Compare([]byte(gotYaml), []byte(wantYaml)); err != nil {
376
			t.Error(err)
377
		}
378
	}
379
}
380

381
func yamlPolicy(t *testing.T, filename string) *model.AuthorizationPolicies {
382
	t.Helper()
383
	data, err := os.ReadFile(filename)
384
	if err != nil {
385
		t.Fatalf("failed to read input yaml file: %v", err)
386
	}
387
	c, _, err := crd.ParseInputs(string(data))
388
	if err != nil {
389
		t.Fatalf("failde to parse CRD: %v", err)
390
	}
391
	var configs []*config.Config
392
	for i := range c {
393
		configs = append(configs, &c[i])
394
	}
395

396
	return newAuthzPolicies(t, configs)
397
}
398

399
func yamlConfig(t *testing.T, filename string, forTCP bool) proto.Message {
400
	t.Helper()
401
	data, err := os.ReadFile(filename)
402
	if err != nil {
403
		t.Fatalf("failed to read file: %v", err)
404
	}
405
	if forTCP {
406
		out := &listener.Filter{}
407
		if err := protomarshal.ApplyYAML(string(data), out); err != nil {
408
			t.Fatalf("failed to parse YAML: %v", err)
409
		}
410
		return out
411
	}
412
	out := &hcm.HttpFilter{}
413
	if err := protomarshal.ApplyYAML(string(data), out); err != nil {
414
		t.Fatalf("failed to parse YAML: %v", err)
415
	}
416
	return out
417
}
418

419
func convertHTTP(in []*hcm.HttpFilter) []proto.Message {
420
	ret := make([]proto.Message, len(in))
421
	for i := range in {
422
		ret[i] = in[i]
423
	}
424
	return ret
425
}
426

427
func convertTCP(in []*listener.Filter) []proto.Message {
428
	ret := make([]proto.Message, len(in))
429
	for i := range in {
430
		ret[i] = in[i]
431
	}
432
	return ret
433
}
434

435
func newAuthzPolicies(t *testing.T, policies []*config.Config) *model.AuthorizationPolicies {
436
	store := memory.Make(collections.Pilot)
437
	for _, p := range policies {
438
		if _, err := store.Create(*p); err != nil {
439
			t.Fatalf("newAuthzPolicies: %v", err)
440
		}
441
	}
442

443
	authzPolicies := model.GetAuthorizationPolicies(&model.Environment{
444
		ConfigStore: store,
445
	})
446
	return authzPolicies
447
}
448

449
func push(t *testing.T, input string, mc *meshconfig.MeshConfig) *model.PushContext {
450
	t.Helper()
451
	p := &model.PushContext{
452
		AuthzPolicies: yamlPolicy(t, basePath+input),
453
		Mesh:          mc,
454
	}
455
	p.ServiceIndex.HostnameAndNamespace = map[host.Name]map[string]*model.Service{
456
		"my-custom-ext-authz.foo.svc.cluster.local": {
457
			"foo": &model.Service{
458
				Hostname: "my-custom-ext-authz.foo.svc.cluster.local",
459
			},
460
		},
461
	}
462
	return p
463
}
464

465
func node(version *model.IstioVersion) *model.Proxy {
466
	return &model.Proxy{
467
		ID:              "test-node",
468
		ConfigNamespace: "foo",
469
		Labels:          httpbin,
470
		Metadata: &model.NodeMetadata{
471
			Labels: httpbin,
472
		},
473
		IstioVersion: version,
474
	}
475
}
476

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.