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