bigywater.tech
Kubernetes · 24

审计日志:一次 kubectl 命令留下了哪些记录

一次 kubectl 命令在 API Server 的审计日志里留下什么?策略里的几个级别到底差在哪?这篇单独起一个开了审计的集群,写一份自己的策略,用 kubectl 响应头里的 Audit-Id 把命令和日志对上,逐字段看一遍,并对比冷热缓存下一条命令发出的请求数。

2 条
一次 create configmap 在日志里的记录数:RequestReceived 与 ResponseComplete 共用一个 auditID
hunter2 → 1 行 / 0 行
同一个口令在 ConfigMap(RequestResponse)和 Secret(Metadata)日志中出现的行数
3 条 vs 1 条
冷缓存与热缓存时 kubectl get pods 在审计日志里的请求数

一次 create 的两条记录

实测 · 本机执行输出
  kubectl -v=8 的响应头里有 Audit-Id:882712fc-bb6e-43fb-8eaf-ea60500ed957;日志里这个 auditID 共 2 条:
    stage=RequestReceived  level=RequestResponse  verb=create  uri=/api/v1/namespaces/k18/configmaps?fieldManager=kubectl-create&fieldVal code=- requestObject=无 responseObject=无
    stage=ResponseComplete level=RequestResponse  verb=create  uri=/api/v1/namespaces/k18/configmaps?fieldManager=kubectl-create&fieldVal code=201 requestObject=有 responseObject=有
  ResponseComplete 的关键字段:user.username=kubernetes-admin;user.groups=['kubeadm:cluster-admins', 'system:authenticated'];userAgent=kubectl/v1.36.1;sourceIPs=['172.18.0.1']
    objectRef={"resource": "configmaps", "namespace": "k18", "name": "c1", "apiVersion": "v1"};responseStatus.code=201
    annotations={"authorization.k8s.io/decision": "allow", "authorization.k8s.io/reason": "RBAC: allowed by ClusterRoleBinding \"kubeadm:cluster-admins\" of ClusterRole \"cluster-admin\" to Group \"kubeadm:cluster-admins\""}
    requestObject.data={'password': 'hunter2'}(明文口令出现在了审计日志里)
  同样的口令 hunter2 在日志文件里出现 1 行,其中属于 secrets 的有 0 行(Secret 的规则是 Metadata,只记元数据)

策略里 k18 命名空间的 ConfigMap 是 RequestResponse 且不 omit 任何阶段,所以一次 kubectl create cm c1 有两条记录,auditID 相同:RequestReceived(还没处理,没有状态码)和 ResponseComplete(code=201,请求体和响应体都在)。后者里能看到 user.username=kubernetes-admin、userAgent=kubectl/v1.36.1、objectRef,以及 annotations 里的授权决定:authorization.k8s.io/decision: allow 和 RBAC: allowed by ClusterRoleBinding "kubeadm:cluster-admins" …。kubectl 的 -v=8 响应头里有同一个 Audit-Id,可以把命令和日志对上。

RequestResponse 把 --from-literal=password=hunter2 的明文写进了 requestObject.data;同样的口令放进 Secret,日志里一行都没有,因为 Secret 的规则是 Metadata。

三个级别

实测 · 本机执行输出
  创建 configmaps  → stage=RequestReceived  level=RequestResponse  verb=create  uri=/api/v1/namespaces/k18/configmaps?fieldManager=kubectl-create&fieldVal code=- requestObject=无 responseObject=无
  创建 configmaps  → stage=ResponseComplete level=RequestResponse  verb=create  uri=/api/v1/namespaces/k18/configmaps?fieldManager=kubectl-create&fieldVal code=201 requestObject=有 responseObject=有
  创建 pods        → stage=ResponseComplete level=Request          verb=create  uri=/api/v1/namespaces/k18/pods?fieldManager=kubectl-client-side-apply&fie code=201 requestObject=有 responseObject=无
  创建 namespaces  → stage=ResponseComplete level=Metadata         verb=create  uri=/api/v1/namespaces?fieldManager=kubectl-create&fieldValidation=Strict code=201 requestObject=无 responseObject=无

同样是“创建一个对象”:Metadata(namespaces,落到最后一条规则)只记谁、什么时候、对什么、结果如何;Request(pods)再加上 requestObject;RequestResponse(configmaps)再加上 responseObject。pods 和最后一条规则都 omitStages: ["RequestReceived"],所以只有一条 ResponseComplete——日志量直接少一半。

一条命令发多少请求

实测 · 本机执行输出
  冷缓存(发现缓存目录是空的)kubectl get pods -n k18:审计日志里 3 条请求,其中 GET 发现类接口 2 条;真正列出 pods 的 1 条
    get /api?timeout=32s
    get /apis?timeout=32s
    list /api/v1/namespaces/k18/pods?limit=500
    …(共 3 条)
  热缓存(紧接着再跑一次)kubectl get pods -n k18:1 条请求:list /api/v1/namespaces/k18/pods?limit=500
  那条列出 pods 的请求:level=Request(k18 的 pods 规则是 Request,但 get/list 没有请求体,所以 requestObject=无)

发现缓存是空的时,kubectl get pods -n k18 在日志里留下 3 条:GET /api、GET /apis(现代 kubectl 用聚合发现,两个请求就够)和真正的 list /api/v1/namespaces/k18/pods?limit=500;紧接着再跑只有最后 1 条。这个 list 在 pods 的 Request 规则下,但 list 没有请求体,所以 requestObject 为空。分析审计日志时要记得“一条命令”可能对应多条记录。

被拒绝与被模拟

实测 · 本机执行输出
  kubectl get pods --as system:serviceaccount:k18:nobody:Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:k18:nobody" cannot list resource "pods" in API group "" in the namespace "k18"
  403:user=kubernetes-admin;impersonatedUser=system:serviceaccount:k18:nobody;code=403;decision=forbid;reason=''
  403:user=system:anonymous;impersonatedUser=-;code=403;decision=forbid;reason=''

403 同样在日志里:kubectl get pods --as system:serviceaccount:k18:nobody 的记录里 user 仍是真实调用者 kubernetes-admin,被模拟的身份在 impersonatedUser,decision=forbid(reason 为空);不带令牌的 curl 则是 user=system:anonymous、同样 forbid。审计日志是回答“谁尝试过什么”的主要依据。

分步演示

审计事件
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。单独起的单节点 kind 集群 kx-audit(Kubernetes v1.37.0),kubeadm 的 ClusterConfiguration 补丁给 kube-apiserver 加了 --audit-policy-file=/etc/kubernetes/policies/audit-policy.yaml 与 --audit-log-path=/var/log/kubernetes/audit.log,策略文件从宿主机挂进节点,日志用 docker exec 读出;策略规则依次是:None(健康检查与 /version)、None(系统组件)、Metadata(secrets)、RequestResponse(k18 的 configmaps)、Request(k18 的 pods,omit RequestReceived)、Metadata(其余,omit RequestReceived);冷缓存实验用空的 KUBECACHEDIR;实验后删除了这个集群;每种情形各一次
    • 没有验证:webhook 后端(--audit-webhook-config-file)与批处理参数;日志轮转(maxage/maxsize/maxbackup);审计对 apiserver 延迟的影响;长连接(watch、exec)的 ResponseStarted 阶段;auditID 在多 apiserver 副本下的关联;策略里 userGroups/verbs/nonResourceURLs 等其他匹配项的完整行为;审计事件里的 stageTimestamp 与耗时分析
    京ICP备2026067502号-1京公网安备11010502063487号