webserver will now automatically create a certificate for itself if no certificates are defined.

This commit is contained in:
Hyatt 2023-03-13 17:46:09 -05:00
parent 2d7593ddf9
commit dd19b87e8d
Signed by: nhyatt
GPG Key ID: C50D0BBB5BC40BEA
6 changed files with 218 additions and 30 deletions

View File

@ -1,7 +1,9 @@
package main
import (
"crypto/tls"
"log"
"mutating-webhook/internal/certificate"
"mutating-webhook/internal/config"
"net/http"
"strconv"
@ -21,7 +23,19 @@ func crossSiteOrigin(w http.ResponseWriter) {
w.Header().Add("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, Authorization, X-API-Token")
}
func strictTransport(w http.ResponseWriter) {
w.Header().Add("Strict-Transport-Security", "max-age=63072000")
}
func httpServer(cfg *config.Config) {
var serverCertificate tls.Certificate
if config.DefaultConfig().WebServerCertificate == "" || cfg.WebServerKey == "" {
log.Printf("[INFO] No webserver certificate configured, automatically generating self signed certificate.")
serverCertificate = certificate.CreateServerCert()
} else {
// read certificate from files
// check for errors
}
path := http.NewServeMux()
connection := &http.Server{
@ -30,6 +44,20 @@ func httpServer(cfg *config.Config) {
ReadTimeout: time.Duration(cfg.WebServerReadTimeout) * time.Second,
WriteTimeout: time.Duration(cfg.WebServerWriteTimeout) * time.Second,
IdleTimeout: time.Duration(cfg.WebServerIdleTimeout) * time.Second,
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
},
Certificates: []tls.Certificate{
serverCertificate,
},
},
}
// healthcheck
@ -39,7 +67,7 @@ func httpServer(cfg *config.Config) {
// web root
path.HandleFunc("/", webRoot)
if err := connection.ListenAndServe(); err != nil {
if err := connection.ListenAndServeTLS("", ""); err != nil {
log.Fatalf("[ERROR] %s\n", err)
}
}
@ -47,6 +75,7 @@ func httpServer(cfg *config.Config) {
func webRoot(w http.ResponseWriter, r *http.Request) {
httpAccessLog(r)
crossSiteOrigin(w)
strictTransport(w)
switch {
case strings.ToLower(r.Method) != "get":
@ -63,6 +92,7 @@ func webRoot(w http.ResponseWriter, r *http.Request) {
func webHealthCheck(w http.ResponseWriter, r *http.Request) {
httpAccessLog(r)
crossSiteOrigin(w)
strictTransport(w)
if strings.ToLower(r.Method) == "get" {
tmpltHealthCheck(w)

View File

@ -0,0 +1,68 @@
package certificate
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"strconv"
"time"
)
func CreateCA() ([]byte, []byte, []byte, error) {
serial, _ := strconv.ParseInt(time.Now().Format("20060102150405"), 10, 64)
ca := &x509.Certificate{
SerialNumber: big.NewInt(serial),
Subject: pkix.Name{
Organization: []string{"Kubernetes Mutating Webserver CA"},
Country: []string{"K8S"},
Province: []string{"Cluster Service"},
Locality: []string{"Cluster Local"},
//StreetAddress: []string{""},
//PostalCode: []string{""},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth,
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
SignatureAlgorithm: x509.SHA384WithRSA,
}
keyPair, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return []byte(""), []byte(""), []byte(""), err
}
certBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &keyPair.PublicKey, keyPair)
if err != nil {
return []byte(""), []byte(""), []byte(""), err
}
c := new(bytes.Buffer)
pem.Encode(c, &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
})
k := new(bytes.Buffer)
pem.Encode(k, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(keyPair),
})
p := new(bytes.Buffer)
pem.Encode(p, &pem.Block{
Type: "PUBLIC KEY",
Bytes: x509.MarshalPKCS1PublicKey(&keyPair.PublicKey),
})
return c.Bytes(), k.Bytes(), p.Bytes(), nil
}

View File

@ -0,0 +1,71 @@
package certificate
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"strconv"
"time"
)
func CreateCert() ([]byte, []byte, []byte, error) {
serial, _ := strconv.ParseInt(time.Now().Format("20060102150405"), 10, 64)
ca := &x509.Certificate{
SerialNumber: big.NewInt(serial + 1),
Subject: pkix.Name{
Organization: []string{"Kubernetes Mutating Webserver"},
Country: []string{"K8S"},
Province: []string{"Cluster Service"},
Locality: []string{"Cluster Local"},
//StreetAddress: []string{""},
//PostalCode: []string{""},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 6, 0),
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth,
},
DNSNames: []string{
"svc.cluster.local",
"*.svc.cluster.local",
},
SubjectKeyId: []byte{1, 2, 3, 4, 6},
KeyUsage: x509.KeyUsageDigitalSignature,
SignatureAlgorithm: x509.SHA384WithRSA,
}
keyPair, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return []byte(""), []byte(""), []byte(""), err
}
certBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &keyPair.PublicKey, keyPair)
if err != nil {
return []byte(""), []byte(""), []byte(""), err
}
c := new(bytes.Buffer)
pem.Encode(c, &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
})
k := new(bytes.Buffer)
pem.Encode(k, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(keyPair),
})
p := new(bytes.Buffer)
pem.Encode(p, &pem.Block{
Type: "PUBLIC KEY",
Bytes: x509.MarshalPKCS1PublicKey(&keyPair.PublicKey),
})
return c.Bytes(), k.Bytes(), p.Bytes(), nil
}

View File

@ -0,0 +1,42 @@
package certificate
import (
"bytes"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"log"
)
func CreateServerCert() tls.Certificate {
caCertPem, caPrivKeyPem, _, _ := CreateCA()
certCertPem, certPrivKeyPem, certPublicKeyPem, _ := CreateCert()
caCertBlob, _ := pem.Decode(caCertPem)
caCert, _ := x509.ParseCertificate(caCertBlob.Bytes)
caPrivKeyBlob, _ := pem.Decode(caPrivKeyPem)
caPrivKey, _ := x509.ParsePKCS1PrivateKey(caPrivKeyBlob.Bytes)
certCertBlob, _ := pem.Decode(certCertPem)
certCert, _ := x509.ParseCertificate(certCertBlob.Bytes)
certPublicKeyBlob, _ := pem.Decode(certPublicKeyPem)
certPublicKey, _ := x509.ParsePKCS1PublicKey(certPublicKeyBlob.Bytes)
signedCert, err := x509.CreateCertificate(rand.Reader, certCert, caCert, certPublicKey, caPrivKey)
if err != nil {
log.Fatalf("[FATAL] CreateCertificate: %v", err)
}
serverCertPem := new(bytes.Buffer)
pem.Encode(serverCertPem, &pem.Block{
Type: "CERTIFICATE",
Bytes: signedCert,
})
serverCert, err := tls.X509KeyPair(append(serverCertPem.Bytes(), caCertPem...), certPrivKeyPem)
if err != nil {
log.Fatalf("[FATAL] x509KeyPair: %v", err)
}
return serverCert
}

View File

@ -18,12 +18,14 @@ type Config struct {
TZoneUTC *time.Location `ignored:"true"`
// logging
LogLevel int `env:"LOG_LEVEL" default:"50"`
Log *logutils.LevelFilter
LogLevel int `env:"LOG_LEVEL" default:"50"`
Log *logutils.LevelFilter `ignored:"true"`
// webserver
WebServerPort int `env:"webserver_port" default:"8080"`
WebServerPort int `env:"webserver_port" default:"8443"`
WebServerIP string `env:"webserver_ip" default:"0.0.0.0"`
WebServerCertificate string `env:"webserver_cert"`
WebServerKey string `env:"webserver_key"`
WebServerReadTimeout int `env:"webserver_read_timeout" default:"5"`
WebServerWriteTimeout int `env:"webserver_write_timeout" default:"1"`
WebServerIdleTimeout int `env:"webserver_idle_timeout" default:"2"`
@ -39,31 +41,6 @@ func DefaultConfig() *Config {
}
}
/*
func (cfg *Config) validate() error {
checks := []struct {
bad bool
errMsg string
}{
{cfg.TimeFormat == "", "no TimeFormat specified"},
{cfg.LogLevel == 0, "no LogLevel specified"},
{cfg.WebServerPort == 0, "no WebServer.Port specified"},
{cfg.WebServerIP == "", "no WebServer.IP specified"},
{cfg.WebServerReadTimeout == 0, "no WebServer.ReadTimeout specified"},
{cfg.WebServerWriteTimeout == 0, "no WebServer.WriteTimeout specified"},
{cfg.WebServerIdleTimeout == 0, "no WebServer.IdleTimeout specified"},
}
for _, check := range checks {
if check.bad {
return fmt.Errorf("invalid config: %s", check.errMsg)
}
}
return nil
}
*/
func (cfg *Config) setLogLevel() {
switch {
case cfg.LogLevel <= 20:

View File

@ -73,6 +73,6 @@ func Init() *Config {
// print running config
cfg.printRunningConfig(cfgInfo)
log.Println("[INFO] initialization complete")
log.Println("[INFO] initialization sequence complete")
return cfg
}