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"
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"
42
extAuthzMatchPrefix = "istio-ext-authz"
43
badCustomActionSuffix = `-deny-due-to-bad-CUSTOM-action`
46
var supportedStatus = func() []int {
48
for code := range envoytypev3.StatusCode_name {
49
supported = append(supported, int(code))
55
type builtExtAuthz struct {
56
http *extauthzhttp.ExtAuthz
57
tcp *extauthztcp.ExtAuthz
61
func processExtensionProvider(push *model.PushContext) map[string]*builtExtAuthz {
62
resolved := map[string]*builtExtAuthz{}
63
for i, config := range push.Mesh.ExtensionProviders {
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))
70
var parsed *builtExtAuthz
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)
78
case *meshconfig.MeshConfig_ExtensionProvider_EnvoyExtAuthzGrpc:
79
if err = validation.ValidateExtensionProviderEnvoyExtAuthzGRPC(p.EnvoyExtAuthzGrpc); err == nil {
80
parsed, err = buildExtAuthzGRPC(push, p.EnvoyExtAuthzGrpc)
86
errs = multierror.Append(errs, multierror.Prefix(err, fmt.Sprintf("failed to parse extension provider %q:", config.Name)))
89
parsed = &builtExtAuthz{}
92
resolved[config.Name] = parsed
95
if authzLog.DebugEnabled() {
96
authzLog.Debugf("Resolved extension providers: %v", spew.Sdump(resolved))
101
func notAllTheSame(names []string) bool {
102
for i := 1; i < len(names); i++ {
103
if names[i-1] != names[i] {
110
func getExtAuthz(resolved map[string]*builtExtAuthz, providers []string) (*builtExtAuthz, error) {
112
return nil, fmt.Errorf("extension provider is either invalid or undefined")
114
if len(providers) < 1 {
115
return nil, fmt.Errorf("no provider specified in authorization policy")
117
if notAllTheSame(providers) {
118
return nil, fmt.Errorf("only 1 provider can be used per workload, found multiple providers: %v", providers)
121
provider := providers[0]
122
ret, found := resolved[provider]
125
for p := range resolved {
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)
136
func buildExtAuthzHTTP(push *model.PushContext,
137
config *meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationHttpProvider,
138
) (*builtExtAuthz, error) {
140
port, err := parsePort(config.Port)
142
errs = multierror.Append(errs, err)
144
hostname, cluster, err := model.LookupCluster(push, config.Service, port)
146
model.IncLookupClusterFailures("authz")
147
errs = multierror.Append(errs, err)
149
status, err := parseStatusOnError(config.StatusOnError)
151
errs = multierror.Append(errs, err)
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)))
157
if !strings.HasPrefix(config.PathPrefix, "/") {
158
errs = multierror.Append(errs, fmt.Errorf("pathPrefix must begin with `/`, found: %q", config.PathPrefix))
161
checkWildcard := func(field string, values []string) {
162
for _, val := range values {
164
errs = multierror.Append(errs, fmt.Errorf("a single wildcard (\"*\") is not supported, change it to either prefix or suffix match: %s", field))
168
checkWildcard("IncludeRequestHeadersInCheck", config.IncludeRequestHeadersInCheck)
170
checkWildcard("IncludeHeadersInCheck", config.IncludeHeadersInCheck)
171
checkWildcard("HeadersToDownstreamOnDeny", config.HeadersToDownstreamOnDeny)
172
checkWildcard("HeadersToDownstreamOnAllow", config.HeadersToDownstreamOnAllow)
173
checkWildcard("HeadersToUpstreamOnAllow", config.HeadersToUpstreamOnAllow)
179
return generateHTTPConfig(hostname, cluster, status, config), nil
182
func buildExtAuthzGRPC(push *model.PushContext,
183
config *meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationGrpcProvider,
184
) (*builtExtAuthz, error) {
186
port, err := parsePort(config.Port)
188
errs = multierror.Append(errs, err)
190
hostname, cluster, err := model.LookupCluster(push, config.Service, port)
192
errs = multierror.Append(errs, err)
194
status, err := parseStatusOnError(config.StatusOnError)
196
errs = multierror.Append(errs, err)
202
return generateGRPCConfig(cluster, hostname, config, status), nil
205
func parsePort(port uint32) (int, error) {
206
if 1 <= port && port <= 65535 {
207
return int(port), nil
209
return 0, fmt.Errorf("port must be in the range [1, 65535], found: %d", port)
212
func parseStatusOnError(status string) (*envoytypev3.HttpStatus, error) {
216
code, err := strconv.ParseInt(status, 10, 32)
218
return nil, multierror.Prefix(err, fmt.Sprintf("invalid statusOnError %q:", status))
220
if _, found := envoytypev3.StatusCode_name[int32(code)]; !found {
221
return nil, fmt.Errorf("unsupported statusOnError %s, supported values: %v", status, supportedStatus)
223
return &envoytypev3.HttpStatus{Code: envoytypev3.StatusCode(code)}, nil
226
func generateHTTPConfig(hostname, cluster string, status *envoytypev3.HttpStatus,
227
config *meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationHttpProvider,
229
service := &extauthzhttp.HttpService{
230
PathPrefix: config.PathPrefix,
231
ServerUri: &core.HttpUri{
233
Timeout: timeoutOrDefault(config.Timeout),
235
Uri: fmt.Sprintf("http://%s", hostname),
236
HttpUpstreamType: &core.HttpUri_Cluster{
241
allowedHeaders := generateHeaders(config.IncludeRequestHeadersInCheck)
242
if allowedHeaders == nil {
246
allowedHeaders = generateHeaders(config.IncludeHeadersInCheck)
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{
254
Value: config.IncludeAdditionalHeadersInCheck[k],
257
if len(headersToAdd) != 0 {
258
service.AuthorizationRequest = &extauthzhttp.AuthorizationRequest{
259
HeadersToAdd: headersToAdd,
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),
271
http := &extauthzhttp.ExtAuthz{
272
StatusOnError: status,
273
FailureModeAllow: config.FailOpen,
274
TransportApiVersion: core.ApiVersion_V3,
275
Services: &extauthzhttp.ExtAuthz_HttpService{
276
HttpService: service,
278
FilterEnabledMetadata: generateFilterMatcher(wellknown.HTTPRoleBasedAccessControl),
279
WithRequestBody: withBodyRequest(config.IncludeRequestBodyInCheck),
281
if allowedHeaders != nil {
282
http.AllowedHeaders = allowedHeaders
284
return &builtExtAuthz{http: http}
287
func generateGRPCConfig(
290
config *meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationGrpcProvider,
291
status *envoytypev3.HttpStatus,
293
grpc := &core.GrpcService{
294
TargetSpecifier: &core.GrpcService_EnvoyGrpc_{
295
EnvoyGrpc: &core.GrpcService_EnvoyGrpc{
296
ClusterName: cluster,
300
Timeout: timeoutOrDefault(config.Timeout),
302
http := &extauthzhttp.ExtAuthz{
303
StatusOnError: status,
304
FailureModeAllow: config.FailOpen,
305
Services: &extauthzhttp.ExtAuthz_GrpcService{
308
FilterEnabledMetadata: generateFilterMatcher(wellknown.HTTPRoleBasedAccessControl),
309
TransportApiVersion: core.ApiVersion_V3,
310
WithRequestBody: withBodyRequest(config.IncludeRequestBodyInCheck),
312
tcp := &extauthztcp.ExtAuthz{
314
FailureModeAllow: config.FailOpen,
315
TransportApiVersion: core.ApiVersion_V3,
317
FilterEnabledMetadata: generateFilterMatcher(wellknown.RoleBasedAccessControl),
319
return &builtExtAuthz{http: http, tcp: tcp}
322
func generateHeaders(headers []string) *envoy_type_matcher_v3.ListStringMatcher {
323
if len(headers) == 0 {
326
var patterns []*envoy_type_matcher_v3.StringMatcher
327
for _, header := range headers {
328
pattern := &envoy_type_matcher_v3.StringMatcher{
331
if strings.HasPrefix(header, "*") {
332
pattern.MatchPattern = &envoy_type_matcher_v3.StringMatcher_Suffix{
333
Suffix: strings.TrimPrefix(header, "*"),
335
} else if strings.HasSuffix(header, "*") {
336
pattern.MatchPattern = &envoy_type_matcher_v3.StringMatcher_Prefix{
337
Prefix: strings.TrimSuffix(header, "*"),
340
pattern.MatchPattern = &envoy_type_matcher_v3.StringMatcher_Exact{
344
patterns = append(patterns, pattern)
346
return &envoy_type_matcher_v3.ListStringMatcher{Patterns: patterns}
349
func generateFilterMatcher(name string) *envoy_type_matcher_v3.MetadataMatcher {
350
return &envoy_type_matcher_v3.MetadataMatcher{
352
Path: []*envoy_type_matcher_v3.MetadataMatcher_PathSegment{
354
Segment: &envoy_type_matcher_v3.MetadataMatcher_PathSegment_Key{
355
Key: authzmodel.RBACExtAuthzShadowRulesStatPrefix + authzmodel.RBACShadowEffectivePolicyID,
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,
371
func timeoutOrDefault(t *durationpb.Duration) *durationpb.Duration {
374
return &durationpb.Duration{Seconds: 600}
379
func withBodyRequest(config *meshconfig.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationRequestBody) *extauthzhttp.BufferSettings {
383
return &extauthzhttp.BufferSettings{
384
MaxRequestBytes: config.MaxRequestBytes,
385
AllowPartialMessage: config.AllowPartialMessage,
386
PackAsBytes: config.PackAsBytes,