别再迷信“内网很安全”了:手搓一个 SPIFFE + mTLS + 网络策略的零信任小工程

简介: 别再迷信“内网很安全”了:手搓一个 SPIFFE + mTLS + 网络策略的零信任小工程

别再迷信“内网很安全”了:手搓一个 SPIFFE + mTLS + 网络策略的零信任小工程

Nov 9, 2026 · @Echo_Wish

先说句得罪人的话:内网,真没你想的那么安全

内网从来不是“自己人”的同义词,它只是“坏人还没进来”的另一种说法。

我干运维这些年,见过最离谱的一次事故,一点都不高大上,没有什么 0day。就是一台没人管的测试机被人拿下,然后顺着内网一路横着摸,摸到了生产数据库。复盘会上大家都沉默了,因为谁都说不出口:这个库为什么能被测试机直接连?答案很扎心——“都在内网里啊,一直都是这么通的。”

这就是传统边界安全的老毛病:城墙修得很高,城里却谁都能串门。只要有一个人翻进来,后面就是“进了村”。

零信任要解决的,就是这个问题。它的核心就三句话:不默认信任任何人,每一次访问都要验证,假设已经有人在里面了。

我先把观点亮出来:零信任不是一个能买回来的产品,它是一套习惯。 习惯就是——每个服务开口说话之前,先掏出身份证;每条网络路径,都得有人明确批准过才能走。

今天我带你用三样都是开源、都能落地的东西,搭一个能跑起来的小工程:

  • SPIFFE / SPIRE:给每个工作负载发一张“电子身份证”。
  • mTLS:双方互相验身份证,然后加密通信。
  • NetworkPolicy:默认全部拒绝,只开必须的口子。

代码我尽量贴全,你拿去改改就能在测试集群里跑。

三层各管一摊:身份、认证、路,缺一不可

这三样东西不是重复建设,而是各管各的活儿,凑一起才叫纵深防御。

咱先立个小场景:集群里有三个服务,web(前端网关)调 order(订单),order 再调 payment(支付)。我们的目标很朴素——payment 只认 order 的调用,其他谁来都不行,包括同一个命名空间里的“兄弟”。

  1. SPIFFE 回答“你是谁”。 每个 Pod 拿到一个形如 spiffe://example.org/ns/shop/sa/order 的身份,背后是短期自动轮换的证书(SVID)。身份跟 IP 没关系,Pod 漂到哪儿它都是它。
  2. mTLS 回答“我凭什么信你”。 双方都出示证书,互相验明正身,中间链路全程加密。不是只有客户端验服务端,服务端也得验客户端。
  3. NetworkPolicy 回答“你有没有资格走这条路”。 它在网络层先把大部分闲杂人等挡在门外,哪怕对方证书再漂亮,路不通就是不通。

有人会问:我只上 mTLS 不行吗?说实话,只靠 mTLS,坏人的包已经到你家门口了,你只是在门口审他;只靠 NetworkPolicy,路是堵上了,但路上走的是谁你并不知道。 两个加起来,才是“门口有保安,大门还上锁”。

第一步:先让每个服务有张“身份证”

身份发放是整个体系的地基,地基歪了,后面全是白搭。

SPIRE 是 SPIFFE 的参考实现,装起来比你想的轻松,官方有现成的 Helm Chart:

helm repo add spiffe https://spiffe.github.io/helm-charts-hardened/

# 先装 CRD,再装 SPIRE 本体
helm upgrade --install spire-crds spiffe/spire-crds -n spire-mgmt --create-namespace
helm upgrade --install spire spiffe/spire -n spire-mgmt \
  --set global.spire.trustDomain=example.org

装好之后,关键就一件事:告诉 SPIRE 谁配拿什么身份。 我们用 ClusterSPIFFEID 这个 CRD,按命名空间和 ServiceAccount 自动生成身份,不用一个个手工注册:

apiVersion: spire.spiffe.io/v1alpha1
kind: ClusterSPIFFEID
metadata:
  name: shop-workloads
spec:
  # 身份 = 命名空间 + ServiceAccount,和 IP 没有半毛钱关系
  spiffeIDTemplate: "spiffe://example.org/ns/{
   { .PodMeta.Namespace }}/sa/{
   { .PodSpec.ServiceAccountName }}"
  podSelector:
    matchLabels:
      spiffe: enabled
  namespaceSelector:
    matchLabels:
      kubernetes.io/metadata.name: shop

然后让业务 Pod 通过 CSI 把 Workload API 的 socket 挂进去,业务代码就能向 SPIRE 要证书了:

spec:
  serviceAccountName: order        # 一个服务一个 SA,别偷懒用 default
  containers:
    - name: order
      env:
        - name: SPIFFE_ENDPOINT_SOCKET
          value: unix:///spiffe-workload-api/spire-agent.sock
      volumeMounts:
        - name: spiffe-workload-api
          mountPath: /spiffe-workload-api
          readOnly: true
  volumes:
    - name: spiffe-workload-api
      csi:
        driver: csi.spiffe.io
        readOnly: true

这里我想啰嗦一句:“一个服务一个 ServiceAccount”这条,千万别省。 我见过太多集群,所有 Pod 都跑在 default 这个 SA 下面,那你发出去的身份全是同一个,等于给全公司的人都发了同一张工牌,后面的授权就没法做了。身份的粒度,决定了你后面能管得多细。

还有个好处我特别喜欢:SVID 默认是短命证书,自动轮换,不用你半夜被“证书过期”叫醒。以前这种告警,我可是没少挨。

第二步:服务之间用 mTLS 对暗号,只认指定的身份

mTLS 的重点不是“加密”,而是“双向验身份”,加密只是顺手的事。

有了身份证,下一步就是让服务互相验。我这里用 Go 的 go-spiffe 库举例,因为它把最麻烦的证书热更新全包了。先看服务端,也就是 payment:

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"

    "github.com/spiffe/go-spiffe/v2/spiffeid"
    "github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
    "github.com/spiffe/go-spiffe/v2/svid/x509svid"
    "github.com/spiffe/go-spiffe/v2/workloadapi"
)

func main() {
   
    ctx := context.Background()

    // 从 SPIRE 拿证书,后续轮换由库自动完成,业务代码无感知
    source, err := workloadapi.NewX509Source(ctx)
    if err != nil {
   
        log.Fatalf("拿不到 SVID: %v", err)
    }
    defer source.Close()

    // 核心:只放行 order 这一个身份
    allowed := spiffeid.RequireFromString("spiffe://example.org/ns/shop/sa/order")
    tlsCfg := tlsconfig.MTLSServerConfig(source, source, tlsconfig.AuthorizeID(allowed))

    mux := http.NewServeMux()
    mux.HandleFunc("/pay", func(w http.ResponseWriter, r *http.Request) {
   
        caller, _ := x509svid.IDFromCert(r.TLS.PeerCertificates[0])
        fmt.Fprintf(w, "收款成功,来电的是:%s\n", caller)
    })

    srv := &http.Server{
   Addr: ":8443", Handler: mux, TLSConfig: tlsCfg}
    log.Fatal(srv.ListenAndServeTLS("", ""))
}

再看客户端,也就是 order。注意它也要反过来验证对面真的是 payment,而不是一个冒牌货:

func newPaymentClient(ctx context.Context) (*http.Client, error) {
   
    source, err := workloadapi.NewX509Source(ctx)
    if err != nil {
   
        return nil, err
    }

    serverID := spiffeid.RequireFromString("spiffe://example.org/ns/shop/sa/payment")
    tlsCfg := tlsconfig.MTLSClientConfig(source, source, tlsconfig.AuthorizeID(serverID))

    return &http.Client{
   
        Transport: &http.Transport{
   TLSClientConfig: tlsCfg},
    }, nil
}

这段代码有个细节我挺想让你品一品:它校验的不是域名,也不是 IP,而是 SPIFFE ID。 以前我们总在纠结“这个域名的证书对不对”,现在问题变成“对面是不是我要找的那个服务”。思路一变,整个信任模型就清爽了。

另外多说一句。如果你的服务不是 Go 写的,或者压根不想改代码,也别慌:让 Envoy 当 sidecar,通过 SDS 从 SPIRE 拿证书,效果是一样的,只是代价换成了多一个 sidecar 的资源和排障复杂度。这笔账怎么算,得看你团队的实际情况,没有标准答案。

第三步:先把路全堵死,再一条一条开

默认拒绝是网络层零信任的灵魂:不是“谁不能访问就禁掉”,而是“谁能访问才放开”。

先上一条“全家都别动”的策略。注意前提:你的 CNI 得支持 NetworkPolicy,比如 Calico 或 Cilium,否则写了也是白写。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: shop
spec:
  podSelector: {
   }          # 命名空间里所有 Pod
  policyTypes: [Ingress, Egress]
---
# 堵死之后,DNS 得先放行,不然服务连名字都解析不了
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: shop
spec:
  podSelector: {
   }
  policyTypes: [Egress]
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - {
    protocol: UDP, port: 53 }
        - {
    protocol: TCP, port: 53 }

然后只开我们真正需要的两条路:order 能出去找 payment,payment 只收 order 的单。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: payment-only-from-order
  namespace: shop
spec:
  podSelector:
    matchLabels: {
    app: payment }
  policyTypes: [Ingress]
  ingress:
    - from:
        - podSelector:
            matchLabels: {
    app: order }
      ports:
        - {
    protocol: TCP, port: 8443 }
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: order-to-payment
  namespace: shop
spec:
  podSelector:
    matchLabels: {
    app: order }
  policyTypes: [Egress]
  egress:
    - to:
        - podSelector:
            matchLabels: {
    app: payment }
      ports:
        - {
    protocol: TCP, port: 8443 }

验证一下,别光写不测:

# order -> payment:应该通
kubectl -n shop exec deploy/order -- nc -zvw3 payment 8443

# web -> payment:应该被拦住(超时)
kubectl -n shop exec deploy/web -- nc -zvw3 payment 8443 || echo "被拦住了,很好"

我得提前跟你说句实话:默认拒绝刚上线的那几天,你大概率会很痛苦。 一堆“怎么突然不通了”的工单会涌过来。但我反而觉得这是好事,每一个“不通了”,背后都是一条你以前根本不知道的隐式依赖。这些依赖原来藏在“反正内网都通”的假象里,现在被一条条逼出来了。不通的那一刻,你才真正看清了自己的系统。

说点踩坑经验:这套东西,难的不在技术

这套方案真正的坑,几乎都不在原理上,而在那些“谁也没想到”的细节里。我把自己踩过、也见别人踩过的几个列一下:

  1. 身份发不下来。 别急着怀疑 SPIRE,先去 spire-server 里查一下注册条目,十有八九是 ServiceAccount 或标签没对上:

    # 在 spire-server 的 Pod 里执行
    spire-server entry show -spiffeID spiffe://example.org/ns/shop/sa/order
    
  2. 健康检查被自己人拦了。 kubelet 的探针不带证书,开了 mTLS 之后,探针直接被服务端拒绝,Pod 就在那儿循环重启。常见解法是单开一个只读的健康检查端口,再用 NetworkPolicy 把它圈起来,或者改用 exec 探针。

  3. 启动时序。 NewX509Source 会等到拿到证书才返回,如果 SPIRE agent 还没就绪,你的服务就会卡在启动阶段,探针和超时时间要留够余量。

  4. 时钟漂移。 短期证书对时间很敏感,节点时钟差个几分钟,就会冒出一堆莫名其妙的“证书无效”。NTP 这种老生常谈的东西,在这里突然变得很重要。

还有一条,我觉得比上面所有技术坑加起来都重要:别想着一口气全铺开。 我的建议是挑一条最关键的链路,比如“订单到支付”,先试点。先观察,把流量摸清楚,再切到强制拦截。很多团队的零信任项目死掉,不是技术不行,而是一上来就把业务搞崩,然后被一句“这玩意儿太折腾了”一票否决。

说句心里话,这几年我越来越觉得,安全方案的成败,取决于它让开发的日子好过还是难过。 如果每个开发为了调个接口,得先研究半天证书,他们一定会想办法绕过去。所以你看,SPIFFE 最让我服气的地方,不是它多“安全”,而是它把证书这件脏活累活藏到了库的后面,开发几乎无感。好的安全,应该是让人想不到去绕它。

最后想问你一句

零信任落地之后,最大的变化不是多了几个组件,而是你看待“信任”的方式变了。

回头看今天这个小工程:SPIFFE 让每个服务有了不会变的身份,mTLS 让每次通话都要先验明正身,NetworkPolicy 让每条路都得有人点头。三层叠起来,就算哪天真有一台测试机被拿下,它也只能面对一堵堵“你是谁、你凭什么过来”的墙,而不是一条畅通无阻的内网大道。

当然,它不是银弹。身份签发服务本身要保护好,策略写错了照样会放行,人为配置失误才是最常见的漏洞。但至少,我们把“默认信任”这个最大的坑给填上了。

最后留个问题给你,也是留给我自己的:你现在的系统里,有多少服务之间的互相信任,是因为“有人认真评估过”,又有多少,只是因为“一直都这样”?

这个问题,值得你下周一上班第一件事,就去查一查。

我是 Echo_Wish,咱们下篇接着聊。

目录
相关文章
|
10天前
|
Linux 开发工具 iOS开发
Codex保姆级教程(下载+安装+登录+使用,含所有版本,2026最新)
Codex是OpenAI推出的开源AI编程智能体,本地运行,可理解整个代码库,支持自然语言指令改代码、执行命令、跑测试、修Bug,兼容Win/macOS/Linux。(239字)
3781 14
|
10天前
|
JavaScript API iOS开发
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(DSH)是DeepSeek于2026年开源的MIT协议AI Agent框架,支持插件化组装模型、工具、沙箱与UI,兼容主流大模型。本文提供免Node.js的桌面端安装包(Win/macOS),含详细图文教程及热门插件推荐。
4044 46
|
11天前
|
iOS开发 Docker MacOS
【2026实测】Docker Desktop下载、安装、汉化、配置教程(附官网安装包)
Docker Desktop 是 Docker 官方推出的桌面容器管理工具,集成引擎、CLI、图形界面与 Kubernetes,支持 Win/macOS/Linux。2026 最新版 4.88.1 强化 ARM 原生运行、镜像加速及资源可视化,附中文汉化与国内镜像配置指南。
1878 6
|
5天前
|
云安全 人工智能 安全
|
5天前
|
存储 弹性计算 人工智能
2026 年阿里云双十一活动入口攻略,新客服务器优惠通道、三大会场入口梳理
综合来看,2026年阿里云双十一活动入口清晰: 阿里云轻量应用服务器、阿里云ECS云服务器、阿里云GPU云服务器、阿里云百炼大模型服务平台 活动会场、综合权益中心、99计划专场三大入口覆盖服务器、AI权益与续费特价;新用户通过控制台首页banner、权益中心、产品页三处路径进入专属专场,老用户通过控制台弹窗和费用中心权益中心进入老客专区,企业用户做企业实名后解锁专属满减与阶梯折扣。
811 0
|
4天前
|
Linux 开发工具 iOS开发
2026最新codex下载安装保姆级使用教程(下载+安装+登录+使用,含所有历史版本)
本文详解OpenAI Codex国内快速上手指南:提供Win/macOS/Linux全平台免安装包、npm/Homebrew等多方式安装、账号/API Key双登录、简体中文切换,覆盖下载、安装、汉化、使用及高频避坑(如微软商店失败、npm超时等),10分钟即可跑通。
930 1
|
11天前
|
安全 算法 定位技术
《高性价比人生指南》完整原版:普通人用最少的资源,过好这一生的底层逻辑~How To Live Better
📖《高性价比人生指南》是一份基于实证、拒绝焦虑的生存手册,倡导以“人生时薪”为视角,重新计算时间、金钱与精力的成本与收益。它不承诺逆袭,只教人算清账:省下的钱若耗掉时间便亏;买体验比买物品更值;睡眠是最高杠杆的健康投资;关系贵在情绪价值而非表面热络;学习重在改变行为而非自我安慰。核心理念:用低成本、高回报的微小行动,换取长期的选择权——不是成为谁,而是掌控自己人生的主动权。
2128 4
|
云安全 安全 网络安全
|
24天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
4202 56

热门文章

最新文章