k8s.io/apiserver@v0.31.1/pkg/endpoints/filters/authorization_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 filters 18 19 import ( 20 "context" 21 "errors" 22 "k8s.io/apimachinery/pkg/fields" 23 "k8s.io/apimachinery/pkg/labels" 24 "k8s.io/apimachinery/pkg/selection" 25 genericfeatures "k8s.io/apiserver/pkg/features" 26 utilfeature "k8s.io/apiserver/pkg/util/feature" 27 featuregatetesting "k8s.io/component-base/featuregate/testing" 28 "net/http" 29 "net/http/httptest" 30 "reflect" 31 "testing" 32 33 "github.com/stretchr/testify/assert" 34 batch "k8s.io/api/batch/v1" 35 "k8s.io/apimachinery/pkg/runtime" 36 "k8s.io/apimachinery/pkg/runtime/serializer" 37 auditinternal "k8s.io/apiserver/pkg/apis/audit" 38 "k8s.io/apiserver/pkg/audit" 39 "k8s.io/apiserver/pkg/authorization/authorizer" 40 ) 41 42 func TestGetAuthorizerAttributes(t *testing.T) { 43 basicLabelRequirement, err := labels.NewRequirement("foo", selection.DoubleEquals, []string{"bar"}) 44 if err != nil { 45 t.Fatal(err) 46 } 47 48 testcases := map[string]struct { 49 Verb string 50 Path string 51 ExpectedAttributes *authorizer.AttributesRecord 52 EnableAuthorizationSelector bool 53 }{ 54 "non-resource root": { 55 Verb: "POST", 56 Path: "/", 57 ExpectedAttributes: &authorizer.AttributesRecord{ 58 Verb: "post", 59 Path: "/", 60 }, 61 }, 62 "non-resource api prefix": { 63 Verb: "GET", 64 Path: "/api/", 65 ExpectedAttributes: &authorizer.AttributesRecord{ 66 Verb: "get", 67 Path: "/api/", 68 }, 69 }, 70 "non-resource group api prefix": { 71 Verb: "GET", 72 Path: "/apis/extensions/", 73 ExpectedAttributes: &authorizer.AttributesRecord{ 74 Verb: "get", 75 Path: "/apis/extensions/", 76 }, 77 }, 78 79 "resource": { 80 Verb: "POST", 81 Path: "/api/v1/nodes/mynode", 82 ExpectedAttributes: &authorizer.AttributesRecord{ 83 Verb: "create", 84 Path: "/api/v1/nodes/mynode", 85 ResourceRequest: true, 86 Resource: "nodes", 87 APIVersion: "v1", 88 Name: "mynode", 89 }, 90 }, 91 "namespaced resource": { 92 Verb: "PUT", 93 Path: "/api/v1/namespaces/myns/pods/mypod", 94 ExpectedAttributes: &authorizer.AttributesRecord{ 95 Verb: "update", 96 Path: "/api/v1/namespaces/myns/pods/mypod", 97 ResourceRequest: true, 98 Namespace: "myns", 99 Resource: "pods", 100 APIVersion: "v1", 101 Name: "mypod", 102 }, 103 }, 104 "API group resource": { 105 Verb: "GET", 106 Path: "/apis/batch/v1/namespaces/myns/jobs", 107 ExpectedAttributes: &authorizer.AttributesRecord{ 108 Verb: "list", 109 Path: "/apis/batch/v1/namespaces/myns/jobs", 110 ResourceRequest: true, 111 APIGroup: batch.GroupName, 112 APIVersion: "v1", 113 Namespace: "myns", 114 Resource: "jobs", 115 }, 116 }, 117 "disabled, ignore good field selector": { 118 Verb: "GET", 119 Path: "/apis/batch/v1/namespaces/myns/jobs?fieldSelector%=foo%3Dbar", 120 ExpectedAttributes: &authorizer.AttributesRecord{ 121 Verb: "list", 122 Path: "/apis/batch/v1/namespaces/myns/jobs", 123 ResourceRequest: true, 124 APIGroup: batch.GroupName, 125 APIVersion: "v1", 126 Namespace: "myns", 127 Resource: "jobs", 128 }, 129 }, 130 "enabled, good field selector": { 131 Verb: "GET", 132 Path: "/apis/batch/v1/namespaces/myns/jobs?fieldSelector=foo%3D%3Dbar", 133 ExpectedAttributes: &authorizer.AttributesRecord{ 134 Verb: "list", 135 Path: "/apis/batch/v1/namespaces/myns/jobs", 136 ResourceRequest: true, 137 APIGroup: batch.GroupName, 138 APIVersion: "v1", 139 Namespace: "myns", 140 Resource: "jobs", 141 FieldSelectorRequirements: fields.Requirements{ 142 fields.OneTermEqualSelector("foo", "bar").Requirements()[0], 143 }, 144 }, 145 EnableAuthorizationSelector: true, 146 }, 147 "enabled, bad field selector": { 148 Verb: "GET", 149 Path: "/apis/batch/v1/namespaces/myns/jobs?fieldSelector=%2Abar", 150 ExpectedAttributes: &authorizer.AttributesRecord{ 151 Verb: "list", 152 Path: "/apis/batch/v1/namespaces/myns/jobs", 153 ResourceRequest: true, 154 APIGroup: batch.GroupName, 155 APIVersion: "v1", 156 Namespace: "myns", 157 Resource: "jobs", 158 FieldSelectorParsingErr: errors.New("invalid selector: '*bar'; can't understand '*bar'"), 159 }, 160 EnableAuthorizationSelector: true, 161 }, 162 "disabled, ignore good label selector": { 163 Verb: "GET", 164 Path: "/apis/batch/v1/namespaces/myns/jobs?labelSelector%=foo%3Dbar", 165 ExpectedAttributes: &authorizer.AttributesRecord{ 166 Verb: "list", 167 Path: "/apis/batch/v1/namespaces/myns/jobs", 168 ResourceRequest: true, 169 APIGroup: batch.GroupName, 170 APIVersion: "v1", 171 Namespace: "myns", 172 Resource: "jobs", 173 }, 174 }, 175 "enabled, good label selector": { 176 Verb: "GET", 177 Path: "/apis/batch/v1/namespaces/myns/jobs?labelSelector=foo%3D%3Dbar", 178 ExpectedAttributes: &authorizer.AttributesRecord{ 179 Verb: "list", 180 Path: "/apis/batch/v1/namespaces/myns/jobs", 181 ResourceRequest: true, 182 APIGroup: batch.GroupName, 183 APIVersion: "v1", 184 Namespace: "myns", 185 Resource: "jobs", 186 LabelSelectorRequirements: labels.Requirements{ 187 *basicLabelRequirement, 188 }, 189 }, 190 EnableAuthorizationSelector: true, 191 }, 192 "enabled, bad label selector": { 193 Verb: "GET", 194 Path: "/apis/batch/v1/namespaces/myns/jobs?labelSelector=%2Abar", 195 ExpectedAttributes: &authorizer.AttributesRecord{ 196 Verb: "list", 197 Path: "/apis/batch/v1/namespaces/myns/jobs", 198 ResourceRequest: true, 199 APIGroup: batch.GroupName, 200 APIVersion: "v1", 201 Namespace: "myns", 202 Resource: "jobs", 203 LabelSelectorParsingErr: errors.New("unable to parse requirement: <nil>: Invalid value: \"*bar\": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')"), 204 }, 205 EnableAuthorizationSelector: true, 206 }, 207 } 208 209 for k, tc := range testcases { 210 t.Run(k, func(t *testing.T) { 211 if tc.EnableAuthorizationSelector { 212 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.AuthorizeWithSelectors, true) 213 } 214 215 req, _ := http.NewRequest(tc.Verb, tc.Path, nil) 216 req.RemoteAddr = "127.0.0.1" 217 218 var attribs authorizer.Attributes 219 var err error 220 var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 221 ctx := req.Context() 222 attribs, err = GetAuthorizerAttributes(ctx) 223 }) 224 handler = WithRequestInfo(handler, newTestRequestInfoResolver()) 225 handler.ServeHTTP(httptest.NewRecorder(), req) 226 227 if err != nil { 228 t.Errorf("%s: unexpected error: %v", k, err) 229 } else if !reflect.DeepEqual(attribs, tc.ExpectedAttributes) { 230 t.Errorf("%s: expected\n\t%#v\ngot\n\t%#v", k, tc.ExpectedAttributes, attribs) 231 } 232 }) 233 } 234 } 235 236 type fakeAuthorizer struct { 237 decision authorizer.Decision 238 reason string 239 err error 240 } 241 242 func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { 243 return f.decision, f.reason, f.err 244 } 245 246 func TestAuditAnnotation(t *testing.T) { 247 testcases := map[string]struct { 248 authorizer fakeAuthorizer 249 decisionAnnotation string 250 reasonAnnotation string 251 }{ 252 "decision allow": { 253 fakeAuthorizer{ 254 authorizer.DecisionAllow, 255 "RBAC: allowed to patch pod", 256 nil, 257 }, 258 "allow", 259 "RBAC: allowed to patch pod", 260 }, 261 "decision forbid": { 262 fakeAuthorizer{ 263 authorizer.DecisionDeny, 264 "RBAC: not allowed to patch pod", 265 nil, 266 }, 267 "forbid", 268 "RBAC: not allowed to patch pod", 269 }, 270 "error": { 271 fakeAuthorizer{ 272 authorizer.DecisionNoOpinion, 273 "", 274 errors.New("can't parse user info"), 275 }, 276 "", 277 reasonError, 278 }, 279 } 280 281 scheme := runtime.NewScheme() 282 negotiatedSerializer := serializer.NewCodecFactory(scheme).WithoutConversion() 283 for k, tc := range testcases { 284 handler := WithAuthorization(&fakeHTTPHandler{}, tc.authorizer, negotiatedSerializer) 285 // TODO: fake audit injector 286 287 req, _ := http.NewRequest("GET", "/api/v1/namespaces/default/pods", nil) 288 req = withTestContext(req, nil, &auditinternal.Event{Level: auditinternal.LevelMetadata}) 289 ae := audit.AuditEventFrom(req.Context()) 290 req.RemoteAddr = "127.0.0.1" 291 handler.ServeHTTP(httptest.NewRecorder(), req) 292 assert.Equal(t, tc.decisionAnnotation, ae.Annotations[decisionAnnotationKey], k+": unexpected decision annotation") 293 assert.Equal(t, tc.reasonAnnotation, ae.Annotations[reasonAnnotationKey], k+": unexpected reason annotation") 294 } 295 296 }