Ingress 网关
此任务向您展示如何使用授权策略在 Istio ingress 网关上实施基于 IP 的访问控制。
Istio 包括了对 Kubernetes Gateway API 的 Beta 支持, 打算未来使其成为流量管理的默认 API。 以下说明指导您在网格中配置流量管理时如何选择使用 Gateway API 或 Istio 配置 API。 请按照您的首选项遵循 Gateway API
或 Istio classic
页签中的指示说明。
请注意,Kubernetes Gateway API CRD 不会默认安装在大多数 Kubernetes 集群上,因此请确保在使用 Gateway API 之前已安装好这些 CRD:
$ kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \
{ kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v0.6.2" | kubectl apply -f -; }
开始之前
在开始此任务之前,请执行以下操作:
阅读 Istio 授权概念。
使用 Istio 安装指南安装 Istio。
在启用边车注入的命名空间
foo
中部署工作负载httpbin
:$ kubectl create ns foo
$ kubectl label namespace foo istio-injection=enabled
$ kubectl apply -f @samples/httpbin/httpbin.yaml@ -n foo
通过 ingress 网关暴露
httpbin
:
配置网关:
$ kubectl apply -f @samples/httpbin/httpbin-gateway.yaml@ -n foo
在 Envoy 中为 ingress 网关打开 RBAC 调试:
$ 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_HOST
和INGRESS_PORT
环境变量。
创建网关:
$ kubectl apply -f @samples/httpbin/gateway-api/httpbin-gateway.yaml@ -n foo
$ kubectl wait --for=condition=programmed gtw -n foo httpbin-gateway
针对 Ingress 网关在 Envoy 中启用 RBAC 调试:
$ kubectl get pods -n foo -o name -l istio.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_PORT
和 INGRESS_HOST
:
$ export INGRESS_HOST=$(kubectl get gtw httpbin-gateway -n foo -o jsonpath='{.status.addresses[0].value}')
$ export INGRESS_PORT=$(kubectl get gtw httpbin-gateway -n foo -o jsonpath='{.spec.listeners[?(@.name=="http")].port}')
使用以下命令验证
httpbin
工作负载和 Ingress 网关是否正常工作:$ curl "$INGRESS_HOST:$INGRESS_PORT"/headers -s -o /dev/null -w "%{http_code}\n"
200
如果没有看到预期的输出,请在几秒钟后重试。 缓存和传播开销可能会导致延迟。
将流量引入 Kubernetes 和 Istio
所有将流量引入 Kubernetes 的方法都涉及在所有工作节点上打开一个端口, 实现这一点的主要功能是 NodePort
服务和 LoadBalancer
服务,甚至 Kubernetes 的 Ingress
资源也必须由 Ingress 控制器支持,该控制器将创建 NodePort
或 LoadBalancer
服务。
NodePort
只是在每个工作节点上打开一个 30000-32767 范围内的端口, 并使用标签选择器来识别将流量发送到哪些 Pod。 您必须在工作节点前面手动创建某种负载均衡器或使用轮询模式的 DNS。LoadBalancer
就像NodePort
一样, 除了它还创建一个特定于环境的外部负载均衡器来处理将流量分配到工作节点。 例如,在 AWS EKS 中,LoadBalancer
服务将创建一个以您的工作程序节点为目标的经典 ELB。如果您的 Kubernetes 环境没有LoadBalancer
实现,那么它的行为就像NodePort
。Istio ingress 网关创建一个LoadBalancer
服务。
如果处理来自 NodePort
或 LoadBalancer
的流量的 Pod 没有在接收流量的工作节点上运行怎么办?Kubernetes 有自己的内部代理, 称为 kube-proxy,用于接收数据包并将数据包转发到正确的节点。
原始客户端的源 IP 地址
如果数据包通过外部代理负载均衡器和/或 kube-proxy,则客户端的原始源 IP 地址将丢失。 以下小节介绍了为不同类型的负载均衡保留原始客户端 IP 以用于日志记录或安全目的的一些策略:
以下是 Istio 在流行的托管 Kubernetes 环境下使用 LoadBalancer
服务创建的负载均衡器类型,以供参考:
云提供商 | 负载均衡器名称 | 负载均衡器类型 |
---|---|---|
AWS EKS | Classic Elastic Load Balancer | TCP Proxy |
GCP GKE | TCP/UDP Network Load Balancer | Network |
Azure AKS | Azure Load Balancer | Network |
IBM IKS/ROKS | Network Load Balancer | Network |
DO DOKS | Load Balancer | Network |
TCP/UDP 代理负载均衡器
如果您使用的是 TCP/UDP 代理外部负载均衡器 (AWS Classic ELB), 它可以使用代理协议 将原始客户端 IP 地址嵌入到分组数据中。外部负载均衡器和 Istio Ingress 网关都必须支持代理协议才能工作。 在 Istio 中,您可以通过如下所示的 EnvoyFilter
启用:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: proxy-protocol
namespace: istio-system
spec:
configPatches:
- applyTo: LISTENER
patch:
operation: MERGE
value:
listener_filters:
- name: envoy.listener.proxy_protocol
- name: envoy.listener.tls_inspector
workloadSelector:
labels:
istio: ingressgateway
apiVersion: networking.istio.io/v1alp ha3
kind: EnvoyFilter
metadata:
name: proxy-protocol
namespace: foo
spec:
configPatches:
- applyTo: LISTENER
patch:
operation: MERGE
value:
listener_filters:
- name: envoy.listener.proxy_protocol
- name: envoy.listener.tls_inspector
workloadSelector:
labels:
istio.io/gateway-name: httpbin-gateway
以下是一个示例配置,展示了如何使 AWS EKS 上的 ingress 网关支持代理协议:
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
meshConfig:
accessLogEncoding: JSON
accessLogFile: /dev/stdout
components:
ingressGateways:
- enabled: true
name: istio-ingressgateway
k8s:
hpaSpec:
maxReplicas: 10
minReplicas: 5
serviceAnnotations:
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
...
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: httpbin-gateway
annotations:
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
spec:
gatewayClassName: istio
...
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: httpbin-gateway
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: httpbin-gateway-istio
minReplicas: 5
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 网关以设置 exteralTrafficPolicy:Local
以保留 Ingress 网关上的原始客户端源 IP:
$ kubectl patch svc istio-ingressgateway -n istio-system -p '{"spec":{"externalTrafficPolicy":"Local"}}'
$ 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面前使用单个负载均衡的快速示例:
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
meshConfig:
accessLogEncoding: JSON
accessLogFile: /dev/stdout
defaultConfig:
gatewayTopology:
numTrustedProxies: 1
IP-based allow list and deny list
何时使用 ipBlocks
与 emoteIpBlocks
: 如果您使用 X-Forwarded-For HTTP 头部 或代理协议来确定原始客户端 IP 地址,则应在 AuthorizationPolicy
中使用 emoteIpBlocks
。 如果您使用的是 ExtraalTrafficPolicy:Local
,那么您的 AuthorizationPolicy
中应该使用 ipBlocks
。
负载均衡器类型 | 客户端源 IP | ipBlocks 与 remoteIpBlocks |
---|---|---|
TCP Proxy | Proxy Protocol | remoteIpBlocks |
Network | packet source address | ipBlocks |
HTTP/HTTPS | X-Forwarded-For | remoteIpBlocks |
- 以下命令为创建授权策略
Inress-Policy
Istio ingress 网关。以下策略将action
字段设置为ALLOW
以 允许ipBlocks
中指定的 IP 地址访问 ingress 网关。 不在列表中的 IP 地址将被拒绝。ipBlocks
支持单 IP 地址和 CIDR 表示法。
ipBlocks:
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ingress-policy
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: ALLOW
rules:
- from:
- source:
ipBlocks: ["1.2.3.4", "5.6.7.0/24"]
EOF
remoteIpBlocks:
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ingress-policy
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: ALLOW
rules:
- from:
- source:
remoteIpBlocks: ["1.2.3.4", "5.6.7.0/24"]
EOF
ipBlocks:
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ingress-policy
namespace: foo
spec:
selector:
matchLabels:
istio.io/gateway-name: httpbin-gateway
action: ALLOW
rules:
- from:
- source:
ipBlocks: ["1.2.3.4", "5.6.7.0/24"]
EOF
remoteIpBlocks:
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ingress-policy
namespace: foo
spec:
selector:
matchLabels:
istio.io/gateway-name: httpbin-gateway
action: ALLOW
rules:
- from:
- source:
remoteIpBlocks: ["1.2.3.4", "5.6.7.0/24"]
EOF
验证对 ingress 网关的请求是否被拒绝:
$ curl "$INGRESS_HOST:$INGRESS_PORT"/headers -s -o /dev/null -w "%{http_code}\n"
403
将原始客户端 IP 地址分配给环境变量。如果您不知道,您可以使用以下命令在 Envoy 日志中找到它:
ipBlocks:
$ 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"
192.168.10.15
remoteIpBlocks:
$ 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"
192.168.10.15
ipBlocks:
$ CLIENT_IP=$(kubectl get pods -n foo -o name -l istio.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"
192.168.10.15
remoteIpBlocks:
$ CLIENT_IP=$(kubectl get pods -n foo -o name -l istio.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"
192.168.10.15
- 更新
inress-policy
以包含您的客户端IP地址:
ipBlocks:
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ingress-policy
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: ALLOW
rules:
- from:
- source:
ipBlocks: ["1.2.3.4", "5.6.7.0/24", "$CLIENT_IP"]
EOF
remoteIpBlocks:
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ingress-policy
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: ALLOW
rules:
- from:
- source:
remoteIpBlocks: ["1.2.3.4", "5.6.7.0/24", "$CLIENT_IP"]
EOF
ipBlocks:
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ingress-policy
namespace: foo
spec:
selector:
matchLabels:
istio.io/gateway-name: httpbin-gateway
action: ALLOW
rules:
- from:
- source:
ipBlocks: ["1.2.3.4", "5.6.7.0/24", "$CLIENT_IP"]
EOF
remoteIpBlocks:
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ingress-policy
namespace: foo
spec:
selector:
matchLabels:
istio.io/gateway-name: httpbin-gateway
action: ALLOW
rules:
- from:
- source:
remoteIpBlocks: ["1.2.3.4", "5.6.7.0/24", "$CLIENT_IP"]
EOF
验证是否允许对 ingress 网关的请求:
$ curl "$INGRESS_HOST:$INGRESS_PORT"/headers -s -o /dev/null -w "%{http_code}\n"
200
更新
Inress-Policy
授权策略,将action
键设置为DENY
, 禁止ipBlocks
中指定的 IP 地址访问 ingress 网关:
ipBlocks:
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ingress-policy
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: DENY
rules:
- from:
- source:
ipBlocks: ["$CLIENT_IP"]
EOF
remoteIpBlocks:
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ingress-policy
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: DENY
rules:
- from:
- source:
remoteIpBlocks: ["$CLIENT_IP"]
EOF
ipBlocks:
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ingress-policy
namespace: foo
spec:
selector:
matchLabels:
istio.io/gateway-name: httpbin-gateway
action: DENY
rules:
- from:
- source:
ipBlocks: ["$CLIENT_IP"]
EOF
remoteIpBlocks:
$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ingress-policy
namespace: foo
spec:
selector:
matchLabels:
istio.io/gateway-name: httpbin-gateway
action: DENY
rules:
- from:
- source:
remoteIpBlocks: ["$CLIENT_IP"]
EOF
验证对 ingress 网关的请求是否被拒绝:
$ curl "$INGRESS_HOST:$INGRESS_PORT"/headers -s -o /dev/null -w "%{http_code}\n"
403
您可以使用在线代理服务来访问使用不同客户端 IP 的 ingress 网关,以验证请求是否被允许。
如果您没有得到预期的响应,请查看应显示 RBAC 调试信息的 ingress 网关日志:
$ 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
$ kubectl get pods -n foo -o name -l istio.io/gateway-name=httpbin-gateway | sed 's|pod/||' | while read -r pod; do kubectl logs "$pod" -n foo; done
清理
- 删除授权策略:
$ kubectl delete authorizationpolicy ingress-policy -n istio-system
$ kubectl delete authorizationpolicy ingress-policy -n foo
移除命令空间
foo
:$ kubectl delete namespace foo