...

Source file src/k8s.io/client-go/util/certificate/csr/csr.go

Documentation: k8s.io/client-go/util/certificate/csr

     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  }
   365  

View as plain text