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

     1  package testutils
     2  
     3  import (
     4  	"crypto/tls"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net"
     8  	"net/http"
     9  	"net/url"
    10  	"path/filepath"
    11  	"strconv"
    12  	"sync"
    13  	"sync/atomic"
    14  
    15  	"github.com/cloudflare/cfssl/api"
    16  	"github.com/cloudflare/cfssl/config"
    17  	cfsslerrors "github.com/cloudflare/cfssl/errors"
    18  	"github.com/cloudflare/cfssl/signer"
    19  	"github.com/docker/swarmkit/ca"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  var crossSignPolicy = config.SigningProfile{
    24  	Usage: []string{"cert sign", "crl sign"},
    25  	// we don't want the intermediate to last for very long
    26  	Expiry:       ca.DefaultNodeCertExpiration,
    27  	Backdate:     ca.CertBackdate,
    28  	CAConstraint: config.CAConstraint{IsCA: true},
    29  	ExtensionWhitelist: map[string]bool{
    30  		ca.BasicConstraintsOID.String(): true,
    31  	},
    32  }
    33  
    34  // NewExternalSigningServer creates and runs a new ExternalSigningServer which
    35  // uses the given rootCA to sign node certificates. A server key and cert are
    36  // generated and saved into the given basedir and then a TLS listener is
    37  // started on a random available port. On success, an HTTPS server will be
    38  // running in a separate goroutine. The URL of the singing endpoint is
    39  // available in the returned *ExternalSignerServer value. Calling the Close()
    40  // method will stop the server.
    41  func NewExternalSigningServer(rootCA ca.RootCA, basedir string) (*ExternalSigningServer, error) {
    42  	serverCN := "external-ca-example-server"
    43  	serverOU := "localhost" // Make a valid server cert for localhost.
    44  
    45  	s, err := rootCA.Signer()
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	// create our own copy of the local signer so we don't mutate the rootCA's signer as we enable and disable CA signing
    50  	copiedSigner := *s
    51  
    52  	// Create TLS credentials for the external CA server which we will run.
    53  	serverPaths := ca.CertPaths{
    54  		Cert: filepath.Join(basedir, "server.crt"),
    55  		Key:  filepath.Join(basedir, "server.key"),
    56  	}
    57  	serverCert, _, err := rootCA.IssueAndSaveNewCertificates(ca.NewKeyReadWriter(serverPaths, nil, nil), serverCN, serverOU, "")
    58  	if err != nil {
    59  		return nil, errors.Wrap(err, "unable to get TLS server certificate")
    60  	}
    61  
    62  	serverTLSConfig := &tls.Config{
    63  		Certificates: []tls.Certificate{*serverCert},
    64  		ClientAuth:   tls.RequireAndVerifyClientCert,
    65  		ClientCAs:    rootCA.Pool,
    66  	}
    67  
    68  	tlsListener, err := tls.Listen("tcp", "localhost:0", serverTLSConfig)
    69  	if err != nil {
    70  		return nil, errors.Wrap(err, "unable to create TLS connection listener")
    71  	}
    72  
    73  	assignedPort := tlsListener.Addr().(*net.TCPAddr).Port
    74  
    75  	signURL := url.URL{
    76  		Scheme: "https",
    77  		Host:   net.JoinHostPort("localhost", strconv.Itoa(assignedPort)),
    78  		Path:   "/sign",
    79  	}
    80  
    81  	ess := &ExternalSigningServer{
    82  		listener: tlsListener,
    83  		URL:      signURL.String(),
    84  	}
    85  
    86  	mux := http.NewServeMux()
    87  	handler := &signHandler{
    88  		numIssued:   &ess.NumIssued,
    89  		localSigner: &copiedSigner,
    90  		origPolicy:  copiedSigner.Policy(),
    91  		flaky:       &ess.flaky,
    92  	}
    93  	mux.Handle(signURL.Path, handler)
    94  	ess.handler = handler
    95  
    96  	server := &http.Server{
    97  		Handler: mux,
    98  	}
    99  
   100  	go server.Serve(tlsListener)
   101  
   102  	return ess, nil
   103  }
   104  
   105  // ExternalSigningServer runs an HTTPS server with an endpoint at a specified
   106  // URL which signs node certificate requests from a swarm manager client.
   107  type ExternalSigningServer struct {
   108  	listener  net.Listener
   109  	NumIssued uint64
   110  	URL       string
   111  	flaky     uint32
   112  	handler   *signHandler
   113  }
   114  
   115  // Stop stops this signing server by closing the underlying TCP/TLS listener.
   116  func (ess *ExternalSigningServer) Stop() error {
   117  	return ess.listener.Close()
   118  }
   119  
   120  // Flake makes the signing server return HTTP 500 errors.
   121  func (ess *ExternalSigningServer) Flake() {
   122  	atomic.StoreUint32(&ess.flaky, 1)
   123  }
   124  
   125  // Deflake restores normal operation after a call to Flake.
   126  func (ess *ExternalSigningServer) Deflake() {
   127  	atomic.StoreUint32(&ess.flaky, 0)
   128  }
   129  
   130  // EnableCASigning updates the root CA signer to be able to sign CAs
   131  func (ess *ExternalSigningServer) EnableCASigning() error {
   132  	ess.handler.mu.Lock()
   133  	defer ess.handler.mu.Unlock()
   134  
   135  	copied := *ess.handler.origPolicy
   136  	if copied.Profiles == nil {
   137  		copied.Profiles = make(map[string]*config.SigningProfile)
   138  	}
   139  	copied.Profiles[ca.ExternalCrossSignProfile] = &crossSignPolicy
   140  
   141  	ess.handler.localSigner.SetPolicy(&copied)
   142  	return nil
   143  }
   144  
   145  // DisableCASigning prevents the server from being able to sign CA certificates
   146  func (ess *ExternalSigningServer) DisableCASigning() {
   147  	ess.handler.mu.Lock()
   148  	defer ess.handler.mu.Unlock()
   149  	ess.handler.localSigner.SetPolicy(ess.handler.origPolicy)
   150  }
   151  
   152  type signHandler struct {
   153  	mu          sync.Mutex
   154  	numIssued   *uint64
   155  	flaky       *uint32
   156  	localSigner *ca.LocalSigner
   157  	origPolicy  *config.Signing
   158  }
   159  
   160  func (h *signHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   161  	if atomic.LoadUint32(h.flaky) == 1 {
   162  		w.WriteHeader(http.StatusInternalServerError)
   163  	}
   164  
   165  	// Check client authentication via mutual TLS.
   166  	if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
   167  		cfsslErr := cfsslerrors.New(cfsslerrors.APIClientError, cfsslerrors.AuthenticationFailure)
   168  		errResponse := api.NewErrorResponse("must authenticate sign request with mutual TLS", cfsslErr.ErrorCode)
   169  		json.NewEncoder(w).Encode(errResponse)
   170  		return
   171  	}
   172  
   173  	clientSub := r.TLS.PeerCertificates[0].Subject
   174  
   175  	// The client certificate OU should be for a swarm manager.
   176  	if len(clientSub.OrganizationalUnit) == 0 || clientSub.OrganizationalUnit[0] != ca.ManagerRole {
   177  		cfsslErr := cfsslerrors.New(cfsslerrors.APIClientError, cfsslerrors.AuthenticationFailure)
   178  		errResponse := api.NewErrorResponse(fmt.Sprintf("client certificate OU must be %q", ca.ManagerRole), cfsslErr.ErrorCode)
   179  		json.NewEncoder(w).Encode(errResponse)
   180  		return
   181  	}
   182  
   183  	// The client certificate must have an Org.
   184  	if len(clientSub.Organization) == 0 {
   185  		cfsslErr := cfsslerrors.New(cfsslerrors.APIClientError, cfsslerrors.AuthenticationFailure)
   186  		errResponse := api.NewErrorResponse("client certificate must have an Organization", cfsslErr.ErrorCode)
   187  		json.NewEncoder(w).Encode(errResponse)
   188  		return
   189  	}
   190  	clientOrg := clientSub.Organization[0]
   191  
   192  	// Decode the certificate signing request.
   193  	var signReq signer.SignRequest
   194  	if err := json.NewDecoder(r.Body).Decode(&signReq); err != nil {
   195  		cfsslErr := cfsslerrors.New(cfsslerrors.APIClientError, cfsslerrors.JSONError)
   196  		errResponse := api.NewErrorResponse(fmt.Sprintf("unable to decode sign request: %s", err), cfsslErr.ErrorCode)
   197  		json.NewEncoder(w).Encode(errResponse)
   198  		return
   199  	}
   200  
   201  	// The signReq should have additional subject info.
   202  	reqSub := signReq.Subject
   203  	if reqSub == nil {
   204  		cfsslErr := cfsslerrors.New(cfsslerrors.CSRError, cfsslerrors.BadRequest)
   205  		errResponse := api.NewErrorResponse("sign request must contain a subject field", cfsslErr.ErrorCode)
   206  		json.NewEncoder(w).Encode(errResponse)
   207  		return
   208  	}
   209  
   210  	if signReq.Profile != ca.ExternalCrossSignProfile {
   211  		// The client's Org should match the Org in the sign request subject.
   212  		if len(reqSub.Name().Organization) == 0 || reqSub.Name().Organization[0] != clientOrg {
   213  			cfsslErr := cfsslerrors.New(cfsslerrors.CSRError, cfsslerrors.BadRequest)
   214  			errResponse := api.NewErrorResponse("sign request subject org does not match client certificate org", cfsslErr.ErrorCode)
   215  			json.NewEncoder(w).Encode(errResponse)
   216  			return
   217  		}
   218  	}
   219  
   220  	// Sign the requested certificate.
   221  	certPEM, err := h.localSigner.Sign(signReq)
   222  	if err != nil {
   223  		cfsslErr := cfsslerrors.New(cfsslerrors.APIClientError, cfsslerrors.ServerRequestFailed)
   224  		errResponse := api.NewErrorResponse(fmt.Sprintf("unable to sign requested certificate: %s", err), cfsslErr.ErrorCode)
   225  		json.NewEncoder(w).Encode(errResponse)
   226  		return
   227  	}
   228  
   229  	result := map[string]string{
   230  		"certificate": string(certPEM),
   231  	}
   232  
   233  	// Increment the number of certs issued.
   234  	atomic.AddUint64(h.numIssued, 1)
   235  
   236  	// Return a successful JSON response.
   237  	json.NewEncoder(w).Encode(api.NewSuccessResponse(result))
   238  }