From d5b95136d5f162645a6bfaa76833cbf5520f7e45 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Wed, 26 Jun 2024 23:10:51 +0200 Subject: enable TLS for local development (using caddy as acme server) --- internal/server/server.go | 21 ++++++++++----- internal/server/tls.go | 66 ++++++++++++++++++++++++++++++++++------------- modd.conf | 3 +-- 3 files changed, 64 insertions(+), 26 deletions(-) diff --git a/internal/server/server.go b/internal/server/server.go index 3110ec0..717320d 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -9,6 +9,7 @@ import ( "os" "slices" "strconv" + "strings" "time" "website/internal/builder" @@ -31,13 +32,16 @@ var ( ) type Config struct { - Development bool `conf:"default:false,flag:dev"` Root string `conf:"default:website"` Redirect bool `conf:"default:true"` ListenAddress string `conf:"default:localhost"` - Port int `conf:"default:3000,short:p"` - TLSPort int `conf:"default:443"` + Port int `conf:"default:8080,short:p"` + TLSPort int `conf:"default:8443"` TLS bool `conf:"default:false"` + + Development bool `conf:"default:false,flag:dev"` + ACMECACert string `conf:"env:ACME_CA_CERT"` + Domains string } type Server struct { @@ -46,13 +50,18 @@ type Server struct { config *cfg.Config } -func applyDevModeOverrides(config *cfg.Config, listenAddress string) { +func applyDevModeOverrides(config *cfg.Config, runtimeConfig *Config) { config.CSP.ScriptSrc = slices.Insert(config.CSP.ScriptSrc, 0, "'unsafe-inline'") config.CSP.ConnectSrc = slices.Insert(config.CSP.ConnectSrc, 0, "'self'") + if runtimeConfig.Domains != "" { + config.Domains = strings.Split(runtimeConfig.Domains, ",") + } else { + config.Domains = []string{runtimeConfig.ListenAddress} + } config.BaseURL = cfg.URL{ URL: &url.URL{ Scheme: "http", - Host: listenAddress, + Host: runtimeConfig.ListenAddress, }, } } @@ -110,7 +119,7 @@ func New(runtimeConfig *Config) (*Server, error) { return nil, errors.WithMessage(err, "error parsing configuration file") } if runtimeConfig.Development { - applyDevModeOverrides(config, runtimeConfig.ListenAddress) + applyDevModeOverrides(config, runtimeConfig) } listenAddress := net.JoinHostPort(runtimeConfig.ListenAddress, strconv.Itoa(runtimeConfig.Port)) diff --git a/internal/server/tls.go b/internal/server/tls.go index 370134c..84dae74 100644 --- a/internal/server/tls.go +++ b/internal/server/tls.go @@ -2,6 +2,8 @@ package server import ( "context" + "crypto/x509" + "website/internal/log" "github.com/ardanlabs/conf/v3" "github.com/caddyserver/caddy/v2" @@ -19,25 +21,45 @@ type redisConfig struct { } func (s *Server) serveTLS() (err error) { - rc := &redisConfig{} - _, err = conf.Parse("REDIS", rc) - if err != nil { - return errors.Wrap(err, "could not parse redis config") - } + if s.runtimeConfig.Development { + ca := s.runtimeConfig.ACMECACert + if ca == "" { + return errors.New("Need ACME_CA_CERT to enable TLS in development") + } + + cp := x509.NewCertPool() + cp.AppendCertsFromPEM([]byte(ca)) + + cfg := certmagic.NewDefault() + issuer := certmagic.NewACMEIssuer(cfg, certmagic.ACMEIssuer{ + CA: "https://localhost/acme/local/directory", + TrustedRoots: cp, + DisableTLSALPNChallenge: true, + AltHTTPPort: s.runtimeConfig.Port, + }) - rs := certmagic_redis.New() - rs.Address = []string{rc.Address} - rs.Username = rc.Username - rs.Password = rc.Password - rs.EncryptionKey = rc.EncryptionKey - rs.KeyPrefix = rc.KeyPrefix - - certmagic.Default.Storage = rs - err = rs.Provision(caddy.Context{ - Context: context.Background(), - }) - if err != nil { - return errors.Wrap(err, "could not provision redis storage") + certmagic.DefaultACME = *issuer + } else { + rc := &redisConfig{} + _, err = conf.Parse("REDIS", rc) + if err != nil { + return errors.Wrap(err, "could not parse redis config") + } + + rs := certmagic_redis.New() + rs.Address = []string{rc.Address} + rs.Username = rc.Username + rs.Password = rc.Password + rs.EncryptionKey = rc.EncryptionKey + rs.KeyPrefix = rc.KeyPrefix + + certmagic.Default.Storage = rs + err = rs.Provision(caddy.Context{ + Context: context.Background(), + }) + if err != nil { + return errors.Wrap(err, "could not provision redis storage") + } } certmagic.DefaultACME.Agreed = true @@ -46,5 +68,13 @@ func (s *Server) serveTLS() (err error) { certmagic.HTTPPort = s.runtimeConfig.Port certmagic.HTTPSPort = s.runtimeConfig.TLSPort + log.Debug( + "starting certmagic", + "http_port", + certmagic.HTTPPort, + "https_port", + certmagic.HTTPSPort, + ) + return certmagic.HTTPS(s.config.Domains, s.Server.Handler) } diff --git a/modd.conf b/modd.conf index f956fea..0c73ea6 100644 --- a/modd.conf +++ b/modd.conf @@ -1,5 +1,4 @@ **/*.go !**/*_templ.go { - daemon +sigint: systemfd -s http::3000 -- \ - templ generate --watch \ + daemon +sigint: templ generate --watch \ --cmd="go run ./cmd/server --dev" } -- cgit 1.4.1