diff --git a/ca/certificates.go b/ca/certificates.go index ec7794bf35..74f8be473a 100644 --- a/ca/certificates.go +++ b/ca/certificates.go @@ -104,6 +104,12 @@ type CertPaths struct { Cert, Key string } +// IssuerInfo contains the subject and public key of the issuer of a certificate +type IssuerInfo struct { + Subject []byte + PublicKey []byte +} + // LocalSigner is a signer that can sign CSRs type LocalSigner struct { cfsigner.Signer @@ -192,39 +198,47 @@ func (rca *RootCA) Signer() (*LocalSigner, error) { } // IssueAndSaveNewCertificates generates a new key-pair, signs it with the local root-ca, and returns a -// tls certificate -func (rca *RootCA) IssueAndSaveNewCertificates(kw KeyWriter, cn, ou, org string) (*tls.Certificate, error) { +// TLS certificate and the issuer information for the certificate. +func (rca *RootCA) IssueAndSaveNewCertificates(kw KeyWriter, cn, ou, org string) (*tls.Certificate, *IssuerInfo, error) { csr, key, err := GenerateNewCSR() if err != nil { - return nil, errors.Wrap(err, "error when generating new node certs") + return nil, nil, errors.Wrap(err, "error when generating new node certs") } // Obtain a signed Certificate certChain, err := rca.ParseValidateAndSignCSR(csr, cn, ou, org) if err != nil { - return nil, errors.Wrap(err, "failed to sign node certificate") + return nil, nil, errors.Wrap(err, "failed to sign node certificate") + } + signer, err := rca.Signer() + if err != nil { // should never happen, since if ParseValidateAndSignCSR did not fail this root CA must have a signer + return nil, nil, err } // Create a valid TLSKeyPair out of the PEM encoded private key and certificate tlsKeyPair, err := tls.X509KeyPair(certChain, key) if err != nil { - return nil, err + return nil, nil, err } if err := kw.Write(certChain, key, nil); err != nil { - return nil, err + return nil, nil, err } - return &tlsKeyPair, nil + return &tlsKeyPair, &IssuerInfo{ + PublicKey: signer.parsedCert.RawSubjectPublicKeyInfo, + Subject: signer.parsedCert.RawSubject, + }, nil } // RequestAndSaveNewCertificates gets new certificates issued, either by signing them locally if a signer is -// available, or by requesting them from the remote server at remoteAddr. -func (rca *RootCA) RequestAndSaveNewCertificates(ctx context.Context, kw KeyWriter, config CertificateRequestConfig) (*tls.Certificate, error) { +// available, or by requesting them from the remote server at remoteAddr. This function returns the TLS +// certificate and the issuer information for the certificate. +func (rca *RootCA) RequestAndSaveNewCertificates(ctx context.Context, kw KeyWriter, config CertificateRequestConfig) (*tls.Certificate, *IssuerInfo, error) { // Create a new key/pair and CSR csr, key, err := GenerateNewCSR() if err != nil { - return nil, errors.Wrap(err, "error when generating new node certs") + return nil, nil, errors.Wrap(err, "error when generating new node certs") } // Get the remote manager to issue a CA signed certificate for this node @@ -246,41 +260,49 @@ func (rca *RootCA) RequestAndSaveNewCertificates(ctx context.Context, kw KeyWrit config.ForceRemote = true } if err != nil { - return nil, err + return nil, nil, err } // Доверяй, но проверяй. // Before we overwrite our local key + certificate, let's make sure the server gave us one that is valid // Create an X509Cert so we can .Verify() // Check to see if this certificate was signed by our CA, and isn't expired - parsedCerts, err := ValidateCertChain(rca.Pool, signedCert, false) + parsedCerts, chains, err := ValidateCertChain(rca.Pool, signedCert, false) if err != nil { - return nil, err + return nil, nil, err } + // ValidateChain, if successful, will always return at least 1 parsed cert and at least 1 chain containing + // at least 2 certificates: the leaf and the root. + leafCert := parsedCerts[0] + issuer := chains[0][1] + // Create a valid TLSKeyPair out of the PEM encoded private key and certificate tlsKeyPair, err := tls.X509KeyPair(signedCert, key) if err != nil { - return nil, err + return nil, nil, err } var kekUpdate *KEKData for i := 0; i < 5; i++ { // ValidateCertChain will always return at least 1 cert, so indexing at 0 is safe - kekUpdate, err = rca.getKEKUpdate(ctx, parsedCerts[0], tlsKeyPair, config.ConnBroker) + kekUpdate, err = rca.getKEKUpdate(ctx, leafCert, tlsKeyPair, config.ConnBroker) if err == nil { break } } if err != nil { - return nil, err + return nil, nil, err } if err := kw.Write(signedCert, key, kekUpdate); err != nil { - return nil, err + return nil, nil, err } - return &tlsKeyPair, nil + return &tlsKeyPair, &IssuerInfo{ + PublicKey: issuer.RawSubjectPublicKeyInfo, + Subject: issuer.RawSubject, + }, nil } func (rca *RootCA) getKEKUpdate(ctx context.Context, leafCert *x509.Certificate, keypair tls.Certificate, connBroker *connectionbroker.Broker) (*KEKData, error) { @@ -430,7 +452,7 @@ func NewRootCA(rootCertBytes, signCertBytes, signKeyBytes []byte, certExpiry tim var intermediatePool *x509.CertPool var parsedIntermediates []*x509.Certificate if len(intermediates) > 0 { - parsedIntermediates, err = ValidateCertChain(pool, intermediates, false) + parsedIntermediates, _, err = ValidateCertChain(pool, intermediates, false) if err != nil { return RootCA{}, errors.Wrap(err, "invalid intermediate chain") } @@ -469,15 +491,15 @@ func NewRootCA(rootCertBytes, signCertBytes, signKeyBytes []byte, certExpiry tim // intermediate pool), because this function is intended to be used when reading certs from untrusted locations such as // from disk or over a network when a CSR is signed, so it is extra pedantic. // This function always returns all the parsed certificates in the bundle in order, which means there will always be -// at least 1 certificate if there is no error. -func ValidateCertChain(rootPool *x509.CertPool, certs []byte, allowExpired bool) ([]*x509.Certificate, error) { +// at least 1 certificate if there is no error, and the valid chains found by Certificate.Verify +func ValidateCertChain(rootPool *x509.CertPool, certs []byte, allowExpired bool) ([]*x509.Certificate, [][]*x509.Certificate, error) { // Parse all the certificates in the cert bundle parsedCerts, err := helpers.ParseCertificatesPEM(certs) if err != nil { - return nil, err + return nil, nil, err } if len(parsedCerts) == 0 { - return nil, errors.New("no certificates to validate") + return nil, nil, errors.New("no certificates to validate") } now := time.Now() // ensure that they form a chain, each one being signed by the one after it @@ -486,7 +508,7 @@ func ValidateCertChain(rootPool *x509.CertPool, certs []byte, allowExpired bool) // Manual expiry validation because we want more information on which certificate in the chain is expired, and // because this is an easier way to allow expired certs. if now.Before(cert.NotBefore) { - return nil, errors.Wrapf( + return nil, nil, errors.Wrapf( x509.CertificateInvalidError{ Cert: cert, Reason: x509.Expired, @@ -495,7 +517,7 @@ func ValidateCertChain(rootPool *x509.CertPool, certs []byte, allowExpired bool) i+1, cert.Subject.CommonName, cert.NotBefore.UTC().Format(time.RFC1123), now.Format(time.RFC1123)) } if !allowExpired && now.After(cert.NotAfter) { - return nil, errors.Wrapf( + return nil, nil, errors.Wrapf( x509.CertificateInvalidError{ Cert: cert, Reason: x509.Expired, @@ -508,7 +530,7 @@ func ValidateCertChain(rootPool *x509.CertPool, certs []byte, allowExpired bool) // check that the previous cert was signed by this cert prevCert := parsedCerts[i-1] if err := prevCert.CheckSignatureFrom(cert); err != nil { - return nil, errors.Wrapf(err, "certificates do not form a chain: (%d - %s) is not signed by (%d - %s)", + return nil, nil, errors.Wrapf(err, "certificates do not form a chain: (%d - %s) is not signed by (%d - %s)", i, prevCert.Subject.CommonName, i+1, cert.Subject.CommonName) } @@ -526,6 +548,8 @@ func ValidateCertChain(rootPool *x509.CertPool, certs []byte, allowExpired bool) CurrentTime: now, } + var chains [][]*x509.Certificate + // If we accept expired certs, try to build a valid cert chain using some subset of the certs. We start off using the // first certificate's NotAfter as the current time, thus ensuring that the first cert is not expired. If the chain // still fails to validate due to expiry issues, continue iterating over the rest of the certs. @@ -540,22 +564,22 @@ func ValidateCertChain(rootPool *x509.CertPool, certs []byte, allowExpired bool) } verifyOpts.CurrentTime = cert.NotAfter - _, err = parsedCerts[0].Verify(verifyOpts) + chains, err = parsedCerts[0].Verify(verifyOpts) if err == nil { - return parsedCerts, nil + return parsedCerts, chains, nil } } if invalid, ok := err.(x509.CertificateInvalidError); ok && invalid.Reason == x509.Expired { - return nil, errors.New("there is no time span for which all of the certificates, including a root, are valid") + return nil, nil, errors.New("there is no time span for which all of the certificates, including a root, are valid") } - return nil, err + return nil, nil, err } - _, err = parsedCerts[0].Verify(verifyOpts) + chains, err = parsedCerts[0].Verify(verifyOpts) if err != nil { - return nil, err + return nil, nil, err } - return parsedCerts, nil + return parsedCerts, chains, nil } // newLocalSigner validates the signing cert and signing key to create a local signer, which accepts a crypto signer and a cert diff --git a/ca/certificates_test.go b/ca/certificates_test.go index ab8df146ce..768017165d 100644 --- a/ca/certificates_test.go +++ b/ca/certificates_test.go @@ -43,10 +43,10 @@ func init() { os.Setenv(ca.PassphraseENVVarPrev, "") } -func checkSingleCert(t *testing.T, certBytes []byte, issuerName, cn, ou, org string, additionalDNSNames ...string) { +func checkLeafCert(t *testing.T, certBytes []byte, issuerName, cn, ou, org string, additionalDNSNames ...string) []*x509.Certificate { certs, err := helpers.ParseCertificatesPEM(certBytes) require.NoError(t, err) - require.Len(t, certs, 1) + require.NotEmpty(t, certs) require.Equal(t, issuerName, certs[0].Issuer.CommonName) require.Equal(t, cn, certs[0].Subject.CommonName) require.Equal(t, []string{ou}, certs[0].Subject.OrganizationalUnit) @@ -56,6 +56,7 @@ func checkSingleCert(t *testing.T, certBytes []byte, issuerName, cn, ou, org str for _, dnsName := range append(additionalDNSNames, cn, ou) { require.Contains(t, certs[0].DNSNames, dnsName) } + return certs } // TestMain runs every test in this file twice - once with a local CA and @@ -215,7 +216,7 @@ func TestParseValidateAndSignCSR(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, signedCert) - checkSingleCert(t, signedCert, "rootCN", "CN", "OU", "ORG") + assert.Len(t, checkLeafCert(t, signedCert, "rootCN", "CN", "OU", "ORG"), 1) } func TestParseValidateAndSignMaliciousCSR(t *testing.T) { @@ -242,7 +243,7 @@ func TestParseValidateAndSignMaliciousCSR(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, signedCert) - checkSingleCert(t, signedCert, "rootCN", "CN", "OU", "ORG") + assert.Len(t, checkLeafCert(t, signedCert, "rootCN", "CN", "OU", "ORG"), 1) } func TestGetRemoteCA(t *testing.T) { @@ -298,7 +299,7 @@ func TestGetRemoteCA(t *testing.T) { for _, rootCA := range []ca.RootCA{tc.RootCA, otherRootCA} { krw := ca.NewKeyReadWriter(paths.Node, nil, nil) - _, err := rootCA.IssueAndSaveNewCertificates(krw, "cn", "ou", "org") + _, _, err := rootCA.IssueAndSaveNewCertificates(krw, "cn", "ou", "org") require.NoError(t, err) certPEM, _, err := krw.Read() @@ -323,18 +324,20 @@ func TestGetRemoteCAInvalidHash(t *testing.T) { assert.Error(t, err) } -func testRequestAndSaveNewCertificates(t *testing.T, tc *testutils.TestCA, numIntermediates int) { +// returns the issuer as well as all the parsed certs returned from the request +func testRequestAndSaveNewCertificates(t *testing.T, tc *testutils.TestCA) (*ca.IssuerInfo, []*x509.Certificate) { defer tc.Stop() // Copy the current RootCA without the signer rca := ca.RootCA{Certs: tc.RootCA.Certs, Pool: tc.RootCA.Pool} - tlsCert, err := rca.RequestAndSaveNewCertificates(tc.Context, tc.KeyReadWriter, + tlsCert, issuerInfo, err := rca.RequestAndSaveNewCertificates(tc.Context, tc.KeyReadWriter, ca.CertificateRequestConfig{ Token: tc.ManagerToken, ConnBroker: tc.ConnBroker, }) require.NoError(t, err) require.NotNil(t, tlsCert) + require.NotNil(t, issuerInfo) perms, err := permbits.Stat(tc.Paths.Node.Cert) require.NoError(t, err) require.False(t, perms.GroupWrite()) @@ -346,14 +349,19 @@ func testRequestAndSaveNewCertificates(t *testing.T, tc *testutils.TestCA, numIn // ensure that the same number of certs was written parsedCerts, err := helpers.ParseCertificatesPEM(certs) require.NoError(t, err) - require.Len(t, parsedCerts, 1+numIntermediates) + return issuerInfo, parsedCerts } func TestRequestAndSaveNewCertificatesNoIntermediate(t *testing.T) { t.Parallel() tc := testutils.NewTestCA(t) - testRequestAndSaveNewCertificates(t, tc, 0) + issuerInfo, parsedCerts := testRequestAndSaveNewCertificates(t, tc) + require.Len(t, parsedCerts, 1) + + root, err := helpers.ParseCertificatePEM(tc.RootCA.Certs) + require.NoError(t, err) + require.Equal(t, root.RawSubject, issuerInfo.Subject) } func TestRequestAndSaveNewCertificatesWithIntermediates(t *testing.T) { @@ -369,7 +377,14 @@ func TestRequestAndSaveNewCertificatesWithIntermediates(t *testing.T) { defer os.RemoveAll(tempdir) tc := testutils.NewTestCAFromRootCA(t, tempdir, rca, nil) - testRequestAndSaveNewCertificates(t, tc, 1) + issuerInfo, parsedCerts := testRequestAndSaveNewCertificates(t, tc) + require.Len(t, parsedCerts, 2) + + intermediate, err := helpers.ParseCertificatePEM(tc.RootCA.Intermediates) + require.NoError(t, err) + require.Equal(t, intermediate, parsedCerts[1]) + require.Equal(t, intermediate.RawSubject, issuerInfo.Subject) + require.Equal(t, intermediate.RawSubjectPublicKeyInfo, issuerInfo.PublicKey) } func TestRequestAndSaveNewCertificatesWithKEKUpdate(t *testing.T) { @@ -385,7 +400,7 @@ func TestRequestAndSaveNewCertificatesWithKEKUpdate(t *testing.T) { // key for the manager and worker are both unencrypted for _, token := range []string{tc.ManagerToken, tc.WorkerToken} { - _, err := rca.RequestAndSaveNewCertificates(tc.Context, tc.KeyReadWriter, + _, _, err := rca.RequestAndSaveNewCertificates(tc.Context, tc.KeyReadWriter, ca.CertificateRequestConfig{ Token: token, ConnBroker: tc.ConnBroker, @@ -413,7 +428,7 @@ func TestRequestAndSaveNewCertificatesWithKEKUpdate(t *testing.T) { // key for the manager will be encrypted, but certs for the worker will not be for _, token := range []string{tc.ManagerToken, tc.WorkerToken} { - _, err := rca.RequestAndSaveNewCertificates(tc.Context, tc.KeyReadWriter, + _, _, err := rca.RequestAndSaveNewCertificates(tc.Context, tc.KeyReadWriter, ca.CertificateRequestConfig{ Token: token, ConnBroker: tc.ConnBroker, @@ -433,36 +448,69 @@ func TestRequestAndSaveNewCertificatesWithKEKUpdate(t *testing.T) { } } -func TestIssueAndSaveNewCertificates(t *testing.T) { - tc := testutils.NewTestCA(t) - defer tc.Stop() +// returns the issuer of the issued certificate and the parsed certs of the issued certificate +func testIssueAndSaveNewCertificates(t *testing.T, rca *ca.RootCA) { + tempdir, err := ioutil.TempDir("", "test-issue-and-save-new-certificates") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + paths := ca.NewConfigPaths(tempdir) + krw := ca.NewKeyReadWriter(paths.Node, nil, nil) - // Test the creation of a manager certificate - cert, err := tc.RootCA.IssueAndSaveNewCertificates(tc.KeyReadWriter, "CN", ca.ManagerRole, tc.Organization) - assert.NoError(t, err) - assert.NotNil(t, cert) - perms, err := permbits.Stat(tc.Paths.Node.Cert) - assert.NoError(t, err) - assert.False(t, perms.GroupWrite()) - assert.False(t, perms.OtherWrite()) + var issuer *x509.Certificate + if len(rca.Intermediates) > 0 { + issuer, err = helpers.ParseCertificatePEM(rca.Intermediates) + require.NoError(t, err) + } else { + issuer, err = helpers.ParseCertificatePEM(rca.Certs) + require.NoError(t, err) + } - certBytes, err := ioutil.ReadFile(tc.Paths.Node.Cert) - assert.NoError(t, err) + // Test the creation of a manager and worker certificate + for _, role := range []string{ca.ManagerRole, ca.WorkerRole} { + var additionalNames []string + if role == ca.ManagerRole { + additionalNames = []string{ca.CARole} + } - checkSingleCert(t, certBytes, "swarm-test-CA", "CN", ca.ManagerRole, tc.Organization, ca.CARole) + cert, issuerInfo, err := rca.IssueAndSaveNewCertificates(krw, "CN", role, "org") + require.NoError(t, err) + require.NotNil(t, cert) + require.Equal(t, issuer.RawSubjectPublicKeyInfo, issuerInfo.PublicKey) + require.Equal(t, issuer.RawSubject, issuerInfo.Subject) + perms, err := permbits.Stat(paths.Node.Cert) + require.NoError(t, err) + require.False(t, perms.GroupWrite()) + require.False(t, perms.OtherWrite()) - // Test the creation of a worker node cert - cert, err = tc.RootCA.IssueAndSaveNewCertificates(tc.KeyReadWriter, "CN", ca.WorkerRole, tc.Organization) - assert.NoError(t, err) - assert.NotNil(t, cert) - perms, err = permbits.Stat(tc.Paths.Node.Cert) - assert.NoError(t, err) - assert.False(t, perms.GroupWrite()) - assert.False(t, perms.OtherWrite()) + certBytes, err := ioutil.ReadFile(paths.Node.Cert) + require.NoError(t, err) + parsed := checkLeafCert(t, certBytes, issuer.Subject.CommonName, "CN", role, "org", additionalNames...) + if len(rca.Intermediates) > 0 { + require.Len(t, parsed, 2) + require.Equal(t, parsed[1], issuer) + } else { + require.Len(t, parsed, 1) + } + } +} - certBytes, err = ioutil.ReadFile(tc.Paths.Node.Cert) - assert.NoError(t, err) - checkSingleCert(t, certBytes, "swarm-test-CA", "CN", ca.WorkerRole, tc.Organization) +func TestIssueAndSaveNewCertificatesNoIntermediates(t *testing.T) { + if testutils.External { + return // this does not use the test CA at all + } + rca, err := ca.CreateRootCA("rootCN") + require.NoError(t, err) + testIssueAndSaveNewCertificates(t, &rca) +} + +func TestIssueAndSaveNewCertificatesWithIntermediates(t *testing.T) { + if testutils.External { + return // this does not use the test CA at all + } + rca, err := ca.NewRootCA(testutils.ECDSACertChain[2], testutils.ECDSACertChain[1], testutils.ECDSACertChainKeys[1], + ca.DefaultNodeCertExpiration, testutils.ECDSACertChain[1]) + require.NoError(t, err) + testIssueAndSaveNewCertificates(t, &rca) } func TestGetRemoteSignedCertificate(t *testing.T) { @@ -902,12 +950,12 @@ func TestNewRootCABundle(t *testing.T) { // If I use newRootCA's IssueAndSaveNewCertificates to sign certs, I'll get the correct CA in the chain kw := ca.NewKeyReadWriter(paths.Node, nil, nil) - _, err = newRootCA.IssueAndSaveNewCertificates(kw, "CN", "OU", "ORG") + _, _, err = newRootCA.IssueAndSaveNewCertificates(kw, "CN", "OU", "ORG") assert.NoError(t, err) certBytes, err := ioutil.ReadFile(paths.Node.Cert) assert.NoError(t, err) - checkSingleCert(t, certBytes, "rootCN1", "CN", "OU", "ORG") + assert.Len(t, checkLeafCert(t, certBytes, "rootCN1", "CN", "OU", "ORG"), 1) } func TestNewRootCANonDefaultExpiry(t *testing.T) { @@ -1165,15 +1213,17 @@ func TestRootCAWithCrossSignedIntermediates(t *testing.T) { paths := ca.NewConfigPaths(tempdir) krw := ca.NewKeyReadWriter(paths.Node, nil, nil) - _, err = signWithIntermediate.IssueAndSaveNewCertificates(krw, "cn", "ou", "org") + _, _, err = signWithIntermediate.IssueAndSaveNewCertificates(krw, "cn", "ou", "org") require.NoError(t, err) tlsCert, _, err := krw.Read() require.NoError(t, err) - parsedCerts, err := ca.ValidateCertChain(signWithIntermediate.Pool, tlsCert, false) + parsedCerts, chains, err := ca.ValidateCertChain(signWithIntermediate.Pool, tlsCert, false) require.NoError(t, err) require.Len(t, parsedCerts, 2) + require.Len(t, chains, 1) require.Equal(t, parsedIntermediate.Raw, parsedCerts[1].Raw) + require.Equal(t, parsedCerts, chains[0][:len(chains[0])-1]) // the last one is the root oldRoot, err := ca.NewRootCA(testutils.ECDSACertChain[2], testutils.ECDSACertChain[2], testutils.ECDSACertChainKeys[2], ca.DefaultNodeCertExpiration, nil) require.NoError(t, err) @@ -1181,12 +1231,26 @@ func TestRootCAWithCrossSignedIntermediates(t *testing.T) { newRoot, err := ca.NewRootCA(fauxRootCert, fauxRootCert, testutils.ECDSACertChainKeys[1], ca.DefaultNodeCertExpiration, nil) require.NoError(t, err) - for _, root := range []ca.RootCA{signWithIntermediate, oldRoot, newRoot} { - parsedCerts, err = ca.ValidateCertChain(root.Pool, tlsCert, false) - require.NoError(t, err) - require.Len(t, parsedCerts, 2) - require.Equal(t, parsedIntermediate.Raw, parsedCerts[1].Raw) + checkValidateAgainstAllRoots := func(cert []byte) { + for i, root := range []ca.RootCA{signWithIntermediate, oldRoot, newRoot} { + parsedCerts, chains, err := ca.ValidateCertChain(root.Pool, cert, false) + require.NoError(t, err) + require.Len(t, parsedCerts, 2) + require.Len(t, chains, 1) + require.True(t, len(chains[0]) >= 2) // there are always at least 2 certs at minimum: the leaf and the root + require.Equal(t, parsedCerts[0], chains[0][0]) + require.Equal(t, parsedIntermediate.Raw, parsedCerts[1].Raw) + + chainWithoutRoot := chains[0][:len(chains[0])-1] + if i == 2 { + // against the new root, the cert can chain directly up to the root without the intermediate + require.Equal(t, parsedCerts[0:1], chainWithoutRoot) + } else { + require.Equal(t, parsedCerts, chainWithoutRoot) + } + } } + checkValidateAgainstAllRoots(tlsCert) if !testutils.External { return @@ -1213,12 +1277,7 @@ func TestRootCAWithCrossSignedIntermediates(t *testing.T) { tlsCert, err = externalCA.Sign(context.Background(), ca.PrepareCSR(newCSR, "cn", ca.ManagerRole, secConfig.ClientTLSCreds.Organization())) require.NoError(t, err) - for _, root := range []ca.RootCA{signWithIntermediate, oldRoot, newRoot} { - parsedCerts, err = ca.ValidateCertChain(root.Pool, tlsCert, false) - require.NoError(t, err) - require.Len(t, parsedCerts, 2) - require.Equal(t, parsedIntermediate.Raw, parsedCerts[1].Raw) - } + checkValidateAgainstAllRoots(tlsCert) } func TestNewRootCAWithPassphrase(t *testing.T) { @@ -1387,7 +1446,7 @@ func TestValidateCertificateChain(t *testing.T) { for _, invalid := range invalids { pool := x509.NewCertPool() pool.AppendCertsFromPEM(invalid.root) - _, err := ca.ValidateCertChain(pool, invalid.cert, invalid.allowExpiry) + _, _, err := ca.ValidateCertChain(pool, invalid.cert, invalid.allowExpiry) require.Error(t, err, invalid.errorStr) require.Contains(t, err.Error(), invalid.errorStr) } @@ -1412,8 +1471,13 @@ func TestValidateCertificateChain(t *testing.T) { } for _, valid := range valids { - _, err := ca.ValidateCertChain(rootPool, valid.cert, valid.allowExpiry) + parsedCerts, chains, err := ca.ValidateCertChain(rootPool, valid.cert, valid.allowExpiry) require.NoError(t, err) + require.NotEmpty(t, chain) + for _, chain := range chains { + require.Equal(t, parsedCerts[0], chain[0]) // the leaf certs are equal + require.True(t, len(chain) >= 2) + } } } @@ -1439,7 +1503,7 @@ func TestRootCACrossSignCACertificate(t *testing.T) { paths := ca.NewConfigPaths(tempdir) krw := ca.NewKeyReadWriter(paths.Node, nil, nil) - _, err = rootCA2.IssueAndSaveNewCertificates(krw, "cn", "ou", "org") + _, _, err = rootCA2.IssueAndSaveNewCertificates(krw, "cn", "ou", "org") require.NoError(t, err) certBytes, _, err := krw.Read() require.NoError(t, err) diff --git a/ca/config.go b/ca/config.go index 6f469636b7..6fcc5cb362 100644 --- a/ca/config.go +++ b/ca/config.go @@ -71,6 +71,9 @@ type SecurityConfig struct { externalCA *ExternalCA keyReadWriter *KeyReadWriter + certificate *tls.Certificate + issuerInfo *IssuerInfo + externalCAClientRootPool *x509.CertPool ServerTLSCreds *MutableTLSCreds @@ -85,25 +88,41 @@ type CertificateUpdate struct { } // NewSecurityConfig initializes and returns a new SecurityConfig. -func NewSecurityConfig(rootCA *RootCA, krw *KeyReadWriter, clientTLSCreds, serverTLSCreds *MutableTLSCreds) *SecurityConfig { +func NewSecurityConfig(rootCA *RootCA, krw *KeyReadWriter, tlsKeyPair *tls.Certificate, issuerInfo *IssuerInfo) (*SecurityConfig, error) { + // Create the Server TLS Credentials for this node. These will not be used by workers. + serverTLSCreds, err := rootCA.NewServerTLSCredentials(tlsKeyPair) + if err != nil { + return nil, err + } + + // Create a TLSConfig to be used when this node connects as a client to another remote node. + // We're using ManagerRole as remote serverName for TLS host verification because both workers + // and managers always connect to remote managers. + clientTLSCreds, err := rootCA.NewClientTLSCredentials(tlsKeyPair, ManagerRole) + if err != nil { + return nil, err + } + // Make a new TLS config for the external CA client without a // ServerName value set. - clientTLSConfig := clientTLSCreds.Config() - externalCATLSConfig := &tls.Config{ - Certificates: clientTLSConfig.Certificates, - RootCAs: clientTLSConfig.RootCAs, + Certificates: []tls.Certificate{*tlsKeyPair}, + RootCAs: rootCA.Pool, MinVersion: tls.VersionTLS12, } return &SecurityConfig{ - rootCA: rootCA, - keyReadWriter: krw, + rootCA: rootCA, + keyReadWriter: krw, + + certificate: tlsKeyPair, + issuerInfo: issuerInfo, + externalCA: NewExternalCA(rootCA, externalCATLSConfig), ClientTLSCreds: clientTLSCreds, ServerTLSCreds: serverTLSCreds, externalCAClientRootPool: rootCA.Pool, - } + }, nil } // RootCA returns the root CA. @@ -136,19 +155,26 @@ func (s *SecurityConfig) UpdateRootCA(rootCA *RootCA, externalCARootPool *x509.C s.rootCA = rootCA s.externalCAClientRootPool = externalCARootPool - clientTLSConfig := s.ClientTLSCreds.Config() - return s.updateTLSCredentials(clientTLSConfig.Certificates) + return s.updateTLSCredentials(s.certificate, s.issuerInfo) +} + +// IssuerInfo returns the issuer subject and issuer public key +func (s *SecurityConfig) IssuerInfo() *IssuerInfo { + s.mu.Lock() + defer s.mu.Unlock() + return s.issuerInfo } // updateTLSCredentials updates the client, server, and TLS credentials on a security config. This function expects // something else to have taken out a lock on the SecurityConfig. -func (s *SecurityConfig) updateTLSCredentials(certificates []tls.Certificate) error { - clientConfig, err := NewClientTLSConfig(certificates, s.rootCA.Pool, ManagerRole) +func (s *SecurityConfig) updateTLSCredentials(certificate *tls.Certificate, issuerInfo *IssuerInfo) error { + certs := []tls.Certificate{*certificate} + clientConfig, err := NewClientTLSConfig(certs, s.rootCA.Pool, ManagerRole) if err != nil { return errors.Wrap(err, "failed to create a new client config using the new root CA") } - serverConfig, err := NewServerTLSConfig(certificates, s.rootCA.Pool) + serverConfig, err := NewServerTLSConfig(certs, s.rootCA.Pool) if err != nil { return errors.Wrap(err, "failed to create a new server config using the new root CA") } @@ -160,7 +186,7 @@ func (s *SecurityConfig) updateTLSCredentials(certificates []tls.Certificate) er // Update the external CA to use the new client TLS // config using a copy without a serverName specified. s.externalCA.UpdateTLSConfig(&tls.Config{ - Certificates: certificates, + Certificates: certs, RootCAs: s.externalCAClientRootPool, MinVersion: tls.VersionTLS12, }) @@ -169,6 +195,8 @@ func (s *SecurityConfig) updateTLSCredentials(certificates []tls.Certificate) er return errors.Wrap(err, "failed to update the server TLS credentials") } + s.certificate = certificate + s.issuerInfo = issuerInfo return nil } @@ -300,9 +328,13 @@ func LoadSecurityConfig(ctx context.Context, rootCA RootCA, krw *KeyReadWriter, } // Check to see if this certificate was signed by our CA, and isn't expired - if _, err := ValidateCertChain(rootCA.Pool, cert, allowExpired); err != nil { + _, chains, err := ValidateCertChain(rootCA.Pool, cert, allowExpired) + if err != nil { return nil, err } + // ValidateChain, if successful, will always return at least 1 chain containing + // at least 2 certificates: the leaf and the root. + issuer := chains[0][1] // Now that we know this certificate is valid, create a TLS Certificate for our // credentials @@ -311,26 +343,17 @@ func LoadSecurityConfig(ctx context.Context, rootCA RootCA, krw *KeyReadWriter, return nil, err } - // Load the Certificates as server credentials - serverTLSCreds, err := rootCA.NewServerTLSCredentials(&keyPair) - if err != nil { - return nil, err - } - - // Load the Certificates also as client credentials. - // Both workers and managers always connect to remote managers, - // so ServerName is always set to ManagerRole here. - clientTLSCreds, err := rootCA.NewClientTLSCredentials(&keyPair, ManagerRole) - if err != nil { - return nil, err + secConfig, err := NewSecurityConfig(&rootCA, krw, &keyPair, &IssuerInfo{ + Subject: issuer.RawSubject, + PublicKey: issuer.RawSubjectPublicKeyInfo, + }) + if err == nil { + log.G(ctx).WithFields(logrus.Fields{ + "node.id": secConfig.ClientTLSCreds.NodeID(), + "node.role": secConfig.ClientTLSCreds.Role(), + }).Debug("loaded node credentials") } - - log.G(ctx).WithFields(logrus.Fields{ - "node.id": clientTLSCreds.NodeID(), - "node.role": clientTLSCreds.Role(), - }).Debug("loaded node credentials") - - return NewSecurityConfig(&rootCA, krw, clientTLSCreds, serverTLSCreds), nil + return secConfig, err } // CertificateRequestConfig contains the information needed to request a @@ -365,12 +388,12 @@ func (rootCA RootCA) CreateSecurityConfig(ctx context.Context, krw *KeyReadWrite org := identity.NewID() proposedRole := ManagerRole - tlsKeyPair, err := rootCA.IssueAndSaveNewCertificates(krw, cn, proposedRole, org) + tlsKeyPair, issuerInfo, err := rootCA.IssueAndSaveNewCertificates(krw, cn, proposedRole, org) switch errors.Cause(err) { case ErrNoValidSigner: // Request certificate issuance from a remote CA. // Last argument is nil because at this point we don't have any valid TLS creds - tlsKeyPair, err = rootCA.RequestAndSaveNewCertificates(ctx, krw, config) + tlsKeyPair, issuerInfo, err = rootCA.RequestAndSaveNewCertificates(ctx, krw, config) if err != nil { log.G(ctx).WithError(err).Error("failed to request save new certificate") return nil, err @@ -388,24 +411,14 @@ func (rootCA RootCA) CreateSecurityConfig(ctx context.Context, krw *KeyReadWrite return nil, err } - // Create the Server TLS Credentials for this node. These will not be used by workers. - serverTLSCreds, err := rootCA.NewServerTLSCredentials(tlsKeyPair) - if err != nil { - return nil, err - } - - // Create a TLSConfig to be used when this node connects as a client to another remote node. - // We're using ManagerRole as remote serverName for TLS host verification - clientTLSCreds, err := rootCA.NewClientTLSCredentials(tlsKeyPair, ManagerRole) - if err != nil { - return nil, err + secConfig, err := NewSecurityConfig(&rootCA, krw, tlsKeyPair, issuerInfo) + if err == nil { + log.G(ctx).WithFields(logrus.Fields{ + "node.id": secConfig.ClientTLSCreds.NodeID(), + "node.role": secConfig.ClientTLSCreds.Role(), + }).Debugf("new node credentials generated: %s", krw.Target()) } - log.G(ctx).WithFields(logrus.Fields{ - "node.id": clientTLSCreds.NodeID(), - "node.role": clientTLSCreds.Role(), - }).Debugf("new node credentials generated: %s", krw.Target()) - - return NewSecurityConfig(&rootCA, krw, clientTLSCreds, serverTLSCreds), nil + return secConfig, err } // RenewTLSConfigNow gets a new TLS cert and key, and updates the security config if provided. This is similar to @@ -422,7 +435,7 @@ func RenewTLSConfigNow(ctx context.Context, s *SecurityConfig, connBroker *conne // Let's request new certs. Renewals don't require a token. rootCA := s.RootCA() - tlsKeyPair, err := rootCA.RequestAndSaveNewCertificates(ctx, + tlsKeyPair, issuerInfo, err := rootCA.RequestAndSaveNewCertificates(ctx, s.KeyWriter(), CertificateRequestConfig{ ConnBroker: connBroker, @@ -434,7 +447,7 @@ func RenewTLSConfigNow(ctx context.Context, s *SecurityConfig, connBroker *conne } s.mu.Lock() defer s.mu.Unlock() - return s.updateTLSCredentials([]tls.Certificate{*tlsKeyPair}) + return s.updateTLSCredentials(tlsKeyPair, issuerInfo) } // RenewTLSConfig will continuously monitor for the necessity of renewing the local certificates, either by diff --git a/ca/config_test.go b/ca/config_test.go index 4267b90272..485a7181cb 100644 --- a/ca/config_test.go +++ b/ca/config_test.go @@ -16,9 +16,9 @@ import ( "golang.org/x/net/context" cfconfig "github.com/cloudflare/cfssl/config" + "github.com/cloudflare/cfssl/helpers" "github.com/docker/swarmkit/ca" "github.com/docker/swarmkit/ca/testutils" - "github.com/docker/swarmkit/ioutils" "github.com/docker/swarmkit/manager/state/store" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -83,8 +83,12 @@ func TestDownloadRootCAWrongCAHash(t *testing.T) { } func TestCreateSecurityConfigEmptyDir(t *testing.T) { + if testutils.External { + return // this doesn't require any servers at all + } tc := testutils.NewTestCA(t) defer tc.Stop() + assert.NoError(t, tc.CAServer.Stop()) // Remove all the contents from the temp dir and try again with a new node os.RemoveAll(tc.TempDir) @@ -99,45 +103,59 @@ func TestCreateSecurityConfigEmptyDir(t *testing.T) { assert.NotNil(t, nodeConfig.ClientTLSCreds) assert.NotNil(t, nodeConfig.ServerTLSCreds) assert.Equal(t, tc.RootCA, *nodeConfig.RootCA()) + + root, err := helpers.ParseCertificatePEM(tc.RootCA.Certs) + assert.NoError(t, err) + + issuerInfo := nodeConfig.IssuerInfo() + assert.NotNil(t, issuerInfo) + assert.Equal(t, root.RawSubjectPublicKeyInfo, issuerInfo.PublicKey) + assert.Equal(t, root.RawSubject, issuerInfo.Subject) } func TestCreateSecurityConfigNoCerts(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() + krw := ca.NewKeyReadWriter(tc.Paths.Node, nil, nil) + root, err := helpers.ParseCertificatePEM(tc.RootCA.Certs) + assert.NoError(t, err) + + validateNodeConfig := func(rootCA *ca.RootCA) { + nodeConfig, err := rootCA.CreateSecurityConfig(tc.Context, krw, + ca.CertificateRequestConfig{ + Token: tc.WorkerToken, + ConnBroker: tc.ConnBroker, + }) + assert.NoError(t, err) + assert.NotNil(t, nodeConfig) + assert.NotNil(t, nodeConfig.ClientTLSCreds) + assert.NotNil(t, nodeConfig.ServerTLSCreds) + assert.Equal(t, tc.RootCA, *nodeConfig.RootCA()) + + issuerInfo := nodeConfig.IssuerInfo() + assert.NotNil(t, issuerInfo) + assert.Equal(t, root.RawSubjectPublicKeyInfo, issuerInfo.PublicKey) + assert.Equal(t, root.RawSubject, issuerInfo.Subject) + } + // Remove only the node certificates form the directory, and attest that we get // new certificates that are locally signed os.RemoveAll(tc.Paths.Node.Cert) - krw := ca.NewKeyReadWriter(tc.Paths.Node, nil, nil) - nodeConfig, err := tc.RootCA.CreateSecurityConfig(tc.Context, krw, - ca.CertificateRequestConfig{ - Token: tc.WorkerToken, - ConnBroker: tc.ConnBroker, - }) - assert.NoError(t, err) - assert.NotNil(t, nodeConfig) - assert.NotNil(t, nodeConfig.ClientTLSCreds) - assert.NotNil(t, nodeConfig.ServerTLSCreds) - assert.Equal(t, tc.RootCA, *nodeConfig.RootCA()) + validateNodeConfig(&tc.RootCA) // Remove only the node certificates form the directory, get a new rootCA, and attest that we get // new certificates that are issued by the remote CA os.RemoveAll(tc.Paths.Node.Cert) rootCA, err := ca.GetLocalRootCA(tc.Paths.RootCA) assert.NoError(t, err) - nodeConfig, err = rootCA.CreateSecurityConfig(tc.Context, krw, - ca.CertificateRequestConfig{ - Token: tc.WorkerToken, - ConnBroker: tc.ConnBroker, - }) - assert.NoError(t, err) - assert.NotNil(t, nodeConfig) - assert.NotNil(t, nodeConfig.ClientTLSCreds) - assert.NotNil(t, nodeConfig.ServerTLSCreds) - assert.Equal(t, rootCA, *nodeConfig.RootCA()) + validateNodeConfig(&rootCA) } func TestLoadSecurityConfigExpiredCert(t *testing.T) { + if testutils.External { + return // this doesn't require any servers at all + } tc := testutils.NewTestCA(t) defer tc.Stop() s, err := tc.RootCA.Signer() @@ -146,7 +164,7 @@ func TestLoadSecurityConfigExpiredCert(t *testing.T) { krw := ca.NewKeyReadWriter(tc.Paths.Node, nil, nil) now := time.Now() - _, err = tc.RootCA.IssueAndSaveNewCertificates(krw, "cn", "ou", "org") + _, _, err = tc.RootCA.IssueAndSaveNewCertificates(krw, "cn", "ou", "org") require.NoError(t, err) certBytes, _, err := krw.Read() require.NoError(t, err) @@ -177,6 +195,9 @@ func TestLoadSecurityConfigExpiredCert(t *testing.T) { } func TestLoadSecurityConfigInvalidCert(t *testing.T) { + if testutils.External { + return // this doesn't require any servers at all + } tc := testutils.NewTestCA(t) defer tc.Stop() @@ -189,20 +210,12 @@ some random garbage\n _, err := ca.LoadSecurityConfig(tc.Context, tc.RootCA, krw, false) assert.Error(t, err) - - nodeConfig, err := tc.RootCA.CreateSecurityConfig(tc.Context, krw, - ca.CertificateRequestConfig{ - ConnBroker: tc.ConnBroker, - }) - - assert.NoError(t, err) - assert.NotNil(t, nodeConfig) - assert.NotNil(t, nodeConfig.ClientTLSCreds) - assert.NotNil(t, nodeConfig.ServerTLSCreds) - assert.Equal(t, tc.RootCA, *nodeConfig.RootCA()) } func TestLoadSecurityConfigInvalidKey(t *testing.T) { + if testutils.External { + return // this doesn't require any servers at all + } tc := testutils.NewTestCA(t) defer tc.Stop() @@ -215,24 +228,17 @@ some random garbage\n _, err := ca.LoadSecurityConfig(tc.Context, tc.RootCA, krw, false) assert.Error(t, err) - - nodeConfig, err := tc.RootCA.CreateSecurityConfig(tc.Context, krw, - ca.CertificateRequestConfig{ - ConnBroker: tc.ConnBroker, - }) - assert.NoError(t, err) - assert.NotNil(t, nodeConfig) - assert.NotNil(t, nodeConfig.ClientTLSCreds) - assert.NotNil(t, nodeConfig.ServerTLSCreds) - assert.Equal(t, tc.RootCA, *nodeConfig.RootCA()) } func TestLoadSecurityConfigIncorrectPassphrase(t *testing.T) { + if testutils.External { + return // this doesn't require any servers at all + } tc := testutils.NewTestCA(t) defer tc.Stop() paths := ca.NewConfigPaths(tc.TempDir) - _, err := tc.RootCA.IssueAndSaveNewCertificates(ca.NewKeyReadWriter(paths.Node, []byte("kek"), nil), + _, _, err := tc.RootCA.IssueAndSaveNewCertificates(ca.NewKeyReadWriter(paths.Node, []byte("kek"), nil), "nodeID", ca.WorkerRole, tc.Organization) require.NoError(t, err) @@ -241,6 +247,9 @@ func TestLoadSecurityConfigIncorrectPassphrase(t *testing.T) { } func TestLoadSecurityConfigIntermediates(t *testing.T) { + if testutils.External { + return // this doesn't require any servers at all + } tempdir, err := ioutil.TempDir("", "test-load-config-with-intermediates") require.NoError(t, err) defer os.RemoveAll(tempdir) @@ -255,11 +264,18 @@ func TestLoadSecurityConfigIntermediates(t *testing.T) { _, err = ca.LoadSecurityConfig(context.Background(), rootCA, krw, false) require.Error(t, err) + intermediate, err := helpers.ParseCertificatePEM(testutils.ECDSACertChain[1]) + require.NoError(t, err) + // loading the complete chain succeeds require.NoError(t, krw.Write(append(testutils.ECDSACertChain[0], testutils.ECDSACertChain[1]...), testutils.ECDSACertChainKeys[0], nil)) secConfig, err := ca.LoadSecurityConfig(context.Background(), rootCA, krw, false) require.NoError(t, err) require.NotNil(t, secConfig) + issuerInfo := secConfig.IssuerInfo() + require.NotNil(t, issuerInfo) + require.Equal(t, intermediate.RawSubjectPublicKeyInfo, issuerInfo.PublicKey) + require.Equal(t, intermediate.RawSubject, issuerInfo.Subject) // set up a GRPC server using these credentials secConfig.ServerTLSCreds.Config().ClientAuth = tls.RequireAndVerifyClientCert @@ -454,21 +470,10 @@ func TestRenewTLSConfigUpdateRootCARace(t *testing.T) { } } -func TestRenewTLSConfigWorker(t *testing.T) { - t.Parallel() - - tc := testutils.NewTestCA(t) - defer tc.Stop() +func writeAlmostExpiringCertToDisk(t *testing.T, tc *testutils.TestCA, cn, ou, org string) { s, err := tc.RootCA.Signer() require.NoError(t, err) - 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.WorkerRole) - 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. @@ -483,22 +488,27 @@ func TestRenewTLSConfigWorker(t *testing.T) { }, }) - // Create a new CSR and overwrite the key on disk - csr, key, err := ca.GenerateNewCSR() + // Issue a new certificate with the same details as the current config, but with 1 min expiration time, and + // overwrite the existing cert on disk + _, _, err = newRootCA.IssueAndSaveNewCertificates(ca.NewKeyReadWriter(tc.Paths.Node, nil, nil), cn, ou, org) 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) +func TestRenewTLSConfigWorker(t *testing.T) { + t.Parallel() - // 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) + tc := testutils.NewTestCA(t) + defer tc.Stop() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() - err = ioutils.AtomicWriteFile(tc.Paths.Node.Key, key, 0600) + // Get a new nodeConfig with a TLS cert that has the default Cert duration, but overwrite + // the cert on disk with one that expires in 1 minute + nodeConfig, err := tc.WriteNewNodeConfig(ca.WorkerRole) assert.NoError(t, err) + c := nodeConfig.ClientTLSCreds + writeAlmostExpiringCertToDisk(t, tc, c.NodeID(), c.Role(), c.Organization()) renew := make(chan struct{}) updates := ca.RenewTLSConfig(ctx, nodeConfig, tc.ConnBroker, renew) @@ -510,6 +520,14 @@ func TestRenewTLSConfigWorker(t *testing.T) { assert.NotNil(t, certUpdate) assert.Equal(t, ca.WorkerRole, certUpdate.Role) } + + root, err := helpers.ParseCertificatePEM(tc.RootCA.Certs) + assert.NoError(t, err) + + issuerInfo := nodeConfig.IssuerInfo() + assert.NotNil(t, issuerInfo) + assert.Equal(t, root.RawSubjectPublicKeyInfo, issuerInfo.PublicKey) + assert.Equal(t, root.RawSubject, issuerInfo.Subject) } func TestRenewTLSConfigManager(t *testing.T) { @@ -517,50 +535,18 @@ func TestRenewTLSConfigManager(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() - s, err := tc.RootCA.Signer() - assert.NoError(t, err) 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.Certs, s.Cert, s.Key, ca.DefaultNodeCertExpiration, nil) - assert.NoError(t, err) - newSigner, err := newRootCA.Signer() - assert.NoError(t, err) - newSigner.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() + // Get a new nodeConfig with a TLS cert that has the default Cert duration, but overwrite + // the cert on disk with one that expires in 1 minute + nodeConfig, err := tc.WriteNewNodeConfig(ca.WorkerRole) 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) + writeAlmostExpiringCertToDisk(t, tc, c.NodeID(), c.Role(), c.Organization()) - // 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) - - // Get a new nodeConfig with a TLS cert that has 1 minute to live renew := make(chan struct{}) - updates := ca.RenewTLSConfig(ctx, nodeConfig, tc.ConnBroker, renew) select { case <-time.After(10 * time.Second): @@ -568,8 +554,16 @@ func TestRenewTLSConfigManager(t *testing.T) { case certUpdate := <-updates: assert.NoError(t, certUpdate.Err) assert.NotNil(t, certUpdate) - assert.Equal(t, ca.ManagerRole, certUpdate.Role) + assert.Equal(t, ca.WorkerRole, certUpdate.Role) } + + root, err := helpers.ParseCertificatePEM(tc.RootCA.Certs) + assert.NoError(t, err) + + issuerInfo := nodeConfig.IssuerInfo() + assert.NotNil(t, issuerInfo) + assert.Equal(t, root.RawSubjectPublicKeyInfo, issuerInfo.PublicKey) + assert.Equal(t, root.RawSubject, issuerInfo.Subject) } func TestRenewTLSConfigWithNoNode(t *testing.T) { @@ -577,46 +571,16 @@ func TestRenewTLSConfigWithNoNode(t *testing.T) { tc := testutils.NewTestCA(t) defer tc.Stop() - s, err := tc.RootCA.Signer() - require.NoError(t, err) 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.Certs, s.Cert, s.Key, ca.DefaultNodeCertExpiration, nil) - assert.NoError(t, err) - newSigner, err := newRootCA.Signer() - assert.NoError(t, err) - newSigner.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() + // Get a new nodeConfig with a TLS cert that has the default Cert duration, but overwrite + // the cert on disk with one that expires in 1 minute + nodeConfig, err := tc.WriteNewNodeConfig(ca.WorkerRole) 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) + writeAlmostExpiringCertToDisk(t, tc, c.NodeID(), c.Role(), c.Organization()) // Delete the node from the backend store err = tc.MemoryStore.Update(func(tx store.Tx) error { diff --git a/ca/external_test.go b/ca/external_test.go index 0efa485077..4c415eb864 100644 --- a/ca/external_test.go +++ b/ca/external_test.go @@ -41,7 +41,7 @@ func TestExternalCACrossSign(t *testing.T) { krw := ca.NewKeyReadWriter(paths.Node, nil, nil) - _, err = rootCA2.IssueAndSaveNewCertificates(krw, "cn", "ou", "org") + _, _, err = rootCA2.IssueAndSaveNewCertificates(krw, "cn", "ou", "org") require.NoError(t, err) certBytes, _, err := krw.Read() require.NoError(t, err) diff --git a/ca/testutils/cautils.go b/ca/testutils/cautils.go index 2b90dae1fd..36d1b24d39 100644 --- a/ca/testutils/cautils.go +++ b/ca/testutils/cautils.go @@ -306,17 +306,16 @@ func genSecurityConfig(s *store.MemoryStore, rootCA ca.RootCA, krw *ca.KeyReadWr return nil, err } - nodeServerTLSCreds, err := rootCA.NewServerTLSCredentials(&nodeCert) + err = createNode(s, nodeID, role, csr, certChain) if err != nil { return nil, err } - nodeClientTLSCreds, err := rootCA.NewClientTLSCredentials(&nodeCert, ca.ManagerRole) - if err != nil { - return nil, err + signingCert := rootCA.Certs + if len(rootCA.Intermediates) > 0 { + signingCert = rootCA.Intermediates } - - err = createNode(s, nodeID, role, csr, certChain) + parsedCert, err := helpers.ParseCertificatePEM(signingCert) if err != nil { return nil, err } @@ -330,7 +329,10 @@ func genSecurityConfig(s *store.MemoryStore, rootCA ca.RootCA, krw *ca.KeyReadWr } } - return ca.NewSecurityConfig(&rootCA, krw, nodeClientTLSCreds, nodeServerTLSCreds), nil + return ca.NewSecurityConfig(&rootCA, krw, &nodeCert, &ca.IssuerInfo{ + PublicKey: parsedCert.RawSubjectPublicKeyInfo, + Subject: parsedCert.RawSubject, + }) } func createClusterObject(t *testing.T, s *store.MemoryStore, clusterID, workerToken, managerToken string, externalCAs ...*api.ExternalCA) { diff --git a/ca/testutils/externalutils.go b/ca/testutils/externalutils.go index f1605d4031..a7d1589761 100644 --- a/ca/testutils/externalutils.go +++ b/ca/testutils/externalutils.go @@ -46,7 +46,7 @@ func NewExternalSigningServer(rootCA ca.RootCA, basedir string) (*ExternalSignin Cert: filepath.Join(basedir, "server.crt"), Key: filepath.Join(basedir, "server.key"), } - serverCert, err := rootCA.IssueAndSaveNewCertificates(ca.NewKeyReadWriter(serverPaths, nil, nil), serverCN, serverOU, "") + serverCert, _, err := rootCA.IssueAndSaveNewCertificates(ca.NewKeyReadWriter(serverPaths, nil, nil), serverCN, serverOU, "") if err != nil { return nil, errors.Wrap(err, "unable to get TLS server certificate") } diff --git a/ca/transport_test.go b/ca/transport_test.go index 257913a99d..f523fe81a0 100644 --- a/ca/transport_test.go +++ b/ca/transport_test.go @@ -20,7 +20,7 @@ func TestNewMutableTLS(t *testing.T) { rootCA, err := CreateRootCA("rootCN") require.NoError(t, err) - cert, err := rootCA.IssueAndSaveNewCertificates(krw, "CN", ManagerRole, "org") + cert, _, err := rootCA.IssueAndSaveNewCertificates(krw, "CN", ManagerRole, "org") assert.NoError(t, err) tlsConfig, err := NewServerTLSConfig([]tls.Certificate{*cert}, rootCA.Pool) @@ -41,7 +41,7 @@ func TestGetAndValidateCertificateSubject(t *testing.T) { rootCA, err := CreateRootCA("rootCN") require.NoError(t, err) - cert, err := rootCA.IssueAndSaveNewCertificates(krw, "CN", ManagerRole, "org") + cert, _, err := rootCA.IssueAndSaveNewCertificates(krw, "CN", ManagerRole, "org") assert.NoError(t, err) name, err := GetAndValidateCertificateSubject([]tls.Certificate{*cert}) @@ -62,9 +62,9 @@ func TestLoadNewTLSConfig(t *testing.T) { require.NoError(t, err) // Create two different certs and two different TLS configs - cert1, err := rootCA.IssueAndSaveNewCertificates(krw, "CN1", ManagerRole, "org") + cert1, _, err := rootCA.IssueAndSaveNewCertificates(krw, "CN1", ManagerRole, "org") assert.NoError(t, err) - cert2, err := rootCA.IssueAndSaveNewCertificates(krw, "CN2", WorkerRole, "org") + cert2, _, err := rootCA.IssueAndSaveNewCertificates(krw, "CN2", WorkerRole, "org") assert.NoError(t, err) tlsConfig1, err := NewServerTLSConfig([]tls.Certificate{*cert1}, rootCA.Pool) assert.NoError(t, err) diff --git a/cmd/external-ca-example/main.go b/cmd/external-ca-example/main.go index 5b3bd8b0f3..875ac13d21 100644 --- a/cmd/external-ca-example/main.go +++ b/cmd/external-ca-example/main.go @@ -36,7 +36,7 @@ func main() { nodeID := identity.NewID() kw := ca.NewKeyReadWriter(nodeConfigPaths.Node, nil, nil) - if _, err := rootCA.IssueAndSaveNewCertificates(kw, nodeID, ca.ManagerRole, clusterID); err != nil { + if _, _, err := rootCA.IssueAndSaveNewCertificates(kw, nodeID, ca.ManagerRole, clusterID); err != nil { logrus.Fatalf("unable to create initial manager node credentials: %s", err) } diff --git a/integration/node.go b/integration/node.go index 1d8f787a45..64dad8409a 100644 --- a/integration/node.go +++ b/integration/node.go @@ -63,7 +63,7 @@ func newTestNode(joinAddr, joinToken string, lateBind bool, rootCA *ca.RootCA) ( return nil, err } // generate TLS certs for this manager for bootstrapping, else the node will generate its own CA - _, err = rootCA.IssueAndSaveNewCertificates( + _, _, err = rootCA.IssueAndSaveNewCertificates( ca.NewKeyReadWriter(certPaths.Node, nil, nil), identity.NewID(), ca.ManagerRole, identity.NewID()) if err != nil { diff --git a/node/node_test.go b/node/node_test.go index 4fb21f7fc5..125430e346 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -112,7 +112,7 @@ func TestLoadSecurityConfigLoadFromDisk(t *testing.T) { krw := ca.NewKeyReadWriter(paths.Node, []byte("passphrase"), nil) require.NoError(t, err) - _, err = rootCA.IssueAndSaveNewCertificates(krw, identity.NewID(), ca.WorkerRole, identity.NewID()) + _, _, err = rootCA.IssueAndSaveNewCertificates(krw, identity.NewID(), ca.WorkerRole, identity.NewID()) require.NoError(t, err) node, err := New(&Config{