Ingress 网关

此任务向您展示如何使用授权策略在 Istio Ingress 网关上实施基于 IP 的访问控制。

Istio 支持 Kubernetes Gateway API, 并计划将其作为未来流量管理的默认 API。 以下说明指导您在网格中配置流量管理时如何选择使用 Gateway API 或 Istio 配置 API。 请按照您的首选项遵循 Gateway APIIstio APIs 页签中的指示说明。

请注意,Kubernetes Gateway API CRD 不会默认安装在大多数 Kubernetes 集群上, 因此请确保在使用 Gateway API 之前已安装好这些 CRD:

  1. $ kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \
  2. { kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml; }

开始之前

在开始此任务之前,请执行以下操作:

  • 阅读 Istio 授权概念

  • 使用 Istio 安装指南安装 Istio。

  • 在启用 Sidecar 注入的命名空间 foo 中部署工作负载 httpbin

    Zip

    1. $ kubectl create ns foo
    2. $ kubectl label namespace foo istio-injection=enabled
    3. $ kubectl apply -f @samples/httpbin/httpbin.yaml@ -n foo
  • 通过 Ingress 网关暴露 httpbin

配置网关:

Zip

  1. $ kubectl apply -f @samples/httpbin/httpbin-gateway.yaml@ -n foo

在 Envoy 中为 Ingress 网关打开 RBAC 调试:

  1. $ kubectl get pods -n istio-system -o name -l istio=ingressgateway | sed 's|pod/||' | while read -r pod; do istioctl proxy-config log "$pod" -n istio-system --level rbac:debug; done

遵从确定 Ingress IP 和端口中的指示说明来定义 INGRESS_HOSTINGRESS_PORT 环境变量。

创建网关:

Zip

  1. $ kubectl apply -f @samples/httpbin/gateway-api/httpbin-gateway.yaml@ -n foo

等待网关就绪:

  1. $ kubectl wait --for=condition=programmed gtw -n foo httpbin-gateway

针对 Ingress 网关在 Envoy 中启用 RBAC 调试:

  1. $ kubectl get pods -n foo -o name -l gateway.networking.k8s.io/gateway-name=httpbin-gateway | sed 's|pod/||' | while read -r pod; do istioctl proxy-config log "$pod" -n foo --level rbac:debug; done

设置环境变量 INGRESS_PORTINGRESS_HOST

  1. $ export INGRESS_HOST=$(kubectl get gtw httpbin-gateway -n foo -o jsonpath='{.status.addresses[0].value}')
  2. $ export INGRESS_PORT=$(kubectl get gtw httpbin-gateway -n foo -o jsonpath='{.spec.listeners[?(@.name=="http")].port}')
  • 使用以下命令验证 httpbin 工作负载和 Ingress 网关是否正常工作:

    1. $ curl "$INGRESS_HOST:$INGRESS_PORT"/headers -s -o /dev/null -w "%{http_code}\n"
    2. 200

    如果没有看到预期的输出,请在几秒钟后重试。 缓存和传播开销可能会导致延迟。

将流量引入 Kubernetes 和 Istio

所有将流量引入 Kubernetes 的方法都涉及在所有工作节点上打开一个端口, 实现这一点的主要功能是 NodePort 服务和 LoadBalancer 服务,甚至 Kubernetes 的 Ingress 资源也必须由 Ingress 控制器支持,该控制器将创建 NodePortLoadBalancer 服务。

  • NodePort 只是在每个工作节点上打开一个 30000-32767 范围内的端口, 并使用标签选择器来识别将流量发送到哪些 Pod。 您必须在工作节点前面手动创建某种负载均衡器或使用轮询模式的 DNS。

  • LoadBalancer 就像 NodePort 一样, 除了它还创建一个特定于环境的外部负载均衡器来处理将流量分配到工作节点。 例如,在 AWS EKS 中,LoadBalancer 服务将创建一个以您的工作程序节点为目标的经典 ELB。如果您的 Kubernetes 环境没有 LoadBalancer 实现,那么它的行为就像 NodePort。Istio Ingress 网关创建一个 LoadBalancer服务。

如果处理来自 NodePortLoadBalancer 的流量的 Pod 没有在接收流量的工作节点上运行怎么办?Kubernetes 有自己的内部代理, 称为 kube-proxy,用于接收数据包并将数据包转发到正确的节点。

原始客户端的源 IP 地址

如果数据包通过外部代理负载均衡器和/或 kube-proxy,则客户端的原始源 IP 地址将丢失。 以下小节介绍了为不同类型的负载均衡保留原始客户端 IP 以用于日志记录或安全目的的一些策略:

  1. TCP/UDP Proxy Load Balancer
  2. Network Load Balancer
  3. HTTP/HTTPS Load Balancer

以下是 Istio 在流行的托管 Kubernetes 环境下使用 LoadBalancer 服务创建的负载均衡器类型,以供参考:

云提供商负载均衡器名称负载均衡器类型
AWS EKSClassic Elastic Load BalancerTCP Proxy
GCP GKETCP/UDP Network Load BalancerNetwork
Azure AKSAzure Load BalancerNetwork
IBM IKS/ROKSNetwork Load BalancerNetwork
DO DOKSLoad BalancerNetwork

您可以指示 AWS EKS 在网关服务上创建带有注解的 Network Load Balancer:

  1. apiVersion: install.istio.io/v1alpha1
  2. kind: IstioOperator
  3. spec:
  4. meshConfig:
  5. accessLogEncoding: JSON
  6. accessLogFile: /dev/stdout
  7. components:
  8. ingressGateways:
  9. - enabled: true
  10. k8s:
  11. hpaSpec:
  12. maxReplicas: 10
  13. minReplicas: 5
  14. serviceAnnotations:
  15. service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
  1. apiVersion: gateway.networking.k8s.io/v1
  2. kind: Gateway
  3. metadata:
  4. name: httpbin-gateway
  5. annotations:
  6. service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
  7. spec:
  8. gatewayClassName: istio
  9. ...

TCP/UDP 代理负载均衡器

如果您使用的是 TCP/UDP 代理外部负载均衡器 (AWS Classic ELB), 它可以使用 PROXY 协议 将原始客户端 IP 地址嵌入到分组数据中。外部负载均衡器和 Istio Ingress 网关都必须支持 PROXY 协议才能工作。

以下是一个样例配置,显示了如何在支持 PROXY 协议的 AWS EKS 上部署 Ingress Gateway:

  1. apiVersion: install.istio.io/v1alpha1
  2. kind: IstioOperator
  3. spec:
  4. meshConfig:
  5. accessLogEncoding: JSON
  6. accessLogFile: /dev/stdout
  7. defaultConfig:
  8. gatewayTopology:
  9. proxyProtocol: {}
  10. components:
  11. ingressGateways:
  12. - enabled: true
  13. name: istio-ingressgateway
  14. k8s:
  15. hpaSpec:
  16. maxReplicas: 10
  17. minReplicas: 5
  18. serviceAnnotations:
  19. service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
  20. ...
  1. apiVersion: gateway.networking.k8s.io/v1
  2. kind: Gateway
  3. metadata:
  4. name: httpbin-gateway
  5. annotations:
  6. service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
  7. proxy.istio.io/config: '{"gatewayTopology" : { "proxyProtocol": {} }}'
  8. spec:
  9. gatewayClassName: istio
  10. ...
  11. ---
  12. apiVersion: autoscaling/v2
  13. kind: HorizontalPodAutoscaler
  14. metadata:
  15. name: httpbin-gateway
  16. spec:
  17. scaleTargetRef:
  18. apiVersion: apps/v1
  19. kind: Deployment
  20. name: httpbin-gateway-istio
  21. minReplicas: 5
  22. maxReplicas: 10

网络负载均衡器

如果您正在使用保留客户端 IP 地址的 TCP/UDP 网络负载均衡器 (AWS 网络负载均衡器、 GCP 外部网络负载均衡器、Azure 负载均衡器),或者您正在使用轮询 DNS,则您可以通过绕过 kube-proxy 并阻止其将流量发送到其他节点,使用 externalTrafficPolicy: Local 设置来同时保留 Kubernetes 内部的客户端 IP。

对于生产部署,如果您开启了 externalTrafficPolicy: Local, 强烈建议 将一个 Ingress 网关实例部署到多个节点。 否则,这将导致 只有 具有活动 Ingress 网关实例的节点能够接受并将传入的 NLB 流量分发到集群的其余部分, 从而造成潜在的 Ingress 流量瓶颈和降低的内部负载均衡能力, 甚至在具有 Ingress 网关实例的节点子集关闭时完全丧失到集群的 Ingress 流量。 有关更多信息,请参阅服务源 IP Type=NodePort

使用以下命令更新 Ingress 网关以设置 externalTrafficPolicy: Local 以保留 Ingress 网关上的原始客户端源 IP:

  1. $ kubectl patch svc istio-ingressgateway -n istio-system -p '{"spec":{"externalTrafficPolicy":"Local"}}'
  1. $ kubectl patch svc httpbin-gateway-istio -n foo -p '{"spec":{"externalTrafficPolicy":"Local"}}'

HTTP/HTTPS 负载均衡

如果您使用的是 HTTP/HTTPS 外部负载均衡器 (AWS、ALB、GCP),它可以将原始客户端 IP 地址放在 X-Forwarded-For 报头中。通过一些配置,Istio 可以从该报头中提取客户端 IP 地址。 请参阅配置网关网络拓扑。 在 Kubernetes 面前使用单个负载均衡的快速示例:

  1. apiVersion: install.istio.io/v1alpha1
  2. kind: IstioOperator
  3. spec:
  4. meshConfig:
  5. accessLogEncoding: JSON
  6. accessLogFile: /dev/stdout
  7. defaultConfig:
  8. gatewayTopology:
  9. numTrustedProxies: 1

基于 IP 的允许列表和拒绝列表

何时使用 ipBlocksremoteIpBlocks: 如果您使用 X-Forwarded-For HTTP 头部 或 PROXY 协议来确定原始客户端 IP 地址,则应在 AuthorizationPolicy 中使用 remoteIpBlocks。 如果您使用的是 externalTrafficPolicy: Local,那么您的 AuthorizationPolicy 中应该使用 ipBlocks

负载均衡器类型客户端源 IPipBlocksremoteIpBlocks
TCP ProxyPROXY ProtocolremoteIpBlocks
Networkpacket source addressipBlocks
HTTP/HTTPSX-Forwarded-ForremoteIpBlocks
  • 以下命令为创建授权策略ingress-policy Istio Ingress 网关。 以下策略将 action 字段设置为 ALLOW 以允许 ipBlocks 中指定的 IP 地址访问 Ingress 网关。 不在列表中的 IP 地址将被拒绝。ipBlocks 支持单 IP 地址和 CIDR 表示法。

ipBlocks:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  3. kind: AuthorizationPolicy
  4. metadata:
  5. name: ingress-policy
  6. namespace: istio-system
  7. spec:
  8. selector:
  9. matchLabels:
  10. app: istio-ingressgateway
  11. action: ALLOW
  12. rules:
  13. - from:
  14. - source:
  15. ipBlocks: ["1.2.3.4", "5.6.7.0/24"]
  16. EOF

remoteIpBlocks:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  3. kind: AuthorizationPolicy
  4. metadata:
  5. name: ingress-policy
  6. namespace: istio-system
  7. spec:
  8. selector:
  9. matchLabels:
  10. app: istio-ingressgateway
  11. action: ALLOW
  12. rules:
  13. - from:
  14. - source:
  15. remoteIpBlocks: ["1.2.3.4", "5.6.7.0/24"]
  16. EOF

ipBlocks:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  3. kind: AuthorizationPolicy
  4. metadata:
  5. name: ingress-policy
  6. namespace: foo
  7. spec:
  8. targetRef:
  9. kind: Gateway
  10. group: gateway.networking.k8s.io
  11. name: httpbin-gateway
  12. action: ALLOW
  13. rules:
  14. - from:
  15. - source:
  16. ipBlocks: ["1.2.3.4", "5.6.7.0/24"]
  17. EOF

remoteIpBlocks:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  3. kind: AuthorizationPolicy
  4. metadata:
  5. name: ingress-policy
  6. namespace: foo
  7. spec:
  8. targetRef:
  9. kind: Gateway
  10. group: gateway.networking.k8s.io
  11. name: httpbin-gateway
  12. action: ALLOW
  13. rules:
  14. - from:
  15. - source:
  16. remoteIpBlocks: ["1.2.3.4", "5.6.7.0/24"]
  17. EOF
  • 验证对 Ingress 网关的请求是否被拒绝:

    1. $ curl "$INGRESS_HOST:$INGRESS_PORT"/headers -s -o /dev/null -w "%{http_code}\n"
    2. 403
  • 将原始客户端 IP 地址分配给环境变量。如果您不知道,您可以使用以下命令在 Envoy 日志中找到它:

ipBlocks:

  1. $ CLIENT_IP=$(kubectl get pods -n istio-system -o name -l istio=ingressgateway | sed 's|pod/||' | while read -r pod; do kubectl logs "$pod" -n istio-system | grep remoteIP; done | tail -1 | awk -F, '{print $3}' | awk -F: '{print $2}' | sed 's/ //') && echo "$CLIENT_IP"
  2. 192.168.10.15

remoteIpBlocks:

  1. $ CLIENT_IP=$(kubectl get pods -n istio-system -o name -l istio=ingressgateway | sed 's|pod/||' | while read -r pod; do kubectl logs "$pod" -n istio-system | grep remoteIP; done | tail -1 | awk -F, '{print $4}' | awk -F: '{print $2}' | sed 's/ //') && echo "$CLIENT_IP"
  2. 192.168.10.15

ipBlocks:

  1. $ CLIENT_IP=$(kubectl get pods -n foo -o name -l gateway.networking.k8s.io/gateway-name=httpbin-gateway | sed 's|pod/||' | while read -r pod; do kubectl logs "$pod" -n foo | grep remoteIP; done | tail -1 | awk -F, '{print $3}' | awk -F: '{print $2}' | sed 's/ //') && echo "$CLIENT_IP"
  2. 192.168.10.15

remoteIpBlocks:

  1. $ CLIENT_IP=$(kubectl get pods -n foo -o name -l gateway.networking.k8s.io/gateway-name=httpbin-gateway | sed 's|pod/||' | while read -r pod; do kubectl logs "$pod" -n foo | grep remoteIP; done | tail -1 | awk -F, '{print $4}' | awk -F: '{print $2}' | sed 's/ //') && echo "$CLIENT_IP"
  2. 192.168.10.15
  • 更新 ingress-policy 以包含您的客户端 IP 地址:

ipBlocks:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  3. kind: AuthorizationPolicy
  4. metadata:
  5. name: ingress-policy
  6. namespace: istio-system
  7. spec:
  8. selector:
  9. matchLabels:
  10. app: istio-ingressgateway
  11. action: ALLOW
  12. rules:
  13. - from:
  14. - source:
  15. ipBlocks: ["1.2.3.4", "5.6.7.0/24", "$CLIENT_IP"]
  16. EOF

remoteIpBlocks:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  3. kind: AuthorizationPolicy
  4. metadata:
  5. name: ingress-policy
  6. namespace: istio-system
  7. spec:
  8. selector:
  9. matchLabels:
  10. app: istio-ingressgateway
  11. action: ALLOW
  12. rules:
  13. - from:
  14. - source:
  15. remoteIpBlocks: ["1.2.3.4", "5.6.7.0/24", "$CLIENT_IP"]
  16. EOF

ipBlocks:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  3. kind: AuthorizationPolicy
  4. metadata:
  5. name: ingress-policy
  6. namespace: foo
  7. spec:
  8. targetRef:
  9. kind: Gateway
  10. group: gateway.networking.k8s.io
  11. name: httpbin-gateway
  12. action: ALLOW
  13. rules:
  14. - from:
  15. - source:
  16. ipBlocks: ["1.2.3.4", "5.6.7.0/24", "$CLIENT_IP"]
  17. EOF

remoteIpBlocks:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  3. kind: AuthorizationPolicy
  4. metadata:
  5. name: ingress-policy
  6. namespace: foo
  7. spec:
  8. targetRef:
  9. kind: Gateway
  10. group: gateway.networking.k8s.io
  11. name: httpbin-gateway
  12. action: ALLOW
  13. rules:
  14. - from:
  15. - source:
  16. remoteIpBlocks: ["1.2.3.4", "5.6.7.0/24", "$CLIENT_IP"]
  17. EOF
  • 验证是否允许对 Ingress 网关的请求:

    1. $ curl "$INGRESS_HOST:$INGRESS_PORT"/headers -s -o /dev/null -w "%{http_code}\n"
    2. 200
  • 更新 ingress-policy 授权策略,将 action 键设置为 DENY, 禁止 ipBlocks 中指定的 IP 地址访问 Ingress 网关:

ipBlocks:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  3. kind: AuthorizationPolicy
  4. metadata:
  5. name: ingress-policy
  6. namespace: istio-system
  7. spec:
  8. selector:
  9. matchLabels:
  10. app: istio-ingressgateway
  11. action: DENY
  12. rules:
  13. - from:
  14. - source:
  15. ipBlocks: ["$CLIENT_IP"]
  16. EOF

remoteIpBlocks:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  3. kind: AuthorizationPolicy
  4. metadata:
  5. name: ingress-policy
  6. namespace: istio-system
  7. spec:
  8. selector:
  9. matchLabels:
  10. app: istio-ingressgateway
  11. action: DENY
  12. rules:
  13. - from:
  14. - source:
  15. remoteIpBlocks: ["$CLIENT_IP"]
  16. EOF

ipBlocks:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  3. kind: AuthorizationPolicy
  4. metadata:
  5. name: ingress-policy
  6. namespace: foo
  7. spec:
  8. targetRef:
  9. kind: Gateway
  10. group: gateway.networking.k8s.io
  11. name: httpbin-gateway
  12. action: DENY
  13. rules:
  14. - from:
  15. - source:
  16. ipBlocks: ["$CLIENT_IP"]
  17. EOF

remoteIpBlocks:

  1. $ kubectl apply -f - <<EOF
  2. apiVersion: security.istio.io/v1
  3. kind: AuthorizationPolicy
  4. metadata:
  5. name: ingress-policy
  6. namespace: foo
  7. spec:
  8. targetRef:
  9. kind: Gateway
  10. group: gateway.networking.k8s.io
  11. name: httpbin-gateway
  12. action: DENY
  13. rules:
  14. - from:
  15. - source:
  16. remoteIpBlocks: ["$CLIENT_IP"]
  17. EOF
  • 验证对 Ingress 网关的请求是否被拒绝:

    1. $ curl "$INGRESS_HOST:$INGRESS_PORT"/headers -s -o /dev/null -w "%{http_code}\n"
    2. 403
  • 您可以使用在线代理服务来访问使用不同客户端 IP 的 Ingress 网关,以验证请求是否被允许。

  • 如果您没有得到预期的响应,请查看应显示 RBAC 调试信息的 Ingress 网关日志:

  1. $ kubectl get pods -n istio-system -o name -l istio=ingressgateway | sed 's|pod/||' | while read -r pod; do kubectl logs "$pod" -n istio-system; done
  1. $ kubectl get pods -n foo -o name -l gateway.networking.k8s.io/gateway-name=httpbin-gateway | sed 's|pod/||' | while read -r pod; do kubectl logs "$pod" -n foo; done

清理

  • 删除授权策略:
  1. $ kubectl delete authorizationpolicy ingress-policy -n istio-system
  1. $ kubectl delete authorizationpolicy ingress-policy -n foo
  • 移除命令空间 foo

    1. $ kubectl delete namespace foo