Istio 旁路/镜像联动阻断方案(IngressGateway + AuthorizationPolicy + ModSecurity + Redis)

背景

旁路/镜像模式通过检测组件识别恶意请求,阻断则在入口或服务侧生效。Istio 提供 AuthorizationPolicy,可在 IngressGateway 或服务级进行精细的访问控制。本方案以 IngressGateway 为阻断点,使用 ModSecurity 作为专业的旁路检测引擎,检测结果(IP 和 User-Agent)写入 Redis,通过控制器动态下发 DENY 策略,实现“旁路检测→网关阻断”。

架构

  • 旁路检测:Nginx Ingress (镜像源) -> ModSecurity (检测) -> Log Processor (Sidecar) -> Redis
  • 决策引擎:Log Processor 实时分析审计日志,提取攻击源 IP 和特征 User-Agent 写入 Redis(含 TTL)
  • 执行器:控制器轮询 Redis,生成 Istio AuthorizationPolicy(包含 IP 黑名单和 UA 黑名单),下发至 IngressGateway
  • 观测:阻断率与误报率监测,定期调整

前置条件

  • 已部署 Istio(含 IngressGateway),Mesh 正常工作
  • 使用 Docker 运行第三方组件(如 Redis)
  • IngressGateway 能获取真实客户端 IP(确保 X-Forwarded-For/use_remote_address 配置合适)

部署步骤

1. Docker 运行 Redis

1
2
3
4
docker run -d \
  --name waf-redis \
  -p 6379:6379 \
  redis:7

Redis 键设计:

  • waf:ban:ip (Set) / waf:ban:ttl:<ip> (String)
  • waf:ban:ua (Set) / waf:ban:ttl:ua:<ua> (String)

2. 部署 ModSecurity 旁路检测器

创建一个包含 ModSecurity(Nginx版)和日志处理 Sidecar 的 Deployment。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
apiVersion: v1
kind: Namespace
metadata:
  name: waf-poc
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: waf-detector-config
  namespace: waf-poc
data:
  # Nginx 配置:启用 ModSecurity 并直接返回 200
  default.conf: |
    server {
        listen 80;
        server_name localhost;
        
        modsecurity on;
        modsecurity_rules_file /etc/nginx/modsec/main.conf;

        location / {
            return 200 "OK";
        }
    }
  
  # 日志处理脚本:读取日志 -> 提取 IP/UA -> 写入 Redis
  log_processor.py: |
    import time, json, os, redis
    
    LOG_FILE = "/var/log/modsec/audit.log"
    REDIS_HOST = os.getenv('REDIS_HOST', 'redis')
    
    r = redis.Redis(host=REDIS_HOST, port=6379, decode_responses=True)
    
    def follow(thefile):
        thefile.seek(0, 2)
        while True:
            line = thefile.readline()
            if not line:
                time.sleep(0.1)
                continue
            yield line

    print("Starting Log Processor...")
    while not os.path.exists(LOG_FILE):
        print(f"Waiting for {LOG_FILE}...")
        time.sleep(1)

    with open(LOG_FILE, "r") as f:
        for line in follow(f):
            try:
                entry = json.loads(line)
                transaction = entry.get('transaction', {})
                client_ip = transaction.get('client_ip')
                headers = transaction.get('request', {}).get('headers', {})
                user_agent = headers.get('User-Agent')
                messages = [m['message'] for m in transaction.get('messages', [])]
                
                if client_ip:
                    print(f"Attack detected from {client_ip}: {messages[0] if messages else 'Unknown'}")
                    
                    # 写入 IP 黑名单
                    r.sadd('waf:ban:ip', client_ip)
                    r.setex(f'waf:ban:ttl:{client_ip}', 600, '1')
                    
                    # 写入 User-Agent 黑名单 (示例:如果包含 BadBot)
                    if user_agent and "BadBot" in user_agent:
                        r.sadd('waf:ban:ua', user_agent)
                        r.setex(f'waf:ban:ttl:ua:{user_agent}', 3600, '1')
            except Exception as e:
                print(f"Error processing log: {e}")
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: waf-detector
  namespace: waf-poc
spec:
  replicas: 1
  selector:
    matchLabels:
      app: waf-detector
  template:
    metadata:
      labels:
        app: waf-detector
    spec:
      containers:
      - name: modsec
        image: owasp/modsecurity-crs:nginx
        env:
        - name: PARANOIA
          value: "1"
        - name: MODSEC_AUDIT_LOG_FORMAT
          value: "JSON"
        ports:
        - containerPort: 80
        volumeMounts:
        - name: config
          mountPath: /etc/nginx/conf.d/default.conf
          subPath: default.conf
        - name: logs
          mountPath: /var/log/modsec
          
      - name: processor
        image: python:3.11-slim
        command: ["sh", "-c"]
        args: ["pip install redis && python -u /app/log_processor.py"]
        env:
        - name: REDIS_HOST
          value: redis
        volumeMounts:
        - name: script
          mountPath: /app/log_processor.py
          subPath: log_processor.py
        - name: logs
          mountPath: /var/log/modsec
          
      volumes:
      - name: config
        configMap:
          name: waf-detector-config
      - name: script
        configMap:
          name: waf-detector-config
      - name: logs
        emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: waf-detector
  namespace: waf-poc
spec:
  selector:
    app: waf-detector
  ports:
  - port: 80
    targetPort: 80

3. 配置 Istio 流量镜像

在 VirtualService 中配置 Mirror,将流量复制一份给 WAF 检测器。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: app-vs
  namespace: default
spec:
  hosts:
  - example.com
  gateways:
  - istio-system/ingressgateway
  http:
  - route:
    - destination:
        host: app-svc.default.svc.cluster.local
        port:
          number: 80
    # 流量镜像配置
    mirror:
      host: waf-detector.waf-poc.svc.cluster.local
      port:
        number: 80
    mirrorPercentage:
      value: 100

4. 部署控制器 (Redis -> AuthorizationPolicy)

控制器负责将 Redis 中的黑名单转换为 Istio 的 AuthorizationPolicy

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
apiVersion: v1
kind: ConfigMap
metadata:
  name: waf-controller-istio
  namespace: waf-poc
data:
  controller.py: |
    import os, redis, time
    from kubernetes import client, config
    
    config.load_incluster_config()
    api = client.CustomObjectsApi()
    
    r = redis.Redis(host=os.getenv('REDIS_HOST','redis'), port=int(os.getenv('REDIS_PORT','6379')))
    ns = os.getenv('NS','istio-system')
    policy_name = os.getenv('POLICY_NAME','waf-block-policy')
    
    # 目标:Istio IngressGateway
    target_selector = {"matchLabels": {"istio": "ingressgateway"}}
    
    def sync():
        # 1. 获取有效 IP
        ips = [i.decode() for i in r.smembers('waf:ban:ip')]
        valid_ips = [f"{ip}/32" for ip in ips if r.ttl(f'waf:ban:ttl:{ip}') > 0]
        
        # 2. 获取有效 UA
        uas = [u.decode() for u in r.smembers('waf:ban:ua')]
        valid_uas = [ua for ua in uas if r.ttl(f'waf:ban:ttl:ua:{ua}') > 0]
        
        rules = []
        # 规则 1: 拦截 IP
        if valid_ips:
            rules.append({
                "from": [{"source": {"remoteIpBlocks": valid_ips}}]
            })
            
        # 规则 2: 拦截 User-Agent (精确匹配)
        if valid_uas:
            rules.append({
                "to": [{"operation": {"headers": {"User-Agent": valid_uas}}}]
            })
            
        spec = {
            "selector": target_selector,
            "action": "DENY",
            "rules": rules
        }
        
        body = {
            "apiVersion": "security.istio.io/v1",
            "kind": "AuthorizationPolicy",
            "metadata": {"name": policy_name, "namespace": ns},
            "spec": spec
        }
        
        try:
            api.get_namespaced_custom_object("security.istio.io", "v1", ns, "authorizationpolicies", policy_name)
            print(f"Updating policy: {len(valid_ips)} IPs, {len(valid_uas)} UAs")
            api.patch_namespaced_custom_object("security.istio.io", "v1", ns, "authorizationpolicies", policy_name, body)
        except client.exceptions.ApiException as e:
            if e.status == 404:
                print(f"Creating policy: {len(valid_ips)} IPs, {len(valid_uas)} UAs")
                api.create_namespaced_custom_object("security.istio.io", "v1", ns, "authorizationpolicies", body)
            else:
                print(f"Error: {e}")

    if __name__ == "__main__":
        sync()
---
apiVersion: batch/v1
kind: CronJob
metadata:
  name: waf-controller-istio
  namespace: waf-poc
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: waf-controller-istio
          restartPolicy: Never
          containers:
          - name: controller
            image: python:3.11-slim
            command: ["sh", "-c"]
            args: ["pip install kubernetes redis && python /opt/controller.py"]
            env:
            - name: REDIS_HOST
              value: redis
            volumeMounts:
            - name: script
              mountPath: /opt/controller.py
              subPath: controller.py
          volumes:
          - name: script
            configMap:
              name: waf-controller-istio
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: waf-controller-istio
  namespace: waf-poc
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: waf-controller-istio
  namespace: istio-system
rules:
- apiGroups: ["security.istio.io"]
  resources: ["authorizationpolicies"]
  verbs: ["get","create","patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: waf-controller-istio
  namespace: istio-system
subjects:
- kind: ServiceAccount
  name: waf-controller-istio
  namespace: waf-poc
roleRef:
  kind: Role
  name: waf-controller-istio
  apiGroup: rbac.authorization.k8s.io

WAF 规则管理

ModSecurity 支持通过挂载配置文件来添加自定义规则。

1. 自定义规则挂载

将自定义规则写入 ConfigMap:

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: ConfigMap
metadata:
  name: waf-custom-rules
  namespace: waf-poc
data:
  custom.conf: |
    # 示例:检测 Log4j Payload
    SecRule ARGS "\$\{jndi:" "id:1001,phase:2,deny,status:403,msg:'Log4j Attack'"

修改 Deployment,将此 ConfigMap 挂载到 /etc/nginx/modsec/ 目录,并确保主配置文件包含该文件。

2. 验证与观测

查看日志处理器输出,确认攻击是否被检测并写入 Redis:

1
kubectl logs -f -n waf-poc deployment/waf-detector -c processor

检查 Istio 生成的策略:

1
kubectl get authorizationpolicy -n istio-system waf-block-policy -o yaml

运维要点

  • 策略一致性:Istio 的 AuthorizationPolicy 阻断非常严格,确保 Redis 中的数据准确(Log Processor 可增加白名单过滤)。
  • User-Agent 匹配:AuthorizationPolicy 的 Header 匹配默认是精确匹配,确保 Redis 存入的是完整的 UA 字符串。
  • 性能:大量 IP 列表可能导致配置体积增大,建议设置合理的 TTL(如 10 分钟)让旧名单自动过期。
0%