k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/certificates/ctbattest/admission_test.go (about) 1 /* 2 Copyright 2022 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 ctbattest 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "testing" 24 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/runtime" 27 "k8s.io/apimachinery/pkg/runtime/schema" 28 "k8s.io/apiserver/pkg/admission" 29 "k8s.io/apiserver/pkg/authentication/user" 30 "k8s.io/apiserver/pkg/authorization/authorizer" 31 "k8s.io/apiserver/pkg/util/feature" 32 featuregatetesting "k8s.io/component-base/featuregate/testing" 33 certificatesapi "k8s.io/kubernetes/pkg/apis/certificates" 34 "k8s.io/kubernetes/pkg/features" 35 ) 36 37 func TestPluginValidate(t *testing.T) { 38 tests := []struct { 39 description string 40 clusterTrustBundleFeatureEnabled bool 41 attributes admission.Attributes 42 allowedName string 43 allowed bool 44 authzErr error 45 }{ 46 { 47 description: "wrong type on create", 48 clusterTrustBundleFeatureEnabled: true, 49 attributes: &testAttributes{ 50 resource: certificatesapi.Resource("clustertrustbundles"), 51 obj: &certificatesapi.ClusterTrustBundleList{}, 52 operation: admission.Create, 53 }, 54 allowed: false, 55 }, 56 { 57 description: "wrong type on update", 58 clusterTrustBundleFeatureEnabled: true, 59 attributes: &testAttributes{ 60 resource: certificatesapi.Resource("clustertrustbundles"), 61 obj: &certificatesapi.ClusterTrustBundleList{}, 62 operation: admission.Update, 63 }, 64 allowed: false, 65 }, 66 { 67 description: "reject requests if looking up permissions fails", 68 clusterTrustBundleFeatureEnabled: true, 69 attributes: &testAttributes{ 70 resource: certificatesapi.Resource("clustertrustbundles"), 71 obj: &certificatesapi.ClusterTrustBundle{ 72 Spec: certificatesapi.ClusterTrustBundleSpec{ 73 SignerName: "abc.com/xyz", 74 }, 75 }, 76 operation: admission.Update, 77 }, 78 authzErr: errors.New("forced error"), 79 allowed: false, 80 }, 81 { 82 description: "should allow create if no signer name is specified", 83 clusterTrustBundleFeatureEnabled: true, 84 allowedName: "abc.com/xyz", 85 attributes: &testAttributes{ 86 resource: certificatesapi.Resource("clustertrustbundles"), 87 obj: &certificatesapi.ClusterTrustBundle{ 88 Spec: certificatesapi.ClusterTrustBundleSpec{}, 89 }, 90 operation: admission.Create, 91 }, 92 allowed: true, 93 }, 94 { 95 description: "should allow update if no signer name is specified", 96 clusterTrustBundleFeatureEnabled: true, 97 allowedName: "abc.com/xyz", 98 attributes: &testAttributes{ 99 resource: certificatesapi.Resource("clustertrustbundles"), 100 oldObj: &certificatesapi.ClusterTrustBundle{ 101 Spec: certificatesapi.ClusterTrustBundleSpec{}, 102 }, 103 obj: &certificatesapi.ClusterTrustBundle{ 104 Spec: certificatesapi.ClusterTrustBundleSpec{}, 105 }, 106 operation: admission.Update, 107 }, 108 allowed: true, 109 }, 110 { 111 description: "should allow create if user is authorized for specific signerName", 112 clusterTrustBundleFeatureEnabled: true, 113 allowedName: "abc.com/xyz", 114 attributes: &testAttributes{ 115 resource: certificatesapi.Resource("clustertrustbundles"), 116 obj: &certificatesapi.ClusterTrustBundle{ 117 Spec: certificatesapi.ClusterTrustBundleSpec{ 118 SignerName: "abc.com/xyz", 119 }, 120 }, 121 operation: admission.Create, 122 }, 123 allowed: true, 124 }, 125 { 126 description: "should allow update if user is authorized for specific signerName", 127 clusterTrustBundleFeatureEnabled: true, 128 allowedName: "abc.com/xyz", 129 attributes: &testAttributes{ 130 resource: certificatesapi.Resource("clustertrustbundles"), 131 oldObj: &certificatesapi.ClusterTrustBundle{ 132 Spec: certificatesapi.ClusterTrustBundleSpec{ 133 SignerName: "abc.com/xyz", 134 }, 135 }, 136 obj: &certificatesapi.ClusterTrustBundle{ 137 Spec: certificatesapi.ClusterTrustBundleSpec{ 138 SignerName: "abc.com/xyz", 139 }, 140 }, 141 operation: admission.Update, 142 }, 143 allowed: true, 144 }, 145 { 146 description: "should allow create if user is authorized with wildcard", 147 clusterTrustBundleFeatureEnabled: true, 148 allowedName: "abc.com/*", 149 attributes: &testAttributes{ 150 resource: certificatesapi.Resource("clustertrustbundles"), 151 obj: &certificatesapi.ClusterTrustBundle{ 152 Spec: certificatesapi.ClusterTrustBundleSpec{ 153 SignerName: "abc.com/xyz", 154 }, 155 }, 156 operation: admission.Create, 157 }, 158 allowed: true, 159 }, 160 { 161 description: "should allow update if user is authorized with wildcard", 162 clusterTrustBundleFeatureEnabled: true, 163 allowedName: "abc.com/*", 164 attributes: &testAttributes{ 165 resource: certificatesapi.Resource("clustertrustbundles"), 166 oldObj: &certificatesapi.ClusterTrustBundle{ 167 Spec: certificatesapi.ClusterTrustBundleSpec{ 168 SignerName: "abc.com/xyz", 169 }, 170 }, 171 obj: &certificatesapi.ClusterTrustBundle{ 172 Spec: certificatesapi.ClusterTrustBundleSpec{ 173 SignerName: "abc.com/xyz", 174 }, 175 }, 176 operation: admission.Update, 177 }, 178 allowed: true, 179 }, 180 { 181 description: "should deny create if user does not have permission for this signerName", 182 clusterTrustBundleFeatureEnabled: true, 183 allowedName: "notabc.com/xyz", 184 attributes: &testAttributes{ 185 resource: certificatesapi.Resource("clustertrustbundles"), 186 obj: &certificatesapi.ClusterTrustBundle{ 187 Spec: certificatesapi.ClusterTrustBundleSpec{ 188 SignerName: "abc.com/xyz", 189 }, 190 }, 191 operation: admission.Create, 192 }, 193 allowed: false, 194 }, 195 { 196 description: "should deny update if user does not have permission for this signerName", 197 clusterTrustBundleFeatureEnabled: true, 198 allowedName: "notabc.com/xyz", 199 attributes: &testAttributes{ 200 resource: certificatesapi.Resource("clustertrustbundles"), 201 obj: &certificatesapi.ClusterTrustBundle{ 202 Spec: certificatesapi.ClusterTrustBundleSpec{ 203 SignerName: "abc.com/xyz", 204 }, 205 }, 206 operation: admission.Update, 207 }, 208 allowed: false, 209 }, 210 { 211 description: "should always allow no-op update", 212 clusterTrustBundleFeatureEnabled: true, 213 authzErr: errors.New("broken"), 214 attributes: &testAttributes{ 215 resource: certificatesapi.Resource("clustertrustbundles"), 216 oldObj: &certificatesapi.ClusterTrustBundle{ 217 Spec: certificatesapi.ClusterTrustBundleSpec{ 218 SignerName: "panda.com/foo", 219 }, 220 }, 221 obj: &certificatesapi.ClusterTrustBundle{ 222 Spec: certificatesapi.ClusterTrustBundleSpec{ 223 SignerName: "panda.com/foo", 224 }, 225 }, 226 operation: admission.Update, 227 }, 228 allowed: true, 229 }, 230 { 231 description: "should always allow finalizer update", 232 clusterTrustBundleFeatureEnabled: true, 233 authzErr: errors.New("broken"), 234 attributes: &testAttributes{ 235 resource: certificatesapi.Resource("clustertrustbundles"), 236 oldObj: &certificatesapi.ClusterTrustBundle{ 237 Spec: certificatesapi.ClusterTrustBundleSpec{ 238 SignerName: "panda.com/foo", 239 }, 240 }, 241 obj: &certificatesapi.ClusterTrustBundle{ 242 ObjectMeta: metav1.ObjectMeta{ 243 OwnerReferences: []metav1.OwnerReference{ 244 {APIVersion: "something"}, 245 }, 246 }, 247 Spec: certificatesapi.ClusterTrustBundleSpec{ 248 SignerName: "panda.com/foo", 249 }, 250 }, 251 operation: admission.Update, 252 }, 253 allowed: true, 254 }, 255 } 256 257 for _, tc := range tests { 258 t.Run(tc.description, func(t *testing.T) { 259 p := Plugin{ 260 authz: fakeAuthorizer{ 261 t: t, 262 verb: "attest", 263 allowedName: tc.allowedName, 264 decision: authorizer.DecisionAllow, 265 err: tc.authzErr, 266 }, 267 } 268 269 featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ClusterTrustBundle, tc.clusterTrustBundleFeatureEnabled) 270 p.InspectFeatureGates(feature.DefaultFeatureGate) 271 272 err := p.Validate(context.Background(), tc.attributes, nil) 273 if err == nil && !tc.allowed { 274 t.Errorf("Expected authorization policy to reject ClusterTrustBundle but it was allowed") 275 } 276 if err != nil && tc.allowed { 277 t.Errorf("Expected authorization policy to accept ClusterTrustBundle but it was rejected: %v", err) 278 } 279 }) 280 } 281 } 282 283 type fakeAuthorizer struct { 284 t *testing.T 285 verb string 286 allowedName string 287 decision authorizer.Decision 288 err error 289 } 290 291 func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { 292 if f.err != nil { 293 return f.decision, "forced error", f.err 294 } 295 if a.GetVerb() != f.verb { 296 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised verb '%s'", a.GetVerb()), nil 297 } 298 if a.GetAPIGroup() != "certificates.k8s.io" { 299 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised groupName '%s'", a.GetAPIGroup()), nil 300 } 301 if a.GetAPIVersion() != "*" { 302 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised apiVersion '%s'", a.GetAPIVersion()), nil 303 } 304 if a.GetResource() != "signers" { 305 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised resource '%s'", a.GetResource()), nil 306 } 307 if a.GetName() != f.allowedName { 308 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised resource name '%s'", a.GetName()), nil 309 } 310 if !a.IsResourceRequest() { 311 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised IsResourceRequest '%t'", a.IsResourceRequest()), nil 312 } 313 return f.decision, "", nil 314 } 315 316 type testAttributes struct { 317 resource schema.GroupResource 318 subresource string 319 operation admission.Operation 320 obj, oldObj runtime.Object 321 name string 322 323 admission.Attributes // nil panic if any other methods called 324 } 325 326 func (t *testAttributes) GetResource() schema.GroupVersionResource { 327 return t.resource.WithVersion("ignored") 328 } 329 330 func (t *testAttributes) GetSubresource() string { 331 return t.subresource 332 } 333 334 func (t *testAttributes) GetObject() runtime.Object { 335 return t.obj 336 } 337 338 func (t *testAttributes) GetOldObject() runtime.Object { 339 return t.oldObj 340 } 341 342 func (t *testAttributes) GetName() string { 343 return t.name 344 } 345 346 func (t *testAttributes) GetOperation() admission.Operation { 347 return t.operation 348 } 349 350 func (t *testAttributes) GetUserInfo() user.Info { 351 return &user.DefaultInfo{Name: "ignored"} 352 }