Browse code

Collapse local and cluster PolicyRule retrieval

Mostly consistent with upstream - did not remove the reason field

Clayton Coleman authored on 2016/09/21 07:19:15
Showing 7 changed files
... ...
@@ -1,6 +1,8 @@
1 1
 package authorizer
2 2
 
3 3
 import (
4
+	"errors"
5
+
4 6
 	kapi "k8s.io/kubernetes/pkg/api"
5 7
 	"k8s.io/kubernetes/pkg/auth/user"
6 8
 	kerrors "k8s.io/kubernetes/pkg/util/errors"
... ...
@@ -21,36 +23,20 @@ func NewAuthorizer(ruleResolver rulevalidation.AuthorizationRuleResolver, forbid
21 21
 func (a *openshiftAuthorizer) Authorize(ctx kapi.Context, passedAttributes Action) (bool, string, error) {
22 22
 	attributes := CoerceToDefaultAuthorizationAttributes(passedAttributes)
23 23
 
24
-	// keep track of errors in case we are unable to authorize the action.
25
-	// It is entirely possible to get an error and be able to continue determine authorization status in spite of it.
26
-	// This is most common when a bound role is missing, but enough roles are still present and bound to authorize the request.
27
-	errs := []error{}
28
-
29
-	masterContext := kapi.WithNamespace(ctx, kapi.NamespaceNone)
30
-	globalAllowed, globalReason, err := a.authorizeWithNamespaceRules(masterContext, attributes)
31
-	if globalAllowed {
32
-		return true, globalReason, nil
33
-	}
34
-	if err != nil {
35
-		errs = append(errs, err)
24
+	user, ok := kapi.UserFrom(ctx)
25
+	if !ok {
26
+		return false, "", errors.New("no user available on context")
36 27
 	}
37
-
38 28
 	namespace, _ := kapi.NamespaceFrom(ctx)
39
-	if len(namespace) != 0 {
40
-		namespaceAllowed, namespaceReason, err := a.authorizeWithNamespaceRules(ctx, attributes)
41
-		if namespaceAllowed {
42
-			return true, namespaceReason, nil
43
-		}
44
-		if err != nil {
45
-			errs = append(errs, err)
46
-		}
29
+	allowed, reason, err := a.authorizeWithNamespaceRules(user, namespace, attributes)
30
+	if allowed {
31
+		return true, reason, nil
47 32
 	}
48
-
49
-	if len(errs) > 0 {
50
-		return false, "", kerrors.NewAggregate(errs)
33
+	// errors are allowed to occur
34
+	if err != nil {
35
+		return false, "", err
51 36
 	}
52 37
 
53
-	user, _ := kapi.UserFrom(ctx)
54 38
 	denyReason, err := a.forbiddenMessageMaker.MakeMessage(MessageContext{user, namespace, attributes})
55 39
 	if err != nil {
56 40
 		denyReason = err.Error()
... ...
@@ -64,37 +50,18 @@ func (a *openshiftAuthorizer) Authorize(ctx kapi.Context, passedAttributes Actio
64 64
 // This is done because policy rules are purely additive and policy determinations
65 65
 // can be made on the basis of those rules that are found.
66 66
 func (a *openshiftAuthorizer) GetAllowedSubjects(ctx kapi.Context, attributes Action) (sets.String, sets.String, error) {
67
-	errs := []error{}
68
-
69
-	masterContext := kapi.WithNamespace(ctx, kapi.NamespaceNone)
70
-	globalUsers, globalGroups, err := a.getAllowedSubjectsFromNamespaceBindings(masterContext, attributes)
71
-	if err != nil {
72
-		errs = append(errs, err)
73
-	}
74
-	localUsers, localGroups, err := a.getAllowedSubjectsFromNamespaceBindings(ctx, attributes)
75
-	if err != nil {
76
-		errs = append(errs, err)
77
-	}
78
-
79
-	users := sets.String{}
80
-	users.Insert(globalUsers.List()...)
81
-	users.Insert(localUsers.List()...)
82
-
83
-	groups := sets.String{}
84
-	groups.Insert(globalGroups.List()...)
85
-	groups.Insert(localGroups.List()...)
86
-
87
-	return users, groups, kerrors.NewAggregate(errs)
67
+	namespace, _ := kapi.NamespaceFrom(ctx)
68
+	return a.getAllowedSubjectsFromNamespaceBindings(namespace, attributes)
88 69
 }
89 70
 
90
-func (a *openshiftAuthorizer) getAllowedSubjectsFromNamespaceBindings(ctx kapi.Context, passedAttributes Action) (sets.String, sets.String, error) {
71
+func (a *openshiftAuthorizer) getAllowedSubjectsFromNamespaceBindings(namespace string, passedAttributes Action) (sets.String, sets.String, error) {
91 72
 	attributes := CoerceToDefaultAuthorizationAttributes(passedAttributes)
92 73
 
93
-	errs := []error{}
74
+	var errs []error
94 75
 
95
-	roleBindings, err := a.ruleResolver.GetRoleBindings(ctx)
76
+	roleBindings, err := a.ruleResolver.GetRoleBindings(namespace)
96 77
 	if err != nil {
97
-		return nil, nil, err
78
+		errs = append(errs, err)
98 79
 	}
99 80
 
100 81
 	users := sets.String{}
... ...
@@ -129,26 +96,34 @@ func (a *openshiftAuthorizer) getAllowedSubjectsFromNamespaceBindings(ctx kapi.C
129 129
 // authorizeWithNamespaceRules returns isAllowed, reason, and error.  If an error is returned, isAllowed and reason are still valid.  This seems strange
130 130
 // but errors are not always fatal to the authorization process.  It is entirely possible to get an error and be able to continue determine authorization
131 131
 // status in spite of it.  This is most common when a bound role is missing, but enough roles are still present and bound to authorize the request.
132
-func (a *openshiftAuthorizer) authorizeWithNamespaceRules(ctx kapi.Context, passedAttributes Action) (bool, string, error) {
132
+func (a *openshiftAuthorizer) authorizeWithNamespaceRules(user user.Info, namespace string, passedAttributes Action) (bool, string, error) {
133 133
 	attributes := CoerceToDefaultAuthorizationAttributes(passedAttributes)
134 134
 
135
-	allRules, ruleRetrievalError := a.ruleResolver.GetEffectivePolicyRules(ctx)
135
+	allRules, ruleRetrievalError := a.ruleResolver.RulesFor(user, namespace)
136 136
 
137
+	var errs []error
137 138
 	for _, rule := range allRules {
138 139
 		matches, err := attributes.RuleMatches(rule)
139 140
 		if err != nil {
140
-			return false, "", err
141
+			errs = append(errs, err)
142
+			continue
141 143
 		}
142 144
 		if matches {
143
-			namespace := kapi.NamespaceValue(ctx)
144 145
 			if len(namespace) == 0 {
145 146
 				return true, "allowed by cluster rule", nil
146 147
 			}
148
+			// not 100% accurate, because the rule may have been provided by a cluster rule. we no longer have
149
+			// this distinction upstream in practice.
147 150
 			return true, "allowed by rule in " + namespace, nil
148 151
 		}
149 152
 	}
150
-
151
-	return false, "", ruleRetrievalError
153
+	if len(errs) == 0 {
154
+		return false, "", ruleRetrievalError
155
+	}
156
+	if ruleRetrievalError != nil {
157
+		errs = append(errs, ruleRetrievalError)
158
+	}
159
+	return false, "", kerrors.NewAggregate(errs)
152 160
 }
153 161
 
154 162
 // TODO this may or may not be the behavior we want for managing rules.  As a for instance, a verb might be specified
... ...
@@ -414,7 +414,7 @@ func TestGlobalPolicyOutranksLocalPolicy(t *testing.T) {
414 414
 			Resource: "roles",
415 415
 		},
416 416
 		expectedAllowed: true,
417
-		expectedReason:  "allowed by cluster rule",
417
+		expectedReason:  "allowed by rule in adze",
418 418
 	}
419 419
 	test.clusterPolicies = newDefaultClusterPolicies()
420 420
 	test.policies = append(test.policies, newAdzePolicies()...)
... ...
@@ -23,7 +23,7 @@ func TestClusterAdminUseGroup(t *testing.T) {
23 23
 			Resource: "jobs",
24 24
 		},
25 25
 		expectedAllowed: true,
26
-		expectedReason:  "allowed by cluster rule",
26
+		expectedReason:  "allowed by rule in mallet",
27 27
 	}
28 28
 	test.clusterPolicies = newDefaultClusterPolicies()
29 29
 	test.clusterBindings = newDefaultClusterPolicyBindings()
... ...
@@ -40,7 +40,7 @@ func TestClusterReaderUseGroup(t *testing.T) {
40 40
 			Resource: "jobs",
41 41
 		},
42 42
 		expectedAllowed: true,
43
-		expectedReason:  "allowed by cluster rule",
43
+		expectedReason:  "allowed by rule in mallet",
44 44
 	}
45 45
 	test.clusterPolicies = newDefaultClusterPolicies()
46 46
 	test.clusterBindings = newDefaultClusterPolicyBindings()
... ...
@@ -74,26 +74,15 @@ func GetEffectivePolicyRules(ctx kapi.Context, ruleResolver rulevalidation.Autho
74 74
 		return nil, []error{kapierrors.NewBadRequest(fmt.Sprintf("user missing from context"))}
75 75
 	}
76 76
 
77
-	errors := []error{}
78
-	rules := []authorizationapi.PolicyRule{}
79
-
80
-	namespaceRules, err := ruleResolver.GetEffectivePolicyRules(ctx)
77
+	var errors []error
78
+	var rules []authorizationapi.PolicyRule
79
+	namespaceRules, err := ruleResolver.RulesFor(user, namespace)
81 80
 	if err != nil {
82 81
 		errors = append(errors, err)
83 82
 	}
84 83
 	for _, rule := range namespaceRules {
85 84
 		rules = append(rules, rulevalidation.BreakdownRule(rule)...)
86 85
 	}
87
-	if len(namespace) != 0 {
88
-		masterContext := kapi.WithNamespace(ctx, kapi.NamespaceNone)
89
-		clusterRules, err := ruleResolver.GetEffectivePolicyRules(masterContext)
90
-		if err != nil {
91
-			errors = append(errors, err)
92
-		}
93
-		for _, rule := range clusterRules {
94
-			rules = append(rules, rulevalidation.BreakdownRule(rule)...)
95
-		}
96
-	}
97 86
 
98 87
 	if scopes := user.GetExtra()[authorizationapi.ScopesKey]; len(scopes) > 0 {
99 88
 		rules, err = filterRulesByScopes(rules, scopes, namespace, clusterPolicyGetter)
... ...
@@ -1,8 +1,6 @@
1 1
 package rulevalidation
2 2
 
3 3
 import (
4
-	"errors"
5
-
6 4
 	kapi "k8s.io/kubernetes/pkg/api"
7 5
 	kapierror "k8s.io/kubernetes/pkg/api/errors"
8 6
 	"k8s.io/kubernetes/pkg/auth/user"
... ...
@@ -27,48 +25,57 @@ func NewDefaultRuleResolver(policyGetter client.PoliciesListerNamespacer, bindin
27 27
 }
28 28
 
29 29
 type AuthorizationRuleResolver interface {
30
-	GetRoleBindings(ctx kapi.Context) ([]authorizationinterfaces.RoleBinding, error)
30
+	GetRoleBindings(namespace string) ([]authorizationinterfaces.RoleBinding, error)
31 31
 	GetRole(roleBinding authorizationinterfaces.RoleBinding) (authorizationinterfaces.Role, error)
32
-	// GetEffectivePolicyRules returns the list of rules that apply to a given user in a given namespace and error.  If an error is returned, the slice of
32
+	// RulesFor returns the list of rules that apply to a given user in a given namespace and error.  If an error is returned, the slice of
33 33
 	// PolicyRules may not be complete, but it contains all retrievable rules.  This is done because policy rules are purely additive and policy determinations
34 34
 	// can be made on the basis of those rules that are found.
35
-	GetEffectivePolicyRules(ctx kapi.Context) ([]authorizationapi.PolicyRule, error)
35
+	RulesFor(info user.Info, namespace string) ([]authorizationapi.PolicyRule, error)
36 36
 }
37 37
 
38
-func (a *DefaultRuleResolver) GetRoleBindings(ctx kapi.Context) ([]authorizationinterfaces.RoleBinding, error) {
39
-	namespace := kapi.NamespaceValue(ctx)
38
+func (a *DefaultRuleResolver) GetRoleBindings(namespace string) ([]authorizationinterfaces.RoleBinding, error) {
39
+	clusterBindings, clusterErr := a.clusterBindingLister.List(kapi.ListOptions{})
40 40
 
41
-	if len(namespace) == 0 {
42
-		policyBindingList, err := a.clusterBindingLister.List(kapi.ListOptions{})
43
-		if err != nil {
44
-			return nil, err
45
-		}
41
+	var namespaceBindings *authorizationapi.PolicyBindingList
42
+	var namespaceErr error
43
+	if a.bindingLister != nil && len(namespace) > 0 {
44
+		namespaceBindings, namespaceErr = a.bindingLister.PolicyBindings(namespace).List(kapi.ListOptions{})
45
+	}
46 46
 
47
-		ret := make([]authorizationinterfaces.RoleBinding, 0, len(policyBindingList.Items))
48
-		for _, policyBinding := range policyBindingList.Items {
47
+	// return all loaded bindings
48
+	expect := 0
49
+	if clusterBindings != nil {
50
+		expect += len(clusterBindings.Items)
51
+	}
52
+	if namespaceBindings != nil {
53
+		expect += len(namespaceBindings.Items)
54
+	}
55
+	bindings := make([]authorizationinterfaces.RoleBinding, 0, expect)
56
+	if clusterBindings != nil {
57
+		for _, policyBinding := range clusterBindings.Items {
49 58
 			for _, value := range policyBinding.RoleBindings {
50
-				ret = append(ret, authorizationinterfaces.NewClusterRoleBindingAdapter(value))
59
+				bindings = append(bindings, authorizationinterfaces.NewClusterRoleBindingAdapter(value))
51 60
 			}
52 61
 		}
53
-		return ret, nil
54 62
 	}
55
-
56
-	if a.bindingLister == nil {
57
-		return nil, nil
63
+	if namespaceBindings != nil {
64
+		for _, policyBinding := range namespaceBindings.Items {
65
+			for _, value := range policyBinding.RoleBindings {
66
+				bindings = append(bindings, authorizationinterfaces.NewLocalRoleBindingAdapter(value))
67
+			}
68
+		}
58 69
 	}
59 70
 
60
-	policyBindingList, err := a.bindingLister.PolicyBindings(namespace).List(kapi.ListOptions{})
61
-	if err != nil {
62
-		return nil, err
71
+	// return all errors
72
+	var errs []error
73
+	if clusterErr != nil {
74
+		errs = append(errs, clusterErr)
63 75
 	}
64
-
65
-	ret := make([]authorizationinterfaces.RoleBinding, 0, len(policyBindingList.Items))
66
-	for _, policyBinding := range policyBindingList.Items {
67
-		for _, value := range policyBinding.RoleBindings {
68
-			ret = append(ret, authorizationinterfaces.NewLocalRoleBindingAdapter(value))
69
-		}
76
+	if namespaceErr != nil {
77
+		errs = append(errs, namespaceErr)
70 78
 	}
71
-	return ret, nil
79
+
80
+	return bindings, kerrors.NewAggregate(errs)
72 81
 }
73 82
 
74 83
 func (a *DefaultRuleResolver) GetRole(roleBinding authorizationinterfaces.RoleBinding) (authorizationinterfaces.Role, error) {
... ...
@@ -113,20 +120,17 @@ func (a *DefaultRuleResolver) GetRole(roleBinding authorizationinterfaces.RoleBi
113 113
 
114 114
 }
115 115
 
116
-// GetEffectivePolicyRules returns the list of rules that apply to a given user in a given namespace and error.  If an error is returned, the slice of
116
+// RulesFor returns the list of rules that apply to a given user in a given namespace and error.  If an error is returned, the slice of
117 117
 // PolicyRules may not be complete, but it contains all retrievable rules.  This is done because policy rules are purely additive and policy determinations
118 118
 // can be made on the basis of those rules that are found.
119
-func (a *DefaultRuleResolver) GetEffectivePolicyRules(ctx kapi.Context) ([]authorizationapi.PolicyRule, error) {
120
-	roleBindings, err := a.GetRoleBindings(ctx)
119
+func (a *DefaultRuleResolver) RulesFor(user user.Info, namespace string) ([]authorizationapi.PolicyRule, error) {
120
+	var errs []error
121
+
122
+	roleBindings, err := a.GetRoleBindings(namespace)
121 123
 	if err != nil {
122
-		return nil, err
123
-	}
124
-	user, exists := kapi.UserFrom(ctx)
125
-	if !exists {
126
-		return nil, errors.New("user missing from context")
124
+		errs = append(errs, err)
127 125
 	}
128 126
 
129
-	errs := []error{}
130 127
 	rules := make([]authorizationapi.PolicyRule, 0, len(roleBindings))
131 128
 	for _, roleBinding := range roleBindings {
132 129
 		if !roleBinding.AppliesToUser(user) {
... ...
@@ -146,6 +150,7 @@ func (a *DefaultRuleResolver) GetEffectivePolicyRules(ctx kapi.Context) ([]autho
146 146
 
147 147
 	return rules, kerrors.NewAggregate(errs)
148 148
 }
149
+
149 150
 func appliesToUser(ruleUsers, ruleGroups sets.String, user user.Info) bool {
150 151
 	if ruleUsers.Has(user.GetName()) {
151 152
 		return true
... ...
@@ -11,53 +11,46 @@ import (
11 11
 	kapierrors "k8s.io/kubernetes/pkg/api/errors"
12 12
 	"k8s.io/kubernetes/pkg/api/unversioned"
13 13
 
14
-	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
15 14
 	authorizationinterfaces "github.com/openshift/origin/pkg/authorization/interfaces"
16 15
 )
17 16
 
18 17
 func ConfirmNoEscalation(ctx kapi.Context, resource unversioned.GroupResource, name string, ruleResolver AuthorizationRuleResolver, role authorizationinterfaces.Role) error {
19
-	ruleResolutionErrors := []error{}
18
+	var ruleResolutionErrors []error
20 19
 
21
-	ownerLocalRules, err := ruleResolver.GetEffectivePolicyRules(ctx)
22
-	if err != nil {
23
-		// do not fail in this case.  Rules are purely additive, so we can continue with a coverage check based on the rules we have
24
-		user, _ := kapi.UserFrom(ctx)
25
-		glog.V(1).Infof("non-fatal error getting local rules for %v: %v", user, err)
26
-		ruleResolutionErrors = append(ruleResolutionErrors, err)
20
+	user, ok := kapi.UserFrom(ctx)
21
+	if !ok {
22
+		return kapierrors.NewForbidden(resource, name, fmt.Errorf("no user provided in context"))
27 23
 	}
28
-	masterContext := kapi.WithNamespace(ctx, "")
29
-	ownerGlobalRules, err := ruleResolver.GetEffectivePolicyRules(masterContext)
24
+	namespace, _ := kapi.NamespaceFrom(ctx)
25
+
26
+	ownerRules, err := ruleResolver.RulesFor(user, namespace)
30 27
 	if err != nil {
31 28
 		// do not fail in this case.  Rules are purely additive, so we can continue with a coverage check based on the rules we have
32
-		user, _ := kapi.UserFrom(ctx)
33
-		glog.V(1).Infof("non-fatal error getting global rules for %v: %v", user, err)
29
+		glog.V(1).Infof("non-fatal error getting rules for %v: %v", user, err)
34 30
 		ruleResolutionErrors = append(ruleResolutionErrors, err)
35 31
 	}
36 32
 
37
-	ownerRules := make([]authorizationapi.PolicyRule, 0, len(ownerGlobalRules)+len(ownerLocalRules))
38
-	ownerRules = append(ownerRules, ownerLocalRules...)
39
-	ownerRules = append(ownerRules, ownerGlobalRules...)
40
-
41 33
 	ownerRightsCover, missingRights := Covers(ownerRules, role.Rules())
42
-	if !ownerRightsCover {
43
-		if compactedMissingRights, err := CompactRules(missingRights); err == nil {
44
-			missingRights = compactedMissingRights
45
-		}
46
-
47
-		missingRightsStrings := make([]string, 0, len(missingRights))
48
-		for _, missingRight := range missingRights {
49
-			missingRightsStrings = append(missingRightsStrings, missingRight.CompactString())
50
-		}
51
-		sort.Strings(missingRightsStrings)
52
-		user, _ := kapi.UserFrom(ctx)
53
-		var internalErr error
54
-		if len(ruleResolutionErrors) > 0 {
55
-			internalErr = fmt.Errorf("user %q cannot grant extra privileges:\n%v\nrule resolution errors: %v)", user.GetName(), strings.Join(missingRightsStrings, "\n"), ruleResolutionErrors)
56
-		} else {
57
-			internalErr = fmt.Errorf("user %q cannot grant extra privileges:\n%v", user.GetName(), strings.Join(missingRightsStrings, "\n"))
58
-		}
59
-		return kapierrors.NewForbidden(resource, name, internalErr)
34
+	if ownerRightsCover {
35
+		return nil
60 36
 	}
61 37
 
62
-	return nil
38
+	// determine what resources the user is missing
39
+	if compactedMissingRights, err := CompactRules(missingRights); err == nil {
40
+		missingRights = compactedMissingRights
41
+	}
42
+
43
+	missingRightsStrings := make([]string, 0, len(missingRights))
44
+	for _, missingRight := range missingRights {
45
+		missingRightsStrings = append(missingRightsStrings, missingRight.CompactString())
46
+	}
47
+	sort.Strings(missingRightsStrings)
48
+
49
+	var internalErr error
50
+	if len(ruleResolutionErrors) > 0 {
51
+		internalErr = fmt.Errorf("user %q cannot grant extra privileges:\n%v\nrule resolution errors: %v)", user.GetName(), strings.Join(missingRightsStrings, "\n"), ruleResolutionErrors)
52
+	} else {
53
+		internalErr = fmt.Errorf("user %q cannot grant extra privileges:\n%v", user.GetName(), strings.Join(missingRightsStrings, "\n"))
54
+	}
55
+	return kapierrors.NewForbidden(resource, name, internalErr)
63 56
 }
... ...
@@ -730,7 +730,7 @@ func TestAuthorizationSubjectAccessReviewAPIGroup(t *testing.T) {
730 730
 		},
731 731
 		response: authorizationapi.SubjectAccessReviewResponse{
732 732
 			Allowed:   true,
733
-			Reason:    "allowed by cluster rule",
733
+			Reason:    "allowed by rule in any-project",
734 734
 			Namespace: "any-project",
735 735
 		},
736 736
 	}.run(t)
... ...
@@ -742,7 +742,7 @@ func TestAuthorizationSubjectAccessReviewAPIGroup(t *testing.T) {
742 742
 		},
743 743
 		response: authorizationapi.SubjectAccessReviewResponse{
744 744
 			Allowed:   true,
745
-			Reason:    "allowed by cluster rule",
745
+			Reason:    "allowed by rule in any-project",
746 746
 			Namespace: "any-project",
747 747
 		},
748 748
 	}.run(t)
... ...
@@ -754,7 +754,7 @@ func TestAuthorizationSubjectAccessReviewAPIGroup(t *testing.T) {
754 754
 		},
755 755
 		response: authorizationapi.SubjectAccessReviewResponse{
756 756
 			Allowed:   true,
757
-			Reason:    "allowed by cluster rule",
757
+			Reason:    "allowed by rule in any-project",
758 758
 			Namespace: "any-project",
759 759
 		},
760 760
 	}.run(t)
... ...
@@ -766,7 +766,7 @@ func TestAuthorizationSubjectAccessReviewAPIGroup(t *testing.T) {
766 766
 		},
767 767
 		response: authorizationapi.SubjectAccessReviewResponse{
768 768
 			Allowed:   true,
769
-			Reason:    "allowed by cluster rule",
769
+			Reason:    "allowed by rule in any-project",
770 770
 			Namespace: "any-project",
771 771
 		},
772 772
 	}.run(t)