reload certificate on SIGHUP
This commit is contained in:
parent
2e196465b6
commit
802bf1eb6d
4 changed files with 56 additions and 33 deletions
11
README.md
11
README.md
|
@ -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).
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
38
main.go
|
@ -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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue