Posted in

Go的net/http默认配置暗藏3大离谱后门:KeepAlive无限维持、Header大小不限、TLS握手无超时(CVE级风险预警)

第一章:Go的net/http默认配置暗藏3大离谱后门:KeepAlive无限维持、Header大小不限、TLS握手无超时(CVE级风险预警)

Go 标准库 net/http 以“开箱即用”著称,但其默认 Server 配置在生产环境中实为高危裸奔状态。以下三大默认行为已被多个真实攻击链利用,构成可被归类为 CVE 级别的拒绝服务与资源耗尽风险。

KeepAlive 无限维持连接

http.Server 默认启用 KeepAliveIdleTimeout 为 0(即永不超时),导致恶意客户端可长期霸占连接池,耗尽 MaxConns 或文件描述符。
修复方式:显式设置超时值——

server := &http.Server{
    Addr:         ":8080",
    Handler:      myHandler,
    IdleTimeout:  30 * time.Second,     // 关键:强制空闲连接30秒后关闭
    ReadTimeout:  10 * time.Second,     // 防止慢读攻击
    WriteTimeout: 10 * time.Second,     // 防止慢写攻击
}

Header 大小完全不限制

net/http 默认不对请求头总长度设限,攻击者可发送数 MB 的畸形 CookieUser-Agent,触发内存暴涨甚至 OOM。标准库无内置防护,需手动拦截。
验证方法:启动服务后执行

curl -H "X-Long-Header: $(printf 'A%.0s' {1..2000000})" http://localhost:8080/

观察进程 RSS 内存是否陡增 >100MB。

TLS 握手无超时控制

当使用 http.Server.TLSConfig 启动 HTTPS 时,net/http 不接管 TLS 握手超时——底层 tls.Conn.Handshake() 可无限阻塞,导致 goroutine 泄漏。此问题在 Go 1.21 前未暴露超时接口。
解决方案(Go 1.21+):

srv := &http.Server{...}
srv.TLSConfig = &tls.Config{
    GetConfigForClient: func(*tls.ClientHelloInfo) (*tls.Config, error) {
        // 此处无法设超时 → 改用 ListenAndServeTLS + 自定义 listener
    }
}
// ✅ 推荐做法:包装 listener 强制握手超时
ln, _ := tls.Listen("tcp", ":443", tlsConfig)
timeoutLn := &timeoutListener{Listener: ln, timeout: 5 * time.Second}
srv.Serve(timeoutLn)
风险项 默认值 安全建议值 触发后果
IdleTimeout 0(禁用) 30–60 秒 连接池耗尽、FD 耗尽
MaxHeaderBytes 0(不限) 1MB(1048576) 内存溢出、GC 压力飙升
TLS Handshake 无超时机制 5 秒(需自定义 listener) goroutine 永久泄漏

第二章:离谱一:KeepAlive无限维持——连接池失控与资源耗尽的完美风暴

2.1 HTTP/1.1 KeepAlive机制原理与Go标准库实现反模式分析

HTTP/1.1 默认启用持久连接(Keep-Alive),复用 TCP 连接以降低延迟与资源开销。客户端通过 Connection: keep-alive 显式声明,服务端响应中亦需携带该头,并设置 Keep-Alive: timeout=30, max=100 等参数。

Go 标准库中的隐式超时陷阱

// net/http/server.go 片段(简化)
srv := &http.Server{
    Addr: ":8080",
    ReadTimeout:  5 * time.Second,      // ❌ 误用:覆盖整个请求读取(含body)
    IdleTimeout: 30 * time.Second,     // ✅ 正确:仅控制空闲连接存活期
}

ReadTimeout 会中断未完成的请求体读取,导致 Keep-Alive 连接被非预期关闭;而 IdleTimeout 才真正约束连接空闲期,符合 RFC 7230 对“persistent connection lifetime”的定义。

Keep-Alive 行为对比表

参数 HTTP/1.1 规范语义 Go http.Server 实现效果
timeout 服务器最大空闲等待时间 IdleTimeout 精确映射
max 单连接最大请求数 无原生支持,需手动计数+主动关闭

连接复用状态流转(mermaid)

graph TD
    A[Client sends request] --> B{Connection idle?}
    B -->|Yes, < IdleTimeout| C[Reuse TCP]
    B -->|No or expired| D[Close and reconnect]
    C --> E[Server responds with Connection: keep-alive]

2.2 实验复现:单客户端触发数千长连接导致Server FD耗尽

为验证服务端文件描述符(FD)耗尽瓶颈,我们构建轻量级压测环境:单客户端并发建立 3000+ TCP 连接,服务端采用 epoll + 阻塞 accept 模式。

复现脚本核心逻辑

import socket, threading
def spawn_conn():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(5)
    s.connect(('127.0.0.1', 8080))  # 持有连接不发数据
    s.recv(1)  # 防止连接被快速重置
for _ in range(3200):
    threading.Thread(target=spawn_conn).start()

此脚本绕过连接池复用,每线程独占 FD;settimeout 避免阻塞挂起;recv(1) 维持 ESTABLISHED 状态。Linux 默认 ulimit -n 为 1024,3200 连接将迅速触达服务端 FD 上限。

关键观测指标

指标 说明
lsof -p <pid> \| wc -l 1025+ 服务进程打开的 FD 数超限
cat /proc/sys/fs/file-nr 1025 0 9223372036854775807 已分配 FD 接近 soft limit

FD 耗尽链路

graph TD
    A[客户端发起 connect] --> B[服务端 accept 返回 fd]
    B --> C[fd 存入 epoll 实例]
    C --> D[fd 占用内核 file_struct]
    D --> E[超出 ulimit -n → accept 返回 -1/EMFILE]

2.3 源码级追踪:http.Transport.IdleConnTimeout为何默认为0且未生效

http.Transport.IdleConnTimeout 的零值并非“禁用”,而是委托给底层连接的读写超时逻辑。Go 标准库在 roundTrip 流程中仅当该字段 > 0 时才启动独立的空闲连接清理 goroutine。

空闲连接清理触发条件

  • IdleConnTimeout > 0:启用 idleConnTimer,定期扫描 idleConn map
  • IdleConnTimeout == 0:跳过定时器启动,依赖 keepAlivesEnabledMaxIdleConnsPerHost 协同限流

关键源码片段(net/http/transport.go)

func (t *Transport) getIdleConnCh(m string) (chan *persistConn, bool) {
    if t.IdleConnTimeout <= 0 { // ← 零值直接跳过空闲管理
        return nil, false
    }
    // ... 启动 timer 和 idleConn map 维护
}

分析:getIdleConnCh 是复用连接的入口函数。当 IdleConnTimeout==0,立即返回 nil, false,后续所有空闲连接不会被纳入 t.idleConn 管理,自然无法触发超时关闭。

超时行为对比表

IdleConnTimeout 值 是否启用 idleConnTimer 连接是否进入 idleConn map 实际空闲淘汰机制
无(仅靠连接池容量驱逐)
30 * time.Second 定时扫描 + 显式 close
graph TD
    A[发起 HTTP 请求] --> B{IdleConnTimeout > 0?}
    B -->|Yes| C[启动 idleConnTimer]
    B -->|No| D[跳过空闲管理逻辑]
    C --> E[定期扫描 idleConn map]
    D --> F[仅按 MaxIdleConnsPerHost 驱逐]

2.4 生产环境惨案:K8s Ingress代理层OOM与连接泄漏真实案例

某日午间,集群Ingress Controller(Nginx-based)CPU突增至98%,随后Pod被OOMKilled反复重启,外部请求502激增。

根本诱因:Keepalive连接未回收

Ingress配置中keepalive_requests设为0(无限),但上游服务偶发慢响应,导致连接长期滞留:

# nginx-configmap.yaml
data:
  keepalive: "32"           # 最大空闲连接数
  keepalive_requests: "0"   # ⚠️ 实际等效于无上限(NGINX < 1.19.1)
  keepalive_timeout: "75s"  # 空闲超时,但不触发主动关闭

keepalive_requests: 0 在旧版NGINX中禁用请求数限制,连接仅靠keepalive_timeout释放;而上游服务TCP FIN延迟,造成TIME_WAIT堆积与文件描述符耗尽。

关键指标异常对比

指标 正常值 故障峰值
nginx_ingress_controller_nginx_process_connections_active ~1.2k >18k
process_open_fds ~2.4k 65,535(达ulimit上限)

连接泄漏路径

graph TD
  A[Client] -->|HTTP/1.1 keep-alive| B(Ingress Pod)
  B --> C{Upstream Service}
  C -.->|Slow ACK/FIN| D[Connection stuck in ESTABLISHED]
  D --> E[fd leak → OOM]

2.5 安全加固方案:动态IdleConnTimeout+MaxIdleConnsPerHost双阈值策略

在高并发 HTTP 客户端场景中,连接池失控是资源耗尽与连接泄漏的主因。静态配置易导致冷热不均:固定 IdleConnTimeout 无法适配流量峰谷,而统一 MaxIdleConnsPerHost 又难以平衡多租户/多服务间隔离性。

动态阈值设计原理

基于实时 QPS 与平均响应时长,按如下规则动态计算:

  • IdleConnTimeout = max(30s, min(300s, 5 × avgRT))
  • MaxIdleConnsPerHost = clamp(4, 2 × ceil(QPSₕᵢgₕₑₛₜ/10), 100)

核心实现代码

func newDynamicTransport(qpsMetric *expvar.Int, rtMetric *expvar.Float) *http.Transport {
    return &http.Transport{
        IdleConnTimeout: time.Duration(
            clampInt(30, int(5*rtMetric.Value()), 300)) * time.Second,
        MaxIdleConnsPerHost: clampInt(4,
            int(2*math.Ceil(float64(qpsMetric.Value())/10)), 100),
    }
}

逻辑分析IdleConnTimeout 避免过早回收活跃连接(防RT抖动误判),又防止长空闲连接滞留;MaxIdleConnsPerHost 按QPS线性扩缩,兼顾吞吐与内存可控性。clampInt 确保参数始终落在安全区间。

参数 最小值 推荐基线 上限 作用
IdleConnTimeout 30s 5×P95 RT 300s 平衡复用率与连接陈旧风险
MaxIdleConnsPerHost 4 2×(QPS/10) 100 防止单主机连接池挤占全局资源

连接池自适应流程

graph TD
    A[采集QPS/RT指标] --> B{是否触发重算?}
    B -->|是| C[更新IdleConnTimeout]
    B -->|是| D[更新MaxIdleConnsPerHost]
    C --> E[生效新Transport]
    D --> E

第三章:离谱二:Header大小不限——HTTP协议层拒绝服务的隐形导火索

3.1 RFC 7230对Header字段的隐含约束与Go标准库的零校验现实

RFC 7230 明确规定 HTTP/1.1 header 字段名须符合 token 语法(^[a-zA-Z0-9!#$%&'*+-.^_\|~]+$),且**禁止空格、控制字符及冒号后缀**;但 Go 标准库net/httpHeader.Set()Write()` 阶段完全跳过合法性校验

隐式约束 vs 实际行为

  • RFC 7230 要求:Content-Type 合法,Content :Type 非法(含空格)
  • Go 行为:h.Set("Content :Type", "text/plain") 无报错,直接写入 wire

危险示例

h := http.Header{}
h.Set("X-Forwarded-For", "127.0.0.1\r\nSet-Cookie: fake=1") // 注入换行

net/http 不过滤 CRLF,底层 bufio.Writer 原样输出,触发响应头注入(HTTP Response Splitting)。

校验缺失对比表

检查项 RFC 7230 要求 net/http 实现
字段名含空格 ❌ 禁止 ✅ 允许
值含 \r\n ❌ 禁止(分隔符) ✅ 透传
名称大小写敏感 ⚠️ 不敏感(规范建议小写) ✅ 保留原始大小写
graph TD
    A[Header.Set] --> B{RFC token 检查?}
    B -->|否| C[直接存入 map[string][]string]
    C --> D[WriteHeader → bufio.Writer.WriteString]
    D --> E[原始字节流出,无 sanitization]

3.2 PoC构造:1MB恶意Header触发goroutine阻塞与内存爆炸式增长

恶意Header构造原理

攻击者向Go HTTP服务器发送含1MB X-Forwarded-For 头的请求,该Header被net/http默认解析为单个字符串并缓存于Request.Header map中。

内存与调度双击穿机制

  • Go HTTP Server为每个请求启动独立goroutine处理;
  • headerValueToString()内部调用strings.TrimSpace()时,对超长字符串反复分配切片底层数组;
  • runtime无法及时GC,导致堆内存呈指数级碎片化增长;
  • 调度器因大量goroutine等待I/O(实际卡在字符串拷贝)而陷入高负载阻塞。

PoC核心片段

// 构造1MB恶意Header(仅示意,生产环境禁用)
maliciousHeader := strings.Repeat("a,", 500000) + "b" // 约1MB
req, _ := http.NewRequest("GET", "/", nil)
req.Header.Set("X-Forwarded-For", maliciousHeader)

此代码触发headerValueToString()strings.TrimSpace()对超长字符串执行O(n)遍历+多次append([]byte{}, ...),每次扩容均引发底层[]byte重分配,叠加runtime.mallocgc高频调用,最终耗尽P结构体本地缓存,强制触发全局GC风暴。

阶段 内存增幅 Goroutine状态
请求接收 +2MB Running → Runnable
Header解析 +8MB Blocked on memcpy
GC触发 +64MB All parked
graph TD
    A[Client发送1MB Header] --> B[http.Server.ServeHTTP]
    B --> C[parseHeader?]
    C --> D[headerValueToString]
    D --> E[strings.TrimSpace]
    E --> F[反复slice扩容+mallocgc]
    F --> G[堆内存爆炸 & P本地缓存耗尽]

3.3 对比测试:Nginx/Apache的Header限制策略与Go net/http的防御真空

HTTP头长度边界行为差异

Nginx 默认 large_client_header_buffers 为 4×8KB,Apache 通过 LimitRequestFieldSize(默认8190字节)硬限单个Header;而 Go net/http 无内置Header长度校验,仅依赖底层 bufio.Reader 的默认 4KB 缓冲——超长Header将直接触发 http: request header too large panic。

关键配置对照表

组件 配置项 默认值 是否可禁用校验
Nginx large_client_header_buffers 4 8k 否(需调大缓冲)
Apache LimitRequestFieldSize 8190 是(设为0不推荐)
Go net/http 无显式配置 ~4096*2? 否(需手动包装Server)

Go 中的防御补丁示例

// 自定义Header读取器,拦截超长字段
func limitHeaderSize(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        for name, values := range r.Header {
            for _, v := range values {
                if len(name)+len(v) > 8190 { // 模拟Apache阈值
                    http.Error(w, "Header field too long", http.StatusBadRequest)
                    return
                }
            }
        }
        next.ServeHTTP(w, r)
    })
}

逻辑分析:该中间件在路由前遍历所有Header键值对,按 key + value 字节总和判断是否越界。参数 8190 与Apache对齐,避免因服务端策略不一致导致的协议级拒绝服务(如X-Forwarded-For注入超长链)。

第四章:离谱三:TLS握手无超时——加密通道建立阶段的无限等待陷阱

4.1 TLS 1.3握手状态机与Go crypto/tls.Dial中Deadline缺失的致命设计

TLS 1.3 将握手压缩为1-RTT,状态机跃迁高度依赖时序:idle → client_hello_sent → server_hello_received → finished_sent。而 crypto/tls.Dial 默认不绑定 ContextDeadline,导致阻塞在 readServerHello 时无限等待。

状态机关键跃迁点

  • Client 发送 ClientHello 后进入 client_hello_sent
  • 若未收到 ServerHello(如中间设备丢包、服务端崩溃),连接卡死

Go 标准库的隐患代码

// ❌ 无超时控制:底层 conn.Read() 可能永久阻塞
conn, err := tls.Dial("tcp", "example.com:443", &tls.Config{
    MinVersion: tls.VersionTLS13,
})

该调用绕过 net.Dialer.Timeout,因 tls.Dial 内部直接使用未设 deadline 的 net.ConnreadHandshake 无上下文感知能力。

风险环节 是否受 Deadline 控制 原因
TCP 连接建立 ✅(若用 Dialer) Dialer.Timeout 生效
TLS 握手读取阶段 conn.SetReadDeadline 未被调用
graph TD
    A[Start Dial] --> B[Write ClientHello]
    B --> C[Block on readServerHello]
    C --> D{Server responds?}
    D -- Yes --> E[Proceed to 1-RTT]
    D -- No --> F[Hang indefinitely]

4.2 网络层模拟:SYN洪泛+FIN_WAIT2劫持导致Server goroutine永久挂起

复现关键状态机异常

当客户端发送 SYN 后不完成三次握手,服务端 net.Listener.Accept() 仍可返回连接,但后续 conn.Read() 在 FIN_WAIT2 残留连接上会永久阻塞——因内核未触发 EPOLLIN,Go runtime 无法唤醒对应 goroutine。

goroutine 挂起链路

// server.go: 简化版监听逻辑
ln, _ := net.Listen("tcp", ":8080")
for {
    conn, err := ln.Accept() // ✅ 返回伪“活跃”连接(SYN_RECV 状态残留)
    if err != nil { continue }
    go func(c net.Conn) {
        buf := make([]byte, 1024)
        _, _ = c.Read(buf) // ❌ 在 FIN_WAIT2 连接上永不返回
    }(conn)
}

c.Read() 底层调用 pollDesc.waitRead(),而 FIN_WAIT2 状态下 socket 既无数据也无 EOF,epoll_wait 不就绪,goroutine 永久休眠于 Gwaiting 状态。

状态迁移对照表

TCP 状态 Accept 是否返回 Read 是否阻塞 Go goroutine 状态
ESTABLISHED 否(有数据) Running → Done
SYN_RECV 是(Linux 特性) 是(零字节) Gwaiting(永久)
FIN_WAIT2 否(通常) 是(若 Accept 成功) Gwaiting(永久)

防御建议

  • 设置 SetReadDeadline() 强制超时
  • 使用 net.ListenConfig{KeepAlive: 30 * time.Second}
  • Accept() 后立即 SetDeadline() 并验证 conn.RemoteAddr() 可达性

4.3 源码剖析:tls.Config.GetConfigForClient未继承context超时的底层缺陷

问题根源定位

GetConfigForClient 是 TLS 握手阶段动态选择 *tls.Config 的回调函数,但其签名 func(*tls.ClientHelloInfo) (*tls.Config, error) 完全不接收 context.Context 参数,导致无法感知上游请求的 deadline 或 cancellation。

关键代码片段

// src/crypto/tls/handshake_server.go(Go 1.22+)
func (c *Conn) serverHandshake(ctx context.Context) error {
    // ⚠️ 此处已传入 ctx,但调用 GetConfigForClient 时却丢弃了它
    config := c.config
    if c.config.GetConfigForClient != nil {
        newConf, err := c.config.GetConfigForClient(&clientHelloInfo)
        if newConf != nil {
            config = newConf // 新 config 无 ctx 绑定能力
        }
    }
    // 后续 handshake 流程仍依赖 config 中的 Timeouts(如 ReadTimeout),但无法响应 ctx.Done()
}

逻辑分析:GetConfigForClient 返回的新 *tls.Config 实例独立于调用上下文,其内部 GetCertificateVerifyPeerCertificate 等回调仍运行在无 context 环境中,无法主动终止耗时证书查找或 OCSP 查询。

影响对比

场景 是否受 context 控制 原因
tls.Config.Timeouts ✅ 有限支持(仅读写超时) 依赖 net.Conn 底层设置
GetConfigForClient 内部 DNS/HTTP 请求 ❌ 完全失控 无 context 透传机制
GetCertificate 中私钥解密 ❌ 无法中断 阻塞式 crypto 操作

修复方向示意

graph TD
    A[ClientHello] --> B{GetConfigForClient?}
    B -->|Yes| C[注入 context-aware wrapper]
    B -->|No| D[使用默认 config]
    C --> E[NewConfigWithContext ctx]
    E --> F[可控的证书加载/验证]

4.4 替代方案:基于net.Conn包装器的强制TLS握手超时注入实践

当标准 tls.Dial 无法满足细粒度超时控制(如仅对握手阶段设限)时,可封装 net.Conn 实现精准干预。

核心思路

通过自定义 Conn 实现,在 Handshake() 调用前启动独立超时监控,避免阻塞整个连接生命周期。

关键实现片段

type timeoutConn struct {
    net.Conn
    tlsConn *tls.Conn
    handshaker tls.HandshakeTimeout
}

func (c *timeoutConn) Handshake() error {
    done := make(chan error, 1)
    go func() { done <- c.tlsConn.Handshake() }()
    select {
    case err := <-done: return err
    case <-time.After(c.handshaker): return errors.New("TLS handshake timeout")
    }
}

逻辑分析:handshaker 是毫秒级超时阈值;协程避免阻塞主 goroutine;通道缓冲为1确保无泄漏。tls.Conn 需提前初始化但暂不握手。

对比方案能力

方案 握手超时独立控制 复用标准 tls.Config 侵入性
tls.Dial + context ❌(影响整个 dial)
net.Conn 包装器
graph TD
    A[Client Dial] --> B[建立底层 TCP 连接]
    B --> C[Wrap as timeoutConn]
    C --> D[Handshake 启动协程]
    D --> E{超时触发?}
    E -->|是| F[返回 timeout 错误]
    E -->|否| G[返回 Handshake 结果]

第五章:从离谱到可靠:构建企业级HTTP服务的安全基线配置模板

配置失效的代价:一个真实生产事故回溯

某金融客户在Kubernetes集群中部署Nginx作为API网关,初始配置仅启用proxy_pass与基础日志,未设任何安全头。攻击者利用缺失X-Content-Type-Options: nosniff与宽松Content-Security-Policy,诱导用户点击伪造PDF链接,触发浏览器MIME类型猜测漏洞,执行内联JavaScript窃取JWT令牌。事后审计发现,该配置在CI/CD流水线中已沿用14个月,无人校验安全合规性。

关键安全头强制注入模板(Nginx)

以下为经PCI DSS与OWASP ASVS 4.0.3验证的最小可行头集合,需嵌入location /块内:

add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https:; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;" always;

注意:'unsafe-inline'仅限遗留系统临时豁免,新项目必须配合nonce或hash机制。

TLS协议与密钥交换硬性约束

使用OpenSSL 3.0+验证的现代密码套件组合(禁用TLS 1.0/1.1,强制ECDHE密钥交换):

协议版本 允许密码套件示例 禁用原因
TLS 1.2 ECDHE-ECDSA-AES128-GCM-SHA256 FIPS 140-3认证要求
TLS 1.3 TLS_AES_128_GCM_SHA256 移除静态RSA密钥交换
ALL ADH, EXP, EXPORT, NULL, RC4 已知加密弱点(CVE-2013-2566等)

自动化基线校验流水线

在GitLab CI中集成nginx -t与自定义安全扫描器,关键检查项包括:

  • 检测server_tokens off;是否全局启用
  • 验证client_max_body_size是否≤100m(防DoS)
  • 扫描proxy_set_header是否意外透传X-Forwarded-For原始值(需real_ip_header X-Real-IP; set_real_ip_from 10.0.0.0/8;配套)
flowchart LR
A[MR提交] --> B{Nginx配置变更?}
B -->|是| C[执行nginx -t语法校验]
C --> D[运行security-audit.sh脚本]
D --> E[检查X-Frame-Options是否为DENY]
D --> F[验证TLS最低版本≥1.2]
E --> G[准入合并]
F --> G

静态资源服务的最小权限隔离

/static/路径启用独立location块,禁用动态执行能力:

location ^~ /static/ {
    alias /app/dist/;
    expires 1y;
    add_header Cache-Control "public, immutable";
    # 显式禁止脚本执行
    types { } default_type text/plain;
    add_header Content-Disposition "attachment";
}

此配置使/static/malware.js响应头强制为text/plain,浏览器拒绝解析执行。

安全上下文注入实践

在Kubernetes Deployment中通过securityContext强化容器边界:

securityContext:
  runAsNonRoot: true
  runAsUser: 101
  seccompProfile:
    type: RuntimeDefault
  capabilities:
    drop: ["ALL"]

配合Nginx配置中的user nginx;指令,形成进程级与容器级双重权限收敛。

日志脱敏与威胁感知增强

重写默认access_log格式,自动过滤敏感字段:

log_format secure '$remote_addr - $remote_user [$time_local] '
                  '"$request" $status $body_bytes_sent '
                  '"$http_referer" "$http_user_agent" '
                  '$request_time $upstream_response_time '
                  'token_id=$arg_token_id '
                  'card_last4=$1';
map $request_uri $card_last4 {
  ~\/api\/payment\/\w+\/(\d{4})$ $1;
  default "-";
}

对 Go 语言充满热情,坚信它是未来的主流语言之一。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注