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 }