unsetunset0x00 前言unsetunset
在实际的工作和攻防对抗的时候,您是否遇到过以下问题:
-
由于 API Server 错误配置,导致攻击者可以接管集群。 -
根据教程对 API Server 不安全端口的风险进行漏洞复现,却无法复现成功。 -
无法深入浅出的分析 API Server 不安全的服务的风险,被 API Server 各种大小版本之间的变化弄得一团乱。 -
由于环境限制,无法搭建多个 Kubernetes 环境进行靶场练习。 -
初学云原生安全时,通常感到无从下手,难度较大。
为了解决上述问题,喵苗安全专家组成员特意制作了一系列《Kubernetes 安全防护指南(基础篇)》免费版图文教程,帮助大家入门云原生安全。本次课程的亮点有:
-
根据 Kubernetes 各个版本发布的时间线,带领大家阅读 Kubernetes 关键源码,深入浅出地分析 Kubernetes 风险。 -
制作了开源的 Kubernetes 练习靶场,可在本地一键启动,无需虚拟机和服务器。 -
精心绘制的插图、诙谐有趣的叙述方式、理论知识与靶场练习相结合,带领你领略 Kubernetes 的安全。 -
搭建一个系统的 Kubernetes 学习框架,防止“管中窥豹,可见一斑”。 -
结合自身经验,帮助您总结关键知识和技能。
unsetunset0x01 基本概念unsetunset
API Server 就像它名字所描述的那样,主要负责提供 REST API 接口 ,方便我们对 Kubernetes 集群进行管控。拥有 API Server 全部权限的用户相当于拥有了集群中所有机器的 root 访问权限。
而命令行工具 kubectl 就是 API Server 的客户端工具,主要负责向 API Server 发送请求以实现资源和工作负载的管理。需要注意的是,任何对 API Server 有写权限的人都可以以相同的方式对集群进行管控。
如图所示,当我们使用 kubectl 获取集群信息的时候,只要通过-v 8
将日志的等级调到 8,就可以看到完整的 HTTP(S) 数据包。
unsetunset0x02 本地访问不安全的服务unsetunset
早期,Kubernetes 集群需要允许负载均衡等组件进行健康检查和发现,所以默认情况下,API Server 会在本地监听不安全的服务。
以 2018 年 3 月 7 日发布的 Kubernetes v1.9.0 为例,Kubernetes 在创建 API Server 的时候,会创建不安全的服务InsecureServing
// NewServerRunOptions creates a new ServerRunOptions object with default parameters// 代码片段链接:https://github.com/kubernetes/kubernetes/blob/b5ec061c7ab9995a801206ea74f614ced04206d2/cmd/kube-apiserver/app/options/options.go#L76funcNewServerRunOptions() *ServerRunOptions { s := ServerRunOptions{ GenericServerRunOptions: genericoptions.NewServerRunOptions(), Etcd: genericoptions.NewEtcdOptions(storagebackend.NewDefaultConfig(kubeoptions.DefaultEtcdPathPrefix, nil)), SecureServing: kubeoptions.NewSecureServingOptions(),// API Server 会创建不安全的服务 InsecureServing: kubeoptions.NewInsecureServingOptions(), Audit: genericoptions.NewAuditOptions(), Features: genericoptions.NewFeatureOptions(), Admission: genericoptions.NewAdmissionOptions(), Authentication: kubeoptions.NewBuiltInAuthenticationOptions().WithAll(), Authorization: kubeoptions.NewBuiltInAuthorizationOptions(), CloudProvider: kubeoptions.NewCloudProviderOptions(), StorageSerialization: kubeoptions.NewStorageSerializationOptions(), APIEnablement: kubeoptions.NewAPIEnablementOptions(), EnableLogsHandler: true, EventTTL: 1 * time.Hour, MasterCount: 1, EndpointReconcilerType: string(reconcilers.MasterCountReconcilerType), KubeletConfig: kubeletclient.KubeletClientConfig{ Port: ports.KubeletPort, ReadOnlyPort: ports.KubeletReadOnlyPort, PreferredAddressTypes: []string{// --override-hostnamestring(api.NodeHostName),// internal, preferring DNS if reportedstring(api.NodeInternalDNS),string(api.NodeInternalIP),// external, preferring DNS if reportedstring(api.NodeExternalDNS),string(api.NodeExternalIP), }, EnableHttps: true, HTTPTimeout: time.Duration(5) * time.Second, }, ServiceNodePortRange: kubeoptions.DefaultServiceNodePortRange, }// Overwrite the default for storage data format. s.Etcd.DefaultStorageMediaType = "application/vnd.kubernetes.protobuf"// register all admission plugins RegisterAllAdmissionPlugins(s.Admission.Plugins)// Set the default for admission plugins names s.Admission.PluginNames = []string{"AlwaysAdmit"}return &s}
而这个不安全的服务的端口就是8080
,只能在本地进行访问,任何对该端口的请求都会跳过身份验证和权限检查。
// NewInsecureServingOptions is for creating an unauthenticated, unauthorized, insecure port.// No one should be using these anymore.// 代码片段链接:https://github.com/kubernetes/kubernetes/blob/b5ec061c7ab9995a801206ea74f614ced04206d2/pkg/kubeapiserver/options/serving.go#L75C1-L83C1funcNewInsecureServingOptions() *InsecureServingOptions {return &InsecureServingOptions{ BindAddress: net.ParseIP("127.0.0.1"),// 不安全服务的端口为 8080 BindPort: 8080, }}
我们可以直接通过curl
命令进行检查,看看本地是否在监听不安全的服务。若命令执行过后,列出了 API 接口,那么不安全的服务是打开的状态。
curl localhost:8080
若该服务能被任何人进行访问,那么任何能够访问 master 节点的人都可以对集群进行管控,这是极其不安全的。所以默认情况下,这个不安全的服务只能在本地进行访问。
图 4:默认情况下,不安全的服务只能通过本地进行访问
unsetunset0x03 远程访问不安全的服务unsetunset
当然,这个不安全的服务也可以通过配置进行修改,让它可以被远程访问到。具体需要修改的 Flag 就是--insecure-bind-address
和--insecure-port
。
--insecure-port
用于提供未加密、未认证访问的服务端口。之所以会有这个设计,是因为 Kubernetes 假设了用户已经配置了防火墙规则,这个端口无法从集群外部进行访问,并且 nginx 默认会将集群公共地址上的 443 端口代理到这个端口。
--insecure-bind-address
主要是与--insecure-port
一起使用,若设置为0.0.0.0
,则允许所有的 IPv4 接口进行访问;若设置为::
,则允许所有的 IPv6 接口进行访问。
如图所示,我们可以将--insecure-bind-address
设置为0.0.0.0
,--insecure-port
设置为8080
,以达到可远程访问不安全的服务的目的。
配置生效过后,只要我们获取到集群的 IP 地址,就能够对集群的不安全服务进行远程访问与控制。
由此看来,--insecure-bind-address
和--insecure-port
是非常不安全的。
所以,在 2018 年 3 月 27 日发布的 v1.10.0 版本中,Kubernetes 将--insecure-bind-address
和--insecure-port
这两个 Flag 标记为弃用的状态,并宣称在未来的版本中将完全移除。
// 代码片段链接:https://github.com/kubernetes/kubernetes/blob/f170ef66340f6355d331ed90902574ff0532a20a/pkg/kubeapiserver/options/serving.go#L98func(s *InsecureServingOptions)AddFlags(fs *pflag.FlagSet) { fs.IPVar(&s.BindAddress, "insecure-bind-address", s.BindAddress, ""+"The IP address on which to serve the --insecure-port (set to 0.0.0.0 for all IPv4 interfaces and :: for all IPv6 interfaces).") fs.MarkDeprecated("insecure-bind-address", "This flag will be removed in a future version.") fs.Lookup("insecure-bind-address").Hidden = false fs.IntVar(&s.BindPort, "insecure-port", s.BindPort, ""+"The port on which to serve unsecured, unauthenticated access. It is assumed "+"that firewall rules are set up such that this port is not reachable from outside of "+"the cluster and that port 443 on the cluster's public address is proxied to this "+"port. This is performed by nginx in the default setup. Set to zero to disable.") fs.MarkDeprecated("insecure-port", "This flag will be removed in a future version.") fs.Lookup("insecure-port").Hidden = false}
此后,--insecure-bind-address
和--insecure-port
这两个 Flag 还能进行使用,但官方团队建议用户应尽早弃用,因为他们认为任何服务都应该先通过安全认证,才能进行使用。
于是,--insecure-bind-address
和--insecure-port
就相当于被宣判了“死缓”。
unsetunset0x04 不安全的服务被关闭unsetunset
时间来到 2020 年 12 月 9 日,Kubernetes 发布的 v1.20.0 版本在根据默认配置创建 API Server 服务的时候,就删除了创建不安全的服务的代码。
// NewServerRunOptions creates a new ServerRunOptions object with default parameters// 代码片段链接:https://github.com/kubernetes/kubernetes/blob/4a89df5617b8e1e26abb16150502d04e6c180533/cmd/kube-apiserver/app/options/options.go#L98funcNewServerRunOptions() *ServerRunOptions { s := ServerRunOptions{ GenericServerRunOptions: genericoptions.NewServerRunOptions(), Etcd: genericoptions.NewEtcdOptions(storagebackend.NewDefaultConfig(kubeoptions.DefaultEtcdPathPrefix, nil)), SecureServing: kubeoptions.NewSecureServingOptions(),// 在此处,删除了不安全服务创建的代码 Audit: genericoptions.NewAuditOptions(), Features: genericoptions.NewFeatureOptions(), Admission: kubeoptions.NewAdmissionOptions(), Authentication: kubeoptions.NewBuiltInAuthenticationOptions().WithAll(), Authorization: kubeoptions.NewBuiltInAuthorizationOptions(), CloudProvider: kubeoptions.NewCloudProviderOptions(), APIEnablement: genericoptions.NewAPIEnablementOptions(), EgressSelector: genericoptions.NewEgressSelectorOptions(), Metrics: metrics.NewOptions(), Logs: logs.NewOptions(), EnableLogsHandler: true, EventTTL: 1 * time.Hour, MasterCount: 1, EndpointReconcilerType: string(reconcilers.LeaseEndpointReconcilerType), IdentityLeaseDurationSeconds: 3600, IdentityLeaseRenewIntervalSeconds: 10, KubeletConfig: kubeletclient.KubeletClientConfig{ Port: ports.KubeletPort, ReadOnlyPort: ports.KubeletReadOnlyPort, PreferredAddressTypes: []string{// --override-hostnamestring(api.NodeHostName),// internal, preferring DNS if reportedstring(api.NodeInternalDNS),string(api.NodeInternalIP),// external, preferring DNS if reportedstring(api.NodeExternalDNS),string(api.NodeExternalIP), }, HTTPTimeout: time.Duration(5) * time.Second, }, ServiceNodePortRange: kubeoptions.DefaultServiceNodePortRange, }// Overwrite the default for storage data format. s.Etcd.DefaultStorageMediaType = "application/vnd.kubernetes.protobuf"return &s}
并且--insecure-port
只能设置为0
,表示关闭不安全的服务。
// TODO: delete this check after insecure flags removed in v1.24// 代码片段链接:https://github.com/kubernetes/kubernetes/blob/4a89df5617b8e1e26abb16150502d04e6c180533/cmd/kube-apiserver/app/server.go#L92funccheckNonZeroInsecurePort(fs *pflag.FlagSet)error {for _, name := range options.InsecurePortFlags { val, err := fs.GetInt(name)if err != nil {return err }if val != 0 {return fmt.Errorf("invalid port value %d: only zero is allowed", val) } }returnnil}
如果强行将--insecure-port
标志设置为非 0 端口,例如 8080,API Server 将会报错无法提供服务。只有将--insecure-port
改回为 0 后,APIserver 恢复正常。
通过限制端口的配置,Kubernetes 无需修改--insecure-bind-address
的相关代码,即可关闭不安全的服务。
所以,Kubernetes 自 v1.20.0 版本之后,--insecure-bind-address
和--insecure-port
这两个 Flag 将不再起任何作用了。
// TODO: remove these insecure flags in v1.24// 代码片段链接:https://github.com/kubernetes/kubernetes/blob/4a89df5617b8e1e26abb16150502d04e6c180533/cmd/kube-apiserver/app/options/options.go#L146funcaddDummyInsecureFlags(fs *pflag.FlagSet) {var ( bindAddr = net.IPv4(127, 0, 0, 1) bindPort int )for _, name := range []string{"insecure-bind-address", "address"} { fs.IPVar(&bindAddr, name, bindAddr, ""+"The IP address on which to serve the insecure port (set to 0.0.0.0 for all IPv4 interfaces and :: for all IPv6 interfaces).") fs.MarkDeprecated(name, "This flag has no effect now and will be removed in v1.24.") }for _, name := range InsecurePortFlags { fs.IntVar(&bindPort, name, bindPort, ""+"The port on which to serve unsecured, unauthenticated access.") fs.MarkDeprecated(name, "This flag has no effect now and will be removed in v1.24.") }}
unsetunset0x05 不安全的服务被移除unsetunset
2022 年 5 月 25 日,Kubernetes 发布了 v1.24.0 版本,在这次的发布信息中,Kubernetes 终于将--insecure-bind-address
和--insecure-port
两个 Flag 完全移除。
若您还在使用这两个 Flag,API Server 将报错、无法启动。至此,不安全的服务将完全退出历史舞台。
unsetunset0x06 总结unsetunset
本篇文章,我们讨论了 API Server 不安全服务的前世今生。在实际工作和学习中,我们只需要记住一点:任何不谈及 Kubernetes 版本的安全风险指南都是耍流氓。
当我们获取到一个 Kubernetes 集群时,需要关注其版本信息:
-
2018 年 3 月 7 日,Kubernetes 发布了 v1.10.0 版本,宣称弃用
--insecure-bind-address
和--insecure-port
这两个标志,这相当于被宣判了“死缓”,因为我们仍然可以使用这两个 Flag,将 API Server 配置为可远程访问。 -
2020 年 12 月 9 日,Kubernetes 发布了 v1.20.0 版本,在这之后,您无法将
--insecure-port
的设置为非 0 值,因为 API Server 不安全服务已关闭。 -
2022 年 5 月 25 日,Kubernetes 发布了 v1.24.0 版本,API Server 不安全服务已完全退出历史舞台。
说到这里,你是否需要去看看自己的集群是哪个版本呢?
Kubernetes 是一个非常著名的开源项目,它结合了来自世界各地的开源爱好者的智慧和力量,不断的进行更新与迭代,也许在今天或明天,就解决了某个安全风险、或引入新的安全风险。
不安全的服务所带来的风险被完全解决了,但安全的服务未必始终是安全的。
下一讲,我们来说说 Kubernetes 开放在 6443 端口的安全服务,敬请期待~
原文始发于微信公众号(喵苗安全):API Server 安全风险(一):不安全的服务(理论篇)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论