github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/manager/controlapi/ca_rotation_test.go (about) 1 package controlapi 2 3 import ( 4 "context" 5 "crypto/x509" 6 "encoding/pem" 7 "io/ioutil" 8 "os" 9 "testing" 10 "time" 11 12 "github.com/cloudflare/cfssl/helpers" 13 "github.com/cloudflare/cfssl/initca" 14 "github.com/docker/swarmkit/api" 15 "github.com/docker/swarmkit/ca" 16 "github.com/docker/swarmkit/ca/testutils" 17 "github.com/stretchr/testify/require" 18 "google.golang.org/grpc/codes" 19 "google.golang.org/grpc/status" 20 ) 21 22 type rootCARotationTestCase struct { 23 rootCA api.RootCA 24 caConfig api.CAConfig 25 26 // what to expect if the validate and update succeeds - we can't always check that everything matches, for instance if 27 // random values for join tokens or cross signed certs, or generated root rotation cert/key, 28 // are expected 29 expectRootCA api.RootCA 30 expectJoinTokenChange bool 31 expectGeneratedRootRotation bool 32 expectGeneratedCross bool 33 description string // in case an expectation fails 34 35 // what error string to expect if the validate fails 36 expectErrorString string 37 } 38 39 var initialLocalRootCA = api.RootCA{ 40 CACert: testutils.ECDSA256SHA256Cert, 41 CAKey: testutils.ECDSA256Key, 42 CACertHash: "DEADBEEF", 43 JoinTokens: api.JoinTokens{ 44 Worker: "SWMTKN-1-worker", 45 Manager: "SWMTKN-1-manager", 46 }, 47 } 48 var rotationCert, rotationKey = testutils.ECDSACertChain[2], testutils.ECDSACertChainKeys[2] 49 50 func uglifyOnePEM(pemBytes []byte) []byte { 51 pemBlock, _ := pem.Decode(pemBytes) 52 pemBlock.Headers = map[string]string{ 53 "this": "should", 54 "be": "removed", 55 } 56 return append(append([]byte("\n\t "), pem.EncodeToMemory(pemBlock)...), []byte(" \t")...) 57 } 58 59 func getSecurityConfig(t *testing.T, localRootCA *ca.RootCA, cluster *api.Cluster) *ca.SecurityConfig { 60 tempdir, err := ioutil.TempDir("", "test-validate-CA") 61 require.NoError(t, err) 62 defer os.RemoveAll(tempdir) 63 paths := ca.NewConfigPaths(tempdir) 64 secConfig, cancel, err := localRootCA.CreateSecurityConfig(context.Background(), ca.NewKeyReadWriter(paths.Node, nil, nil), ca.CertificateRequestConfig{}) 65 require.NoError(t, err) 66 cancel() 67 return secConfig 68 } 69 70 func TestValidateCAConfigInvalidValues(t *testing.T) { 71 t.Parallel() 72 localRootCA, err := ca.NewRootCA(initialLocalRootCA.CACert, initialLocalRootCA.CACert, initialLocalRootCA.CAKey, 73 ca.DefaultNodeCertExpiration, nil) 74 require.NoError(t, err) 75 76 initialExternalRootCA := initialLocalRootCA 77 initialExternalRootCA.CAKey = nil 78 79 crossSigned, err := localRootCA.CrossSignCACertificate(rotationCert) 80 require.NoError(t, err) 81 82 initExternalRootCAWithRotation := initialExternalRootCA 83 initExternalRootCAWithRotation.RootRotation = &api.RootRotation{ 84 CACert: rotationCert, 85 CAKey: rotationKey, 86 CrossSignedCACert: crossSigned, 87 } 88 89 initWithExternalRootRotation := initialLocalRootCA 90 initWithExternalRootRotation.RootRotation = &api.RootRotation{ 91 CACert: rotationCert, 92 CrossSignedCACert: crossSigned, 93 } 94 95 // set up 2 external CAs that can be contacted for signing 96 tempdir, err := ioutil.TempDir("", "test-validate-CA") 97 require.NoError(t, err) 98 defer os.RemoveAll(tempdir) 99 100 initExtServer, err := testutils.NewExternalSigningServer(localRootCA, tempdir) 101 require.NoError(t, err) 102 defer initExtServer.Stop() 103 104 // we need to accept client certs from the original cert 105 rotationRootCA, err := ca.NewRootCA(append(initialLocalRootCA.CACert, rotationCert...), rotationCert, rotationKey, 106 ca.DefaultNodeCertExpiration, nil) 107 require.NoError(t, err) 108 rotateExtServer, err := testutils.NewExternalSigningServer(rotationRootCA, tempdir) 109 require.NoError(t, err) 110 defer rotateExtServer.Stop() 111 112 for _, invalid := range []rootCARotationTestCase{ 113 { 114 rootCA: initialLocalRootCA, 115 caConfig: api.CAConfig{ 116 SigningCAKey: initialLocalRootCA.CAKey, 117 }, 118 expectErrorString: "the signing CA cert must also be provided", 119 }, 120 { 121 rootCA: initExternalRootCAWithRotation, // even if a root rotation is already in progress, the current CA external URL must be present 122 caConfig: api.CAConfig{ 123 ExternalCAs: []*api.ExternalCA{ 124 { 125 URL: initExtServer.URL, 126 CACert: initialLocalRootCA.CACert, 127 Protocol: 3, // wrong protocol 128 }, 129 { 130 URL: initExtServer.URL, 131 CACert: rotationCert, // wrong cert 132 }, 133 }, 134 }, 135 expectErrorString: "there must be at least one valid, reachable external CA corresponding to the current CA certificate", 136 }, 137 { 138 rootCA: initialExternalRootCA, 139 caConfig: api.CAConfig{ 140 SigningCACert: rotationCert, // even if there's a desired cert, the current CA external URL must be present 141 ExternalCAs: []*api.ExternalCA{ // right certs, but invalid URLs in several ways 142 { 143 URL: rotateExtServer.URL, 144 CACert: initialExternalRootCA.CACert, 145 }, 146 { 147 URL: "invalidurl", 148 CACert: initialExternalRootCA.CACert, 149 }, 150 { 151 URL: "https://too:many:colons:1:2:3", 152 CACert: initialExternalRootCA.CACert, 153 }, 154 }, 155 }, 156 expectErrorString: "there must be at least one valid, reachable external CA corresponding to the current CA certificate", 157 }, 158 { 159 rootCA: initialLocalRootCA, 160 caConfig: api.CAConfig{ 161 SigningCACert: rotationCert, 162 ExternalCAs: []*api.ExternalCA{ 163 { 164 URL: rotateExtServer.URL, 165 CACert: rotationCert, 166 Protocol: 3, // wrong protocol 167 }, 168 { 169 URL: rotateExtServer.URL, 170 // wrong cert because no cert is assumed to be the current root CA cert 171 }, 172 }, 173 }, 174 expectErrorString: "there must be at least one valid, reachable external CA corresponding to the desired CA certificate", 175 }, 176 { 177 rootCA: initialLocalRootCA, 178 caConfig: api.CAConfig{ 179 SigningCACert: rotationCert, 180 ExternalCAs: []*api.ExternalCA{ // right certs, but invalid URLs in several ways 181 { 182 URL: initExtServer.URL, 183 CACert: rotationCert, 184 }, 185 { 186 URL: "invalidurl", 187 CACert: rotationCert, 188 }, 189 { 190 URL: "https://too:many:colons:1:2:3", 191 CACert: initialExternalRootCA.CACert, 192 }, 193 }, 194 }, 195 expectErrorString: "there must be at least one valid, reachable external CA corresponding to the desired CA certificate", 196 }, 197 { 198 rootCA: initWithExternalRootRotation, 199 caConfig: api.CAConfig{ // no forceRotate change, no explicit signing cert change 200 ExternalCAs: []*api.ExternalCA{ 201 { 202 URL: rotateExtServer.URL, 203 CACert: rotationCert, 204 Protocol: 3, // wrong protocol 205 }, 206 { 207 URL: rotateExtServer.URL, 208 CACert: initialLocalRootCA.CACert, // wrong cert 209 }, 210 }, 211 }, 212 expectErrorString: "there must be at least one valid, reachable external CA corresponding to the next CA certificate", 213 }, 214 { 215 rootCA: initWithExternalRootRotation, 216 caConfig: api.CAConfig{ // no forceRotate change, no explicit signing cert change 217 ExternalCAs: []*api.ExternalCA{ 218 { 219 URL: initExtServer.URL, 220 CACert: rotationCert, 221 // right CA cert, but the server cert is not signed by this CA cert 222 }, 223 { 224 URL: "invalidurl", 225 CACert: rotationCert, 226 // right CA cert, but invalid URL 227 }, 228 }, 229 }, 230 expectErrorString: "there must be at least one valid, reachable external CA corresponding to the next CA certificate", 231 }, 232 { 233 rootCA: initialExternalRootCA, 234 caConfig: api.CAConfig{}, // removing the current external CA is not supported 235 expectErrorString: "there must be at least one valid, reachable external CA corresponding to the current CA certificate", 236 }, 237 { 238 rootCA: initialExternalRootCA, 239 caConfig: api.CAConfig{ 240 SigningCACert: rotationCert, 241 ExternalCAs: []*api.ExternalCA{ 242 { 243 URL: initExtServer.URL, 244 CACert: initialLocalRootCA.CACert, // current cert 245 }, 246 { 247 URL: rotateExtServer.URL, 248 CACert: rotationCert, //new cert 249 }, 250 }, 251 }, 252 expectErrorString: "rotating from one external CA to a different external CA is not supported", 253 }, 254 { 255 rootCA: initialExternalRootCA, 256 caConfig: api.CAConfig{ 257 SigningCACert: rotationCert, 258 ExternalCAs: []*api.ExternalCA{ 259 { 260 URL: initExtServer.URL, 261 // no cert means the current cert 262 }, 263 { 264 URL: rotateExtServer.URL, 265 CACert: rotationCert, //new cert 266 }, 267 }, 268 }, 269 expectErrorString: "rotating from one external CA to a different external CA is not supported", 270 }, 271 { 272 rootCA: initialLocalRootCA, 273 caConfig: api.CAConfig{ 274 SigningCACert: append(rotationCert, initialLocalRootCA.CACert...), 275 SigningCAKey: rotationKey, 276 }, 277 expectErrorString: "cannot contain multiple certificates", 278 }, 279 { 280 rootCA: initialLocalRootCA, 281 caConfig: api.CAConfig{ 282 SigningCACert: testutils.ReDateCert(t, rotationCert, rotationCert, rotationKey, 283 time.Now().Add(-1*time.Minute), time.Now().Add(364*helpers.OneDay)), 284 SigningCAKey: rotationKey, 285 }, 286 expectErrorString: "expires too soon", 287 }, 288 { 289 rootCA: initialLocalRootCA, 290 caConfig: api.CAConfig{ 291 SigningCACert: initialLocalRootCA.CACert, 292 SigningCAKey: testutils.ExpiredKey, // same cert but mismatching key 293 }, 294 expectErrorString: "certificate key mismatch", 295 }, 296 { 297 // this is just one class of failures caught by NewRootCA, not going to bother testing others, since they are 298 // extensively tested in NewRootCA 299 rootCA: initialLocalRootCA, 300 caConfig: api.CAConfig{ 301 SigningCACert: testutils.ExpiredCert, 302 SigningCAKey: testutils.ExpiredKey, 303 }, 304 expectErrorString: "expired", 305 }, 306 } { 307 cluster := &api.Cluster{ 308 RootCA: invalid.rootCA, 309 Spec: api.ClusterSpec{ 310 CAConfig: invalid.caConfig, 311 }, 312 } 313 secConfig := getSecurityConfig(t, &localRootCA, cluster) 314 _, err := validateCAConfig(context.Background(), secConfig, cluster) 315 require.Error(t, err, invalid.expectErrorString) 316 s, _ := status.FromError(err) 317 require.Equal(t, codes.InvalidArgument, s.Code(), invalid.expectErrorString) 318 require.Contains(t, s.Message(), invalid.expectErrorString) 319 } 320 } 321 322 func runValidTestCases(t *testing.T, testcases []*rootCARotationTestCase, localRootCA *ca.RootCA) { 323 for _, valid := range testcases { 324 cluster := &api.Cluster{ 325 RootCA: *valid.rootCA.Copy(), 326 Spec: api.ClusterSpec{ 327 CAConfig: valid.caConfig, 328 }, 329 } 330 secConfig := getSecurityConfig(t, localRootCA, cluster) 331 result, err := validateCAConfig(context.Background(), secConfig, cluster) 332 require.NoError(t, err, valid.description) 333 334 // ensure that the cluster was not mutated 335 require.Equal(t, valid.rootCA, cluster.RootCA) 336 337 // Because join tokens are random, we can't predict exactly what it is, so this needs to be manually checked 338 if valid.expectJoinTokenChange { 339 require.NotEmpty(t, result.JoinTokens, valid.rootCA.JoinTokens, valid.description) 340 } else { 341 require.Equal(t, result.JoinTokens, valid.rootCA.JoinTokens, valid.description) 342 } 343 result.JoinTokens = valid.expectRootCA.JoinTokens 344 345 // If a cross-signed certificates is generated, we cant know what it is ahead of time. All we can do is check that it's 346 // correctly generated. 347 if valid.expectGeneratedCross || valid.expectGeneratedRootRotation { // both generate cross signed certs 348 require.NotNil(t, result.RootRotation, valid.description) 349 require.NotEmpty(t, result.RootRotation.CrossSignedCACert, valid.description) 350 351 // make sure the cross-signed cert is signed by the current root CA (and not an intermediate, if a root rotation is in progress) 352 parsedCross, err := helpers.ParseCertificatePEM(result.RootRotation.CrossSignedCACert) // there should just be one 353 require.NoError(t, err) 354 _, err = parsedCross.Verify(x509.VerifyOptions{Roots: localRootCA.Pool}) 355 require.NoError(t, err, valid.description) 356 357 // if we are expecting generated certs or root rotation, we can expect the expected root CA has a root rotation 358 result.RootRotation.CrossSignedCACert = valid.expectRootCA.RootRotation.CrossSignedCACert 359 } 360 361 // If a root rotation cert is generated, we can't assert what the cert and key are. So if we expect it to be generated, 362 // just assert that the value has changed. 363 if valid.expectGeneratedRootRotation { 364 require.NotNil(t, result.RootRotation, valid.description) 365 require.NotEqual(t, valid.rootCA.RootRotation, result.RootRotation, valid.description) 366 result.RootRotation = valid.expectRootCA.RootRotation 367 } 368 369 require.Equal(t, result, &valid.expectRootCA, valid.description) 370 } 371 } 372 373 func TestValidateCAConfigValidValues(t *testing.T) { 374 t.Parallel() 375 localRootCA, err := ca.NewRootCA(testutils.ECDSA256SHA256Cert, testutils.ECDSA256SHA256Cert, testutils.ECDSA256Key, 376 ca.DefaultNodeCertExpiration, nil) 377 require.NoError(t, err) 378 379 parsedCert, err := helpers.ParseCertificatePEM(testutils.ECDSA256SHA256Cert) 380 require.NoError(t, err) 381 parsedKey, err := helpers.ParsePrivateKeyPEM(testutils.ECDSA256Key) 382 require.NoError(t, err) 383 384 initialExternalRootCA := initialLocalRootCA 385 initialExternalRootCA.CAKey = nil 386 387 // set up 2 external CAs that can be contacted for signing 388 tempdir, err := ioutil.TempDir("", "test-validate-CA") 389 require.NoError(t, err) 390 defer os.RemoveAll(tempdir) 391 392 initExtServer, err := testutils.NewExternalSigningServer(localRootCA, tempdir) 393 require.NoError(t, err) 394 defer initExtServer.Stop() 395 require.NoError(t, initExtServer.EnableCASigning()) 396 397 // we need to accept client certs from the original cert 398 rotationRootCA, err := ca.NewRootCA(append(initialLocalRootCA.CACert, rotationCert...), rotationCert, rotationKey, 399 ca.DefaultNodeCertExpiration, nil) 400 require.NoError(t, err) 401 rotateExtServer, err := testutils.NewExternalSigningServer(rotationRootCA, tempdir) 402 require.NoError(t, err) 403 defer rotateExtServer.Stop() 404 require.NoError(t, rotateExtServer.EnableCASigning()) 405 406 getExpectedRootCA := func(hasKey bool) api.RootCA { 407 result := initialLocalRootCA 408 result.LastForcedRotation = 5 409 result.JoinTokens = api.JoinTokens{} 410 if !hasKey { 411 result.CAKey = nil 412 } 413 return result 414 } 415 getRootCAWithRotation := func(base api.RootCA, cert, key, cross []byte) api.RootCA { 416 init := base 417 init.RootRotation = &api.RootRotation{ 418 CACert: cert, 419 CAKey: key, 420 CrossSignedCACert: cross, 421 } 422 return init 423 } 424 425 // no change in the CAConfig spec means no rotation 426 runValidTestCases(t, []*rootCARotationTestCase{ 427 { 428 description: "no specified config changes results no root rotation", 429 rootCA: initialLocalRootCA, 430 caConfig: api.CAConfig{}, 431 expectRootCA: initialLocalRootCA, 432 }, 433 }, &localRootCA) 434 435 // These require no rotation, because the cert is exactly the same or there is no change specified. 436 testcases := []*rootCARotationTestCase{ 437 { 438 description: "same desired cert and key as current Root CA results in no root rotation", 439 rootCA: initialLocalRootCA, 440 caConfig: api.CAConfig{ 441 SigningCACert: uglifyOnePEM(initialLocalRootCA.CACert), 442 SigningCAKey: initialLocalRootCA.CAKey, 443 ForceRotate: 5, 444 }, 445 expectRootCA: getExpectedRootCA(true), 446 }, 447 { 448 description: "same desired cert as current Root CA but external->internal (remove external CA is ok) results in no root rotation and no key -> key", 449 rootCA: initialExternalRootCA, 450 caConfig: api.CAConfig{ 451 SigningCACert: uglifyOnePEM(initialLocalRootCA.CACert), 452 SigningCAKey: initialLocalRootCA.CAKey, 453 ForceRotate: 5, 454 }, 455 expectRootCA: getExpectedRootCA(true), 456 }, 457 { 458 description: "same desired cert as current Root CA but internal->external results in no root rotation and key -> no key", 459 rootCA: initialLocalRootCA, 460 caConfig: api.CAConfig{ 461 SigningCACert: initialLocalRootCA.CACert, 462 ExternalCAs: []*api.ExternalCA{ 463 { 464 URL: initExtServer.URL, 465 CACert: uglifyOnePEM(initialLocalRootCA.CACert), 466 }, 467 }, 468 ForceRotate: 5, 469 }, 470 expectRootCA: getExpectedRootCA(false), 471 }, 472 { 473 description: "same desired cert and key as current Root CA but adding an external CA results in no root rotation and no key change", 474 rootCA: initialLocalRootCA, 475 caConfig: api.CAConfig{ 476 SigningCACert: initialLocalRootCA.CACert, 477 SigningCAKey: initialLocalRootCA.CAKey, 478 ExternalCAs: []*api.ExternalCA{ 479 { 480 URL: initExtServer.URL, 481 CACert: uglifyOnePEM(initialLocalRootCA.CACert), 482 }, 483 }, 484 ForceRotate: 5, 485 }, 486 expectRootCA: getExpectedRootCA(true), 487 }, 488 } 489 runValidTestCases(t, testcases, &localRootCA) 490 491 // These are the same test cases as above, but we are testing that it will abort root rotation because 492 // the desired cert is the same as the current RootCA cert 493 crossSigned, err := localRootCA.CrossSignCACertificate(rotationCert) 494 require.NoError(t, err) 495 for _, testcase := range testcases { 496 testcase.rootCA = getRootCAWithRotation(testcase.rootCA, rotationCert, rotationKey, crossSigned) 497 } 498 testcases[0].description = "same desired cert and key as current RootCA results in aborting root rotation" 499 testcases[1].description = "same desired cert as current Root CA but external->internal (remove external CA is ok) results in aborting root rotation and no key -> key" 500 testcases[2].description = "same desired cert, even if internal->external, as current RootCA results in aborting root rotation and key -> no key" 501 testcases[3].description = "same desired cert and key as current Root CA but adding an external CA results in aborting root rotation and no key change" 502 runValidTestCases(t, testcases, &localRootCA) 503 504 // These will not change the root rotation because the desired cert is the same as the current to-be-rotated-to cert 505 expectedBaseRootCA := getExpectedRootCA(true) // the main root CA expected will always have a signing key 506 testcases = []*rootCARotationTestCase{ 507 { 508 description: "same desired cert and key as current root rotation results in no change in root rotation", 509 rootCA: getRootCAWithRotation(initialLocalRootCA, rotationCert, rotationKey, crossSigned), 510 caConfig: api.CAConfig{ 511 SigningCACert: testutils.ECDSACertChain[2], 512 SigningCAKey: testutils.ECDSACertChainKeys[2], 513 ForceRotate: 5, 514 }, 515 expectRootCA: getRootCAWithRotation(expectedBaseRootCA, rotationCert, rotationKey, crossSigned), 516 }, 517 { 518 description: "same desired cert as current root rotation but external->internal results minor change in root rotation (no key -> key)", 519 rootCA: getRootCAWithRotation(initialLocalRootCA, rotationCert, nil, crossSigned), 520 caConfig: api.CAConfig{ 521 SigningCACert: testutils.ECDSACertChain[2], 522 SigningCAKey: testutils.ECDSACertChainKeys[2], 523 ForceRotate: 5, 524 }, 525 expectRootCA: getRootCAWithRotation(expectedBaseRootCA, rotationCert, rotationKey, crossSigned), 526 }, 527 { 528 description: "same desired cert as current root rotation but internal->external results minor change in root rotation (key -> no key)", 529 rootCA: getRootCAWithRotation(initialLocalRootCA, rotationCert, rotationKey, crossSigned), 530 caConfig: api.CAConfig{ 531 SigningCACert: testutils.ECDSACertChain[2], 532 ForceRotate: 5, 533 ExternalCAs: []*api.ExternalCA{ 534 { 535 URL: rotateExtServer.URL, 536 CACert: append(testutils.ECDSACertChain[2], ' '), 537 }, 538 }, 539 }, 540 expectRootCA: getRootCAWithRotation(expectedBaseRootCA, rotationCert, nil, crossSigned), 541 }, 542 } 543 runValidTestCases(t, testcases, &localRootCA) 544 545 // These all require a new root rotation because the desired cert is different, even if it has the same key and/or subject as the current 546 // cert or the current-to-be-rotated cert. 547 renewedInitialCert, err := initca.RenewFromSigner(parsedCert, parsedKey) 548 require.NoError(t, err) 549 parsedRotationCert, err := helpers.ParseCertificatePEM(rotationCert) 550 require.NoError(t, err) 551 parsedRotationKey, err := helpers.ParsePrivateKeyPEM(rotationKey) 552 require.NoError(t, err) 553 renewedRotationCert, err := initca.RenewFromSigner(parsedRotationCert, parsedRotationKey) 554 require.NoError(t, err) 555 differentInitialCert, err := testutils.CreateCertFromSigner("otherRootCN", parsedKey) 556 require.NoError(t, err) 557 differentRootCA, err := ca.NewRootCA(append(initialLocalRootCA.CACert, differentInitialCert...), differentInitialCert, 558 initialLocalRootCA.CAKey, ca.DefaultNodeCertExpiration, nil) 559 require.NoError(t, err) 560 differentExtServer, err := testutils.NewExternalSigningServer(differentRootCA, tempdir) 561 require.NoError(t, err) 562 defer differentExtServer.Stop() 563 require.NoError(t, differentExtServer.EnableCASigning()) 564 testcases = []*rootCARotationTestCase{ 565 { 566 description: "desired cert being a renewed current cert and key results in a root rotation because the cert has changed", 567 rootCA: initialLocalRootCA, 568 caConfig: api.CAConfig{ 569 SigningCACert: uglifyOnePEM(renewedInitialCert), 570 SigningCAKey: initialLocalRootCA.CAKey, 571 ForceRotate: 5, 572 }, 573 expectRootCA: getRootCAWithRotation(expectedBaseRootCA, renewedInitialCert, initialLocalRootCA.CAKey, nil), 574 expectGeneratedCross: true, 575 }, 576 { 577 description: "desired cert being a renewed current cert, external->internal results in a root rotation because the cert has changed", 578 rootCA: initialExternalRootCA, 579 caConfig: api.CAConfig{ 580 SigningCACert: uglifyOnePEM(renewedInitialCert), 581 SigningCAKey: initialLocalRootCA.CAKey, 582 ForceRotate: 5, 583 ExternalCAs: []*api.ExternalCA{ 584 { 585 URL: initExtServer.URL, 586 }, 587 }, 588 }, 589 expectRootCA: getRootCAWithRotation(getExpectedRootCA(false), renewedInitialCert, initialLocalRootCA.CAKey, nil), 590 expectGeneratedCross: true, 591 }, 592 { 593 description: "desired cert being a renewed current cert, internal->external results in a root rotation because the cert has changed", 594 rootCA: initialLocalRootCA, 595 caConfig: api.CAConfig{ 596 SigningCACert: append([]byte("\n\n"), renewedInitialCert...), 597 ForceRotate: 5, 598 ExternalCAs: []*api.ExternalCA{ 599 { 600 URL: initExtServer.URL, 601 CACert: uglifyOnePEM(renewedInitialCert), 602 }, 603 }, 604 }, 605 expectRootCA: getRootCAWithRotation(expectedBaseRootCA, renewedInitialCert, nil, nil), 606 expectGeneratedCross: true, 607 }, 608 { 609 description: "desired cert being a renewed rotation RootCA cert + rotation key results in replaced root rotation because the cert has changed", 610 rootCA: getRootCAWithRotation(initialLocalRootCA, rotationCert, rotationKey, crossSigned), 611 caConfig: api.CAConfig{ 612 SigningCACert: uglifyOnePEM(renewedRotationCert), 613 SigningCAKey: rotationKey, 614 ForceRotate: 5, 615 }, 616 expectRootCA: getRootCAWithRotation(expectedBaseRootCA, renewedRotationCert, rotationKey, nil), 617 expectGeneratedCross: true, 618 }, 619 { 620 description: "desired cert being a different rotation rootCA cert results in replaced root rotation (only new external CA required, not old rotation external CA)", 621 rootCA: getRootCAWithRotation(initialLocalRootCA, rotationCert, nil, crossSigned), 622 caConfig: api.CAConfig{ 623 SigningCACert: uglifyOnePEM(differentInitialCert), 624 ForceRotate: 5, 625 ExternalCAs: []*api.ExternalCA{ 626 { 627 // we need a different external server, because otherwise the external server's cert will fail to validate 628 // (not signed by the right cert - note that there's a bug in go 1.7 where this is not needed, because the 629 // subject names of cert names aren't checked, but go 1.8 fixes this.) 630 URL: differentExtServer.URL, 631 CACert: append([]byte("\n\t"), differentInitialCert...), 632 }, 633 }, 634 }, 635 expectRootCA: getRootCAWithRotation(expectedBaseRootCA, differentInitialCert, nil, nil), 636 expectGeneratedCross: true, 637 }, 638 } 639 runValidTestCases(t, testcases, &localRootCA) 640 641 // These require rotation because the cert and key are generated and hence completely different. 642 testcases = []*rootCARotationTestCase{ 643 { 644 description: "generating cert and key results in root rotation", 645 rootCA: initialLocalRootCA, 646 caConfig: api.CAConfig{ForceRotate: 5}, 647 expectRootCA: getRootCAWithRotation(getExpectedRootCA(true), nil, nil, nil), 648 expectGeneratedRootRotation: true, 649 }, 650 { 651 description: "generating cert for external->internal results in root rotation", 652 rootCA: initialExternalRootCA, 653 caConfig: api.CAConfig{ 654 ForceRotate: 5, 655 ExternalCAs: []*api.ExternalCA{ 656 { 657 URL: initExtServer.URL, 658 CACert: uglifyOnePEM(initialExternalRootCA.CACert), 659 }, 660 }, 661 }, 662 expectRootCA: getRootCAWithRotation(getExpectedRootCA(false), nil, nil, nil), 663 expectGeneratedRootRotation: true, 664 }, 665 { 666 description: "generating cert and key results in replacing root rotation", 667 rootCA: getRootCAWithRotation(initialLocalRootCA, rotationCert, rotationKey, crossSigned), 668 caConfig: api.CAConfig{ForceRotate: 5}, 669 expectRootCA: getRootCAWithRotation(getExpectedRootCA(true), nil, nil, nil), 670 expectGeneratedRootRotation: true, 671 }, 672 { 673 description: "generating cert and key results in replacing root rotation; external CAs required by old root rotation are no longer necessary", 674 rootCA: getRootCAWithRotation(initialLocalRootCA, rotationCert, nil, crossSigned), 675 caConfig: api.CAConfig{ForceRotate: 5}, 676 expectRootCA: getRootCAWithRotation(getExpectedRootCA(true), nil, nil, nil), 677 expectGeneratedRootRotation: true, 678 }, 679 } 680 runValidTestCases(t, testcases, &localRootCA) 681 682 // These require no change at all because the force rotate value hasn't changed, and there is no desired cert specified 683 testcases = []*rootCARotationTestCase{ 684 { 685 description: "no desired certificate specified, no force rotation: no change to internal signer root (which has no outstanding rotation)", 686 rootCA: initialLocalRootCA, 687 expectRootCA: initialLocalRootCA, 688 }, 689 { 690 description: "no desired certificate specified, no force rotation: no change to external CA root (which has no outstanding rotation)", 691 rootCA: initialExternalRootCA, 692 caConfig: api.CAConfig{ 693 ExternalCAs: []*api.ExternalCA{ 694 { 695 URL: initExtServer.URL, 696 CACert: uglifyOnePEM(initialExternalRootCA.CACert), 697 }, 698 }, 699 }, 700 expectRootCA: initialExternalRootCA, 701 }, 702 } 703 runValidTestCases(t, testcases, &localRootCA) 704 705 for _, testcase := range testcases { 706 testcase.rootCA = getRootCAWithRotation(testcase.rootCA, rotationCert, rotationKey, crossSigned) 707 testcase.expectRootCA = testcase.rootCA 708 } 709 testcases[0].description = "no desired certificate specified, no force rotation: no change to internal signer root or to outstanding rotation" 710 testcases[1].description = "no desired certificate specified, no force rotation: no change to external CA root or to outstanding rotation" 711 runValidTestCases(t, testcases, &localRootCA) 712 }