· 伟大航路的开始 ·
在近几年,云原生可谓是站到了一个"风口浪尖"的位置,仿佛一夜之间,改革春风吹满地,各家企业都争相赶着上云,这个现象在我眼里,称之为浪潮也不为过。然而,上云除了带来技术红利的同时,也带来了相当多的安全问题,比如对于 ak/sk (accesskey/accesssecret) 的泄露这样一个简简单单的问题,就让很多甲方头痛,头痛的点不在于检测本身,而在于检测面的覆盖。
ak/sk ,它既可能存在在代码仓库,也可能存在于镜像,容器,集群等各种对象,而每种对象又有不同的实现,比如对于镜像来说,我们可以用 docker 去管理,也可以用 containerd 去管理。这就造成了一个问题,或许 70% 的检测逻辑都能复用一些传统的安全手段,但是面的广度,导致了我们并没有办法,能够有效覆盖链路上所有可能存在的风险。
于是乎,我们诞生了一个想法,我们能不能写这样一套SDK,它让开发者操作所有的云原生对象就像操作主机一样简单直接,同时它可以屏蔽同对象不同类别的差异化实现(比如对于集群来说,我们不需要考虑 kubernetes 还是 openshift ),基于这套 SDK,开发者只需要关心核心的检测逻辑,而不需要担心任何的兼容和适配性问题。这个想法一出来,就和公司的小伙伴们一拍即合,决定立刻开始做,这就是问脉 SDK 的由来。
修炼 "一指禅"
type Runtime interface {
ListImageIDs() ([]string, error)
ListContainerIDs() ([]string, error)
}
通过上面定义的 interface ,我们已经可以在忽略容器运行时的前提下,获取所有不同容器运行时的 镜像/容器 的唯一标识了(此处省略 n 行代码)。然而只有标识是不够的,我们还需要基于标识去打开对象,通过对象进行进一步的操作,于是我们完善了一下 Runtime 的定义。
type Runtime interface {
ListImageIDs() ([]string, error)
OpenImageByID(id string) (Image, error)
ListContainerIDs() ([]string, error)
OpenContainerByID(id string) (Container, error)
}
在完成了上述 interface 的编写后,顺其自然的就发现,我们还需要定义 Image 和 Container 的公共行为。这里就必须要提到一个概念,OCI 规范(Open Container Initiative),OCI 的本质是一个业界通用的容器规范,它包含核心的两部分,分别是 image-spec 和 runtime-spec ,对应到实际的概念,则分别是镜像规范以及容器规范。目前所有使用率较高的容器运行时,均遵循 OCI 规范,包括老大哥 docker 。而整个 OCI 规范下的运行流程如下图所示。
因此,基于 OCI 规范出发,镜像和容器都应该包含获取 OCISpec 这样一个公共行为。但显然只能获取 OCISpec 是不够的,因为它所提供的内容并不足以让我们做任何的安全检测。那让我们反过来思考,如果需要做安全检测,我们需要给这些对象赋予什么能力?答案其实很清晰,对于镜像而言,我们需要让它支持 Filesystem 的操作指令,比如打开镜像中的指定路径 /etc/shadow 。对于容器,我们需要让它支持 Filesystem/Psutil/Netstat 等操作指令,比如获取容器中的 1 号进程的 cmdline 。最终我们将镜像和容器定义为如下结构
type Image interface {
FileSystem
ID() string
OCISpec() *image-spec.Spec, error
Repos() ([]string, error)
RepoRefs() ([]string, error)
}
type Container interface {
FileSystem
Psutil
Netstat
ID() string
Name() string
ImageID() string
OCISpec() *runtime-spec.Spec, error
}
小试牛刀
光说不练假把式,不能被用起来的 SDK 就不是一个好 SDK。完成了上面的功能开发后,让我们来看看,如果要检测镜像里面是否有包含 RSA 私钥的文件应该怎么写。
d, err := docker.New()
if err != nil {
panic(err)
}
首先我们需要初始化一个容器运行时,这里显示的指定了 docker ,当然我们也可以指定其他容器运行时,返回的都是 Runtime 接口。
ids, _ := d.ListImageIDs()
for _, id := range ids {
i, err := d.OpenImageByID(id)
if err != nil {
continue
}
// do something ...
}
接下来我们调用 Runtime 的 ListImageIDs 获取所有的镜像 ID,再调用 OpenImageByID 即可打开一个镜像实例。
_ = i.Walk("/", func(path string, info fs.FileInfo, err error) error {
f, err := i.Open(path)
if err != nil {
return nil
}
b, err := ioutil.ReadAll(f)
if err != nil {
return nil
}
if strings.Contains(string(b), "-----BEGIN RSA PRIVATE KEY-----") {
fmt.Printf("[alert] find rsa key in image: %s, filepath: %sn", ref, path)
}
return nil
})
结语
今天只是给大家简单介绍了一下问脉 SDK 最基础的功能,然而只有这些功能显然是不够的。接下来的文章,会给大家介绍各种各样的 “黑魔法”,诸如 binding/插件系统/跨对象关联 等等,然后一步步揭秘我们是如何将现在只有小小雏形的 SDK,打造成云原生安全的最强外挂。
容器安全相关技术投稿请联系:
若有收获,就点个赞吧~
原文始发于微信公众号(长亭百川云平台):技术分享|问脉 SDK: 云原生安全的最强外挂 (上)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论