Skip to content

Commit

Permalink
Auto-generate certs in-memory (kubernetes#2795)
Browse files Browse the repository at this point in the history
(cherry picked from commit cdae55e)
  • Loading branch information
liggitt authored and Sebastian Florek committed Mar 28, 2018
1 parent f176d98 commit 27c17e5
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 24 deletions.
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 @@ -78,26 +79,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("Successfully created and stored certificates")
log.Println("Successfuly 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 @@ -137,12 +151,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

0 comments on commit 27c17e5

Please sign in to comment.