PaaS 场景中,需要在集群中给客户提供容器部署他们自己开发的代码,如果使用 命名空间 来表示租户,则需要有效隔离租户,让隔壁的租户无法访问本租户的资源。下面的一些策略可以用来实现这种能力。
实验准备
计划部署 2 个租户,tn1 和 tn2,分别部署 httpbin 服务端应用和 wget 测试工具。
以下代码可以直接拷贝食用:
代码语言:txt复制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 访问(不同租户之间不能横向穿透)
代码语言:txt复制apiVersion: extensions/v1beta1
kind: NetworkPolicy
metadata:
name: deny-others
namespace: tn1
spec:
ingress:
- from:
- podSelector: {}
podSelector: {}
policyTypes:
- Ingress
测试结果
代码语言:txt复制# 在 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,配置如下:
代码语言:txt复制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无法被访问:
代码语言:txt复制apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: tn1
spec:
{}
上述配置,在所有的 pod 无法都访问 tn1 里的服务,他限制了所有的流量:
代码语言:txt复制# wget -qSO- httpbin.tn1/get
HTTP/1.1 403 Forbidden
wget: server returned error: HTTP/1.1 403 Forbidden
租户间横向打通
通过 AuthorizationPolicy 可以轻松对命名空间进行配置:
代码语言:txt复制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 访问:
代码语言:txt复制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 可以直接在控制台设置。
代码语言:txt复制outboundTrafficPolicy:
mode: REGISTRY_ONLY
创建 Service Entry,开放 tn1 命名空间对 baidu 的访问权限。
代码如下:
代码语言:txt复制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 也可以访问 )。
代码语言:txt复制# 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 不可以访问
代码语言:txt复制# 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 只可以为 “.” 或 “*”,分别对应着 当前命名空间 和 所有命名空间 有效。