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.CreateAndWriteRootCA("rootCN", paths.RootCA) assert.NoError(t, err) csr, _, err := ca.GenerateAndWriteNewKey(paths.Node) 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 TestGenerateAndSignNewTLSCert(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.CreateAndWriteRootCA("rootCN", paths.RootCA) assert.NoError(t, err) _, err = ca.GenerateAndSignNewTLSCert(rootCA, "CN", "OU", "ORG", paths.Node) assert.NoError(t, err) perms, err := permbits.Stat(paths.Node.Cert) assert.NoError(t, err) assert.False(t, perms.GroupWrite()) assert.False(t, perms.OtherWrite()) perms, err = permbits.Stat(paths.Node.Key) assert.NoError(t, err) assert.False(t, perms.GroupRead()) assert.False(t, perms.OtherRead()) certBytes, err := ioutil.ReadFile(paths.Node.Cert) assert.NoError(t, err) certs, err := helpers.ParseCertificatesPEM(certBytes) assert.NoError(t, err) assert.Len(t, certs, 2) assert.Equal(t, "CN", certs[0].Subject.CommonName) assert.Equal(t, "OU", certs[0].Subject.OrganizationalUnit[0]) assert.Equal(t, "ORG", certs[0].Subject.Organization[0]) assert.Equal(t, "rootCN", certs[1].Subject.CommonName) }
func TestNewRootCABundle(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") assert.NoError(t, err) defer os.RemoveAll(tempBaseDir) paths := ca.NewConfigPaths(tempBaseDir) // Write one root CA to disk, keep the bytes secondRootCA, err := ca.CreateAndWriteRootCA("rootCN2", paths.RootCA) assert.NoError(t, err) // Overwrite the first root CA on disk firstRootCA, err := ca.CreateAndWriteRootCA("rootCN1", paths.RootCA) assert.NoError(t, err) // Overwrite the bytes of the second Root CA with the bundle, creating a valid 2 cert bundle bundle := append(firstRootCA.Cert, secondRootCA.Cert...) err = ioutil.WriteFile(paths.RootCA.Cert, bundle, 0644) assert.NoError(t, err) newRootCA, err := ca.NewRootCA(bundle, firstRootCA.Key, ca.DefaultNodeCertExpiration) assert.NoError(t, err) assert.Equal(t, bundle, newRootCA.Cert) assert.Equal(t, 2, len(newRootCA.Pool.Subjects())) // Now load the bundle from disk diskRootCA, err := ca.GetLocalRootCA(tempBaseDir) assert.NoError(t, err) assert.Equal(t, bundle, diskRootCA.Cert) assert.Equal(t, 2, len(diskRootCA.Pool.Subjects())) // If I use GenerateAndSignNewTLSCert to sign certs, I'll get the correct CA in the chain _, err = ca.GenerateAndSignNewTLSCert(diskRootCA, "CN", "OU", "ORG", paths.Node) assert.NoError(t, err) certBytes, err := ioutil.ReadFile(paths.Node.Cert) assert.NoError(t, err) certs, err := helpers.ParseCertificatesPEM(certBytes) assert.NoError(t, err) assert.Len(t, certs, 2) assert.Equal(t, "CN", certs[0].Subject.CommonName) assert.Equal(t, "OU", certs[0].Subject.OrganizationalUnit[0]) assert.Equal(t, "ORG", certs[0].Subject.Organization[0]) assert.Equal(t, "rootCN1", certs[1].Subject.CommonName) }
func TestNewRootCAWithPassphrase(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") assert.NoError(t, err) defer os.RemoveAll(tempBaseDir) defer os.Setenv(ca.PassphraseENVVar, "") defer os.Setenv(ca.PassphraseENVVarPrev, "") paths := ca.NewConfigPaths(tempBaseDir) rootCA, err := ca.CreateAndWriteRootCA("rootCN", paths.RootCA) assert.NoError(t, err) // Ensure that we're encrypting the Key bytes out of NewRoot if there // is a passphrase set as an env Var os.Setenv(ca.PassphraseENVVar, "password1") newRootCA, err := ca.NewRootCA(rootCA.Cert, rootCA.Key, ca.DefaultNodeCertExpiration) assert.NoError(t, err) assert.NotEqual(t, rootCA.Key, newRootCA.Key) assert.Equal(t, rootCA.Cert, newRootCA.Cert) assert.NotContains(t, string(rootCA.Key), string(newRootCA.Key)) assert.Contains(t, string(newRootCA.Key), "Proc-Type: 4,ENCRYPTED") // Ensure that we're decrypting the Key bytes out of NewRoot if there // is a passphrase set as an env Var anotherNewRootCA, err := ca.NewRootCA(newRootCA.Cert, newRootCA.Key, ca.DefaultNodeCertExpiration) assert.NoError(t, err) assert.Equal(t, newRootCA, anotherNewRootCA) assert.NotContains(t, string(rootCA.Key), string(anotherNewRootCA.Key)) assert.Contains(t, string(anotherNewRootCA.Key), "Proc-Type: 4,ENCRYPTED") // Ensure that we cant decrypt the Key bytes out of NewRoot if there // is a wrong passphrase set as an env Var os.Setenv(ca.PassphraseENVVar, "password2") anotherNewRootCA, err = ca.NewRootCA(newRootCA.Cert, newRootCA.Key, ca.DefaultNodeCertExpiration) assert.Error(t, err) // Ensure that we cant decrypt the Key bytes out of NewRoot if there // is a wrong passphrase set as an env Var os.Setenv(ca.PassphraseENVVarPrev, "password2") anotherNewRootCA, err = ca.NewRootCA(newRootCA.Cert, newRootCA.Key, ca.DefaultNodeCertExpiration) assert.Error(t, err) // Ensure that we can decrypt the Key bytes out of NewRoot if there // is a wrong passphrase set as an env Var, but a valid as Prev os.Setenv(ca.PassphraseENVVarPrev, "password1") anotherNewRootCA, err = ca.NewRootCA(newRootCA.Cert, newRootCA.Key, ca.DefaultNodeCertExpiration) assert.NoError(t, err) assert.Equal(t, newRootCA, anotherNewRootCA) assert.NotContains(t, string(rootCA.Key), string(anotherNewRootCA.Key)) assert.Contains(t, string(anotherNewRootCA.Key), "Proc-Type: 4,ENCRYPTED") }
func TestNewRootCA(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.CreateAndWriteRootCA("rootCN", paths.RootCA) assert.NoError(t, err) newRootCA, err := ca.NewRootCA(rootCA.Cert, rootCA.Key, ca.DefaultNodeCertExpiration) assert.NoError(t, err) assert.Equal(t, rootCA, newRootCA) }
func TestCreateAndWriteRootCAExpiry(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.CreateAndWriteRootCA("rootCN", paths.RootCA) assert.NoError(t, err) // Convert the certificate into an object to create a RootCA parsedCert, err := helpers.ParseCertificatePEM(rootCA.Cert) assert.NoError(t, err) duration, err := time.ParseDuration(ca.RootCAExpiration) assert.NoError(t, err) assert.True(t, time.Now().Add(duration).AddDate(0, -1, 0).Before(parsedCert.NotAfter)) }
func TestGetLocalRootCA(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") assert.NoError(t, err) defer os.RemoveAll(tempBaseDir) paths := ca.NewConfigPaths(tempBaseDir) // First, try to load the local Root CA with the certificate missing. _, err = ca.GetLocalRootCA(tempBaseDir) assert.Equal(t, ca.ErrNoLocalRootCA, err) // Create the local Root CA to ensure that we can reload it correctly. rootCA, err := ca.CreateAndWriteRootCA("rootCN", paths.RootCA) assert.True(t, rootCA.CanSign()) assert.NoError(t, err) rootCA2, err := ca.GetLocalRootCA(tempBaseDir) assert.NoError(t, err) assert.True(t, rootCA2.CanSign()) assert.Equal(t, rootCA, rootCA2) // Try again, this time without a private key. assert.NoError(t, os.Remove(paths.RootCA.Key)) rootCA3, err := ca.GetLocalRootCA(tempBaseDir) assert.NoError(t, err) assert.False(t, rootCA3.CanSign()) assert.Equal(t, rootCA.Cert, rootCA3.Cert) // Try with a private key that does not match the CA cert public key. privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) assert.NoError(t, err) privKeyBytes, err := x509.MarshalECPrivateKey(privKey) assert.NoError(t, err) privKeyPem := pem.EncodeToMemory(&pem.Block{ Type: "EC PRIVATE KEY", Bytes: privKeyBytes, }) assert.NoError(t, ioutil.WriteFile(paths.RootCA.Key, privKeyPem, os.FileMode(0600))) _, err = ca.GetLocalRootCA(tempBaseDir) assert.EqualError(t, err, "certificate key mismatch") }
func TestCreateAndWriteRootCA(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") assert.NoError(t, err) defer os.RemoveAll(tempBaseDir) paths := ca.NewConfigPaths(tempBaseDir) _, err = ca.CreateAndWriteRootCA("rootCN", paths.RootCA) assert.NoError(t, err) perms, err := permbits.Stat(paths.RootCA.Cert) assert.NoError(t, err) assert.False(t, perms.GroupWrite()) assert.False(t, perms.OtherWrite()) perms, err = permbits.Stat(paths.RootCA.Key) assert.NoError(t, err) assert.False(t, perms.GroupRead()) assert.False(t, perms.OtherRead()) }
func TestParseValidateAndSignMaliciousCSR(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.CreateAndWriteRootCA("rootCN", paths.RootCA) assert.NoError(t, err) req := &cfcsr.CertificateRequest{ Names: []cfcsr.Name{ { O: "maliciousOrg", OU: "maliciousOU", L: "maliciousLocality", }, }, CN: "maliciousCN", Hosts: []string{"docker.com"}, KeyRequest: &cfcsr.BasicKeyRequest{A: "ecdsa", S: 256}, } csr, _, err := cfcsr.ParseRequest(req) 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.Empty(t, parsedCert[0].Subject.Locality) assert.Equal(t, "ORG", parsedCert[0].Subject.Organization[0]) assert.Equal(t, "rootCN", parsedCert[1].Subject.CommonName) }
func main() { // Create root material within the current directory. rootPaths := ca.CertPaths{ Cert: filepath.Join("ca", "root.crt"), Key: filepath.Join("ca", "root.key"), } // Initialize the Root CA. rootCA, err := ca.CreateAndWriteRootCA("external-ca-example", rootPaths) if err != nil { log.Fatalf("unable to initialize Root CA: %s", err) } // Create the initial manager node credentials. nodeConfigPaths := ca.NewConfigPaths("certificates") clusterID := identity.NewID() nodeID := identity.NewID() if _, err := ca.GenerateAndSignNewTLSCert(rootCA, nodeID, ca.ManagerRole, clusterID, nodeConfigPaths.Node); err != nil { log.Fatalf("unable to create initial manager node credentials: %s", err) } // And copy the Root CA certificate into the node config path for its // CA. ioutil.WriteFile(nodeConfigPaths.RootCA.Cert, rootCA.Cert, os.FileMode(0644)) server, err := testutils.NewExternalSigningServer(rootCA, "ca") if err != nil { log.Fatalf("unable to start server: %s", err) } defer server.Stop() log.Infof("Now run: swarmd --manager -d . --listen-control-api ./swarmd.sock --external-ca-url %s", server.URL) sigC := make(chan os.Signal, 1) signal.Notify(sigC, syscall.SIGTERM, syscall.SIGINT) <-sigC }
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.CreateAndWriteRootCA("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.GenerateAndWriteNewKey(paths.Node) 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)) }