有没有办法在不停机的情况下更新 net/http 服务器中的 TLS 证书?
Is there a way to update the TLS certificates in a net/http server without any downtime?
我有一个简单的 https 服务器,服务于这样一个简单的页面(为简洁起见,没有错误处理):
package main
import (
"crypto/tls"
"fmt"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "hello!")
})
xcert, _ := tls.LoadX509KeyPair("cert1.crt", "key1.pem")
tlsConf := &tls.Config{
Certificates: []tls.Certificate{xcert},
}
srv := &http.Server{
Addr: ":https",
Handler: mux,
TLSConfig: tlsConf,
}
srv.ListenAndServeTLS("", "")
}
我想使用 Let's Encrypt TLS 证书通过 https 提供内容。我希望能够在不停机的情况下更新证书并更新服务器中的证书。
我尝试了 运行 一个 goroutine 来更新 tlsConf
:
go func(c *tls.Config) {
xcert, _ := tls.LoadX509KeyPair("cert2.crt", "key2.pem")
select {
case <-time.After(3 * time.Minute):
c.Certificates = []tls.Certificate{xcert}
c.BuildNameToCertificate()
fmt.Println("cert switched!")
}
}(tlsConf)
但是,这不起作用,因为服务器没有 "read in" 更改的配置。无论如何要求服务器重新加载 TLSConfig
?
有:您可以使用 tls.Config
的 GetCertificate
成员而不是填充 Certificates
。首先,定义一个封装证书和重新加载功能的数据结构(在本例中接收到 SIGHUP
信号):
type keypairReloader struct {
certMu sync.RWMutex
cert *tls.Certificate
certPath string
keyPath string
}
func NewKeypairReloader(certPath, keyPath string) (*keypairReloader, error) {
result := &keypairReloader{
certPath: certPath,
keyPath: keyPath,
}
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, err
}
result.cert = &cert
go func() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP)
for range c {
log.Printf("Received SIGHUP, reloading TLS certificate and key from %q and %q", *tlsCertPath, *tlsKeyPath)
if err := result.maybeReload(); err != nil {
log.Printf("Keeping old TLS certificate because the new one could not be loaded: %v", err)
}
}
}()
return result, nil
}
func (kpr *keypairReloader) maybeReload() error {
newCert, err := tls.LoadX509KeyPair(kpr.certPath, kpr.keyPath)
if err != nil {
return err
}
kpr.certMu.Lock()
defer kpr.certMu.Unlock()
kpr.cert = &newCert
return nil
}
func (kpr *keypairReloader) GetCertificateFunc() func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
kpr.certMu.RLock()
defer kpr.certMu.RUnlock()
return kpr.cert, nil
}
}
然后,在您的服务器代码中,使用:
kpr, err := NewKeypairReloader(*tlsCertPath, *tlsKeyPath)
if err != nil {
log.Fatal(err)
}
srv.TLSConfig.GetCertificate = kpr.GetCertificateFunc()
我最近 implemented this pattern 在 RobustIRC。
我还在 Kubernetes controller-runtime 中找到了这个不错的实现:
https://github.com/kubernetes-sigs/controller-runtime/blob/master/pkg/certwatcher/certwatcher.go
我想根据简单的文件轮询实现添加我自己的答案。我用它来重新加载通过 Kubernetes Secrets 交付的证书。
import (
"crypto/tls"
"fmt"
"os"
"time"
)
type CertReloader struct {
CertFile string // path to the x509 certificate for https
KeyFile string // path to the x509 private key matching `CertFile`
cachedCert *tls.Certificate
cachedCertModTime time.Time
}
// Implementation for tls.Config.GetCertificate useful when using
// Kubernetes Secrets which update the filesystem at runtime.
func (cr *CertReloader) GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) {
stat, err := os.Stat(cr.KeyFile)
if err != nil {
return nil, fmt.Errorf("failed checking key file modification time: %w", err)
}
if cr.cachedCert == nil || stat.ModTime().After(cr.cachedCertModTime) {
pair, err := tls.LoadX509KeyPair(cr.CertFile, cr.KeyFile)
if err != nil {
return nil, fmt.Errorf("failed loading tls key pair: %w", err)
}
cr.cachedCert = &pair
cr.cachedCertModTime = stat.ModTime()
}
return cr.cachedCert, nil
}
我有一个简单的 https 服务器,服务于这样一个简单的页面(为简洁起见,没有错误处理):
package main
import (
"crypto/tls"
"fmt"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "hello!")
})
xcert, _ := tls.LoadX509KeyPair("cert1.crt", "key1.pem")
tlsConf := &tls.Config{
Certificates: []tls.Certificate{xcert},
}
srv := &http.Server{
Addr: ":https",
Handler: mux,
TLSConfig: tlsConf,
}
srv.ListenAndServeTLS("", "")
}
我想使用 Let's Encrypt TLS 证书通过 https 提供内容。我希望能够在不停机的情况下更新证书并更新服务器中的证书。
我尝试了 运行 一个 goroutine 来更新 tlsConf
:
go func(c *tls.Config) {
xcert, _ := tls.LoadX509KeyPair("cert2.crt", "key2.pem")
select {
case <-time.After(3 * time.Minute):
c.Certificates = []tls.Certificate{xcert}
c.BuildNameToCertificate()
fmt.Println("cert switched!")
}
}(tlsConf)
但是,这不起作用,因为服务器没有 "read in" 更改的配置。无论如何要求服务器重新加载 TLSConfig
?
有:您可以使用 tls.Config
的 GetCertificate
成员而不是填充 Certificates
。首先,定义一个封装证书和重新加载功能的数据结构(在本例中接收到 SIGHUP
信号):
type keypairReloader struct {
certMu sync.RWMutex
cert *tls.Certificate
certPath string
keyPath string
}
func NewKeypairReloader(certPath, keyPath string) (*keypairReloader, error) {
result := &keypairReloader{
certPath: certPath,
keyPath: keyPath,
}
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, err
}
result.cert = &cert
go func() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP)
for range c {
log.Printf("Received SIGHUP, reloading TLS certificate and key from %q and %q", *tlsCertPath, *tlsKeyPath)
if err := result.maybeReload(); err != nil {
log.Printf("Keeping old TLS certificate because the new one could not be loaded: %v", err)
}
}
}()
return result, nil
}
func (kpr *keypairReloader) maybeReload() error {
newCert, err := tls.LoadX509KeyPair(kpr.certPath, kpr.keyPath)
if err != nil {
return err
}
kpr.certMu.Lock()
defer kpr.certMu.Unlock()
kpr.cert = &newCert
return nil
}
func (kpr *keypairReloader) GetCertificateFunc() func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
kpr.certMu.RLock()
defer kpr.certMu.RUnlock()
return kpr.cert, nil
}
}
然后,在您的服务器代码中,使用:
kpr, err := NewKeypairReloader(*tlsCertPath, *tlsKeyPath)
if err != nil {
log.Fatal(err)
}
srv.TLSConfig.GetCertificate = kpr.GetCertificateFunc()
我最近 implemented this pattern 在 RobustIRC。
我还在 Kubernetes controller-runtime 中找到了这个不错的实现: https://github.com/kubernetes-sigs/controller-runtime/blob/master/pkg/certwatcher/certwatcher.go
我想根据简单的文件轮询实现添加我自己的答案。我用它来重新加载通过 Kubernetes Secrets 交付的证书。
import (
"crypto/tls"
"fmt"
"os"
"time"
)
type CertReloader struct {
CertFile string // path to the x509 certificate for https
KeyFile string // path to the x509 private key matching `CertFile`
cachedCert *tls.Certificate
cachedCertModTime time.Time
}
// Implementation for tls.Config.GetCertificate useful when using
// Kubernetes Secrets which update the filesystem at runtime.
func (cr *CertReloader) GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) {
stat, err := os.Stat(cr.KeyFile)
if err != nil {
return nil, fmt.Errorf("failed checking key file modification time: %w", err)
}
if cr.cachedCert == nil || stat.ModTime().After(cr.cachedCertModTime) {
pair, err := tls.LoadX509KeyPair(cr.CertFile, cr.KeyFile)
if err != nil {
return nil, fmt.Errorf("failed loading tls key pair: %w", err)
}
cr.cachedCert = &pair
cr.cachedCertModTime = stat.ModTime()
}
return cr.cachedCert, nil
}