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) }
// NewExternalSigningServer creates and runs a new ExternalSigningServer which // uses the given rootCA to sign node certificates. A server key and cert are // generated and saved into the given basedir and then a TLS listener is // started on a random available port. On success, an HTTPS server will be // running in a separate goroutine. The URL of the singing endpoint is // available in the returned *ExternalSignerServer value. Calling the Close() // method will stop the server. func NewExternalSigningServer(rootCA ca.RootCA, basedir string) (*ExternalSigningServer, error) { serverCN := "external-ca-example-server" serverOU := "localhost" // Make a valid server cert for localhost. // Create TLS credentials for the external CA server which we will run. serverPaths := ca.CertPaths{ Cert: filepath.Join(basedir, "server.crt"), Key: filepath.Join(basedir, "server.key"), } serverCert, err := ca.GenerateAndSignNewTLSCert(rootCA, serverCN, serverOU, "", serverPaths) if err != nil { return nil, fmt.Errorf("unable to get TLS server certificate: %s", err) } serverTLSConfig := &tls.Config{ Certificates: []tls.Certificate{*serverCert}, ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: rootCA.Pool, } tlsListener, err := tls.Listen("tcp", "localhost:0", serverTLSConfig) if err != nil { return nil, fmt.Errorf("unable to create TLS connection listener: %s", err) } assignedPort := tlsListener.Addr().(*net.TCPAddr).Port signURL := url.URL{ Scheme: "https", Host: net.JoinHostPort("localhost", strconv.Itoa(assignedPort)), Path: "/sign", } ess := &ExternalSigningServer{ listener: tlsListener, URL: signURL.String(), } mux := http.NewServeMux() handler := &signHandler{ numIssued: &ess.NumIssued, rootCA: rootCA, } mux.Handle(signURL.Path, handler) server := &http.Server{ Handler: mux, } go server.Serve(tlsListener) return ess, nil }
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 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 }