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.
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
- directory listing support
- only modern tls ciphers (from [Mozilla's TLS ciphers recommendations](https://statics.tls.security.mozilla.org/server-side-tls-conf.json))
- concurrent requests limiter
- reloads ssl certs and flushes/reopens log files on SIGHUP
- single file gemini implementation, focus on simplicity, no bells and whistles
- directory listing support `-autoindex`
- reloads ssl certs and reopens log files on SIGHUP, e.g. after Let's Encrypt renewal
- KISS, single file gemini implementation, handler func in main
- modern tls ciphers (from [Mozilla's TLS ciphers recommendations](https://statics.tls.security.mozilla.org/server-side-tls-conf.json))
This tool is used alongside the markdown to gemtext converter
[md2gmi](https://github.com/n0x1m/md2gmi).

View file

@ -91,6 +91,8 @@ type Server struct {
Logger *log.Logger
TLSConfig *tls.Config
TLSConfigLoader func() (*tls.Config, error)
Handler Handler // handler to invoke
ReadTimeout time.Duration
MaxOpenConns int
@ -115,14 +117,30 @@ func (s *Server) logf(format string, v ...interface{}) {
s.log(fmt.Sprintf(format, v...))
}
func (s *Server) loadTLS() (err error) {
s.TLSConfig, err = s.TLSConfigLoader()
return err
}
func (s *Server) ListenAndServe() error {
err := s.loadTLS()
if err != nil {
return err
}
hup := make(chan os.Signal, 1)
signal.Notify(hup, syscall.SIGHUP)
go func() {
for {
<-hup
s.log("reloading certificate")
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()
}
}

View file

@ -11,7 +11,7 @@ import (
)
// 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()
template := &x509.Certificate{
SerialNumber: big.NewInt(now.Unix()),
@ -20,7 +20,7 @@ func GenX509KeyPair(host string) (tls.Certificate, error) {
Organization: []string{host},
},
NotBefore: now,
NotAfter: now.AddDate(0, 0, 7),
NotAfter: now.AddDate(0, 0, daysvalid),
BasicConstraintsValid: true,
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},

38
main.go
View file

@ -27,6 +27,7 @@ const (
defaultCertPath = ""
defaultKeyPath = ""
autoCertDaysValid = 7
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 == "" {
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])
@ -84,12 +71,12 @@ func main() {
mux := gemini.NewMux()
mux.Use(middleware.Logger(flogger))
mux.Handle(gemini.HandlerFunc(fileserver.Serve(root, true)))
mux.Handle(gemini.HandlerFunc(fileserver.Serve(root, autoindex)))
server := &gemini.Server{
Addr: addr,
Hostname: host,
TLSConfig: gemini.TLSConfig(host, cert),
TLSConfigLoader: setupCertificate(crt, key, host),
Handler: mux,
MaxOpenConns: maxconns,
ReadTimeout: time.Duration(timeout) * time.Second,
@ -118,6 +105,25 @@ func main() {
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) {
logger := log.New(os.Stdout, "", log.LUTC|log.Ldate|log.Ltime)