k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/certificates/signing/admission_test.go (about) 1 /* 2 Copyright 2020 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 signing 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "testing" 24 25 "k8s.io/apimachinery/pkg/runtime" 26 "k8s.io/apimachinery/pkg/runtime/schema" 27 "k8s.io/apiserver/pkg/admission" 28 "k8s.io/apiserver/pkg/authentication/user" 29 "k8s.io/apiserver/pkg/authorization/authorizer" 30 31 certificatesapi "k8s.io/kubernetes/pkg/apis/certificates" 32 ) 33 34 func TestPlugin_Validate(t *testing.T) { 35 tests := map[string]struct { 36 attributes admission.Attributes 37 allowedName string 38 allowed bool 39 authzErr error 40 }{ 41 "wrong type": { 42 attributes: &testAttributes{ 43 resource: certificatesapi.Resource("certificatesigningrequests"), 44 subresource: "status", 45 oldObj: &certificatesapi.CertificateSigningRequestList{}, 46 obj: &certificatesapi.CertificateSigningRequestList{}, 47 operation: admission.Update, 48 }, 49 allowed: false, 50 }, 51 "allowed if the 'certificate' and conditions field has not changed": { 52 attributes: &testAttributes{ 53 resource: certificatesapi.Resource("certificatesigningrequests"), 54 subresource: "status", 55 oldObj: &certificatesapi.CertificateSigningRequest{Status: certificatesapi.CertificateSigningRequestStatus{ 56 Certificate: []byte("data"), 57 }}, 58 obj: &certificatesapi.CertificateSigningRequest{Status: certificatesapi.CertificateSigningRequestStatus{ 59 Certificate: []byte("data"), 60 }}, 61 operation: admission.Update, 62 }, 63 allowed: true, 64 authzErr: errors.New("faked error"), 65 }, 66 "deny request if authz lookup fails on certificate change": { 67 allowedName: "abc.com/xyz", 68 attributes: &testAttributes{ 69 resource: certificatesapi.Resource("certificatesigningrequests"), 70 subresource: "status", 71 oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{ 72 SignerName: "abc.com/xyz", 73 }}, 74 obj: &certificatesapi.CertificateSigningRequest{ 75 Spec: certificatesapi.CertificateSigningRequestSpec{ 76 SignerName: "abc.com/xyz", 77 }, 78 Status: certificatesapi.CertificateSigningRequestStatus{ 79 Certificate: []byte("data"), 80 }, 81 }, 82 operation: admission.Update, 83 }, 84 authzErr: errors.New("test"), 85 allowed: false, 86 }, 87 "deny request if authz lookup fails on condition change": { 88 allowedName: "abc.com/xyz", 89 attributes: &testAttributes{ 90 resource: certificatesapi.Resource("certificatesigningrequests"), 91 subresource: "status", 92 oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{ 93 SignerName: "abc.com/xyz", 94 }}, 95 obj: &certificatesapi.CertificateSigningRequest{ 96 Spec: certificatesapi.CertificateSigningRequestSpec{ 97 SignerName: "abc.com/xyz", 98 }, 99 Status: certificatesapi.CertificateSigningRequestStatus{ 100 Conditions: []certificatesapi.CertificateSigningRequestCondition{{Type: certificatesapi.CertificateFailed}}, 101 }, 102 }, 103 operation: admission.Update, 104 }, 105 authzErr: errors.New("test"), 106 allowed: false, 107 }, 108 "allow request if user is authorized for specific signerName": { 109 allowedName: "abc.com/xyz", 110 attributes: &testAttributes{ 111 resource: certificatesapi.Resource("certificatesigningrequests"), 112 subresource: "status", 113 oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{ 114 SignerName: "abc.com/xyz", 115 }}, 116 obj: &certificatesapi.CertificateSigningRequest{ 117 Spec: certificatesapi.CertificateSigningRequestSpec{ 118 SignerName: "abc.com/xyz", 119 }, 120 Status: certificatesapi.CertificateSigningRequestStatus{ 121 Certificate: []byte("data"), 122 }, 123 }, 124 operation: admission.Update, 125 }, 126 allowed: true, 127 }, 128 "allow request if user is authorized with wildcard": { 129 allowedName: "abc.com/*", 130 attributes: &testAttributes{ 131 resource: certificatesapi.Resource("certificatesigningrequests"), 132 subresource: "status", 133 oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{ 134 SignerName: "abc.com/xyz", 135 }}, 136 obj: &certificatesapi.CertificateSigningRequest{ 137 Spec: certificatesapi.CertificateSigningRequestSpec{ 138 SignerName: "abc.com/xyz", 139 }, 140 Status: certificatesapi.CertificateSigningRequestStatus{ 141 Certificate: []byte("data"), 142 }, 143 }, 144 operation: admission.Update, 145 }, 146 allowed: true, 147 }, 148 "should deny request if user does not have permission for this signerName": { 149 allowedName: "notabc.com/xyz", 150 attributes: &testAttributes{ 151 resource: certificatesapi.Resource("certificatesigningrequests"), 152 subresource: "status", 153 oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{ 154 SignerName: "abc.com/xyz", 155 }}, 156 obj: &certificatesapi.CertificateSigningRequest{ 157 Spec: certificatesapi.CertificateSigningRequestSpec{ 158 SignerName: "abc.com/xyz", 159 }, 160 Status: certificatesapi.CertificateSigningRequestStatus{ 161 Certificate: []byte("data"), 162 }, 163 }, 164 operation: admission.Update, 165 }, 166 allowed: false, 167 }, 168 "should deny request if user attempts to update signerName to a new value they *do* have permission to sign for": { 169 allowedName: "allowed.com/xyz", 170 attributes: &testAttributes{ 171 resource: certificatesapi.Resource("certificatesigningrequests"), 172 subresource: "status", 173 oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{ 174 SignerName: "notallowed.com/xyz", 175 }}, 176 obj: &certificatesapi.CertificateSigningRequest{ 177 Spec: certificatesapi.CertificateSigningRequestSpec{ 178 SignerName: "allowed.com/xyz", 179 }, 180 Status: certificatesapi.CertificateSigningRequestStatus{ 181 Certificate: []byte("data"), 182 }, 183 }, 184 operation: admission.Update, 185 }, 186 allowed: false, 187 }, 188 } 189 190 for n, test := range tests { 191 t.Run(n, func(t *testing.T) { 192 p := Plugin{ 193 authz: fakeAuthorizer{ 194 t: t, 195 verb: "sign", 196 allowedName: test.allowedName, 197 decision: authorizer.DecisionAllow, 198 err: test.authzErr, 199 }, 200 } 201 err := p.Validate(context.Background(), test.attributes, nil) 202 if err == nil && !test.allowed { 203 t.Errorf("Expected authorization policy to reject CSR but it was allowed") 204 } 205 if err != nil && test.allowed { 206 t.Errorf("Expected authorization policy to accept CSR but it was rejected: %v", err) 207 } 208 }) 209 } 210 } 211 212 type fakeAuthorizer struct { 213 t *testing.T 214 verb string 215 allowedName string 216 decision authorizer.Decision 217 err error 218 } 219 220 func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { 221 if f.err != nil { 222 return f.decision, "forced error", f.err 223 } 224 if a.GetVerb() != f.verb { 225 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised verb '%s'", a.GetVerb()), nil 226 } 227 if a.GetAPIGroup() != "certificates.k8s.io" { 228 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised groupName '%s'", a.GetAPIGroup()), nil 229 } 230 if a.GetAPIVersion() != "*" { 231 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised apiVersion '%s'", a.GetAPIVersion()), nil 232 } 233 if a.GetResource() != "signers" { 234 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised resource '%s'", a.GetResource()), nil 235 } 236 if a.GetName() != f.allowedName { 237 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised resource name '%s'", a.GetName()), nil 238 } 239 if !a.IsResourceRequest() { 240 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised IsResourceRequest '%t'", a.IsResourceRequest()), nil 241 } 242 return f.decision, "", nil 243 } 244 245 type testAttributes struct { 246 resource schema.GroupResource 247 subresource string 248 operation admission.Operation 249 oldObj, obj runtime.Object 250 name string 251 252 admission.Attributes // nil panic if any other methods called 253 } 254 255 func (t *testAttributes) GetResource() schema.GroupVersionResource { 256 return t.resource.WithVersion("ignored") 257 } 258 259 func (t *testAttributes) GetSubresource() string { 260 return t.subresource 261 } 262 263 func (t *testAttributes) GetOldObject() runtime.Object { 264 return t.oldObj 265 } 266 267 func (t *testAttributes) GetObject() runtime.Object { 268 return t.obj 269 } 270 271 func (t *testAttributes) GetName() string { 272 return t.name 273 } 274 275 func (t *testAttributes) GetOperation() admission.Operation { 276 return t.operation 277 } 278 279 func (t *testAttributes) GetUserInfo() user.Info { 280 return &user.DefaultInfo{Name: "ignored"} 281 }