Skip to content

Commit 5602529

Browse files
author
OpenShift Bot
authored
Merge pull request #13259 from deads2k/auth-05-interface
Merged by openshift-bot
2 parents 18a751b + c3bb1dd commit 5602529

File tree

21 files changed

+131
-127
lines changed

21 files changed

+131
-127
lines changed

pkg/authorization/api/synthetic.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ const (
77
CustomBuildResource = "builds/custom"
88
JenkinsPipelineBuildResource = "builds/jenkinspipeline"
99

10-
NodeMetricsResource = "nodes/metrics"
11-
NodeStatsResource = "nodes/stats"
12-
NodeSpecResource = "nodes/spec"
13-
NodeLogResource = "nodes/log"
10+
NodeMetricsSubresource = "metrics"
11+
NodeStatsSubresource = "stats"
12+
NodeSpecSubresource = "spec"
13+
NodeLogSubresource = "log"
1414

1515
RestrictedEndpointsResource = "endpoints/restricted"
1616
)

pkg/authorization/authorizer/adapter/attributes.go

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package adapter
22

33
import (
4-
"strings"
5-
64
kapi "k8s.io/kubernetes/pkg/api"
75
kauthorizer "k8s.io/kubernetes/pkg/auth/authorizer"
86
"k8s.io/kubernetes/pkg/auth/user"
@@ -38,16 +36,11 @@ func OriginAuthorizerAttributes(kattrs kauthorizer.Attributes) (kapi.Context, oa
3836
APIGroup: kattrs.GetAPIGroup(),
3937
APIVersion: kattrs.GetAPIVersion(),
4038
Resource: kattrs.GetResource(),
39+
Subresource: kattrs.GetSubresource(),
4140
ResourceName: kattrs.GetName(),
4241

4342
NonResourceURL: kattrs.IsResourceRequest() == false,
4443
URL: kattrs.GetPath(),
45-
46-
// TODO: add to kube authorizer attributes
47-
// RequestAttributes interface{}
48-
}
49-
if len(kattrs.GetSubresource()) > 0 {
50-
oattrs.Resource = kattrs.GetResource() + "/" + kattrs.GetSubresource()
5144
}
5245

5346
return ctx, oattrs
@@ -86,19 +79,11 @@ func (a AdapterAttributes) GetName() string {
8679
}
8780

8881
func (a AdapterAttributes) GetSubresource() string {
89-
tokens := strings.SplitN(a.authorizationAttributes.GetResource(), "/", 2)
90-
if len(tokens) != 2 {
91-
return ""
92-
}
93-
return tokens[1]
82+
return a.authorizationAttributes.GetSubresource()
9483
}
9584

9685
func (a AdapterAttributes) GetResource() string {
97-
tokens := strings.SplitN(a.authorizationAttributes.GetResource(), "/", 2)
98-
if len(tokens) < 1 {
99-
return ""
100-
}
101-
return tokens[0]
86+
return a.authorizationAttributes.GetResource()
10287
}
10388

10489
// GetUserName satisfies the kubernetes authorizer.Attributes interface

pkg/authorization/authorizer/adapter/attributes_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ func TestRoundTrip(t *testing.T) {
2222
APIVersion: "av",
2323
APIGroup: "ag",
2424
Resource: "r",
25+
Subresource: "sub",
2526
ResourceName: "rn",
2627
NonResourceURL: true,
2728
URL: "/123",
@@ -73,6 +74,9 @@ func TestRoundTrip(t *testing.T) {
7374
if oattrs.GetResource() != oattrs2.GetResource() {
7475
t.Errorf("Expected %v, got %v", oattrs.GetResource(), oattrs2.GetResource())
7576
}
77+
if oattrs.GetSubresource() != oattrs2.GetSubresource() {
78+
t.Errorf("Expected %v, got %v", oattrs.GetSubresource(), oattrs2.GetSubresource())
79+
}
7680

7781
// Ensure origin-specific info is preserved
7882
if oattrs.GetAPIVersion() != oattrs2.GetAPIVersion() {
@@ -95,7 +99,7 @@ func TestRoundTrip(t *testing.T) {
9599
func TestAttributeIntersection(t *testing.T) {
96100
// These are the things we expect to be shared
97101
// Everything in this list should be used by OriginAuthorizerAttributes
98-
expectedIntersection := sets.NewString("GetVerb", "GetResource", "GetAPIGroup", "GetAPIVersion")
102+
expectedIntersection := sets.NewString("GetVerb", "GetResource", "GetSubresource", "GetAPIGroup", "GetAPIVersion")
99103

100104
// These are the things we expect to only be in the Kubernetes interface
101105
// Everything in this list should be used by OriginAuthorizerAttributes or derivative (like IsReadOnly)

pkg/authorization/authorizer/attributes.go

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,42 @@ type DefaultAuthorizationAttributes struct {
1414
APIVersion string
1515
APIGroup string
1616
Resource string
17+
Subresource string
1718
ResourceName string
1819
NonResourceURL bool
1920
URL string
2021
}
2122

2223
// ToDefaultAuthorizationAttributes coerces Action to DefaultAuthorizationAttributes. Namespace is not included
2324
// because the authorizer takes that information on the context
24-
func ToDefaultAuthorizationAttributes(in authorizationapi.Action) DefaultAuthorizationAttributes {
25+
func ToDefaultAuthorizationAttributes(in authorizationapi.Action) Action {
26+
tokens := strings.SplitN(in.Resource, "/", 2)
27+
resource := ""
28+
subresource := ""
29+
switch {
30+
case len(tokens) == 2:
31+
subresource = tokens[1]
32+
fallthrough
33+
case len(tokens) == 1:
34+
resource = tokens[0]
35+
}
36+
2537
return DefaultAuthorizationAttributes{
2638
Verb: in.Verb,
2739
APIGroup: in.Group,
2840
APIVersion: in.Version,
29-
Resource: in.Resource,
41+
Resource: resource,
42+
Subresource: subresource,
3043
ResourceName: in.ResourceName,
3144
URL: in.Path,
3245
NonResourceURL: in.IsNonResourceURL,
3346
}
3447
}
3548

36-
func (a DefaultAuthorizationAttributes) RuleMatches(rule authorizationapi.PolicyRule) (bool, error) {
49+
func RuleMatches(a Action, rule authorizationapi.PolicyRule) (bool, error) {
3750
if a.IsNonResourceURL() {
38-
if a.nonResourceMatches(rule) {
39-
if a.verbMatches(rule.Verbs) {
51+
if nonResourceMatches(a, rule) {
52+
if verbMatches(a, rule.Verbs) {
4053
return true, nil
4154
}
4255
}
@@ -50,12 +63,12 @@ func (a DefaultAuthorizationAttributes) RuleMatches(rule authorizationapi.Policy
5063
return false, nil
5164
}
5265

53-
if a.verbMatches(rule.Verbs) {
54-
if a.apiGroupMatches(rule.APIGroups) {
66+
if verbMatches(a, rule.Verbs) {
67+
if apiGroupMatches(a, rule.APIGroups) {
5568

5669
allowedResourceTypes := authorizationapi.NormalizeResources(rule.Resources)
57-
if a.resourceMatches(allowedResourceTypes) {
58-
if a.nameMatches(rule.ResourceNames) {
70+
if resourceMatches(a, allowedResourceTypes) {
71+
if nameMatches(a, rule.ResourceNames) {
5972
return true, nil
6073
}
6174
}
@@ -65,7 +78,7 @@ func (a DefaultAuthorizationAttributes) RuleMatches(rule authorizationapi.Policy
6578
return false, nil
6679
}
6780

68-
func (a DefaultAuthorizationAttributes) apiGroupMatches(allowedGroups []string) bool {
81+
func apiGroupMatches(a Action, allowedGroups []string) bool {
6982
// allowedGroups is expected to be small, so I don't feel bad about this.
7083
for _, allowedGroup := range allowedGroups {
7184
if allowedGroup == authorizationapi.APIGroupAll {
@@ -80,32 +93,36 @@ func (a DefaultAuthorizationAttributes) apiGroupMatches(allowedGroups []string)
8093
return false
8194
}
8295

83-
func (a DefaultAuthorizationAttributes) verbMatches(verbs sets.String) bool {
96+
func verbMatches(a Action, verbs sets.String) bool {
8497
return verbs.Has(authorizationapi.VerbAll) || verbs.Has(strings.ToLower(a.GetVerb()))
8598
}
8699

87-
func (a DefaultAuthorizationAttributes) resourceMatches(allowedResourceTypes sets.String) bool {
88-
return allowedResourceTypes.Has(authorizationapi.ResourceAll) || allowedResourceTypes.Has(strings.ToLower(a.GetResource()))
100+
func resourceMatches(a Action, allowedResourceTypes sets.String) bool {
101+
if allowedResourceTypes.Has(authorizationapi.ResourceAll) {
102+
return true
103+
}
104+
105+
if len(a.GetSubresource()) == 0 {
106+
return allowedResourceTypes.Has(strings.ToLower(a.GetResource()))
107+
}
108+
109+
return allowedResourceTypes.Has(strings.ToLower(a.GetResource() + "/" + a.GetSubresource()))
89110
}
90111

91112
// nameMatches checks to see if the resourceName of the action is in a the specified whitelist. An empty whitelist indicates that any name is allowed.
92113
// An empty string in the whitelist should only match the action's resourceName if the resourceName itself is empty string. This behavior allows for the
93114
// combination of a whitelist for gets in the same rule as a list that won't have a resourceName. I don't recommend writing such a rule, but we do
94115
// handle it like you'd expect: white list is respected for gets while not preventing the list you explicitly asked for.
95-
func (a DefaultAuthorizationAttributes) nameMatches(allowedResourceNames sets.String) bool {
116+
func nameMatches(a Action, allowedResourceNames sets.String) bool {
96117
if len(allowedResourceNames) == 0 {
97118
return true
98119
}
99120

100121
return allowedResourceNames.Has(a.GetResourceName())
101122
}
102123

103-
func (a DefaultAuthorizationAttributes) GetVerb() string {
104-
return a.Verb
105-
}
106-
107124
// nonResourceMatches take the remainer of a URL and attempts to match it against a series of explicitly allowed steps that can end in a wildcard
108-
func (a DefaultAuthorizationAttributes) nonResourceMatches(rule authorizationapi.PolicyRule) bool {
125+
func nonResourceMatches(a Action, rule authorizationapi.PolicyRule) bool {
109126
for allowedNonResourcePath := range rule.NonResourceURLs {
110127
// if the allowed resource path ends in a wildcard, check to see if the URL starts with it
111128
if strings.HasSuffix(allowedNonResourcePath, "*") {
@@ -135,6 +152,10 @@ func splitPath(thePath string) []string {
135152
// DefaultAuthorizationAttributes satisfies the Action interface
136153
var _ Action = DefaultAuthorizationAttributes{}
137154

155+
func (a DefaultAuthorizationAttributes) GetVerb() string {
156+
return a.Verb
157+
}
158+
138159
func (a DefaultAuthorizationAttributes) GetAPIVersion() string {
139160
return a.APIVersion
140161
}
@@ -147,6 +168,10 @@ func (a DefaultAuthorizationAttributes) GetResource() string {
147168
return a.Resource
148169
}
149170

171+
func (a DefaultAuthorizationAttributes) GetSubresource() string {
172+
return a.Subresource
173+
}
174+
150175
func (a DefaultAuthorizationAttributes) GetResourceName() string {
151176
return a.ResourceName
152177
}

pkg/authorization/authorizer/attributes_builder.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,12 @@ func (a *openshiftAuthorizationAttributeBuilder) GetAttributes(req *http.Request
3030
}, nil
3131
}
3232

33-
resource := requestInfo.Resource
34-
if len(requestInfo.Subresource) > 0 {
35-
resource = requestInfo.Resource + "/" + requestInfo.Subresource
36-
}
37-
3833
return DefaultAuthorizationAttributes{
3934
Verb: requestInfo.Verb,
4035
APIGroup: requestInfo.APIGroup,
4136
APIVersion: requestInfo.APIVersion,
42-
Resource: resource,
37+
Resource: requestInfo.Resource,
38+
Subresource: requestInfo.Subresource,
4339
ResourceName: requestInfo.Name,
4440
NonResourceURL: false,
4541
URL: requestInfo.Path,

pkg/authorization/authorizer/attributes_test.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package authorizer
22

33
import (
4+
"strings"
45
"testing"
56

67
authorizationapi "github.com/openshift/origin/pkg/authorization/api"
@@ -29,7 +30,21 @@ func (a authorizationAttributesAdapter) GetAPIGroup() string {
2930
}
3031

3132
func (a authorizationAttributesAdapter) GetResource() string {
32-
return a.attrs.Resource
33+
// to match the RequestInfoFactory assuming an in.resource of one/two/three, one==resource, two==subresource, three=nothing
34+
tokens := strings.SplitN(a.attrs.Resource, "/", 2)
35+
if len(tokens) >= 1 {
36+
return tokens[0]
37+
}
38+
return ""
39+
}
40+
41+
func (a authorizationAttributesAdapter) GetSubresource() string {
42+
// to match the RequestInfoFactory assuming an in.resource of one/two/three, one==resource, two==subresource, three=nothing
43+
tokens := strings.SplitN(a.attrs.Resource, "/", 2)
44+
if len(tokens) >= 2 {
45+
return tokens[1]
46+
}
47+
return ""
3348
}
3449

3550
func (a authorizationAttributesAdapter) GetResourceName() string {

pkg/authorization/authorizer/authorizer.go

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ func NewAuthorizer(ruleResolver rulevalidation.AuthorizationRuleResolver, forbid
2020
return &openshiftAuthorizer{ruleResolver, forbiddenMessageMaker}
2121
}
2222

23-
func (a *openshiftAuthorizer) Authorize(ctx kapi.Context, passedAttributes Action) (bool, string, error) {
24-
attributes := CoerceToDefaultAuthorizationAttributes(passedAttributes)
25-
23+
func (a *openshiftAuthorizer) Authorize(ctx kapi.Context, attributes Action) (bool, string, error) {
2624
user, ok := kapi.UserFrom(ctx)
2725
if !ok {
2826
return false, "", errors.New("no user available on context")
@@ -54,9 +52,7 @@ func (a *openshiftAuthorizer) GetAllowedSubjects(ctx kapi.Context, attributes Ac
5452
return a.getAllowedSubjectsFromNamespaceBindings(namespace, attributes)
5553
}
5654

57-
func (a *openshiftAuthorizer) getAllowedSubjectsFromNamespaceBindings(namespace string, passedAttributes Action) (sets.String, sets.String, error) {
58-
attributes := CoerceToDefaultAuthorizationAttributes(passedAttributes)
59-
55+
func (a *openshiftAuthorizer) getAllowedSubjectsFromNamespaceBindings(namespace string, attributes Action) (sets.String, sets.String, error) {
6056
var errs []error
6157

6258
roleBindings, err := a.ruleResolver.GetRoleBindings(namespace)
@@ -77,7 +73,7 @@ func (a *openshiftAuthorizer) getAllowedSubjectsFromNamespaceBindings(namespace
7773
}
7874

7975
for _, rule := range role.Rules() {
80-
matches, err := attributes.RuleMatches(rule)
76+
matches, err := RuleMatches(attributes, rule)
8177
if err != nil {
8278
errs = append(errs, err)
8379
continue
@@ -96,14 +92,12 @@ func (a *openshiftAuthorizer) getAllowedSubjectsFromNamespaceBindings(namespace
9692
// authorizeWithNamespaceRules returns isAllowed, reason, and error. If an error is returned, isAllowed and reason are still valid. This seems strange
9793
// 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
9894
// 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.
99-
func (a *openshiftAuthorizer) authorizeWithNamespaceRules(user user.Info, namespace string, passedAttributes Action) (bool, string, error) {
100-
attributes := CoerceToDefaultAuthorizationAttributes(passedAttributes)
101-
95+
func (a *openshiftAuthorizer) authorizeWithNamespaceRules(user user.Info, namespace string, attributes Action) (bool, string, error) {
10296
allRules, ruleRetrievalError := a.ruleResolver.RulesFor(user, namespace)
10397

10498
var errs []error
10599
for _, rule := range allRules {
106-
matches, err := attributes.RuleMatches(rule)
100+
matches, err := RuleMatches(attributes, rule)
107101
if err != nil {
108102
errs = append(errs, err)
109103
continue
@@ -126,24 +120,6 @@ func (a *openshiftAuthorizer) authorizeWithNamespaceRules(user user.Info, namesp
126120
return false, "", kerrors.NewAggregate(errs)
127121
}
128122

129-
// TODO this may or may not be the behavior we want for managing rules. As a for instance, a verb might be specified
130-
// that our attributes builder will never satisfy. For now, I think gets us close. Maybe a warning message of some kind?
131-
func CoerceToDefaultAuthorizationAttributes(passedAttributes Action) *DefaultAuthorizationAttributes {
132-
attributes, ok := passedAttributes.(*DefaultAuthorizationAttributes)
133-
if !ok {
134-
attributes = &DefaultAuthorizationAttributes{
135-
APIGroup: passedAttributes.GetAPIGroup(),
136-
Verb: passedAttributes.GetVerb(),
137-
Resource: passedAttributes.GetResource(),
138-
ResourceName: passedAttributes.GetResourceName(),
139-
NonResourceURL: passedAttributes.IsNonResourceURL(),
140-
URL: passedAttributes.GetURL(),
141-
}
142-
}
143-
144-
return attributes
145-
}
146-
147123
func doesApplyToUser(ruleUsers, ruleGroups sets.String, user user.Info) bool {
148124
if ruleUsers.Has(user.GetName()) {
149125
return true

pkg/authorization/authorizer/bootstrap_policy_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,8 +378,9 @@ func TestAdminGetStatusInMallet(t *testing.T) {
378378
test := &authorizeTest{
379379
context: kapi.WithUser(kapi.WithNamespace(kapi.NewContext(), "mallet"), &user.DefaultInfo{Name: "Matthew"}),
380380
attributes: &DefaultAuthorizationAttributes{
381-
Verb: "get",
382-
Resource: "pods/status",
381+
Verb: "get",
382+
Resource: "pods",
383+
Subresource: "status",
383384
},
384385
expectedAllowed: true,
385386
expectedReason: "allowed by rule in mallet",

pkg/authorization/authorizer/cache/authorizer.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ func cacheKey(ctx kapi.Context, a authorizer.Action) (string, error) {
128128
"apiVersion": a.GetAPIVersion(),
129129
"apiGroup": a.GetAPIGroup(),
130130
"resource": a.GetResource(),
131+
"subresource": a.GetSubresource(),
131132
"resourceName": a.GetResourceName(),
132133
"nonResourceURL": a.IsNonResourceURL(),
133134
"url": a.GetURL(),

pkg/authorization/authorizer/cache/authorizer_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func TestCacheKey(t *testing.T) {
2929
"empty": {
3030
Context: kapi.NewContext(),
3131
Attrs: &authorizer.DefaultAuthorizationAttributes{},
32-
ExpectedKey: `{"apiGroup":"","apiVersion":"","nonResourceURL":false,"resource":"","resourceName":"","url":"","verb":""}`,
32+
ExpectedKey: `{"apiGroup":"","apiVersion":"","nonResourceURL":false,"resource":"","resourceName":"","subresource":"","url":"","verb":""}`,
3333
},
3434
"full": {
3535
Context: kapi.WithUser(kapi.WithNamespace(kapi.NewContext(), "myns"), &user.DefaultInfo{Name: "me", Groups: []string{"group1", "group2"}}),
@@ -38,11 +38,12 @@ func TestCacheKey(t *testing.T) {
3838
APIVersion: "av",
3939
APIGroup: "ag",
4040
Resource: "r",
41+
Subresource: "sub",
4142
ResourceName: "rn",
4243
NonResourceURL: true,
4344
URL: "/abc",
4445
},
45-
ExpectedKey: `{"apiGroup":"ag","apiVersion":"av","groups":["group1","group2"],"namespace":"myns","nonResourceURL":true,"resource":"r","resourceName":"rn","scopes":null,"url":"/abc","user":"me","verb":"v"}`,
46+
ExpectedKey: `{"apiGroup":"ag","apiVersion":"av","groups":["group1","group2"],"namespace":"myns","nonResourceURL":true,"resource":"r","resourceName":"rn","scopes":null,"subresource":"sub","url":"/abc","user":"me","verb":"v"}`,
4647
},
4748
}
4849

0 commit comments

Comments
 (0)