>> 99%的软件实现都是CRUD, 但有些CRUD还是能玩出花来的。
背景
Grafeas, 希腊语中Scribe(抄写员)的意思,是Google在2017年联合多家厂商发起的开源项目,希望可以定义一套统一的的方法来审计和管理软件供应链,具体的背景可以阅读「安全交付:GCP 的安全软件供应链」
如上图所示,所谓的软件供应链,可以大抵等同于从源码到发布物的整条流水线。感谢区块链的科普,大家对于可信供应链的概念倒也不算陌生,一盒在超市里售卖的鸡蛋通过区块链可以回溯到下蛋的母鸡,然后孵出母鸡的蛋,然后找到下了那个蛋的母鸡,如此循环,一直到堆栈溢出...
言归正传,首先Grafeas不是区块链技术,官方称为「A Component Metadata API」。解释之前先描述一个实际例子, 笔者在Google的时候也维护着MySQL和PostgreSQL的分支以及其包含的一众第三方依赖,时不时的就会被系统分配到一个bug,说某个CVE影响到了当前代码仓库里的MySQL版本,要去修复。可以发现系统要做到这样的通知需要一些信息:
- 代码仓库里的MySQL版本号
- 针对当前MySQL的CVE信息
- 以及当前MySQL的维护者
基本上各公司的安全扫描系统都会存类似信息,没什么太特别的,只是说每家都搞了一套自己的罢了,Google内部一套,JFrog一套,阿里也是自己一套,诸如此类。这时候,还是Google先站了出来,继续奉行标准先行,API先行的策略,推出了Grafeas想来统一一下这套数据模型。这个项目起于2017年, 3年过去了,还是不温不火的状态,Github星星数还没上千,当然这和软件供应链本身就是个小众领域也有关系。不过笔者相信软件供应链,尤其是可信安全这块接下来会受到越来越多的关注,有这么几个原因 :
- 随着持续集成以及微服务成为主流,我们发布软件的频率极速上升。
- 随着应用越来越复杂,以及开源的趋势,一个应用依赖了越来越多的三方依赖。
- 随着应用基本功能的日趋完善同质化,诸如安全/合规类的特性会成为竞争关键点。
代码走读
模型和API
至于Grafeas这个项目,我个人还是比较看好的,主要是因为它的数据建模比较合理。Grafeas里最核心的两个概念叫做Note和Occurence, 其中Note记录了某种分析类型的具体实例,比如某一个具体的CVE就可以作为Vulnerability分析类型的一个实例。而Occurrence记录了针对某一个具体Note实例,在某个特定资源上发现的情况。拿之前MySQL的例子来说,如果MySQL某个版本曝出了某个CVE,这个CVE就是一个Note实例。而基于这个MySQL版本所打的镜像资源就会出现一个Occurrence,记录这个镜像有这么一个CVE。
目前Note的类型有这么些
oneof type {
// A note describing a package vulnerability.
grafeas.v1.VulnerabilityNote vulnerability = 10;
// A note describing build provenance for a verifiable build.
grafeas.v1.BuildNote build = 11;
// A note describing a base image.
grafeas.v1.ImageNote image = 12;
// A note describing a package hosted by various package managers.
grafeas.v1.PackageNote package = 13;
// A note describing something that can be deployed.
grafeas.v1.DeploymentNote deployment = 14;
// A note describing the initial analysis of a resource.
grafeas.v1.DiscoveryNote discovery = 15;
// A note describing an attestation role.
grafeas.v1.AttestationNote attestation = 16;
// A note describing available package upgrades.
grafeas.v1.UpgradeNote upgrade = 17;
}
Occurence也有与之对应的
// Required. Immutable. Describes the details of the note kind found on this
// resource.
oneof details {
// Describes a security vulnerability.
grafeas.v1.VulnerabilityOccurrence vulnerability = 8;
// Describes a verifiable build.
grafeas.v1.BuildOccurrence build = 9;
// Describes how this resource derives from the basis in the associated
// note.
grafeas.v1.ImageOccurrence image = 10;
// Describes the installation of a package on the linked resource.
grafeas.v1.PackageOccurrence package = 11;
// Describes the deployment of an artifact on a runtime.
grafeas.v1.DeploymentOccurrence deployment = 12;
// Describes when a resource was discovered.
grafeas.v1.DiscoveryOccurrence discovery = 13;
// Describes an attestation of an artifact.
grafeas.v1.AttestationOccurrence attestation = 14;
// Describes an available package upgrade on the linked resource.
grafeas.v1.UpgradeOccurrence upgrade = 15;
}
Occurrence中这样把资源和Note关联起来
// Required. Immutable. A URI that represents the resource for which the
// occurrence applies. For example,
// `https://gcr.io/project/image@sha256:123abc` for a Docker image.
string resource_uri = 2;
// Required. Immutable. The analysis note associated with this occurrence, in
// the form of `projects/[PROVIDER_ID]/notes/[NOTE_ID]`. This field can be
// used as a filter in list requests.
string note_name = 3;
// Output only. This explicitly denotes which of the occurrence details are
// specified. This field can be used as a filter in list requests.
grafeas.v1.NoteKind kind = 4;
如果要继续探寻模型,寻着上面这些一路看下去就可以。目前的API版本是v1, 关键的数据模型相对于之前v1beta1增加了Upgrade这个类别,总体上模型比较稳定,体现了Google一贯在数据建模上的老道,这也来源于工程实践的积累。
除了最核心的建模之外,核心的Grafeas API设计也比较严谨,虽然只是普通的CRUD,但也是教科书般的grpc接口设计
- 命名的规范一致性,Get/List/Delete/Update/Create/BatchCreate, 其中Get/Delete/Update/Create针对单条操作用的是名词的单数形式,List/BatchCreate针对多条操作用的是复数形式。
- enum和anyof type的配对使用分别体现在Note以及Occurrence的枚举中。
- List接口采用page_token的形式。
- Update接口使用google.protobuf.FieldMask。
还有一个值得一提的是List请求里的filter字段,用的也是Google自己开源的Common Expression Language(CEL)。不过目前代码里还没有真正实现filter的功能,在storage这层是被无视掉的。
一些可改进点
整体的代码结构比较简单,虽然项目是2017年开始的,但是开发活跃度不是很高,主要还是几个Google Cloud的工程师在参与。
API
可以探讨的一个点是Grafeas服务本身是个monolithic service,包含了note和occurrence两个服务,comment中其实已经说:
// Analysis results are stored as a series of occurrences. An `Occurrence`
// contains information about a specific analysis instance on a resource. An
// occurrence refers to a `Note`. A note contains details describing the
// analysis and is generally stored in a separate project, called a `Provider`.
// Multiple occurrences can refer to the same note.
分离的话可以有更好的separation of concerns。还有在运行态,occurrence无论从调用频次还是重要性应该都高于note。比如在每次应用准入的时候,必然会调用occurrence接口校验应用镜像是否符合规范,显然如果occurrence服务这个时候挂了,要么应用无法启动,要么服务降级,暴露风险到线上。
功能
除了上文提到的filter,还有不少功能没有实现,比如看初始化grpcServer的地方,Auth, Filter, Logger均没有实现
g := grafeas.API{
Storage: *db,
Auth: &grafeas.NoOpAuth{},
Filter: &grafeas.NoOpFilter{},
Logger: &grafeas.NoOpLogger{},
EnforceValidation: true,
}
持久层
如前所示,持久层做了一层Storage接口的抽象,目前实现了基于内存的memstore, 基于BoltDB的embeddedstore, 以及基于PostgreSQL的pgsqlstore。从目前的代码看,storage的整体节奏有点问题,一方面embeddedstore如果选择sqlite的话,应该可以在写的时候做到和pgsqlstore更多的代码复用,抽象出更好的针对关系数据库的接口,方便对接mysql/oceanbase这些。另一方面,选择PostgreSQL,其实是不错的选择,但是没有利用上PostgreSQL的独特功能,可能有兼容性考虑,但还是有暴殄天物之感,例如:
- 直接是把Note/Occurrenence序列化下存的, 没有使用PostgreSQL强大的JSON功能。然后使用了Text而不是二进制类型bytea。
- 既然是存了个Blob, 相应的也就无法用到PostgreSQL的ENUM类型。
- 也没有选择放在单独的schema里面,目前几张表名projects/notes/occurrences/operations还是容易引起名字冲突的。
当然因为模型比较简单,storage层对接个区块链应该也不是太难的问题。
扩展性
毕竟是Google的作品,还是带着Google的烙印,例如project应该就是源自Google Cloud的project概念,目前的deployment note也只支持Google Cloud的GKE和App Engine Flex。
整个项目在扩展性方面还没有太好的规划,比如没有引入any.proto以及配套的插件体系,使得在添加Note类型时需要直接修改核心代码。不过这个更可能是项目本身的克制,还在确定稳定的核心模型。另外还有一个核心扩展点在于提供Analysis的框架,帮助vendor可以更好把各种notes/occurrences集成进来。
集成
目前看到大厂里,Grafeas只集成进了Google Cloud自家的Container Analysis以及JFrog XRay里。像通过插件机制集成到Jenkins里,集成到Gitlab里还停留在Feature Request阶段。
总结
总的来说Grafeas这个项目还有不少工作要做,但是对于最核心的部分,软件供应链建模以及相对应的API,项目设计得比较妥当,担得上「A Component Metadata API」这个称号。这个感觉类似于Google在CI/CD云原生领域的Tekton项目,同样是靠出色的建模脱颖而出。而且有意思的是这两个项目都出自Google,而不是垂直行业里的老大JFrog/Jenkins,反而是等Google推出后,JFrog/Jenkins不约而同地加入了Google的Grafeas/Tekton项目。
好了,单是把各种Notes和Occurrence存起来还只是可信软件供应链里的一部分,另一部分则是在于怎么样使用这些信息,比如作为软件部署时的准入规则,这就要说到随Grafeas应运而生的Kritis(希腊语Judge)项目了,下回分解。