Kubernetes安全——RBAC&NetworkPolicy

发布时间 2023-06-12 17:37:43作者: jiayou111

权限管理RBAC

参考链接:https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/rbac/

1.Role和ClusterRole

Role 作用于namespace内的角色
ClusterRole 作用于整个集群的集群角色

# role default ns的pod读权限
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""] 
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

# clusterrole 所有ns中secret的读权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  # "namespace" 被忽略,因为 ClusterRoles 不受名字空间限制
  name: secret-reader
rules:
- apiGroups: [""]
  # 在 HTTP 层面,用来访问 Secret 资源的名称为 "secrets"
  resources: ["secrets"]
  verbs: ["get", "watch", "list"]

2.RoleBinding和ClusterRoleBinding

RoleBinding 作用于命名空间,可以绑定role或clusterrole,对象可以是user或group或serviceaccount。
ClusterRoleBinding 作用于集群,可以为整个集群定义一组公用的ClusterRole,然后在多个命名空间中重复使用。

# rolebinding 将pod-reader的role授予default ns下的user jane,即允许jane读取default ns下的pod
apiVersion: rbac.authorization.k8s.io/v1
# 此角色绑定允许 "jane" 读取 "default" 名字空间中的 Pod
# 你需要在该命名空间中有一个名为 “pod-reader” 的 Role
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
subjects:
# 你可以指定不止一个“subject(主体)”
- kind: User
  name: jane # "name" 是区分大小写的
  apiGroup: rbac.authorization.k8s.io
roleRef:
  # "roleRef" 指定与某 Role 或 ClusterRole 的绑定关系
  kind: Role        # 此字段必须是 Role 或 ClusterRole
  name: pod-reader  # 此字段必须与你要绑定的 Role 或 ClusterRole 的名称匹配
  apiGroup: rbac.authorization.k8s.io

# rolebinding 将secret-reader的clusterrole授予deployment ns 下的user dave,即允许dave读取 deployment ns下的secret
apiVersion: rbac.authorization.k8s.io/v1
# 此角色绑定使得用户 "dave" 能够读取 "development" 名字空间中的 Secrets
# 你需要一个名为 "secret-reader" 的 ClusterRole
kind: RoleBinding
metadata:
  name: read-secrets
  # RoleBinding 的名字空间决定了访问权限的授予范围。
  # 这里隐含授权仅在 "development" 名字空间内的访问权限。
  namespace: development
subjects:
- kind: User
  name: dave # 'name' 是区分大小写的
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

# ClusterRoleBinding可用于集群级别和所有命名空间授予权限,允许组manager中所有用户读取任何命名空间的secret
apiVersion: rbac.authorization.k8s.io/v1
# 此集群角色绑定允许 “manager” 组中的任何人访问任何名字空间中的 Secret 资源
kind: ClusterRoleBinding
metadata:
  name: read-secrets-global
subjects:
- kind: Group
  name: manager      # 'name' 是区分大小写的
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

K8S多租户权限管理实战

一般这样用:创建ns,创建ns下的sa,创建clusterrole,创建rolebinding将sa和clusterrole绑定

# ns
k create ns kube-users

# sa
k create sa kube-system -n kube-users

# clusterrole 配置pod日志查询和执行命令的权限
cat pod-exec-cr.yaml 
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: pod-exec 
rules:
- apiGroups:
  - "" 
  resources:
  - pod
  - pods/log
  verbs:
  - get
  - list
- apiGroups:
  - "" 
  resources:
  - pods/exec 
  verbs:
  - create 

# clusterrole 全局ns只读权限
cat ns-readonly.yaml 
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: namespace-readonly 
rules:
- apiGroups: 
  - ""
  resources: 
  - namespaces
  verbs: 
  - get
  - list
  - watch
- apiGroups: 
  - metrics.k8s.io
  resources:
  - pods 
  verbs:
  - get
  - list
  - watch 

# 授权 clusterrolebinding
k create clusterrolebinding namespace-readonly --clusterrole=namespace-readonly --serviceaccount=kube-users:kube-system

# 授权 rolebinding
k create rolebinding sa-kube-system-pod-exec --clusterrole=pod-exec --serviceaccount=kube-users:kube-system --namespace=kube-system

# sa对应的secret
k get sa kube-system -n kube-users -o yaml

# 查看secret对于的token
k describe secret kubernetes-dashboard-admin-sa-token -n kubernetes-dashboard
k get secret -n kube-users $(k get secret -n kube-users|grep kube-system-token-name | awk '{print $1}') -o go-template='{{.data.token}}' | base64 -d

Network Policy

在集群中安装支持网络策略的插件,比如Calico、Weave。注意Flannel不支持。
参考链接:https://kubernetes.io/zh-cn/docs/concepts/services-networking/network-policies/

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - ipBlock:
            cidr: 172.17.0.0/16
            except:
              - 172.17.1.0/24
        - namespaceSelector:
            matchLabels:
              project: myproject
        - podSelector:
            matchLabels:
              role: frontend
      ports:
        - protocol: TCP
          port: 6379
  egress:
    - to:
        - ipBlock:
            cidr: 10.0.0.0/24
      ports:
        - protocol: TCP
          port: 5978

示例1:隔离中间件服务

有一个项目有自己的Mysql和Redis,只希望这个项目的应用能访问该中间件,即中间件只对项目开放,这样可以起到安全的效果。

k create ns nw-demo

# mysql
k create deploy mysql --image=registry.cn-beijing.aliyuncs.com/dotbalo/mysql:5.7.23 -n nw-demo
k set env deploy/mysql MYSQL_ROOT_PASSWORD=mysql -n nw-demo

# redis
k create deploy redis --image=registry.cn-beijing.aliyuncs.com/dotbalo/redis:5.0.9-alpine3.11 -n nw-demo

k get pods -n nw-demo -o wide                   
NAME                     READY   STATUS    RESTARTS   AGE     IP            NODE               NOMINATED NODE   READINESS GATES
mysql-857b9b4c94-jwdsh   1/1     Running   0          3m17s   10.244.1.59   k8s-worker-node1   <none>           <none>
redis-57bf8685c8-fssxw   1/1     Running   0          74s     10.244.1.60   k8s-worker-node1   <none>           <none>

# 测试网络连通性
telnet 10.244.1.59 3306
telnet 10.244.1.60 6379

# 根据pod标签进行隔离
k get pods -n nw-demo --show-labels
NAME                     READY   STATUS    RESTARTS   AGE     LABELS
mysql-857b9b4c94-jwdsh   1/1     Running   0          6m48s   app=mysql,pod-template-hash=857b9b4c94
redis-57bf8685c8-fssxw   1/1     Running   0          4m45s   app=redis,pod-template-hash=57bf8685c8

# 创建网络策略
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mysql-np 
  namespace: nw-demo
spec:
  podSelector:
    matchLabels:
      app: mysql
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              access-nw-mysql-redis: "true"
      ports:
        - protocol: TCP
          port: 3306 

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: redis-np 
  namespace: nw-demo
spec:
  podSelector:
    matchLabels:
      app: redis
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              access-nw-mysql-redis: "true"
      ports:
        - protocol: TCP
          port: 6379

# 创建networkpolicy
k get networkpolicy -n nw-demo
NAME       POD-SELECTOR   AGE
mysql-np   app=mysql      6m46s
redis-np   app=redis      6m46s

k get pods -n nw-demo -o wide        
NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE               NOMINATED NODE   READINESS GATES
mysql-857b9b4c94-jwdsh   1/1     Running   0          20m   10.244.1.59   k8s-worker-node1   <none>           <none>
redis-57bf8685c8-fssxw   1/1     Running   0          18m   10.244.1.60   k8s-worker-node1   <none>           <none>


# 测试
telnet 10.244.1.59 3306
telnet 10.244.1.60 6379
不通

k run -ti debug-tools --image=registry.cn-beijing.aliyuncs.com/dotbalo/debug-tools:latest -n nw-demo

# 添加label
k label namespace nw-demo access-nw-mysql-redis="true"
namespace/nw-demo labeled

k exec -it debug-tools -n nw-demo -- curl 10.244.1.59:3306

k run -ti debug-tools --image=registry.cn-beijing.aliyuncs.com/dotbalo/debug-tools:latest -n default
If you don't see a command prompt, try pressing enter.
(07:32 debug-tools:/) 
(07:32 debug-tools:/) curl 10.244.1.60:6379

# 同理如果需要default可以正常访问,即给default namespace添加label就可以。
k get ns nw-demo --show-labels
NAME      STATUS   AGE   LABELS
nw-demo   Active   48m   access-nw-mysql-redis=true,kubernetes.io/metadata.name=nw-demo

示例2:服务发布限制于Ingress

访问项目是通过域名方式,而非IP+端口,使用Ingress配置域名,然后代理该项目。可以利用网络策略隔离该项目的前端服务,只对Ingress Controller开放。
假设现在创建一个Nginx服务充当前端页面,配置网络策略只让Ingress Controller访问该应用:

# 创建应用
k create deploy nginx --image=registry.cn-beijing.aliyuncs.com/dotbalo/nginx:latest -n nw-demo
# 暴露服务
k expose deploy nginx -n nw-demo --port=80
# 查看
k get svc,pod -n nw-demo -l app=nginx
NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/nginx   ClusterIP   10.96.36.183   <none>        80/TCP    35s

NAME                         READY   STATUS    RESTARTS   AGE
pod/nginx-5c9c878b87-d8nqp   1/1     Running   0          92s

# 测试访问
k exec debug-tools -- curl -Is nginx.nw-demo
HTTP/1.1 200 OK
Server: nginx/1.23.3
Date: Mon, 12 Jun 2023 07:45:20 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 13 Dec 2022 15:53:53 GMT
Connection: keep-alive
ETag: "6398a011-267"
Accept-Ranges: bytes

k -n nw-demo exec debug-tools -- curl -Is nginx.nw-demo
HTTP/1.1 200 OK
Server: nginx/1.23.3
Date: Mon, 12 Jun 2023 07:45:36 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 13 Dec 2022 15:53:53 GMT
Connection: keep-alive
ETag: "6398a011-267"
Accept-Ranges: bytes

# network policy只让Ingress Controller访问服务
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: nginx-np 
  namespace: nw-demo 
spec:
  podSelector:
    matchLabels:
      app: nginx
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              app.kubernetes.io/name: ingress-nginx 
          podSelector:
            matchLabels:
              app.kubernetes.io/name: ingress-nginx 
        - podSelector: {}
      ports:
        - protocol: TCP
          port: 80 
对具有app=nginx的pod生效,只让具有app.kubernetes.io/name=ingress-nginx标签的ns下具有app.kubernetes.io/name: ingress-nginx标签的Pod访问(podSelector前面没有-即为且的关系)。同时还有一个允许当前ns下pod访问的策略-podSelector: {}

# 测试连通性
k exec debug-tools -- curl -Is nginx.nw-demo --connect-timeout 2 
HTTP/1.1 200 OK
Server: nginx/1.23.3
Date: Mon, 12 Jun 2023 08:17:11 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 13 Dec 2022 15:53:53 GMT
Connection: keep-alive
ETag: "6398a011-267"
Accept-Ranges: bytes

k exec debug-tools -- curl -Is nginx.nw-demo --connect-timeout 2 -n nw-demo
HTTP/1.1 200 OK
Server: nginx/1.23.3
Date: Mon, 12 Jun 2023 08:17:28 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 13 Dec 2022 15:53:53 GMT
Connection: keep-alive
ETag: "6398a011-267"
Accept-Ranges: bytes
command terminated with exit code 6

k get pods -n ingress-nginx -o wide                                                        
NAME                                        READY   STATUS      RESTARTS   AGE   IP               NODE               NOMINATED NODE   READINESS GATES
ingress-nginx-admission-create-h4tk6        0/1     Completed   0          52d   10.244.1.5       k8s-worker-node1   <none>           <none>
ingress-nginx-admission-patch-gl2rc         0/1     Completed   0          52d   10.244.1.6       k8s-worker-node1   <none>           <none>
ingress-nginx-controller-797448cb7f-h282r   1/1     Running     0          49d   192.168.73.102   k8s-worker-node1   <none>           <none>

k exec ingress-nginx-controller-797448cb7f-h282r -n ingress-nginx -- curl -Is nginx.nw-demo
k create ingress nginx --rule="testnp.com/*=nginx:80" -n nw-demo
curl -H "Host:testnp.com" 192.168.73.102