“「K8S 生态周报」内容主要包含我所接触到的 K8S 生态相关的每周值得推荐的一些信息。欢迎订阅知乎专栏「k8s生态」[1]。 ”
大家好,我是张晋涛。
上游进展
Kubernetes 发布了 v1.22.16 和 1.23.14,1.24.8,1.25.4等版本,其中最重要的就是以下两个安全漏洞了。
CVE-2022-3162
当用户被授权允许在集群范围内 list 或 watch 某个 namespace 范围的自定义资源时,可以读取在同一 API 组下,不同类型的其他自定义资源。
这个漏洞影响范围是:
- kube-apiserver v1.25.0 - v1.25.3
- kube-apiserver v1.24.0 - v1.24.7
- kube-apiserver v1.23.0 - v1.23.13
- kube-apiserver v1.22.0 - v1.22.15
- kube-apiserver < v1.21.?
CVE-2022-3294
我们平时如果想要进入 Pod 内进行操作(即 kubectl exec
)的时候,它的过程是:
- kubectl -> kube-apiserver:
kubectl 会请求 /api/v1/namespaces/<ns>/pods/<pod>/exec
到 kube-apiserver,实际的代码也很简单,可以看到是一个 POST 请求,并且按照传递的参数构造请求。
fn := func() error {
restClient, err := restclient.RESTClientFor(p.Config)
if err != nil {
return err
}
// TODO: consider abstracting into a client invocation or client helper
req := restClient.Post().
Resource("pods").
Name(pod.Name).
Namespace(pod.Namespace).
SubResource("exec")
req.VersionedParams(&corev1.PodExecOptions{
Container: containerName,
Command: p.Command,
Stdin: p.Stdin,
Stdout: p.Out != nil,
Stderr: p.ErrOut != nil,
TTY: t.Raw,
}, scheme.ParameterCodec)
return p.Executor.Execute("POST", req.URL(), p.Config, p.In, p.Out, p.ErrOut, t.Raw, sizeQueue)
}
- kube-apiserver -> 目标节点的 kubelet
当 kube-apiserver 接收到来自 client 的请求后,就需要构造新的请求然后到目标节点上执行了。 这部分的最直接的代码是如下的内容,会有一个 ExecLocation
函数,用来返回目标位置。
func ExecLocation(
ctx context.Context,
getter ResourceGetter,
connInfo client.ConnectionInfoGetter,
name string,
opts *api.PodExecOptions,
) (*url.URL, http.RoundTripper, error) {
return streamLocation(ctx, getter, connInfo, name, opts, opts.Container, "exec")
}
当然,和节点信息有关的部分,是在 streamLocation
函数的部分来获取的,如下:
container, err = validateContainer(container, pod)
if err != nil {
return nil, nil, err
}
nodeName := types.NodeName(pod.Spec.NodeName)
if len(nodeName) == 0 {
// If pod has not been assigned a host, return an empty location
return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name))
}
nodeInfo, err := connInfo.GetConnectionInfo(ctx, nodeName)
if err != nil {
return nil, nil, err
}
params := url.Values{}
if err := streamParams(params, opts); err != nil {
return nil, nil, err
}
loc := &url.URL{
Scheme: nodeInfo.Scheme,
Host: net.JoinHostPort(nodeInfo.Hostname, nodeInfo.Port),
Path: fmt.Sprintf("/%s/%s/%s/%s", path, pod.Namespace, pod.Name, container),
RawQuery: params.Encode(),
}
return loc, nodeInfo.Transport, nil
通过以上的步骤,kube-apiserver 就知道要跟哪个 Node 连接了。
不过这里有个需要注意的内容,上面的 ResourceGetter
是个通过 ResourceLocation
获取资源的接口,这也是这个漏洞中的核心。
func ResourceLocation(getter ResourceGetter, connection client.ConnectionInfoGetter, proxyTransport http.RoundTripper, ctx context.Context, id string) (*url.URL, http.RoundTripper, error) {
schemeReq, name, portReq, valid := utilnet.SplitSchemeNamePort(id)
if !valid {
return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid node request %q", id))
}
info, err := connection.GetConnectionInfo(ctx, types.NodeName(name))
if err != nil {
return nil, nil, err
}
if err := proxyutil.IsProxyableHostname(ctx, &net.Resolver{}, info.Hostname); err != nil {
return nil, nil, errors.NewBadRequest(err.Error())
}
// We check if we want to get a default Kubelet's transport. It happens if either:
// - no port is specified in request (Kubelet's port is default)
// - the requested port matches the kubelet port for this node
if portReq == "" || portReq == info.Port {
return &url.URL{
Scheme: info.Scheme,
Host: net.JoinHostPort(info.Hostname, info.Port),
},
info.Transport,
nil
}
- if err := proxyutil.IsProxyableHostname(ctx, &net.Resolver{}, info.Hostname); err != nil {
- return nil, nil, errors.NewBadRequest(err.Error())
- }
// Otherwise, return the requested scheme and port, and the proxy transport
return &url.URL{Scheme: schemeReq, Host: net.JoinHostPort(info.Hostname, portReq)}, proxyTransport, nil
}
上面是在 v1.22.16 中的修复,可以看到实际是把 proxyutil.IsProxyableHostname
的判断逻辑移动到了前面,在之前有可能会跳过此判断。
如果跳过了这个判断,就可能会导致原本经过认证的请求被发送到 API Server 所在的私有网络(说直白点,就是有可能会篡改目标地址)。
所以,这个漏洞的触发条件也很明确,只有能篡改 Node 地址才会受到影响。
受影响的版本如下:
- Kubernetes kube-apiserver ≤ v1.25.3
- Kubernetes kube-apiserver ≤ v1.24.7
- Kubernetes kube-apiserver ≤ v1.23.13
- Kubernetes kube-apiserver ≤ v1.22.15
解决办法要么是升级 kube-apiserver,要么可以设置 egress proxy 来进行管理。 但是如果升级 kube-apiserver 也有可能会导致一些依赖于 Node/Proxy 的子资源场景下的不可用,需要注意。
本周 Kubernetes v1.26.0-rc.0 也发布了,按照之前的习惯,正式版和这个版本中差别就不会很大了。 下期我会写一篇介绍 v1.26 版本中重点需要关注的内容,敬请期待!
好了,以上就是本次的全部内容,我们下期再聊!
参考资料
[1] k8s生态: https://zhuanlan.zhihu.com/container