github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/swarm/ca_test.go (about)

     1  package swarm
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     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 := os.CreateTemp("", "testfile")
    69  	if err != nil {
    70  		return "", err
    71  	}
    72  	_, err = tmpfile.Write([]byte(data))
    73  	if err != nil {
    74  		return "", err
    75  	}
    76  	return tmpfile.Name(), tmpfile.Close()
    77  }
    78  
    79  func TestDisplayTrustRootInvalidFlags(t *testing.T) {
    80  	// we need an actual PEMfile to test
    81  	tmpfile, err := writeFile(cert)
    82  	assert.NilError(t, err)
    83  	t.Cleanup(func() { _ = os.Remove(tmpfile) })
    84  
    85  	errorTestCases := []invalidCATestCases{
    86  		{
    87  			args:     []string{"--ca-cert=" + tmpfile},
    88  			errorMsg: "flag requires the `--rotate` flag to update the CA",
    89  		},
    90  		{
    91  			args:     []string{"--ca-key=" + tmpfile},
    92  			errorMsg: "flag requires the `--rotate` flag to update the CA",
    93  		},
    94  		{ // to make sure we're not erroring because we didn't provide a CA key along with the CA cert
    95  			args: []string{
    96  				"--ca-cert=" + tmpfile,
    97  				"--ca-key=" + tmpfile,
    98  			},
    99  			errorMsg: "flag requires the `--rotate` flag to update the CA",
   100  		},
   101  		{
   102  			args:     []string{"--cert-expiry=2160h0m0s"},
   103  			errorMsg: "flag requires the `--rotate` flag to update the CA",
   104  		},
   105  		{
   106  			args:     []string{"--external-ca=protocol=cfssl,url=https://some.example.com/https/url"},
   107  			errorMsg: "flag requires the `--rotate` flag to update the CA",
   108  		},
   109  		{ // to make sure we're not erroring because we didn't provide a CA cert and external CA
   110  			args: []string{
   111  				"--ca-cert=" + tmpfile,
   112  				"--external-ca=protocol=cfssl,url=https://some.example.com/https/url",
   113  			},
   114  			errorMsg: "flag requires the `--rotate` flag to update the CA",
   115  		},
   116  		{
   117  			args: []string{
   118  				"--rotate",
   119  				"--external-ca=protocol=cfssl,url=https://some.example.com/https/url",
   120  			},
   121  			errorMsg: "rotating to an external CA requires the `--ca-cert` flag to specify the external CA's cert - " +
   122  				"to add an external CA with the current root CA certificate, use the `update` command instead",
   123  		},
   124  		{
   125  			args: []string{
   126  				"--rotate",
   127  				"--ca-cert=" + tmpfile,
   128  			},
   129  			errorMsg: "the --ca-cert flag requires that a --ca-key flag and/or --external-ca flag be provided as well",
   130  		},
   131  	}
   132  
   133  	for _, testCase := range errorTestCases {
   134  		cmd := newCACommand(
   135  			test.NewFakeCli(&fakeClient{
   136  				swarmInspectFunc: func() (swarm.Swarm, error) {
   137  					return swarm.Swarm{
   138  						ClusterInfo: swarm.ClusterInfo{
   139  							TLSInfo: swarm.TLSInfo{
   140  								TrustRoot: "root",
   141  							},
   142  						},
   143  					}, nil
   144  				},
   145  			}))
   146  		assert.Check(t, cmd.Flags().Parse(testCase.args))
   147  		cmd.SetOut(io.Discard)
   148  		assert.ErrorContains(t, cmd.Execute(), testCase.errorMsg)
   149  	}
   150  }
   151  
   152  func TestDisplayTrustRoot(t *testing.T) {
   153  	buffer := new(bytes.Buffer)
   154  	trustRoot := "trustme"
   155  	err := displayTrustRoot(buffer, swarm.Swarm{
   156  		ClusterInfo: swarm.ClusterInfo{
   157  			TLSInfo: swarm.TLSInfo{TrustRoot: trustRoot},
   158  		},
   159  	})
   160  	assert.NilError(t, err)
   161  	assert.Check(t, is.Equal(trustRoot+"\n", buffer.String()))
   162  }
   163  
   164  type swarmUpdateRecorder struct {
   165  	spec swarm.Spec
   166  }
   167  
   168  func (s *swarmUpdateRecorder) swarmUpdate(sp swarm.Spec, _ swarm.UpdateFlags) error {
   169  	s.spec = sp
   170  	return nil
   171  }
   172  
   173  func swarmInspectFuncWithFullCAConfig() (swarm.Swarm, error) {
   174  	return swarm.Swarm{
   175  		ClusterInfo: swarm.ClusterInfo{
   176  			Spec: *swarmSpecWithFullCAConfig(),
   177  		},
   178  	}, nil
   179  }
   180  
   181  func TestUpdateSwarmSpecDefaultRotate(t *testing.T) {
   182  	s := &swarmUpdateRecorder{}
   183  	cli := test.NewFakeCli(&fakeClient{
   184  		swarmInspectFunc: swarmInspectFuncWithFullCAConfig,
   185  		swarmUpdateFunc:  s.swarmUpdate,
   186  	})
   187  	cmd := newCACommand(cli)
   188  	cmd.SetArgs([]string{"--rotate", "--detach"})
   189  	cmd.SetOut(cli.OutBuffer())
   190  	assert.NilError(t, cmd.Execute())
   191  
   192  	expected := swarmSpecWithFullCAConfig()
   193  	expected.CAConfig.ForceRotate = 2
   194  	expected.CAConfig.SigningCACert = ""
   195  	expected.CAConfig.SigningCAKey = ""
   196  	assert.Check(t, is.DeepEqual(*expected, s.spec))
   197  }
   198  
   199  func TestUpdateSwarmSpecCertAndKey(t *testing.T) {
   200  	certfile, err := writeFile(cert)
   201  	assert.NilError(t, err)
   202  	defer os.Remove(certfile)
   203  
   204  	keyfile, err := writeFile(key)
   205  	assert.NilError(t, err)
   206  	defer os.Remove(keyfile)
   207  
   208  	s := &swarmUpdateRecorder{}
   209  	cli := test.NewFakeCli(&fakeClient{
   210  		swarmInspectFunc: swarmInspectFuncWithFullCAConfig,
   211  		swarmUpdateFunc:  s.swarmUpdate,
   212  	})
   213  	cmd := newCACommand(cli)
   214  	cmd.SetArgs([]string{
   215  		"--rotate",
   216  		"--detach",
   217  		"--ca-cert=" + certfile,
   218  		"--ca-key=" + keyfile,
   219  		"--cert-expiry=3m",
   220  	})
   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  	})
   248  	cmd.SetOut(cli.OutBuffer())
   249  	assert.NilError(t, cmd.Execute())
   250  
   251  	expected := swarmSpecWithFullCAConfig()
   252  	expected.CAConfig.SigningCACert = cert
   253  	expected.CAConfig.SigningCAKey = ""
   254  	expected.CAConfig.ExternalCAs = []*swarm.ExternalCA{
   255  		{
   256  			Protocol: swarm.ExternalCAProtocolCFSSL,
   257  			URL:      "https://some.external.ca.example.com",
   258  			CACert:   cert,
   259  			Options:  make(map[string]string),
   260  		},
   261  	}
   262  	assert.Check(t, is.DeepEqual(*expected, s.spec))
   263  }
   264  
   265  func TestUpdateSwarmSpecCertAndKeyAndExternalCA(t *testing.T) {
   266  	certfile, err := writeFile(cert)
   267  	assert.NilError(t, err)
   268  	defer os.Remove(certfile)
   269  
   270  	keyfile, err := writeFile(key)
   271  	assert.NilError(t, err)
   272  	defer os.Remove(keyfile)
   273  
   274  	s := &swarmUpdateRecorder{}
   275  	cli := test.NewFakeCli(&fakeClient{
   276  		swarmInspectFunc: swarmInspectFuncWithFullCAConfig,
   277  		swarmUpdateFunc:  s.swarmUpdate,
   278  	})
   279  	cmd := newCACommand(cli)
   280  	cmd.SetArgs([]string{
   281  		"--rotate",
   282  		"--detach",
   283  		"--ca-cert=" + certfile,
   284  		"--ca-key=" + keyfile,
   285  		"--external-ca=protocol=cfssl,url=https://some.external.ca.example.com",
   286  	})
   287  	cmd.SetOut(cli.OutBuffer())
   288  	assert.NilError(t, cmd.Execute())
   289  
   290  	expected := swarmSpecWithFullCAConfig()
   291  	expected.CAConfig.SigningCACert = cert
   292  	expected.CAConfig.SigningCAKey = key
   293  	expected.CAConfig.ExternalCAs = []*swarm.ExternalCA{
   294  		{
   295  			Protocol: swarm.ExternalCAProtocolCFSSL,
   296  			URL:      "https://some.external.ca.example.com",
   297  			CACert:   cert,
   298  			Options:  make(map[string]string),
   299  		},
   300  	}
   301  	assert.Check(t, is.DeepEqual(*expected, s.spec))
   302  }