feat(ssl): Add LetsEncrypt certificate option
This commit is contained in:
125
app/proxy/cert.go
Normal file
125
app/proxy/cert.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"git.secnex.io/secnex/masterlog"
|
||||
"git.secnex.io/secnex/pgproxy/config"
|
||||
)
|
||||
|
||||
// generateSelfSignedCert generates a self-signed certificate for the given hostnames
|
||||
func generateSelfSignedCert(hostnames []string) (tls.Certificate, error) {
|
||||
// Generate private key
|
||||
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
||||
// Create certificate template
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(365 * 24 * time.Hour * 10) // Valid for 10 years
|
||||
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"PostgreSQL Proxy"},
|
||||
Country: []string{"DE"},
|
||||
Province: []string{""},
|
||||
Locality: []string{""},
|
||||
StreetAddress: []string{""},
|
||||
PostalCode: []string{""},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
// Set CommonName to first hostname
|
||||
if len(hostnames) > 0 {
|
||||
template.Subject.CommonName = hostnames[0]
|
||||
}
|
||||
|
||||
// Add all hostnames as DNS names (SAN)
|
||||
template.DNSNames = hostnames
|
||||
|
||||
// Create certificate
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
||||
// Parse certificate
|
||||
cert, err := x509.ParseCertificate(certDER)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
||||
// Create TLS certificate
|
||||
return tls.Certificate{
|
||||
Certificate: [][]byte{certDER},
|
||||
PrivateKey: privKey,
|
||||
Leaf: cert,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getCertificateForMappings generates or loads a certificate for the given mappings
|
||||
func getCertificateForMappings(config *config.Config) (tls.Certificate, error) {
|
||||
// If certificate files are specified, try to load them
|
||||
if config.TLS.CertFile != "" && config.TLS.KeyFile != "" {
|
||||
cert, err := tls.LoadX509KeyPair(config.TLS.CertFile, config.TLS.KeyFile)
|
||||
if err == nil {
|
||||
masterlog.Info("Loaded TLS certificate from files", map[string]interface{}{
|
||||
"certFile": config.TLS.CertFile,
|
||||
"keyFile": config.TLS.KeyFile,
|
||||
})
|
||||
return cert, nil
|
||||
}
|
||||
// If loading failed, log warning and fall through to generate
|
||||
masterlog.Info("Failed to load TLS certificate, generating self-signed certificate", map[string]interface{}{
|
||||
"error": err,
|
||||
"certFile": config.TLS.CertFile,
|
||||
"keyFile": config.TLS.KeyFile,
|
||||
})
|
||||
}
|
||||
|
||||
// Generate self-signed certificate for all external hostnames
|
||||
hostnames := make([]string, 0, len(config.Mappings))
|
||||
for _, mapping := range config.Mappings {
|
||||
hostnames = append(hostnames, mapping.External)
|
||||
}
|
||||
|
||||
// If no hostnames, use a default
|
||||
if len(hostnames) == 0 {
|
||||
hostnames = []string{"localhost"}
|
||||
}
|
||||
|
||||
masterlog.Info("Generating self-signed certificate", map[string]interface{}{
|
||||
"hostnames": hostnames,
|
||||
})
|
||||
|
||||
cert, err := generateSelfSignedCert(hostnames)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
||||
masterlog.Info("Generated self-signed certificate", map[string]interface{}{
|
||||
"hostnames": hostnames,
|
||||
"validUntil": cert.Leaf.NotAfter.Format(time.RFC3339),
|
||||
})
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
Reference in New Issue
Block a user