前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用golang部署运行tls的https服务时,不用停机,高效证书下放,如何实现?

使用golang部署运行tls的https服务时,不用停机,高效证书下放,如何实现?

作者头像
用户1413827
发布2023-11-28 15:44:40
6400
发布2023-11-28 15:44:40
举报
文章被收录于专栏:站长运维站长运维

使用golang部署运行tls的https服务时,不用停机,高效证书下放,如何实现?

第一部分

这篇文章主要介绍如何在应用golang语言开发http/https服务时,如何让tls自动获取证书,而不必在证书更新或重置以后,还要重启服务器来让业务重新起效,本文分成三部分,第一部分会介绍tls加密的常用加密算法进行分析总结,虽然与主干关系不特别大,但是该段络会帮你厘清一个日常使用中,非常容易被混淆的问题;第二部分会重点介绍如何部署一个不需要重启也能tls自动更新的高抽象度的http服务;第三部分会对整个文章进行总结,相信基于该文章的学习,你一定会对tls领域和流量监测、安全防护领域常见的算法有相对深刻的理解,也对如何高度抽象一个自签名的golang服务有全新的认识。那么文章开始!

我之前已经有文章分享过如果来做爬虫,用python的scrapy和基于node的puppeteer之间的优劣对比。也分享过我之前抓取某国外站点的时候,由于页面验签要正确的传输参数,在post请求中提交到后台才能返回正确的结果,这个在国内的也有很多站点有这种验签机制的存在,比如说我在写基于puppeteer的自动发布文章应用的时候,一些平台,像今日头条平台、像简书用浏览器缓存的cookies就可以避免重复登录,直接模拟浏览器登录发布文章就好了。但是像百度的百家号,只有cookie是无法成功验签的,还要获取下放的token,才能完整成功完成发布百家号的流程,总之,就是“兵来将挡,水来土淹”。

但是你有考虑过,这些平台其实也是基于tls的验签机会来保护客户端和服务器端数据交互安全性的,但是遵循的底层原理却是各有不同的。比如说JA3指纹算法,它能基于TLS客户端与服务端之间握手消息内容生成一个指纹,具体来说,就是在进行TLS握手时,客户端会发送一些包含有关自身支持的加密套件、TLS/SSL版本等信息的消息给服务器,服务器会回应类似的消息,JA3也是根据这些传输消息生成的指纹。它是基于四层网络传输协议,在第四层,即传输层被使用的。而我上面举的例子,比如说向浏览器种token,把它添加到传输报文的报文头中,服务器对于浏览器提交带着的这个token进行校验,以确定其合法性,其实它作用的是应用层协议(第七层)中使用的身份验证机制,而并非传输层(四层网络传输协议的第4层),这里要注意区分。

对于tls的生成,其实有很多算法,但是JA3算法被最广泛使用,那相比于其它算法,它有什么样的优势和劣势呢?我做了个图表进行总结,供大家参考:

算法

优点

缺点

JA3 指纹算法

可以识别 TLS 客户端版本;可以基于握手消息内容生成指纹,具有更高的精度;在不同设备和操作系统上的一致性较好; 它是一种开放标准,任何人都可以实现它并将其集成到自己的应用程序或工具中,这使它成为一个通用的、可扩展的方案; 可用来验证TLS是否被篡改,与SSL证书指纹不同,JA3算法可以检测中间人攻击等网络层面的攻击行为;

无法判断代理层的影响;无法识别使用自定义密码套件的客户端;只能用于 TLS 握手识别。

SSL/TLS 证书指纹算法

不受代理层、客户端版本等因素的影响;可以识别采用自定义密码套件的客户端。

无法识别中间人攻击;证书签发机构可能存在错误或欺诈。

HTTP 消息头指纹算法

可以识别代理层、CDN 等影响;适用范围广,可用于 HTTP 流量识别。

可能存在误判;对于加密流量而言,只能识别应用层信息。

TCP/IP 指纹算法

可以识别代理层、NAT 等影响;可以在网络层识别流量。

具有较高的误判率;对于加密流量而言,只能识别网络层信息。

DNS 指纹算法

可以在域名解析阶段进行指纹识别;不受代理层等因素的影响。

无法识别加密流量;可能存在 DNS 缓存的干扰。

python中有JA3算法也非常常用,特别是下列一些场景:

  1. 识别恶意软件:通过 JA3 算法可以识别出具有特定 JA3 指纹的恶意软件,从而帮助网络安全人员及时发现和防范攻击。
  2. 流量识别:JA3 算法可以用于流量识别和分类,帮助工程师进行流量监控、分析等操作。
  3. 加密流量检测:由于 JA3 算法可以识别 TLS 客户端版本和加密套件,因此它可以被用来检测加密流量是否合法以及是否遵循最佳实践。
  4. 网络侦查:JA3 算法可以用于识别用户访问网站的类型、客户端操作系统、浏览器版本等信息,帮助网络侦查人员了解对方的技术能力和行为习惯。
  5. 安全策略制定:通过对 JA3 数据的统计和分析,可以了解不同客户端的使用情况,并据此制定相应的安全策略和措施,提高网络安全性。

第二部分

那如何来部署golang服务,让其支持动态更新TLS certificates而无需停机?我们知道Transport Layer Security(TLS)是一种基于SSLv3的加密协议,用于在两个站点之间加密和解密流量。换言之,TLS确保你正在访问的站点和你之间数据的传输数据不被侦测到。这是通过相互交换数字证书来实现的:一个存在于web服务器上的私有证书,另一个通常随web浏览器分发的公共证书。

在生产环境,服务都是以安全方式运行,但服务验证经过一定周期就会过期。然后对于服务响应去验证、重新生成,同时不用停机,就可以重新使用生成的验签证书。这篇文章,演示一下TLS验证是在基于golang语言的HTTPS服务是如何使用的。

这篇教程有先要满足下面这些先决条件。

  • 要对客户端-服务端模型要有基本理解
  • Golang的基础知识
配置HTTP Server

开始这篇文章之前,先演示一个简单的HTTP服务,只需要使用http.ListenAndServe函数启动一个HTTP服务,再用http.HandleFunc函数对于特定endpoint注册一个response handler

开始,配置HTTP服务:

代码语言:javascript
复制
package main
import (
    "net/http"
    "fmt"
    "log"
)

func main(){
    mux := http.NewServeMux()
    mux.HandleFun("/",func(res http.ResponseWriter, req *http.Request){
        fmt.Fprint(res, "Running Http Servcer")
    })

    srv := &http.Server{
        Addr: fmt.Sprintf(":%d",8080),
        Handler: mux,
    }
    //在8080端口运行
    log.Fatal(srv.ListenAndServe())
}

上面例子,用go run server.go,会在HTTP服务的8080端口运行,浏览器输入http://localhost:8080,你就会看到Hello World!输出在屏幕上。

srv.ListenAndServe()调用了go语言HTTP服务的标准库配置,然而,你可以使用Server结构类型来定制server

启动一个HTTPS服务,用配置调用函数srv.ListenAndServeTLS(certFile,keyFile),与srv.ListenAndServe()函数相似。ListenAndServeListenAndServeTLS两个函数在HTTP包和Server结构中都是可用的。

ListenAndServeTLSListenAndServe函数类似,除了前者对HTTPS服务的支持。

代码语言:javascript
复制
func ListenAndServeTLS(certFile string,keyFile string) error

从以上函数签名上可以看出,两者唯一的区别在于额外的certFilekeyFile参数,分别代表SSL Certificate文件的路径和private key文件。

生成private keySSL certificate

以下是生成根key和certificate的步骤:

    1. 生成根key

openssl genrsa -des3 -out rootCA.key 4096

    1. 创建和对根certificateself-signature(自签名)

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

接着,按下面的方式为每个服务生成certificate:

    1. 创建certificate key openssl genrsa -out localhost.key 2048
    1. 创建certificate-signing request(CSR)CSR是在哪里指定你想生成的certificate的详情。根key的拥有者将执行request来生成certificate。当创建CSR时,重要的是指定提供IP地址的Common Name,或者服务的域名,否则certificate无法验证。
代码语言:javascript
复制
openssl req -new -key localhost.key -out localhost.csr
    1. 使用TLS CSR和密钥以及CA根密钥生成证书
代码语言:javascript
复制
openssl x509 -req -in localhost.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out localhost.crt -days 500 -sha256

最后,遵循同样步骤为每个客户端生成certificate

配置一个HTTPS Server

既然有private keycertificate文件,就可以对先前的go程序做修改了,这次用ListenAndServeTLS代替。

代码语言:javascript
复制
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,使用localhost.key作为keyFile启动一个HTTPS服务。再浏览器访问https://localhost:8443,或者命令行工具(CLI),可以看到如下输出:

代码语言:javascript
复制
$ curl https://localhost:8443/ --cacert rootCA.crt --key client.key --cert client.crt 

Running HTTPS Server!!

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

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

当运行以上的HTTPS服务,你把certFilekeyFile传给了ListenAndServeTLS函数,然而,如果因为certificate过期certFilekeyFile发生变化,服务需要重启来使变化生效,为了克服这种中断导致的的短暂服务中断,可以使用net/http包的TLSConfig

cryto/tls包的TLSConfig结构会配置服务的TLS参数,包括服务证书等。所有TLSConfig的参数都是可选项,同时也要注意给TLSconfig的参数配置选项赋以空结构,就等同于赋个nil值给它。然而,配置GetCertificate字段却是相当有益的。

代码语言:javascript
复制
type Config struct{
    GetCertificate func(*ClientHelloInfo) (*Certicate,error)
}

TLSConfigGetCertificate字段会基于ClientHelloInfo返回证书。

我需要实现GetCertificate闭包函数,该函数使用tls.LoadX509KeyPair(certFile string, keyFile string) 或者 tls.X509KeyPair(certFile []byte, keyFile []byte)函数来获取证书,举两个例子:

代码语言:javascript
复制
#这是使用tls.LoadX509KeyPair(certFile string, keyFile string)函数的例子
func GetCertificate(certFile string, keyFile string) func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
    cert, err := tls.LoadX509KeyPair(certFile, keyFile)
    if err != nil {
        log.Fatal(err)
    }
    return func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
        return &cert, nil
    }
}
#这是使用tls.X509KeyPair的例子:
func GetCertificate(certData []byte, keyData []byte) func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
    cert, err := tls.X509KeyPair(certData, keyData)
    if err != nil {
        log.Fatal(err)
    }
    return func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
        return &cert, nil
    }
}
#以上两个函数使用后都会返回一个闭包,可以用作tls.config结构体中的GetCertificate字段。

现在我将使用TLSConfig字段值创建Server结构:

代码语言:javascript
复制
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) {
                                               // 总是获取最新的localhost.crt和localhost.key 
                                               // 将证书文件保存在全局位置中,这样创建新证书时可以更新它们,并且该闭包函数可以引用它。
    cert, err := tls.LoadX509KeyPair("localhost.crt", "localhost.key")
    if err != nil {
     return nil, err
    }
    return &cert, nil
   },
  },
 }

 // 在8443端口运行服务
 log.Fatal(srv.ListenAndServeTLS("", ""))
}

以上程序中,我实现了GetCertificate闭包函数,通过使用LoadX509KeyPair及证收和之前创建的私有文件,返回了一个类型为Certificatecert对象。同时函数了一个error,方便调试和追踪。

由于我正在用TLS配置,为srv服务对象做预配置,我不需要给srv.ListenAndServeTLS函数调用提供certFilekeyFile。运行服务,它会像之前一样运行,但是区别点就在于,我从调用对象中抽象了所有的服务配置,因此这些配置即便更新,也会动态加载,而不必重启服务。

代码语言:javascript
复制
$ curl https://localhost:8443/ --cacert rootCA.crt --key client.key --cert client.crt 

Running HTTPS Server!!

第三部分

好了,这篇有关如何抽象TLS服务配置,达到不需要重启服务就能加载变更证书的文章就分享至些,感谢阅读,我特别将可用于tls加密的指纹算法提到第一段来讲,并把JA3指纹算法在四层服务传输协议中的使用,和浏览器token验签属于应用层(七层网络传输服务)协议中使用的身份验证机制做了区分,方便你在今后使用的过程中有更深刻的理解。

四层服务传输协议(TCP/IP模型)和七层服务传输协议(OSI模型)的对比如下:

OSI模型

TCP/IP模型

应用层(7)

应用层(4)

表示层(6)

会话层(5)

传输层(4)

传输层(3)

网络层(3)

网际层(2)

数据链路层(2)

网络接口层(1)

物理层(1)

其中,TCP/IP模型将原本的“会话层、表示层、应用层”合并为一个应用层,而将原本的“网络层、数据链路层、物理层”分别放在了网际层、网络接口层两个层级中。

下面是四层服务传输协议(TCP/IP模型)和七层服务传输协议(OSI模型)每层常见的使用场景的对比图表:

OSI 模型

TCP/IP 模型

常见的使用场景

应用层(7)

应用层(4)

HTTP、FTP、SMTP等应用程序

表示层(6)

数据压缩和加密

会话层(5)

远程访问和RPC

传输层(4)

传输层(3)

TCP和UDP协议

网络层(3)

网际层(2)

IP、ICMP、ARP等

数据链路层(2)

网络接口层(1)

以太网、WiFi、ATM等

物理层(1)

传输介质及物理设备

在 OSI 模型中,每一层都有自己的功能和特点。应用层负责定义应用程序之间的交互规则;表示层用于对应用数据进行编码和解码;会话层为不同主机上的应用程序之间建立会话连接;传输层提供端到端的可靠数据传输服务;网络层负责将数据包从源主机传输到目标主机;数据链路层管理网络节点之间的数据帧传输;物理层负责传输介质及物理设备。

在 TCP/IP 模型中,应用层包含了 OSI 模型的应用层、表示层和会话层的功能;传输层提供端到端的可靠数据传输服务;网际层负责将数据包从源主机传输到目标主机;网络接口层管理网络节点之间的数据帧传输。

总之,在网络通信中,无论是使用 OSI 模型还是 TCP/IP 模型,每一层都有各自的功能和特点,能够互相配合完成数据传输和网络通信的任务。

这两个图表,相信可以让你对服务传输有更深刻的理解。感谢阅读。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-04-10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用golang部署运行tls的https服务时,不用停机,高效证书下放,如何实现?
    • 第一部分
      • 第二部分
        • 第三部分
        相关产品与服务
        数据传输服务
        腾讯云数据传输服务(Data Transfer Service,DTS)可帮助用户在业务不停服的前提下轻松完成数据库迁移上云,利用实时同步通道轻松构建高可用的数据库多活架构,通过数据订阅来满足商业数据挖掘、业务异步解耦等场景需求。同时,DTS 还提供私有化独立输出版本 DTS-DBbridge,支持异构数据库和同构数据库之间迁移和同步,可以帮助企业实现完整数据库迁移(如 Oracle)。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档