github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/ca/external.go (about)

     1  package ca
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	cryptorand "crypto/rand"
     7  	"crypto/tls"
     8  	"crypto/x509"
     9  	"encoding/hex"
    10  	"encoding/json"
    11  	"encoding/pem"
    12  	"io"
    13  	"io/ioutil"
    14  	"net/http"
    15  	"sync"
    16  	"time"
    17  
    18  	"github.com/cloudflare/cfssl/api"
    19  	"github.com/cloudflare/cfssl/config"
    20  	"github.com/cloudflare/cfssl/csr"
    21  	"github.com/cloudflare/cfssl/signer"
    22  	"github.com/docker/swarmkit/log"
    23  	"github.com/pkg/errors"
    24  	"github.com/sirupsen/logrus"
    25  	"golang.org/x/net/context/ctxhttp"
    26  )
    27  
    28  const (
    29  	// ExternalCrossSignProfile is the profile that we will be sending cross-signing CSR sign requests with
    30  	ExternalCrossSignProfile = "CA"
    31  
    32  	// CertificateMaxSize is the maximum expected size of a certificate.
    33  	// While there is no specced upper limit to the size of an x509 certificate in PEM format,
    34  	// one with a ridiculous RSA key size (16384) and 26 256-character DNS SAN fields is about 14k.
    35  	// While there is no upper limit on the length of certificate chains, long chains are impractical.
    36  	// To be conservative, and to also account for external CA certificate responses in JSON format
    37  	// from CFSSL, we'll set the max to be 256KiB.
    38  	CertificateMaxSize int64 = 256 << 10
    39  )
    40  
    41  // ErrNoExternalCAURLs is an error used it indicate that an ExternalCA is
    42  // configured with no URLs to which it can proxy certificate signing requests.
    43  var ErrNoExternalCAURLs = errors.New("no external CA URLs")
    44  
    45  // ExternalCA is able to make certificate signing requests to one of a list
    46  // remote CFSSL API endpoints.
    47  type ExternalCA struct {
    48  	ExternalRequestTimeout time.Duration
    49  
    50  	mu            sync.Mutex
    51  	intermediates []byte
    52  	urls          []string
    53  	client        *http.Client
    54  }
    55  
    56  // NewExternalCATLSConfig takes a TLS certificate and root pool and returns a TLS config that can be updated
    57  // without killing existing connections
    58  func NewExternalCATLSConfig(certs []tls.Certificate, rootPool *x509.CertPool) *tls.Config {
    59  	return &tls.Config{
    60  		Certificates: certs,
    61  		RootCAs:      rootPool,
    62  		MinVersion:   tls.VersionTLS12,
    63  	}
    64  }
    65  
    66  // NewExternalCA creates a new ExternalCA which uses the given tlsConfig to
    67  // authenticate to any of the given URLS of CFSSL API endpoints.
    68  func NewExternalCA(intermediates []byte, tlsConfig *tls.Config, urls ...string) *ExternalCA {
    69  	return &ExternalCA{
    70  		ExternalRequestTimeout: 5 * time.Second,
    71  		intermediates:          intermediates,
    72  		urls:                   urls,
    73  		client: &http.Client{
    74  			Transport: &http.Transport{
    75  				TLSClientConfig: tlsConfig,
    76  			},
    77  		},
    78  	}
    79  }
    80  
    81  // UpdateTLSConfig updates the HTTP Client for this ExternalCA by creating
    82  // a new client which uses the given tlsConfig.
    83  func (eca *ExternalCA) UpdateTLSConfig(tlsConfig *tls.Config) {
    84  	eca.mu.Lock()
    85  	defer eca.mu.Unlock()
    86  
    87  	eca.client = &http.Client{
    88  		Transport: &http.Transport{
    89  			TLSClientConfig: tlsConfig,
    90  		},
    91  	}
    92  }
    93  
    94  // UpdateURLs updates the list of CSR API endpoints by setting it to the given urls.
    95  func (eca *ExternalCA) UpdateURLs(urls ...string) {
    96  	eca.mu.Lock()
    97  	defer eca.mu.Unlock()
    98  
    99  	eca.urls = urls
   100  }
   101  
   102  // Sign signs a new certificate by proxying the given certificate signing
   103  // request to an external CFSSL API server.
   104  func (eca *ExternalCA) Sign(ctx context.Context, req signer.SignRequest) (cert []byte, err error) {
   105  	// Get the current HTTP client and list of URLs in a small critical
   106  	// section. We will use these to make certificate signing requests.
   107  	eca.mu.Lock()
   108  	urls := eca.urls
   109  	client := eca.client
   110  	intermediates := eca.intermediates
   111  	eca.mu.Unlock()
   112  
   113  	if len(urls) == 0 {
   114  		return nil, ErrNoExternalCAURLs
   115  	}
   116  
   117  	csrJSON, err := json.Marshal(req)
   118  	if err != nil {
   119  		return nil, errors.Wrap(err, "unable to JSON-encode CFSSL signing request")
   120  	}
   121  
   122  	// Try each configured proxy URL. Return after the first success. If
   123  	// all fail then the last error will be returned.
   124  	for _, url := range urls {
   125  		requestCtx, cancel := context.WithTimeout(ctx, eca.ExternalRequestTimeout)
   126  		cert, err = makeExternalSignRequest(requestCtx, client, url, csrJSON)
   127  		cancel()
   128  		if err == nil {
   129  			return append(cert, intermediates...), err
   130  		}
   131  		log.G(ctx).Debugf("unable to proxy certificate signing request to %s: %s", url, err)
   132  	}
   133  
   134  	return nil, err
   135  }
   136  
   137  // CrossSignRootCA takes a RootCA object, generates a CA CSR, sends a signing request with the CA CSR to the external
   138  // CFSSL API server in order to obtain a cross-signed root
   139  func (eca *ExternalCA) CrossSignRootCA(ctx context.Context, rca RootCA) ([]byte, error) {
   140  	// ExtractCertificateRequest generates a new key request, and we want to continue to use the old
   141  	// key.  However, ExtractCertificateRequest will also convert the pkix.Name to csr.Name, which we
   142  	// need in order to generate a signing request
   143  	rcaSigner, err := rca.Signer()
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	rootCert := rcaSigner.parsedCert
   148  	cfCSRObj := csr.ExtractCertificateRequest(rootCert)
   149  
   150  	der, err := x509.CreateCertificateRequest(cryptorand.Reader, &x509.CertificateRequest{
   151  		RawSubjectPublicKeyInfo: rootCert.RawSubjectPublicKeyInfo,
   152  		RawSubject:              rootCert.RawSubject,
   153  		PublicKeyAlgorithm:      rootCert.PublicKeyAlgorithm,
   154  		Subject:                 rootCert.Subject,
   155  		Extensions:              rootCert.Extensions,
   156  		DNSNames:                rootCert.DNSNames,
   157  		EmailAddresses:          rootCert.EmailAddresses,
   158  		IPAddresses:             rootCert.IPAddresses,
   159  	}, rcaSigner.cryptoSigner)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  	req := signer.SignRequest{
   164  		Request: string(pem.EncodeToMemory(&pem.Block{
   165  			Type:  "CERTIFICATE REQUEST",
   166  			Bytes: der,
   167  		})),
   168  		Subject: &signer.Subject{
   169  			CN:    rootCert.Subject.CommonName,
   170  			Names: cfCSRObj.Names,
   171  		},
   172  		Profile: ExternalCrossSignProfile,
   173  	}
   174  	// cfssl actually ignores non subject alt name extensions in the CSR, so we have to add the CA extension in the signing
   175  	// request as well
   176  	for _, ext := range rootCert.Extensions {
   177  		if ext.Id.Equal(BasicConstraintsOID) {
   178  			req.Extensions = append(req.Extensions, signer.Extension{
   179  				ID:       config.OID(ext.Id),
   180  				Critical: ext.Critical,
   181  				Value:    hex.EncodeToString(ext.Value),
   182  			})
   183  		}
   184  	}
   185  	return eca.Sign(ctx, req)
   186  }
   187  
   188  func makeExternalSignRequest(ctx context.Context, client *http.Client, url string, csrJSON []byte) (cert []byte, err error) {
   189  	resp, err := ctxhttp.Post(ctx, client, url, "application/json", bytes.NewReader(csrJSON))
   190  	if err != nil {
   191  		return nil, recoverableErr{err: errors.Wrap(err, "unable to perform certificate signing request")}
   192  	}
   193  	defer resp.Body.Close()
   194  
   195  	b := io.LimitReader(resp.Body, CertificateMaxSize)
   196  	body, err := ioutil.ReadAll(b)
   197  	if err != nil {
   198  		return nil, recoverableErr{err: errors.Wrap(err, "unable to read CSR response body")}
   199  	}
   200  
   201  	if resp.StatusCode != http.StatusOK {
   202  		return nil, recoverableErr{err: errors.Errorf("unexpected status code in CSR response: %d - %s", resp.StatusCode, string(body))}
   203  	}
   204  
   205  	var apiResponse api.Response
   206  	if err := json.Unmarshal(body, &apiResponse); err != nil {
   207  		logrus.Debugf("unable to JSON-parse CFSSL API response body: %s", string(body))
   208  		return nil, recoverableErr{err: errors.Wrap(err, "unable to parse JSON response")}
   209  	}
   210  
   211  	if !apiResponse.Success || apiResponse.Result == nil {
   212  		if len(apiResponse.Errors) > 0 {
   213  			return nil, errors.Errorf("response errors: %v", apiResponse.Errors)
   214  		}
   215  
   216  		return nil, errors.New("certificate signing request failed")
   217  	}
   218  
   219  	result, ok := apiResponse.Result.(map[string]interface{})
   220  	if !ok {
   221  		return nil, errors.Errorf("invalid result type: %T", apiResponse.Result)
   222  	}
   223  
   224  	certPEM, ok := result["certificate"].(string)
   225  	if !ok {
   226  		return nil, errors.Errorf("invalid result certificate field type: %T", result["certificate"])
   227  	}
   228  
   229  	return []byte(certPEM), nil
   230  }