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

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

前言

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

这条通信链路有三个角色:服务端、Nginx、客户端。

服务端部署在内网,与nginx使用http通信。

客户端在公网,与nginx使用https通信,且双向加密认证。

服务端

服务端只使用http,所以这里用gin框架写个简单的示例,返回客户端一些基本的http信息,比如客户端IP、请求方法、host等。

package main

import (

"log"

"net/http"

"time"

"github.com/gin-gonic/gin"

)

/* 中间件: 获取api处理时长 */

func midElapsed(c *gin.Context) {

start := time.Now()

c.Next()

elapsed := time.Since(start)

log.Printf("API: %s, elapsed: %s", c.Request.URL.Path, elapsed)

}

/* 处理 GET / 请求 */

func f1(c *gin.Context) {

// 获取客户端IP

clientIP := c.ClientIP()

// 获取请求方法

method := c.Request.Method

// 获取协议

proto := c.Request.Proto

// 获取host

host := c.Request.Host

// 请求Path

path := c.Request.URL.Path

log.Printf("客户端IP: %s, 请求方法: %s, 协议: %s, host: %s, path: %s", clientIP, method, proto, host, path)

// 获取请求头

headers := c.Request.Header

for hk, hv := range headers {

log.Printf("header key: %s, value: %s", hk, hv)

}

// 获取名为"mycookie"的cookie

var cookies []string

cookie, err := c.Cookie("mycookie")

if err != nil {

log.Printf("get cookie [mycookie] error: %s", err)

} else {

log.Printf("get cookie [mycookie]: %s", cookie)

cookies = append(cookies, cookie)

}

c.JSON(http.StatusOK, gin.H{

"clientIP": clientIP,

"method": method,

"proto": proto,

"host": host,

"headers": headers,

"cookies": cookies,

"path": path,

})

}

func main() {

r := gin.Default()

r.Use(midElapsed) // 全局引用计算耗时的中间件

r.GET("/", f1)

r.Run("0.0.0.0:8080")

}

生成证书

生成ca根证书。生成过程会要求填写密码、CN、ON、OU等信息,记住密码,填写的信息也要和下一步openssl.cnf文件内容一致。

openssl req -x509 -newkey rsa:4096 -keyout ca.key -out ca.crt -days 3650

新建并编辑文件openssl.cnf文件。req_distinguished_name中内容按需填写,DNS.1要替换成实际域名。

[req]

req_extensions = v3_req

distinguished_name = req_distinguished_name

prompt = no

[req_distinguished_name]

countryName = CN

stateOrProvinceName = Anhui

localityName = Hefei

organizationName = zhangsan

commonName = qw.er.com

[v3_req]

subjectAltName = @alt_names

[alt_names]

DNS.1 = qw.er.com

生成服务端证书

openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr -subj "/CN=qw.er.com" -config openssl.cnf

# 提示输入ca私钥的密码

openssl x509 -req -in server.csr -out server.crt -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -extensions v3_req -extfile openssl.cnf

生成客户端证书

openssl req -newkey rsa:2048 -nodes -keyout client.key -out client.csr -subj "/CN=qw.er.com" -config openssl.cnf

# 提示输入ca私钥的密码

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反向代理服务端的配置示例如下

server {

listen 80 ssl;

server_name qw.er.com;

ssl_certificate /home/atlas/apps/nginx/certs/qwer/server.crt;

ssl_certificate_key /home/atlas/apps/nginx/certs/qwer/server.key;

# 校验客户端证书

ssl_verify_client on;

ssl_client_certificate /home/atlas/apps/nginx/certs/qwer/ca.crt;

location / {

proxy_set_header Host $host;

proxy_set_header X-real-ip $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_pass http://192.168.0.10:8080; # 服务端地址

}

}

客户端

以下示例使用命令行传参的方式,指定tls证书文件和是否使用tls通信。

package main

import (

"crypto/tls"

"crypto/x509"

"flag"

"io"

"log"

"net/http"

"os"

"time"

)

var (

cafile = flag.String("cafile", "ca.crt", "ca 证书文件")

crtfile = flag.String("crtfile", "client.crt", "客户端tls证书")

keyfile = flag.String("keyfile", "client.key", "客户端tls私钥")

url = flag.String("url", "http://127.0.0.1:8080", "url")

isTls = flag.Bool("tls", false, "是否使用tls")

)

func tlsClient(cafile, crtfile, keyfile string) *http.Transport {

// 加载证书和私钥

clientCert, err := tls.LoadX509KeyPair(crtfile, keyfile)

if err != nil {

log.Fatalf("load key pair error: %s", err)

}

// 加载ca证书

clientCA, err := os.ReadFile(cafile)

if err != nil {

log.Fatalf("load ca cert error: %s", err)

}

// 创建根证书池并添加ca证书

caCertPool := x509.NewCertPool()

caCertPool.AppendCertsFromPEM(clientCA)

// 创建transport

tr := &http.Transport{

TLSClientConfig: &tls.Config{

Certificates: []tls.Certificate{clientCert},

RootCAs: caCertPool,

},

}

return tr

}

func main() {

flag.Parse()

req, err := http.NewRequest("GET", *url, nil)

if err != nil {

log.Fatalf("new request error: %s", err)

}

// 自定义HTTP请求头

req.Header.Set("myheader1", "myheader1value123")

// 自定义一个cookie对象

cookie := &http.Cookie{

Name: "mycookie",

Value: "mycookievalue",

}

req.AddCookie(cookie)

client := &http.Client{

Timeout: time.Second * 5,

}

if *isTls {

client.Transport = tlsClient(*cafile, *crtfile, *keyfile)

}

resp, err := client.Do(req)

if err != nil {

log.Fatalf("get error: %s", err)

}

defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)

if err != nil {

log.Fatalf("read error: %s", err)

}

log.Printf("body: %+v", string(body))

}

Nginx配置

server {

listen 80 ssl;

server_name qw.er.com;

ssl_certificate /home/elifen/apps/nginx/certs/qwer/server.crt;

ssl_certificate_key /home/elifen/apps/nginx/certs/qwer/server.key;

ssl_verify_client on;

ssl_client_certificate /home/elifen/apps/nginx/qwer/ca.crt;

location / {

proxy_set_header Host $host;

proxy_set_header X-real-ip $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_pass http://192.168.0.10:8080;

}

}

测试

这里需要先确保qw.er.com能被正常解析到nginx服务器,比如配置hosts文件或dns解析记录。

go run main.go -cafile ./ca.crt -crtfile ./client.crt -keyfile ./client.key -url 'https://qw.er.com:80/' -tls

输出示例

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"}

相关推荐

侠盗飞车5
365比分下载

侠盗飞车5

📅 07-27 👁️ 8032
澳门世界杯男单半决赛:林诗栋4-1夺胜梁靖崑,晋级决赛迎战雨果
如何正确连接猫、路由器和交换机?详细教程分享