1
// Copyright Istio Authors
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
7
// http://www.apache.org/licenses/LICENSE-2.0
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.
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"
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"
39
basePath = "testdata/"
43
httpbin = map[string]string{
47
meshConfigGRPCNoNamespace = &meshconfig.MeshConfig{
48
ExtensionProviders: []*meshconfig.MeshConfig_ExtensionProvider{
51
Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExtAuthzGrpc{
52
EnvoyExtAuthzGrpc: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationGrpcProvider{
53
Service: "my-custom-ext-authz.foo.svc.cluster.local",
62
meshConfigGRPC = &meshconfig.MeshConfig{
63
ExtensionProviders: []*meshconfig.MeshConfig_ExtensionProvider{
66
Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExtAuthzGrpc{
67
EnvoyExtAuthzGrpc: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationGrpcProvider{
68
Service: "foo/my-custom-ext-authz.foo.svc.cluster.local",
70
Timeout: &durationpb.Duration{Nanos: 2000 * 1000},
73
IncludeRequestBodyInCheck: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationRequestBody{
74
MaxRequestBytes: 4096,
75
AllowPartialMessage: true,
82
meshConfigHTTP = &meshconfig.MeshConfig{
83
ExtensionProviders: []*meshconfig.MeshConfig_ExtensionProvider{
86
Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExtAuthzHttp{
87
EnvoyExtAuthzHttp: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationHttpProvider{
88
Service: "foo/my-custom-ext-authz.foo.svc.cluster.local",
90
Timeout: &durationpb.Duration{Seconds: 10},
94
IncludeRequestHeadersInCheck: []string{"x-custom-id", "x-prefix-*", "*-suffix"},
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,
103
HeadersToUpstreamOnAllow: []string{"Authorization", "x-prefix-*", "*-suffix"},
104
HeadersToDownstreamOnDeny: []string{"Set-cookie", "x-prefix-*", "*-suffix"},
105
HeadersToDownstreamOnAllow: []string{"Set-cookie", "x-prefix-*", "*-suffix"},
111
meshConfigInvalid = &meshconfig.MeshConfig{
112
ExtensionProviders: []*meshconfig.MeshConfig_ExtensionProvider{
115
Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExtAuthzHttp{
116
EnvoyExtAuthzHttp: &meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationHttpProvider{
117
Service: "foo/my-custom-ext-authz",
120
StatusOnError: "999",
128
func TestGenerator_GenerateHTTP(t *testing.T) {
129
testCases := []struct {
131
tdBundle trustdomain.Bundle
132
meshConfig *meshconfig.MeshConfig
133
version *model.IstioVersion
138
name: "allow-empty-rule",
139
input: "allow-empty-rule-in.yaml",
140
want: []string{"allow-empty-rule-out.yaml"},
143
name: "allow-full-rule",
144
input: "allow-full-rule-in.yaml",
145
want: []string{"allow-full-rule-out.yaml"},
148
name: "allow-nil-rule",
149
input: "allow-nil-rule-in.yaml",
150
want: []string{"allow-nil-rule-out.yaml"},
154
input: "allow-path-in.yaml",
155
want: []string{"allow-path-out.yaml"},
158
name: "audit-full-rule",
159
input: "audit-full-rule-in.yaml",
160
want: []string{"audit-full-rule-out.yaml"},
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"},
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"},
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"},
181
name: "custom-bad-multiple-providers",
182
meshConfig: meshConfigHTTP,
183
input: "custom-bad-multiple-providers-in.yaml",
184
want: []string{"custom-bad-out.yaml"},
187
name: "custom-bad-invalid-config",
188
meshConfig: meshConfigInvalid,
189
input: "custom-simple-http-in.yaml",
190
want: []string{"custom-bad-out.yaml"},
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"},
198
name: "deny-empty-rule",
199
input: "deny-empty-rule-in.yaml",
200
want: []string{"deny-empty-rule-out.yaml"},
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"},
208
name: "dry-run-allow",
209
input: "dry-run-allow-in.yaml",
210
want: []string{"dry-run-allow-out.yaml"},
214
input: "dry-run-mix-in.yaml",
215
want: []string{"dry-run-mix-out.yaml"},
218
name: "multiple-policies",
219
input: "multiple-policies-in.yaml",
220
want: []string{"multiple-policies-out.yaml"},
223
name: "single-policy",
224
input: "single-policy-in.yaml",
225
want: []string{"single-policy-out.yaml"},
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"},
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"},
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"},
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"},
254
for _, extended := range []bool{false, true} {
255
for _, tc := range testCases {
256
t.Run(tc.name, func(t *testing.T) {
258
IsCustomBuilder: tc.meshConfig != nil,
259
UseExtendedJwt: extended,
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,
267
policies := push.AuthzPolicies.ListAuthorizationPolicies(selectionOpts)
268
g := New(tc.tdBundle, push, policies, option)
270
t.Fatalf("failed to create generator")
275
for i := range wants {
276
wants[i] = "extended-" + wants[i]
279
verify(t, convertHTTP(got), baseDir, tc.want, false /* forTCP */)
285
func TestGenerator_GenerateTCP(t *testing.T) {
286
testCases := []struct {
288
tdBundle trustdomain.Bundle
289
meshConfig *meshconfig.MeshConfig
294
name: "allow-both-http-tcp",
295
input: "allow-both-http-tcp-in.yaml",
296
want: []string{"allow-both-http-tcp-out.yaml"},
299
name: "allow-only-http",
300
input: "allow-only-http-in.yaml",
301
want: []string{"allow-only-http-out.yaml"},
304
name: "audit-both-http-tcp",
305
input: "audit-both-http-tcp-in.yaml",
306
want: []string{"audit-both-http-tcp-out.yaml"},
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"},
315
name: "custom-only-http",
316
meshConfig: meshConfigHTTP,
317
input: "custom-only-http-in.yaml",
321
name: "deny-both-http-tcp",
322
input: "deny-both-http-tcp-in.yaml",
323
want: []string{"deny-both-http-tcp-out.yaml"},
327
input: "dry-run-mix-in.yaml",
328
want: []string{"dry-run-mix-out.yaml"},
333
for _, tc := range testCases {
334
t.Run(tc.name, func(t *testing.T) {
336
IsCustomBuilder: tc.meshConfig != nil,
338
push := push(t, baseDir+tc.input, tc.meshConfig)
340
selectionOpts := model.WorkloadSelectionOpts{
341
Namespace: proxy.ConfigNamespace,
342
WorkloadLabels: proxy.Labels,
344
policies := push.AuthzPolicies.ListAuthorizationPolicies(selectionOpts)
345
g := New(tc.tdBundle, push, policies, option)
347
t.Fatalf("failed to create generator")
350
verify(t, convertTCP(got), baseDir, tc.want, true /* forTCP */)
355
func verify(t *testing.T, gots []proto.Message, baseDir string, wants []string, forTCP bool) {
358
if len(gots) != len(wants) {
359
t.Fatalf("got %d configs but want %d", len(gots), len(wants))
361
for i, got := range gots {
362
gotYaml, err := protomarshal.ToYAML(got)
364
t.Fatalf("failed to convert to YAML: %v", err)
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)
372
t.Fatalf("failed to convert to YAML: %v", err)
375
if err := util.Compare([]byte(gotYaml), []byte(wantYaml)); err != nil {
381
func yamlPolicy(t *testing.T, filename string) *model.AuthorizationPolicies {
383
data, err := os.ReadFile(filename)
385
t.Fatalf("failed to read input yaml file: %v", err)
387
c, _, err := crd.ParseInputs(string(data))
389
t.Fatalf("failde to parse CRD: %v", err)
391
var configs []*config.Config
393
configs = append(configs, &c[i])
396
return newAuthzPolicies(t, configs)
399
func yamlConfig(t *testing.T, filename string, forTCP bool) proto.Message {
401
data, err := os.ReadFile(filename)
403
t.Fatalf("failed to read file: %v", err)
406
out := &listener.Filter{}
407
if err := protomarshal.ApplyYAML(string(data), out); err != nil {
408
t.Fatalf("failed to parse YAML: %v", err)
412
out := &hcm.HttpFilter{}
413
if err := protomarshal.ApplyYAML(string(data), out); err != nil {
414
t.Fatalf("failed to parse YAML: %v", err)
419
func convertHTTP(in []*hcm.HttpFilter) []proto.Message {
420
ret := make([]proto.Message, len(in))
427
func convertTCP(in []*listener.Filter) []proto.Message {
428
ret := make([]proto.Message, len(in))
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)
443
authzPolicies := model.GetAuthorizationPolicies(&model.Environment{
449
func push(t *testing.T, input string, mc *meshconfig.MeshConfig) *model.PushContext {
451
p := &model.PushContext{
452
AuthzPolicies: yamlPolicy(t, basePath+input),
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",
465
func node(version *model.IstioVersion) *model.Proxy {
468
ConfigNamespace: "foo",
470
Metadata: &model.NodeMetadata{
473
IstioVersion: version,