第9章 安全

 
我们需要考虑,在数据从一个点到另一个点的传输过程中,如何保护它们,也需要考虑在 其他情况下如何进行保护。我们需要考虑底层操作系统及网络的安全。有太多需要考虑的 点,有太多可以做的事情!那到底需要多安全呢?我们如何知道什么是足够安全呢?
 
我们还需要考虑人的因素。谁在使用我们的系统,他又会做些什么?而这又与我们的服务 器如何交互有什么关系?让我们从这里开始。
 
9.1身份验证和授权
 
当谈到与我们系统交互的人和事时,身份验证和授权是核心概念。在安全领域中,身份验 证是确认他是谁的过程。对于一个人,通常通过用户输入的用户名和密码来验证。我们认 为只有用户本人才能够知道这些信息,因此输入这些信息的人一定是他。当然,还存在其 他更复杂的系统。我的手机可以用指纹来确认我是我本人。通常来说,当我们抽一象地讨论 进行身份验证的人或事时,我们称之为主体(principal)。
 
通过授权机制,可以把主体映射到他可以进行的操作中。通常,当一个主体通过身份验证 后,我们将获得关于他的信息,这些信息可以帮助我们决定其可以进行的操作。例如,当我们知道他在哪个部门或办公室工作后,系统可以通过这些信息来决定他能做什么和不能 做什么。
 
一般来讲,对于单一的单块系统来说,应用程序本身会处理身份验证和授权。例如 Django, 一个Python的Web框架,提供了现成的用户管理功能。不过,在分布式系统这 个领域,”我们需要考虑更髙级的方案。我们不希望每个人使用不同的用户名和密码来登录 不同的系统。我们的目的是要有一个单一的标识且只需进行一次验证。
 
9.1.1常见的单点登录实现
 
身份验证和授权的一种常用方法是,使用某种形式的SSO (Single Sign-On,单点登录)解 决方案。在企业级领域中占据统治地位的SAML和OpenlD Connect,也提供了这方面的 能力。虽然术语略有不同,但它们或多或少使用了相同的核心概念。这里使用的术语来自 SAML。
 
当主体试图访问一个资源(比如基于Web的接口)时,他会被定向到一个身份提供者那里 进行身份验证。这个身份提供者会要求他提供用户名和密码,或使用更先进的双重身份验 证。一旦身份提供者确认主体已通过身份验证,它会发消息给服务提供者,让服务提供者 来决定是否允许他访问资源。
 
这个身份提供者可能是一个外部托管系统,也可能是你自己组织内部的系统。例如,谷歌 提供了一个OpenlD Connect身份提供者。不过,对于企业来说,通常有自己的身份提供 者,它会连接到公司的目录服务。目录服务可能使用LDAP (Lightweight Directory Access Protocol,轻量级目录访问协议)或活动目录(Active Directory)。这些系统允许你存储主 体的信息,例如他们在组织中扮演什么样的角色。通常情况下,目录服务和身份提供者是 同一个系统,不过有时也会有所不同,但保持连接。例如,Okta是一个托管的SAML身 份提供者,它可以处理像双重身份验证这样的任务,但可以连接到你公司的目录服务,将 其作为信息来源。
 
 
SAML是一个基于SOAP的标准,尽管有库和工具支持它,但用起来还是相当复杂。基于 Google和其他公司处理SSO的方式,OpenID Connect已经成为了 OAuth 2.0具体实现中的 一个标准。它使用简单的REST调用,因为提高了其易用性,在我看来很有可能进军企业 级应用。现在其最大的障碍是缺乏支持它的身份提供者。对于一个面向公众的网站,你或 许可以使用Google作为提供者,但对于内部系统,或对于数据需要有更多控制权的系统 而言,你会希望有自己的内部身份提供者。在写本书的时候,相比SAML丰富的选择(包 括似乎无处不在的活动目录),OpenAM和Gluu是这个领域为数不多的两个选项。除非等 到现有的身份提供者开始支持OpenID Connect,不然它的发展会仅限于公共身份提供者这 种有限的情况。
 
因此,尽管我认为OpenID Connect是未来的方向,但很有可能需要一段时间,它才能被广泛地应用。
 
9.1.2单点登录网关
 
在微服务系统中,每个服务可以自己处理如何重定向到身份提供者,并与其进行握手。显 然,这意味着大量的重复工作。使用共享库可以解决这个问题,但我们必须小心地避免可 能来自共享代码的耦合。而且如果有多个不同的技术栈,共享库也很难提供帮助。
 
你可以使用位于服务和外部世界之间的网关(如图9-1所示)作为代理,而不是让每个服 务管理与身份提供者握手。基本想法是,我们可以集中处理重定向用户的行为,并且只在 一个地方执行握手。
图9-1:使用网关实现单点登录
然而,我们仍然需要解决下游服务如何接受主体信息的问题,例如用户名和角色。如果你 使用HTTP,可以把这些信息放到HTTP头上。在这方面,Shibboleth这样的工具可以帮助 你。我见过人们把它和Apache—起使用,这种方式能够很好地处理与基于SAML的身份 提供者的集成。
 
另一个问题是,如果我们决定把身份认证的责任移到网关,那么孤立地在微服务中定位问 题就变得更难。还记得在第7章我们探讨过的重现类生产环境的挑战吗?如果选择使用网 关路由,请确保你的开发人员不需要太多的工作,就可以启动一个网关及其背后的服务。
 
这种方法的最后一个问题是,它会带给你一种虚假的安全感。我喜欢深度防御的理念,从 网络边界,到子网,到防火墙,到主机,到操作系统,再到底层硬件。你需要在所有这些 方面都实现安全措施的能力,我们将很快提到其中的一些。我见过有些人把所有的鸡蛋都放在一个篮子里,依靠网关来处理每一步的安全措施。我们都知道当这个点发生故障后, 会发生什么……
显然,你还可以使用这个网关来做其他事情。例如,如果你使用Apache的一个实例运行 Shibboleth,也可以在这一级别决定终止HTTPS,运行入侵检测,等等。不过,一定要小 心。网关层承担越来越多的功能后,最终本身会是一个庞大的耦合点。而且功能越多,受 攻击面就越大。
 
9.1.3细粒度的授权
 
网关可以提供相当有效的粗粒度的身份验证。例如,它可以阻止任何未登录用户访问帮助 台应用程序。假如我们的网关在身份验证完成时能提取出主体的属性,则可以据此做出更 细致的决定。例如,我们通常将人放到某些组或分配某些角色,通过使用这些信息来了解 他们能做什么。所以,对于帮助台应用程序,我们可能只允许具有某个特定的角色(例如 工作人员)的主体访问。不过,超出允许(或禁止)的特定资源或端点的访问部分,它们 可以留给微服务本身来处理,它会对允许哪些操作做进一步的决定。
 
回到我们的帮助台应用程序:我们会允许任何员工查看任何信息和所有细节吗?更可能的 是,工作上会有不同的角色。例如,CALL_CENTER组中的主体可以查看除付款细节外 所有客户的信息。该主体也可以发起退款,但是额度会受限制。然而,CALL_CENTER一 TEAM_LEADER角色的主体,可以进行更大额度的退款。
 
应该在微服务内部做这些决定(也就是角色能够做什么,应该在微服务内部做决定,而不要放到网关层或身份提供者中)。我见过,人们以可怕的方式,使用身份提供者提供的各种 属性,比如像CALL_CENTER_50_DOLLAR_REFUND这种非常细粒度的角色,将属于系 统行为的某个特定部分的信息放到目录服务中。这对系统维护来说是一场噩梦,并且很难 让我们的服务拥有独立的生命周期,因为有关服务行为的一部分信息突然间被放置到了别 的地方,甚至有可能是由组织中一个不同部分管理的系统。
 
相反,你应该倾向于使用粗粒度的角色,围绕组织的工作方式建模(也就是创建的角色要与组织的工作方式相匹配,不要创建太细的角色,比如一个操作就创建一个角色)。回到之前的章节,请 记住,我们构建的软件要与组织的工作方式相匹配。所以也请以这种方式来使用角色。
 
9.2服务间的身份验证和授权
 
到目前为止,我们一直在使用主体这个术语,用来描述可以进行身份验证和授权的任何 事物,但我们的例子都是关于使用电脑的人类。那程序或其他服务之间如何进行身份验 证呢?
 
9.2.1在边界内允许一切
 
我们的第一个选项是,在边界内对服务的任何调用都是默认可信的。
取决于数据的敏感性,这种方式可能没有问题。一些组织尝试在他们的网络范围内确保安 全,因此认为,当两个服务彼此访问时,它们不需要额外做任何事情。然而,如果一个攻 击者入侵你的网络,你将对典型的中间人攻击基本没有任何防备。如果攻击者决定拦截并 读取你正在发送的数据,在你不知情时更改数据,甚至在某些情况下假装是你正在通信的 对象,你将不得而知。
 
迄今为止,边界内信任这种形式被大多数组织采用。他们可能决定在通信中使用HTTPS, 但仅此而已。我可没说这是一件好事!对于大多数使用这种模式的组织来说,我担心隐式 信任模型并不是一个明智的决定,而更糟糕的是,很多时候人们没有在一开始意识到它的风险。
 
9.2.2 HTTP(S)基本身份验证
 
HTTP基本身份验证,允许客户端在标准的HTTP头中发送用户名和密码。服务端可以验 证这些信息,并确认客户端是否有权访问服务。这样做的好处在于,这是一种非常容易理 解且得到广泛支持的协议。问题在于,通过HTTP有很高的风险,因为用户名和密码并没 有以安全的方式发送。任何中间方都可以看到HTTP头的信息并读取里面的数据。因此, HTTP基本身份验证通常应该通过HTTPS进行通信。
 
当使用HTTPS时,客户端获得强有力的保证,它所通信的服务端就是客户端想要通信的 服务端。它给予我们额外的保护,避免人们窃听客户端和服务端之间的通信,或篡改有效 负载。
 
服务端需要管理自己的SSL证书,当需要管理多台机器时会出现问题。一些组织自己承 担签发证书的过程,这是一个额外的行政和运营负担。管理这方面的自动化工具远不够成 熟,使用它们后你会发现,需要自己处理的事情就不止证书签发了。自签名证书不容易撒 销,因此需要对灾难情景有更多的考虑。看看你是否能够避免自签名,以避开所有的这些 工作。
 
SSL之上的流量不能被反向代理服务器(比如Varnish或Squid)所缓存,这是使用 HTTPS的另一个缺点。这意味着,如果你需要缓存信息,就不得不在服务端或客户端内部 实现。你可以在负载均衡中把Https的请求转成Http的请求,然后在负载均衡之后就可以 使用缓存了。
 
还需要考虑,如果我们已经在使用现成的SSO方案(比如包含用户名密码信息的SAML), 该怎么办。我们想要基本身份验证使用同一套认证信息,然后在同一个进程里颁发和撤销 吗?让服务与实现SSO所使用的那个目录服务进行通信即可做到这一点。或者,我们可以 在服务内部存储用户名和密码,但需要承担存在重复行为的风险。
 
注意:使用这种方法,服务器只知道客户端有用户名和密码。我们不知道这个信息是否来自我们期望的机器;它可能来自网络中的其他人。
 
9.2.3 使用SAML或OpenID Connect
 
如果你已经在使用SAML或OpenID Connect作为身份验证和授权方案,你可以在服务之 间的交互中也使用它们。如果你正在使用一个网关,可以使用同一个网关来路由所有内网 通信,但如果每个服务自己处理集成,那么系统应该就自然而然这么工作。这样做的好处 在于,你利用现有的基础设施,并把所有服务的访问控制集中在中央目录服务器。如果想 要避免中间人的攻击,我们仍然需要通过HTTPS来路由通信。
 
客户端有一组凭证,用于向身份提供者验证自身,而服务获取所需的信息,用于任何细粒 度的身份验证。
 
这意味着你需要为客户端创建账户,有时被称为服务账户。许多组织普遍使用这种方法。 不过,需提醒一句:如果你打算创建服务账户,应尽量限制其使用范围。因此,考虑每个 微服务都要有自己的一组凭证。如果凭证被泄露,你只需撤销有限的受影响的凭证即可, 这使得撒销/更改访问更简单。
 
然而,还有其他几个缺点。首先,像使用基本身份验证一样,我们需要安全地存储凭证: 用户名和密码放在哪里?客户端需要找到一些安全的方法来存储这些数据。另一个问题 是,在这个领域的技术实现方面,做身份验证需要写相当繁琐的代码。尤其是SAML,在 其之上实现一个客户端非常痛苦。OpenID Connect的工作流要简单些,但正如我们前面所 讨论过的,它尚未被很好地支持。
 
9.2.4客户端证书
 
确认客户端身份的另一种方法是,使用TLS (Transport Layer Security,安全传输层协议), TLS是SSL在客户端证书方面的继任者。在这里,每个客户端都安装了一个X.509证书, 用于客户端和服务器端之间建立通信链路。服务器可以验证客户端证书的真实性,为客户 端的有效性提供强有力的保证。
 
使用这种方法,证书管理的工作要比只使用服务器端证书更加繁重。它不只是创建和管理 数量更多的证书这么简单;相反,所有的复杂性在于证书本身,你很有可能会花费大量的 时间来试图诊断服务端为什么不接受你认为的一个完全有效的客户端证书。接下来,我们 要考虑在最坏的情况下,撤销和补发证书的难度。使用通配符证书能够解决一些问题,但 不是全部。这些额外的负担意味着,当你特别关注所发数据的敏感性,或无法控制发送数 据所使用的网络时,才考虑使用这种技术。因此,你应该在通过互联网发送非常重要的数 据时,才使用安全通信。
9.2.5    HTTP 之上的 HMAC
 
正如我们前面所讨论的,如果担心用户名和密码被泄露,基本身份验证使用普通HTTP并 不是非常明智的。传统的替代方式是使用HTTPS路由通信,但也有一些缺点。除了需要 管理证书,HTTPS通信的开销使得服务器压力增加(尽管,老实说,这比几年前影响要小 得多),而且通信难以被轻松地缓存。
 
另一种方法使用HMAC (Hash-based Message Authentication Code,基于哈希的消息码)对 请求进行签名,它是OAuth规范的一部分,并被广泛应用于亚马逊AWS的S3 API。
 
使用HMAC,请求主体和私有密钥一起被哈希处理,生成的哈希值随请求一起发送。然 后,服务器使用请求主体和自己的私钥副本重建哈希值。如果匹配,它便接受请求。这样 做的好处是,如果一个中间人更改了请求,那么哈希值会不匹配,服务器便知道该请求已 被篡改过。并且,私钥永远不会随请求发送,因此不存在传输中被泄露的问题。额外的好 处是,这个通信更容易被缓存,而且生成哈希的开销要低于处理HTTPS通信的开销(虽 然你的情况有可能不同)。
 
这种方法有三个缺点。首先,客户端和服务器需要一个共享的、以某种方式交流的密钥。 它们如何共享?可能是在两端都硬编码,但这样会带来一个问题,当密钥被泄露后,你 需要撒销访问。如果你通过一些替代的协议来共享密钥,那么需要确保这个协议是非常 安全的!
 
其次,这是一种模式,而不是标准,因此有各种不同的实现方式。结果就是,缺乏一个优 秀的、开放的且有效的实现方式。通常来说,如果你对这种方式感兴趣,需要多去看看和 理解不同的实现方式。你可以先去看看亚马逊的S3,然后参考它的方式,特别是类似于 SHA-256中使用的适当长度的密钥和合理的哈希函数。JWT (JSON Web Tokens, JSON Web 令牌, http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html)也值得一看,它 们使用类似的方式实现,并且似乎正在吸引更多的关注。但是要注意,正确实现这个方式 的难度。我的同事曾经与一个团队实施自己的JWT方案,仅仅因为忽略了一个布尔检查, 就导致整个身份验证码都失效!希望随着时间的推移,我们可以看到更多可重用库的实 现。
 
最后,要理解这种方法只能保证第三方无法篡改请求,且私钥本身不会泄露。但请求中所 带的其他数据,对网络嗅探来说仍是可见的。
 
9.2.6    API密钥
 
像Twitter、谷歌、Flickr和AWS这样的服务商,提供的所有公共API都使用API密钥。 API密钥允许服务识别出是谁在进行调用,然后对他们能做的进行限制。限制通常不仅限 于特定资源的访问,还可以扩展到类似于针对特定的调用者限速,以保护其他人服务调用的质量等。
 
具体该如何使用API密钥方式来处理你的微服务间的访问,取决于你所使用的具体技术。 一些系统使用一个共享的API密钥,并且使用一种类似干刚才所说的HMAC的方式。更 常见的方法是,使用一个公钥私钥对。通常情况下,正如集中管理人的身份标识一样,我 们也会集中管理密钥。网关模型在这个领域很受欢迎。
 
其受欢迎的原因一部分源于这样一个事实,API密钥重点关注的是对程序来说的易用性。 相对干处理SAML握手,基于API密钥的身份验证更简单直接。
 
API密钥的解决方案在商业和开源领域存在很多选项,每种系统提供的具体功能有所不同。 有些产品只处理API密钥交换和一些基本的密钥管理。其他的工具提供包括限速、变现、 API目录和发现系统等功能。
 
一些API系统允许你将API密钥和现有目录服务联系起来。这允许将API密钥发布给你组 织中的主体(代表人或系统),从而可以跟管理普通凭证一样,来控制这些密钥的生命周 期。这为通过不同的方式访问系统,但保持一样的可靠信息来源提供了可能性。例如,如 图9-2所示,SSO使用SAML对人进行身份验证及服务间进行通信时使用API密钥。
图9-2:使用目录服务让SSO和API网关同步主体信息
 
9.2.7代理问题
 
给指定的微服务进行主体的身份验证非常简单。但是,如果该服务需要更多的调用才能完 成,会发生什么呢?图9-3展示了 MusicCorp的在线购物网站。我们的在线商店是一个基于浏览器的、使用JavaScript的用户界面。它使用我们在第4章介绍的“为前端服务的后 端”模式,来调用服务器端的商店应用程序。浏览器对服务端调用时的身份验证,可以使 用SAML、OpenID Connect或类似的方式。到目前为止,一切还好。
图9-3: —个混淆代理人可以实施诡计的例子
 
当我登录后,可以单击一个链接来查看订单的详细信息。为了显示这些信息,我们需 要从订单服务中找到原始订单,但我们也想要查看订单的物流信息。所以点击链接/ orderStatus/12345,会使在线商店通过在线商店服务向订单服务和物流服务发送请求,以 获得这些信息。但这些下游服务是否该接受在线商店的调用呢?我们可以采用一种隐式信 任的方式:因为调用在我们的信任边界内,所以是可以接受的。我们甚至可以使用证书或 API密钥,来确认真的是在线商店请求这些信息。但这是否就足够了呢?
 
有一种安全漏洞叫作混淆代理人问题,指的是在服务间通信的上下文中,攻击者采用一些 措施欺骗代理服务,让它调用其下游服务,从而做到一些他不应该能做的事情。例如,作 为一个用户,当我登录到在线购物系统时,可以查看我的账户详情。但如果我使用登录后 的凭证,欺骗在线购物用户界面去请求别人的信息,那该怎么办?
 
在这个例子中,如何阻止我查询不属干我的订单? 一旦登录,我可以尝试给不属于我的其 他订单发送请求,看是否能得到有用的信息。我们可以尝试让在线商店本身防止这种情况 的发生,通过检查订单是谁的,然后拒绝别人访问不属于自己的订单。然而,如果有很多 不同的应用程序来访问这些信息,可能会在很多地方重复这个逻辑(也就是如果是访问其他每个信息,那么每个信息也都需要做这种检查)。我们可以直接路由用 户界面的请求到订单服务,让它来验证请求,不过这样会遇到我们在第4章讨论过的各种 缺点。   
 
另一种方法是,当在线商店给汀单服务发送请求时,它不仅要说明想要哪I订单,还要说 明以谁的名义来调用。一些身份验证方案允许我们传递原始主体的凭证给下游,不过使用 SAML时,这会是~'场疆梦,包含嵌套的SAML断言在技术上是可实现的-不过非常之难,以至于从来没人实现过。当然,这可能变得更加复杂。想象一下,如果在线商店调用 的服务,转而调用更多的下游服务。我们需要在验证代理可信性上花费多少精力?
 
不幸的是,这个问题没有简单的答案,因为它本身就不是一个简单的问题。不过,要知道 它的存在。根据所讨论操作的敏感性,你可能需要在隐式信任、验证调用方的身份或要求 调用者提供原始主体的凭证这些安全方式里做一个选择。(可以给每个用户加上额外的权限,限制其只能访问某些url,比如用户joe只能在后台访问自己信息的路径:/joe/***)
 

猜你喜欢

转载自www.cnblogs.com/mongotea/p/11992114.html