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

     1  package node
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/x509"
     7  	"encoding/pem"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/cloudflare/cfssl/helpers"
    17  	"github.com/docker/swarmkit/agent"
    18  	agentutils "github.com/docker/swarmkit/agent/testutils"
    19  	"github.com/docker/swarmkit/api"
    20  	"github.com/docker/swarmkit/ca"
    21  	"github.com/docker/swarmkit/ca/keyutils"
    22  	cautils "github.com/docker/swarmkit/ca/testutils"
    23  	"github.com/docker/swarmkit/identity"
    24  	"github.com/docker/swarmkit/log"
    25  	"github.com/docker/swarmkit/manager/state/store"
    26  	"github.com/docker/swarmkit/testutils"
    27  	"github.com/pkg/errors"
    28  	"github.com/stretchr/testify/require"
    29  )
    30  
    31  func getLoggingContext(t *testing.T) context.Context {
    32  	return log.WithLogger(context.Background(), log.L.WithField("test", t.Name()))
    33  }
    34  
    35  // If there is nothing on disk and no join addr, we create a new CA and a new set of TLS certs.
    36  // If AutoLockManagers is enabled, the TLS key is encrypted with a randomly generated lock key.
    37  func TestLoadSecurityConfigNewNode(t *testing.T) {
    38  	for _, autoLockManagers := range []bool{true, false} {
    39  		tempdir, err := ioutil.TempDir("", "test-new-node")
    40  		require.NoError(t, err)
    41  		defer os.RemoveAll(tempdir)
    42  
    43  		paths := ca.NewConfigPaths(filepath.Join(tempdir, "certificates"))
    44  
    45  		node, err := New(&Config{
    46  			StateDir:         tempdir,
    47  			AutoLockManagers: autoLockManagers,
    48  		})
    49  		require.NoError(t, err)
    50  		securityConfig, cancel, err := node.loadSecurityConfig(context.Background(), paths)
    51  		require.NoError(t, err)
    52  		defer cancel()
    53  		require.NotNil(t, securityConfig)
    54  
    55  		unencryptedReader := ca.NewKeyReadWriter(paths.Node, nil, nil)
    56  		_, _, err = unencryptedReader.Read()
    57  		if !autoLockManagers {
    58  			require.NoError(t, err)
    59  		} else {
    60  			require.IsType(t, ca.ErrInvalidKEK{}, err)
    61  		}
    62  	}
    63  }
    64  
    65  // If there's only a root CA on disk (no TLS certs), and no join addr, we create a new CA
    66  // and a new set of TLS certs.  Similarly if there's only a TLS cert and key, and no CA.
    67  func TestLoadSecurityConfigPartialCertsOnDisk(t *testing.T) {
    68  	tempdir, err := ioutil.TempDir("", "test-new-node")
    69  	require.NoError(t, err)
    70  	defer os.RemoveAll(tempdir)
    71  
    72  	paths := ca.NewConfigPaths(filepath.Join(tempdir, "certificates"))
    73  	rootCA, err := ca.CreateRootCA(ca.DefaultRootCN)
    74  	require.NoError(t, err)
    75  	require.NoError(t, ca.SaveRootCA(rootCA, paths.RootCA))
    76  
    77  	node, err := New(&Config{
    78  		StateDir: tempdir,
    79  	})
    80  	require.NoError(t, err)
    81  	securityConfig, cancel, err := node.loadSecurityConfig(context.Background(), paths)
    82  	require.NoError(t, err)
    83  	defer cancel()
    84  	require.NotNil(t, securityConfig)
    85  
    86  	cert, key, err := securityConfig.KeyReader().Read()
    87  	require.NoError(t, err)
    88  
    89  	// a new CA was generated because no existing TLS certs were present
    90  	require.NotEqual(t, rootCA.Certs, securityConfig.RootCA().Certs)
    91  
    92  	// if the TLS key and cert are on disk, but there's no CA, a new CA and TLS
    93  	// key+cert are generated
    94  	require.NoError(t, os.RemoveAll(paths.RootCA.Cert))
    95  
    96  	node, err = New(&Config{
    97  		StateDir: tempdir,
    98  	})
    99  	require.NoError(t, err)
   100  	securityConfig, cancel, err = node.loadSecurityConfig(context.Background(), paths)
   101  	require.NoError(t, err)
   102  	defer cancel()
   103  	require.NotNil(t, securityConfig)
   104  
   105  	newCert, newKey, err := securityConfig.KeyReader().Read()
   106  	require.NoError(t, err)
   107  	require.NotEqual(t, cert, newCert)
   108  	require.NotEqual(t, key, newKey)
   109  }
   110  
   111  // If there are CAs and TLS certs on disk, it tries to load and fails if there
   112  // are any errors, even if a join token is provided.
   113  func TestLoadSecurityConfigLoadFromDisk(t *testing.T) {
   114  	tempdir, err := ioutil.TempDir("", "test-load-node-tls")
   115  	require.NoError(t, err)
   116  	defer os.RemoveAll(tempdir)
   117  
   118  	paths := ca.NewConfigPaths(filepath.Join(tempdir, "certificates"))
   119  
   120  	tc := cautils.NewTestCA(t)
   121  	defer tc.Stop()
   122  	peer, err := tc.ConnBroker.Remotes().Select()
   123  	require.NoError(t, err)
   124  
   125  	// Load successfully with valid passphrase
   126  	rootCA, err := ca.CreateRootCA(ca.DefaultRootCN)
   127  	require.NoError(t, err)
   128  	require.NoError(t, ca.SaveRootCA(rootCA, paths.RootCA))
   129  
   130  	krw := ca.NewKeyReadWriter(paths.Node, []byte("passphrase"), nil)
   131  	require.NoError(t, err)
   132  	_, _, err = rootCA.IssueAndSaveNewCertificates(krw, identity.NewID(), ca.WorkerRole, identity.NewID())
   133  	require.NoError(t, err)
   134  
   135  	node, err := New(&Config{
   136  		StateDir:  tempdir,
   137  		JoinAddr:  peer.Addr,
   138  		JoinToken: tc.ManagerToken,
   139  		UnlockKey: []byte("passphrase"),
   140  	})
   141  	require.NoError(t, err)
   142  	securityConfig, cancel, err := node.loadSecurityConfig(context.Background(), paths)
   143  	require.NoError(t, err)
   144  	defer cancel()
   145  	require.NotNil(t, securityConfig)
   146  
   147  	// Invalid passphrase
   148  	node, err = New(&Config{
   149  		StateDir:  tempdir,
   150  		JoinAddr:  peer.Addr,
   151  		JoinToken: tc.ManagerToken,
   152  	})
   153  	require.NoError(t, err)
   154  	_, _, err = node.loadSecurityConfig(context.Background(), paths)
   155  	require.Equal(t, ErrInvalidUnlockKey, err)
   156  
   157  	// Invalid CA
   158  	otherRootCA, err := ca.CreateRootCA(ca.DefaultRootCN)
   159  	require.NoError(t, err)
   160  	require.NoError(t, ca.SaveRootCA(otherRootCA, paths.RootCA))
   161  	node, err = New(&Config{
   162  		StateDir:  tempdir,
   163  		JoinAddr:  peer.Addr,
   164  		JoinToken: tc.ManagerToken,
   165  		UnlockKey: []byte("passphrase"),
   166  	})
   167  	require.NoError(t, err)
   168  	_, _, err = node.loadSecurityConfig(context.Background(), paths)
   169  	require.IsType(t, x509.UnknownAuthorityError{}, errors.Cause(err))
   170  
   171  	// Convert to PKCS1 and require FIPS
   172  	require.NoError(t, krw.DowngradeKey())
   173  	// go back to the previous root CA
   174  	require.NoError(t, ca.SaveRootCA(rootCA, paths.RootCA))
   175  	node, err = New(&Config{
   176  		StateDir:  tempdir,
   177  		JoinAddr:  peer.Addr,
   178  		JoinToken: tc.ManagerToken,
   179  		UnlockKey: []byte("passphrase"),
   180  		FIPS:      true,
   181  	})
   182  	require.NoError(t, err)
   183  	_, _, err = node.loadSecurityConfig(context.Background(), paths)
   184  	require.Equal(t, keyutils.ErrFIPSUnsupportedKeyFormat, errors.Cause(err))
   185  }
   186  
   187  // If there is no CA, and a join addr is provided, one is downloaded from the
   188  // join server. If there is a CA, it is just loaded from disk.  The TLS key and
   189  // cert are also downloaded.
   190  func TestLoadSecurityConfigDownloadAllCerts(t *testing.T) {
   191  	tempdir, err := ioutil.TempDir("", "test-join-node")
   192  	require.NoError(t, err)
   193  	defer os.RemoveAll(tempdir)
   194  
   195  	paths := ca.NewConfigPaths(filepath.Join(tempdir, "certificates"))
   196  
   197  	// join addr is invalid
   198  	node, err := New(&Config{
   199  		StateDir: tempdir,
   200  		JoinAddr: "127.0.0.1:12",
   201  	})
   202  	require.NoError(t, err)
   203  	_, _, err = node.loadSecurityConfig(context.Background(), paths)
   204  	require.Error(t, err)
   205  
   206  	tc := cautils.NewTestCA(t)
   207  	defer tc.Stop()
   208  
   209  	peer, err := tc.ConnBroker.Remotes().Select()
   210  	require.NoError(t, err)
   211  
   212  	node, err = New(&Config{
   213  		StateDir:  tempdir,
   214  		JoinAddr:  peer.Addr,
   215  		JoinToken: tc.ManagerToken,
   216  	})
   217  	require.NoError(t, err)
   218  	_, cancel, err := node.loadSecurityConfig(context.Background(), paths)
   219  	require.NoError(t, err)
   220  	cancel()
   221  
   222  	// the TLS key and cert were written to disk unencrypted
   223  	_, _, err = ca.NewKeyReadWriter(paths.Node, nil, nil).Read()
   224  	require.NoError(t, err)
   225  
   226  	// remove the TLS cert and key, and mark the root CA cert so that we will
   227  	// know if it gets replaced
   228  	require.NoError(t, os.Remove(paths.Node.Cert))
   229  	require.NoError(t, os.Remove(paths.Node.Key))
   230  	certBytes, err := ioutil.ReadFile(paths.RootCA.Cert)
   231  	require.NoError(t, err)
   232  	pemBlock, _ := pem.Decode(certBytes)
   233  	require.NotNil(t, pemBlock)
   234  	pemBlock.Headers["marked"] = "true"
   235  	certBytes = pem.EncodeToMemory(pemBlock)
   236  	require.NoError(t, ioutil.WriteFile(paths.RootCA.Cert, certBytes, 0644))
   237  
   238  	// also make sure the new set gets downloaded and written to disk with a passphrase
   239  	// by updating the memory store with manager autolock on and an unlock key
   240  	require.NoError(t, tc.MemoryStore.Update(func(tx store.Tx) error {
   241  		clusters, err := store.FindClusters(tx, store.All)
   242  		require.NoError(t, err)
   243  		require.Len(t, clusters, 1)
   244  
   245  		newCluster := clusters[0].Copy()
   246  		newCluster.Spec.EncryptionConfig.AutoLockManagers = true
   247  		newCluster.UnlockKeys = []*api.EncryptionKey{{
   248  			Subsystem: ca.ManagerRole,
   249  			Key:       []byte("passphrase"),
   250  		}}
   251  		return store.UpdateCluster(tx, newCluster)
   252  	}))
   253  
   254  	// Join with without any passphrase - this should be fine, because the TLS
   255  	// key is downloaded and then loaded just fine.  However, it *is* written
   256  	// to disk encrypted.
   257  	node, err = New(&Config{
   258  		StateDir:  tempdir,
   259  		JoinAddr:  peer.Addr,
   260  		JoinToken: tc.ManagerToken,
   261  	})
   262  	require.NoError(t, err)
   263  	_, cancel, err = node.loadSecurityConfig(context.Background(), paths)
   264  	require.NoError(t, err)
   265  	cancel()
   266  
   267  	// make sure the CA cert has not been replaced
   268  	readCertBytes, err := ioutil.ReadFile(paths.RootCA.Cert)
   269  	require.NoError(t, err)
   270  	require.Equal(t, certBytes, readCertBytes)
   271  
   272  	// the TLS node cert and key were saved to disk encrypted, though
   273  	_, _, err = ca.NewKeyReadWriter(paths.Node, nil, nil).Read()
   274  	require.Error(t, err)
   275  	_, _, err = ca.NewKeyReadWriter(paths.Node, []byte("passphrase"), nil).Read()
   276  	require.NoError(t, err)
   277  }
   278  
   279  // If there is nothing on disk and no join addr, and FIPS is enabled, we create a cluster whose
   280  // ID starts with 'FIPS.'
   281  func TestLoadSecurityConfigNodeFIPSCreateCluster(t *testing.T) {
   282  	tempdir, err := ioutil.TempDir("", "test-security-config-fips-new-cluster")
   283  	require.NoError(t, err)
   284  	defer os.RemoveAll(tempdir)
   285  
   286  	paths := ca.NewConfigPaths(filepath.Join(tempdir, "certificates"))
   287  
   288  	tc := cautils.NewTestCA(t)
   289  	defer tc.Stop()
   290  
   291  	config := &Config{
   292  		StateDir: tempdir,
   293  		FIPS:     true,
   294  	}
   295  
   296  	node, err := New(config)
   297  	require.NoError(t, err)
   298  	securityConfig, cancel, err := node.loadSecurityConfig(tc.Context, paths)
   299  	require.NoError(t, err)
   300  	defer cancel()
   301  	require.NotNil(t, securityConfig)
   302  	require.True(t, strings.HasPrefix(securityConfig.ClientTLSCreds.Organization(), "FIPS."))
   303  }
   304  
   305  // If FIPS is enabled and there is a join address, the cluster ID is whatever the CA set
   306  // the cluster ID to.
   307  func TestLoadSecurityConfigNodeFIPSJoinCluster(t *testing.T) {
   308  	tempdir, err := ioutil.TempDir("", "test-security-config-fips-join-cluster")
   309  	require.NoError(t, err)
   310  	defer os.RemoveAll(tempdir)
   311  
   312  	certDir := filepath.Join(tempdir, "certificates")
   313  	paths := ca.NewConfigPaths(certDir)
   314  
   315  	for _, fips := range []bool{true, false} {
   316  		require.NoError(t, os.RemoveAll(certDir))
   317  
   318  		var tc *cautils.TestCA
   319  		if fips {
   320  			tc = cautils.NewFIPSTestCA(t)
   321  		} else {
   322  			tc = cautils.NewTestCA(t)
   323  		}
   324  		defer tc.Stop()
   325  
   326  		peer, err := tc.ConnBroker.Remotes().Select()
   327  		require.NoError(t, err)
   328  
   329  		node, err := New(&Config{
   330  			StateDir:  tempdir,
   331  			JoinAddr:  peer.Addr,
   332  			JoinToken: tc.ManagerToken,
   333  			FIPS:      true,
   334  		})
   335  		require.NoError(t, err)
   336  		securityConfig, cancel, err := node.loadSecurityConfig(tc.Context, paths)
   337  		require.NoError(t, err)
   338  		defer cancel()
   339  		require.NotNil(t, securityConfig)
   340  		require.Equal(t, fips, strings.HasPrefix(securityConfig.ClientTLSCreds.Organization(), "FIPS."))
   341  	}
   342  }
   343  
   344  // If the certificate specifies that the cluster requires FIPS mode, loading the security
   345  // config will fail if the node is not FIPS enabled.
   346  func TestLoadSecurityConfigRespectsFIPSCert(t *testing.T) {
   347  	tempdir, err := ioutil.TempDir("", "test-security-config-fips-cert-on-disk")
   348  	require.NoError(t, err)
   349  	defer os.RemoveAll(tempdir)
   350  
   351  	tc := cautils.NewFIPSTestCA(t)
   352  	defer tc.Stop()
   353  
   354  	certDir := filepath.Join(tempdir, "certificates")
   355  	require.NoError(t, os.Mkdir(certDir, 0700))
   356  	paths := ca.NewConfigPaths(certDir)
   357  
   358  	// copy certs and keys from the test CA using a hard link
   359  	_, err = tc.WriteNewNodeConfig(ca.ManagerRole)
   360  	require.NoError(t, err)
   361  	require.NoError(t, os.Link(tc.Paths.Node.Cert, paths.Node.Cert))
   362  	require.NoError(t, os.Link(tc.Paths.Node.Key, paths.Node.Key))
   363  	require.NoError(t, os.Link(tc.Paths.RootCA.Cert, paths.RootCA.Cert))
   364  
   365  	node, err := New(&Config{StateDir: tempdir})
   366  	require.NoError(t, err)
   367  	_, _, err = node.loadSecurityConfig(tc.Context, paths)
   368  	require.Equal(t, ErrMandatoryFIPS, err)
   369  
   370  	node, err = New(&Config{
   371  		StateDir: tempdir,
   372  		FIPS:     true,
   373  	})
   374  	require.NoError(t, err)
   375  	securityConfig, cancel, err := node.loadSecurityConfig(tc.Context, paths)
   376  	require.NoError(t, err)
   377  	defer cancel()
   378  	require.NotNil(t, securityConfig)
   379  	require.True(t, strings.HasPrefix(securityConfig.ClientTLSCreds.Organization(), "FIPS."))
   380  }
   381  
   382  // If FIPS is disabled and there is a join address and token, and the join token indicates
   383  // the cluster requires fips, then loading the security config will fail.  However, if
   384  // there are already certs on disk, it will load them and ignore the join token.
   385  func TestLoadSecurityConfigNonFIPSNodeJoinCluster(t *testing.T) {
   386  	tempdir, err := ioutil.TempDir("", "test-security-config-nonfips-join-cluster")
   387  	require.NoError(t, err)
   388  	defer os.RemoveAll(tempdir)
   389  
   390  	certDir := filepath.Join(tempdir, "certificates")
   391  	require.NoError(t, os.Mkdir(certDir, 0700))
   392  	paths := ca.NewConfigPaths(certDir)
   393  
   394  	tc := cautils.NewTestCA(t)
   395  	defer tc.Stop()
   396  	// copy certs and keys from the test CA using a hard link
   397  	_, err = tc.WriteNewNodeConfig(ca.ManagerRole)
   398  	require.NoError(t, err)
   399  	require.NoError(t, os.Link(tc.Paths.Node.Cert, paths.Node.Cert))
   400  	require.NoError(t, os.Link(tc.Paths.Node.Key, paths.Node.Key))
   401  	require.NoError(t, os.Link(tc.Paths.RootCA.Cert, paths.RootCA.Cert))
   402  
   403  	tcFIPS := cautils.NewFIPSTestCA(t)
   404  	defer tcFIPS.Stop()
   405  
   406  	peer, err := tcFIPS.ConnBroker.Remotes().Select()
   407  	require.NoError(t, err)
   408  
   409  	node, err := New(&Config{
   410  		StateDir:  tempdir,
   411  		JoinAddr:  peer.Addr,
   412  		JoinToken: tcFIPS.ManagerToken,
   413  	})
   414  	require.NoError(t, err)
   415  	securityConfig, cancel, err := node.loadSecurityConfig(tcFIPS.Context, paths)
   416  	require.NoError(t, err)
   417  	defer cancel()
   418  	require.NotNil(t, securityConfig)
   419  	require.False(t, strings.HasPrefix(securityConfig.ClientTLSCreds.Organization(), "FIPS."))
   420  
   421  	// remove the node cert only - now that the node has to download the certs, it will check the
   422  	// join address and fail
   423  	require.NoError(t, os.Remove(paths.Node.Cert))
   424  
   425  	_, _, err = node.loadSecurityConfig(tcFIPS.Context, paths)
   426  	require.Equal(t, ErrMandatoryFIPS, err)
   427  
   428  	// remove all the certs (CA and node) - the node will also check the join address and fail
   429  	require.NoError(t, os.RemoveAll(certDir))
   430  
   431  	_, _, err = node.loadSecurityConfig(tcFIPS.Context, paths)
   432  	require.Equal(t, ErrMandatoryFIPS, err)
   433  }
   434  
   435  func TestManagerRespectsDispatcherRootCAUpdate(t *testing.T) {
   436  	tmpDir, err := ioutil.TempDir("", "manager-root-ca-update")
   437  	require.NoError(t, err)
   438  	defer os.RemoveAll(tmpDir)
   439  
   440  	// don't bother with a listening socket
   441  	cAddr := filepath.Join(tmpDir, "control.sock")
   442  	cfg := &Config{
   443  		ListenControlAPI: cAddr,
   444  		StateDir:         tmpDir,
   445  		Executor:         &agentutils.TestExecutor{},
   446  	}
   447  
   448  	node, err := New(cfg)
   449  	require.NoError(t, err)
   450  
   451  	require.NoError(t, node.Start(context.Background()))
   452  
   453  	select {
   454  	case <-node.Ready():
   455  	case <-time.After(5 * time.Second):
   456  		require.FailNow(t, "node did not ready in time")
   457  	}
   458  
   459  	// ensure that we have a second dispatcher that we can connect to when we shut down ours
   460  	paths := ca.NewConfigPaths(filepath.Join(tmpDir, certDirectory))
   461  	rootCA, err := ca.GetLocalRootCA(paths.RootCA)
   462  	require.NoError(t, err)
   463  	managerSecConfig, cancel, err := ca.LoadSecurityConfig(context.Background(), rootCA, ca.NewKeyReadWriter(paths.Node, nil, nil), false)
   464  	require.NoError(t, err)
   465  	defer cancel()
   466  
   467  	mockDispatcher, cleanup := agentutils.NewMockDispatcher(t, managerSecConfig, false)
   468  	defer cleanup()
   469  	node.remotes.Observe(api.Peer{Addr: mockDispatcher.Addr}, 1)
   470  
   471  	currentCACerts := rootCA.Certs
   472  
   473  	// shut down our current manager so that when the root CA changes, the manager doesn't "fix" it.
   474  	node.manager.Stop(context.Background(), false)
   475  
   476  	// fake an update from a remote dispatcher
   477  	node.notifyNodeChange <- &agent.NodeChanges{
   478  		RootCert: append(currentCACerts, cautils.ECDSA256SHA256Cert...),
   479  	}
   480  
   481  	// the node root CA certificates have changed now
   482  	time.Sleep(250 * time.Millisecond)
   483  	certPath := filepath.Join(tmpDir, certDirectory, "swarm-root-ca.crt")
   484  	caCerts, err := ioutil.ReadFile(certPath)
   485  	require.NoError(t, err)
   486  	require.NotEqual(t, currentCACerts, caCerts)
   487  
   488  	require.NoError(t, node.Stop(context.Background()))
   489  }
   490  
   491  func TestAgentRespectsDispatcherRootCAUpdate(t *testing.T) {
   492  	tmpDir, err := ioutil.TempDir("", "manager-root-ca-update")
   493  	require.NoError(t, err)
   494  	defer os.RemoveAll(tmpDir)
   495  
   496  	// bootstrap worker TLS certificates
   497  	paths := ca.NewConfigPaths(filepath.Join(tmpDir, certDirectory))
   498  	rootCA, err := ca.CreateRootCA("rootCN")
   499  	require.NoError(t, err)
   500  	require.NoError(t, ca.SaveRootCA(rootCA, paths.RootCA))
   501  	managerSecConfig, cancel, err := rootCA.CreateSecurityConfig(context.Background(),
   502  		ca.NewKeyReadWriter(paths.Node, nil, nil), ca.CertificateRequestConfig{})
   503  	require.NoError(t, err)
   504  	defer cancel()
   505  
   506  	_, _, err = rootCA.IssueAndSaveNewCertificates(ca.NewKeyReadWriter(paths.Node, nil, nil), "workerNode",
   507  		ca.WorkerRole, managerSecConfig.ServerTLSCreds.Organization())
   508  	require.NoError(t, err)
   509  
   510  	mockDispatcher, cleanup := agentutils.NewMockDispatcher(t, managerSecConfig, false)
   511  	defer cleanup()
   512  
   513  	cfg := &Config{
   514  		StateDir: tmpDir,
   515  		Executor: &agentutils.TestExecutor{},
   516  		JoinAddr: mockDispatcher.Addr,
   517  	}
   518  	node, err := New(cfg)
   519  	require.NoError(t, err)
   520  
   521  	require.NoError(t, node.Start(context.Background()))
   522  
   523  	select {
   524  	case <-node.Ready():
   525  	case <-time.After(5 * time.Second):
   526  		require.FailNow(t, "node did not ready in time")
   527  	}
   528  
   529  	currentCACerts, err := ioutil.ReadFile(paths.RootCA.Cert)
   530  	require.NoError(t, err)
   531  	parsedCerts, err := helpers.ParseCertificatesPEM(currentCACerts)
   532  	require.NoError(t, err)
   533  	require.Len(t, parsedCerts, 1)
   534  
   535  	// fake an update from the dispatcher
   536  	node.notifyNodeChange <- &agent.NodeChanges{
   537  		RootCert: append(currentCACerts, cautils.ECDSA256SHA256Cert...),
   538  	}
   539  
   540  	require.NoError(t, testutils.PollFuncWithTimeout(nil, func() error {
   541  		caCerts, err := ioutil.ReadFile(paths.RootCA.Cert)
   542  		require.NoError(t, err)
   543  		if bytes.Equal(currentCACerts, caCerts) {
   544  			return errors.New("new certificates have not been replaced yet")
   545  		}
   546  		parsedCerts, err := helpers.ParseCertificatesPEM(caCerts)
   547  		if err != nil {
   548  			return err
   549  		}
   550  		if len(parsedCerts) != 2 {
   551  			return fmt.Errorf("expecting 2 new certificates, got %d", len(parsedCerts))
   552  		}
   553  		return nil
   554  	}, time.Second))
   555  
   556  	require.NoError(t, node.Stop(context.Background()))
   557  }
   558  
   559  func TestCertRenewals(t *testing.T) {
   560  	tmpDir, err := ioutil.TempDir("", "no-top-level-role")
   561  	require.NoError(t, err)
   562  	defer os.RemoveAll(tmpDir)
   563  
   564  	paths := ca.NewConfigPaths(filepath.Join(tmpDir, "certificates"))
   565  
   566  	// don't bother with a listening socket
   567  	cAddr := filepath.Join(tmpDir, "control.sock")
   568  	cfg := &Config{
   569  		ListenControlAPI: cAddr,
   570  		StateDir:         tmpDir,
   571  		Executor:         &agentutils.TestExecutor{},
   572  	}
   573  	node, err := New(cfg)
   574  	require.NoError(t, err)
   575  
   576  	require.NoError(t, node.Start(context.Background()))
   577  
   578  	select {
   579  	case <-node.Ready():
   580  	case <-time.After(5 * time.Second):
   581  		require.FailNow(t, "node did not ready in time")
   582  	}
   583  
   584  	currentNodeCert, err := ioutil.ReadFile(paths.Node.Cert)
   585  	require.NoError(t, err)
   586  
   587  	// Fake an update from the dispatcher. Make sure the Role field is
   588  	// ignored when DesiredRole has not changed.
   589  	node.notifyNodeChange <- &agent.NodeChanges{
   590  		Node: &api.Node{
   591  			Spec: api.NodeSpec{
   592  				DesiredRole: api.NodeRoleManager,
   593  			},
   594  			Role: api.NodeRoleWorker,
   595  		},
   596  	}
   597  
   598  	time.Sleep(500 * time.Millisecond)
   599  
   600  	nodeCert, err := ioutil.ReadFile(paths.Node.Cert)
   601  	require.NoError(t, err)
   602  	if !bytes.Equal(currentNodeCert, nodeCert) {
   603  		t.Fatal("Certificate should not have been renewed")
   604  	}
   605  
   606  	// Fake an update from the dispatcher. When DesiredRole doesn't match
   607  	// the current role, a cert renewal should be triggered.
   608  	node.notifyNodeChange <- &agent.NodeChanges{
   609  		Node: &api.Node{
   610  			Spec: api.NodeSpec{
   611  				DesiredRole: api.NodeRoleWorker,
   612  			},
   613  			Role: api.NodeRoleWorker,
   614  		},
   615  	}
   616  
   617  	require.NoError(t, testutils.PollFuncWithTimeout(nil, func() error {
   618  		nodeCert, err := ioutil.ReadFile(paths.Node.Cert)
   619  		require.NoError(t, err)
   620  		if bytes.Equal(currentNodeCert, nodeCert) {
   621  			return errors.New("certificate has not been replaced yet")
   622  		}
   623  		currentNodeCert = nodeCert
   624  		return nil
   625  	}, 5*time.Second))
   626  
   627  	require.NoError(t, node.Stop(context.Background()))
   628  }
   629  
   630  func TestManagerFailedStartup(t *testing.T) {
   631  	tmpDir, err := ioutil.TempDir("", "manager-root-ca-update")
   632  	require.NoError(t, err)
   633  	defer os.RemoveAll(tmpDir)
   634  
   635  	paths := ca.NewConfigPaths(filepath.Join(tmpDir, certDirectory))
   636  
   637  	rootCA, err := ca.CreateRootCA(ca.DefaultRootCN)
   638  	require.NoError(t, err)
   639  	require.NoError(t, ca.SaveRootCA(rootCA, paths.RootCA))
   640  
   641  	krw := ca.NewKeyReadWriter(paths.Node, nil, nil)
   642  	require.NoError(t, err)
   643  	_, _, err = rootCA.IssueAndSaveNewCertificates(krw, identity.NewID(), ca.ManagerRole, identity.NewID())
   644  	require.NoError(t, err)
   645  
   646  	// don't bother with a listening socket
   647  	cAddr := filepath.Join(tmpDir, "control.sock")
   648  	cfg := &Config{
   649  		ListenControlAPI: cAddr,
   650  		StateDir:         tmpDir,
   651  		Executor:         &agentutils.TestExecutor{},
   652  		JoinAddr:         "127.0.0.1",
   653  	}
   654  
   655  	node, err := New(cfg)
   656  	require.NoError(t, err)
   657  
   658  	require.NoError(t, node.Start(context.Background()))
   659  
   660  	select {
   661  	case <-node.Ready():
   662  		require.FailNow(t, "node should not become ready")
   663  	case <-time.After(5 * time.Second):
   664  		require.FailNow(t, "node neither became ready nor encountered an error")
   665  	case <-node.closed:
   666  		require.EqualError(t, node.err, "manager stopped: can't initialize raft node: attempted to join raft cluster without knowing own address")
   667  	}
   668  }
   669  
   670  // TestFIPSConfiguration ensures that new keys will be stored in PKCS8 format.
   671  func TestFIPSConfiguration(t *testing.T) {
   672  	ctx := getLoggingContext(t)
   673  	tmpDir, err := ioutil.TempDir("", "fips")
   674  	require.NoError(t, err)
   675  	defer os.RemoveAll(tmpDir)
   676  
   677  	paths := ca.NewConfigPaths(filepath.Join(tmpDir, "certificates"))
   678  
   679  	// don't bother with a listening socket
   680  	cAddr := filepath.Join(tmpDir, "control.sock")
   681  	cfg := &Config{
   682  		ListenControlAPI: cAddr,
   683  		StateDir:         tmpDir,
   684  		Executor:         &agentutils.TestExecutor{},
   685  		FIPS:             true,
   686  	}
   687  	node, err := New(cfg)
   688  	require.NoError(t, err)
   689  	require.NoError(t, node.Start(ctx))
   690  	defer func() {
   691  		require.NoError(t, node.Stop(ctx))
   692  	}()
   693  
   694  	select {
   695  	case <-node.Ready():
   696  	case <-time.After(5 * time.Second):
   697  		require.FailNow(t, "node did not ready in time")
   698  	}
   699  
   700  	nodeKey, err := ioutil.ReadFile(paths.Node.Key)
   701  	require.NoError(t, err)
   702  	pemBlock, _ := pem.Decode(nodeKey)
   703  	require.NotNil(t, pemBlock)
   704  	require.True(t, keyutils.IsPKCS8(pemBlock.Bytes))
   705  }