func TestNodeCertificateRenewalsDoNotRequireToken(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() csr, _, err := ca.GenerateNewCSR() assert.NoError(t, err) role := api.NodeRoleManager issueRequest := &api.IssueNodeCertificateRequest{CSR: csr, Role: role} issueResponse, err := tc.NodeCAClients[2].IssueNodeCertificate(context.Background(), issueRequest) assert.NoError(t, err) assert.NotNil(t, issueResponse.NodeID) assert.Equal(t, api.NodeMembershipAccepted, issueResponse.NodeMembership) statusRequest := &api.NodeCertificateStatusRequest{NodeID: issueResponse.NodeID} statusResponse, err := tc.NodeCAClients[2].NodeCertificateStatus(context.Background(), statusRequest) assert.NoError(t, err) assert.Equal(t, api.IssuanceStateIssued, statusResponse.Status.State) assert.NotNil(t, statusResponse.Certificate.Certificate) assert.Equal(t, role, statusResponse.Certificate.Role) role = api.NodeRoleWorker issueRequest = &api.IssueNodeCertificateRequest{CSR: csr, Role: role} issueResponse, err = tc.NodeCAClients[1].IssueNodeCertificate(context.Background(), issueRequest) require.NoError(t, err) assert.NotNil(t, issueResponse.NodeID) assert.Equal(t, api.NodeMembershipAccepted, issueResponse.NodeMembership) statusRequest = &api.NodeCertificateStatusRequest{NodeID: issueResponse.NodeID} statusResponse, err = tc.NodeCAClients[2].NodeCertificateStatus(context.Background(), statusRequest) require.NoError(t, err) assert.Equal(t, api.IssuanceStateIssued, statusResponse.Status.State) assert.NotNil(t, statusResponse.Certificate.Certificate) assert.Equal(t, role, statusResponse.Certificate.Role) }
func TestParseValidateAndSignCSR(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") assert.NoError(t, err) defer os.RemoveAll(tempBaseDir) paths := ca.NewConfigPaths(tempBaseDir) rootCA, err := ca.CreateRootCA("rootCN", paths.RootCA) assert.NoError(t, err) csr, _, err := ca.GenerateNewCSR() assert.NoError(t, err) signedCert, err := rootCA.ParseValidateAndSignCSR(csr, "CN", "OU", "ORG") assert.NoError(t, err) assert.NotNil(t, signedCert) parsedCert, err := helpers.ParseCertificatesPEM(signedCert) assert.NoError(t, err) assert.Equal(t, 2, len(parsedCert)) assert.Equal(t, "CN", parsedCert[0].Subject.CommonName) assert.Equal(t, 1, len(parsedCert[0].Subject.OrganizationalUnit)) assert.Equal(t, "OU", parsedCert[0].Subject.OrganizationalUnit[0]) assert.Equal(t, 3, len(parsedCert[0].Subject.Names)) assert.Equal(t, "ORG", parsedCert[0].Subject.Organization[0]) assert.Equal(t, "rootCN", parsedCert[1].Subject.CommonName) }
func TestIssueNodeCertificateBrokenCA(t *testing.T) { if !testutils.External { t.Skip("test only applicable for external CA configuration") } tc := testutils.NewTestCA(t) defer tc.Stop() csr, _, err := ca.GenerateNewCSR() assert.NoError(t, err) tc.ExternalSigningServer.Flake() go func() { time.Sleep(250 * time.Millisecond) tc.ExternalSigningServer.Deflake() }() issueRequest := &api.IssueNodeCertificateRequest{CSR: csr, Token: tc.WorkerToken} issueResponse, err := tc.NodeCAClients[0].IssueNodeCertificate(context.Background(), issueRequest) assert.NoError(t, err) assert.NotNil(t, issueResponse.NodeID) assert.Equal(t, api.NodeMembershipAccepted, issueResponse.NodeMembership) statusRequest := &api.NodeCertificateStatusRequest{NodeID: issueResponse.NodeID} statusResponse, err := tc.NodeCAClients[0].NodeCertificateStatus(context.Background(), statusRequest) require.NoError(t, err) assert.Equal(t, api.IssuanceStateIssued, statusResponse.Status.State) assert.NotNil(t, statusResponse.Certificate.Certificate) assert.Equal(t, api.NodeRoleWorker, statusResponse.Certificate.Role) }
func TestRenewTLSConfigWithNoNode(t *testing.T) { t.Parallel() tc := testutils.NewTestCA(t) defer tc.Stop() ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Get a new nodeConfig with a TLS cert that has the default Cert duration nodeConfig, err := tc.WriteNewNodeConfig(ca.ManagerRole) assert.NoError(t, err) // Create a new RootCA, and change the policy to issue 6 minute certificates. // Because of the default backdate of 5 minutes, this issues certificates // valid for 1 minute. newRootCA, err := ca.NewRootCA(tc.RootCA.Cert, tc.RootCA.Key, ca.DefaultNodeCertExpiration) assert.NoError(t, err) newRootCA.Signer.SetPolicy(&cfconfig.Signing{ Default: &cfconfig.SigningProfile{ Usage: []string{"signing", "key encipherment", "server auth", "client auth"}, Expiry: 6 * time.Minute, }, }) // Create a new CSR and overwrite the key on disk csr, key, err := ca.GenerateNewCSR() assert.NoError(t, err) // Issue a new certificate with the same details as the current config, but with 1 min expiration time c := nodeConfig.ClientTLSCreds signedCert, err := newRootCA.ParseValidateAndSignCSR(csr, c.NodeID(), c.Role(), c.Organization()) assert.NoError(t, err) assert.NotNil(t, signedCert) // Overwrite the certificate on disk with one that expires in 1 minute err = ioutils.AtomicWriteFile(tc.Paths.Node.Cert, signedCert, 0644) assert.NoError(t, err) err = ioutils.AtomicWriteFile(tc.Paths.Node.Key, key, 0600) assert.NoError(t, err) // Delete the node from the backend store err = tc.MemoryStore.Update(func(tx store.Tx) error { node := store.GetNode(tx, nodeConfig.ClientTLSCreds.NodeID()) assert.NotNil(t, node) return store.DeleteNode(tx, nodeConfig.ClientTLSCreds.NodeID()) }) assert.NoError(t, err) renew := make(chan struct{}) updates := ca.RenewTLSConfig(ctx, nodeConfig, tc.Remotes, renew) select { case <-time.After(10 * time.Second): assert.Fail(t, "TestRenewTLSConfig timed-out") case certUpdate := <-updates: assert.Error(t, certUpdate.Err) assert.Contains(t, certUpdate.Err.Error(), "not found when attempting to renew certificate") } }
func TestIssueNodeCertificateWorkerFromDifferentOrgRenewal(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() csr, _, err := ca.GenerateNewCSR() assert.NoError(t, err) // Since we're using a client that has a different Organization, this request will be treated // as a new certificate request, not allowing auto-renewal. Therefore, the request will fail. issueRequest := &api.IssueNodeCertificateRequest{CSR: csr} _, err = tc.NodeCAClients[3].IssueNodeCertificate(context.Background(), issueRequest) assert.Error(t, err) }
func TestEncryptECPrivateKey(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") assert.NoError(t, err) defer os.RemoveAll(tempBaseDir) _, key, err := ca.GenerateNewCSR() assert.NoError(t, err) encryptedKey, err := ca.EncryptECPrivateKey(key, "passphrase") assert.NoError(t, err) keyBlock, _ := pem.Decode(encryptedKey) assert.NotNil(t, keyBlock) assert.Equal(t, keyBlock.Headers["Proc-Type"], "4,ENCRYPTED") assert.Contains(t, keyBlock.Headers["DEK-Info"], "AES-256-CBC") }
func TestGetRemoteSignedCertificateNodeInfo(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() // Create a new CSR to be signed csr, _, err := ca.GenerateNewCSR() assert.NoError(t, err) cert, err := ca.GetRemoteSignedCertificate(context.Background(), csr, tc.RootCA.Pool, ca.CertificateRequestConfig{ Token: tc.WorkerToken, Remotes: tc.Remotes, }) assert.NoError(t, err) assert.NotNil(t, cert) }
func TestNewNodeCertificateBadToken(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() csr, _, err := ca.GenerateNewCSR() assert.NoError(t, err) // Issuance fails if wrong secret is provided role := api.NodeRoleManager issueRequest := &api.IssueNodeCertificateRequest{CSR: csr, Role: role, Token: "invalid-secret"} _, err = tc.NodeCAClients[0].IssueNodeCertificate(context.Background(), issueRequest) assert.EqualError(t, err, "rpc error: code = 3 desc = A valid join token is necessary to join this cluster") role = api.NodeRoleWorker issueRequest = &api.IssueNodeCertificateRequest{CSR: csr, Role: role, Token: "invalid-secret"} _, err = tc.NodeCAClients[0].IssueNodeCertificate(context.Background(), issueRequest) assert.EqualError(t, err, "rpc error: code = 3 desc = A valid join token is necessary to join this cluster") }
func TestForceRotationIsNoop(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() // Get a new Certificate issued csr, _, err := ca.GenerateNewCSR() assert.NoError(t, err) issueRequest := &api.IssueNodeCertificateRequest{CSR: csr, Token: tc.WorkerToken} issueResponse, err := tc.NodeCAClients[0].IssueNodeCertificate(context.Background(), issueRequest) assert.NoError(t, err) assert.NotNil(t, issueResponse.NodeID) assert.Equal(t, api.NodeMembershipAccepted, issueResponse.NodeMembership) // Check that the Certificate is successfully issued statusRequest := &api.NodeCertificateStatusRequest{NodeID: issueResponse.NodeID} statusResponse, err := tc.NodeCAClients[0].NodeCertificateStatus(context.Background(), statusRequest) require.NoError(t, err) assert.Equal(t, api.IssuanceStateIssued, statusResponse.Status.State) assert.NotNil(t, statusResponse.Certificate.Certificate) assert.Equal(t, api.NodeRoleWorker, statusResponse.Certificate.Role) // Update the certificate status to IssuanceStateRotate which should be a server-side noop err = tc.MemoryStore.Update(func(tx store.Tx) error { // Attempt to retrieve the node with nodeID node := store.GetNode(tx, issueResponse.NodeID) assert.NotNil(t, node) node.Certificate.Status.State = api.IssuanceStateRotate return store.UpdateNode(tx, node) }) assert.NoError(t, err) // Wait a bit and check that the certificate hasn't changed/been reissued time.Sleep(250 * time.Millisecond) statusNewResponse, err := tc.NodeCAClients[0].NodeCertificateStatus(context.Background(), statusRequest) require.NoError(t, err) assert.Equal(t, statusResponse.Certificate.Certificate, statusNewResponse.Certificate.Certificate) assert.Equal(t, api.IssuanceStateRotate, statusNewResponse.Certificate.Status.State) assert.Equal(t, api.NodeRoleWorker, statusNewResponse.Certificate.Role) }
func TestIssueNodeCertificate(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() csr, _, err := ca.GenerateNewCSR() assert.NoError(t, err) issueRequest := &api.IssueNodeCertificateRequest{CSR: csr, Token: tc.WorkerToken} issueResponse, err := tc.NodeCAClients[0].IssueNodeCertificate(context.Background(), issueRequest) assert.NoError(t, err) assert.NotNil(t, issueResponse.NodeID) assert.Equal(t, api.NodeMembershipAccepted, issueResponse.NodeMembership) statusRequest := &api.NodeCertificateStatusRequest{NodeID: issueResponse.NodeID} statusResponse, err := tc.NodeCAClients[0].NodeCertificateStatus(context.Background(), statusRequest) require.NoError(t, err) assert.Equal(t, api.IssuanceStateIssued, statusResponse.Status.State) assert.NotNil(t, statusResponse.Certificate.Certificate) assert.Equal(t, api.NodeRoleWorker, statusResponse.Certificate.Role) }
func TestNewRootCANonDefaultExpiry(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") assert.NoError(t, err) defer os.RemoveAll(tempBaseDir) paths := ca.NewConfigPaths(tempBaseDir) rootCA, err := ca.CreateRootCA("rootCN", paths.RootCA) assert.NoError(t, err) newRootCA, err := ca.NewRootCA(rootCA.Cert, rootCA.Key, 1*time.Hour) assert.NoError(t, err) // Create and sign a new CSR csr, _, err := ca.GenerateNewCSR() assert.NoError(t, err) cert, err := newRootCA.ParseValidateAndSignCSR(csr, "CN", ca.ManagerRole, "ORG") assert.NoError(t, err) parsedCerts, err := helpers.ParseCertificatesPEM(cert) assert.NoError(t, err) assert.Len(t, parsedCerts, 2) assert.True(t, time.Now().Add(time.Minute*59).Before(parsedCerts[0].NotAfter)) assert.True(t, time.Now().Add(time.Hour).Add(time.Minute).After(parsedCerts[0].NotAfter)) // Sign the same CSR again, this time with a 59 Minute expiration RootCA (under the 60 minute minimum). // This should use the default of 3 months newRootCA, err = ca.NewRootCA(rootCA.Cert, rootCA.Key, 59*time.Minute) assert.NoError(t, err) cert, err = newRootCA.ParseValidateAndSignCSR(csr, "CN", ca.ManagerRole, "ORG") assert.NoError(t, err) parsedCerts, err = helpers.ParseCertificatesPEM(cert) assert.NoError(t, err) assert.Len(t, parsedCerts, 2) assert.True(t, time.Now().Add(ca.DefaultNodeCertExpiration).AddDate(0, 0, -1).Before(parsedCerts[0].NotAfter)) assert.True(t, time.Now().Add(ca.DefaultNodeCertExpiration).AddDate(0, 0, 1).After(parsedCerts[0].NotAfter)) }
func TestGetRemoteSignedCertificate(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() // Create a new CSR to be signed csr, _, err := ca.GenerateNewCSR() assert.NoError(t, err) certs, err := ca.GetRemoteSignedCertificate(context.Background(), csr, tc.RootCA.Pool, ca.CertificateRequestConfig{ Token: tc.ManagerToken, Remotes: tc.Remotes, }) assert.NoError(t, err) assert.NotNil(t, certs) // Test the expiration for a manager certificate parsedCerts, err := helpers.ParseCertificatesPEM(certs) assert.NoError(t, err) assert.Len(t, parsedCerts, 2) assert.True(t, time.Now().Add(ca.DefaultNodeCertExpiration).AddDate(0, 0, -1).Before(parsedCerts[0].NotAfter)) assert.True(t, time.Now().Add(ca.DefaultNodeCertExpiration).AddDate(0, 0, 1).After(parsedCerts[0].NotAfter)) assert.Equal(t, parsedCerts[0].Subject.OrganizationalUnit[0], ca.ManagerRole) // Test the expiration for an worker certificate certs, err = ca.GetRemoteSignedCertificate(tc.Context, csr, tc.RootCA.Pool, ca.CertificateRequestConfig{ Token: tc.WorkerToken, Remotes: tc.Remotes, }) assert.NoError(t, err) assert.NotNil(t, certs) parsedCerts, err = helpers.ParseCertificatesPEM(certs) assert.NoError(t, err) assert.Len(t, parsedCerts, 2) assert.True(t, time.Now().Add(ca.DefaultNodeCertExpiration).AddDate(0, 0, -1).Before(parsedCerts[0].NotAfter)) assert.True(t, time.Now().Add(ca.DefaultNodeCertExpiration).AddDate(0, 0, 1).After(parsedCerts[0].NotAfter)) assert.Equal(t, parsedCerts[0].Subject.OrganizationalUnit[0], ca.WorkerRole) }
func TestGetRemoteSignedCertificateWithPending(t *testing.T) { t.Parallel() tc := testutils.NewTestCA(t) defer tc.Stop() // Create a new CSR to be signed csr, _, err := ca.GenerateNewCSR() assert.NoError(t, err) updates, cancel := state.Watch(tc.MemoryStore.WatchQueue(), state.EventCreateNode{}) defer cancel() completed := make(chan error) go func() { _, err := ca.GetRemoteSignedCertificate(context.Background(), csr, tc.RootCA.Pool, ca.CertificateRequestConfig{ Token: tc.WorkerToken, Remotes: tc.Remotes, }) completed <- err }() event := <-updates node := event.(state.EventCreateNode).Node.Copy() // Directly update the status of the store err = tc.MemoryStore.Update(func(tx store.Tx) error { node.Certificate.Status.State = api.IssuanceStateIssued return store.UpdateNode(tx, node) }) assert.NoError(t, err) // Make sure GetRemoteSignedCertificate didn't return an error assert.NoError(t, <-completed) }
func TestNewNodeCertificateRequiresToken(t *testing.T) { t.Parallel() tc := testutils.NewTestCA(t) defer tc.Stop() csr, _, err := ca.GenerateNewCSR() assert.NoError(t, err) // Issuance fails if no secret is provided role := api.NodeRoleManager issueRequest := &api.IssueNodeCertificateRequest{CSR: csr, Role: role} _, err = tc.NodeCAClients[0].IssueNodeCertificate(context.Background(), issueRequest) assert.EqualError(t, err, "rpc error: code = 3 desc = A valid join token is necessary to join this cluster") role = api.NodeRoleWorker issueRequest = &api.IssueNodeCertificateRequest{CSR: csr, Role: role} _, err = tc.NodeCAClients[0].IssueNodeCertificate(context.Background(), issueRequest) assert.EqualError(t, err, "rpc error: code = 3 desc = A valid join token is necessary to join this cluster") // Issuance fails if wrong secret is provided role = api.NodeRoleManager issueRequest = &api.IssueNodeCertificateRequest{CSR: csr, Role: role, Token: "invalid-secret"} _, err = tc.NodeCAClients[0].IssueNodeCertificate(context.Background(), issueRequest) assert.EqualError(t, err, "rpc error: code = 3 desc = A valid join token is necessary to join this cluster") role = api.NodeRoleWorker issueRequest = &api.IssueNodeCertificateRequest{CSR: csr, Role: role, Token: "invalid-secret"} _, err = tc.NodeCAClients[0].IssueNodeCertificate(context.Background(), issueRequest) assert.EqualError(t, err, "rpc error: code = 3 desc = A valid join token is necessary to join this cluster") // Issuance succeeds if correct token is provided role = api.NodeRoleManager issueRequest = &api.IssueNodeCertificateRequest{CSR: csr, Role: role, Token: tc.ManagerToken} _, err = tc.NodeCAClients[0].IssueNodeCertificate(context.Background(), issueRequest) assert.NoError(t, err) role = api.NodeRoleWorker issueRequest = &api.IssueNodeCertificateRequest{CSR: csr, Role: role, Token: tc.WorkerToken} _, err = tc.NodeCAClients[0].IssueNodeCertificate(context.Background(), issueRequest) assert.NoError(t, err) // Rotate manager and worker tokens var ( newManagerToken string newWorkerToken string ) assert.NoError(t, tc.MemoryStore.Update(func(tx store.Tx) error { clusters, _ := store.FindClusters(tx, store.ByName(store.DefaultClusterName)) newWorkerToken = ca.GenerateJoinToken(&tc.RootCA) clusters[0].RootCA.JoinTokens.Worker = newWorkerToken newManagerToken = ca.GenerateJoinToken(&tc.RootCA) clusters[0].RootCA.JoinTokens.Manager = newManagerToken return store.UpdateCluster(tx, clusters[0]) })) // updating the join token may take a little bit in order to register on the CA server, so poll assert.NoError(t, raftutils.PollFunc(nil, func() error { // Old token should fail role = api.NodeRoleManager issueRequest = &api.IssueNodeCertificateRequest{CSR: csr, Role: role, Token: tc.ManagerToken} _, err = tc.NodeCAClients[0].IssueNodeCertificate(context.Background(), issueRequest) if err == nil { return fmt.Errorf("join token not updated yet") } return nil })) // Old token should fail assert.EqualError(t, err, "rpc error: code = 3 desc = A valid join token is necessary to join this cluster") role = api.NodeRoleWorker issueRequest = &api.IssueNodeCertificateRequest{CSR: csr, Role: role, Token: tc.WorkerToken} _, err = tc.NodeCAClients[0].IssueNodeCertificate(context.Background(), issueRequest) assert.EqualError(t, err, "rpc error: code = 3 desc = A valid join token is necessary to join this cluster") // New token should succeed role = api.NodeRoleManager issueRequest = &api.IssueNodeCertificateRequest{CSR: csr, Role: role, Token: newManagerToken} _, err = tc.NodeCAClients[0].IssueNodeCertificate(context.Background(), issueRequest) assert.NoError(t, err) role = api.NodeRoleWorker issueRequest = &api.IssueNodeCertificateRequest{CSR: csr, Role: role, Token: newWorkerToken} _, err = tc.NodeCAClients[0].IssueNodeCertificate(context.Background(), issueRequest) assert.NoError(t, err) }
func TestLoadSecurityConfigExpiredCert(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() _, key, err := ca.GenerateNewCSR() require.NoError(t, err) require.NoError(t, ioutil.WriteFile(tc.Paths.Node.Key, key, 0600)) certKey, err := helpers.ParsePrivateKeyPEM(key) require.NoError(t, err) rootKey, err := helpers.ParsePrivateKeyPEM(tc.RootCA.Key) require.NoError(t, err) rootCert, err := helpers.ParseCertificatePEM(tc.RootCA.Cert) require.NoError(t, err) serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) require.NoError(t, err) genCert := func(notBefore, notAfter time.Time) { derBytes, err := x509.CreateCertificate(rand.Reader, &x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ CommonName: "CN", OrganizationalUnit: []string{"OU"}, Organization: []string{"ORG"}, }, NotBefore: notBefore, NotAfter: notAfter, }, rootCert, certKey.Public(), rootKey) require.NoError(t, err) certBytes := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: derBytes, }) require.NoError(t, ioutil.WriteFile(tc.Paths.Node.Cert, certBytes, 0644)) } krw := ca.NewKeyReadWriter(tc.Paths.Node, nil, nil) now := time.Now() // A cert that is not yet valid is not valid even if expiry is allowed genCert(now.Add(time.Hour), now.Add(time.Hour*2)) _, err = ca.LoadSecurityConfig(tc.Context, tc.RootCA, krw, false) require.Error(t, err) require.IsType(t, x509.CertificateInvalidError{}, errors.Cause(err)) _, err = ca.LoadSecurityConfig(tc.Context, tc.RootCA, krw, true) require.Error(t, err) require.IsType(t, x509.CertificateInvalidError{}, errors.Cause(err)) // a cert that is expired is not valid if expiry is not allowed genCert(now.Add(time.Hour*-3), now.Add(time.Hour*-1)) _, err = ca.LoadSecurityConfig(tc.Context, tc.RootCA, krw, false) require.Error(t, err) require.IsType(t, x509.CertificateInvalidError{}, errors.Cause(err)) // but it is valid if expiry is allowed _, err = ca.LoadSecurityConfig(tc.Context, tc.RootCA, krw, true) require.NoError(t, err) }