istio

Форк
0
388 строк · 12.7 Кб
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
	"fmt"
19
	"net/url"
20
	"sort"
21
	"strconv"
22
	"strings"
23

24
	"github.com/davecgh/go-spew/spew"
25
	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
26
	extauthzhttp "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_authz/v3"
27
	extauthztcp "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/ext_authz/v3"
28
	envoy_type_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
29
	envoytypev3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
30
	"github.com/hashicorp/go-multierror"
31
	"google.golang.org/protobuf/types/known/durationpb"
32

33
	meshconfig "istio.io/api/mesh/v1alpha1"
34
	"istio.io/istio/pilot/pkg/model"
35
	authzmodel "istio.io/istio/pilot/pkg/security/authz/model"
36
	"istio.io/istio/pkg/config/validation"
37
	"istio.io/istio/pkg/maps"
38
	"istio.io/istio/pkg/wellknown"
39
)
40

41
const (
42
	extAuthzMatchPrefix   = "istio-ext-authz"
43
	badCustomActionSuffix = `-deny-due-to-bad-CUSTOM-action`
44
)
45

46
var supportedStatus = func() []int {
47
	var supported []int
48
	for code := range envoytypev3.StatusCode_name {
49
		supported = append(supported, int(code))
50
	}
51
	sort.Ints(supported)
52
	return supported
53
}()
54

55
type builtExtAuthz struct {
56
	http *extauthzhttp.ExtAuthz
57
	tcp  *extauthztcp.ExtAuthz
58
	err  error
59
}
60

61
func processExtensionProvider(push *model.PushContext) map[string]*builtExtAuthz {
62
	resolved := map[string]*builtExtAuthz{}
63
	for i, config := range push.Mesh.ExtensionProviders {
64
		var errs error
65
		if config.Name == "" {
66
			errs = multierror.Append(errs, fmt.Errorf("extension provider name must not be empty, found empty at index: %d", i))
67
		} else if _, found := resolved[config.Name]; found {
68
			errs = multierror.Append(errs, fmt.Errorf("extension provider name must be unique, found duplicate: %s", config.Name))
69
		}
70
		var parsed *builtExtAuthz
71
		var err error
72
		// TODO(yangminzhu): Refactor and cache the ext_authz config.
73
		switch p := config.Provider.(type) {
74
		case *meshconfig.MeshConfig_ExtensionProvider_EnvoyExtAuthzHttp:
75
			if err = validation.ValidateExtensionProviderEnvoyExtAuthzHTTP(p.EnvoyExtAuthzHttp); err == nil {
76
				parsed, err = buildExtAuthzHTTP(push, p.EnvoyExtAuthzHttp)
77
			}
78
		case *meshconfig.MeshConfig_ExtensionProvider_EnvoyExtAuthzGrpc:
79
			if err = validation.ValidateExtensionProviderEnvoyExtAuthzGRPC(p.EnvoyExtAuthzGrpc); err == nil {
80
				parsed, err = buildExtAuthzGRPC(push, p.EnvoyExtAuthzGrpc)
81
			}
82
		default:
83
			continue
84
		}
85
		if err != nil {
86
			errs = multierror.Append(errs, multierror.Prefix(err, fmt.Sprintf("failed to parse extension provider %q:", config.Name)))
87
		}
88
		if parsed == nil {
89
			parsed = &builtExtAuthz{}
90
		}
91
		parsed.err = errs
92
		resolved[config.Name] = parsed
93
	}
94

95
	if authzLog.DebugEnabled() {
96
		authzLog.Debugf("Resolved extension providers: %v", spew.Sdump(resolved))
97
	}
98
	return resolved
99
}
100

101
func notAllTheSame(names []string) bool {
102
	for i := 1; i < len(names); i++ {
103
		if names[i-1] != names[i] {
104
			return true
105
		}
106
	}
107
	return false
108
}
109

110
func getExtAuthz(resolved map[string]*builtExtAuthz, providers []string) (*builtExtAuthz, error) {
111
	if resolved == nil {
112
		return nil, fmt.Errorf("extension provider is either invalid or undefined")
113
	}
114
	if len(providers) < 1 {
115
		return nil, fmt.Errorf("no provider specified in authorization policy")
116
	}
117
	if notAllTheSame(providers) {
118
		return nil, fmt.Errorf("only 1 provider can be used per workload, found multiple providers: %v", providers)
119
	}
120

121
	provider := providers[0]
122
	ret, found := resolved[provider]
123
	if !found {
124
		var li []string
125
		for p := range resolved {
126
			li = append(li, p)
127
		}
128
		return nil, fmt.Errorf("available providers are %v but found %q", li, provider)
129
	} else if ret.err != nil {
130
		return nil, fmt.Errorf("found errors in provider %s: %v", provider, ret.err)
131
	}
132

133
	return ret, nil
134
}
135

136
func buildExtAuthzHTTP(push *model.PushContext,
137
	config *meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationHttpProvider,
138
) (*builtExtAuthz, error) {
139
	var errs error
140
	port, err := parsePort(config.Port)
141
	if err != nil {
142
		errs = multierror.Append(errs, err)
143
	}
144
	hostname, cluster, err := model.LookupCluster(push, config.Service, port)
145
	if err != nil {
146
		model.IncLookupClusterFailures("authz")
147
		errs = multierror.Append(errs, err)
148
	}
149
	status, err := parseStatusOnError(config.StatusOnError)
150
	if err != nil {
151
		errs = multierror.Append(errs, err)
152
	}
153
	if config.PathPrefix != "" {
154
		if _, err := url.Parse(config.PathPrefix); err != nil {
155
			errs = multierror.Append(errs, multierror.Prefix(err, fmt.Sprintf("invalid pathPrefix %q:", config.PathPrefix)))
156
		}
157
		if !strings.HasPrefix(config.PathPrefix, "/") {
158
			errs = multierror.Append(errs, fmt.Errorf("pathPrefix must begin with `/`, found: %q", config.PathPrefix))
159
		}
160
	}
161
	checkWildcard := func(field string, values []string) {
162
		for _, val := range values {
163
			if val == "*" {
164
				errs = multierror.Append(errs, fmt.Errorf("a single wildcard (\"*\") is not supported, change it to either prefix or suffix match: %s", field))
165
			}
166
		}
167
	}
168
	checkWildcard("IncludeRequestHeadersInCheck", config.IncludeRequestHeadersInCheck)
169
	//nolint: staticcheck
170
	checkWildcard("IncludeHeadersInCheck", config.IncludeHeadersInCheck)
171
	checkWildcard("HeadersToDownstreamOnDeny", config.HeadersToDownstreamOnDeny)
172
	checkWildcard("HeadersToDownstreamOnAllow", config.HeadersToDownstreamOnAllow)
173
	checkWildcard("HeadersToUpstreamOnAllow", config.HeadersToUpstreamOnAllow)
174

175
	if errs != nil {
176
		return nil, errs
177
	}
178

179
	return generateHTTPConfig(hostname, cluster, status, config), nil
180
}
181

182
func buildExtAuthzGRPC(push *model.PushContext,
183
	config *meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationGrpcProvider,
184
) (*builtExtAuthz, error) {
185
	var errs error
186
	port, err := parsePort(config.Port)
187
	if err != nil {
188
		errs = multierror.Append(errs, err)
189
	}
190
	hostname, cluster, err := model.LookupCluster(push, config.Service, port)
191
	if err != nil {
192
		errs = multierror.Append(errs, err)
193
	}
194
	status, err := parseStatusOnError(config.StatusOnError)
195
	if err != nil {
196
		errs = multierror.Append(errs, err)
197
	}
198
	if errs != nil {
199
		return nil, errs
200
	}
201

202
	return generateGRPCConfig(cluster, hostname, config, status), nil
203
}
204

205
func parsePort(port uint32) (int, error) {
206
	if 1 <= port && port <= 65535 {
207
		return int(port), nil
208
	}
209
	return 0, fmt.Errorf("port must be in the range [1, 65535], found: %d", port)
210
}
211

212
func parseStatusOnError(status string) (*envoytypev3.HttpStatus, error) {
213
	if status == "" {
214
		return nil, nil
215
	}
216
	code, err := strconv.ParseInt(status, 10, 32)
217
	if err != nil {
218
		return nil, multierror.Prefix(err, fmt.Sprintf("invalid statusOnError %q:", status))
219
	}
220
	if _, found := envoytypev3.StatusCode_name[int32(code)]; !found {
221
		return nil, fmt.Errorf("unsupported statusOnError %s, supported values: %v", status, supportedStatus)
222
	}
223
	return &envoytypev3.HttpStatus{Code: envoytypev3.StatusCode(code)}, nil
224
}
225

226
func generateHTTPConfig(hostname, cluster string, status *envoytypev3.HttpStatus,
227
	config *meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationHttpProvider,
228
) *builtExtAuthz {
229
	service := &extauthzhttp.HttpService{
230
		PathPrefix: config.PathPrefix,
231
		ServerUri: &core.HttpUri{
232
			// Timeout is required.
233
			Timeout: timeoutOrDefault(config.Timeout),
234
			// Uri is required but actually not used in the ext_authz filter.
235
			Uri: fmt.Sprintf("http://%s", hostname),
236
			HttpUpstreamType: &core.HttpUri_Cluster{
237
				Cluster: cluster,
238
			},
239
		},
240
	}
241
	allowedHeaders := generateHeaders(config.IncludeRequestHeadersInCheck)
242
	if allowedHeaders == nil {
243
		// IncludeHeadersInCheck is deprecated, only use it if IncludeRequestHeadersInCheck is not set.
244
		// TODO: Remove the IncludeHeadersInCheck field before promoting to beta.
245
		//nolint: staticcheck
246
		allowedHeaders = generateHeaders(config.IncludeHeadersInCheck)
247
	}
248
	var headersToAdd []*core.HeaderValue
249
	additionalHeaders := maps.Keys(config.IncludeAdditionalHeadersInCheck)
250
	sort.Strings(additionalHeaders)
251
	for _, k := range additionalHeaders {
252
		headersToAdd = append(headersToAdd, &core.HeaderValue{
253
			Key:   k,
254
			Value: config.IncludeAdditionalHeadersInCheck[k],
255
		})
256
	}
257
	if len(headersToAdd) != 0 {
258
		service.AuthorizationRequest = &extauthzhttp.AuthorizationRequest{
259
			HeadersToAdd: headersToAdd,
260
		}
261
	}
262

263
	if len(config.HeadersToUpstreamOnAllow) > 0 || len(config.HeadersToDownstreamOnDeny) > 0 ||
264
		len(config.HeadersToDownstreamOnAllow) > 0 {
265
		service.AuthorizationResponse = &extauthzhttp.AuthorizationResponse{
266
			AllowedUpstreamHeaders:        generateHeaders(config.HeadersToUpstreamOnAllow),
267
			AllowedClientHeaders:          generateHeaders(config.HeadersToDownstreamOnDeny),
268
			AllowedClientHeadersOnSuccess: generateHeaders(config.HeadersToDownstreamOnAllow),
269
		}
270
	}
271
	http := &extauthzhttp.ExtAuthz{
272
		StatusOnError:       status,
273
		FailureModeAllow:    config.FailOpen,
274
		TransportApiVersion: core.ApiVersion_V3,
275
		Services: &extauthzhttp.ExtAuthz_HttpService{
276
			HttpService: service,
277
		},
278
		FilterEnabledMetadata: generateFilterMatcher(wellknown.HTTPRoleBasedAccessControl),
279
		WithRequestBody:       withBodyRequest(config.IncludeRequestBodyInCheck),
280
	}
281
	if allowedHeaders != nil {
282
		http.AllowedHeaders = allowedHeaders
283
	}
284
	return &builtExtAuthz{http: http}
285
}
286

287
func generateGRPCConfig(
288
	cluster string,
289
	hostname string,
290
	config *meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationGrpcProvider,
291
	status *envoytypev3.HttpStatus,
292
) *builtExtAuthz {
293
	grpc := &core.GrpcService{
294
		TargetSpecifier: &core.GrpcService_EnvoyGrpc_{
295
			EnvoyGrpc: &core.GrpcService_EnvoyGrpc{
296
				ClusterName: cluster,
297
				Authority:   hostname,
298
			},
299
		},
300
		Timeout: timeoutOrDefault(config.Timeout),
301
	}
302
	http := &extauthzhttp.ExtAuthz{
303
		StatusOnError:    status,
304
		FailureModeAllow: config.FailOpen,
305
		Services: &extauthzhttp.ExtAuthz_GrpcService{
306
			GrpcService: grpc,
307
		},
308
		FilterEnabledMetadata: generateFilterMatcher(wellknown.HTTPRoleBasedAccessControl),
309
		TransportApiVersion:   core.ApiVersion_V3,
310
		WithRequestBody:       withBodyRequest(config.IncludeRequestBodyInCheck),
311
	}
312
	tcp := &extauthztcp.ExtAuthz{
313
		StatPrefix:            "tcp.",
314
		FailureModeAllow:      config.FailOpen,
315
		TransportApiVersion:   core.ApiVersion_V3,
316
		GrpcService:           grpc,
317
		FilterEnabledMetadata: generateFilterMatcher(wellknown.RoleBasedAccessControl),
318
	}
319
	return &builtExtAuthz{http: http, tcp: tcp}
320
}
321

322
func generateHeaders(headers []string) *envoy_type_matcher_v3.ListStringMatcher {
323
	if len(headers) == 0 {
324
		return nil
325
	}
326
	var patterns []*envoy_type_matcher_v3.StringMatcher
327
	for _, header := range headers {
328
		pattern := &envoy_type_matcher_v3.StringMatcher{
329
			IgnoreCase: true,
330
		}
331
		if strings.HasPrefix(header, "*") {
332
			pattern.MatchPattern = &envoy_type_matcher_v3.StringMatcher_Suffix{
333
				Suffix: strings.TrimPrefix(header, "*"),
334
			}
335
		} else if strings.HasSuffix(header, "*") {
336
			pattern.MatchPattern = &envoy_type_matcher_v3.StringMatcher_Prefix{
337
				Prefix: strings.TrimSuffix(header, "*"),
338
			}
339
		} else {
340
			pattern.MatchPattern = &envoy_type_matcher_v3.StringMatcher_Exact{
341
				Exact: header,
342
			}
343
		}
344
		patterns = append(patterns, pattern)
345
	}
346
	return &envoy_type_matcher_v3.ListStringMatcher{Patterns: patterns}
347
}
348

349
func generateFilterMatcher(name string) *envoy_type_matcher_v3.MetadataMatcher {
350
	return &envoy_type_matcher_v3.MetadataMatcher{
351
		Filter: name,
352
		Path: []*envoy_type_matcher_v3.MetadataMatcher_PathSegment{
353
			{
354
				Segment: &envoy_type_matcher_v3.MetadataMatcher_PathSegment_Key{
355
					Key: authzmodel.RBACExtAuthzShadowRulesStatPrefix + authzmodel.RBACShadowEffectivePolicyID,
356
				},
357
			},
358
		},
359
		Value: &envoy_type_matcher_v3.ValueMatcher{
360
			MatchPattern: &envoy_type_matcher_v3.ValueMatcher_StringMatch{
361
				StringMatch: &envoy_type_matcher_v3.StringMatcher{
362
					MatchPattern: &envoy_type_matcher_v3.StringMatcher_Prefix{
363
						Prefix: extAuthzMatchPrefix,
364
					},
365
				},
366
			},
367
		},
368
	}
369
}
370

371
func timeoutOrDefault(t *durationpb.Duration) *durationpb.Duration {
372
	if t == nil {
373
		// Default timeout is 600s.
374
		return &durationpb.Duration{Seconds: 600}
375
	}
376
	return t
377
}
378

379
func withBodyRequest(config *meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationRequestBody) *extauthzhttp.BufferSettings {
380
	if config == nil {
381
		return nil
382
	}
383
	return &extauthzhttp.BufferSettings{
384
		MaxRequestBytes:     config.MaxRequestBytes,
385
		AllowPartialMessage: config.AllowPartialMessage,
386
		PackAsBytes:         config.PackAsBytes,
387
	}
388
}
389

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

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

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

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