Skip to content

Commit 9b28703

Browse files
author
OpenShift Bot
committed
Merge pull request #2319 from deads2k/forbidden-message
Merged by openshift-bot
2 parents 54cc564 + 9276301 commit 9b28703

11 files changed

+364
-49
lines changed

pkg/authorization/authorizer/authorizer.go

+8-18
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import (
1212
)
1313

1414
type openshiftAuthorizer struct {
15-
ruleResolver rulevalidation.AuthorizationRuleResolver
15+
ruleResolver rulevalidation.AuthorizationRuleResolver
16+
forbiddenMessageMaker ForbiddenMessageMaker
1617
}
1718

18-
func NewAuthorizer(ruleResolver rulevalidation.AuthorizationRuleResolver) Authorizer {
19-
return &openshiftAuthorizer{ruleResolver}
19+
func NewAuthorizer(ruleResolver rulevalidation.AuthorizationRuleResolver, forbiddenMessageMaker ForbiddenMessageMaker) Authorizer {
20+
return &openshiftAuthorizer{ruleResolver, forbiddenMessageMaker}
2021
}
2122

2223
func (a *openshiftAuthorizer) Authorize(ctx kapi.Context, passedAttributes AuthorizationAttributes) (bool, string, error) {
@@ -51,21 +52,10 @@ func (a *openshiftAuthorizer) Authorize(ctx kapi.Context, passedAttributes Autho
5152
return false, "", kerrors.NewAggregate(errs)
5253
}
5354

54-
username := "MISSING"
55-
if user, userExists := kapi.UserFrom(ctx); userExists {
56-
username = user.GetName()
57-
}
58-
59-
denyReason := "denied by default"
60-
if passedAttributes.IsNonResourceURL() {
61-
denyReason = fmt.Sprintf("%v cannot %v on %v", username, attributes.GetVerb(), attributes.GetURL())
62-
63-
} else {
64-
resourceNamePart := ""
65-
if len(attributes.GetResourceName()) > 0 {
66-
resourceNamePart = fmt.Sprintf(" with name \"%v\"", attributes.GetResourceName())
67-
}
68-
denyReason = fmt.Sprintf("%v cannot %v on %v%v in %v", username, attributes.GetVerb(), attributes.GetResource(), resourceNamePart, namespace)
55+
user, _ := kapi.UserFrom(ctx)
56+
denyReason, err := a.forbiddenMessageMaker.MakeMessage(MessageContext{user, namespace, attributes})
57+
if err != nil {
58+
denyReason = err.Error()
6959
}
7060

7161
return false, denyReason, nil

pkg/authorization/authorizer/authorizer_test.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func TestResourceNameDeny(t *testing.T) {
4444
ResourceName: "just-a-user",
4545
},
4646
expectedAllowed: false,
47-
expectedReason: `just-a-user cannot get on users with name "just-a-user"`,
47+
expectedReason: `User "just-a-user" cannot get users`,
4848
}
4949
test.clusterPolicies = newDefaultClusterPolicies()
5050
test.clusterBindings = newDefaultClusterPolicyBindings()
@@ -165,7 +165,7 @@ func TestNonResourceDeny(t *testing.T) {
165165
URL: "not-allowed",
166166
},
167167
expectedAllowed: false,
168-
expectedReason: `no-one cannot get on not-allowed`,
168+
expectedReason: `User "no-one" cannot "get" on "not-allowed"`,
169169
}
170170
test.clusterPolicies = newDefaultClusterPolicies()
171171
test.clusterBindings = newDefaultClusterPolicyBindings()
@@ -182,7 +182,7 @@ func TestHealthDeny(t *testing.T) {
182182
URL: "/healthz",
183183
},
184184
expectedAllowed: false,
185-
expectedReason: `no-one cannot get on /healthz`,
185+
expectedReason: `User "no-one" cannot "get" on "/healthz"`,
186186
}
187187
test.clusterPolicies = newDefaultClusterPolicies()
188188
test.clusterBindings = newDefaultClusterPolicyBindings()
@@ -214,7 +214,7 @@ func TestDisallowedViewingGlobalPods(t *testing.T) {
214214
Resource: "pods",
215215
},
216216
expectedAllowed: false,
217-
expectedReason: `SomeYahoo cannot get on pods`,
217+
expectedReason: `User "SomeYahoo" cannot get pods`,
218218
}
219219
test.clusterPolicies = newDefaultClusterPolicies()
220220
test.clusterBindings = newDefaultClusterPolicyBindings()
@@ -281,7 +281,7 @@ func TestResourceRestrictionsWork(t *testing.T) {
281281
Resource: "pods",
282282
},
283283
expectedAllowed: false,
284-
expectedReason: `Rachel cannot get on pods in adze`,
284+
expectedReason: `User "Rachel" cannot get pods in project "adze"`,
285285
}
286286
test2.clusterPolicies = newDefaultClusterPolicies()
287287
test2.policies = newAdzePolicies()
@@ -330,7 +330,7 @@ func TestLocalRightsDoNotGrantGlobalRights(t *testing.T) {
330330
Resource: "buildConfigs",
331331
},
332332
expectedAllowed: false,
333-
expectedReason: `Rachel cannot get on buildConfigs in backsaw`,
333+
expectedReason: `User "Rachel" cannot get buildConfigs in project "backsaw"`,
334334
}
335335
test.clusterPolicies = newDefaultClusterPolicies()
336336
test.policies = append(test.policies, newAdzePolicies()...)
@@ -363,7 +363,7 @@ func TestVerbRestrictionsWork(t *testing.T) {
363363
Resource: "buildConfigs",
364364
},
365365
expectedAllowed: false,
366-
expectedReason: `Valerie cannot create on buildConfigs in adze`,
366+
expectedReason: `User "Valerie" cannot create buildConfigs in project "adze"`,
367367
}
368368
test2.clusterPolicies = newDefaultClusterPolicies()
369369
test2.policies = newAdzePolicies()
@@ -377,7 +377,7 @@ func (test *authorizeTest) test(t *testing.T) {
377377
policyBindingRegistry := testpolicyregistry.NewPolicyBindingRegistry(test.bindings, test.bindingRetrievalError)
378378
clusterPolicyRegistry := clusterpolicyregistry.NewSimulatedRegistry(testpolicyregistry.NewClusterPolicyRegistry(test.clusterPolicies, test.policyRetrievalError))
379379
clusterPolicyBindingRegistry := clusterpolicybindingregistry.NewSimulatedRegistry(testpolicyregistry.NewClusterPolicyBindingRegistry(test.clusterBindings, test.bindingRetrievalError))
380-
authorizer := NewAuthorizer(rulevalidation.NewDefaultRuleResolver(policyRegistry, policyBindingRegistry, clusterPolicyRegistry, clusterPolicyBindingRegistry))
380+
authorizer := NewAuthorizer(rulevalidation.NewDefaultRuleResolver(policyRegistry, policyBindingRegistry, clusterPolicyRegistry, clusterPolicyBindingRegistry), NewForbiddenMessageResolver(""))
381381

382382
actualAllowed, actualReason, actualError := authorizer.Authorize(test.context, *test.attributes)
383383

pkg/authorization/authorizer/bootstrap_policy_test.go

+14-14
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func TestViewerGetAllowedKindInAdze(t *testing.T) {
7474
Resource: "pods",
7575
},
7676
expectedAllowed: false,
77-
expectedReason: "Victor cannot get on pods in adze",
77+
expectedReason: `User "Victor" cannot get pods in project "adze"`,
7878
}
7979
test.clusterPolicies = newDefaultClusterPolicies()
8080
test.policies = newAdzePolicies()
@@ -94,7 +94,7 @@ func TestViewerGetDisallowedKindInMallet(t *testing.T) {
9494
Resource: "policies",
9595
},
9696
expectedAllowed: false,
97-
expectedReason: "Victor cannot get on policies in mallet",
97+
expectedReason: `User "Victor" cannot get policies in project "mallet"`,
9898
}
9999
test.clusterPolicies = newDefaultClusterPolicies()
100100
test.policies = newAdzePolicies()
@@ -113,7 +113,7 @@ func TestViewerGetDisallowedKindInAdze(t *testing.T) {
113113
Resource: "policies",
114114
},
115115
expectedAllowed: false,
116-
expectedReason: "Victor cannot get on policies in adze",
116+
expectedReason: `User "Victor" cannot get policies in project "adze"`,
117117
}
118118
test.clusterPolicies = newDefaultClusterPolicies()
119119
test.policies = newAdzePolicies()
@@ -133,7 +133,7 @@ func TestViewerCreateAllowedKindInMallet(t *testing.T) {
133133
Resource: "pods",
134134
},
135135
expectedAllowed: false,
136-
expectedReason: "Victor cannot create on pods in mallet",
136+
expectedReason: `User "Victor" cannot create pods in project "mallet"`,
137137
}
138138
test.clusterPolicies = newDefaultClusterPolicies()
139139
test.policies = newAdzePolicies()
@@ -152,7 +152,7 @@ func TestViewerCreateAllowedKindInAdze(t *testing.T) {
152152
Resource: "pods",
153153
},
154154
expectedAllowed: false,
155-
expectedReason: "Victor cannot create on pods in adze",
155+
expectedReason: `User "Victor" cannot create pods in project "adze"`,
156156
}
157157
test.clusterPolicies = newDefaultClusterPolicies()
158158
test.policies = newAdzePolicies()
@@ -191,7 +191,7 @@ func TestEditorUpdateAllowedKindInAdze(t *testing.T) {
191191
Resource: "pods",
192192
},
193193
expectedAllowed: false,
194-
expectedReason: "Edgar cannot update on pods in adze",
194+
expectedReason: `User "Edgar" cannot update pods in project "adze"`,
195195
}
196196
test.clusterPolicies = newDefaultClusterPolicies()
197197
test.policies = newAdzePolicies()
@@ -211,7 +211,7 @@ func TestEditorUpdateDisallowedKindInMallet(t *testing.T) {
211211
Resource: "roleBindings",
212212
},
213213
expectedAllowed: false,
214-
expectedReason: "Edgar cannot update on roleBindings in mallet",
214+
expectedReason: `User "Edgar" cannot update roleBindings in project "mallet"`,
215215
}
216216
test.clusterPolicies = newDefaultClusterPolicies()
217217
test.policies = newAdzePolicies()
@@ -230,7 +230,7 @@ func TestEditorUpdateDisallowedKindInAdze(t *testing.T) {
230230
Resource: "roleBindings",
231231
},
232232
expectedAllowed: false,
233-
expectedReason: "Edgar cannot update on roleBindings in adze",
233+
expectedReason: `User "Edgar" cannot update roleBindings in project "adze"`,
234234
}
235235
test.clusterPolicies = newDefaultClusterPolicies()
236236
test.policies = newAdzePolicies()
@@ -269,7 +269,7 @@ func TestEditorGetAllowedKindInAdze(t *testing.T) {
269269
Resource: "pods",
270270
},
271271
expectedAllowed: false,
272-
expectedReason: "Edgar cannot get on pods in adze",
272+
expectedReason: `User "Edgar" cannot get pods in project "adze"`,
273273
}
274274
test.clusterPolicies = newDefaultClusterPolicies()
275275
test.policies = newAdzePolicies()
@@ -308,7 +308,7 @@ func TestAdminUpdateAllowedKindInAdze(t *testing.T) {
308308
Resource: "roleBindings",
309309
},
310310
expectedAllowed: false,
311-
expectedReason: "Matthew cannot update on roleBindings in adze",
311+
expectedReason: `User "Matthew" cannot update roleBindings in project "adze"`,
312312
}
313313
test.clusterPolicies = newDefaultClusterPolicies()
314314
test.policies = newAdzePolicies()
@@ -328,7 +328,7 @@ func TestAdminUpdateStatusInMallet(t *testing.T) {
328328
Resource: "pods/status",
329329
},
330330
expectedAllowed: false,
331-
expectedReason: "Matthew cannot update on pods/status in mallet",
331+
expectedReason: `User "Matthew" cannot update pods/status in project "mallet"`,
332332
}
333333
test.clusterPolicies = newDefaultClusterPolicies()
334334
test.policies = newAdzePolicies()
@@ -367,7 +367,7 @@ func TestAdminUpdateDisallowedKindInMallet(t *testing.T) {
367367
Resource: "policies",
368368
},
369369
expectedAllowed: false,
370-
expectedReason: "Matthew cannot update on policies in mallet",
370+
expectedReason: `User "Matthew" cannot update policies in project "mallet"`,
371371
}
372372
test.clusterPolicies = newDefaultClusterPolicies()
373373
test.policies = newAdzePolicies()
@@ -386,7 +386,7 @@ func TestAdminUpdateDisallowedKindInAdze(t *testing.T) {
386386
Resource: "roles",
387387
},
388388
expectedAllowed: false,
389-
expectedReason: "Matthew cannot update on roles in adze",
389+
expectedReason: `User "Matthew" cannot update roles in project "adze"`,
390390
}
391391
test.clusterPolicies = newDefaultClusterPolicies()
392392
test.policies = newAdzePolicies()
@@ -425,7 +425,7 @@ func TestAdminGetAllowedKindInAdze(t *testing.T) {
425425
Resource: "policies",
426426
},
427427
expectedAllowed: false,
428-
expectedReason: "Matthew cannot get on policies in adze",
428+
expectedReason: `User "Matthew" cannot get policies in project "adze"`,
429429
}
430430
test.clusterPolicies = newDefaultClusterPolicies()
431431
test.policies = newAdzePolicies()

pkg/authorization/authorizer/interfaces.go

+13
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"net/http"
55

66
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
7+
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
78
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
89
)
910

@@ -29,3 +30,15 @@ type AuthorizationAttributes interface {
2930
// GetURL returns the URL path being requested, including the leading '/'
3031
GetURL() string
3132
}
33+
34+
// ForbiddenMessageMaker creates a forbidden message from a MessageContext
35+
type ForbiddenMessageMaker interface {
36+
MakeMessage(ctx MessageContext) (string, error)
37+
}
38+
39+
// MessageContext contains sufficient information to create a forbidden message. It is bundled in this one object to make it easy and obvious how to build a golang template
40+
type MessageContext struct {
41+
User user.Info
42+
Namespace string
43+
Attributes AuthorizationAttributes
44+
}
+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package authorizer
2+
3+
import (
4+
"bytes"
5+
"text/template"
6+
7+
authorizationapi "github.com/openshift/origin/pkg/authorization/api"
8+
)
9+
10+
const DefaultProjectRequestForbidden = "You may not request a new project via this API."
11+
12+
type ForbiddenMessageResolver struct {
13+
// TODO if these maps were map[string]map[util.StringSet]ForbiddenMessageMaker, we'd be able to handle cases where sets of resources wanted slightly different messages
14+
// unfortunately, maps don't support keys like that, requiring StringSet serialization and deserialization.
15+
namespacedVerbsToResourcesToForbiddenMessageMaker map[string]map[string]ForbiddenMessageMaker
16+
rootScopedVerbsToResourcesToForbiddenMessageMaker map[string]map[string]ForbiddenMessageMaker
17+
18+
nonResourceURLForbiddenMessageMaker ForbiddenMessageMaker
19+
defaultForbiddenMessageMaker ForbiddenMessageMaker
20+
}
21+
22+
func NewForbiddenMessageResolver(projectRequestForbiddenTemplate string) *ForbiddenMessageResolver {
23+
messageResolver := &ForbiddenMessageResolver{
24+
namespacedVerbsToResourcesToForbiddenMessageMaker: map[string]map[string]ForbiddenMessageMaker{},
25+
rootScopedVerbsToResourcesToForbiddenMessageMaker: map[string]map[string]ForbiddenMessageMaker{},
26+
nonResourceURLForbiddenMessageMaker: newTemplateForbiddenMessageMaker(`User "{{.User.GetName}}" cannot "{{.Attributes.GetVerb}}" on "{{.Attributes.GetURL}}"`),
27+
defaultForbiddenMessageMaker: newTemplateForbiddenMessageMaker(`User "{{.User.GetName}}" cannot "{{.Attributes.GetVerb}}" "{{.Attributes.GetResource}}" with name "{{.Attributes.GetResourceName}}" in project "{{.Namespace}}"`),
28+
}
29+
30+
// general messages
31+
messageResolver.addNamespacedForbiddenMessageMaker("create", authorizationapi.ResourceAll, newTemplateForbiddenMessageMaker(`User "{{.User.GetName}}" cannot create {{.Attributes.GetResource}} in project "{{.Namespace}}"`))
32+
messageResolver.addRootScopedForbiddenMessageMaker("create", authorizationapi.ResourceAll, newTemplateForbiddenMessageMaker(`User "{{.User.GetName}}" cannot create {{.Attributes.GetResource}} at the cluster scope`))
33+
messageResolver.addNamespacedForbiddenMessageMaker("get", authorizationapi.ResourceAll, newTemplateForbiddenMessageMaker(`User "{{.User.GetName}}" cannot get {{.Attributes.GetResource}} in project "{{.Namespace}}"`))
34+
messageResolver.addRootScopedForbiddenMessageMaker("get", authorizationapi.ResourceAll, newTemplateForbiddenMessageMaker(`User "{{.User.GetName}}" cannot get {{.Attributes.GetResource}} at the cluster scope`))
35+
messageResolver.addNamespacedForbiddenMessageMaker("list", authorizationapi.ResourceAll, newTemplateForbiddenMessageMaker(`User "{{.User.GetName}}" cannot list {{.Attributes.GetResource}} in project "{{.Namespace}}"`))
36+
messageResolver.addRootScopedForbiddenMessageMaker("list", authorizationapi.ResourceAll, newTemplateForbiddenMessageMaker(`User "{{.User.GetName}}" cannot list all {{.Attributes.GetResource}} in the cluster`))
37+
messageResolver.addNamespacedForbiddenMessageMaker("update", authorizationapi.ResourceAll, newTemplateForbiddenMessageMaker(`User "{{.User.GetName}}" cannot update {{.Attributes.GetResource}} in project "{{.Namespace}}"`))
38+
messageResolver.addRootScopedForbiddenMessageMaker("update", authorizationapi.ResourceAll, newTemplateForbiddenMessageMaker(`User "{{.User.GetName}}" cannot update {{.Attributes.GetResource}} at the cluster scope`))
39+
messageResolver.addNamespacedForbiddenMessageMaker("delete", authorizationapi.ResourceAll, newTemplateForbiddenMessageMaker(`User "{{.User.GetName}}" cannot delete {{.Attributes.GetResource}} in project "{{.Namespace}}"`))
40+
messageResolver.addRootScopedForbiddenMessageMaker("delete", authorizationapi.ResourceAll, newTemplateForbiddenMessageMaker(`User "{{.User.GetName}}" cannot delete {{.Attributes.GetResource}} at the cluster scope`))
41+
42+
// project request rejection
43+
projectRequestDeny := projectRequestForbiddenTemplate
44+
if len(projectRequestDeny) == 0 {
45+
projectRequestDeny = DefaultProjectRequestForbidden
46+
}
47+
messageResolver.addRootScopedForbiddenMessageMaker("create", "projectrequests", newTemplateForbiddenMessageMaker(projectRequestDeny))
48+
49+
return messageResolver
50+
}
51+
52+
func (m *ForbiddenMessageResolver) addNamespacedForbiddenMessageMaker(verb, resource string, messageMaker ForbiddenMessageMaker) {
53+
m.addForbiddenMessageMaker(m.namespacedVerbsToResourcesToForbiddenMessageMaker, verb, resource, messageMaker)
54+
}
55+
56+
func (m *ForbiddenMessageResolver) addRootScopedForbiddenMessageMaker(verb, resource string, messageMaker ForbiddenMessageMaker) {
57+
m.addForbiddenMessageMaker(m.rootScopedVerbsToResourcesToForbiddenMessageMaker, verb, resource, messageMaker)
58+
}
59+
60+
func (m *ForbiddenMessageResolver) addForbiddenMessageMaker(target map[string]map[string]ForbiddenMessageMaker, verb, resource string, messageMaker ForbiddenMessageMaker) {
61+
resourcesToForbiddenMessageMaker, exists := target[verb]
62+
if !exists {
63+
resourcesToForbiddenMessageMaker = map[string]ForbiddenMessageMaker{}
64+
target[verb] = resourcesToForbiddenMessageMaker
65+
}
66+
67+
resourcesToForbiddenMessageMaker[resource] = messageMaker
68+
}
69+
70+
func (m *ForbiddenMessageResolver) MakeMessage(ctx MessageContext) (string, error) {
71+
if ctx.Attributes.IsNonResourceURL() {
72+
return m.nonResourceURLForbiddenMessageMaker.MakeMessage(ctx)
73+
}
74+
75+
messageMakerMap := m.namespacedVerbsToResourcesToForbiddenMessageMaker
76+
if len(ctx.Namespace) == 0 {
77+
messageMakerMap = m.rootScopedVerbsToResourcesToForbiddenMessageMaker
78+
}
79+
80+
resourcesToForbiddenMessageMaker, exists := messageMakerMap[ctx.Attributes.GetVerb()]
81+
if !exists {
82+
resourcesToForbiddenMessageMaker, exists = messageMakerMap[authorizationapi.VerbAll]
83+
if !exists {
84+
return m.defaultForbiddenMessageMaker.MakeMessage(ctx)
85+
}
86+
}
87+
88+
messageMaker, exists := resourcesToForbiddenMessageMaker[ctx.Attributes.GetResource()]
89+
if !exists {
90+
messageMaker, exists = resourcesToForbiddenMessageMaker[authorizationapi.ResourceAll]
91+
if !exists {
92+
return m.defaultForbiddenMessageMaker.MakeMessage(ctx)
93+
}
94+
}
95+
96+
specificMessage, err := messageMaker.MakeMessage(ctx)
97+
if err != nil {
98+
return m.defaultForbiddenMessageMaker.MakeMessage(ctx)
99+
}
100+
101+
return specificMessage, nil
102+
}
103+
104+
type templateForbiddenMessageMaker struct {
105+
parsedTemplate *template.Template
106+
}
107+
108+
func newTemplateForbiddenMessageMaker(text string) templateForbiddenMessageMaker {
109+
parsedTemplate := template.Must(template.New("").Parse(text))
110+
111+
return templateForbiddenMessageMaker{parsedTemplate}
112+
}
113+
114+
func (m templateForbiddenMessageMaker) MakeMessage(ctx MessageContext) (string, error) {
115+
buffer := &bytes.Buffer{}
116+
err := m.parsedTemplate.Execute(buffer, ctx)
117+
return string(buffer.Bytes()), err
118+
}

0 commit comments

Comments
 (0)