Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-generate certs in-memory #2795

Merged
merged 1 commit into from
Jan 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/app/backend/cert/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

package api

import "crypto/tls"

const (
// Certificate file names that will be generated by Dashboard
DashboardCertName = "dashboard.crt"
Expand All @@ -23,8 +25,8 @@ const (
// Manager is responsible for generating and storing self-signed certificates that can be used by Dashboard
// to serve over HTTPS.
type Manager interface {
// GenerateCertificates generates self-signed certificates.
GenerateCertificates()
// GetCertificates loads existing certificates or generates self-signed certificates.
GetCertificates() (tls.Certificate, error)
}

// Creator is responsible for preparing and generating certificates.
Expand All @@ -35,6 +37,8 @@ type Creator interface {
GenerateCertificate(key interface{}) []byte
// StoreCertificates saves certificates in a given path
StoreCertificates(path string, key interface{}, certBytes []byte)
// KeyCertPEMBytes converts the key and cert to PEM format
KeyCertPEMBytes(key interface{}, certBytes []byte) (keyPEM []byte, certPEM []byte, err error)
// GetKeyFileName returns certificate key file name
GetKeyFileName() string
// GetCertFileName returns certificate file name
Expand Down
25 changes: 13 additions & 12 deletions src/app/backend/cert/ecdsa/creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"io/ioutil"
"log"
"math/big"
"net"
Expand Down Expand Up @@ -76,26 +77,26 @@ func (self *ecdsaCreator) GenerateCertificate(key interface{}) []byte {

// StoreCertificates implements certificate Creator interface. See Creator for more information.
func (self *ecdsaCreator) StoreCertificates(path string, key interface{}, certBytes []byte) {
ecdsaKey := self.getKey(key)
certOut, err := os.Create(path + string(os.PathSeparator) + self.GetCertFileName())
keyPEM, certPEM, err := self.KeyCertPEMBytes(key, certBytes)
if err != nil {
log.Fatalf("[ECDSAManager] Failed to marshal cert/key pair: %v", err)
}
if err := ioutil.WriteFile(path+string(os.PathSeparator)+self.GetCertFileName(), certPEM, os.FileMode(0644)); err != nil {
log.Fatalf("[ECDSAManager] Failed to open %s for writing: %s", self.GetCertFileName(), err)
}

pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
certOut.Close()

keyOut, err := os.OpenFile(path+string(os.PathSeparator)+self.GetKeyFileName(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
if err := ioutil.WriteFile(path+string(os.PathSeparator)+self.GetKeyFileName(), keyPEM, os.FileMode(0600)); err != nil {
log.Fatalf("[ECDSAManager] Failed to open %s for writing: %s", self.GetKeyFileName(), err)
}
}

marshaledKey, err := x509.MarshalECPrivateKey(ecdsaKey)
func (self *ecdsaCreator) KeyCertPEMBytes(key interface{}, certBytes []byte) ([]byte, []byte, error) {
marshaledKey, err := x509.MarshalECPrivateKey(self.getKey(key))
if err != nil {
log.Fatalf("[ECDSAManager] Unable to marshal %s: %v", self.GetKeyFileName(), err)
return nil, nil, err
}
pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: marshaledKey})
keyOut.Close()
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: marshaledKey})
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
return keyPEM, certPEM, nil
}

// GetKeyFileName implements certificate Creator interface. See Creator for more information.
Expand Down
18 changes: 13 additions & 5 deletions src/app/backend/cert/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package cert

import (
"crypto/tls"
"log"
"os"

Expand All @@ -28,16 +29,23 @@ type Manager struct {
}

// GenerateCertificates implements Manager interface. See Manager for more information.
func (self *Manager) GenerateCertificates() {
func (self *Manager) GetCertificates() (tls.Certificate, error) {
if self.keyFileExists() && self.certFileExists() {
log.Println("Certificates already exist. Skipping.")
return
log.Println("Certificates already exist. Returning.")
return tls.LoadX509KeyPair(
self.path(self.creator.GetCertFileName()),
self.path(self.creator.GetKeyFileName()),
)
}

key := self.creator.GenerateKey()
cert := self.creator.GenerateCertificate(key)
self.creator.StoreCertificates(self.certDir, key, cert)
log.Println("Successfuly created and stored certificates")
log.Println("Successfully created certificates")
keyPEM, certPEM, err := self.creator.KeyCertPEMBytes(key, cert)
if err != nil {
return tls.Certificate{}, err
}
return tls.X509KeyPair(certPEM, keyPEM)
}

func (self *Manager) keyFileExists() bool {
Expand Down
27 changes: 22 additions & 5 deletions src/app/backend/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package main

import (
"crypto/elliptic"
"crypto/tls"
"flag"
"fmt"
"log"
Expand Down Expand Up @@ -121,11 +122,24 @@ func main() {
handleFatalInitError(err)
}

var servingCerts []tls.Certificate
if args.Holder.GetAutoGenerateCertificates() {
log.Println("Auto-generating certificates")
certCreator := ecdsa.NewECDSACreator(args.Holder.GetKeyFile(), args.Holder.GetCertFile(), elliptic.P256())
certManager := cert.NewCertManager(certCreator, args.Holder.GetDefaultCertDir())
certManager.GenerateCertificates()
servingCert, err := certManager.GetCertificates()
if err != nil {
handleFatalInitError(err)
}
servingCerts = []tls.Certificate{servingCert}
} else if args.Holder.GetCertFile() != "" && args.Holder.GetKeyFile() != "" {
certFilePath := args.Holder.GetDefaultCertDir() + string(os.PathSeparator) + args.Holder.GetCertFile()
keyFilePath := args.Holder.GetDefaultCertDir() + string(os.PathSeparator) + args.Holder.GetKeyFile()
servingCert, err := tls.LoadX509KeyPair(certFilePath, keyFilePath)
if err != nil {
handleFatalInitError(err)
}
servingCerts = []tls.Certificate{servingCert}
}

// Run a HTTP server that serves static public files from './public' and handles API calls.
Expand All @@ -138,12 +152,15 @@ func main() {
http.Handle("/metrics", prometheus.Handler())

// Listen for http or https
if args.Holder.GetCertFile() != "" && args.Holder.GetKeyFile() != "" {
certFilePath := args.Holder.GetDefaultCertDir() + string(os.PathSeparator) + args.Holder.GetCertFile()
keyFilePath := args.Holder.GetDefaultCertDir() + string(os.PathSeparator) + args.Holder.GetKeyFile()
if servingCerts != nil {
log.Printf("Serving securely on HTTPS port: %d", args.Holder.GetPort())
secureAddr := fmt.Sprintf("%s:%d", args.Holder.GetBindAddress(), args.Holder.GetPort())
go func() { log.Fatal(http.ListenAndServeTLS(secureAddr, certFilePath, keyFilePath, nil)) }()
server := &http.Server{
Addr: secureAddr,
Handler: http.DefaultServeMux,
TLSConfig: &tls.Config{Certificates: servingCerts},
}
go func() { log.Fatal(server.ListenAndServeTLS("", "")) }()
} else {
log.Printf("Serving insecurely on HTTP port: %d", args.Holder.GetInsecurePort())
addr := fmt.Sprintf("%s:%d", args.Holder.GetInsecureBindAddress(), args.Holder.GetInsecurePort())
Expand Down