Microsevices陷阱: 安全

自计算机普及开始,安全问题就成为困扰产业发展的阿喀琉斯之踵。如今,安全问题爆发所造成严重后果的并不鲜见,事实上每一个产品参与者都承担了安全缺陷所带来的风险和损失。因此业界必须重视安全,理解并持续加固IT设施。当采用微服务架构后,安全问题的解决将面临新一轮挑战,更高的成本投入也是显而易见。

1. 认证(Authentication)和授权(Authorization)

在应用级,安全的第一道锁就是认证和授权。认证的目的是确认用户提交的身份信息是否属实,例如系统登录验证,而能够唯一识别用户的信息通常被称作Principal。授权是指依据用户的Principal允许其从事某些操作的机制。一旦Principal被验证,系统就能根据相关信息(例如Hierarchy)对用户进行授权。但在微服务架构下,由于服务的相互隔离性,Principal的传递面临挑战,毕竟用户并不希望访问任何独立服务都输一遍密码——这是一个很直观的用户体验问题。

单点登录(SSO)

SSO是最常见的一种认证和授权方案。SAML、OpenID Connect是当前业界比较流行的SSO实现,但基本原理相差无几,本文将以SAML为例简单介绍SSO技术。

当Principal试图访问系统资源时,首先由身份提供者验证其密钥信息,如果验证通过,Principal将被引导至服务提供者,并且由后者决定是否向Principal授权相关资源。

在上述过程中,身份提供者可以是第三方系统。例如,Google提供的就是基于OpenID Connect的身份提供服务,对于其它企业应用来说,这种身份提供者有权链接至组织内部的目录服务(可以是LDAP或Active Directory,此类系统能够存放Principles),也可以由自身直接提供数据服务。基于SAML技术的有Okta,也提供链接至企业目录的服务。

SAML本身基于SOAP标准,其背后的工程复杂度并不低;而OpenID Connect实际上是一种OAuth 2.0的特定实现,来源于采用SSO的Google和其它一些公司,后者使用简单REST调用。OpenID Connect的缺点是本身缺少支持它的身份提供服务,因此更广泛的用于互联网第三方登录机制,特别是在如今越来越多的公众互联网服务应用中。然而如果要采用组织内的身份提供服务,目前较好的方案是OpenAM和Gluu,但并不完善。这也成为OpenID Connect在统治SSO领域之路上的绊脚石,尽管它看起来确实是最终的选择。

单点登录网关

在微服务架构中,每个独立的服务都有权决定对应的身份提供者,显然这将造成大量的资源浪费。如果考虑采用共享代码库,还要规避异构技术栈的问题。因此这里给出一个基于SSO网关的有效解决方案。

该方法的核心是只在一处处理全部用户重定向请求和握手信息,并且要求下游服务都接收一个Principle,如果基于HTTP构建微服务通信,那么很自然地就可以利用HEADER解决信息承载的问题,Shibboleth就是其中一个实践方案。此外,如果认证服务被置于网关,就更难对隔离状态下的微服务进行调试,同时也不利于日常开发。

上述方法最大的问题是它可能给人一种绝对安全的错觉——导致越来越多的功能依赖网关进行传递。这么做的结果就是网关服务变得越来越臃肿,成为系统中一个巨大的耦合点,同时也提高了单点失败的风险,特别是它也是反安全模式的——功能扩充意味着增加攻击面,就更容易遭受攻击。

细粒度授权

网关能够提供一个有效但较粗粒度的授权。但是由于它只有Principle而无法做到深度解析,就缺少更细的授权功能,而后者更多需要微服务自身去负责。而对于用户Role这种Principle信息来说,围绕组织功能实现粗粒度授权——然后进一步在微服务中实现细粒度授权。

2. 服务间认证和授权

Principle通常用来指代人机交互过程中的认证和授权对象。随着微服务架构日益复杂,服务间也必然会出现类似的交互过程。下面列举了若干种当前常用的解决方案:

边界内畅通

最简单的可能是,当服务请求来自组织内部——那么自然被当作是可信的。或者更远一些,建立HTTPS通信以防止中间人攻击。缺点是一旦内网被攻破,那么内部系统几乎没有任何防御,这就是安全领域的单点失败,然而事实上这也是目前多数组织的选择。

HTTP(S)基础认证

HTTP(S)协议具有基本的认证机制,其在Header中携带一个用户名和密码,并由服务端进行认证,实现起来也十分方便。缺点是采用HTTP传输用户名和密码十分危险,你几乎始终需要HTTPS——但需要额外的证书管理成本。此外,基于SSL的通信无法被类似Varnish、Squid等反向代理缓存,也就是说,在这种情况下,缓存策略只能在应用服务端、或是客户端实施。一种解决方法是在外层搭建一个LBS用于解析SSL通信,并在LBS后端存储缓存。

另一方面,如果现有架构已经采用SSO,那么如何结合SSO也是一个难题。如果允许基本服务访问与SSO一致的目录服务,也可以另外搭建一份目录服务——这种重复功能的存在会导致更多的潜在风险。还需注意:如果采用身份认证方案,那就意味着拥有Principle就能够访问资源,而无论它身在何处。

采用SAML和OpenID Connect

直接应用SSO架构能够减轻一些开发成本,如果基于SSO网关,那就意味着所有的通信将路由至网关,否则就又相当于重复功能。此外,客户端服务需要妥善保存自身的证书,以便用于各类服务间通信的认证——这就需要第三方存储服务。此外,无论是SAML和OpenID Connect,其组织内应用的支持都还远未成熟。

客户端证书

另一种认证方法是采用TLS(相当于SSL的继任)的特性,由于TLS要求每个客户端都具备X.509证书,那么基于证书认证的通信可以保证安全性。问题是需要一个完整的证书管理机制,因为这不仅仅意味着创建和管理证书,同时还要验证证书正确工作。采用通用证书也许是一种方法,但会引起一定的安全风险。因此当通信安全要求较高时,才应考虑该方案。

基于HTTP的HMAC

HMAC指一种基于哈希值的消息码技术,它克服了HTTP基础认证的安全缺陷,同时能够在HTTP上实现类似HTTPS通信。该技术最初由Amazon的AWS S3 API实现,并且属于OAuth规范的一部分。HMAC通过计算消息体和私钥的哈希值,将结果封装进消息体本身。服务端同样保存了一份私钥,然后重算并比较消息体携带的值,如果二者结果一致,则被认为是合法的请求。HMAC能够有效防止中间人攻击,同时由于私钥本身不会被明文传输,因此能保证一定的安全性。同时比起HTTPS还拥有更好的计算性能。

HMAC的主要缺点在于,首先服务间需要共享相同的私钥,这种私钥可以是硬编码的(缺少灵活性),也可以通过第三方获取(需要额外设计私钥交换机制)。其次,当前HMAC并没有一种统一的实现,需要开发者自己决定实现细节,比如采用SHA-256。JSON Web Tokens(JWT)也是一种可行的方案,但依然缺少标准实现。最后,需要知道HMAC只能保证通信不被第三方篡改,由于消息体本身使用HTTP传输,依然会被网络程序嗅探。

API密钥

目前绝大多数的互联网服务都采用API密钥解决认证和授权问题。然而如果要直接用于微服务架构,还存在一些困难。首先,一些系统使用共享的API密钥,同时基于类似HMAC的方法进行通信,而也有部分系统采用公钥+私钥的形式。此外,密钥管理一般也是集中式的,类似前文提到的网关方案。

API密钥真正风靡的原因是其易用性,与SAML相比,基于API密钥的认证与授权几乎就是零成本。而且API密钥还能够用于频率控制、转化率分析、API目录、以及服务发现系统,具有相当的灵活性。一些API系统还允许将API密钥链接至现有目录服务,这样就能真正实现同步管理Principle和密钥,达到高可配置化。一种随之而来的复杂结构是:用户认证统一采用SAML实施SSO,然后取得API密钥用于服务间通信,二者共用一套目录服务。

代理问题

随着服务数量和调用层级增加,代理问题可能影响系统安全。如果采用传统单一系统的形式,服务调用和用户界面直接通信,因此SSO就能直接解决所有问题。但对于微服务而言,调用层级使得SSO不再有效。例如,当用户访问A服务,并且需要通过A服务调用B服务的借口时,对B来说现有SSO方案就无能为力,此时为了确保用户合法性,就只能在发生调用时携带原始Principle,并在B端进行重新认证。随着微服务架构的普及,此类应用场景会越来越多,代码和功能的重复性会显著提升。

一般而言,解决上述问题存在三种基本方法:1.忽略安全性,即隐式可信,一些安全性要求低的应用就无所谓了。2.前面提到的传递Principle。3.服务B向A请求Principle。但无论是哪一种,目前都缺少一个成熟的解决方案。

3.静态数据安全

静态数据Data at Rest,与使用中数据Data in Use,以及动态数据Data in Motion,分别描述了计算领域中的三种数据形态。使用中数据,一般指存在于内存、寄存器或逻辑计算单元中的数据。动态数据,主要指网络中传输的数据。而静态数据,主要指存放在物理介质中的数据。通常所说的安全一般都是针对使用中的动态数据,例如网络安全、系统安全和应用安全。然而如果上述安全措施不再有效,静态数据被窃取就会显得易如反掌——从而为业界引入了深度安全的概念。

无论如何,数据窃取事件的发生不外乎未加密存储、或是保护系统失效,在任何安全方案中,此类隐患是必须得到重视的。

尽量采用稳固的加密算法

如果要自己实现加密算法,安全性就很难保证。即使采用第三方加密算法,也需要时刻保证该算法是否会随时被发现漏洞并攻破。AES-128和AES-256是一种有效的静态数据加密算法,许多语言都内置了算法实现,Java和C#也可以采用Bouncy Castle Libraries。密码也应至少实现带盐哈希加密。

密钥存储

许多加密算法都引入了密钥环节,因此对密钥本身的保护也不容忽视,否则再强大的加密算法也是十分脆弱的。采用第三方系统管理密钥是必须的,或者直接采用类似SQL Server的透明数据加密。无论采用何种方案,都需要仔细分析相关的安全风险。

可选和必选

应有选择的加密静态数据——这不仅关系到应用性能问题。一方面,前文介绍的日志和监控需要明文数据,此外,数据移植也会因为引入解密、加密过程而变得繁琐和低效。因此,对数据进行安全性分级是必要的。此外,对高安全要求的数据,当数据获取后即加密,只在请求数据时解密——除此之外不要在任何形式下存储该数据。对于备份数据,应实现整体加密并妥善保存和管理。

4.深度防御

安全如今已经不仅仅是一个单一的概念,要实现高可靠的安全,必须采用综合、深度防御,摒弃单点失败带来的潜在风险。当防御因素增加,攻击者成本也就越高,系统安全性才能得到保证。

防火墙

防火墙依然在进化,相比过去的端口限制和包识别,像ModSecurity已经实现了限制IP段连接次数、主动监测某些恶意攻击等功能。同时,采用多层防火墙也是必要的,例如系统级可以采用Iptables,而在应用级,服务内部也可以设置防火墙进行自身防御。

日志

日志是把双刃剑,一方面,良好的日志结构能方便发现各种风险,包括安全问题。但是日志中的敏感数据本身也会造成风险,适当遮蔽这部分数据是有必要的。

入侵检测和防御系统(IDS/IPS)

与防火墙不同的是,入侵检测主要监控系统内部行为,并发出警告或选择阻止危险行为。但是IDS(IPS)在实施上需要投入长期的人力成本,现有的IDS基本都是基于启发式防御,其基本形式就是通过设置一些规则,当某些系统行为与该规则相匹配,则认为该行为有风险。但在实施过程中,特别是系统初期建设时,入侵规则的建立是和系统和架构特点息息相关的。因此通常应从一个被动式IDS/IPS开始,逐步完善入侵规则,再逐渐过渡到主动防御——才是有效且可靠的。

网络隔离

在单一系统中,由于应用设施都部署在同一环境,从而导致安全性的单点失败风险。而对于微服务架构,由于服务隔离性,本身就可以通过现有的网络管理机制增加安全性。例如AWS就提供一种自定义虚拟私有云VPC的服务,该服务允许主机处于相互隔离的子网中,从而通过定义网络规则指定服务间的可见性,或者指定网络通信的路由方式。

操作系统

操作系统级别的安全策略,主要集中在运行服务的用户最小权限、以及基础软件的漏洞修复速度。目前大型软件的更新都支持自动化,同时提供警告机制,例如微软的SCCM和RedHat的Spacewalk。

另一方面,操作系统的第三方安全模块也值得考虑。例如RedHat采用的SELinux,Ubuntu和SuSE支持的AppArmour,还有GrSSecurity,上述模块允许用户定义系统安全行为,并且直接监视内核行为,及时制止相关操作。

5.小结

在德语中有一个短语Datensparsamkeit,意思是当你需要存储数据时,应尽量保证只保存有效且合法的最小数据集,该定义来源于德国的隐私保护法案。实际上,当你不携带“有价值”的数据,那么自然就不会引起攻击者觊觎了。许多系统发生隐私泄漏事件,其本身就无权存储相关信息,这才是风险的源头。

另外,组织内部人员也是一大风险因素,如何处理权限的创建和删除、避免社会工程攻击、或者其他内部人员的恶意攻击,都是组织管理需要考虑的问题。

安全领域的另一大忌就是避免重复造轮子,因为很难得到充分的审议和验证,此类代码将成为极高的风险源。

最后,随着OWASP等的流行和安全测试框架的日益完善,可以考虑把安全测试引入到现有CI/CD流程,例如Zed Attack Proxy(ZAP),Ruby支持的Brakeman,Nesssus等等。微软提出的安全开发生命周期,也是一个有效的开发团队安全实施模型。然后就是定期邀请组织内外的安全专家逐轮审议、修订安全架构,以确保安全设施的持续更新。

Comments