Hello 大家好我们上午的session就正式开始了我们先做介绍一下今天上午我们这个topic会给大家去介绍在软件供应链安全如何用Cavernal和Notation两个Sensef项目去确保软件供应链安全然后结合GitOps这样的一个方式去确保软件供应链安全然后我的co-speaker书婷我会邀请她先做介绍一下大家好 我是赵书婷可以叫我书婷我是Cavernal的co-creator现在是Cavernal的一名mentender同时在Nomada担任stuff engineer很荣幸今天来跟familie一起来发表这个演讲谢谢我叫卓文飞 然后我英文名还有我的githubid那些推特的那些叫Familie Zhou然后我现在是在微软做产品经理然后也在负责notary project和orus这两个sensef项目的maternter然后还有一个rotify project是做这个我们软件供应链安全验证的一个环节然后我们今天的topic主要会去分享下面这五个agenda这五个topic首先第一个我们会跟大家去简单介绍一下我们目前的这个软件供应链安全的一个概念和主要的一个挑战尤其是在国内软件供应链安全还是一个非常新的一个概念它的实践和落地在国内还非常的早期It's very early adoption stage然后第二个呢我会去简单的去介绍一下最近的这个软件供应链安全的一些攻击的事件然后大家为什么要去关注软件供应链安全第三个呢我们会去给大家去快速的go through一下目前在sensef在我们业界有哪些开源供应链安全的流行的框架和一些好的开源项目大家可以去试用或者说可以去做调研然后最后呢我们会主要的focus在两个开源项目第一个是sensef的notary project做进行的签名和验证然后第二个呢是这个keyvernal一个开放的kubernetes native的策略引擎来在kubernetes上去做一个验证然后最后我们会有一个live demo这个图大家应该非常熟悉I copied it from thesensef software supply chain security paper这个图呢其实它对比了传统的软件供应链和我们传统的一个供应链比如说像汽车制造特别是在一些传统的制造行业我们的一个产品像原材料的进口或者原材料的一个收集到最终一个产品的上市它会经过什么样的流程其实可以对比来看像食品加工与制造像汽车像一些这种传统的制造行业跟我们现在的软件的生产和开发其实是非常相似的因为现在我们去写一个软件我们大概率不会从低航代码都是我们自己去所有的代码都是我们自己去写我们可能要基于一些市面上比较好用的轮子或者是用一些这种basic image来开发我们的业务软件或者是我们的开源项目所以我们可能要需要用到大量的这种外部的dependencies这就意味着这些dependencies可能会带来一些不可信的或者是一些带有这种软件漏洞的这些问题在里面然后下面这个图呢是讲了我们现在当前这个软件生命周期的一个一个形态是什么样子从依赖的构建然后再到进向打包编译然后再到我们Nedwork去分发到最终的用户去部署其实跟传统的生产制造是非常相似的那其实在现代的这种软件生命周期当中其实在各个环节都有可能会发生一些安全攻击比如说在这个dependency的阶段可能会引入一些第三方的依赖比如说最著名的是两年前Lock4J对吧当时Lock4J出来那各大IT公司都是要求我们要尽快的把自己的软件打个Patch要升级到最新的这个Lock4J然后呢在构建阶段其实也会发生一些这种CVE的事件或者是一些软件安全的漏洞比如说在今年4月份OpenAI它就有一个因为Redis的一个漏洞Redis它有一个这个软件漏洞造成了OpenAI的ChartGBT不同的用户它能够在不同的这个聊天对话里面看到对方的输入的信息其实也就是这个信息这个泄漏的安全的隐患所以这个呢就导致OpenAI在当天把他们ChartGBT下线了6个小时其实这个就对全球的用户造成了6个小时的一个影响对吧ChartGBT现在用户量这么大所以当这个安全的 Security Attack或者是CVE的事件发生以后其实对整个业务的影响是非常非常大的是这种毁灭性的一个打击如果是电商如果是想象在双十一或者是在黑五这种事件发生了这种CVE或者是发生了这个软件漏洞的攻击那其实对整个业务的影响是非常非常大的其实在北美啊其实软件供应链安全已经是非常如火如荼很多公司都有自上而下的一些法律政策去要求比如说在2021年白宫是下达了一个这种Cyber Security的政策要求所有的这个软件是必须要去带上它的S-Bone然后还有各种各样的政策要求它的软件供应链去符合一定的安全和合规的要求但其实在我们国内目前还没有这种相关的法案去出台来要求公司怎样去符合一个合规性但其实我们去看业内的一些调研和报告比如说我这里给到的是一个新思科技在2023年一个最新的报告他们采取了1700多份的这个Code Repository做一个样本然后呢他们发现有45%的代码它是包含这种软件license这个软件协议的一个问题的它可能有一些不合规的协议比如说像GPLV3或者是像BSL大家最近可能知道HashCorp改了他们的开源协议对吧这个其实就会造成有在你的商业产品或者是在你的这个项目里面用到了一些商业不太友好的客人软件的这种协议所以你可能立马要去downgrade或者说要去换一个另外的找一个另外的替代品其实这样的事件非常非常的常见在前几年还有像elastic search还有mongodb都有这种license的替换所以这个对业务的影响也非常大但可能很多企业它并不知道有这么一个事件或者说它并不知道到底它的哪些dependencies或者哪些components哪些项目里面包含这些不合规的软件然后第二个就是89%的这些代码它可能包括了一些超过4年以上没有人维护的代码也就是说你的这些失去维护或者是out of daily的这些代码和项目它可能会带来一些安全上的隐患因为你可能有一些security的问题没有被及时修复然后下面还有两个数据其实就是说目前其实有超过91%的这些项目它可能包含这些在过去两年没有任何的更新的这些代码这个其实可以看到在这些企业里面可能对这些不合规软件包括不安全的软件的一个检索和及时更新会是一个非常大的一个纲蓄然后再看到我们为什么需要软件供应率安全我可以再做一个类比如果回到20年前回到20年前或者10年前我们移动互联网包括这个web钢信息的时候对吧我们左边可能是需要一个SSL certificate需要一个证书去证明你作为用户去访问的一个网站它是安全的它是可信的对吧为什么呢因为这个证书上它为用户提供了它的一个透明的来源就是告诉你这个网站是由谁开发的然后它是有售信的机构进行了audit然后给你签发了一个这种say的证书来证明你的这个网站是售信的大家如果去打开一个网站然后在左上角去点那个一个小锁的icon你会发现它里面会包含这些certificate的信息也证明你的这个网站是来自于一个售信的企业提供的服务那这样的话你就敢去看这个网站包括在这个网站上去进行消费对吧然后如果这个网站是一个http的就是它没有一个一个真实的可信的证书那它可能会在下面有一个not secure的icon那你可能知道哎 这个网站我不能去用那类比到我们现在的这个image类比到我们现在这个原生的时代那我们的这个image你怎么去确保你在dogahub在github上去拉到这个image它是安全的其实这个很难去判断对吧因为你单纯去看这个image的tag你或者是看它的digest你可能没法去判断这个image就是安全的虽然可能dogahub它现在最近提供了一些安全扫描但它可能除了这个漏洞的扫描分析以外它可能还有其他的问题比如说它可能没法去把很有log4j的这个image扫描出来所以像现在呢就有很多的这个image它会去要求image的这个开发者去带上一些其他的信息比如说像signaturesignature呢其实主要就是证明你的镜像你的软件它是可信的然后它是真实的它是来自于一个受信的机构开发出来的然后第二个呢sbonsbon这个东西可能获得获少听说过它就是软件的物料成分表就好比我们在食品上看到的一个食品加工成分表一样你有哪些有哪些依赖构成你的软件其实在这个sbon里面都能看得到然后还有像这个venerability scanning report它可能你可以用像trivi或者是这些其他的扫描工具去扫描你的软件证明你的软件是没有安全漏洞的然后把你的这个报告给附着到你的image上面然后一并分发到这个容器镜像仓库来证明你的软件是没有漏洞的然后还有像这个provenance attestation像现在业内比较流行的provenance attestation的方案in total大家可能或者说或少听过它是证明你的软件的来源是真实的然后是它是怎么样被生产出来的最后呢是这个image lifecycle metadata就证明你的软件是实时的最新的没有过期的一些景象那接下来呢我们来去看一看如果我们要去提升软件规律安全我们到底要怎么做follows 怎么样的一个practice或者是一个实践来提升我们的软件规律安全我觉得可以从两个方面去介绍首先业内有两个比较流行的framework首先 第一个是这个微软在去年开源的一个叫s2c2f软件规律安全消费者框架它是面向最终用户的所以如果是你们公司是一个这种软件规律链的一个主要是用户的话你可以去看一看左边这个然后右边这个是selsa framework它目前是在OpenSSF这两个framwork其实都是在OpenSSF开源安全软件基金会这个底下去浮化然后selsa它就强调从软件的生产来源 构建 打包和最终的到消费者这边去部署它是强调一个全生命周期的一个安全的理念然后呢它也有很多这样的实践去提供给不仅仅是消费者还包括这个组织然后呢我们今天讲的另外一个项目Kirvano也已经全面的去用到了selsa来确保他们的供应链安全然后我接下来会把话筒给Kirvano的每天的书题来介绍介绍一下好 谢谢凡曼这里我们讲到这个selsa framework在Kirvano这个project里也是实现了一个n to n的一个包裹从我们看到这个sourced controlledKirvano要求contributor来进行dco sign off然后在整个构建build和release的环节Kirvano是通过get up action来进行Kirvano进向的签名然后同时呢在release的过程中在我们build image的过程中也会generate providenceattached to这个Kirvano的image那么在最后packaging的环节deliver到end user的这个手里的之前呢Kirvano也通过get upopen ssfscore card然后来进行一个对dependency进行一个periodic scan来保证我们的dependency永远是没有CVE漏洞的然后定期我们会收到一些dependency的报告然后会提前我们及时的去bump这个dependency的version那么我们这里看到其实在CNCF的整个生态系统中关于security提供的这个工具还是非常的丰富的总有一款总有一款适合你包括我们已经毕业的项目open policy agent针对covernities他们有Opac gatekeeper然后包括这里的Falco它进行round time scanin total当然还有我们今天两个重点项目Kirvano and notation这里我们在着重介绍Kirvano and notation之前呢我想给大家来进行一个快速的分类在这个整个开源环节中如何对你的供应链进行这个安全扫描当然最经典的应用场景就是对镜像进行签名以及验证这里我们提供的项目呢有notation cosine然后呢就是这个OCA artifact distribution你可以用这个or as cranelike inspect in the image这个镜像那么谈到Kirvano我们知道它是一个policymanagement也就是covernities的策略引擎当然还有刚刚提到的open policy还有对你进行round time security scanFalco和vulnerability scanning这里我们可以用到trivy还有一些其他的工具所以说整个开源系统的对于这个secure你的software supply chain是这个工具提供的是非常丰富的那么接下来我们重点看一下今天两个明星项目notation和covernal我们来了解一下什么是covernal我作为一名covernal的开发者对它还是比较熟悉的它首先是covernities原生的策略引擎也就是说比如说你熟悉covernitiesnative resource那么对于covernal它自己manage resource包括covernalthe cluster policy或者是policy report它都是covernitiesthe CRD来进行你可以对它进行一个无缝的转换就是当你在定义这些resource的时候你可以沿用covernities的风格那么你可以看到covernal其实现在的这个社区也是在蓬勃发展我们拥有超过2300名的slack members然后在我们的policy pool里面有将近300个sample policy这样我们的end user就可以来就相当于整个cool还是非常丰富你可以找到任何你需要的这个example那么covernal主要的use case是帮你eliminate misconfiguration也就是对于那些配置来进行either validation或者是对这些配置进行一些补定我们所说的patch or mutation当然基于这个policyapplication的结果它可以来对你的encommentadmission requesteither进行block或者是audit的一个动作block的意思就是当你在对covernities发送一个请求你想要部署一个pod那么在这个时候如果它validcovernal policy的时候我们就可以帮你来拒绝这个pod的申请如果你需要一个self control的话那么我们可以对它产生一个报告然后让你对它有一个历史的可以查找的记录那么我们来简单看一下covernal的整个architecture它最经典的use case就是作为admission controller也就是covernal利用了covernities的这个webhook来对你的encomment request做mutation validation然后在这个基础之上covernal还可以对你existing就是已经存在的resource进行background scanning然后生成刚刚我说的policy report然后对你的所有的安全漏洞和你deployed在cluster上的一些policy进行一个periodic的background scanning另外还有如果你不想把covernal跑在你的cluster里我们还有cli可以让你对你的static file进行一个validation除此之外mutation也是一个generation也是一个非常经典的covernal的功能它就是在mutation和validation的基础之外它可以帮你生成covernities的resource这里包括CR就是custom resource所以整个covernal是covernities native的一个策略引擎好 那我们接下来看一下这个covernal policy到底是长什么样子它作为一个covernities CR你一个policy里面可以包括一到多个root也就是准则规则在你每一个root里面它可以进行matching resource的一个声明比如说你这条root要apply到你的pod上或者是pod controllerlike deploymentstiffle setand other resources所以这里match and exclude是用来帮你做出选择来表明这个root到底apply在什么样的资源上那么最重要的就是这个root body的环节我们支持validate, mutategenerate 还有进向的验证这里你可以从四个里面选择任意一个来define on这个root如果你需要另外一种another root typeyou can define in a different rootwithin the same policyor different policies对,那接下来你看到是一个cavernal verified image的一个policy这里你看到它重新就reusedcubernetes metadatalike api version and kindhere the kindnesscluster policy就是define这个资源的类型那么metadata就可以给它一个名字因为这里cluster policy是classified resource所以你不需要给它name space那么在接下来的spec环节你可以对整个policy进行一个声明也就是比如说你看到第一个validation failure action它是可以来控制你的request你到底是想要block或者是对它进行一个soft control也就是allow它并且对它产生一个生成policy report然后接下来的两个settingwebpack timeout和failure policy它是可以直接reflect到webpack configuration也就是cubernetes的resource的配置上的那么接下来我们看到的rubbody第一个首先你要给它一个名字然后接下来就是我说的你需要选择你这个入到底是applied到什么样的resource这里你可以看到Cavernal这一条入是我默认它就是我define它applied到pod那么我对pod的image来进行签名的验证这里我definetype是notary也就是说我的image是用notation来进行签名的那么这里我给它一个certificate然后希望Cavernal来用certificate来帮我验证签名来保证整个供应链的安全所以这就是一个很简单的Cavernal policy我们会在lifetime看到整个policy是怎么样applied那么接下来我们让feminine来介绍一下notation这个project刚刚其实Cavernal它做的一个主要的事情是做verify它可以verify任何的东西不局限于signature它可以verifyspawnvulnerability scanning report就是这个漏洞扫描报告还有任何的这些跟软件供应�安全相关的这些制品那接下来我讲的这个notation是做进向或者说做这个制品签名的一个方案它也是在CNCFincubating的一个开源项目然后呢它提供了像这个开放的标准我们有一个spec然后我们是基于这个spec去实现它的cli这个cli的工具最右边我有放了一个截图它有些比较好用的应用的这个cli的tour然后它还提供library如果你要你需要在企业内部去构建自己的cli tour或者是跟也有的这个pqi的设施去做集成其实也可以利用我们的library去做一些事情那其实notation它的功能非常的简单也非常的直观它主要就是做签名和验证签名它就是去生成一个这种标准的signature的文件它是符合oci这个oci规范的然后也就意味着你可以把你的signature和你的image一起存在这些oci registry比如说像harbor或者说像adrid container registryarliwing的或者任何这种oci符合oci规范的registry你可以一起去把你的镜像和你的signature去做分发然后它还提供验证的功能验证的话它可以去根据它的一套trust policy 去定义来自于哪个软件镜像仓库的image可以被验证或者说来自某一家公司比如说微软生产的软件它能够被部署那另外一个公司生产的软件它不能被部署或者说它可以指定这样的一个软件的来源在你的trust policy 里面然后最后呢notation 它还可以去跟一些流行的ci cd 的方案去做基层然后我们其实也已经做了一些深度的基层像github actions我稍后会去demo一个github actions的sign和verified demo结合kivernal然后也会去演示在flux cd当前比较流行的一个github's 的方案然后目前notation 这个项目是有来自主要的一个贡献者是来自于AWS 和微软微软Adrian两个大的云共银商虽然在商业上在竞争但是我们在community在一起合作在一起去打磨这个kivernal 项目同时它又回馈到我们各自的云上的商业产品里面然后还有一些kivernal 项目像content-dharborvonifydarker 这些其实都在用notation 去做签名和验证的事情好那我们的一个前期的介绍就看完了我们就直接到demo吧因为可能前面讲了这么多概念我觉得到demo可能更加直观一点所以这个地方我们会更多focus在一些细节上面去吧一个demo给大家演示完整就从在k8s 上做进向的签名生成这个signature然后再把这个signaturepush到一个registry我们是用githubcontent registry作为一个实力然后最后呢在下面这个这一部分呢会把这个push过去的这个image还包括它的signature它的这个进向签名做一个验证然后确保我们只有被一个合规的有一个合规的signature的进向可以被部署如果它没有被签名那这个进向就不能被部署一个很简单但是有很直观的demo然后我们签名在下面这部分签名这个环节会用github actions去完成然后下面这个验证在k8s的部署前期这个验证会用flux cd 去完成有两套不同的cicd或者是github's 方案我们现在切到我们的demo首先我这里是有一个这个workflow的文件这个是直接调用了我们在github上去发布的一个notation的github actions去做sign and verify的操作那这里我可能暂时先不过细节我回头再过来看但核心就是我们可以用我们的notation的github actions去做sign and verify好 那我这里随便改点东西来push到我们的远端因为我们的这个流程是把它设置成on push来trigger我们的workflow那我们就改一下这个readme吧我就把这个加一下就随便改个地方然后保存然后把它push上去那我们刚刚把我们的这个workflow给做了一点点小的调整因为我们的这个trigger的event是设置成了一个有一个push然后我们就会自动的去trigger这个github actionsworkflow来做进行的签名签名完成以后它就会把这个sign过的imagepush到github's container registry那我们稍后在github's container registry就能够看到我们这个被sign过的这个image包括signature被push到这个registry我们现在到这个我们对应的github's散户去看大家可以稍等几秒钟在等待这个剑系我可以在顺便说一下就除了在CICD当中去用notation其实notation是如果在terminal在中端里面其实也可以直接去用刚刚我有一个地方演示它的这个cli对吧其实如果在在这个terminal里面Sign这个image它的流程也非常简单首先你可以用notation key add去添加一个你自己的一个key比如存在你自己的一个key vote里面的一个key来去做一个sign的前置条件然后完成以后就可以去做signing然后这里其实我们已经把这个sign的workflow给出发了就可以看到它其实主要就做三个事情第一个事情是把notation在github'saction里面装上第二个事情是去登录这个github然后因为我这边是用的一个addukey vote我的key是存在了我的key和set是存在了我的addukey vote但其实你也可以用其他任何的这个key vote的solution比如说hashcorp voteAWS KMS或者是其他的一些开放的这种vote去存你的key和set然后第三件事情就是去用我们的这个key vote当中的这个key去做前名这里其实如果刚刚这里前名过完过后呢它就应该是会去生成一个这种signature然后会把这个signaturepush到我们对应的github content registry我们可以回到我们的github content registry然后去看这个signature是不是有一个叫sign的一个image的tag被push上去然后还有一个它关联的一个signature也被push上去OK 这个是一分钟前我们push的我们有一个sign的这个image这个image呢如果我们去用notation的命令比如说像notation list去看它的这个结构的话我们可以看到它其实因为有reverse API的一个指示它是能够去看到这个image它依赖的或者说它附带的这个signature的我们用这个notation的命令来演示一下notation list加上sign的tag我们就可以看到是底下带了一个signature的OK 那既然我们已经有了这个sign过的image那我们就可以去在CICD当中去验证我们这个sign过的image看它是不是能够被顺利部署我们还会去验证一个没有被sign过的看它能不能够被keyvono给中断它的一个请求那我们接下来把时间交给舒婷好的 我们刚刚看我们实时generate了一个sign的image tag那我们这里已经设置好了cavernal policy我们来简单看一下这个policy是干什么的OK 这里是我们刚刚看到一个cavernal policy的defination我依旧matched support这里我给它attached这个certificate是用来做image verification那么这里我选择image reference也就是刚刚我们pushed那个image image这里the vault card也就是说我让这个cavernal policy可以来verify任意的image tag或image digest所以在 within this policy in place的情况下呢我们就来简单的做一个测试就是我要再通过kubectl来创建一个pod然后run的是我们刚刚sign的这个image这里我用了一个siversite端的dry run来保证它不会影响之后的demo如果我们执行这条命令的话你可以看到我这个pod被成功的create了那如果我们把这个image tag变成unsign的呢再发送这个请求你就可以看到cavernal policyblock了这个pod creationinvade你的这个unsign image并没有signature associated with it所以cavernal对它进行了一个block这里我设置的也就是刚刚说的failure action是enforce也就是说我要block我的pod creation那么flux在这里有朋友可能问了flux到底在哪里然后我给大家展示一下其实我flux已经装到了我的local cluster上给我一分钟flux system对 我的flux controller已经inplace了并且我也create了一些flux的这个git repository和customization这里你可以看到我有cavernal controllercavernal and cavernal policies也就是说我整个cavernal的环节都是通过flux的git ops来设置的那么我现在对我远程的flux来think的report进行一些改动比如说我们把verify image的sert变成一个invalid这是我们刚刚在cluster里看到的policy我来comment一下这个sert然后我这里有一个invalid sert我们把它push到远端的report上我远端的report是通过flux来一直watch integrate flux的brunch那么我对report进行一些改动之后我期望的是flux看到了这些改动并且把这些改动think到我的cluster里那么刚刚看到的policy我们有是一个合法的sert那么在它flux进行think完之后它会变成我们刚刚push的一个invalid sert那么我们再create刚刚signed或者unsigned image的时候我会期望cavernal给我report这个image这个certificate是不可以用来verify这个image因为它不是签名的时候的certificate所以它可能需要一点时间我们来看一下等这个flux把它进行一个synchronization的操作好 我们现在看到我刚刚做了一个这个coop-quaddleget-sapo-dash-o-yaml的一个动作然后刚刚一长串的它已经被我们改成了这个invalid-sert那么在这个时候我如果再创建刚刚的那个signed image这里是unsignedlet's quickly change that如果我在跑这个signed image那么发送这个请求之后我会看到cavernal这里说你的这个certificate并不match你在签名的时候的这个certificate所以我即便用的是signed image我也会对它进行block同理可以apply到这个unsigned image tag所以这就是我们今天的一个demo用get-have-action来进行进向的签名那么用cavernal结合flux来进行一个验证它的好处就是在于比如说你有e到ng cluster你对你的report进行改动它可以sync到你所有的downstream cluster里这里不需要你在手动配置那么它一切都是automated所以为我们就是进行了一个一个improvement可以这么说来减少一些人为的工作量好那大概这就是我们今天的所有内容如果大家有问题或者是对我们的项目感兴趣这里是cavernal notation和oras的website大家可以去free browser然后这个是我们的vchat如果微信如果大家有一些有感兴趣的use case或者是任何你想跟我们分享的story都可以加入这个群人然后让我们来了解你的故事接下来我们到了Q&A的环节不知道有没有小伙伴有问题OK好 谢谢我有一个问题想请教一下就是我看到了一个公司要的一个配置在我的cavernal的CRD里面这个pod resource里面我想问一下因为我的公司要可能是做rotation的我想问一下如果我将来做rotation的话那我的公要是每一个pod的policy我都要重新写一遍重新把publickey去提供这是一个很好的问题我们今天demo的是embed到这个class of policy里那么你可以通过一些其他的图来把你的这些certificate写到cubernetes to secretly或者是config map里cavernal可以进行API lookup然后调去它的certificate如果你不想进行这个操作的话cavernal也提供了这个external的API call你可以远程去lookup你的certificate比如说你存在Azure keyword我们都可以进Pose call或者是get来进行certificate lookup谢谢第二个问题是cavernal和OPI的区别是什么因为我感到它的价格都是一模一样的这是一个很经典的问题我们经常会遇到这个问题因为OPI它作为一个general的policy就是策略引擎它是可以跑在部署在cubernetes之外的任何环境里的目前cavernal还是对于cubernetes云原生所以它是default是装在cubernetes的cluster上即便我们有Cli有这个playground可以让用户来进行一个off clustered一个tryout但是它的最经典的use case还是跑在cluster里另外cavernal和OPIgetkeeper的主要区别是getkeeper它可以用于validation, mutationcavernal是一个可以用于validation, mutationcavernal在这个之外它有generation刚刚我说的你可以生存cubernetes的任何resource包括CR你可以对你的cluster进行scanning还有我们的image verification所以对于这两个的对比我只能说cavernal可能对于cubernetes的开发会更加全面一些谢谢OK我们大概时间到了如果还有问题的话就欢迎大家加入我们的vchat群或者是slack channel来进行交流今天大概就这样谢谢大家谢谢