istio

Форк
0
376 строк · 12.6 Кб
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
	"strconv"
20

21
	listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
22
	rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3"
23
	rbachttp "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/rbac/v3"
24
	hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
25
	rbactcp "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/rbac/v3"
26
	"github.com/hashicorp/go-multierror"
27

28
	"istio.io/api/annotation"
29
	"istio.io/istio/pilot/pkg/model"
30
	authzmodel "istio.io/istio/pilot/pkg/security/authz/model"
31
	"istio.io/istio/pilot/pkg/security/trustdomain"
32
	"istio.io/istio/pilot/pkg/util/protoconv"
33
	"istio.io/istio/pkg/maps"
34
	"istio.io/istio/pkg/wellknown"
35
)
36

37
var rbacPolicyMatchNever = &rbacpb.Policy{
38
	Permissions: []*rbacpb.Permission{{Rule: &rbacpb.Permission_NotRule{
39
		NotRule: &rbacpb.Permission{Rule: &rbacpb.Permission_Any{Any: true}},
40
	}}},
41
	Principals: []*rbacpb.Principal{{Identifier: &rbacpb.Principal_NotId{
42
		NotId: &rbacpb.Principal{Identifier: &rbacpb.Principal_Any{Any: true}},
43
	}}},
44
}
45

46
// General setting to control behavior
47
type Option struct {
48
	IsCustomBuilder bool
49
	UseFilterState  bool
50
	UseExtendedJwt  bool
51
}
52

53
// Builder builds Istio authorization policy to Envoy filters.
54
type Builder struct {
55
	trustDomainBundle trustdomain.Bundle
56
	option            Option
57

58
	// populated when building for CUSTOM action.
59
	customPolicies []model.AuthorizationPolicy
60
	extensions     map[string]*builtExtAuthz
61

62
	// populated when building for ALLOW/DENY/AUDIT action.
63
	denyPolicies  []model.AuthorizationPolicy
64
	allowPolicies []model.AuthorizationPolicy
65
	auditPolicies []model.AuthorizationPolicy
66

67
	// logger emits logs about policies
68
	logger *AuthzLogger
69
}
70

71
// New returns a new builder for the given workload with the authorization policy.
72
// Returns nil if none of the authorization policies are enabled for the workload.
73
func New(trustDomainBundle trustdomain.Bundle, push *model.PushContext, policies model.AuthorizationPoliciesResult, option Option) *Builder {
74
	if option.IsCustomBuilder {
75
		if len(policies.Custom) == 0 {
76
			return nil
77
		}
78
		return &Builder{
79
			customPolicies:    policies.Custom,
80
			extensions:        processExtensionProvider(push),
81
			trustDomainBundle: trustDomainBundle,
82
			option:            option,
83
		}
84
	}
85

86
	if len(policies.Deny) == 0 && len(policies.Allow) == 0 && len(policies.Audit) == 0 {
87
		return nil
88
	}
89
	return &Builder{
90
		denyPolicies:      policies.Deny,
91
		allowPolicies:     policies.Allow,
92
		auditPolicies:     policies.Audit,
93
		trustDomainBundle: trustDomainBundle,
94
		option:            option,
95
	}
96
}
97

98
// BuildHTTP returns the HTTP filters built from the authorization policy.
99
func (b Builder) BuildHTTP() []*hcm.HttpFilter {
100
	b.logger = &AuthzLogger{}
101
	defer b.logger.Report()
102
	if b.option.IsCustomBuilder {
103
		// Use the DENY action so that a HTTP rule is properly handled when generating for TCP filter chain.
104
		if configs := b.build(b.customPolicies, rbacpb.RBAC_DENY, false); configs != nil {
105
			b.logger.AppendDebugf("built %d HTTP filters for CUSTOM action", len(configs.http))
106
			return configs.http
107
		}
108
		return nil
109
	}
110

111
	var filters []*hcm.HttpFilter
112
	if configs := b.build(b.auditPolicies, rbacpb.RBAC_LOG, false); configs != nil {
113
		b.logger.AppendDebugf("built %d HTTP filters for AUDIT action", len(configs.http))
114
		filters = append(filters, configs.http...)
115
	}
116
	if configs := b.build(b.denyPolicies, rbacpb.RBAC_DENY, false); configs != nil {
117
		b.logger.AppendDebugf("built %d HTTP filters for DENY action", len(configs.http))
118
		filters = append(filters, configs.http...)
119
	}
120
	if configs := b.build(b.allowPolicies, rbacpb.RBAC_ALLOW, false); configs != nil {
121
		b.logger.AppendDebugf("built %d HTTP filters for ALLOW action", len(configs.http))
122
		filters = append(filters, configs.http...)
123
	}
124
	return filters
125
}
126

127
// BuildTCP returns the TCP filters built from the authorization policy.
128
func (b Builder) BuildTCP() []*listener.Filter {
129
	b.logger = &AuthzLogger{}
130
	defer b.logger.Report()
131
	if b.option.IsCustomBuilder {
132
		if configs := b.build(b.customPolicies, rbacpb.RBAC_DENY, true); configs != nil {
133
			b.logger.AppendDebugf("built %d TCP filters for CUSTOM action", len(configs.tcp))
134
			return configs.tcp
135
		}
136
		return nil
137
	}
138

139
	var filters []*listener.Filter
140
	if configs := b.build(b.auditPolicies, rbacpb.RBAC_LOG, true); configs != nil {
141
		b.logger.AppendDebugf("built %d TCP filters for AUDIT action", len(configs.tcp))
142
		filters = append(filters, configs.tcp...)
143
	}
144
	if configs := b.build(b.denyPolicies, rbacpb.RBAC_DENY, true); configs != nil {
145
		b.logger.AppendDebugf("built %d TCP filters for DENY action", len(configs.tcp))
146
		filters = append(filters, configs.tcp...)
147
	}
148
	if configs := b.build(b.allowPolicies, rbacpb.RBAC_ALLOW, true); configs != nil {
149
		b.logger.AppendDebugf("built %d TCP filters for ALLOW action", len(configs.tcp))
150
		filters = append(filters, configs.tcp...)
151
	}
152
	return filters
153
}
154

155
type builtConfigs struct {
156
	http []*hcm.HttpFilter
157
	tcp  []*listener.Filter
158
}
159

160
func (b Builder) isDryRun(policy model.AuthorizationPolicy) bool {
161
	dryRun := false
162
	if val, ok := policy.Annotations[annotation.IoIstioDryRun.Name]; ok {
163
		var err error
164
		dryRun, err = strconv.ParseBool(val)
165
		if err != nil {
166
			b.logger.AppendError(fmt.Errorf("failed to parse the value of %s: %v", annotation.IoIstioDryRun.Name, err))
167
		}
168
	}
169
	return dryRun
170
}
171

172
func shadowRuleStatPrefix(rule *rbacpb.RBAC) string {
173
	switch rule.GetAction() {
174
	case rbacpb.RBAC_ALLOW:
175
		return authzmodel.RBACShadowRulesAllowStatPrefix
176
	case rbacpb.RBAC_DENY:
177
		return authzmodel.RBACShadowRulesDenyStatPrefix
178
	default:
179
		return ""
180
	}
181
}
182

183
func (b Builder) build(policies []model.AuthorizationPolicy, action rbacpb.RBAC_Action, forTCP bool) *builtConfigs {
184
	if len(policies) == 0 {
185
		return nil
186
	}
187

188
	enforceRules := &rbacpb.RBAC{
189
		Action:   action,
190
		Policies: map[string]*rbacpb.Policy{},
191
	}
192
	shadowRules := &rbacpb.RBAC{
193
		Action:   action,
194
		Policies: map[string]*rbacpb.Policy{},
195
	}
196

197
	var providers []string
198
	filterType := "HTTP"
199
	if forTCP {
200
		filterType = "TCP"
201
	}
202
	hasEnforcePolicy, hasDryRunPolicy := false, false
203
	for _, policy := range policies {
204
		var currentRule *rbacpb.RBAC
205
		if b.isDryRun(policy) {
206
			currentRule = shadowRules
207
			hasDryRunPolicy = true
208
		} else {
209
			currentRule = enforceRules
210
			hasEnforcePolicy = true
211
		}
212
		if b.option.IsCustomBuilder {
213
			providers = append(providers, policy.Spec.GetProvider().GetName())
214
		}
215
		for i, rule := range policy.Spec.Rules {
216
			// The name will later be used by ext_authz filter to get the evaluation result from dynamic metadata.
217
			name := policyName(policy.Namespace, policy.Name, i, b.option)
218
			if rule == nil {
219
				b.logger.AppendError(fmt.Errorf("skipped nil rule %s", name))
220
				continue
221
			}
222
			m, err := authzmodel.New(rule, b.option.UseExtendedJwt)
223
			if err != nil {
224
				b.logger.AppendError(multierror.Prefix(err, fmt.Sprintf("skipped invalid rule %s:", name)))
225
				continue
226
			}
227
			m.MigrateTrustDomain(b.trustDomainBundle)
228
			if len(b.trustDomainBundle.TrustDomains) > 1 {
229
				b.logger.AppendDebugf("patched source principal with trust domain aliases %v", b.trustDomainBundle.TrustDomains)
230
			}
231
			generated, err := m.Generate(forTCP, !b.option.UseFilterState, action)
232
			if err != nil {
233
				b.logger.AppendDebugf("skipped rule %s on TCP filter chain: %v", name, err)
234
				continue
235
			}
236
			if generated != nil {
237
				currentRule.Policies[name] = generated
238
				b.logger.AppendDebugf("generated config from rule %s on %s filter chain successfully", name, filterType)
239
			}
240
		}
241
		if len(policy.Spec.Rules) == 0 {
242
			// Generate an explicit policy that never matches.
243
			name := policyName(policy.Namespace, policy.Name, 0, b.option)
244
			b.logger.AppendDebugf("generated config from policy %s on %s filter chain successfully", name, filterType)
245
			currentRule.Policies[name] = rbacPolicyMatchNever
246
		}
247
	}
248

249
	if !hasEnforcePolicy {
250
		enforceRules = nil
251
	}
252
	if !hasDryRunPolicy {
253
		shadowRules = nil
254
	}
255
	if forTCP {
256
		return &builtConfigs{tcp: b.buildTCP(enforceRules, shadowRules, providers)}
257
	}
258
	return &builtConfigs{http: b.buildHTTP(enforceRules, shadowRules, providers)}
259
}
260

261
func (b Builder) buildHTTP(rules *rbacpb.RBAC, shadowRules *rbacpb.RBAC, providers []string) []*hcm.HttpFilter {
262
	if !b.option.IsCustomBuilder {
263
		rbac := &rbachttp.RBAC{
264
			Rules:                 rules,
265
			ShadowRules:           shadowRules,
266
			ShadowRulesStatPrefix: shadowRuleStatPrefix(shadowRules),
267
		}
268
		return []*hcm.HttpFilter{
269
			{
270
				Name:       wellknown.HTTPRoleBasedAccessControl,
271
				ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: protoconv.MessageToAny(rbac)},
272
			},
273
		}
274
	}
275

276
	extauthz, err := getExtAuthz(b.extensions, providers)
277
	if err != nil {
278
		b.logger.AppendError(multierror.Prefix(err, "failed to process CUSTOM action, will generate deny configs for the specified rules:"))
279
		rbac := &rbachttp.RBAC{Rules: getBadCustomDenyRules(rules)}
280
		return []*hcm.HttpFilter{
281
			{
282
				Name:       wellknown.HTTPRoleBasedAccessControl,
283
				ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: protoconv.MessageToAny(rbac)},
284
			},
285
		}
286
	}
287
	// Add the RBAC filter in shadow mode so that it only evaluates the matching rules for CUSTOM action but not enforce it.
288
	// The evaluation result is stored in the dynamic metadata keyed by the policy name. And then the ext_authz filter
289
	// can utilize these metadata to trigger the enforcement conditionally.
290
	// See https://docs.google.com/document/d/1V4mCQCw7mlGp0zSQQXYoBdbKMDnkPOjeyUb85U07iSI/edit#bookmark=kix.jdq8u0an2r6s
291
	// for more details.
292
	rbac := &rbachttp.RBAC{
293
		ShadowRules:           rules,
294
		ShadowRulesStatPrefix: authzmodel.RBACExtAuthzShadowRulesStatPrefix,
295
	}
296
	return []*hcm.HttpFilter{
297
		{
298
			Name:       wellknown.HTTPRoleBasedAccessControl,
299
			ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: protoconv.MessageToAny(rbac)},
300
		},
301
		{
302
			Name:       wellknown.HTTPExternalAuthorization,
303
			ConfigType: &hcm.HttpFilter_TypedConfig{TypedConfig: protoconv.MessageToAny(extauthz.http)},
304
		},
305
	}
306
}
307

308
func (b Builder) buildTCP(rules *rbacpb.RBAC, shadowRules *rbacpb.RBAC, providers []string) []*listener.Filter {
309
	if !b.option.IsCustomBuilder {
310
		rbac := &rbactcp.RBAC{
311
			Rules:                 rules,
312
			StatPrefix:            authzmodel.RBACTCPFilterStatPrefix,
313
			ShadowRules:           shadowRules,
314
			ShadowRulesStatPrefix: shadowRuleStatPrefix(shadowRules),
315
		}
316
		return []*listener.Filter{
317
			{
318
				Name:       wellknown.RoleBasedAccessControl,
319
				ConfigType: &listener.Filter_TypedConfig{TypedConfig: protoconv.MessageToAny(rbac)},
320
			},
321
		}
322
	}
323

324
	extauthz, err := getExtAuthz(b.extensions, providers)
325
	if err != nil {
326
		b.logger.AppendError(multierror.Prefix(err, "failed to process CUSTOM action, will generate deny configs for the specified rules:"))
327
		rbac := &rbactcp.RBAC{
328
			Rules:      getBadCustomDenyRules(rules),
329
			StatPrefix: authzmodel.RBACTCPFilterStatPrefix,
330
		}
331
		return []*listener.Filter{
332
			{
333
				Name:       wellknown.RoleBasedAccessControl,
334
				ConfigType: &listener.Filter_TypedConfig{TypedConfig: protoconv.MessageToAny(rbac)},
335
			},
336
		}
337
	} else if extauthz.tcp == nil {
338
		b.logger.AppendDebugf("ignored CUSTOM action with HTTP provider on TCP filter chain")
339
		return nil
340
	}
341

342
	rbac := &rbactcp.RBAC{
343
		ShadowRules:           rules,
344
		StatPrefix:            authzmodel.RBACTCPFilterStatPrefix,
345
		ShadowRulesStatPrefix: authzmodel.RBACExtAuthzShadowRulesStatPrefix,
346
	}
347
	return []*listener.Filter{
348
		{
349
			Name:       wellknown.RoleBasedAccessControl,
350
			ConfigType: &listener.Filter_TypedConfig{TypedConfig: protoconv.MessageToAny(rbac)},
351
		},
352
		{
353
			Name:       wellknown.ExternalAuthorization,
354
			ConfigType: &listener.Filter_TypedConfig{TypedConfig: protoconv.MessageToAny(extauthz.tcp)},
355
		},
356
	}
357
}
358

359
func getBadCustomDenyRules(rules *rbacpb.RBAC) *rbacpb.RBAC {
360
	rbac := &rbacpb.RBAC{
361
		Action:   rbacpb.RBAC_DENY,
362
		Policies: map[string]*rbacpb.Policy{},
363
	}
364
	for _, key := range maps.Keys(rules.Policies) {
365
		rbac.Policies[key+badCustomActionSuffix] = rules.Policies[key]
366
	}
367
	return rbac
368
}
369

370
func policyName(namespace, name string, rule int, option Option) string {
371
	prefix := ""
372
	if option.IsCustomBuilder {
373
		prefix = extAuthzMatchPrefix + "-"
374
	}
375
	return fmt.Sprintf("%sns[%s]-policy[%s]-rule[%d]", prefix, namespace, name, rule)
376
}
377

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

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

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

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