github.com/xeptore/docker-cli@v20.10.14+incompatible/cli/command/swarm/ca_test.go (about)

     1  package swarm
     2  
     3  import (
     4  	"bytes"
     5  	"io/ioutil"
     6  	"os"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/docker/cli/internal/test"
    11  	"github.com/docker/docker/api/types/swarm"
    12  	"gotest.tools/v3/assert"
    13  	is "gotest.tools/v3/assert/cmp"
    14  )
    15  
    16  const (
    17  	cert = `
    18  -----BEGIN CERTIFICATE-----
    19  MIIBuDCCAV4CCQDOqUYOWdqMdjAKBggqhkjOPQQDAzBjMQswCQYDVQQGEwJVUzEL
    20  MAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzANBgNVBAoMBkRv
    21  Y2tlcjEPMA0GA1UECwwGRG9ja2VyMQ0wCwYDVQQDDARUZXN0MCAXDTE4MDcwMjIx
    22  MjkxOFoYDzMwMTcxMTAyMjEyOTE4WjBjMQswCQYDVQQGEwJVUzELMAkGA1UECAwC
    23  Q0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzANBgNVBAoMBkRvY2tlcjEPMA0G
    24  A1UECwwGRG9ja2VyMQ0wCwYDVQQDDARUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0D
    25  AQcDQgAEgvvZl5Vqpr1e+g5IhoU6TZHgRau+BZETVFTmqyWYajA/mooRQ1MZTozu
    26  s9ZZZA8tzUhIqS36gsFuyIZ4YiAlyjAKBggqhkjOPQQDAwNIADBFAiBQ7pCPQrj8
    27  8zaItMf0pk8j1NU5XrFqFEZICzvjzUJQBAIhAKq2gFwoTn8KH+cAAXZpAGJPmOsT
    28  zsBT8gBAOHhNA6/2
    29  -----END CERTIFICATE-----`
    30  	key = `
    31  -----BEGIN EC PRIVATE KEY-----
    32  MHcCAQEEICyheZpw70pbgO4hEuwhZTETWyTpNJmJ3TyFaWT6WTRkoAoGCCqGSM49
    33  AwEHoUQDQgAEgvvZl5Vqpr1e+g5IhoU6TZHgRau+BZETVFTmqyWYajA/mooRQ1MZ
    34  Tozus9ZZZA8tzUhIqS36gsFuyIZ4YiAlyg==
    35  -----END EC PRIVATE KEY-----`
    36  )
    37  
    38  func swarmSpecWithFullCAConfig() *swarm.Spec {
    39  	return &swarm.Spec{
    40  		CAConfig: swarm.CAConfig{
    41  			SigningCACert:  "cacert",
    42  			SigningCAKey:   "cakey",
    43  			ForceRotate:    1,
    44  			NodeCertExpiry: time.Duration(200),
    45  			ExternalCAs: []*swarm.ExternalCA{
    46  				{
    47  					URL:      "https://example.com/ca",
    48  					Protocol: swarm.ExternalCAProtocolCFSSL,
    49  					CACert:   "excacert",
    50  				},
    51  			},
    52  		},
    53  	}
    54  }
    55  
    56  func TestDisplayTrustRootNoRoot(t *testing.T) {
    57  	buffer := new(bytes.Buffer)
    58  	err := displayTrustRoot(buffer, swarm.Swarm{})
    59  	assert.Error(t, err, "No CA information available")
    60  }
    61  
    62  type invalidCATestCases struct {
    63  	args     []string
    64  	errorMsg string
    65  }
    66  
    67  func writeFile(data string) (string, error) {
    68  	tmpfile, err := ioutil.TempFile("", "testfile")
    69  	if err != nil {
    70  		return "", err
    71  	}
    72  	_, err = tmpfile.Write([]byte(data))
    73  	if err != nil {
    74  		return "", err
    75  	}
    76  	tmpfile.Close()
    77  	return tmpfile.Name(), nil
    78  }
    79  
    80  func TestDisplayTrustRootInvalidFlags(t *testing.T) {
    81  	// we need an actual PEMfile to test
    82  	tmpfile, err := writeFile(cert)
    83  	assert.NilError(t, err)
    84  	defer os.Remove(tmpfile)
    85  
    86  	errorTestCases := []invalidCATestCases{
    87  		{
    88  			args:     []string{"--ca-cert=" + tmpfile},
    89  			errorMsg: "flag requires the `--rotate` flag to update the CA",
    90  		},
    91  		{
    92  			args:     []string{"--ca-key=" + tmpfile},
    93  			errorMsg: "flag requires the `--rotate` flag to update the CA",
    94  		},
    95  		{ // to make sure we're not erroring because we didn't provide a CA key along with the CA cert
    96  			args: []string{
    97  				"--ca-cert=" + tmpfile,
    98  				"--ca-key=" + tmpfile,
    99  			},
   100  			errorMsg: "flag requires the `--rotate` flag to update the CA",
   101  		},
   102  		{
   103  			args:     []string{"--cert-expiry=2160h0m0s"},
   104  			errorMsg: "flag requires the `--rotate` flag to update the CA",
   105  		},
   106  		{
   107  			args:     []string{"--external-ca=protocol=cfssl,url=https://some.example.com/https/url"},
   108  			errorMsg: "flag requires the `--rotate` flag to update the CA",
   109  		},
   110  		{ // to make sure we're not erroring because we didn't provide a CA cert and external CA
   111  			args: []string{
   112  				"--ca-cert=" + tmpfile,
   113  				"--external-ca=protocol=cfssl,url=https://some.example.com/https/url",
   114  			},
   115  			errorMsg: "flag requires the `--rotate` flag to update the CA",
   116  		},
   117  		{
   118  			args: []string{
   119  				"--rotate",
   120  				"--external-ca=protocol=cfssl,url=https://some.example.com/https/url",
   121  			},
   122  			errorMsg: "rotating to an external CA requires the `--ca-cert` flag to specify the external CA's cert - " +
   123  				"to add an external CA with the current root CA certificate, use the `update` command instead",
   124  		},
   125  		{
   126  			args: []string{
   127  				"--rotate",
   128  				"--ca-cert=" + tmpfile,
   129  			},
   130  			errorMsg: "the --ca-cert flag requires that a --ca-key flag and/or --external-ca flag be provided as well",
   131  		},
   132  	}
   133  
   134  	for _, testCase := range errorTestCases {
   135  		cmd := newCACommand(
   136  			test.NewFakeCli(&fakeClient{
   137  				swarmInspectFunc: func() (swarm.Swarm, error) {
   138  					return swarm.Swarm{
   139  						ClusterInfo: swarm.ClusterInfo{
   140  							TLSInfo: swarm.TLSInfo{
   141  								TrustRoot: "root",
   142  							},
   143  						},
   144  					}, nil
   145  				},
   146  			}))
   147  		assert.Check(t, cmd.Flags().Parse(testCase.args))
   148  		cmd.SetOut(ioutil.Discard)
   149  		assert.ErrorContains(t, cmd.Execute(), testCase.errorMsg)
   150  	}
   151  }
   152  
   153  func TestDisplayTrustRoot(t *testing.T) {
   154  	buffer := new(bytes.Buffer)
   155  	trustRoot := "trustme"
   156  	err := displayTrustRoot(buffer, swarm.Swarm{
   157  		ClusterInfo: swarm.ClusterInfo{
   158  			TLSInfo: swarm.TLSInfo{TrustRoot: trustRoot},
   159  		},
   160  	})
   161  	assert.NilError(t, err)
   162  	assert.Check(t, is.Equal(trustRoot+"\n", buffer.String()))
   163  }
   164  
   165  type swarmUpdateRecorder struct {
   166  	spec swarm.Spec
   167  }
   168  
   169  func (s *swarmUpdateRecorder) swarmUpdate(sp swarm.Spec, _ swarm.UpdateFlags) error {
   170  	s.spec = sp
   171  	return nil
   172  }
   173  
   174  func swarmInspectFuncWithFullCAConfig() (swarm.Swarm, error) {
   175  	return swarm.Swarm{
   176  		ClusterInfo: swarm.ClusterInfo{
   177  			Spec: *swarmSpecWithFullCAConfig(),
   178  		},
   179  	}, nil
   180  }
   181  
   182  func TestUpdateSwarmSpecDefaultRotate(t *testing.T) {
   183  	s := &swarmUpdateRecorder{}
   184  	cli := test.NewFakeCli(&fakeClient{
   185  		swarmInspectFunc: swarmInspectFuncWithFullCAConfig,
   186  		swarmUpdateFunc:  s.swarmUpdate,
   187  	})
   188  	cmd := newCACommand(cli)
   189  	cmd.SetArgs([]string{"--rotate", "--detach"})
   190  	cmd.SetOut(cli.OutBuffer())
   191  	assert.NilError(t, cmd.Execute())
   192  
   193  	expected := swarmSpecWithFullCAConfig()
   194  	expected.CAConfig.ForceRotate = 2
   195  	expected.CAConfig.SigningCACert = ""
   196  	expected.CAConfig.SigningCAKey = ""
   197  	assert.Check(t, is.DeepEqual(*expected, s.spec))
   198  }
   199  
   200  func TestUpdateSwarmSpecCertAndKey(t *testing.T) {
   201  	certfile, err := writeFile(cert)
   202  	assert.NilError(t, err)
   203  	defer os.Remove(certfile)
   204  
   205  	keyfile, err := writeFile(key)
   206  	assert.NilError(t, err)
   207  	defer os.Remove(keyfile)
   208  
   209  	s := &swarmUpdateRecorder{}
   210  	cli := test.NewFakeCli(&fakeClient{
   211  		swarmInspectFunc: swarmInspectFuncWithFullCAConfig,
   212  		swarmUpdateFunc:  s.swarmUpdate,
   213  	})
   214  	cmd := newCACommand(cli)
   215  	cmd.SetArgs([]string{
   216  		"--rotate",
   217  		"--detach",
   218  		"--ca-cert=" + certfile,
   219  		"--ca-key=" + keyfile,
   220  		"--cert-expiry=3m"})
   221  	cmd.SetOut(cli.OutBuffer())
   222  	assert.NilError(t, cmd.Execute())
   223  
   224  	expected := swarmSpecWithFullCAConfig()
   225  	expected.CAConfig.SigningCACert = cert
   226  	expected.CAConfig.SigningCAKey = key
   227  	expected.CAConfig.NodeCertExpiry = 3 * time.Minute
   228  	assert.Check(t, is.DeepEqual(*expected, s.spec))
   229  }
   230  
   231  func TestUpdateSwarmSpecCertAndExternalCA(t *testing.T) {
   232  	certfile, err := writeFile(cert)
   233  	assert.NilError(t, err)
   234  	defer os.Remove(certfile)
   235  
   236  	s := &swarmUpdateRecorder{}
   237  	cli := test.NewFakeCli(&fakeClient{
   238  		swarmInspectFunc: swarmInspectFuncWithFullCAConfig,
   239  		swarmUpdateFunc:  s.swarmUpdate,
   240  	})
   241  	cmd := newCACommand(cli)
   242  	cmd.SetArgs([]string{
   243  		"--rotate",
   244  		"--detach",
   245  		"--ca-cert=" + certfile,
   246  		"--external-ca=protocol=cfssl,url=https://some.external.ca.example.com"})
   247  	cmd.SetOut(cli.OutBuffer())
   248  	assert.NilError(t, cmd.Execute())
   249  
   250  	expected := swarmSpecWithFullCAConfig()
   251  	expected.CAConfig.SigningCACert = cert
   252  	expected.CAConfig.SigningCAKey = ""
   253  	expected.CAConfig.ExternalCAs = []*swarm.ExternalCA{
   254  		{
   255  			Protocol: swarm.ExternalCAProtocolCFSSL,
   256  			URL:      "https://some.external.ca.example.com",
   257  			CACert:   cert,
   258  			Options:  make(map[string]string),
   259  		},
   260  	}
   261  	assert.Check(t, is.DeepEqual(*expected, s.spec))
   262  }
   263  
   264  func TestUpdateSwarmSpecCertAndKeyAndExternalCA(t *testing.T) {
   265  	certfile, err := writeFile(cert)
   266  	assert.NilError(t, err)
   267  	defer os.Remove(certfile)
   268  
   269  	keyfile, err := writeFile(key)
   270  	assert.NilError(t, err)
   271  	defer os.Remove(keyfile)
   272  
   273  	s := &swarmUpdateRecorder{}
   274  	cli := test.NewFakeCli(&fakeClient{
   275  		swarmInspectFunc: swarmInspectFuncWithFullCAConfig,
   276  		swarmUpdateFunc:  s.swarmUpdate,
   277  	})
   278  	cmd := newCACommand(cli)
   279  	cmd.SetArgs([]string{
   280  		"--rotate",
   281  		"--detach",
   282  		"--ca-cert=" + certfile,
   283  		"--ca-key=" + keyfile,
   284  		"--external-ca=protocol=cfssl,url=https://some.external.ca.example.com"})
   285  	cmd.SetOut(cli.OutBuffer())
   286  	assert.NilError(t, cmd.Execute())
   287  
   288  	expected := swarmSpecWithFullCAConfig()
   289  	expected.CAConfig.SigningCACert = cert
   290  	expected.CAConfig.SigningCAKey = key
   291  	expected.CAConfig.ExternalCAs = []*swarm.ExternalCA{
   292  		{
   293  			Protocol: swarm.ExternalCAProtocolCFSSL,
   294  			URL:      "https://some.external.ca.example.com",
   295  			CACert:   cert,
   296  			Options:  make(map[string]string),
   297  		},
   298  	}
   299  	assert.Check(t, is.DeepEqual(*expected, s.spec))
   300  }