forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 1
/
rest.go
159 lines (137 loc) · 5.83 KB
/
rest.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package subjectaccessreview
import (
"errors"
"fmt"
kapierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authentication/user"
kauthorizer "k8s.io/apiserver/pkg/authorization/authorizer"
apirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
authorizationapi "github.com/openshift/origin/pkg/authorization/apis/authorization"
authorizationvalidation "github.com/openshift/origin/pkg/authorization/apis/authorization/validation"
"github.com/openshift/origin/pkg/authorization/authorizer"
"github.com/openshift/origin/pkg/authorization/registry/util"
)
// REST implements the RESTStorage interface in terms of an Registry.
type REST struct {
authorizer kauthorizer.Authorizer
}
var _ rest.Creater = &REST{}
// NewREST creates a new REST for policies.
func NewREST(authorizer kauthorizer.Authorizer) *REST {
return &REST{authorizer}
}
// New creates a new ResourceAccessReview object
func (r *REST) New() runtime.Object {
return &authorizationapi.SubjectAccessReview{}
}
// Create registers a given new ResourceAccessReview instance to r.registry.
func (r *REST) Create(ctx apirequest.Context, obj runtime.Object, _ rest.ValidateObjectFunc, _ bool) (runtime.Object, error) {
subjectAccessReview, ok := obj.(*authorizationapi.SubjectAccessReview)
if !ok {
return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a subjectAccessReview: %#v", obj))
}
if errs := authorizationvalidation.ValidateSubjectAccessReview(subjectAccessReview); len(errs) > 0 {
return nil, kapierrors.NewInvalid(authorizationapi.Kind(subjectAccessReview.Kind), "", errs)
}
requestingUser, ok := apirequest.UserFrom(ctx)
if !ok {
return nil, kapierrors.NewInternalError(errors.New("missing user on request"))
}
// if a namespace is present on the request, then the namespace on the on the SAR is overwritten.
// This is to support backwards compatibility. To have gotten here in this state, it means that
// the authorizer decided that a user could run an SAR against this namespace
if namespace := apirequest.NamespaceValue(ctx); len(namespace) > 0 {
subjectAccessReview.Action.Namespace = namespace
} else if err := r.isAllowed(requestingUser, subjectAccessReview); err != nil {
// this check is mutually exclusive to the condition above. localSAR and localRAR both clear the namespace before delegating their calls
// We only need to check if the SAR is allowed **again** if the authorizer didn't already approve the request for a legacy call.
return nil, err
}
var userToCheck *user.DefaultInfo
if (len(subjectAccessReview.User) == 0) && (len(subjectAccessReview.Groups) == 0) {
// if no user or group was specified, use the info from the context
ctxUser, exists := apirequest.UserFrom(ctx)
if !exists {
return nil, kapierrors.NewBadRequest("user missing from context")
}
// make a copy, we don't want to risk changing the original
newExtra := map[string][]string{}
for k, v := range ctxUser.GetExtra() {
if v == nil {
newExtra[k] = nil
continue
}
newSlice := make([]string, len(v))
copy(newSlice, v)
newExtra[k] = newSlice
}
userToCheck = &user.DefaultInfo{
Name: ctxUser.GetName(),
Groups: ctxUser.GetGroups(),
UID: ctxUser.GetUID(),
Extra: newExtra,
}
} else {
userToCheck = &user.DefaultInfo{
Name: subjectAccessReview.User,
Groups: subjectAccessReview.Groups.List(),
Extra: map[string][]string{},
}
}
switch {
case subjectAccessReview.Scopes == nil:
// leave the scopes alone. on a self-sar, this means "use incoming request", on regular-sar it means, "use no scope restrictions"
case len(subjectAccessReview.Scopes) == 0:
// this always means "use no scope restrictions", so delete them
delete(userToCheck.Extra, authorizationapi.ScopesKey)
case len(subjectAccessReview.Scopes) > 0:
// this always means, "use these scope restrictions", so force the value
userToCheck.Extra[authorizationapi.ScopesKey] = subjectAccessReview.Scopes
}
attributes := util.ToDefaultAuthorizationAttributes(userToCheck, subjectAccessReview.Action.Namespace, subjectAccessReview.Action)
authorized, reason, err := r.authorizer.Authorize(attributes)
response := &authorizationapi.SubjectAccessReviewResponse{
Namespace: subjectAccessReview.Action.Namespace,
Allowed: authorized == kauthorizer.DecisionAllow,
Reason: reason,
}
if err != nil {
response.EvaluationError = err.Error()
}
return response, nil
}
// isAllowed checks to see if the current user has rights to issue a LocalSubjectAccessReview on the namespace they're attempting to access
func (r *REST) isAllowed(user user.Info, sar *authorizationapi.SubjectAccessReview) error {
var localSARAttributes kauthorizer.AttributesRecord
// if they are running a personalSAR, create synthentic check for selfSAR
if authorizer.IsPersonalAccessReviewFromSAR(sar) {
localSARAttributes = kauthorizer.AttributesRecord{
User: user,
Verb: "create",
Namespace: sar.Action.Namespace,
APIGroup: "authorization.k8s.io",
Resource: "selfsubjectaccessreviews",
ResourceRequest: true,
}
} else {
localSARAttributes = kauthorizer.AttributesRecord{
User: user,
Verb: "create",
Namespace: sar.Action.Namespace,
Resource: "localsubjectaccessreviews",
ResourceRequest: true,
}
}
authorized, reason, err := r.authorizer.Authorize(localSARAttributes)
if err != nil {
return kapierrors.NewForbidden(authorizationapi.Resource(localSARAttributes.GetResource()), localSARAttributes.GetName(), err)
}
if authorized != kauthorizer.DecisionAllow {
forbiddenError := kapierrors.NewForbidden(authorizationapi.Resource(localSARAttributes.GetResource()), localSARAttributes.GetName(), errors.New("") /*discarded*/)
forbiddenError.ErrStatus.Message = reason
return forbiddenError
}
return nil
}