传输层安全 (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()
方法一样。ListenAndServe
和 ListenAndServeTLS
方法在 HTTP 包和 Server
结构上都可用。
ListenAndServeTLS
方法就像 ListenAndServe
方法,只不过它会启动一个 HTTPS 服务器。
func ListenAndServeTLS(certFile string, keyFile string) error
正如您从上面的方法签名中看到的,此方法与 ListenAndServe
方法的唯一区别是额外的 certFile
和 keyFile
参数。这些分别是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
作为 certFile
,locahost.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 服务器时,您将 certFile
和 keyFile
传递给 ListenAndServeTLS
函数。但是,如果由于证书过期导致 certFile
和 keyFile
有任何更改,则需要重新启动服务器才能生效。为了克服这个问题,您可以使用 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
函数调用提供 certFile
和 keyFile
参数值。当我运行此服务器时,它的工作方式将与以前一样。但这一次,我已经从调用者那里抽象出了所有服务器配置。
$ curl https://localhost:8443/ --cacert rootCA.crt --key client.key --cert client.crt
Running HTTPS Server!!
最终想法
作为结尾,提供一些额外的提示:要在 GetCertificate
函数中读取证书和密钥文件,HTTPS 服务器应作为 goroutine 运行。还有一个 StackOverflow 线程,其中提供了其他配置方法
评论已关闭。