k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/auth/accessreview_test.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package auth 18 19 import ( 20 "context" 21 "errors" 22 "net/http" 23 "strings" 24 "sync" 25 "testing" 26 27 authorizationapi "k8s.io/api/authorization/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apiserver/pkg/authentication/authenticator" 30 "k8s.io/apiserver/pkg/authentication/user" 31 "k8s.io/apiserver/pkg/authorization/authorizer" 32 api "k8s.io/kubernetes/pkg/apis/core" 33 "k8s.io/kubernetes/pkg/controlplane" 34 "k8s.io/kubernetes/test/integration/framework" 35 "k8s.io/kubernetes/test/utils/ktesting" 36 ) 37 38 // Inject into control plane an authorizer that uses user info. 39 // TODO(etune): remove this test once a more comprehensive built-in authorizer is implemented. 40 type sarAuthorizer struct{} 41 42 func (sarAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { 43 if a.GetUser().GetName() == "dave" { 44 return authorizer.DecisionNoOpinion, "no", errors.New("I'm sorry, Dave") 45 } 46 47 return authorizer.DecisionAllow, "you're not dave", nil 48 } 49 50 func alwaysAlice(req *http.Request) (*authenticator.Response, bool, error) { 51 return &authenticator.Response{ 52 User: &user.DefaultInfo{ 53 Name: "alice", 54 UID: "alice", 55 Groups: []string{user.AllAuthenticated}, 56 }, 57 }, true, nil 58 } 59 60 func TestSubjectAccessReview(t *testing.T) { 61 tCtx := ktesting.Init(t) 62 clientset, _, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{ 63 ModifyServerConfig: func(config *controlplane.Config) { 64 // Unset BearerToken to disable BearerToken authenticator. 65 config.ControlPlane.Generic.LoopbackClientConfig.BearerToken = "" 66 config.ControlPlane.Generic.Authentication.Authenticator = authenticator.RequestFunc(alwaysAlice) 67 config.ControlPlane.Generic.Authorization.Authorizer = sarAuthorizer{} 68 }, 69 }) 70 defer tearDownFn() 71 72 tests := []struct { 73 name string 74 sar *authorizationapi.SubjectAccessReview 75 expectedError string 76 expectedStatus authorizationapi.SubjectAccessReviewStatus 77 }{ 78 { 79 name: "simple allow", 80 sar: &authorizationapi.SubjectAccessReview{ 81 Spec: authorizationapi.SubjectAccessReviewSpec{ 82 ResourceAttributes: &authorizationapi.ResourceAttributes{ 83 Verb: "list", 84 Group: api.GroupName, 85 Version: "v1", 86 Resource: "pods", 87 }, 88 User: "alice", 89 }, 90 }, 91 expectedStatus: authorizationapi.SubjectAccessReviewStatus{ 92 Allowed: true, 93 Reason: "you're not dave", 94 }, 95 }, 96 { 97 name: "simple deny", 98 sar: &authorizationapi.SubjectAccessReview{ 99 Spec: authorizationapi.SubjectAccessReviewSpec{ 100 ResourceAttributes: &authorizationapi.ResourceAttributes{ 101 Verb: "list", 102 Group: api.GroupName, 103 Version: "v1", 104 Resource: "pods", 105 }, 106 User: "dave", 107 }, 108 }, 109 expectedStatus: authorizationapi.SubjectAccessReviewStatus{ 110 Allowed: false, 111 Reason: "no", 112 EvaluationError: "I'm sorry, Dave", 113 }, 114 }, 115 { 116 name: "simple error", 117 sar: &authorizationapi.SubjectAccessReview{ 118 Spec: authorizationapi.SubjectAccessReviewSpec{ 119 ResourceAttributes: &authorizationapi.ResourceAttributes{ 120 Verb: "list", 121 Group: api.GroupName, 122 Version: "v1", 123 Resource: "pods", 124 }, 125 }, 126 }, 127 expectedError: "at least one of user or group must be specified", 128 }, 129 } 130 131 for _, test := range tests { 132 response, err := clientset.AuthorizationV1().SubjectAccessReviews().Create(tCtx, test.sar, metav1.CreateOptions{}) 133 switch { 134 case err == nil && len(test.expectedError) == 0: 135 136 case err != nil && strings.Contains(err.Error(), test.expectedError): 137 continue 138 139 case err != nil && len(test.expectedError) != 0: 140 t.Errorf("%s: unexpected error: %v", test.name, err) 141 continue 142 default: 143 t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err) 144 continue 145 } 146 if response.Status != test.expectedStatus { 147 t.Errorf("%s: expected %v, got %v", test.name, test.expectedStatus, response.Status) 148 continue 149 } 150 } 151 } 152 153 func TestSelfSubjectAccessReview(t *testing.T) { 154 tCtx := ktesting.Init(t) 155 156 var mutex sync.Mutex 157 158 username := "alice" 159 authenticatorFunc := func(req *http.Request) (*authenticator.Response, bool, error) { 160 mutex.Lock() 161 defer mutex.Unlock() 162 163 return &authenticator.Response{ 164 User: &user.DefaultInfo{ 165 Name: username, 166 UID: username, 167 Groups: []string{user.AllAuthenticated}, 168 }, 169 }, true, nil 170 } 171 172 clientset, _, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{ 173 ModifyServerConfig: func(config *controlplane.Config) { 174 // Unset BearerToken to disable BearerToken authenticator. 175 config.ControlPlane.Generic.LoopbackClientConfig.BearerToken = "" 176 config.ControlPlane.Generic.Authentication.Authenticator = authenticator.RequestFunc(authenticatorFunc) 177 config.ControlPlane.Generic.Authorization.Authorizer = sarAuthorizer{} 178 }, 179 }) 180 defer tearDownFn() 181 182 tests := []struct { 183 name string 184 username string 185 sar *authorizationapi.SelfSubjectAccessReview 186 expectedError string 187 expectedStatus authorizationapi.SubjectAccessReviewStatus 188 }{ 189 { 190 name: "simple allow", 191 username: "alice", 192 sar: &authorizationapi.SelfSubjectAccessReview{ 193 Spec: authorizationapi.SelfSubjectAccessReviewSpec{ 194 ResourceAttributes: &authorizationapi.ResourceAttributes{ 195 Verb: "list", 196 Group: api.GroupName, 197 Version: "v1", 198 Resource: "pods", 199 }, 200 }, 201 }, 202 expectedStatus: authorizationapi.SubjectAccessReviewStatus{ 203 Allowed: true, 204 Reason: "you're not dave", 205 }, 206 }, 207 { 208 name: "simple deny", 209 username: "dave", 210 sar: &authorizationapi.SelfSubjectAccessReview{ 211 Spec: authorizationapi.SelfSubjectAccessReviewSpec{ 212 ResourceAttributes: &authorizationapi.ResourceAttributes{ 213 Verb: "list", 214 Group: api.GroupName, 215 Version: "v1", 216 Resource: "pods", 217 }, 218 }, 219 }, 220 expectedStatus: authorizationapi.SubjectAccessReviewStatus{ 221 Allowed: false, 222 Reason: "no", 223 EvaluationError: "I'm sorry, Dave", 224 }, 225 }, 226 } 227 228 for _, test := range tests { 229 mutex.Lock() 230 username = test.username 231 mutex.Unlock() 232 233 response, err := clientset.AuthorizationV1().SelfSubjectAccessReviews().Create(tCtx, test.sar, metav1.CreateOptions{}) 234 switch { 235 case err == nil && len(test.expectedError) == 0: 236 237 case err != nil && strings.Contains(err.Error(), test.expectedError): 238 continue 239 240 case err != nil && len(test.expectedError) != 0: 241 t.Errorf("%s: unexpected error: %v", test.name, err) 242 continue 243 default: 244 t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err) 245 continue 246 } 247 if response.Status != test.expectedStatus { 248 t.Errorf("%s: expected %v, got %v", test.name, test.expectedStatus, response.Status) 249 continue 250 } 251 } 252 } 253 254 func TestLocalSubjectAccessReview(t *testing.T) { 255 tCtx := ktesting.Init(t) 256 clientset, _, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{ 257 ModifyServerConfig: func(config *controlplane.Config) { 258 // Unset BearerToken to disable BearerToken authenticator. 259 config.ControlPlane.Generic.LoopbackClientConfig.BearerToken = "" 260 config.ControlPlane.Generic.Authentication.Authenticator = authenticator.RequestFunc(alwaysAlice) 261 config.ControlPlane.Generic.Authorization.Authorizer = sarAuthorizer{} 262 }, 263 }) 264 defer tearDownFn() 265 266 tests := []struct { 267 name string 268 namespace string 269 sar *authorizationapi.LocalSubjectAccessReview 270 expectedError string 271 expectedStatus authorizationapi.SubjectAccessReviewStatus 272 }{ 273 { 274 name: "simple allow", 275 namespace: "foo", 276 sar: &authorizationapi.LocalSubjectAccessReview{ 277 ObjectMeta: metav1.ObjectMeta{Namespace: "foo"}, 278 Spec: authorizationapi.SubjectAccessReviewSpec{ 279 ResourceAttributes: &authorizationapi.ResourceAttributes{ 280 Verb: "list", 281 Group: api.GroupName, 282 Version: "v1", 283 Resource: "pods", 284 Namespace: "foo", 285 }, 286 User: "alice", 287 }, 288 }, 289 expectedStatus: authorizationapi.SubjectAccessReviewStatus{ 290 Allowed: true, 291 Reason: "you're not dave", 292 }, 293 }, 294 { 295 name: "simple deny", 296 namespace: "foo", 297 sar: &authorizationapi.LocalSubjectAccessReview{ 298 ObjectMeta: metav1.ObjectMeta{Namespace: "foo"}, 299 Spec: authorizationapi.SubjectAccessReviewSpec{ 300 ResourceAttributes: &authorizationapi.ResourceAttributes{ 301 Verb: "list", 302 Group: api.GroupName, 303 Version: "v1", 304 Resource: "pods", 305 Namespace: "foo", 306 }, 307 User: "dave", 308 }, 309 }, 310 expectedStatus: authorizationapi.SubjectAccessReviewStatus{ 311 Allowed: false, 312 Reason: "no", 313 EvaluationError: "I'm sorry, Dave", 314 }, 315 }, 316 { 317 name: "conflicting namespace", 318 namespace: "foo", 319 sar: &authorizationapi.LocalSubjectAccessReview{ 320 ObjectMeta: metav1.ObjectMeta{Namespace: "foo"}, 321 Spec: authorizationapi.SubjectAccessReviewSpec{ 322 ResourceAttributes: &authorizationapi.ResourceAttributes{ 323 Verb: "list", 324 Group: api.GroupName, 325 Version: "v1", 326 Resource: "pods", 327 Namespace: "bar", 328 }, 329 User: "dave", 330 }, 331 }, 332 expectedError: "must match metadata.namespace", 333 }, 334 { 335 name: "missing namespace", 336 namespace: "foo", 337 sar: &authorizationapi.LocalSubjectAccessReview{ 338 ObjectMeta: metav1.ObjectMeta{Namespace: "foo"}, 339 Spec: authorizationapi.SubjectAccessReviewSpec{ 340 ResourceAttributes: &authorizationapi.ResourceAttributes{ 341 Verb: "list", 342 Group: api.GroupName, 343 Version: "v1", 344 Resource: "pods", 345 }, 346 User: "dave", 347 }, 348 }, 349 expectedError: "must match metadata.namespace", 350 }, 351 } 352 353 for _, test := range tests { 354 response, err := clientset.AuthorizationV1().LocalSubjectAccessReviews(test.namespace).Create(tCtx, test.sar, metav1.CreateOptions{}) 355 switch { 356 case err == nil && len(test.expectedError) == 0: 357 358 case err != nil && strings.Contains(err.Error(), test.expectedError): 359 continue 360 361 case err != nil && len(test.expectedError) != 0: 362 t.Errorf("%s: unexpected error: %v", test.name, err) 363 continue 364 default: 365 t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err) 366 continue 367 } 368 if response.Status != test.expectedStatus { 369 t.Errorf("%s: expected %#v, got %#v", test.name, test.expectedStatus, response.Status) 370 continue 371 } 372 } 373 }