1
16
17 package policybased
18
19 import (
20 "context"
21 "testing"
22
23 rbacv1 "k8s.io/api/rbac/v1"
24 "k8s.io/apimachinery/pkg/api/errors"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/runtime"
27 "k8s.io/apiserver/pkg/authentication/user"
28 "k8s.io/apiserver/pkg/authorization/authorizer"
29 "k8s.io/apiserver/pkg/endpoints/request"
30 "k8s.io/apiserver/pkg/registry/rest"
31 "k8s.io/kubernetes/pkg/apis/rbac"
32 _ "k8s.io/kubernetes/pkg/apis/rbac/install"
33 "k8s.io/kubernetes/pkg/registry/rbac/validation"
34 )
35
36 func TestEscalation(t *testing.T) {
37 createContext := request.WithRequestInfo(request.WithNamespace(context.TODO(), "myns"), &request.RequestInfo{
38 IsResourceRequest: true,
39 Verb: "create",
40 APIGroup: "rbac.authorization.k8s.io",
41 APIVersion: "v1",
42 Namespace: "myns",
43 Resource: "roles",
44 Name: "",
45 })
46 updateContext := request.WithRequestInfo(request.WithNamespace(context.TODO(), "myns"), &request.RequestInfo{
47 IsResourceRequest: true,
48 Verb: "update",
49 APIGroup: "rbac.authorization.k8s.io",
50 APIVersion: "v1",
51 Namespace: "myns",
52 Resource: "roles",
53 Name: "myrole",
54 })
55
56 superuser := &user.DefaultInfo{Name: "superuser", Groups: []string{"system:masters"}}
57 bob := &user.DefaultInfo{Name: "bob"}
58 steve := &user.DefaultInfo{Name: "steve"}
59 alice := &user.DefaultInfo{Name: "alice"}
60
61 authzCalled := 0
62 fakeStorage := &fakeStorage{}
63 fakeAuthorizer := authorizer.AuthorizerFunc(func(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) {
64 authzCalled++
65 if attr.GetUser().GetName() == "steve" {
66 return authorizer.DecisionAllow, "", nil
67 }
68 return authorizer.DecisionNoOpinion, "", nil
69 })
70 fakeRuleResolver, _ := validation.NewTestRuleResolver(
71 nil,
72 nil,
73 []*rbacv1.ClusterRole{{ObjectMeta: metav1.ObjectMeta{Name: "alice-role"}, Rules: []rbacv1.PolicyRule{{APIGroups: []string{"*"}, Resources: []string{"*"}, Verbs: []string{"*"}}}}},
74 []*rbacv1.ClusterRoleBinding{{RoleRef: rbacv1.RoleRef{Name: "alice-role", APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole"}, Subjects: []rbacv1.Subject{{Name: "alice", Kind: "User", APIGroup: "rbac.authorization.k8s.io"}}}},
75 )
76
77 role := &rbac.Role{
78 ObjectMeta: metav1.ObjectMeta{Name: "myrole", Namespace: "myns"},
79 Rules: []rbac.PolicyRule{{APIGroups: []string{""}, Verbs: []string{"get"}, Resources: []string{"pods"}}},
80 }
81
82 s := NewStorage(fakeStorage, fakeAuthorizer, fakeRuleResolver)
83
84 testcases := []struct {
85 name string
86 user user.Info
87 expectAllowed bool
88 expectAuthz bool
89 }{
90
91 {
92 name: "superuser",
93 user: superuser,
94 expectAuthz: false,
95 expectAllowed: true,
96 },
97
98 {
99 name: "bob",
100 user: bob,
101 expectAuthz: true,
102 expectAllowed: false,
103 },
104
105 {
106 name: "steve",
107 user: steve,
108 expectAuthz: true,
109 expectAllowed: true,
110 },
111
112 {
113 name: "alice",
114 user: alice,
115 expectAuthz: true,
116 expectAllowed: true,
117 },
118 }
119
120 for _, tc := range testcases {
121 t.Run(tc.name, func(t *testing.T) {
122 authzCalled, fakeStorage.created, fakeStorage.updated = 0, 0, 0
123 _, err := s.Create(request.WithUser(createContext, tc.user), role, nil, nil)
124
125 if tc.expectAllowed {
126 if err != nil {
127 t.Error(err)
128 return
129 }
130 if fakeStorage.created != 1 {
131 t.Errorf("unexpected calls to underlying storage.Create: %d", fakeStorage.created)
132 return
133 }
134 } else {
135 if !errors.IsForbidden(err) {
136 t.Errorf("expected forbidden, got %v", err)
137 return
138 }
139 if fakeStorage.created != 0 {
140 t.Errorf("unexpected calls to underlying storage.Create: %d", fakeStorage.created)
141 return
142 }
143 }
144
145 if tc.expectAuthz != (authzCalled > 0) {
146 t.Fatalf("expected authz=%v, saw %d calls", tc.expectAuthz, authzCalled)
147 }
148
149 authzCalled, fakeStorage.created, fakeStorage.updated = 0, 0, 0
150 _, _, err = s.Update(request.WithUser(updateContext, tc.user), role.Name, rest.DefaultUpdatedObjectInfo(role), nil, nil, false, nil)
151
152 if tc.expectAllowed {
153 if err != nil {
154 t.Error(err)
155 return
156 }
157 if fakeStorage.updated != 1 {
158 t.Errorf("unexpected calls to underlying storage.Update: %d", fakeStorage.updated)
159 return
160 }
161 } else {
162 if !errors.IsForbidden(err) {
163 t.Errorf("expected forbidden, got %v", err)
164 return
165 }
166 if fakeStorage.updated != 0 {
167 t.Errorf("unexpected calls to underlying storage.Update: %d", fakeStorage.updated)
168 return
169 }
170 }
171
172 if tc.expectAuthz != (authzCalled > 0) {
173 t.Fatalf("expected authz=%v, saw %d calls", tc.expectAuthz, authzCalled)
174 }
175 })
176 }
177 }
178
179 type fakeStorage struct {
180 updated int
181 created int
182 rest.StandardStorage
183 }
184
185 func (f *fakeStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
186 f.created++
187 return nil, nil
188 }
189
190 func (f *fakeStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
191 obj, err := objInfo.UpdatedObject(ctx, &rbac.Role{})
192 if err != nil {
193 return obj, false, err
194 }
195 f.updated++
196 return nil, false, nil
197 }
198
View as plain text