关于容器:问脉-SDK-云原生安全的最强外挂-上

88次阅读

共计 3043 个字符,预计需要花费 8 分钟才能阅读完成。

平凡航路的开始

在近几年,云原生堪称是站到了一个 ” 风口浪尖 ” 的地位,好像一夜之间,改革春风吹满地,各家企业都争相赶着上云,这个景象在我眼里,称之为浪潮也不为过。然而,上云除了带来技术红利的同时,也带来了相当多的平安问题,比方对于  ak/sk  (accesskey/accesssecret) 的泄露这样一个简简单单的问题,就让很多甲方头痛,头痛的点不在于检测自身,而在于检测面的笼罩。

ak/sk,它既可能存在在代码仓库,也可能存在于镜像、容器、集群等各种对象,而每种对象又有不同的实现,比方对于镜像来说,咱们能够用  docker  去治理,也能够用  containerd  去治理。这就造成了一个问题,或者 70% 的检测逻辑都能复用一些传统的平安伎俩,然而面的广度,导致了咱们并没有方法,可能无效笼罩链路上所有可能存在的危险。

于是乎,咱们诞生了一个想法,咱们能不能写这样一套 SDK,它让开发者操作所有的云原生对象就像操作主机一样简略间接,同时它能够屏蔽同对象不同类别的差异化实现(比方对于集群来说,咱们不须要思考  kubernetes 还是  openshift),基于这套 SDK,开发者只须要关怀外围的检测逻辑,而不须要放心任何的兼容和适配性问题。这个想法一进去,就和公司的小伙伴们一拍即合,决定立即开始做,这就是问脉 SDK 的由来。


修炼 “ 一指禅 ”

咱们的指标是心愿开发者用起来足够 easy,如果用武侠小说外面的概念来打比方,就好比是少林绝技“一指禅”,无论状况如许简单,咱们都只须要用一只手指就能击破敌人。

然而这个事件说起来容易,做起来难。咱们面临的第一个问题是,如何对立容器运行时? 相熟云原生的人应该晓得,所有的容器,实际上都是被容器运行时,比方  docker/containerd/cri-o/podman  等拉起来的。尽管他们做的事件差不多,然而性能实现细节上却大有不同,比方  docker  会将镜像的索引存储在一个  JSON  中,而  containerd  却应用了  boltdb  去存储镜像索引。

因而,为了让咱们的“一指禅”足够迷信和正当,咱们须要尽可能的去形象不同容器运行时公共行为,而对于容器运行时来说,公共行为 无外乎和两个对象有关系:镜像和容器(pod  实质上也是容器,所以没有独自列出),基于此,咱们开始写下了第一行代码:

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: %s\n", ref, path)
    }

    return nil
})

 基于关上的镜像实例,间接调用  Walk  函数即可遍历镜像文件,并检测出蕴含了特定字符串的文件(示例中为  —–BEGIN RSA PRIVATE KEY—–),简略的运行一下下面这个只有 50 行的小程序,后果如下:


结语

明天只是给大家简略介绍了一下问脉 SDK 最根底的性能,然而只有这些性能显然是不够的。接下来的文章,会给大家介绍各种各样的“黑魔法”,诸如 binding/ 插件零碎 / 跨对象关联 等等,而后一步步揭秘咱们是如何将当初只有小小雏形的 SDK,打造成云原生平安的最强外挂。

正文完
 0