一、为什么权限管控是Kubeflow Notebook Server绕不开的一道坎

Kubeflow Notebook Server这个东西,说白了就是让数据科学家在Kubernetes集群里像用本地Jupyter一样写代码、跑实验。它很方便,但方便归方便,权限管控这事儿如果没做扎实,分分钟能让你的集群变成"人人皆可访问"的透明仓库。

想象一下这个场景:你们团队有十个开发者,每人都有一个Notebook。结果某天有人发现,自己的Notebook里偷偷多了一个不认识的文件夹,里面的模型参数文件被删了。或者更糟的,有人通过Notebook的终端,直接kubectl到了集群的核心Namespace,把生产环境的配置给改了。

这些问题归根结底都是两件事没做好:环境隔离没到位,资源边界没管住。环境隔离是指每个人的Notebook应该跑在独立的容器里,彼此看不见、摸不着。资源越界访问是指Notebook里的进程不应该有能力去访问它不该碰的Kubernetes资源。

这篇文章就来聊聊怎么排查这两个问题,以及怎么加固。我会用实际案例和可运行的命令带你走一遍,不是那种"理论上应该这样"的空谈。

二、环境隔离机制的工作原理

2.1 隔离是层层递进的

Kubeflow Notebook Server的环境隔离不是一步到位的,而是分了好几层。理解这层关系,你排查问题时才能知道该从哪一层入手。

第一层是容器级隔离。每个Notebook运行在一个独立的Pod里,Pod里的容器由Docker或containerd管理。这是最基础的隔离,操作系统内核通过cgroups和namespace提供隔离能力。

第二层是Namespace级隔离。Kubeflow会给每个用户或者每个Notebook分配独立的Kubernetes Namespace。Namespace是Kubernetes里最核心的逻辑隔离单位,不同Namespace之间的资源默认是不互通的。

第三层是RBAC级隔离。RoleBased Access Control(RBAC)控制着谁能对Namespace里的资源做什么操作。Notebook Server会给每个用户的ServiceAccount绑定特定的Role或ClusterRole。

第四层是NetworkPolicy级隔离。NetworkPolicy控制Pod之间的网络访问。没有配置NetworkPolicy的集群,默认所有Pod都可以互相访问。

这四层里面,任何一层出了问题,都会导致隔离失败。排查的时候,你需要逐层检查。

2.2 一个典型的Notebook Pod结构长什么样

# 技术栈:Kubernetes + Kubeflow
# 以下是一个Kubeflow Notebook Server生成的Pod配置示例
# 注意:这是简化后的结构,实际生成的YAML会更复杂

apiVersion: v1
kind: Pod
metadata:
  name: my-notebook-7b8f9c
  namespace: user-alice-workspace   # 每个用户有自己的Namespace
  labels:
    app.kubernetes.io/component: notebook
    user: alice
  annotations:
    kubeflow.org/notebook-uri: "http://user-alice-workspace.svc.cluster.local"
spec:
  # ServiceAccount决定了Notebook进程能访问哪些Kubernetes API资源
  serviceAccountName: notebook-sa-alice
  containers:
  - name: notebook
    image: gcr.io/kubeflow-images-public/notebook-servers/jupyter-pytorch:latest
    ports:
    - containerPort: 8888
      name: http
    resources:
      requests:
        cpu: "500m"
        memory: "1Gi"
      limits:
        cpu: "2"
        memory: "4Gi"
    volumeMounts:
    - name: jupyter-home
      mountPath: /home/jovyan
    - name: persistent-volume
      mountPath: /mnt/data
      readOnly: false    # 读写挂载,注意这里不是只读
  volumes:
  - name: jupyter-home
    emptyDir: {}
  - name: persistent-volume
    persistentVolumeClaim:
      claimName: alice-data-pvc
  # 安全上下文:限制容器内进程的特权
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 3000
    allowPrivilegeEscalation: false   # 禁止提权

上面这个配置里,有几个关键点值得注意:

  • serviceAccountName: notebook-sa-alice 决定了Notebook里的进程能以什么身份调用Kubernetes API。如果这个ServiceAccount绑定了过大的权限,Notebook就能干出很多不该干的事。
  • allowPrivilegeEscalation: false 是一个安全加固点。如果没设置,容器里的进程可能会通过提权方式获取更高权限。
  • readOnly: false 表示挂载的数据卷是可读写的。如果你的场景不需要写入,改成true能减少风险面。

三、资源越界访问的常见场景与排查方法

3.1 场景一:Notebook能访问其他用户的Pod

这是最常见的问题。两个用户各自有Namespace,但其中一个Notebook的进程能通过Kubernetes API List或者Get另一个Namespace里的资源。

排查方法如下:

# 技术栈:Kubernetes + Kubeflow
# 第一步:确认当前用户的ServiceAccount及其绑定的权限

# 查看alice的Notebook使用的ServiceAccount
kubectl get pod -n user-alice-workspace my-notebook -o jsonpath='{.spec.serviceAccountName}'

# 查看这个ServiceAccount绑定了哪些Role/ClusterRole
kubectl get rolebinding -n user-alice-workspace -o wide | grep notebook-sa-alice
kubectl get clusterrolebinding -o wide | grep notebook-sa-alice

如果输出里看到了cluster-admin或者类似的过大权限,问题基本就找到了。

进一步验证,直接到Notebook里执行命令看能访问到什么:

# 技术栈:Kubernetes + Kubeflow
# 进入Notebook的容器终端,执行以下命令测试权限边界

# 列出当前能访问的Namespace
kubectl get namespaces

# 尝试访问另一个用户的Namespace
kubectl get pods -n user-bob-workspace

# 尝试创建资源(如果权限过大,这一步就能成功,那就危险了)
kubectl create deployment test-pod -n user-bob-workspace --image=nginx --dry-run=server

如果Notebook能List或者Get其他Namespace的资源,说明RBAC配置出了问题。正常情况下,每个Notebook的ServiceAccount只应该能访问自己Namespace内的资源。

3.2 场景二:Notebook能读写其他用户的持久化存储

PVC(PersistentVolumeClaim)的权限管控如果没做好,用户A的Notebook可能挂载到用户B的数据卷上。这比访问Pod更严重,因为数据是持久化的,泄露出去可能回不来了。

排查方法:

# 技术栈:Kubernetes + Kubeflow
# 第一步:查看所有PVC的绑定关系和访问模式

# 列出所有PVC
kubectl get pvc -A -o wide

# 查看PVC的访问模式,ReadWriteMany(RWX)意味着多个Pod可以同时读写
kubectl get pvc -n user-alice-workspace -o jsonpath='{.items[*].spec.accessModes}'
kubectl get pvc -n user-bob-workspace -o jsonpath='{.items[*].spec.accessModes}'

# 检查是否有多人共享的PVC被挂载到了不同Namespace的Pod里
kubectl get pods -A -o json | python3 -c "
import json, sys
data = json.load(sys.stdin)
for pod in data['items']:
    ns = pod['metadata']['namespace']
    name = pod['metadata']['name']
    if 'notebook' not in name:
        continue
    # 收集该Pod挂载的所有PVC名称
    volumes = pod['spec'].get('volumes', [])
    for v in volumes:
        if 'persistentVolumeClaim' in v:
            pvc = v['persistentVolumeClaim']['claimName']
            print(f'Namespace: {ns:30s} | Pod: {name:30s} | PVC: {pvc}')
"

如果输出里出现了同一个PVC被不同Namespace的Pod挂载,而且访问模式是ReadWriteMany,那就说明存储隔离有问题。理想情况下,每个用户应该有自己的PVC,或者至少PVC的挂载应该被RBAC限制在自己Namespace内。

3.3 场景三:通过Notebook的终端命令直接操作Kubernetes集群

这个场景比较隐蔽。用户在Notebook的Terminal里直接运行kubectl命令,如果能执行成功,说明Notebook容器内的kubectl配置和ServiceAccount权限过大。

# 技术栈:Kubernetes + Kubeflow
# 检查Notebook容器内是否预置了kubeconfig文件

# 进入Notebook容器
kubectl exec -it -n user-alice-workspace my-notebook -- sh

# 在容器内执行:
# 检查HOME目录下的kubeconfig
ls -la ~/.kube/

# 检查环境变量中是否有kubeconfig相关变量
env | grep -i kube

# 检查默认上下文
kubectl config current-context
kubectl config view

如果Notebook里预置了~/.kube/config文件,或者环境变量里设置了KUBECONFIG,那么用户在Notebook终端里直接运行kubectl就会生效。更危险的是,如果这个kubeconfig对应的用户权限是cluster-admin,用户就能操作整个集群。

四、实操排查:一步步定位权限漏洞

4.1 第一步:审计现有Notebook的ServiceAccount权限

在动手加固之前,先搞清楚现在到底有多少Notebook,每个Notebook绑定了什么权限。

# 技术栈:Kubernetes + Kubeflow
# 第一步:列出所有Kubeflow Notebook的Pod

# 查找所有带有kubeflow标签的Pod
kubectl get pods -A -l app.kubernetes.io/component=notebook -o wide

# 输出示例:
# NAMESPACE              NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE         READY
# user-alice-workspace   my-notebook-7b8f9c      1/1     Running   0          3h    10.244.1.15   node-1       1/1
# user-bob-workspace     pytorch-notebook-5f4e3  1/1     Running   0          1d    10.244.2.32   node-2       1/1
# user-charlie-workspace tf-notebook-8c7d6b      1/1     Running   0          2d    10.244.3.45   node-3       1/1

拿到Pod列表后,检查每个Pod的ServiceAccount权限:

# 技术栈:Kubernetes + Kubeflow
# 对每个Notebook Pod,检查其ServiceAccount的权限

for pod in $(kubectl get pods -A -l app.kubernetes.io/component=notebook -o jsonpath='{.items[*].metadata.namespace}/{.items[*].metadata.name}'); do
    ns=${pod%%/*}
    name=${pod#*/}
    sa=$(kubectl get pod -n "$ns" "$name" -o jsonpath='{.spec.serviceAccountName}')
    
    echo "=== Namespace: $ns | Pod: $name | ServiceAccount: $sa ==="
    
    # 查看该SA在Namespace内的RoleBinding
    kubectl get rolebinding -n "$ns" -o jsonpath='{.items[*].metadata.name}' 2>/dev/null | \
        tr ' ' '\n' | while read rb; do
            if kubectl get rolebinding "$rb" -n "$ns" -o jsonpath='{.subjects[*].name}' | grep -q "$sa"; then
                role=$(kubectl get rolebinding "$rb" -n "$ns" -o jsonpath='{.roleRef.name}')
                echo "  RoleBinding: $rb -> Role: $role"
                # 打印Role的权限详情
                kubectl get role "$role" -n "$ns" -o jsonpath='{range .rules[*]}{.verbs[*]} {""} {.resources[*]}{""} {""} {.apiGroups[*]}\n{end}'
            fi
        done
    
    # 查看该SA的ClusterRoleBinding(如果有)
    kubectl get clusterrolebinding -o jsonpath='{.items[*].metadata.name}' 2>/dev/null | \
        tr ' ' '\n' | while read crb; do
            if kubectl get clusterrolebinding "$crb" -o jsonpath='{.subjects[*].name}' | grep -q "$sa"; then
                crole=$(kubectl get clusterrolebinding "$crb" -o jsonpath='{.roleRef.name}')
                echo "  ClusterRoleBinding: $crb -> ClusterRole: $crole"
            fi
        done
    
    echo ""
done

这个脚本会输出每个Notebook Pod的ServiceAccount、绑定的Role/ClusterRole以及具体权限规则。如果看到某个Notebook绑定了cluster-admin,那就立刻去处理。

4.2 第二步:检测NetworkPolicy配置

没有NetworkPolicy的集群,所有Pod之间默认是互通的。也就是说,alice的Notebook可以直接访问bob的Notebook的HTTP端口,拿到bob的Notebook页面内容。

# 技术栈:Kubernetes + Kubeflow
# 检查每个用户的Namespace下是否有NetworkPolicy

# 列出所有Namespace的NetworkPolicy
kubectl get networkpolicy -A

# 如果输出为空,说明整个集群没有NetworkPolicy,所有Pod互通
# 如果只有部分Namespace有,其他Namespace仍然处于"默认可达"状态

排查某个Namespace是否需要NetworkPolicy:

# 技术栈:Kubernetes + Kubeflow
# 查看特定Namespace的Pod网络连通性

# 列出user-alice-workspace下的所有Pod IP
alice_pods=$(kubectl get pods -n user-alice-workspace -o jsonpath='{.items[*].status.podIP}')

# 列出user-bob-workspace下的所有Pod IP
bob_pods=$(kubectl get pods -n user-bob-workspace -o jsonpath='{.items[*].status.podIP}')

echo "Alice的Pod IP: $alice_pods"
echo "Bob的Pod IP: $bob_pods"

# 如果两个Namespace之间没有NetworkPolicy限制
# 从alice的Pod可以直接ping或者curl bob的Pod
# 在集群内部执行(需要有调试Pod):
# kubectl exec -it -n user-alice-workspace my-notebook -- curl http://bob-notebook-ip:8888/
# 如果能返回Notebook页面,说明网络隔离没做好

4.3 第三步:检查安全上下文配置

容器级的安全配置如果没设对,用户在容器内可以执行提权操作,获取超出预期的能力。

# 技术栈:Kubernetes + Kubeflow
# 检查所有Notebook Pod的安全上下文配置

kubectl get pods -A -l app.kubernetes.io/component=notebook -o json | python3 -c "
import json, sys

data = json.load(sys.stdin)
print('========== 安全上下文检查报告 ==========')
print()

for pod in data['items']:
    ns = pod['metadata']['namespace']
    name = pod['metadata']['name']
    spec = pod['spec']
    
    print(f'Pod: {ns}/{name}')
    
    # 检查Pod级别的securityContext
    pod_sc = spec.get('securityContext', {})
    if not pod_sc:
        print('  [警告] 未配置Pod级别securityContext')
    
    for container in spec.get('containers', []):
        cname = container['name']
        c_sc = container.get('securityContext', {})
        
        # 检查是否允许提权
        if not c_sc.get('allowPrivilegeEscalation', True):
            print(f'  [安全] 容器{cname}: 禁止提权')
        else:
            print(f'  [警告] 容器{cname}: 允许提权(allowPrivilegeEscalation未设为false)')
        
        # 检查是否以root运行
        run_user = c_sc.get('runAsUser', 'default')
        if run_user == 0 or run_user == '0':
            print(f'  [警告] 容器{cname}: 以root用户运行(runAsUser=0)')
        else:
            print(f'  [安全] 容器{cname}: 以非root用户运行(runAsUser={run_user})')
        
        # 检查capabilities
        caps = c_sc.get('capabilities', {})
        if 'drop' in caps:
            print(f'  [安全] 容器{cname}: 已丢弃capabilities: {caps[\"drop\"]}')
        else:
            print(f'  [警告] 容器{cname}: 未丢弃任何capabilities')
    
    print()
print('========================================')
"

这段脚本会输出一份安全检查报告,告诉你哪些Pod存在安全风险。重点关注那些"以root用户运行"和"允许提权"的容器。

五、权限管控最佳实践与加固方案

5.1 RBAC最小权限原则配置

RBAC加固的核心思路是:每个Notebook的ServiceAccount只拥有完成其工作所需的最小权限集合。

# 技术栈:Kubernetes + Kubeflow
# RoleDefinition:定义Notebook容器需要的最小权限
# 只允许读取和写入自己Namespace内的资源
# 不允许任何集群级别的资源操作

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: notebook-minimal-role
  namespace: user-alice-workspace
rules:
  # 允许List和Get自己Namespace内的所有资源
  # 这是Notebook正常运行所需的基础权限
  - apiGroups: [""]
    resources: ["pods", "services", "configmaps", "secrets", "persistentvolumeclaims"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["pods/log"]
    verbs: ["get"]
  - apiGroups: ["apps"]
    resources: ["deployments", "statefulsets"]
    verbs: ["get", "list"]
  # 允许创建和更新Job(用于数据任务提交)
  - apiGroups: ["batch"]
    resources: ["jobs", "cronjobs"]
    verbs: ["get", "list", "create", "update", "delete"]
  # 允许更新自己Namespace内的Service(用于暴露端口)
  - apiGroups: [""]
    resources: ["services"]
    verbs: ["create", "patch"]
  # 不允许ClusterRoleBinding级别的任何权限
  # 不允许读取其他Namespace的资源
---
# RoleBinding:将Role绑定到用户的ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: notebook-minimal-role-binding
  namespace: user-alice-workspace
subjects:
  - kind: ServiceAccount
    name: notebook-sa-alice
    namespace: user-alice-workspace
roleRef:
  kind: Role
  name: notebook-minimal-role
  apiGroup: rbac.authorization.k8s.io

注意上面的Role规则里,没有包含cluster-admin相关的规则,也没有允许跨Namespace的操作。所有权限都被限制在user-alice-workspace这个Namespace内部。

5.2 NetworkPolicy加固配置

# 技术栈:Kubernetes + Kubeflow
# 默认拒绝所有入站和出站流量
# 只开放Notebook需要的端口

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: notebook-default-deny
  namespace: user-alice-workspace
spec:
  # 选择所有Notebook Pod
  podSelector:
    matchLabels:
      app.kubernetes.io/component: notebook
  # 默认拒绝所有入站和出站流量
  policyTypes:
  - Ingress
  - Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: notebook-allow-traffic
  namespace: user-alice-workspace
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/component: notebook
  policyTypes:
  - Ingress
  - Egress
  ingress:
  # 允许HTTP端口8888的入站流量(用户浏览器访问)
  - from:
    - namespaceSelector: {}
    ports:
    - protocol: TCP
      port: 8888
  # 允许从API Server的入站流量(用于Webhook)
  - from:
    - podSelector:
        matchLabels:
          k8s-app: kube-apiserver
    ports:
    - protocol: TCP
      port: 8888
  egress:
  # 允许DNS解析
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
    ports:
    - protocol: UDP
      port: 53
  # 允许访问Kubernetes API Server(端口6443)
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
    ports:
    - protocol: TCP
      port: 6443
  # 允许访问持久存储的网络端口(根据存储后端不同而不同)
  - to:
    - ipBlock:
        cidr: 10.0.0.0/8
    ports:
    - protocol: TCP
      port: 2049  # NFS

这份配置的效果是:Notebook Pod之间默认不可互访,只允许用户浏览器访问HTTP端口,只允许DNS解析和Kubernetes API调用。如果Notebook需要访问外部网络(比如下载依赖包),你需要额外添加对应的出站规则。

5.3 安全上下文加固配置

# 技术栈:Kubernetes + Kubeflow
# 在Notebook的Deployment或StatefulSet中配置安全上下文
# 以Jupyter Notebook为例

apiVersion: apps/v1
kind: Deployment
metadata:
  name: secure-notebook
  namespace: user-alice-workspace
spec:
  replicas: 1
  selector:
    matchLabels:
      app: notebook
  template:
    metadata:
      labels:
        app: notebook
        app.kubernetes.io/component: notebook
    spec:
      serviceAccountName: notebook-sa-alice
      # Pod级别的securityContext
      securityContext:
        runAsUser: 1000
        runAsGroup: 3000
        fsGroup: 3000
        runAsNonRoot: true       # 强制以非root用户运行
        seccompProfile:
          type: RuntimeDefault   # 启用seccomp安全配置文件
      containers:
      - name: notebook
        image: gcr.io/kubeflow-images-public/notebook-servers/jupyter-pytorch:latest
        ports:
        - containerPort: 8888
        # 容器级别的securityContext
        securityContext:
          allowPrivilegeEscalation: false  # 禁止提权
          readOnlyRootFilesystem: true      # 根文件系统只读
          capabilities:
            drop:
            - ALL                   # 丢弃所有Linux capabilities
        volumeMounts:
        - name: writable-home
          mountPath: /home/jovyan
        # 允许写入的目录挂载为可写卷
        volumes:
        - name: writable-home
          emptyDir: {}

关键加固点说明:

  • runAsNonRoot: true 确保容器不会以root身份运行
  • allowPrivilegeEscalation: false 阻止进程通过setuid等方式提权
  • readOnlyRootFilesystem: true 让根文件系统只读,防止恶意写入
  • capabilities: drop: ALL 丢弃所有Linux capabilities,减少攻击面

六、应用场景与技术优缺点分析

6.1 应用场景

权限管控这套东西,在以下几种场景里尤其需要认真对待。

第一种是多团队共享集群。你们公司可能有数据科学组、机器学习组、工程组,大家都用同一个Kubernetes集群跑Notebook。这种情况下,组间隔离是必须做的,否则A组能偷偷改B组的实验数据。

第二种是客户隔离场景。如果你的Kubeflow是给多个外部客户用的(比如SaaS服务),每个客户的Notebook必须严格隔离,不能有任何越界访问的可能性。这涉及到数据合规和法律责任。

第三种是合规审计场景。如果你的公司通过了等保、SOC2、ISO27001之类的合规认证,审计的时候会检查你的集群权限配置。Notebook的权限管控是审计的必检项。

6.2 技术优缺点

优点:

Kubeflow Notebook Server的权限管控机制有几个明显的好处。第一,它利用了Kubernetes原生的Namespace和RBAC机制,不需要引入额外的第三方组件,运维成本低。第二,权限配置以YAML文件形式存在,可以纳入Git版本管理,做到可追溯、可审计。第三,NetworkPolicy和安全上下文是声明式的,一旦配置好就自动生效,不依赖人工操作。

缺点:

但也有不少坑。第一,RBAC配置比较复杂,尤其是当你需要精细控制每个用户的权限时,Role和RoleBinding的数量会爆炸,维护起来很头疼。第二,NetworkPolicy的配置容易出错,一个错误的规则可能导致Notebook完全无法访问外部资源(比如没法下载pip包)。第三,安全上下文加固过度可能会影响Notebook的正常运行,比如某些Python库需要特定的系统调用,drop ALL capabilities可能会导致库加载失败。

6.3 注意事项

几个容易踩的坑提前说清楚:

坑一:ServiceAccount Token挂载。 默认情况下,Kubernetes会把ServiceAccount Token挂载到容器的/var/run/secrets/kubernetes.io/serviceaccount/目录。如果Notebook的ServiceAccount权限过大,容器内的任何进程都能读取这个Token并调用Kubernetes API。加固方式是在PodSpec里设置automountServiceAccountToken: false,然后通过InitContainer或者Webhook动态注入只包含必要权限的Token。

坑二:Sidecar容器的权限。 有些Notebook镜像会启动Sidecar容器(比如日志采集器、监控代理)。这些Sidecar的权限配置经常被忽略。一个权限过大的Sidecar,效果等同于整个Notebook都获得了过大权限。

坑三:PVC的权限配置。 上面提到的PVC隔离问题,除了访问模式配置,还有StorageClass的权限。某些StorageClass会创建共享的PV,导致多个Namespace的Pod挂载到同一个存储后端。

坑四:Kubeflow自身的权限配置。 Kubeflow的Jupyter WebApp组件(也就是Notebook的管理界面)有自己的ServiceAccount,这个SA的权限也需要仔细审查。如果这个SA能访问所有用户的Namespace,WebApp被攻破就意味着所有Notebook都不安全。

七、补充说明:关联技术详解

7.1 Kubernetes RBAC的工作原理

RBAC是Kubernetes权限控制的基石。它由四个核心对象组成:Role、ClusterRole、RoleBinding、ClusterRoleBinding。

Role定义了在某个Namespace内能做什么操作。ClusterRole定义的是集群级别的权限。RoleBinding把Role绑定到用户(User、ServiceAccount或Group),只在某个Namespace内生效。ClusterRoleBinding把ClusterRole绑定到用户,在整个集群范围内生效。

# 技术栈:Kubernetes + Kubeflow
# RBAC权限验证的实操示例
# 检查某个ServiceAccount是否有特定权限

# 验证notebook-sa-alice是否能List user-bob-workspace的Pods
# 预期结果:应该被拒绝(因为alice的SA不应该有跨Namespace权限)

kubectl auth can-i list pods -n user-bob-workspace \
  --as=system:serviceaccount:user-alice-workspace:notebook-sa-alice

# 验证notebook-sa-alice是否能List自己Namespace的Pods
# 预期结果:应该被允许

kubectl auth can-i list pods -n user-alice-workspace \
  --as=system:serviceaccount:user-alice-workspace:notebook-sa-alice

# 批量检查所有Notebook ServiceAccount的跨Namespace访问能力
for ns in $(kubectl get ns -o name | grep user-); do
    for sa in $(kubectl get sa -n $ns -o name | grep notebook-sa); do
        sa_name=$(echo $sa | sed 's|/||')
        for target_ns in $(kubectl get ns -o name | grep user-); do
            if [ $ns != $target_ns ]; then
                result=$(kubectl auth can-i get pods -n $target_ns \
                    --as=system:serviceaccount:${ns##*/}:$sa_name 2>/dev/null)
                if [ "$result" == "yes" ]; then
                    echo "[越界] $sa_name can access $target_ns/pods"
                fi
            fi
        done
    done
done

这个脚本会扫描所有Notebook的ServiceAccount,检查是否存在跨Namespace访问权限。如果有输出,说明存在越界风险。

7.2 动态审计日志

除了主动排查,还可以配置Kubernetes的审计日志来被动监控异常操作。

# 技术栈:Kubernetes + Kubeflow
# 配置Kubernetes审计日志,记录Notebook相关的敏感操作
# 添加到API Server的配置文件(如kubeadm生成的/apiserver.yaml)

apiVersion: apiserver.config.k8s.io/v1
kind: AuditPolicy
rules:
  # 记录所有对Notebook资源的修改操作
  - level: RequestResponse
    verbs: ["create", "update", "patch", "delete"]
    resources:
    - pods
    - deployments
    - statefulsets
    - persistentvolumeclaims
    - services
    namespaces: ["user-alice-workspace", "user-bob-workspace"]
  # 记录RBAC相关的操作
  - level: RequestResponse
    verbs: ["create", "update", "delete"]
    resources:
    - roles
    - rolebindings
    - clusterroles
    - clusterrolebindings
  # 记录对secrets的读取操作(可能被用于窃取Token)
  - level: Metadata
    verbs: ["get", "list"]
    resources:
    - secrets

配置好审计日志后,用Fluentd或者其他日志工具采集审计日志,设置告警规则。比如:某个Notebook的ServiceAccount在短时间内大量List其他Namespace的Pod,可以触发告警。

八、文章总结

Kubeflow Notebook Server的权限管控,本质上就是在"方便开发者使用"和"保证安全隔离"之间找平衡。完全放开权限,Notebook好用但集群不安全;管得太死,Notebook能跑但开发者被限制得很难受。

核心要点回顾:

第一,环境隔离要逐层检查。从容器级到Namespace级到RBAC级到NetworkPolicy级,每一层都要确认配置正确。不能只做一层就以为安全了。

第二,RBAC遵循最小权限原则。Notebook的ServiceAccount只应该能访问自己Namespace内的资源,不要给集群级别的权限。定期用kubectl auth can-i命令验证权限边界。

第三,NetworkPolicy必须配置。没有NetworkPolicy的集群,Pod之间默认全通。至少要给Notebook所在的Namespace加上默认拒绝策略,再按需开放必要端口。

第四,安全上下文不要省略。runAsNonRoot、allowPrivilegeEscalation: false、readOnlyRootFilesystem: true这几个配置建议作为Notebook部署的默认值。

第五,定期审计。用脚本定期扫描所有Notebook的权限配置,检查是否有越界风险。配合审计日志做被动监控,异常操作能及时发现。

第六,存储隔离别忘。PVC的访问模式和StorageClass配置也要检查,避免不同用户的Notebook挂载到同一个存储后端。

权限管控不是一次性的工作,而是一个持续的过程。随着团队规模扩大、Notebook数量增加,权限配置会越来越复杂。建立一套自动化的权限检查和审计流程,比每次人工排查要高效得多。