近年来,互联网上安全事件频发,企业信息安全愈来愈受到重视,而IDC服务器安全又是纵深防护体系中的重要一环。保障IDC安全,经常使用的是基于主机型入侵检测系统Host-based Intrusion Detection System,即HIDS。在HIDS面对几十万台甚至上百万台规模的IDC环境时,系统架构该如何设计呢?复杂的服务器环境,网络环境,巨大的数据量给咱们带来了哪些技术挑战呢?html
对于HIDS产品,咱们安所有门的产品经理提出了如下需求:linux
首先,服务器业务进程优先级高,HIDS Agent进程本身能够终止,但不能影响宿主机的主要业务,这是第一要点,那么业务须要具有熔断功能,并具有自我恢复能力。golang
其次,进程保活、维持心跳、实时获取新指令能力,百万台Agent的全量控制时间必定要短。举个极端的例子,当Agent出现紧急状况,须要全量中止时,那么全量中止的命令下发,须要在1-2分钟内完成,甚至30秒、20秒内完成。这些将会是很大的技术挑战。算法
还有对配置动态更新,日志级别控制,细分精确控制到每一个Agent上的每一个HIDS子进程,能自由地控制每一个进程的启停,每一个Agent的参数,也能精确的感知每台Agent的上线、下线状况。数据库
同时,Agent自己是安全Agent,安全的因素也要考虑进去,包括通讯通道的安全性,配置管理的安全性等等。编程
最后,服务端也要有一致性保障、可用性保障,对于大量Agent的管理,必须能实现任务分摊,并行处理任务,且保证数据的一致性。考虑到公司规模不断地扩大,业务不断地增多,特别是美团和大众点评合并后,面对的各类操做系统问题,产品还要具有良好的兼容性、可维护性等。安全
总结下来,产品架构要符合如下特性:服务器
在列出产品须要实现的功能点、技术点后,再来分析下遇到的技术挑战,包括不限于如下几点:网络
咱们能够看到,技术难点几乎都是服务器到达必定量级带来的,对于大量的服务,集群分布式是业界常见的解决方案。数据结构
对于管理Agent的服务端来讲,要实现高可用、容灾设计,那么必定要作多机房部署,就必定会遇到数据一致性问题。那么数据的存储,就要考虑分布式存储组件。 分布式数据存储中,存在一个定理叫CAP定理
:
关于CAP定理
,分为如下三点:
根据定理,分布式系统只能知足三项中的两项而不可能知足所有三项。理解CAP定理
的最简单方式是想象两个节点分处分区两侧。容许至少一个节点更新状态会致使数据不一致,即丧失了Consistency。若是为了保证数据一致性,将分区一侧的节点设置为不可用,那么又丧失了Availability。除非两个节点能够互相通讯,才能既保证Consistency又保证Availability,这又会致使丧失Partition Tolerance。
参见:CAP Theorem
为了容灾上设计,集群节点的部署,会选择的异地多机房,因此 「Partition tolerance」是不可能避免的。那么可选的是 AP
与 CP
。
在HIDS集群的场景里,各个Agent对集群持续可用性没有很是强的要求,在短暂时间内,是能够出现异常,出现没法通信的状况。但最终状态必需要一致,不能存在集群下发关停指令,而出现个别Agent不遵从集群控制的状况出现。因此,咱们须要一个知足 CP
的产品。
在开源社区中,比较出名的几款知足CP的产品,好比etcd、ZooKeeper、Consul等。咱们须要根据几款产品的特色,根据咱们需求来选择符合咱们需求的产品。
插一句,网上不少人说Consul是AP产品,这是个错误的描述。既然Consul支持分布式部署,那么必定会出现「网络分区」的问题, 那么必定要支持「Partition tolerance」。另外,在consul的官网上本身也提到了这点 Consul uses a CP architecture, favoring consistency over availability.
Consul is opinionated in its usage while Serf is a more flexible and general purpose tool. In CAP terms, Consul uses a CP architecture, favoring consistency over availability. Serf is an AP system and sacrifices consistency for availability. This means Consul cannot operate if the central servers cannot form a quorum while Serf will continue to function under almost all circumstances.
etcd、ZooKeeper、Consul对比
借用etcd官网上etcd与ZooKeeper和Consul的比较图。
在咱们HIDS Agent的需求中,除了基本的服务发现
、配置同步
、配置多版本控制
、变动通知
等基本需求外,咱们还有基于产品安全性上的考虑,好比传输通道加密
、用户权限控制
、角色管理
、基于Key的权限设定
等,这点 etcd
比较符合咱们要求。不少大型公司都在使用,好比Kubernetes
、AWS
、OpenStack
、Azure
、Google Cloud
、Huawei Cloud
等,而且etcd
的社区支持很是好。基于这几点因素,咱们选择etcd
做为HIDS的分布式集群管理。
对于etcd在项目中的应用,咱们分别使用不一样的API接口实现对应的业务需求,按照业务划分以下:
N/2+1
以上,才会选作Leader,来保证数据一致性。另一个网络分区的Member节点将无主。前缀按角色设定:
/hids/server/config/{hostname}/master
。/hids/agent/master/{hostname}
。/hids/agent/config/{hostname}/plugin/ID/conf_name
。Server Watch /hids/server/config/{hostname}/master
,实现Agent主机上线的瞬间感知。Agent Watch /hids/server/config/{hostname}/
来获取配置变动,任务下发。Agent注册的Key带有Lease Id,并启用keepalive,下线后瞬间感知。 (异常下线,会有1/3的keepalive时间延迟)
关于Key的权限,根据不一样前缀,设定不一样Role权限。赋值给不一样的User,来实现对Key的权限控制。
在etcd节点容灾考虑,考虑DNS故障时,节点会选择部署在多个城市,多个机房,以咱们服务器机房选择来看,在大部分机房都有一个节点,综合承载需求,咱们选择了N台服务器部署在个别重要机房,来知足负载、容灾需求。但对于etcd这种分布式一致性强的组件来讲,每一个写操做都须要N/2-1
的节点确认变动,才会将写请求写入数据库中,再同步到各个节点,那么意味着节点越多,须要确认的网络请求越多,耗时越多,反而会影响集群节点性能。这点,咱们后续将提高单个服务器性能,以及牺牲部分容灾性来提高集群处理速度。
客户端填写的IP列表,包含域名、IP。IP用来规避DNS故障,域名用来作Member节点更新。最好不要使用Discover方案,避免对内网DNS服务器产生较大压力。
同时,在配置etcd节点的地址时,也要考虑到内网DNS故障的场景,地址填写会混合IP、域名两种形式。
咱们在设计产品架构时,为了安全性,开启了TLS证书认证,当节点变动时,证书的生成也一样要考虑到上面两种方案的影响,证书里须要包含固定IP,以及DNS域名范围的两种格式。
etcd Cluster节点扩容
节点扩容,官方手册上也有完整的方案,etcd的Client里实现了健康检测与故障迁移,能自动的迁移到节点IP列表中的其余可用IP。也能定时更新etcd Node List,对于etcd Cluster的集群节点变动来讲,不存在问题。须要咱们注意的是,TLS证书的兼容。
集群核心组件高可用,全部Agent、Server都依赖集群,均可以无缝扩展,且不影响整个集群的稳定性。即便Server所有宕机,也不影响全部Agent的继续工做。
在之后Server版本升级时,Agent不会中断,也不会带来雪崩式的影响。etcd集群能够作到单节点升级,一直到整个集群升级,各个组件全都解耦。
考虑到公司服务器量大,业务复杂,需求环境多变,操做系统可能包括各类Linux以及Windows等。为了保证系统的兼容性,咱们选择了Golang做为开发语言,它具有如下特色:
HIDS产品研发完成后,部署的服务都运行着各类业务的服务器,业务的重要性排在第一,咱们产品的功能排在后面。为此,肯定了几个产品的大方向:
篇幅限制,仅讨论框架设计
、熔断限流
、监控告警
、自我恢复
以及产品实现上的主进程
与进程监控
。
如上图,在框架的设计上,封装经常使用类库,抽象化定义Interface
,剥离etcd Client
,全局化Logger
,抽象化App的启动、退出方法。使得各模块
(如下简称App
)只须要实现本身的业务便可,能够方便快捷的进行逻辑编写,无需关心底层实现、配置来源、重试次数、熔断方案等等。
沙箱隔离
考虑到子进程不能无限的增加下去,那么必然有一个进程包含多个模块的功能,各App
之间既能使用公用底层组件(Logger
、etcd Client
等),又能让彼此之间互不影响,这里进行了沙箱化
处理,各个属性对象仅在各App
的sandbox
里生效。一样能实现了App
进程的性能熔断
,中止全部的业务逻辑功能,但又能具备基本的自我恢复
功能。
IConfig
对各App的配置抽象化处理,实现IConfig的共有方法接口,用于对配置的函数调用,好比Check
的检测方法,检测配置合法性,检测配置的最大值、最小值范围,规避使用人员配置不在合理范围内的状况,从而避免带来的风险。
框架底层用Reflect
来处理JSON配置,解析读取填写的配置项,跟Config对象对比,填充到对应Struct
的属性上,容许JSON配置里只填写变化的配置,没填写的配置项,则使用Config
对应Struct
的默认配置。便于灵活处理配置信息。
type IConfig interface { Check() error //检测配置合法性 } func ConfigLoad(confByte []byte, config IConfig) (IConfig, error) { ... //反射生成临时的IConfig var confTmp IConfig confTmp = reflect.New(reflect.ValueOf(config).Elem().Type()).Interface().(IConfig) ... //反射 confTmp 的属性 confTmpReflect := reflect.TypeOf(confTmp).Elem() confTmpReflectV := reflect.ValueOf(confTmp).Elem() //反射config IConfig configReflect := reflect.TypeOf(config).Elem() configReflectV := reflect.ValueOf(config).Elem() ... for i = 0; i < num; i++ { //遍历处理每一个Field envStructTmp := configReflect.Field(i) //根据配置中的项,来覆盖默认值 if envStructTmp.Type == confStructTmp.Type { configReflectV.FieldByName(envStructTmp.Name).Set(confTmpReflectV.Field(i))
Timer、Clock调度
在业务数据产生时,不少地方须要记录时间,时间的获取也会产生不少系统调用。尤为是在每秒钟产生成千上万个事件,这些事件都须要调用获取时间
接口,进行clock_gettime
等系统调用,会大大增长系统CPU负载。 而不少事件产生时间的准确性要求不高,精确到秒,或者几百个毫秒便可,那么框架里实现了一个颗粒度符合需求的(好比100ms、200ms、或者1s等)间隔时间更新的时钟,即知足事件对时间的需求,又减小了系统调用。
一样,在有些Ticker
场景中,Ticker
的间隔颗粒要求不高时,也能够合并成一个Ticker
,减小对CPU时钟的调用。
Catcher
在多协程场景下,会用到不少协程来处理程序,对于个别协程的panic错误,上层线程要有一个良好的捕获机制,能将协程错误抛出去,并能恢复运行,不要让进程崩溃退出,提升程序的稳定性。
抽象接口
框架底层抽象化封装Sandbox的Init、Run、Shutdown接口,规范各App的对外接口,让App的初始化、运行、中止等操做都标准化。App的模块业务逻辑,不须要关注PID文件管理,不关注与集群通信,不关心与父进程通信等通用操做,只须要实现本身的业务逻辑便可。App与框架的统一控制,采用Context包以及Sync.Cond等条件锁做为同步控制条件,来同步App与框架的生命周期,同步多协程之间同步,并实现App的安全退出,保证数据不丢失。
网络IO
磁盘IO
程序运行日志,对日志级别划分,参考 /usr/include/sys/syslog.h
:
在代码编写时,根据需求选用级别。级别越低日志量越大,重要程度越低,越不须要发送至日志中心,写入本地磁盘。那么在异常状况排查时,方便参考。
日志文件大小控制,分2个文件,每一个文件不超过固定大小,好比20M
、50M
等。而且,对两个文件进行来回写,避免日志写满磁盘的状况。
IRetry
为了增强Agent的鲁棒性,不能由于某些RPC动做失败后致使总体功能不可用,通常会有重试功能。Agent跟etcd Cluster也是TCP长链接(HTTP2),当节点重启更换或网络卡顿等异常时,Agent会重连,那么重连的频率控制,不能是死循环般的重试。假设服务器内网交换机因内网流量较大产生抖动,触发了Agent重连机制,不断的重连又加剧了交换机的负担,形成雪崩效应,这种设计必需要避免。 在每次重试后,须要作必定的回退机制,常见的指数级回退
,好比以下设计,在规避雪崩场景下,又能保障Agent的鲁棒性,设定最大重试间隔,也避免了Agent失控的问题。
//网络库重试Interface type INetRetry interface { //开始链接函数 Connect() error String() string //获取最大重试次数 GetMaxRetry() uint ... } // 底层实现 func (this *Context) Retry(netRetry INetRetry) error { ... maxRetries = netRetry.GetMaxRetry() //最大重试次数 hashMod = netRetry.GetHashMod() for { if c.shutting { return errors.New("c.shutting is true...") } if maxRetries > 0 && retries >= maxRetries { c.logger.Debug("Abandoning %s after %d retries.", netRetry.String(), retries) return errors.New("超过最大重试次数") } ... if e := netRetry.Connect(); e != nil { delay = 1 << retries if delay == 0 { delay = 1 } delay = delay * hashInterval ... c.logger.Emerg("Trying %s after %d seconds , retries:%d,error:%v", netRetry.String(), delay, retries, e) time.Sleep(time.Second * time.Duration(delay)) } ... }
事件拆分
百万台IDC规模的Agent部署,在任务执行、集群通信或对宿主机产生资源影响时,务必要错峰进行,根据每台主机的惟一特征取模,拆分执行,避免形成雪崩效应。
古时候,行军打仗时,提倡「兵马未动,粮草先行」,无疑是冷兵器时代决定胜负走向的重要因素。作产品也是,尤为是大型产品,要对本身运行情况有详细的掌控,作好监控告警,才能确保产品的成功。
对于etcd集群的监控,组件自己提供了Metrics
数据输出接口,官方推荐了Prometheus来采集数据,使用Grafana来作聚合计算、图标绘制,咱们作了Alert
的接口开发,对接了公司的告警系统,实现IM、短信、电话告警。
Agent数量感知,依赖Watch数字,实时准确感知。
以下图,来自产品刚开始灰度时的某一时刻截图,Active Streams(即etcd Watch的Key数量)即为对应Agent数量,每次灰度的产品数量。由于该操做,是Agent直接与集群通信,而且每一个Agent只Watch一个Key。且集群数据具有惟一性、一致性,远比心跳日志的处理要准确的多。
etcd集群Members之间健康情况监控
用于监控管理etcd集群的情况,包括Member
节点之间数据同步,Leader选举次数,投票发起次数,各节点的内存申请情况,GC状况等,对集群的健康情况作全面掌控。
程序运行状态监控告警
全量监控Aagent的资源占用状况,统计天天使用最大CPU内存的主机Agent,肯定问题的影响范围,及时作策略调整,避免影响到业务服务的运行。并在后续版本上逐步作调整优化。
百万台服务器,日志告警量很是大,这个级别的告警信息的筛选、聚合是必不可少的。减小无用告警,让研发运维人员疲于奔命,也避免无用告警致使研发人员放松了警戒,前期忽略个例告警,先解决主要矛盾。
数据采集告警
Ticker
到来,决定是否恢复运行。在前面的配置管理
中的etcd Key
设计里,已经细分到每一个主机(即每一个Agent)一个Key。那么,服务端的管理,只要区分该主机所属机房、环境、群组、产品线便可,那么,咱们的管理Agent的颗粒度能够精确到每一个主机,也就是支持任意纬度的灰度发布管理与命令下发。
组件名为 log_agent
,是公司内部统一日志上报组件,会部署在每一台VM、Docker上。主机上全部业务都可将日志发送至该组件。 log_agent
会将日志上报到Kafka集群中,通过处理后,落入Hive集群中。(细节不在本篇讨论范围)
主进程实现跟etcd集群通讯,管理整个Agent的配置下发与命令下发;管理各个子模块的启动与中止;管理各个子模块的CPU、内存占用状况,对资源超标进行进行熔断处理,让出资源,保证业务进程的运行。
插件化管理其余模块,多进程模式,便于提升产品灵活性,可更简便的更新启动子模块,不会由于个别模块插件的功能、BUG致使整个Agent崩溃。
方案选择
咱们在研发这产品时,作了不少关于linux进程建立监控
的调研,不限于安全产品
,大约有下面三种技术方案:
方案 | Docker兼容性 | 开发难度 | 数据准确性 | 系统侵入性 | |
---|---|---|---|---|---|
cn_proc | 不支持Docker | 通常 | 存在内核拿到的PID,在/proc/ 下丢失的状况 |
无 | |
Audit | 不支持Docker | 通常 | 同cn_proc | 弱,但依赖Auditd | |
Hook | 定制 | 高 | 精确 | 强 |
对于公司的全部服务器来讲,几十万台都是已经在运行的服务器,新上的任何产品,都尽可能避免对服务器有影响,更况且是全部服务器都要部署的Agent。 意味着咱们在选择系统侵入性
来讲,优先选择最小侵入性
的方案。
对于Netlink
的方案原理,能够参考这张图(来自:kernel-proc-connector-and-containers)
系统侵入性比较
cn_proc
跟Autid
在「系统侵入性」和「数据准确性」来讲,cn_proc
方案更好,并且使用CPU、内存等资源状况,更可控。Hook
的方案,对系统侵入性过高了,尤为是这种最底层作HOOK syscall的作法,万一测试不充分,在特定环境下,有必定的几率会出现Bug,而在百万IDC的规模下,这将成为大面积事件,可能会形成重大事故。兼容性上比较
cn_proc
不兼容Docker,这个能够在宿主机上部署来解决。Hook
的方案,须要针对每种Linux的发行版作定制,维护成本较高,且不符合长远目标(收购外部公司时遇到各式各样操做系统问题)数据准确性比较
在大量PID建立的场景,好比Docker的宿主机上,内核返回PID时,由于PID返回很是多很是快,不少进程启动后,马上消失了,另一个线程都还没去读取/proc/
,进程都丢失了,场景常出如今Bash执行某些命令。
最终,咱们选择Linux Kernel Netlink接口的cn_proc指令
做为咱们进程监控方案,借助对Bash命令的收集,做为该方案的补充。固然,仍然存在丢数据的状况,但咱们为了系统稳定性,产品侵入性低等业务需求,牺牲了一些安全性上的保障。
对于Docker的场景,采用宿主机运行,捕获数据,关联到Docker容器,上报到日志中心的作法来实现。
遇到的问题
内核Netlink发送数据卡住
内核返回数据太快,用户态ParseNetlinkMessage
解析读取太慢,致使用户态网络Buff占满,内核再也不发送数据给用户态,进程空闲。对于这个问题,咱们在用户态作了队列控制,确保解析时间的问题不会影响到内核发送数据。对于队列的长度,咱们作了定值限制,生产速度大于消费速度的话,能够丢弃一些数据,来保证业务正常运行,而且来控制进程的内存增加问题。
疑似“内存泄露”问题
在一台Docker的宿主机上,运行了50个Docker实例,每一个Docker都运行了复杂的业务场景,频繁的建立进程,在最初的产品实现上,启动时大约10M内存占用,一天后达到200M的状况。
通过咱们Debug分析发现,在ParseNetlinkMessage
处理内核发出的消息时,PID频繁建立带来内存频繁申请,对象频繁实例化,占用大量内存。同时,在Golang GC时,扫描、清理动做带来大量CPU消耗。在代码中,发现对于linux/connector.h里的struct cb_msg
、linux/cn_proc.h里的struct proc_event
结构体频繁建立,带来内存申请等问题,以及Golang的GC特性,内存申请后,不会在GC时马上归还操做系统,而是在后台任务里,逐渐的归还到操做系统,见:debug.FreeOSMemory
FreeOSMemory forces a garbage collection followed by an
attempt to return as much memory to the operating system
as possible. (Even if this is not called, the runtime gradually
returns memory to the operating system in a background task.)
但在这个业务场景里,大量频繁的建立PID,频繁的申请内存,建立对象,那么申请速度远远大于释放速度,天然内存就一直堆积。
从文档中能够看出,FreeOSMemory
的方法能够将内存归还给操做系统,但咱们并无采用这种方案,由于它治标不治本,无法解决内存频繁申请频繁建立的问题,也不能下降CPU使用率。
为了解决这个问题,咱们采用了sync.Pool
的内置对象池方式,来复用回收对象,避免对象频繁建立,减小内存占用状况,在针对几个频繁建立的对象作对象池化后,一样的测试环境,内存稳定控制在15M左右。
大量对象的复用,也减小了对象的数量,一样的,在Golang GC运行时,也减小了对象的扫描数量、回收数量,下降了CPU使用率。
在产品的研发过程当中,也遇到了一些问题,好比:
方法必定比困难多,但方法不是拍脑壳想出来的,必定要深刻探索问题的根本缘由,找到系统性的修复方法,具有高可用、高性能、监控告警、熔断限流等功能后,对于出现的问题,可以提早发现,将故障影响最小化,提早作处理。在应对产品运营过程当中遇到的各类问题时,逢山开路,遇水搭桥,均可以从容的应对。
通过咱们一年的努力,已经部署了除了个别特殊业务线以外的其余全部服务器,数量达几十万台,产品稳定运行。在数据完整性、准确性上,还有待提升,在精细化运营上,须要多作改进。
本篇更多的是研发角度上软件架构上的设计,关于安全事件分析、数据建模、运营策略等方面的经验和技巧,将来将会由其余同窗进行分享,敬请期待。
咱们在研发这款产品过程当中,也看到了网上开源了几款同类产品,也了解了他们的设计思路,发现不少产品都是把主要方向放在了单个模块的实现上,而忽略了产品架构上的重要性。
好比,有的产品使用了syscall hook
这种侵入性高的方案来保障数据完整性,使得对系统侵入性很是高,Hook代码的稳定性,也严重影响了操做系统内核的稳定。同时,Hook代码也缺乏了监控熔断的措施,在几十万服务器规模的场景下部署,潜在的风险可能让安所有门没法接受,甚至是致命的。
这种设计,可能在服务器量级小时,对于出现的问题多花点时间也能逐个进行维护,但应对几十万甚至上百万台服务器时,对维护成本、稳定性、监控熔断等都是很大的技术挑战。同时,在研发上,也很难实现产品的快速迭代,而这种方式带来的影响,几乎都会致使内核宕机之类致命问题。这种事故,使用服务器的业务方很难进行接受,势必会影响产品的研发速度、推动速度;影响同事(SRE运维等)对产品的信心,进而对后续产品的推动带来很大的阻力。
以上是笔者站在研发角度,从可用性、可靠性、可控性、监控熔断等角度作的架构设计与框架设计,分享的产品研发思路。
笔者认为大规模的服务器安全防御产品,首先须要考虑的是架构的稳定性、监控告警的实时性、熔断限流的准确性等因素,其次再考虑安全数据的完整性、检测方案的可靠性、检测模型的精确性等因素。
九层之台,起于累土。只有打好基础,才能指挥若定,决胜千里以外。
陈驰,美团点评技术专家,2017年加入美团,十年以上互联网产品研发经验,专一于分布式系统架构设计,目前主要从事安全防护产品研发工做。
美团安所有的大多数核心开发人员,拥有多年互联网以及安全领域实践经验,不少同窗参与过大型互联网公司的安全体系建设,其中也不乏全球化安全运营人才,具有百万级IDC规模攻防对抗的经验。安所有也不乏CVE“挖掘圣手”,有受邀在Black Hat等国际顶级会议发言的讲者,固然还有不少漂亮的运营妹子。
目前,美团安所有涉及的技术包括渗透测试、Web防御、二进制安全、内核安全、分布式开发、大数据分析、安全算法等等,同时还有全球合规与隐私保护等策略制定。咱们正在建设一套百万级IDC规模、数十万终端接入的移动办公网络自适应安全体系,这套体系构建于零信任架构之上,横跨多种云基础设施,包括网络层、虚拟化/容器层、Server 软件层(内核态/用户态)、语言虚拟机层(JVM/JS V8)、Web应用层、数据访问层等,并可以基于“大数据+机器学习”技术构建全自动的安全事件感知系统,努力打形成业界最前沿的内置式安全架构和纵深防护体系。
随着美团的高速发展,业务复杂度不断提高,安所有门面临更多的机遇和挑战。咱们但愿将更多表明业界最佳实践的安全项目落地,同时为更多的安全从业者提供一个广阔的发展平台,并提供更多在安全新兴领域不断探索的机会。
美团安所有正在招募Web&二进制攻防、后台&系统开发、机器学习&算法等各路小伙伴。若是你想加入咱们,欢迎简历请发至邮箱zhaoyan17@meituan.com
具体职位信息可参考这里:https://mp.weixin.qq.com/s/ynEq5LqQ2uBcEaHCu7Tsiw
美团安全应急响应中心MTSRC主页:security.meituan.com