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 }