3.4 使用TLS保护服务

到目前为止,在遵循本章的示例时,你可能已经意识到,甚至已经被We b浏览器警告你访问的We b服务是不安全的。大多数浏览器都会以某种方式提示你正在浏览一个使用HTTP而不是HTTPS的网站,可能是一个小图标,也可能是一个全屏警告,提醒你在访问网页之前需要点击。这是一个很好的提醒,所有的网页都应该使用安全连接。当部署只能从本地设备访问的We b服务时,这可能不重要,但在大多数情况下,确实会有用户从另一台设备通过互联网访问你的We b服务。在这些情况下,你应该为你的用户提供HTTPS连接,以便他们输入的数据在传输到Web服务器的途中是安全的。网页浏览器显示的警告也会让用户一眼就知道你是否关心他们的隐私。

幸运的是,使用Let's Encrypt(https://letsencrypt.org)提供正式签名的证书非常容易。Let's Encrypt是由互联网安全研究小组(Internet Security Research Group,ISRG)运营的一项服务。它旨在为在任何公共领域运行网站的任何人提供受信任的、免费的传输层安全(Transport Layer Security,TLS)证书。Let's Encrypt证书的有效期为三个月,这比许多付费提供商提供的证书短得多,因此续订过程需要自动化,否则你将发现自己每三个月更新一次证书。幸运的是,Let's Encrypt本身提供了很好的选项来自动更新证书,甚至有像Caddy(https://caddyserver.com)这样的Web服务器对管理员(或SRE)隐藏了证书更新过程。

对于使用OpenShift路由器部署的服务,可以在路由配置中提供TLS证书。在以下示例中,你将首先部署假证书,然后使用Let's Encrypt请求受信任证书。你可以使用非常相似的过程,通过路由提供来自受信任颁发者的自定义证书。

3.4.1 指定TLS证书

在研究Let's Encrypt颁发的证书的自动续期之前,先看一下如何在OpenShift中配置TLS路由:为此,首先为你使用的DNS名称创建一个自签名证书。本例在OpenShift Local集群上创建,基域名为apps-crc.testing,因此对于此集群中的路由的域名,不可能生成公开信任的证书。你可以使用任何域名运行此示例,但请注意,自签名证书只用于帮助你理解该过程,而不能取代你的We b服务的受信任证书。

本章中部署的所有路由都使用相同的域名——platform-arcade.apps-crc.testing,因此你只需要创建并提供一个证书:

要创建自签名证书,可以使用openssl命令行工具。执行如下命令生成证书:

这将创建一个证书和一个密钥文件,你可以使用它们来加密服务的流量。为此,使用以下两个命令重新创建game路由:

❶指定路由类型edge,这意味着路由器将处理TLS握手。

❷输入上面生成的两个文件。

你刚刚指定了用于游戏服务的HTTPS流量的证书和密钥。现在你可以在路由规范中看到它们:

现在应该可以使用HTTPS方案访问服务了。打开网页浏览器,检查是否可以访问你的服务。在前面的示例中,可以访问https://platform-arcade.apps-crc.testing/s3e。虽然它现在使用HTTPS,但Web浏览器仍然警告你该网页正在使用自签名证书。

3.4.2 将流量重定向到TLS路由

在当前配置中,使用HTTP而不是HTTPS方案访问服务将不再可能。这允许你使用HTTP而不是HTTPS部署第二条路由。你还可以自动将用户重定向到安全路由,这应该是大多数情况下的首选选项。为此,将insecureEdgeTerminationPolicy设置为Redirect。这个设置有三个不同的选项:

•None:默认配置。此路由只能连接到HTTPS服务。

•Allow:允许HTTP和HTTPS的流量。

•Redirect:在访问HTTP方案时,Web服务器将客户端重定向到HTTPS。

要更新此设置,请编辑路由,如下所示:

新的配置也将显示在oc get route的TERMINATION列中:

现在,你可以使用Web浏览器调试工具的网络部分或curl来测试和观察重定向的工作流,如下面的清单所示:

❶使用-k告诉curl信任自签名证书,使用-i显示标头,使用-L跟踪重定向。

❷第一个响应是302,要求客户端使用不同的URL执行相同的请求。

❸该URL使用HTTPS方案,由使用HTTP方案的原始服务器返回。

❹下一个请求被发送到服务器(3)返回的URL,并返回状态码200(OK)。

❺返回包含游戏的网页。

将用户重定向到安全的HTTPS方案是一种很好的实践,特别对于像游戏平台这样直接面向用户的服务。使用重定向,用户不需要关心或考虑We b服务使用的方案。例如,用户不需要担心输入http://或https://。

既然你已经为其中一条路由启用了TLS加密,那么另外两条路由仍然使用未加密的HTTP。由于它们都共享相同的主机名,因此你可以重用相同的证书,而不需要创建或请求新的证书。

使用以下命令从游戏路由中提取TLS属性,并将其应用于平台和highscore路由,或者像之前使用game路由一样重新创建路由:

现在你应该看到所有的路由都显示“edge/Redirect”在TERMINATION列,检查浏览器应该显示你被重定向到正确的方案。由于你仍在使用自签名证书,因此将出现一个警告。

3.4.3 Let's Encrypt受信任证书

现在你已经知道了如何使用自签名证书为给定路由配置证书,接下来可以使用相同的过程在路由中使用正式签名的受信任证书。但是,当证书即将过期时(或者更糟的是,当有人第一次注意到证书已过期时)手动更新证书是一个烦琐的过程,即使证书的有效期超过90天,就像前面示例中的自签名证书一样。假设你的集群服务于多个路由,或者你维护多个集群,并且需要关心所有这些证书。

典型的SRE任务是自动化此过程,这样你就不必再手动续订证书了。如果你决定使用Let's Encrypt,那么Let's Encrypt服务本身(自动请求证书)或社区已经提供了许多自动化过程,因为该服务非常流行。

对于OpenShift,你可以使用cert-manager(https://cert-manager.io)为TLS加密流量配置的所有公开服务自动请求证书。

使用Let's Encrypt的cert-manager需要一个具有公共可访问域名的集群来执行证书续订。使用OpenShift Local可以,但是很难,所以为了练习这一部分,你应该使用第2章中描述的方法之一部署一个可公开访问的集群。

要在OpenShift集群上安装cert-manager,可以使用OperatorHub上提供的cert-manager Operator。

在OpenShift控制台中打开OperatorHub页面,搜索cert-manager Operator,选中并单击Install进行安装,如图3-5所示。

图3-5:从OperatorHub安装cert-manager Operator

默认选项包括观察所有要处理的资源的命名空间,这对于这个用例来说很好。

在OpenShift 4.10中,cert-manager Operator处于技术预览阶段,因此在即将到来的OpenShift版本中用法可能会略有不同。

cert-manager当前的一个限制是它只能处理ingress资源,而不能直接处理路由。这是因为cert-manager是为支持所有Kubernetes安装而构建的,其中入口是默认资源。路由作为OpenShift的一个概念,目前还不被支持。

由于OpenShift也支持ingress资源,并且可以通过路由器公开它们,因此这不是一个问题,但它确实需要你用ingress替换路由。

首先,删除之前为公开服务而创建的路由:

现在,你需要告诉cert-manager你希望如何向ingress提供证书。它支持许多不同的配置选项,你可以在cert-manager文档(https://oreil.ly/kY3Sf)中查找这些选项。由于你现在想要使用Let's Encrypt自动更新,你需要将其配置为使用自动证书管理环境(Automatic Certificate Management Environment,ACME)协议。

为此,创建一个ClusterIssuer资源并指定要使用的ACME端点。为了进行实验,你可以使用Let's Encrypt提供的staging端点。一旦验证了工作流的工作,你就可以用生产端点替换它,以获得受信任证书。

创建一个包含以下内容的文件,用Let's Encrypt Staging环境配置ClusterIssuer:

❶ingress将引用ClusterIssuer的名称,以告诉cert-manager使用此配置来获取证书。

❷要使用的ACME目录的URL。这是Let's Encrypt提供的staging端点。

❸配置操作员使用HTTP01挑战来更新证书。此配置是必需的,因此OpenShift可以通过路由器将挑战流量路由到证书管理器。

使用oc apply将资源应用到OpenShift集群:

你也可以直接从GitHub仓库(https://github.com/OperatingOpenShift/s3e)中创建它,其中包含使用以下URL的示例应用程序:

现在创建一个ingress,指示cert-manager使用你创建的ClusterIssuer获取新证书。下面的ingress配置替换了示例应用中的三条路由:

❶该注释将指示cert-manager使用ClusterIssuer le-staging获取新证书。

❷应用程序可用的主机名。

❸引用由游戏平台组成的不同服务的路径。以前使用路由公开的每个服务都被替换为此ingress中的路径。

❹与❷中指定的主机名相同。这将指示cert-manager证书必需对哪些主机名有效。

使用oc apply创建ingress:

一旦创建,OpenShift路由器将检测ingress配置并生成路由以公开指定的服务。你需要选择一个在集群内有效的主机名,与前面使用的主机名相似或相等,这样路由器才能生成有效的路由。

当在更新过程中运行oc get ingress时,你应该找到一个由cert-manager创建的ingress来执行HTTP01挑战。OpenShift路由器将通过路由公开这个ingress。使用oc get route来执行这个过程:

你可以在OpenShift控制台中查看cert-manager Operator页面的All Instances部分,按照更新过程进行操作,如图3-6所示。根据资源的类型,一旦所有资源的状态转移到Approved、Ready和Valid,你就可以期望完成证书续订。

图3-6:列出由cert-manager Operator管理的所有实例

续订过程完成后,cert-manager将把证书存储在ingress中指定的密钥platform-secret中。这使得路由器能够生成路由资源,最终公开街机游戏平台的服务:

路由的TLS配置将被ingress中引用的密钥内容填充。如你所见,路由器再次配置为使用边缘终止并将请求重定向到HTTPS端点。

使用Curl验证证书是由Let's Encrypt staging端点生成的,尽管它仍然不是一个受信任证书:

在验证证书续订过程是否有效之后,你可以用生产端点替换staging端点。要做到这一点,应用下面的ClusterIssuer,不同之处在于ACME目录URL:

现在更新ingress中的注释,告诉cert-manager使用新的ClusterIssuer:

更新过程将再次启动,更新密钥,最后更新生成的路由资源中的TLS配置。更新完成后,在浏览器中访问平台路由的URL将显示你现在使用的是受信任证书,如图3-7所示,由浏览器URL栏中的盾牌图标表示。

图3-7:为示例应用程序使用受信任证书

使用此设置,你可以通过更改ingress对象上的注释,在staging环境和生产环境之间进行切换,甚至可以在不同的TLS证书提供程序之间进行切换。

要对续订过程中出现的问题进行故障排除,可以在OpenShift控制台中列出的cert-manager创建的资源里查找错误消息。cert-manager在文档(https://oreil.ly/4fBY8)中提供了解决此类问题的指南。你可以按照这些步骤来解决最常见的问题。

3.4.4 与服务的加密通信

3.4.3节讨论了集群外部的客户端与集群之间通信的加密。TLS已在路由器上终止(边缘终止),请求以未加密的方式转发到We b服务。

在某些情况下,这是可以的,但是在许多情况下,你还希望路由器和OpenShift集群的pod之间的通信是加密的,也就是说,路由器应该使用HTTPS来转发请求。要实现这一点,你有两个不同的选择。你可以不在路由器上进行TLS终止,而是将HTTPS请求转发给目标服务(透传,passthrough),或者终止请求并向目标服务发送一个新的HTTPS请求(重加密,reencrypt)。不同终止模式的对比如图3-8所示。

图3-8:TLS终止模式的比较

透传

使用透传TLS终止模式的服务必须自己处理TLS终止。路由器不解密请求,直接将它们传递给接收服务。作为应用程序一部分的某种Web服务器需要接收TLS加密的流量并相应地对其进行解密。这意味着你使用的Web服务必须支持通过HTTPS进行通信,并且你必须向服务提供证书。如果你使用Let's Encrypt的cert-manager Operator,则可以将证书的请求和续订留给cert-manager,并通过将证书挂载到你的Web服务的pod中,使用它写入密钥的证书。

作为透传路由的示例,你可以使用平台部署,当在预定义路径上挂载证书时,平台部署支持TLS通信。

由cert-manager创建的密钥platform-secret与ingress位于相同的命名空间中。当请求新证书时,operator负责更新密钥。

现在你可以把这个密钥文件挂载到/etc/nginx/certs/tls.crt/etc/nginx/certs/tls.key放在平台pod中,平台NGINX服务希望证书存在。

❶volumeMounts指定应该挂载证书的位置。将证书挂载到/etc/nginx/certs文件夹中,这是应用程序容器期望它们存在的位置。

❷secretName引用你在ingress中指定的密钥。

服务如何知道它应该使用这些证书?该平台服务基于NGINX,并使用以下配置同时监听HTTP(端口8080)和HTTPS(端口4443)。当没有挂载证书时,服务使用容器镜像中包含的自签名默认证书。如果没有这些文件,则服务将无法启动:

下一步是在平台pod中公开用于HTTPS通信的端口,而不是HTTP端口。将platform服务中的端口和目标端口由8080修改为4443。

最后,更新ingress中的终止注释和规则。基于路径的路由不可能使用透传终止,因为路由器不解密请求,因此无法知道请求的路径。这意味着你需要从ingress中删除除平台规则之外的所有规则。其他服务将无法访问。

❶将终止注释更改为passthrough,以建议OpenShift创建透传路由。

❷将端口号修改为公开的TLS端口号。

❸将pathType更改为ImplemationSpecific。

❹将路径本身更改为空字符串。

仅更改服务上的“targetPort”设置就足够了,这样你就不必在ingress中更新端口以切换端口。但是,端口号的一致性有助于理解和调试系统,因此你还应该更新用于路由器和服务之间通信的端口。

现在你应该看到路由被更新为使用passthrough终止:

为了验证路由现在使用TLS与pod进行通信,平台服务修改日志输出以输出所使用的协议。你可以跟踪该服务的日志,并从浏览器或第二个终端向路由器发送请求,应该会看到类似以下的输出:

字符串TLSv1.2告诉你TLS已在pod中终止。如果没有在本节中更改的设置,日志行将类似于以下内容:

NGINX默认不记录是否使用TLS。变量$ssl_protocol被添加到NGINX配置的默认日志行中,专门用于可视化终止设置之间的差异。

如果应用程序需要透传,则需要将街机平台的架构更改为基于DNS名称(而不是路径)进行路由。对于在服务之间进行加密通信的需求,另一种选择是使用重加密终止模式。

重加密

对于边缘和透传终止模式,只有一个证书用于将请求从客户机加密到它终止的地方。终止要么发生在路由器中,要么发生在运行服务本身的pod中。对于重加密终止模式,第二个证书开始起作用。呈现给外部客户端的证书将保持不变,但现在路由器将通过HTTPS与服务的pod进行通信,使用不同的TLS证书。此证书是路由器需要信任的证书,并且对服务的内部DNS名称有效。要生成一个这样的证书,请在平台服务中添加以下注释:

你现在应该看到一个新的密钥出现了。更新平台部署以使用此密钥来服务TLS加密流量:

❶Let's Encrypt TLS密钥用于从客户端到路由器的外部流量。

❷生成的TLS密钥用于从路由器到平台服务的内部流量。

❸参考❷中所列的内部通信密钥。

最后,更新ingress的终止注释,重加密路由器和平台pod之间的流量:

❶指示OpenShift为重加密终止设置路由。

❷重加密允许你再次使用基于路径的路由,因此你可以重新添加额外的路径。

现在你应该看到终止模式更改为在生成的路由上重加密:

在观察日志的同时查询服务应该会再次显示请求正在使用TLS:

重加密终止模式与自动生成的证书相结合提供了一种简单的方法来加密从客户端到后端pod的流量,同时仍然允许对路由器中的请求有一些了解。这允许你使用基于路径的路由,并且仍然对端到端的流量进行加密。

因为TLS流量只启用了平台服务,所以只有平台服务的流量可以被服务。你可以将记分服务和游戏服务分离到使用边缘终止的单独ingress中。