Files
pgproxy/app/proxy/letsencrypt.go
2025-12-16 14:15:16 +01:00

115 lines
3.2 KiB
Go

package proxy
import (
"context"
"crypto/tls"
"fmt"
"os"
"git.secnex.io/secnex/masterlog"
"git.secnex.io/secnex/pgproxy/config"
"github.com/caddyserver/certmagic"
)
// setupLetsEncrypt configures certmagic for Let's Encrypt certificate management
func setupLetsEncrypt(config *config.Config) (*certmagic.Config, error) {
// Get hostnames from mappings
hostnames := make([]string, 0, len(config.Mappings))
for _, mapping := range config.Mappings {
hostnames = append(hostnames, mapping.External)
}
if len(hostnames) == 0 {
return nil, fmt.Errorf("no hostnames configured for Let's Encrypt")
}
// Set cache directory
cacheDir := config.TLS.LetsEncrypt.CacheDir
if cacheDir == "" {
cacheDir = "./certs/letsencrypt"
}
// Ensure cache directory exists
if err := os.MkdirAll(cacheDir, 0700); err != nil {
return nil, fmt.Errorf("failed to create cache directory: %w", err)
}
// Configure certmagic
magic := certmagic.NewDefault()
magic.Storage = &certmagic.FileStorage{Path: cacheDir}
// Set email for Let's Encrypt registration
email := config.TLS.LetsEncrypt.Email
if email == "" {
// Use a default email if not provided
email = "admin@" + hostnames[0]
masterlog.Info("Using default email for Let's Encrypt", map[string]interface{}{
"email": email,
})
}
// Configure ACME issuer
acmeIssuer := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
Email: email,
Agreed: true,
})
// Use staging environment if configured
if config.TLS.LetsEncrypt.Staging {
acmeIssuer.CA = certmagic.LetsEncryptStagingCA
masterlog.Info("Using Let's Encrypt staging environment", map[string]interface{}{})
} else {
acmeIssuer.CA = certmagic.LetsEncryptProductionCA
}
magic.Issuers = []certmagic.Issuer{acmeIssuer}
// Obtain certificates for all hostnames
masterlog.Info("Obtaining Let's Encrypt certificates", map[string]interface{}{
"hostnames": hostnames,
"email": email,
"staging": config.TLS.LetsEncrypt.Staging,
})
// Obtain certificates synchronously (this will block until certificates are obtained)
ctx := context.Background()
err := magic.ManageSync(ctx, hostnames)
if err != nil {
return nil, fmt.Errorf("failed to obtain Let's Encrypt certificates: %w", err)
}
masterlog.Info("Successfully obtained Let's Encrypt certificates", map[string]interface{}{
"hostnames": hostnames,
})
return magic, nil
}
// getCertificateForMappingsWithLetsEncrypt gets certificates using Let's Encrypt
func getCertificateForMappingsWithLetsEncrypt(config *config.Config) (*certmagic.Config, error) {
magic, err := setupLetsEncrypt(config)
if err != nil {
return nil, err
}
return magic, nil
}
// createTLSConfigWithLetsEncrypt creates a TLS config that uses certmagic for certificate management
func createTLSConfigWithLetsEncrypt(magic *certmagic.Config) *tls.Config {
return &tls.Config{
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
cert, err := magic.GetCertificate(clientHello)
if err != nil {
masterlog.Error("Failed to get certificate from Let's Encrypt", map[string]interface{}{
"error": err,
"serverName": clientHello.ServerName,
})
return nil, err
}
return cert, nil
},
ClientAuth: tls.NoClientCert,
}
}