在 istio 中使用 namespace 进行资源/租户隔离

时间:2022-07-23
本文章向大家介绍在 istio 中使用 namespace 进行资源/租户隔离,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

PaaS 场景中,需要在集群中给客户提供容器部署他们自己开发的代码,如果使用 命名空间 来表示租户,则需要有效隔离租户,让隔壁的租户无法访问本租户的资源。下面的一些策略可以用来实现这种能力。

实验准备

计划部署 2 个租户,tn1 和 tn2,分别部署 httpbin 服务端应用和 wget 测试工具。

以下代码可以直接拷贝食用:

apiVersion: v1
kind: Namespace
metadata:
  name: tn1
  labels:
    name: tn1
spec:
  finalizers:
    - kubernetes

---
apiVersion: v1
kind: Namespace
metadata:
  name: tn2
  labels:
    name: tn2
spec:
  finalizers:
    - kubernetes
---
# 在 tn1 中部署 httpbin
apiVersion: v1
kind: Service
metadata:
  name: httpbin
  namespace: tn1
  labels:
    app: httpbin
    tenant: tn1
spec:
  ports:
  - port: 80
    name: http
  selector:
    app: httpbin
    tenant: tn1
---   
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
  namespace: tn1
  labels: 
    app: httpbin
    tenant: tn1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpbin
      tenant: tn1
  template:
    metadata:
      labels:
        app: httpbin
        tenant: tn1
    spec:
      containers:
      - image: kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        ports:
        - containerPort: 80
          protocol: TCP


---
# 在 tn2 中部署 httpbin
apiVersion: v1
kind: Service
metadata:
  name: httpbin
  namespace: tn2
  labels:
    app: httpbin
    tenant: tn2
spec:
  ports:
  - port: 80
    name: http
  selector:
    app: httpbin
    tenant: tn2
---   
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
  namespace: tn2
  labels: 
    app: httpbin
    tenant: tn2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpbin
      tenant: tn2
  template:
    metadata:
      labels:
        app: httpbin
        tenant: tn2
    spec:
      containers:
      - image: kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        ports:
        - containerPort: 80
          protocol: TCP

---
# 分别在两个命名空间中部署 alpine/wget 测试程序
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wget
  namespace: tn1
spec: 
  replicas: 1
  selector:
    matchLabels:
      app: wget
  template:
    metadata:
      labels:
        app: wget
    spec:
      containers:
      - image: alpine
        imagePullPolicy: IfNotPresent
        name: wget
        command: ["tail", "-f", "/dev/null"]


---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wget
  namespace: tn2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wget
  template:
    metadata:
      labels:
        app: wget
    spec:
      containers:
      - image: alpine
        imagePullPolicy: IfNotPresent
        name: wget
        command: ["tail", "-f", "/dev/null"]

使用 NetworkPolicy 隔离命名空间网络

如果要在 k8s 中使用 NetworkPolicy,需要安装相应的网络组件,在腾讯云 TKE 中,建议安装 kube-router ,只启用其中的 firewall 功能,用于实现 NetworkPolicy。kube-router 是腾讯云 TKE 官方建议的网络插件。

在腾讯云 TKE 上安装 kube-router 请参考这篇文章:https://cloud.tencent.com/developer/article/1583707

1、禁止非当前命名空间的 pod 访问(不同租户之间不能横向穿透)

apiVersion: extensions/v1beta1
kind: NetworkPolicy
metadata:
  name: deny-others
  namespace: tn1
spec:
  ingress: 
  - from:
    - podSelector: {} 
  podSelector: {} 
  policyTypes:
  - Ingress

测试结果

# 在 tn1 的 wget 上测试
# wget -qSO- http://httpbin.tn1/headers
  HTTP/1.1 200 OK
  server: envoy...
  
# wget -qSO- http://httpbin.tn2/headers
  HTTP/1.1 200 OK
  server: envoy...
  
# 在 tn2 的 wget 上测试
# wget -qSO- http://httpbin.tn1/headers
wget: cant connect to remote host (172.24.253.145): Connection refused...
  
# wget -qSO- http://httpbin.tn2/headers
  HTTP/1.1 200 OK
  server: envoy...
  

2、设置租户之间的东西向访问白名单

在某些情况下,需要让某些租户之间互访,如:同一个公司申请了多个租户。这个也可以是用 NetworkPolicy 来实现。

下面的例子指定了命名空间 tn1 和 tn2 中的 pod 可以访问 tn1 下的 pod,配置如下:

apiVersion: extensions/v1beta1
kind: NetworkPolicy
metadata:
  name: deny-others
  namespace: tn1
spec:
  ingress: 
  - from:
    - namespaceSelector: 
        matchLabels: 
          name: tn1
    - namespaceSelector: 
        matchLabels: 
          name: tn2
  podSelector: {} 
  policyTypes:
  - Ingress

注意:以上配置在 tke 1.16 和 kube-router 0.2.4,并且没有自动注入 sidecar 的情况下测试通过。

目前 kube-router 测试在 istio 中测试有 bug。

建议在纯的 K8S 环境下使用 NetworkPoclicy ,在 Istio 中使用 AuthorizationPolicy,这将在下一节中探讨。

附:在 TKE 安装最新版的 kube-router

可以基于官网的配置 https://github.com/cloudnativelabs/kube-router/blob/master/daemonset/kube-router-firewall-daemonset.yaml 需要修改配置文件的地址,具体有:

  • 文件名 10-kuberouter.conflist 修改为 10-kuberouter.conf (?)
  • /var/lib/kube-router/kubeconfig 修改为 /root/.kube/config

使用 Istio AuthorizationPolicy 隔离资源

使用 istio 的认证策略,前提是有 sidecar。下面的例子中,需要在 namespace 中增加 label: “istio-injection: enabled ”。

禁止命名空间内的 pod 被访问

下面的配置定义了 tn1 中的pod无法被访问:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-all
  namespace: tn1
spec:
  {}

上述配置,在所有的 pod 无法都访问 tn1 里的服务,他限制了所有的流量:

# wget -qSO- httpbin.tn1/get
  HTTP/1.1 403 Forbidden
wget: server returned error: HTTP/1.1 403 Forbidden

租户间横向打通

通过 AuthorizationPolicy 可以轻松对命名空间进行配置:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-all
  namespace: tn1
spec:
  action: ALLOW
  rules: 
  - from:
    - source:
        namespaces: ['tn1']
  

上面的配置放开了 tn1 命名空间内的 pod 对 本命名空间的访问。如果需要打通更多指定的命名空间,只需要将相应的名字加入 配置中即可。

更精细的控制

使用 AuthorizationPolicy ,可以针对 http 的 url,method 进行更精细的控制,下面的例子中,仅仅租户 tn1 的 /get api 才允许被 tn2 访问:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-all
  namespace: tn1
spec:
  action: ALLOW
  rules: 
  - from:
    - source:
        namespaces: ['tn1']
  - from:
    - source:
        namespaces: ['tn2']
    to:
    - operation:
        methods: ["GET"]
        paths: ["/get"]

更详细的权限控制可以参考官方文档:https://istio.io/latest/docs/reference/config/security/authorization-policy/

在 istio 中开放出口流量白名单

在本小节,我们尝试使用 istio 中的 ServiceEntry 来完成对指定命名空间的的出集群流量控制。

场景:在 istio 集群中,需要使用 namespace 来隔离资源,为特定 namespace 开放集群外访问白名单。

在 istio 的 Service Entry 文档中,我们可以找到相关的功能,关键字是 exportTo,以下是试验过程:

首先将 Istio 集群的外部访问设置为 REGISTRY_ONLY(修改 ConfigMap 的 istio 配置),腾讯云 TCM 可以直接在控制台设置。

outboundTrafficPolicy:
  mode: REGISTRY_ONLY

创建 Service Entry,开放 tn1 命名空间对 baidu 的访问权限。

代码如下:

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: baidu-external
  namespace: tn1
spec:
  hosts:
  - www.baidu.com
  exportTo:
  - "."
  ports:
  - number: 443
    name: https
    protocol: TLS
  resolution: DNS
  location: MESH_EXTERNAL

通过测试:

1 带有 sidecar 的 tn1 命名空间下的 pod 可以访问 (没有 sidecar 的 pod 也可以访问 )。

# wget -qSO- https://www.baidu.com
  HTTP/1.1 200 OK
  Content-Length: 2443
  Content-Type: text/html
  Server: bfe
  Date: Wed, 05 Aug 2020 08:09:24 GMT
  Connection: close
  
<!DOCTYPE html>
...

2 带有 sidecar 的 其他命名空间下的 pod 不可以访问

# wget -qSO- https://www.baidu.com
ssl_client: www.baidu.com: handshake failed: unexpected EOF
wget: error getting response: Connection reset by peer

达到访问外部资源隔离的目的。

上面的配置中,起到关键作用的是 namespace 和 exportTo 两项配置。

按照官方文档:当前 exportTo 只可以为 “.” 或 “*”,分别对应着 当前命名空间 和 所有命名空间 有效。