在 Golang 服务器中动态更新 TLS 证书而无需停机

为自动更新的证书配置 HTTPS 服务器并不像您想象的那么难。
2 位读者喜欢这篇文章。
computer servers processing data

Opensource.com

传输层安全 (TLS) 是一种基于 SSLv3 的加密协议,旨在加密和解密两个站点之间的流量。换句话说,TLS 确保您正在访问您想要访问的站点,并防止您和网站之间的任何人看到来回传递的数据。这是通过数字证书的相互交换实现的:一个私有证书存在于 Web 服务器上,一个公共证书通常与 Web 浏览器一起分发。

在生产环境中,所有服务器都安全运行,但服务器证书可能会在一段时间后过期。然后,服务器有责任验证、重新生成和重用新生成的证书,而不会造成任何停机。在本文中,我将演示如何在 Go 中使用 HTTPS 服务器动态更新 TLS 证书。

以下是学习本教程的先决条件

  • 对客户端-服务器工作模型的基本理解
  • Golang 的基本知识

配置 HTTP 服务器

在讨论如何在 HTTPS 服务器上动态更新证书之前,我将提供一个简单的 HTTP 服务器示例。在这种情况下,我只需要 http.ListenAndServe 函数来启动 HTTP 服务器,以及 http.HandleFunc 来为特定端点注册响应处理程序。

首先,配置 HTTP 服务器

package main

import(
	"net/http"
	"fmt"
	"log"
)

func main() {

	mux := http.NewServeMux()
	mux.HandleFunc("/", func( res http.ResponseWriter, req *http.Request ) {
		fmt.Fprint( res, "Running HTTP Server!!" )
	})
	
	srv := &http.Server{
		Addr: fmt.Sprintf(":%d", 8080),
		Handler:           mux,
	}

	// run server on port "8080"
	log.Fatal(srv.ListenAndServe())
}

在上面的示例中,当我运行命令 go run server.go 时,它将在端口 8080 上启动一个 HTTP 服务器。通过在浏览器中访问 http://localhost:8080 URL,您将能够在屏幕上看到 Hello World! 消息。

srv.ListenAndServe() 调用使用 Go 的标准 HTTP 服务器配置。但是,您可以使用 Server 结构类型自定义服务器。

要启动 HTTPS 服务器,请调用 srv.ListenAndServeTLS(certFile, keyFile) 方法并进行一些配置,就像 srv.ListenAndServe() 方法一样。ListenAndServeListenAndServeTLS 方法在 HTTP 包和 Server 结构上都可用。

ListenAndServeTLS 方法就像 ListenAndServe 方法,只不过它会启动一个 HTTPS 服务器。

func ListenAndServeTLS(certFile string, keyFile string) error

正如您从上面的方法签名中看到的,此方法与 ListenAndServe 方法的唯一区别是额外的 certFilekeyFile 参数。这些分别是SSL 证书文件私钥文件的路径。

生成私钥和 SSL 证书

请按照以下步骤生成根密钥和证书

1. 创建根密钥

openssl genrsa -des3 -out rootCA.key 4096

2. 创建并自签名根证书

openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt

接下来,按照以下步骤生成证书(对于每个服务器)

1. 创建证书密钥

 openssl genrsa -out localhost.key 2048

2. 创建证书签名请求 (CSR)。CSR 是您在其中指定要生成的证书的详细信息的地方。根密钥的所有者将处理此请求以生成证书。创建 CSR 时,指定 Common Name 非常重要,提供服务的 IP 地址或域名,否则证书将无法验证。

 openssl req -new -key localhost.key -out localhost.csr

3. 使用 TSL CSR 和密钥以及 CA 根密钥生成证书

  openssl x509 -req -in localhost.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out localhost.crt -days 500 -sha256

最后,按照相同的步骤为每个服务器生成证书,以便为客户端生成证书。

配置 HTTPS 服务器

现在您有了私钥和证书文件,您可以修改之前的 Go 程序并使用 ListenAndServeTLS 方法代替。

package main

import(
	"net/http"
	"fmt"
	"log"
)

func main() {

	mux := http.NewServeMux()
	mux.HandleFunc("/", func( res http.ResponseWriter, req *http.Request ) {
		fmt.Fprint( res, "Running HTTPS Server!!" )
	})
	
	srv := &http.Server{
		Addr: fmt.Sprintf(":%d", 8443),
		Handler:           mux,
	}

	// run server on port "8443"
	log.Fatal(srv.ListenAndServeTLS("localhost.crt", "localhost.key"))
}

当您运行上述程序时,它将使用程序文件所在目录中的 localhost.crt 作为 certFilelocahost.key 作为 keyFile 启动 HTTPS 服务器。通过浏览器或命令行界面 (CLI) 访问 https://localhost:8443 URL,您可以看到以下结果

$ curl https://localhost:8443/ --cacert rootCA.crt --key client.key --cert client.crt 

Running HTTPS Server!!

就是这样!这是大多数人启动 HTTPS 服务器必须做的事情。Go 管理 TLS 通信的默认行为和功能。

配置 HTTPS 服务器以自动更新证书

在运行上面的 HTTPS 服务器时,您将 certFilekeyFile 传递给 ListenAndServeTLS 函数。但是,如果由于证书过期导致 certFilekeyFile 有任何更改,则需要重新启动服务器才能生效。为了克服这个问题,您可以使用 net/http 包的 TLSConfig

crypto/tls 包提供的 TLSConfig 结构配置 Server 的 TLS 参数,包括服务器证书等等。TLSConfig 结构的所有字段都是可选的。因此,将空结构值分配给 TLSConfig 字段与分配 nil 值没有区别。但是,GetCertificate 字段可能非常有用。

type Config struct {
   GetCertificate func(*ClientHelloInfo) (*Certificate, error)
}

TLSConfig 结构的 GetCertificate 字段根据给定的 ClientHelloInfo 返回证书。

我需要实现 GetCertificate 闭包函数,该函数使用 tls.LoadX509KeyPair(certFile string, keyFile string)tls.X509KeyPair(certFile []byte, keyFile []byte) 函数来获取证书。

现在我将创建一个使用 TLSConfig 字段值的 Server 结构。这是一个代码示例格式

package main

import (
	"crypto/tls"
	"fmt"
	"log"
	"net/http"
)

func main() {

	mux := http.NewServeMux()
	mux.HandleFunc("/", func( res http.ResponseWriter, req *http.Request ) {
		fmt.Fprint( res, "Running HTTPS Server!!\n" )
	})
	
	srv := &http.Server{
		Addr: fmt.Sprintf(":%d", 8443),
		Handler:           mux,
		TLSConfig: &tls.Config{
			GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
                                               // Always get latest localhost.crt and localhost.key 
                                               // ex: keeping certificates file somewhere in global location where created certificates updated and this closure function can refer that
				cert, err := tls.LoadX509KeyPair("localhost.crt", "localhost.key")
				if err != nil {
					return nil, err
				}
				return &cert, nil
			},
		},
	}

	// run server on port "8443"
	log.Fatal(srv.ListenAndServeTLS("", ""))
}

在上面的程序中,我实现了 GetCertificate 闭包函数,它通过使用 LoadX509KeyPair 函数以及之前创建的证书和私有文件,返回类型为 Certificate 的证书对象。它还会返回一个错误,因此请谨慎处理。

由于我使用 TLS 配置预配置了服务器实例 srv,因此我不需要向 srv.ListenAndServeTLS 函数调用提供 certFilekeyFile 参数值。当我运行此服务器时,它的工作方式将与以前一样。但这一次,我已经从调用者那里抽象出了所有服务器配置。

$ curl https://localhost:8443/ --cacert rootCA.crt --key client.key --cert client.crt 

Running HTTPS Server!!

最终想法

作为结尾,提供一些额外的提示:要在 GetCertificate 函数中读取证书和密钥文件,HTTPS 服务器应作为 goroutine 运行。还有一个 StackOverflow 线程,其中提供了其他配置方法

Open Enthusiast, Open Source Evangelist
印度班加罗尔

评论已关闭。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.