reload certificate on SIGHUP

This commit is contained in:
dre 2021-07-08 21:16:17 +08:00
parent 2e196465b6
commit 802bf1eb6d
4 changed files with 56 additions and 33 deletions

View file

@ -6,13 +6,12 @@ protocol](https://gemini.circumlunar.space/docs/specification.gmi). Why built ye
server? Because it's educational and that's the spirit of the protocol. server? Because it's educational and that's the spirit of the protocol.
Features Features
- **zero conf**, if no certificate is available, gmifs can generates self-signed certs - **zero conf**, if no certificate is available, gmifs generates a self-signed cert
- **zero dependencies**, Go standard library only - **zero dependencies**, Go standard library only
- directory listing support - directory listing support `-autoindex`
- only modern tls ciphers (from [Mozilla's TLS ciphers recommendations](https://statics.tls.security.mozilla.org/server-side-tls-conf.json)) - reloads ssl certs and reopens log files on SIGHUP, e.g. after Let's Encrypt renewal
- concurrent requests limiter - KISS, single file gemini implementation, handler func in main
- reloads ssl certs and flushes/reopens log files on SIGHUP - modern tls ciphers (from [Mozilla's TLS ciphers recommendations](https://statics.tls.security.mozilla.org/server-side-tls-conf.json))
- single file gemini implementation, focus on simplicity, no bells and whistles
This tool is used alongside the markdown to gemtext converter This tool is used alongside the markdown to gemtext converter
[md2gmi](https://github.com/n0x1m/md2gmi). [md2gmi](https://github.com/n0x1m/md2gmi).

View file

@ -91,6 +91,8 @@ type Server struct {
Logger *log.Logger Logger *log.Logger
TLSConfig *tls.Config TLSConfig *tls.Config
TLSConfigLoader func() (*tls.Config, error)
Handler Handler // handler to invoke Handler Handler // handler to invoke
ReadTimeout time.Duration ReadTimeout time.Duration
MaxOpenConns int MaxOpenConns int
@ -115,14 +117,30 @@ func (s *Server) logf(format string, v ...interface{}) {
s.log(fmt.Sprintf(format, v...)) s.log(fmt.Sprintf(format, v...))
} }
func (s *Server) loadTLS() (err error) {
s.TLSConfig, err = s.TLSConfigLoader()
return err
}
func (s *Server) ListenAndServe() error { func (s *Server) ListenAndServe() error {
err := s.loadTLS()
if err != nil {
return err
}
hup := make(chan os.Signal, 1) hup := make(chan os.Signal, 1)
signal.Notify(hup, syscall.SIGHUP) signal.Notify(hup, syscall.SIGHUP)
go func() { go func() {
for { for {
<-hup <-hup
s.log("reloading certificate")
if s.listener != nil { if s.listener != nil {
// TODO: reload TLSConfig err := s.loadTLS()
if err != nil {
fmt.Fprintf(os.Stderr, "critical: failed to load tls certs: %v", err)
os.Exit(1)
}
s.listener.Close() s.listener.Close()
} }
} }

View file

@ -11,7 +11,7 @@ import (
) )
// GenX509KeyPair generates a TLS keypair with one week validity. // GenX509KeyPair generates a TLS keypair with one week validity.
func GenX509KeyPair(host string) (tls.Certificate, error) { func GenX509KeyPair(host string, daysvalid int) (tls.Certificate, error) {
now := time.Now() now := time.Now()
template := &x509.Certificate{ template := &x509.Certificate{
SerialNumber: big.NewInt(now.Unix()), SerialNumber: big.NewInt(now.Unix()),
@ -20,7 +20,7 @@ func GenX509KeyPair(host string) (tls.Certificate, error) {
Organization: []string{host}, Organization: []string{host},
}, },
NotBefore: now, NotBefore: now,
NotAfter: now.AddDate(0, 0, 7), NotAfter: now.AddDate(0, 0, daysvalid),
BasicConstraintsValid: true, BasicConstraintsValid: true,
IsCA: true, IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},

38
main.go
View file

@ -27,6 +27,7 @@ const (
defaultCertPath = "" defaultCertPath = ""
defaultKeyPath = "" defaultKeyPath = ""
autoCertDaysValid = 7
shutdownTimeout = 10 * time.Second shutdownTimeout = 10 * time.Second
) )
@ -61,20 +62,6 @@ func main() {
} }
} }
var cert tls.Certificate
if crt != "" && key != "" {
log.Println("loading certificate from", crt)
cert, err = tls.LoadX509KeyPair(crt, key)
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
} else if host != "" {
log.Println("generating self-signed temporary certificate")
cert, err = gemini.GenX509KeyPair(host)
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
}
if host == "" { if host == "" {
fmt.Fprintf(os.Stderr, "a keypair with cert and key or at least a common name (hostname) is required for sni\n") fmt.Fprintf(os.Stderr, "a keypair with cert and key or at least a common name (hostname) is required for sni\n")
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
@ -84,12 +71,12 @@ func main() {
mux := gemini.NewMux() mux := gemini.NewMux()
mux.Use(middleware.Logger(flogger)) mux.Use(middleware.Logger(flogger))
mux.Handle(gemini.HandlerFunc(fileserver.Serve(root, true))) mux.Handle(gemini.HandlerFunc(fileserver.Serve(root, autoindex)))
server := &gemini.Server{ server := &gemini.Server{
Addr: addr, Addr: addr,
Hostname: host, Hostname: host,
TLSConfig: gemini.TLSConfig(host, cert), TLSConfigLoader: setupCertificate(crt, key, host),
Handler: mux, Handler: mux,
MaxOpenConns: maxconns, MaxOpenConns: maxconns,
ReadTimeout: time.Duration(timeout) * time.Second, ReadTimeout: time.Duration(timeout) * time.Second,
@ -118,6 +105,25 @@ func main() {
cancel() cancel()
} }
func setupCertificate(crt, key, host string) func() (*tls.Config, error) {
return func() (*tls.Config, error) {
if crt != "" && key != "" {
cert, err := tls.LoadX509KeyPair(crt, key)
if err != nil {
return nil, err
}
return gemini.TLSConfig(host, cert), nil
}
log.Println("generating self-signed temporary certificate")
cert, err := gemini.GenX509KeyPair(host, autoCertDaysValid)
if err != nil {
return nil, err
}
return gemini.TLSConfig(host, cert), nil
}
}
func setupLogger(dir, filename string) (*log.Logger, error) { func setupLogger(dir, filename string) (*log.Logger, error) {
logger := log.New(os.Stdout, "", log.LUTC|log.Ldate|log.Ltime) logger := log.New(os.Stdout, "", log.LUTC|log.Ldate|log.Ltime)