mTLS必须设ClientAuth为tls.RequireAndVerifyClientCert且ClientCAs非空,否则降级单向TLS;客户端需完整证书链、0600私钥权限、RootCAs加载服务端CA。

gRPC 是 Go 微服务间调用的默认选择,不是因为它最简单,而是它在类型安全、性能、协议一致性上几乎没有替代方案。HTTP/JSON 虽然易调试,但字段错位、类型丢失、无契约约束,上线后容易因结构变更引发静默失败。真正要保障跨服务调用的安全性,不能只靠“加个中间件”或“配个 TLS”,得从通信链路、身份认证、凭证管理、运行时环境四层拆解。
为什么单向 TLS 不够,必须用 mTLS
单向 TLS(服务端有证书,客户端不验)只能防窃听,不能防冒充。攻击者只要拿到服务地址,就能伪造请求调用你的订单服务、用户服务。mTLS 强制双方交换并验证证书,确保只有持有合法客户端证书的服务才能接入——这才是微服务间“身份可信”的基础。
-
tls.Config.ClientAuth必须设为tls.RequireAndVerifyClientCert,设成tls.VerifyClientCertIfGiven会导致未带证书的请求静默通过 - 服务端
ClientCAs字段必须加载客户端证书的根 CA 证书池(*x509.CertPool),不是客户端自己的证书文件 - 客户端发起连接时,
tls.Config.RootCAs要加载服务端 CA,tls.Config.Certificates要加载自身完整证书链(含中间证书),否则握手失败报x509: certificate signed by unknown authority - gRPC 场景下,仅靠
credentials.NewTLS不够,必须在拦截器中用peer.FromContext(ctx)取出证书,并用x509.VerifyOptions校验签名,再检查Subject.Organization是否匹配预设值(如"myorg-services")
JWT 验证不能只调 token.Valid
签名有效 ≠ token 可用:过期的 token、被篡改的 aud、伪造的 iss,都会让 token.Valid 返回 true,但业务已失守。
- 必须用
jwt.ParseWithClaims+ 自定义Keyfunc,避免密钥硬编码或复用 - 解析后强制检查:
claims.VerifyExpiresAt(time.Now().Unix(), true)(注意第二个参数不能为false) - 显式比对
claims["aud"]和claims["iss"]是否匹配预设值(如"payment-service") - 检查
token.Header["alg"]是否为"HS256"或"RS256",直接拒绝"none" -
sub字段只放用户唯一标识,别塞权限列表、手机号、邮箱等敏感字段
HTTP 客户端调用下游时必须防 SSRF
微服务常通过 http.Client 调用其他服务,一旦 URL 来自用户输入(比如 webhook 地址、回调配置),就极易触发内网探测或数据库直连。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 禁用
http.DefaultClient,所有出向 client 显式设置Transport,并启用自定义DialContext做域名/IP 白名单校验 - 用
net.ParseIP+privateIP.IsPrivate()检查解析后的 IP,直接拒绝私有网段;对域名做 DNS 解析后校验(不能只看字符串是否含.local) - 上游服务若需传 URL,应限制协议为
https,禁止http、file、ftp等非标准scheme - Kubernetes 环境下可配合
NetworkPolicy限制 Pod 出向流量,但 Go 层仍需做第一道校验——策略不可替代代码逻辑
容器运行时必须以非 root 用户启动
Go 二进制无依赖,容易被 exec 直接提权。若容器默认以 root 启动,一次内存越界就等于交出整个容器控制权。
立即学习“go语言免费学习笔记(深入)”;
-
Dockerfile中USER指令必须放在COPY之后、ENTRYPOINT之前 -
USER必须指定数字 UID(如1001),不能写用户名(镜像里未必存在) - Kubernetes 中需同时设置
securityContext.runAsUser: 1001和runAsNonRoot: true,否则 Pod 可能忽略镜像USER回退到 root - 别用
gosu或su-exec在启动时切用户——Go 进程不 fork,这种 runtime 切换无效,还多一层攻击面
aud/iss 显式校验、以及 SSRF 防御中对 DNS 解析后 IP 的二次校验。这三处不落地,其他防护都可能形同虚设。

















