4、应用隔离实战:容器化隔离(Docker/K8s)、应用白名单机制、API网关与流量管控
应用层的横向扩散,说白了就是攻击者拿到一台机器后,通过进程、端口、API 调用去摸隔壁的服务。我见过太多案例——内网被打穿,不是因为防火墙没配好,而是应用之间太「信任」彼此了。今天咱们就聊聊怎么在应用层把路堵死。
4.1 容器化隔离:Docker 与 K8s 的实战姿势
容器不是虚拟机,它共享内核。这意味着一旦内核被突破,隔壁容器就是裸奔的。我个人的习惯是:把容器当进程管,别当机器管。
4.1.1 Docker 层面的微隔离
很多团队上来就 docker run,默认 bridge 网络,所有容器都能互相 ping 通。这其实很危险。我在项目中遇到过,一个 Web 容器被 getshell,攻击者直接 curl 了同宿主机的 Redis 容器——就因为没做网络隔离。
正确的做法是:
- 自定义网络:每个业务组一个独立的 bridge 网络
- --link 已废弃:别再用它,改用 network 间的连通性控制
- iptables 兜底:Docker 的网络规则本质是 iptables,你可以手动加 DROP 规则
实战命令示例:
# 创建两个隔离网络
docker network create --driver bridge net-frontend
docker network create --driver bridge net-backend
# 只让前端容器能访问后端
docker run --network net-frontend --name web-app -d nginx
docker run --network net-backend --name redis-cache -d redis
# 手动建立单向通道
docker network connect net-backend web-app
这样 web-app 能访问 redis-cache,但 redis-cache 无法主动连 web-app。攻击者就算拿下 Web 容器,想反向打 Redis 也得先过网络层。
4.1.2 K8s 环境下的网络策略
K8s 默认所有 Pod 都能通信——这设计初衷是好的,但安全场景下就是灾难。我记得有一次红蓝对抗,蓝队部署了微服务,结果攻击者从一个日志收集 Pod 横向扫遍了整个集群,就是因为没配 NetworkPolicy。
NetworkPolicy 是 K8s 原生的微隔离手段。它的核心逻辑是:没显式允许,就是拒绝。
我的建议: 新集群第一件事就是部署一个默认拒绝所有入站流量的 NetworkPolicy,然后再按需开放。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
spec:
podSelector:
matchLabels:
app: backend
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
这段 YAML 的意思是:所有 Pod 默认拒绝入站,只有打了 app=frontend 标签的 Pod 才能访问 backend 的 8080 端口。嗯,这里要注意——NetworkPolicy 依赖 CNI 插件支持,Calico、Cilium 都行,Flannel 默认不支持。
4.2 应用白名单机制
容器隔离解决的是网络层面的横向,但进程层面的横向呢?攻击者进了容器,能不能执行 wget、curl、bash?这就是白名单要管的事。
4.2.1 可执行文件白名单
我见过最典型的场景:攻击者通过 RCE 上传了一个小工具,然后 chmod +x 跑起来。如果容器里只有 busybox,那他能用的命令就有限。但很多基础镜像装了 python、perl、curl——这些全是横向扩散的利器。
怎么做?
- Distroless 镜像:Google 出的,里面只有应用和运行时,没有 shell、没有包管理器
- AppArmor / SELinux:强制限制进程能执行的文件路径
- Seccomp:限制系统调用,比如禁用 execve、fork 等
避坑指南: 我曾经在客户现场用 AppArmor 限制了一个 Java 容器,结果 JVM 启动时调用了 mprotect 被拦截,直接崩溃。所以白名单一定要先在 staging 环境跑一轮,别直接上生产。
4.2.2 文件系统只读挂载
攻击者想写文件?把根文件系统挂成只读就行。Docker 支持 --read-only 参数,K8s 里可以用 securityContext 设置 readOnlyRootFilesystem: true。
# Docker 启动只读容器
docker run --read-only --tmpfs /tmp --tmpfs /var/run nginx:alpine
这样容器只能写 /tmp 和 /var/run,其他目录全是只读。攻击者就算拿到 shell,想写个 cron 脚本或者 drop 个二进制文件?没门。
4.3 API 网关与流量管控
容器隔离和白名单管的是「能不能连」和「能不能执行」,但 API 层面的横向扩散还得靠网关。你想想看,攻击者如果已经拿到了一个微服务的 JWT Token,他能不能用这个 Token 去调别的微服务?
4.3.1 网关作为唯一入口
我个人的习惯是:所有外部请求必须经过 API 网关,内部服务之间也尽量走网关。这样流量管控点就集中了。
| 管控维度 | 说明 | 常用工具 |
|---|---|---|
| 身份认证 | 校验 JWT、OAuth2 Token | Kong、APISIX |
| 速率限制 | 防止暴力破解和 DDoS | Nginx + lua |
| 路由隔离 | 不同路径转发到不同后端 | Envoy、Traefik |
| 协议校验 | 只允许 JSON/Protobuf,拒绝二进制 payload | 自定义插件 |
4.3.2 服务网格的流量管控
如果微服务规模大了,API 网关管南北流量还行,东西流量(服务间调用)就力不从心了。这时候我推荐上服务网格,比如 Istio 或 Linkerd。
服务网格的核心是 Sidecar 代理。每个 Pod 旁边跑一个 Envoy,所有进出流量都经过它。你能做的事就多了:
- mTLS 强制加密:服务间通信自动加密,防止中间人
- 访问控制:基于 ServiceAccount 的 RBAC,不是你的服务就别想调
- 熔断与重试:防止一个服务故障后雪崩到其他服务
Istio 访问控制示例:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: backend-policy
namespace: default
spec:
selector:
matchLabels:
app: backend
rules:
- from:
- source:
principals: ["cluster.local/ns/default/sa/frontend-sa"]
to:
- operation:
methods: ["GET"]
paths: ["/api/v1/data"]
这段策略只允许 frontend-sa 这个 ServiceAccount 发起的 GET 请求访问 /api/v1/data,其他的一律拒绝。攻击者就算偷到了 Token,只要 ServiceAccount 不对,照样 403。
4.4 本章核心逻辑图
下面这张图把应用隔离的三个层次串起来了。你可以看到,从容器网络、到进程白名单、再到 API 管控,每一层都在阻断攻击者的横向路径。
这三层不是选一个就行,而是层层递进。容器网络隔离挡住网络扫描,白名单挡住恶意进程,API 管控挡住越权调用。攻击者想横向扩散,得连过三关——说实话,能全过的我还没见过几个。
个人经验: 如果你团队资源有限,优先做第一层和第三层。白名单的维护成本比较高,适合对安全要求极高的场景。我一般建议客户先上 NetworkPolicy + API 网关,跑顺了再加白名单。