1
0
mirror of https://github.com/juanfont/headscale.git synced 2026-02-07 20:04:00 +01:00

feat: reload certificate on sighup reload

This commit is contained in:
Racter Liu 2026-01-22 23:53:32 +08:00
parent 20dff82f95
commit 3f5eb0854d

View File

@ -102,6 +102,11 @@ type Headscale struct {
mapBatcher mapper.Batcher
clientStreamsOpen sync.WaitGroup
// TLS certificate for manual TLS configuration (non-ACME).
// Protected by tlsCertMu for concurrent access during SIGHUP reload.
tlsCertMu sync.RWMutex
tlsCert *tls.Certificate
}
var (
@ -823,20 +828,25 @@ func (h *Headscale) Serve() error {
case syscall.SIGHUP:
log.Info().
Str("signal", sig.String()).
Msg("Received SIGHUP, reloading ACL policy")
Msg("Received SIGHUP, reloading configuration")
if h.cfg.Policy.IsEmpty() {
continue
// Reload TLS certificate if using manual TLS (not ACME/Let's Encrypt)
if h.cfg.TLS.CertPath != "" && h.cfg.TLS.LetsEncrypt.Hostname == "" {
if err := h.reloadTLSCertificate(); err != nil {
log.Error().Err(err).Msg("reloading TLS certificate")
}
}
changes, err := h.state.ReloadPolicy()
if err != nil {
log.Error().Err(err).Msgf("reloading policy")
continue
// Reload ACL policy
if !h.cfg.Policy.IsEmpty() {
changes, err := h.state.ReloadPolicy()
if err != nil {
log.Error().Err(err).Msg("reloading ACL policy")
} else {
h.Change(changes...)
}
}
h.Change(changes...)
default:
info := func(msg string) { log.Info().Msg(msg) }
@ -995,17 +1005,49 @@ func (h *Headscale) getTLSSettings() (*tls.Config, error) {
}
tlsConfig := &tls.Config{
NextProtos: []string{"http/1.1"},
Certificates: make([]tls.Certificate, 1),
MinVersion: tls.VersionTLS12,
NextProtos: []string{"http/1.1"},
MinVersion: tls.VersionTLS12,
}
tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(h.cfg.TLS.CertPath, h.cfg.TLS.KeyPath)
if err := h.reloadTLSCertificate(); err != nil {
return nil, err
}
return tlsConfig, err
tlsConfig.GetCertificate = h.getTLSCertificate
return tlsConfig, nil
}
}
// reloadTLSCertificate loads or reloads the TLS certificate from disk.
// This is called on startup and on SIGHUP for certificate rotation.
func (h *Headscale) reloadTLSCertificate() error {
cert, err := tls.LoadX509KeyPair(h.cfg.TLS.CertPath, h.cfg.TLS.KeyPath)
if err != nil {
return fmt.Errorf("loading TLS certificate: %w", err)
}
h.tlsCertMu.Lock()
h.tlsCert = &cert
h.tlsCertMu.Unlock()
log.Info().
Str("cert_path", h.cfg.TLS.CertPath).
Str("key_path", h.cfg.TLS.KeyPath).
Msg("TLS certificate loaded")
return nil
}
// getTLSCertificate returns the current TLS certificate.
// It implements the tls.Config.GetCertificate callback signature.
func (h *Headscale) getTLSCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
h.tlsCertMu.RLock()
defer h.tlsCertMu.RUnlock()
return h.tlsCert, nil
}
func readOrCreatePrivateKey(path string) (*key.MachinePrivate, error) {
dir := filepath.Dir(path)