[golang]使用mTLS双向加密认证http通信

打印 上一主题 下一主题

主题 875|帖子 875|积分 2625

前言

假设一个场景,服务端部署在内网,客户端需要通过暴露在公网的nginx与服务端进行通信。为了避免在公网进行 http 明文通信造成的信息泄露,nginx与客户端之间的通信应当使用 https 协议,并且nginx也要验证客户端的身份,也就是mTLS双向加密认证通信。
这条通信链路有三个角色:服务端、Nginx、客户端。

  • 服务端部署在内网,与nginx使用http通信。
  • 客户端在公网,与nginx使用https通信,且双向加密认证。

服务端

服务端只使用http,所以这里用gin框架写个简单的示例,返回客户端一些基本的http信息,比如客户端IP、请求方法、host等。
  1. package main
  2. import (
  3.         "log"
  4.         "net/http"
  5.         "time"
  6.         "github.com/gin-gonic/gin"
  7. )
  8. /* 中间件: 获取api处理时长 */
  9. func midElapsed(c *gin.Context) {
  10.         start := time.Now()
  11.         c.Next()
  12.         elapsed := time.Since(start)
  13.         log.Printf("API: %s, elapsed: %s", c.Request.URL.Path, elapsed)
  14. }
  15. /* 处理 GET / 请求 */
  16. func f1(c *gin.Context) {
  17.         // 获取客户端IP
  18.         clientIP := c.ClientIP()
  19.         // 获取请求方法
  20.         method := c.Request.Method
  21.         // 获取协议
  22.         proto := c.Request.Proto
  23.         // 获取host
  24.         host := c.Request.Host
  25.         // 请求Path
  26.         path := c.Request.URL.Path
  27.         log.Printf("客户端IP: %s, 请求方法: %s, 协议: %s, host: %s, path: %s", clientIP, method, proto, host, path)
  28.         // 获取请求头
  29.         headers := c.Request.Header
  30.         for hk, hv := range headers {
  31.                 log.Printf("header key: %s, value: %s", hk, hv)
  32.         }
  33.         // 获取名为"mycookie"的cookie
  34.         var cookies []string
  35.         cookie, err := c.Cookie("mycookie")
  36.         if err != nil {
  37.                 log.Printf("get cookie [mycookie] error: %s", err)
  38.         } else {
  39.                 log.Printf("get cookie [mycookie]: %s", cookie)
  40.                 cookies = append(cookies, cookie)
  41.         }
  42.         c.JSON(http.StatusOK, gin.H{
  43.                 "clientIP": clientIP,
  44.                 "method":   method,
  45.                 "proto":    proto,
  46.                 "host":     host,
  47.                 "headers":  headers,
  48.                 "cookies":  cookies,
  49.                 "path":     path,
  50.         })
  51. }
  52. func main() {
  53.         r := gin.Default()
  54.         r.Use(midElapsed) // 全局引用计算耗时的中间件
  55.         r.GET("/", f1)
  56.         r.Run("0.0.0.0:8080")
  57. }
复制代码
生成证书


  • 生成ca根证书。生成过程会要求填写密码、CN、ON、OU等信息,记住密码,填写的信息也要和下一步openssl.cnf文件内容一致。
  1. openssl req -x509 -newkey rsa:4096 -keyout ca.key -out ca.crt -days 3650
复制代码

  • 新建并编辑文件openssl.cnf文件。req_distinguished_name中内容按需填写,DNS.1要替换成实际域名。
  1. [req]
  2. req_extensions = v3_req
  3. distinguished_name = req_distinguished_name
  4. prompt = no
  5. [req_distinguished_name]
  6. countryName = CN
  7. stateOrProvinceName = Anhui
  8. localityName = Hefei
  9. organizationName = zhangsan
  10. commonName = qw.er.com
  11. [v3_req]
  12. subjectAltName = @alt_names
  13. [alt_names]
  14. DNS.1 = qw.er.com
复制代码

  • 生成服务端证书
  1. openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr -subj "/CN=qw.er.com" -config openssl.cnf
  2. # 提示输入ca私钥的密码
  3. openssl x509 -req -in server.csr -out server.crt -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -extensions v3_req -extfile openssl.cnf
复制代码

  • 生成客户端证书
  1. openssl req -newkey rsa:2048 -nodes -keyout client.key -out client.csr -subj "/CN=qw.er.com" -config openssl.cnf
  2. # 提示输入ca私钥的密码
  3. openssl x509 -req -in client.csr -out client.crt -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -extensions v3_req -extfile openssl.cnf
复制代码
Nginx配置

nginx反向代理服务端的配置示例如下
  1. server {
  2.     listen 80 ssl;
  3.     server_name qw.er.com;
  4.     ssl_certificate /home/atlas/apps/nginx/certs/qwer/server.crt;
  5.     ssl_certificate_key /home/atlas/apps/nginx/certs/qwer/server.key;
  6.    
  7.     # 校验客户端证书
  8.     ssl_verify_client on;
  9.     ssl_client_certificate /home/atlas/apps/nginx/certs/qwer/ca.crt;
  10.     location / {
  11.         proxy_set_header Host $host;
  12.         proxy_set_header X-real-ip $remote_addr;
  13.         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  14.         proxy_pass http://192.168.0.10:8080; # 服务端地址
  15.     }
  16. }
复制代码
客户端

以下示例使用命令行传参的方式,指定tls证书文件和是否使用tls通信。
  1. package main
  2. import (
  3.         "crypto/tls"
  4.         "crypto/x509"
  5.         "flag"
  6.         "io"
  7.         "log"
  8.         "net/http"
  9.         "os"
  10.         "time"
  11. )
  12. var (
  13.         cafile  = flag.String("cafile", "ca.crt", "ca 证书文件")
  14.         crtfile = flag.String("crtfile", "client.crt", "客户端tls证书")
  15.         keyfile = flag.String("keyfile", "client.key", "客户端tls私钥")
  16.         url     = flag.String("url", "http://127.0.0.1:8080", "url")
  17.         isTls   = flag.Bool("tls", false, "是否使用tls")
  18. )
  19. func tlsClient(cafile, crtfile, keyfile string) *http.Transport {
  20.         // 加载证书和私钥
  21.         clientCert, err := tls.LoadX509KeyPair(crtfile, keyfile)
  22.         if err != nil {
  23.                 log.Fatalf("load key pair error: %s", err)
  24.         }
  25.         // 加载ca证书
  26.         clientCA, err := os.ReadFile(cafile)
  27.         if err != nil {
  28.                 log.Fatalf("load ca cert error: %s", err)
  29.         }
  30.         // 创建根证书池并添加ca证书
  31.         caCertPool := x509.NewCertPool()
  32.         caCertPool.AppendCertsFromPEM(clientCA)
  33.         // 创建transport
  34.         tr := &http.Transport{
  35.                 TLSClientConfig: &tls.Config{
  36.                         Certificates: []tls.Certificate{clientCert},
  37.                         RootCAs:      caCertPool,
  38.                 },
  39.         }
  40.         return tr
  41. }
  42. func main() {
  43.         flag.Parse()
  44.         req, err := http.NewRequest("GET", *url, nil)
  45.         if err != nil {
  46.                 log.Fatalf("new request error: %s", err)
  47.         }
  48.         // 自定义HTTP请求头
  49.         req.Header.Set("myheader1", "myheader1value123")
  50.         // 自定义一个cookie对象
  51.         cookie := &http.Cookie{
  52.                 Name: "mycookie",
  53.                 Value: "mycookievalue",
  54.         }
  55.         req.AddCookie(cookie)
  56.         client := &http.Client{
  57.                 Timeout: time.Second * 5,
  58.         }
  59.         if *isTls {
  60.                 client.Transport = tlsClient(*cafile, *crtfile, *keyfile)
  61.         }
  62.         resp, err := client.Do(req)
  63.         if err != nil {
  64.                 log.Fatalf("get error: %s", err)
  65.         }
  66.         defer resp.Body.Close()
  67.         body, err := io.ReadAll(resp.Body)
  68.         if err != nil {
  69.                 log.Fatalf("read error: %s", err)
  70.         }
  71.         log.Printf("body: %+v", string(body))
  72. }
复制代码
Nginx配置
  1. server {
  2.     listen 80 ssl;
  3.     server_name qw.er.com;
  4.     ssl_certificate /home/elifen/apps/nginx/certs/qwer/server.crt;
  5.     ssl_certificate_key /home/elifen/apps/nginx/certs/qwer/server.key;
  6.     ssl_verify_client on;
  7.     ssl_client_certificate /home/elifen/apps/nginx/qwer/ca.crt;
  8.     location / {
  9.         proxy_set_header Host $host;
  10.         proxy_set_header X-real-ip $remote_addr;
  11.         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  12.         proxy_pass http://192.168.0.10:8080;
  13.     }
  14. }
复制代码
测试

这里需要先确保qw.er.com能被正常解析到nginx服务器,比如配置hosts文件或dns解析记录。
  1. go run main.go -cafile ./ca.crt -crtfile ./client.crt -keyfile ./client.key -url 'https://qw.er.com:80/' -tls
复制代码
输出示例
  1. 2023/08/07 17:34:51 body: {"clientIP":"192.168.0.11","cookies":["mycookievalue"],"headers":{"Accept-Encoding":["gzip"],"Connection":["close"],"Cookie":["mycookie=mycookievalue"],"Myheader1":["myheader1value123"],"User-Agent":["Go-http-client/1.1"],"X-Forwarded-For":["192.168.0.11"],"X-Real-Ip":["192.168.0.11"]},"host":"qw.er.com","method":"GET","path":"/","proto":"HTTP/1.0"}
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

悠扬随风

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表