diff --git a/src/app/backend/cert/api/types.go b/src/app/backend/cert/api/types.go index adeac6b806560a16cbf30a9a05264f8616fd0748..e0e1ae4a3924c6d49a5488a9978ba20bbd6aab02 100644 --- a/src/app/backend/cert/api/types.go +++ b/src/app/backend/cert/api/types.go @@ -14,6 +14,8 @@ package api +import "crypto/tls" + const ( // Certificate file names that will be generated by Dashboard DashboardCertName = "dashboard.crt" @@ -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. @@ -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 diff --git a/src/app/backend/cert/ecdsa/creator.go b/src/app/backend/cert/ecdsa/creator.go index b2caea9d80f26a0db5d930ff4d9ca048143a7530..4f0822c5537bd2ae1a3e74f8eea3f512fc9c5e31 100644 --- a/src/app/backend/cert/ecdsa/creator.go +++ b/src/app/backend/cert/ecdsa/creator.go @@ -21,6 +21,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "io/ioutil" "log" "math/big" "net" @@ -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. diff --git a/src/app/backend/cert/manager.go b/src/app/backend/cert/manager.go index 0fedd032e564ba0f5d4e15ced2068e89994bd513..59d54915e36c645052bc791d32363168634736ca 100644 --- a/src/app/backend/cert/manager.go +++ b/src/app/backend/cert/manager.go @@ -15,6 +15,7 @@ package cert import ( + "crypto/tls" "log" "os" @@ -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 { diff --git a/src/app/backend/dashboard.go b/src/app/backend/dashboard.go index 3f443a240c2af17291b7cbeccb6015f382d293bb..8b6270f0b388cd9e1ffebc697f02829898775bfe 100644 --- a/src/app/backend/dashboard.go +++ b/src/app/backend/dashboard.go @@ -16,6 +16,7 @@ package main import ( "crypto/elliptic" + "crypto/tls" "flag" "fmt" "log" @@ -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. @@ -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())