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  }