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

     1  package testutils
     2  
     3  import (
     4  	"context"
     5  	"crypto"
     6  	cryptorand "crypto/rand"
     7  	"crypto/tls"
     8  	"crypto/x509"
     9  	"encoding/pem"
    10  	"io/ioutil"
    11  	"net"
    12  	"os"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	cfcsr "github.com/cloudflare/cfssl/csr"
    18  	"github.com/cloudflare/cfssl/helpers"
    19  	"github.com/cloudflare/cfssl/initca"
    20  	"github.com/docker/swarmkit/api"
    21  	"github.com/docker/swarmkit/ca"
    22  	"github.com/docker/swarmkit/ca/pkcs8"
    23  	"github.com/docker/swarmkit/connectionbroker"
    24  	"github.com/docker/swarmkit/identity"
    25  	"github.com/docker/swarmkit/ioutils"
    26  	"github.com/docker/swarmkit/log"
    27  	"github.com/docker/swarmkit/manager/state/store"
    28  	stateutils "github.com/docker/swarmkit/manager/state/testutils"
    29  	"github.com/docker/swarmkit/remotes"
    30  	"github.com/sirupsen/logrus"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  	"google.golang.org/grpc"
    34  	"google.golang.org/grpc/credentials"
    35  )
    36  
    37  // TestCA is a structure that encapsulates everything needed to test a CA Server
    38  type TestCA struct {
    39  	RootCA                      ca.RootCA
    40  	ExternalSigningServer       *ExternalSigningServer
    41  	MemoryStore                 *store.MemoryStore
    42  	Addr, TempDir, Organization string
    43  	Paths                       *ca.SecurityConfigPaths
    44  	Server                      *grpc.Server
    45  	ServingSecurityConfig       *ca.SecurityConfig
    46  	CAServer                    *ca.Server
    47  	Context                     context.Context
    48  	NodeCAClients               []api.NodeCAClient
    49  	CAClients                   []api.CAClient
    50  	Conns                       []*grpc.ClientConn
    51  	WorkerToken                 string
    52  	ManagerToken                string
    53  	ConnBroker                  *connectionbroker.Broker
    54  	KeyReadWriter               *ca.KeyReadWriter
    55  	ctxCancel                   func()
    56  	securityConfigCleanups      []func() error
    57  }
    58  
    59  // Stop cleans up after TestCA
    60  func (tc *TestCA) Stop() {
    61  	tc.ctxCancel()
    62  	for _, qClose := range tc.securityConfigCleanups {
    63  		qClose()
    64  	}
    65  	os.RemoveAll(tc.TempDir)
    66  	for _, conn := range tc.Conns {
    67  		conn.Close()
    68  	}
    69  	if tc.ExternalSigningServer != nil {
    70  		tc.ExternalSigningServer.Stop()
    71  	}
    72  	tc.CAServer.Stop()
    73  	tc.Server.Stop()
    74  	tc.MemoryStore.Close()
    75  }
    76  
    77  // NewNodeConfig returns security config for a new node, given a role
    78  func (tc *TestCA) NewNodeConfig(role string) (*ca.SecurityConfig, error) {
    79  	return tc.NewNodeConfigOrg(role, tc.Organization)
    80  }
    81  
    82  // WriteNewNodeConfig returns security config for a new node, given a role
    83  // saving the generated key and certificates to disk
    84  func (tc *TestCA) WriteNewNodeConfig(role string) (*ca.SecurityConfig, error) {
    85  	return tc.NewNodeConfigOrg(role, tc.Organization)
    86  }
    87  
    88  // NewNodeConfigOrg returns security config for a new node, given a role and an org
    89  func (tc *TestCA) NewNodeConfigOrg(role, org string) (*ca.SecurityConfig, error) {
    90  	withNonSigningRoot := tc.ExternalSigningServer != nil
    91  	s, qClose, err := genSecurityConfig(tc.MemoryStore, tc.RootCA, tc.KeyReadWriter, role, org, tc.TempDir, withNonSigningRoot)
    92  	if err != nil {
    93  		tc.securityConfigCleanups = append(tc.securityConfigCleanups, qClose)
    94  	}
    95  	return s, err
    96  }
    97  
    98  // External controls whether or not NewTestCA() will create a TestCA server
    99  // configured to use an external signer or not.
   100  var External bool
   101  
   102  // NewTestCA is a helper method that creates a TestCA and a bunch of default
   103  // connections and security configs.
   104  func NewTestCA(t *testing.T, krwGenerators ...func(ca.CertPaths) *ca.KeyReadWriter) *TestCA {
   105  	tempdir, err := ioutil.TempDir("", "swarm-ca-test-")
   106  	if t != nil {
   107  		require.NoError(t, err)
   108  	}
   109  
   110  	cert, key, err := CreateRootCertAndKey("swarm-test-CA")
   111  	if t != nil {
   112  		require.NoError(t, err)
   113  	}
   114  	apiRootCA := api.RootCA{
   115  		CACert: cert,
   116  		CAKey:  key,
   117  	}
   118  
   119  	return newTestCA(t, tempdir, apiRootCA, krwGenerators, false)
   120  }
   121  
   122  // NewFIPSTestCA is a helper method that creates a mandatory fips TestCA and a bunch of default
   123  // connections and security configs.
   124  func NewFIPSTestCA(t *testing.T) *TestCA {
   125  	tempdir, err := ioutil.TempDir("", "swarm-ca-test-")
   126  	if t != nil {
   127  		require.NoError(t, err)
   128  	}
   129  
   130  	cert, key, err := CreateRootCertAndKey("swarm-test-CA")
   131  	if t != nil {
   132  		require.NoError(t, err)
   133  	}
   134  	apiRootCA := api.RootCA{
   135  		CACert: cert,
   136  		CAKey:  key,
   137  	}
   138  
   139  	return newTestCA(t, tempdir, apiRootCA, nil, true)
   140  }
   141  
   142  // NewTestCAFromAPIRootCA is a helper method that creates a TestCA and a bunch of default
   143  // connections and security configs, given a temp directory and an api.RootCA to use for creating
   144  // a cluster and for signing.
   145  func NewTestCAFromAPIRootCA(t *testing.T, tempBaseDir string, apiRootCA api.RootCA, krwGenerators []func(ca.CertPaths) *ca.KeyReadWriter) *TestCA {
   146  	return newTestCA(t, tempBaseDir, apiRootCA, krwGenerators, false)
   147  }
   148  
   149  func newTestCA(t *testing.T, tempBaseDir string, apiRootCA api.RootCA, krwGenerators []func(ca.CertPaths) *ca.KeyReadWriter, fips bool) *TestCA {
   150  	s := store.NewMemoryStore(&stateutils.MockProposer{})
   151  
   152  	paths := ca.NewConfigPaths(tempBaseDir)
   153  	organization := identity.NewID()
   154  	if fips {
   155  		organization = "FIPS." + organization
   156  	}
   157  
   158  	var (
   159  		externalSigningServer *ExternalSigningServer
   160  		externalCAs           []*api.ExternalCA
   161  		err                   error
   162  		rootCA                ca.RootCA
   163  	)
   164  
   165  	if apiRootCA.RootRotation != nil {
   166  		rootCA, err = ca.NewRootCA(
   167  			apiRootCA.CACert, apiRootCA.RootRotation.CACert, apiRootCA.RootRotation.CAKey, ca.DefaultNodeCertExpiration, apiRootCA.RootRotation.CrossSignedCACert)
   168  	} else {
   169  		rootCA, err = ca.NewRootCA(
   170  			apiRootCA.CACert, apiRootCA.CACert, apiRootCA.CAKey, ca.DefaultNodeCertExpiration, nil)
   171  
   172  	}
   173  	if t != nil {
   174  		require.NoError(t, err)
   175  	}
   176  
   177  	// Write the root certificate to disk, using decent permissions
   178  	err = ioutils.AtomicWriteFile(paths.RootCA.Cert, apiRootCA.CACert, 0644)
   179  	if t != nil {
   180  		require.NoError(t, err)
   181  	}
   182  
   183  	if External {
   184  		// Start the CA API server - ensure that the external server doesn't have any intermediates
   185  		var extRootCA ca.RootCA
   186  		if apiRootCA.RootRotation != nil {
   187  			extRootCA, err = ca.NewRootCA(
   188  				apiRootCA.RootRotation.CACert, apiRootCA.RootRotation.CACert, apiRootCA.RootRotation.CAKey, ca.DefaultNodeCertExpiration, nil)
   189  			// remove the key from the API root CA so that once the CA server starts up, it won't have a local signer
   190  			apiRootCA.RootRotation.CAKey = nil
   191  		} else {
   192  			extRootCA, err = ca.NewRootCA(
   193  				apiRootCA.CACert, apiRootCA.CACert, apiRootCA.CAKey, ca.DefaultNodeCertExpiration, nil)
   194  			// remove the key from the API root CA so that once the CA server starts up, it won't have a local signer
   195  			apiRootCA.CAKey = nil
   196  		}
   197  		if t != nil {
   198  			require.NoError(t, err)
   199  		}
   200  
   201  		externalSigningServer, err = NewExternalSigningServer(extRootCA, tempBaseDir)
   202  		if t != nil {
   203  			require.NoError(t, err)
   204  		}
   205  
   206  		externalCAs = []*api.ExternalCA{
   207  			{
   208  				Protocol: api.ExternalCA_CAProtocolCFSSL,
   209  				URL:      externalSigningServer.URL,
   210  				CACert:   extRootCA.Certs,
   211  			},
   212  		}
   213  	}
   214  
   215  	krw := ca.NewKeyReadWriter(paths.Node, nil, nil)
   216  	if len(krwGenerators) > 0 {
   217  		krw = krwGenerators[0](paths.Node)
   218  	}
   219  
   220  	managerConfig, qClose1, err := genSecurityConfig(s, rootCA, krw, ca.ManagerRole, organization, "", External)
   221  	if t != nil {
   222  		assert.NoError(t, err)
   223  	}
   224  
   225  	managerDiffOrgConfig, qClose2, err := genSecurityConfig(s, rootCA, krw, ca.ManagerRole, "swarm-test-org-2", "", External)
   226  	if t != nil {
   227  		assert.NoError(t, err)
   228  	}
   229  
   230  	workerConfig, qClose3, err := genSecurityConfig(s, rootCA, krw, ca.WorkerRole, organization, "", External)
   231  	if t != nil {
   232  		assert.NoError(t, err)
   233  	}
   234  
   235  	l, err := net.Listen("tcp", "127.0.0.1:0")
   236  	if t != nil {
   237  		assert.NoError(t, err)
   238  	}
   239  
   240  	baseOpts := []grpc.DialOption{grpc.WithTimeout(10 * time.Second)}
   241  	insecureClientOpts := append(baseOpts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})))
   242  	clientOpts := append(baseOpts, grpc.WithTransportCredentials(workerConfig.ClientTLSCreds))
   243  	managerOpts := append(baseOpts, grpc.WithTransportCredentials(managerConfig.ClientTLSCreds))
   244  	managerDiffOrgOpts := append(baseOpts, grpc.WithTransportCredentials(managerDiffOrgConfig.ClientTLSCreds))
   245  
   246  	conn1, err := grpc.Dial(l.Addr().String(), insecureClientOpts...)
   247  	if t != nil {
   248  		assert.NoError(t, err)
   249  	}
   250  
   251  	conn2, err := grpc.Dial(l.Addr().String(), clientOpts...)
   252  	if t != nil {
   253  		assert.NoError(t, err)
   254  	}
   255  
   256  	conn3, err := grpc.Dial(l.Addr().String(), managerOpts...)
   257  	if t != nil {
   258  		assert.NoError(t, err)
   259  	}
   260  
   261  	conn4, err := grpc.Dial(l.Addr().String(), managerDiffOrgOpts...)
   262  	if t != nil {
   263  		assert.NoError(t, err)
   264  	}
   265  
   266  	serverOpts := []grpc.ServerOption{grpc.Creds(managerConfig.ServerTLSCreds)}
   267  	grpcServer := grpc.NewServer(serverOpts...)
   268  
   269  	clusterObj := createClusterObject(t, s, organization, apiRootCA, &rootCA, externalCAs...)
   270  
   271  	caServer := ca.NewServer(s, managerConfig)
   272  	caServer.SetReconciliationRetryInterval(50 * time.Millisecond)
   273  	caServer.SetRootReconciliationInterval(50 * time.Millisecond)
   274  	api.RegisterCAServer(grpcServer, caServer)
   275  	api.RegisterNodeCAServer(grpcServer, caServer)
   276  
   277  	fields := logrus.Fields{"testHasExternalCA": External}
   278  	if t != nil {
   279  		fields["testname"] = t.Name()
   280  	}
   281  	ctx, ctxCancel := context.WithCancel(log.WithLogger(context.Background(), log.L.WithFields(fields)))
   282  
   283  	go grpcServer.Serve(l)
   284  	go caServer.Run(ctx)
   285  
   286  	// Wait for caServer to be ready to serve
   287  	<-caServer.Ready()
   288  	remotes := remotes.NewRemotes(api.Peer{Addr: l.Addr().String()})
   289  
   290  	caClients := []api.CAClient{api.NewCAClient(conn1), api.NewCAClient(conn2), api.NewCAClient(conn3)}
   291  	nodeCAClients := []api.NodeCAClient{api.NewNodeCAClient(conn1), api.NewNodeCAClient(conn2), api.NewNodeCAClient(conn3), api.NewNodeCAClient(conn4)}
   292  	conns := []*grpc.ClientConn{conn1, conn2, conn3, conn4}
   293  
   294  	return &TestCA{
   295  		RootCA:                 rootCA,
   296  		ExternalSigningServer:  externalSigningServer,
   297  		MemoryStore:            s,
   298  		TempDir:                tempBaseDir,
   299  		Organization:           organization,
   300  		Paths:                  paths,
   301  		Context:                ctx,
   302  		CAClients:              caClients,
   303  		NodeCAClients:          nodeCAClients,
   304  		Conns:                  conns,
   305  		Addr:                   l.Addr().String(),
   306  		Server:                 grpcServer,
   307  		ServingSecurityConfig:  managerConfig,
   308  		CAServer:               caServer,
   309  		WorkerToken:            clusterObj.RootCA.JoinTokens.Worker,
   310  		ManagerToken:           clusterObj.RootCA.JoinTokens.Manager,
   311  		ConnBroker:             connectionbroker.New(remotes),
   312  		KeyReadWriter:          krw,
   313  		ctxCancel:              ctxCancel,
   314  		securityConfigCleanups: []func() error{qClose1, qClose2, qClose3},
   315  	}
   316  }
   317  
   318  func createNode(s *store.MemoryStore, nodeID, role string, csr, cert []byte) error {
   319  	apiRole, _ := ca.FormatRole(role)
   320  
   321  	err := s.Update(func(tx store.Tx) error {
   322  		node := &api.Node{
   323  			ID: nodeID,
   324  			Certificate: api.Certificate{
   325  				CSR:  csr,
   326  				CN:   nodeID,
   327  				Role: apiRole,
   328  				Status: api.IssuanceStatus{
   329  					State: api.IssuanceStateIssued,
   330  				},
   331  				Certificate: cert,
   332  			},
   333  			Spec: api.NodeSpec{
   334  				DesiredRole: apiRole,
   335  				Membership:  api.NodeMembershipAccepted,
   336  			},
   337  			Role: apiRole,
   338  		}
   339  
   340  		return store.CreateNode(tx, node)
   341  	})
   342  
   343  	return err
   344  }
   345  
   346  func genSecurityConfig(s *store.MemoryStore, rootCA ca.RootCA, krw *ca.KeyReadWriter, role, org, tmpDir string, nonSigningRoot bool) (*ca.SecurityConfig, func() error, error) {
   347  	req := &cfcsr.CertificateRequest{
   348  		KeyRequest: cfcsr.NewBasicKeyRequest(),
   349  	}
   350  
   351  	csr, key, err := cfcsr.ParseRequest(req)
   352  	if err != nil {
   353  		return nil, nil, err
   354  	}
   355  
   356  	key, err = pkcs8.ConvertECPrivateKeyPEM(key)
   357  	if err != nil {
   358  		return nil, nil, err
   359  	}
   360  
   361  	// Obtain a signed Certificate
   362  	nodeID := identity.NewID()
   363  
   364  	certChain, err := rootCA.ParseValidateAndSignCSR(csr, nodeID, role, org)
   365  	if err != nil {
   366  		return nil, nil, err
   367  	}
   368  
   369  	// If we were instructed to persist the files
   370  	if tmpDir != "" {
   371  		paths := ca.NewConfigPaths(tmpDir)
   372  		if err := ioutil.WriteFile(paths.Node.Cert, certChain, 0644); err != nil {
   373  			return nil, nil, err
   374  		}
   375  		if err := ioutil.WriteFile(paths.Node.Key, key, 0600); err != nil {
   376  			return nil, nil, err
   377  		}
   378  	}
   379  
   380  	// Load a valid tls.Certificate from the chain and the key
   381  	nodeCert, err := tls.X509KeyPair(certChain, key)
   382  	if err != nil {
   383  		return nil, nil, err
   384  	}
   385  
   386  	err = createNode(s, nodeID, role, csr, certChain)
   387  	if err != nil {
   388  		return nil, nil, err
   389  	}
   390  
   391  	signingCert := rootCA.Certs
   392  	if len(rootCA.Intermediates) > 0 {
   393  		signingCert = rootCA.Intermediates
   394  	}
   395  	parsedCert, err := helpers.ParseCertificatePEM(signingCert)
   396  	if err != nil {
   397  		return nil, nil, err
   398  	}
   399  
   400  	if nonSigningRoot {
   401  		rootCA = ca.RootCA{
   402  			Certs:         rootCA.Certs,
   403  			Digest:        rootCA.Digest,
   404  			Pool:          rootCA.Pool,
   405  			Intermediates: rootCA.Intermediates,
   406  		}
   407  	}
   408  
   409  	return ca.NewSecurityConfig(&rootCA, krw, &nodeCert, &ca.IssuerInfo{
   410  		PublicKey: parsedCert.RawSubjectPublicKeyInfo,
   411  		Subject:   parsedCert.RawSubject,
   412  	})
   413  }
   414  
   415  func createClusterObject(t *testing.T, s *store.MemoryStore, clusterID string, apiRootCA api.RootCA, caRootCA *ca.RootCA, externalCAs ...*api.ExternalCA) *api.Cluster {
   416  	fips := strings.HasPrefix(clusterID, "FIPS.")
   417  	cluster := &api.Cluster{
   418  		ID: clusterID,
   419  		Spec: api.ClusterSpec{
   420  			Annotations: api.Annotations{
   421  				Name: store.DefaultClusterName,
   422  			},
   423  			CAConfig: api.CAConfig{
   424  				ExternalCAs: externalCAs,
   425  			},
   426  		},
   427  		RootCA: apiRootCA,
   428  		FIPS:   fips,
   429  	}
   430  	if cluster.RootCA.JoinTokens.Worker == "" {
   431  		cluster.RootCA.JoinTokens.Worker = ca.GenerateJoinToken(caRootCA, fips)
   432  	}
   433  	if cluster.RootCA.JoinTokens.Manager == "" {
   434  		cluster.RootCA.JoinTokens.Manager = ca.GenerateJoinToken(caRootCA, fips)
   435  	}
   436  	err := s.Update(func(tx store.Tx) error {
   437  		store.CreateCluster(tx, cluster)
   438  		return nil
   439  	})
   440  	if t != nil {
   441  		assert.NoError(t, err)
   442  	}
   443  	return cluster
   444  }
   445  
   446  // CreateRootCertAndKey returns a generated certificate and key for a root CA
   447  func CreateRootCertAndKey(rootCN string) ([]byte, []byte, error) {
   448  	// Create a simple CSR for the CA using the default CA validator and policy
   449  	req := cfcsr.CertificateRequest{
   450  		CN:         rootCN,
   451  		KeyRequest: cfcsr.NewBasicKeyRequest(),
   452  		CA:         &cfcsr.CAConfig{Expiry: ca.RootCAExpiration},
   453  	}
   454  
   455  	// Generate the CA and get the certificate and private key
   456  	cert, _, key, err := initca.New(&req)
   457  	if err != nil {
   458  		return nil, nil, err
   459  	}
   460  
   461  	key, err = pkcs8.ConvertECPrivateKeyPEM(key)
   462  	if err != nil {
   463  		return nil, nil, err
   464  	}
   465  
   466  	return cert, key, err
   467  }
   468  
   469  // ReDateCert takes an existing cert and changes the not before and not after date, to make it easier
   470  // to test expiry
   471  func ReDateCert(t *testing.T, cert, signerCert, signerKey []byte, notBefore, notAfter time.Time) []byte {
   472  	signee, err := helpers.ParseCertificatePEM(cert)
   473  	require.NoError(t, err)
   474  	signer, err := helpers.ParseCertificatePEM(signerCert)
   475  	require.NoError(t, err)
   476  	key, err := helpers.ParsePrivateKeyPEM(signerKey)
   477  	require.NoError(t, err)
   478  	signee.NotBefore = notBefore
   479  	signee.NotAfter = notAfter
   480  
   481  	derBytes, err := x509.CreateCertificate(cryptorand.Reader, signee, signer, signee.PublicKey, key)
   482  	require.NoError(t, err)
   483  	return pem.EncodeToMemory(&pem.Block{
   484  		Type:  "CERTIFICATE",
   485  		Bytes: derBytes,
   486  	})
   487  }
   488  
   489  // CreateCertFromSigner creates a Certificate authority for a new Swarm Cluster given an existing key only.
   490  func CreateCertFromSigner(rootCN string, priv crypto.Signer) ([]byte, error) {
   491  	req := cfcsr.CertificateRequest{
   492  		CN:         rootCN,
   493  		KeyRequest: &cfcsr.BasicKeyRequest{A: ca.RootKeyAlgo, S: ca.RootKeySize},
   494  		CA:         &cfcsr.CAConfig{Expiry: ca.RootCAExpiration},
   495  	}
   496  	cert, _, err := initca.NewFromSigner(&req, priv)
   497  	return cert, err
   498  }