k8s.io/client-go@v0.22.2/util/certificate/csr/csr.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 csr 18 19 import ( 20 "context" 21 "crypto" 22 "crypto/x509" 23 "encoding/pem" 24 "fmt" 25 "reflect" 26 "time" 27 28 certificatesv1 "k8s.io/api/certificates/v1" 29 certificatesv1beta1 "k8s.io/api/certificates/v1beta1" 30 "k8s.io/apimachinery/pkg/api/errors" 31 apierrors "k8s.io/apimachinery/pkg/api/errors" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/fields" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/apimachinery/pkg/types" 36 "k8s.io/apimachinery/pkg/util/wait" 37 "k8s.io/apimachinery/pkg/watch" 38 clientset "k8s.io/client-go/kubernetes" 39 "k8s.io/client-go/tools/cache" 40 watchtools "k8s.io/client-go/tools/watch" 41 certutil "k8s.io/client-go/util/cert" 42 "k8s.io/klog/v2" 43 "k8s.io/utils/pointer" 44 ) 45 46 // RequestCertificate will either use an existing (if this process has run 47 // before but not to completion) or create a certificate signing request using the 48 // PEM encoded CSR and send it to API server. An optional requestedDuration may be passed 49 // to set the spec.expirationSeconds field on the CSR to control the lifetime of the issued 50 // certificate. This is not guaranteed as the signer may choose to ignore the request. 51 func RequestCertificate(client clientset.Interface, csrData []byte, name, signerName string, requestedDuration *time.Duration, usages []certificatesv1.KeyUsage, privateKey interface{}) (reqName string, reqUID types.UID, err error) { 52 csr := &certificatesv1.CertificateSigningRequest{ 53 // Username, UID, Groups will be injected by API server. 54 TypeMeta: metav1.TypeMeta{Kind: "CertificateSigningRequest"}, 55 ObjectMeta: metav1.ObjectMeta{ 56 Name: name, 57 }, 58 Spec: certificatesv1.CertificateSigningRequestSpec{ 59 Request: csrData, 60 Usages: usages, 61 SignerName: signerName, 62 }, 63 } 64 if len(csr.Name) == 0 { 65 csr.GenerateName = "csr-" 66 } 67 if requestedDuration != nil { 68 csr.Spec.ExpirationSeconds = DurationToExpirationSeconds(*requestedDuration) 69 } 70 71 reqName, reqUID, err = create(client, csr) 72 switch { 73 case err == nil: 74 return reqName, reqUID, err 75 76 case errors.IsAlreadyExists(err) && len(name) > 0: 77 klog.Infof("csr for this node already exists, reusing") 78 req, err := get(client, name) 79 if err != nil { 80 return "", "", formatError("cannot retrieve certificate signing request: %v", err) 81 } 82 if err := ensureCompatible(req, csr, privateKey); err != nil { 83 return "", "", fmt.Errorf("retrieved csr is not compatible: %v", err) 84 } 85 klog.Infof("csr for this node is still valid") 86 return req.Name, req.UID, nil 87 88 default: 89 return "", "", formatError("cannot create certificate signing request: %v", err) 90 } 91 } 92 93 func DurationToExpirationSeconds(duration time.Duration) *int32 { 94 return pointer.Int32(int32(duration / time.Second)) 95 } 96 97 func ExpirationSecondsToDuration(expirationSeconds int32) time.Duration { 98 return time.Duration(expirationSeconds) * time.Second 99 } 100 101 func get(client clientset.Interface, name string) (*certificatesv1.CertificateSigningRequest, error) { 102 v1req, v1err := client.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), name, metav1.GetOptions{}) 103 if v1err == nil || !apierrors.IsNotFound(v1err) { 104 return v1req, v1err 105 } 106 107 v1beta1req, v1beta1err := client.CertificatesV1beta1().CertificateSigningRequests().Get(context.TODO(), name, metav1.GetOptions{}) 108 if v1beta1err != nil { 109 return nil, v1beta1err 110 } 111 112 v1req = &certificatesv1.CertificateSigningRequest{ 113 ObjectMeta: v1beta1req.ObjectMeta, 114 Spec: certificatesv1.CertificateSigningRequestSpec{ 115 Request: v1beta1req.Spec.Request, 116 }, 117 } 118 if v1beta1req.Spec.SignerName != nil { 119 v1req.Spec.SignerName = *v1beta1req.Spec.SignerName 120 } 121 for _, usage := range v1beta1req.Spec.Usages { 122 v1req.Spec.Usages = append(v1req.Spec.Usages, certificatesv1.KeyUsage(usage)) 123 } 124 return v1req, nil 125 } 126 127 func create(client clientset.Interface, csr *certificatesv1.CertificateSigningRequest) (reqName string, reqUID types.UID, err error) { 128 // only attempt a create via v1 if we specified signerName and usages and are not using the legacy unknown signerName 129 if len(csr.Spec.Usages) > 0 && len(csr.Spec.SignerName) > 0 && csr.Spec.SignerName != "kubernetes.io/legacy-unknown" { 130 v1req, v1err := client.CertificatesV1().CertificateSigningRequests().Create(context.TODO(), csr, metav1.CreateOptions{}) 131 switch { 132 case v1err != nil && apierrors.IsNotFound(v1err): 133 // v1 CSR API was not found, continue to try v1beta1 134 135 case v1err != nil: 136 // other creation error 137 return "", "", v1err 138 139 default: 140 // success 141 return v1req.Name, v1req.UID, v1err 142 } 143 } 144 145 // convert relevant bits to v1beta1 146 v1beta1csr := &certificatesv1beta1.CertificateSigningRequest{ 147 ObjectMeta: csr.ObjectMeta, 148 Spec: certificatesv1beta1.CertificateSigningRequestSpec{ 149 SignerName: &csr.Spec.SignerName, 150 Request: csr.Spec.Request, 151 }, 152 } 153 for _, usage := range csr.Spec.Usages { 154 v1beta1csr.Spec.Usages = append(v1beta1csr.Spec.Usages, certificatesv1beta1.KeyUsage(usage)) 155 } 156 157 // create v1beta1 158 v1beta1req, v1beta1err := client.CertificatesV1beta1().CertificateSigningRequests().Create(context.TODO(), v1beta1csr, metav1.CreateOptions{}) 159 if v1beta1err != nil { 160 return "", "", v1beta1err 161 } 162 return v1beta1req.Name, v1beta1req.UID, nil 163 } 164 165 // WaitForCertificate waits for a certificate to be issued until timeout, or returns an error. 166 func WaitForCertificate(ctx context.Context, client clientset.Interface, reqName string, reqUID types.UID) (certData []byte, err error) { 167 fieldSelector := fields.OneTermEqualSelector("metadata.name", reqName).String() 168 169 var lw *cache.ListWatch 170 var obj runtime.Object 171 for { 172 // see if the v1 API is available 173 if _, err := client.CertificatesV1().CertificateSigningRequests().List(ctx, metav1.ListOptions{FieldSelector: fieldSelector}); err == nil { 174 // watch v1 objects 175 obj = &certificatesv1.CertificateSigningRequest{} 176 lw = &cache.ListWatch{ 177 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 178 options.FieldSelector = fieldSelector 179 return client.CertificatesV1().CertificateSigningRequests().List(ctx, options) 180 }, 181 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 182 options.FieldSelector = fieldSelector 183 return client.CertificatesV1().CertificateSigningRequests().Watch(ctx, options) 184 }, 185 } 186 break 187 } else { 188 klog.V(2).Infof("error fetching v1 certificate signing request: %v", err) 189 } 190 191 // return if we've timed out 192 if err := ctx.Err(); err != nil { 193 return nil, wait.ErrWaitTimeout 194 } 195 196 // see if the v1beta1 API is available 197 if _, err := client.CertificatesV1beta1().CertificateSigningRequests().List(ctx, metav1.ListOptions{FieldSelector: fieldSelector}); err == nil { 198 // watch v1beta1 objects 199 obj = &certificatesv1beta1.CertificateSigningRequest{} 200 lw = &cache.ListWatch{ 201 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 202 options.FieldSelector = fieldSelector 203 return client.CertificatesV1beta1().CertificateSigningRequests().List(ctx, options) 204 }, 205 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 206 options.FieldSelector = fieldSelector 207 return client.CertificatesV1beta1().CertificateSigningRequests().Watch(ctx, options) 208 }, 209 } 210 break 211 } else { 212 klog.V(2).Infof("error fetching v1beta1 certificate signing request: %v", err) 213 } 214 215 // return if we've timed out 216 if err := ctx.Err(); err != nil { 217 return nil, wait.ErrWaitTimeout 218 } 219 220 // wait and try again 221 time.Sleep(time.Second) 222 } 223 224 var issuedCertificate []byte 225 _, err = watchtools.UntilWithSync( 226 ctx, 227 lw, 228 obj, 229 nil, 230 func(event watch.Event) (bool, error) { 231 switch event.Type { 232 case watch.Modified, watch.Added: 233 case watch.Deleted: 234 return false, fmt.Errorf("csr %q was deleted", reqName) 235 default: 236 return false, nil 237 } 238 239 switch csr := event.Object.(type) { 240 case *certificatesv1.CertificateSigningRequest: 241 if csr.UID != reqUID { 242 return false, fmt.Errorf("csr %q changed UIDs", csr.Name) 243 } 244 approved := false 245 for _, c := range csr.Status.Conditions { 246 if c.Type == certificatesv1.CertificateDenied { 247 return false, fmt.Errorf("certificate signing request is denied, reason: %v, message: %v", c.Reason, c.Message) 248 } 249 if c.Type == certificatesv1.CertificateFailed { 250 return false, fmt.Errorf("certificate signing request failed, reason: %v, message: %v", c.Reason, c.Message) 251 } 252 if c.Type == certificatesv1.CertificateApproved { 253 approved = true 254 } 255 } 256 if approved { 257 if len(csr.Status.Certificate) > 0 { 258 klog.V(2).Infof("certificate signing request %s is issued", csr.Name) 259 issuedCertificate = csr.Status.Certificate 260 return true, nil 261 } 262 klog.V(2).Infof("certificate signing request %s is approved, waiting to be issued", csr.Name) 263 } 264 265 case *certificatesv1beta1.CertificateSigningRequest: 266 if csr.UID != reqUID { 267 return false, fmt.Errorf("csr %q changed UIDs", csr.Name) 268 } 269 approved := false 270 for _, c := range csr.Status.Conditions { 271 if c.Type == certificatesv1beta1.CertificateDenied { 272 return false, fmt.Errorf("certificate signing request is denied, reason: %v, message: %v", c.Reason, c.Message) 273 } 274 if c.Type == certificatesv1beta1.CertificateFailed { 275 return false, fmt.Errorf("certificate signing request failed, reason: %v, message: %v", c.Reason, c.Message) 276 } 277 if c.Type == certificatesv1beta1.CertificateApproved { 278 approved = true 279 } 280 } 281 if approved { 282 if len(csr.Status.Certificate) > 0 { 283 klog.V(2).Infof("certificate signing request %s is issued", csr.Name) 284 issuedCertificate = csr.Status.Certificate 285 return true, nil 286 } 287 klog.V(2).Infof("certificate signing request %s is approved, waiting to be issued", csr.Name) 288 } 289 290 default: 291 return false, fmt.Errorf("unexpected type received: %T", event.Object) 292 } 293 294 return false, nil 295 }, 296 ) 297 if err == wait.ErrWaitTimeout { 298 return nil, wait.ErrWaitTimeout 299 } 300 if err != nil { 301 return nil, formatError("cannot watch on the certificate signing request: %v", err) 302 } 303 304 return issuedCertificate, nil 305 } 306 307 // ensureCompatible ensures that a CSR object is compatible with an original CSR 308 func ensureCompatible(new, orig *certificatesv1.CertificateSigningRequest, privateKey interface{}) error { 309 newCSR, err := parseCSR(new.Spec.Request) 310 if err != nil { 311 return fmt.Errorf("unable to parse new csr: %v", err) 312 } 313 origCSR, err := parseCSR(orig.Spec.Request) 314 if err != nil { 315 return fmt.Errorf("unable to parse original csr: %v", err) 316 } 317 if !reflect.DeepEqual(newCSR.Subject, origCSR.Subject) { 318 return fmt.Errorf("csr subjects differ: new: %#v, orig: %#v", newCSR.Subject, origCSR.Subject) 319 } 320 if len(new.Spec.SignerName) > 0 && len(orig.Spec.SignerName) > 0 && new.Spec.SignerName != orig.Spec.SignerName { 321 return fmt.Errorf("csr signerNames differ: new %q, orig: %q", new.Spec.SignerName, orig.Spec.SignerName) 322 } 323 signer, ok := privateKey.(crypto.Signer) 324 if !ok { 325 return fmt.Errorf("privateKey is not a signer") 326 } 327 newCSR.PublicKey = signer.Public() 328 if err := newCSR.CheckSignature(); err != nil { 329 return fmt.Errorf("error validating signature new CSR against old key: %v", err) 330 } 331 if len(new.Status.Certificate) > 0 { 332 certs, err := certutil.ParseCertsPEM(new.Status.Certificate) 333 if err != nil { 334 return fmt.Errorf("error parsing signed certificate for CSR: %v", err) 335 } 336 now := time.Now() 337 for _, cert := range certs { 338 if now.After(cert.NotAfter) { 339 return fmt.Errorf("one of the certificates for the CSR has expired: %s", cert.NotAfter) 340 } 341 } 342 } 343 return nil 344 } 345 346 // formatError preserves the type of an API message but alters the message. Expects 347 // a single argument format string, and returns the wrapped error. 348 func formatError(format string, err error) error { 349 if s, ok := err.(errors.APIStatus); ok { 350 se := &errors.StatusError{ErrStatus: s.Status()} 351 se.ErrStatus.Message = fmt.Sprintf(format, se.ErrStatus.Message) 352 return se 353 } 354 return fmt.Errorf(format, err) 355 } 356 357 // parseCSR extracts the CSR from the API object and decodes it. 358 func parseCSR(pemData []byte) (*x509.CertificateRequest, error) { 359 // extract PEM from request object 360 block, _ := pem.Decode(pemData) 361 if block == nil || block.Type != "CERTIFICATE REQUEST" { 362 return nil, fmt.Errorf("PEM block type must be CERTIFICATE REQUEST") 363 } 364 return x509.ParseCertificateRequest(block.Bytes) 365 }