新:在TwitterMastodon

信任管理器

信任管理器是管理 Kubernetes 和 OpenShift 集群中信任捆绑的最简单方法。

它编排受信任的 X.509 证书捆绑包,这些捆绑包主要用于在 TLS 握手期间验证证书,但也可用在其他情况下。

概述

信任管理器是一个小型 Kubernetes 运算符,旨在帮助减少在集群中管理 TLS 信任捆绑包的开销。

它添加了 Bundle 自定义 Kubernetes 资源 (CRD),它可以从各种来源读取输入,并将生成的证书组合成一个捆绑包,供您的应用程序使用。

信任管理器确保快速轻松地使您的受信任证书保持最新,并使集群管理员能够轻松地自动提供安全的捆绑包,而无需担心重建容器以更新信任存储。

它旨在补充 cert-manager,在从 cert-manager IssuerClusterIssuer 消费 CA 证书时效果很好,但如果需要,也可以完全独立于 cert-manager 使用。

安装

有关如何安装信任管理器的说明,请参阅 安装指南

用法

信任管理器有意简化,只添加了一个新的 Kubernetes CustomResourceDefintionBundle

一个 Bundle 代表一组应在集群中分发的 X.509 证书。

所有 Bundle 都是集群范围的。

Bundle 包含一个 sources 列表,信任管理器将从中组装最终捆绑包,以及一个 target,描述如何以及在哪里写入生成的捆绑包。

一个示例 Bundle 可能如下所示

apiVersion: trust.cert-manager.io/v1alpha1
kind: Bundle
metadata:
name: my-org.com # The bundle name will also be used for the target
spec:
sources:
# Include a bundle of publicly trusted certificates which can be
# used to validate most TLS certificates on the internet, such as
# those issued by Let's Encrypt, Google, Amazon and others.
- useDefaultCAs: true
# A Secret in the "trust" namespace; see "Trust Namespace" below for further details
- secret:
name: "my-db-tls"
key: "ca.crt"
# Here is another Secret source, but this time using a label selector instead of a Secret's name.
- secret:
selector:
matchLabels:
fruit: apple
key: "ca.crt"
# A ConfigMap in the "trust" namespace; see "Trust Namespace" below for further details
- configMap:
name: "my-org.net"
key: "root-certs.pem"
# Here is another ConfigMap source, but this time using a label selector instead of a ConfigMap's name.
- configMap:
selector:
matchLabels:
fruit: apple
key: "ca.crt"
# A manually specified string
- inLine: |
-----BEGIN CERTIFICATE-----
MIIC5zCCAc+gAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
....
0V3NCaQrXoh+3xrXgX/vMdijYLUSo/YPEWmo
-----END CERTIFICATE-----
target:
# Sync the bundle to a ConfigMap called `my-org.com` in every namespace which
# has the label "linkerd.io/inject=enabled"
# All ConfigMaps will include a PEM-formatted bundle, here named "root-certs.pem"
# and in this case we also request binary formatted bundles in JKS and PKCS#12 formats,
# here named "bundle.jks" and "bundle.p12".
configMap:
key: "root-certs.pem"
additionalFormats:
jks:
key: "bundle.jks"
pkcs12:
key: "bundle.p12"
namespaceSelector:
matchLabels:
linkerd.io/inject: "enabled"

Bundle 资源目前支持几种源类型

  • configMap - 信任管理器命名空间中的 ConfigMap 资源
  • secret - 信任管理器命名空间中的 Secret 资源
  • inLine - 手动指定的字符串,包含至少一个证书
  • useDefaultCAs - 通常是公开信任证书的捆绑包

ConfigMap 是默认的目标类型,但从 v0.7.0 开始,信任管理器也支持 Secret 资源作为目标。

Secret 目标的支持必须在信任管理器控制器中明确启用;请参阅下面“启用 Secret 目标”下的详细信息。

ConfigMapSecret 也支持指定标签选择器以一次选择多个资源,这在 ConfigMapSecret 的名称仅在运行时才知道的动态环境中非常有用。在添加源(无论是 ConfigMap 还是 Secret 类型)时,字段 nameselector 是互斥的:必须设置其中一个,但不能同时设置。

所有源和目标选项都在信任管理器的 API 参考文档 中有记录。

目标

所有 Bundle 目标都写入 ConfigMap(以及/或 Secret),其名称与 Bundle 的名称相匹配,每个目标都包含一个 PEM 格式的捆绑包。

用户还可以选择将 JKS/PKCS#12 格式的二进制信任存储写入目标。JKS 从 v0.5.0 开始支持,PKCS#12 从 v0.7.0 开始支持。

使用 JKS 和 PKCS#12 信任存储的应用程序通常需要出于遗留原因设置密码。这些密码通常是安全伪装 - 它们要么使用非常弱的加密,要么以明文形式将密码提供在它们加密的文件旁边,这违背了拥有密码的目的。

信任捆绑不包含私钥,因此对于大多数用例来说,加密它们没有任何安全优势。因此,信任存储的密码默认设置为 changeit(对于 JKS)和 ""(空字符串或“无密码”)(对于 PKCS#12)。

最近的版本允许您通过设置捆绑 YAML 文件 spec.target.additionalFormats.jks.passwordspec.target.additionalFormats.pkcs12.password 来更改密码。

较旧的版本对当前的默认值进行了硬编码,无法更改它们。有关更多信息,请阅读 为什么密码没有用

命名空间选择器

目标的 namespaceSelector 用于限制将 Bundle 的目标同步到的命名空间。

namespaceSelector 支持字段 matchLabels

有关如何配置标签选择器的更多信息,请参阅 Kubernetes 文档

如果 namespaceSelector 为空,则 Bundle 的目标将同步到所有命名空间。

⚠️ 信任管理器的未来更新 **将** 更改此行为,以便空命名空间选择器默认情况下仅同步到信任管理器命名空间。

快速入门示例

让我们从创建自己的 Bundle 示例开始!

首先,我们将创建一个演示集群

git clone https://github.com/cert-manager/trust-manager trust-manager
cd trust-manager
make demo

一旦我们有了正在运行的集群,我们就可以使用在信任管理器启动时配置的默认 CA 创建一个 Bundle。由于我们使用 Helm 安装了信任管理器,因此我们的默认 CA 包包含从 Debian 容器派生的公开信任证书。

kubectl --kubeconfig ./bin/kubeconfig.yaml apply -f - <<EOF
apiVersion: trust.cert-manager.io/v1alpha1
kind: Bundle
metadata:
name: example-bundle
spec:
sources:
- useDefaultCAs: true
target:
configMap:
key: "trust-bundle.pem"
EOF

太简单了!现在让我们检查一下是否一切正常同步,以及我们的 ConfigMap 是否已写入

kubectl --kubeconfig ./bin/kubeconfig.yaml get bundle example-bundle | less
kubectl --kubeconfig ./bin/kubeconfig.yaml get configmap example-bundle -o "jsonpath={.data['trust-bundle\.pem']}" | less

太好了 - 我们有了信任捆绑包。我们可以立即将其用于我们的容器,但让我们更进一步,创建一个虚拟的“组织 CA”,我们希望将其包含在我们的 Bundle 中。

我们将使用 cert-manager 生成我们的虚拟组织证书

kubectl --kubeconfig ./bin/kubeconfig.yaml apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: trust-manager-selfsigned-issuer
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: trust-manager-example-ca
namespace: cert-manager
spec:
isCA: true
commonName: trust-manager-example-ca
secretName: trust-manager-example-ca-secret
privateKey:
algorithm: ECDSA
size: 256
issuerRef:
name: trust-manager-selfsigned-issuer
kind: ClusterIssuer
group: cert-manager.io
EOF

现在让我们检查一下 cert-manager 为我们创建的 Secret

kubectl --kubeconfig ./bin/kubeconfig.yaml get -n cert-manager secret trust-manager-example-ca-secret -o"jsonpath={.data['tls\.crt']}" | base64 -d
# tls.crt will contain a PEM certificate, starting with -----BEGIN CERTIFICATE-----

🤔 想知道为什么我们使用了 tls.crt 而不是 ca.crt?更多详细信息 如下

最后,我们将更新我们的 Bundle 以包含我们的新私有 CA

kubectl --kubeconfig ./bin/kubeconfig.yaml apply -f - <<EOF
apiVersion: trust.cert-manager.io/v1alpha1
kind: Bundle
metadata:
name: example-bundle
spec:
sources:
- useDefaultCAs: true
- secret:
name: "trust-manager-example-ca-secret"
key: "tls.crt"
target:
configMap:
key: "trust-bundle.pem"
EOF

我们完成了!example-bundle ConfigMap 应该已经更新了。

如果您再次检查 ConfigMap,您在列表中看到的最后一个证书应该是我们新的虚拟 CA。

安全维护 Trust-manager 安装

如果您在任何 Bundle 资源上选择 useDefaultCAs 源,则务必使您的默认 CA 包镜像保持最新。否则,这等同于在 Debian 容器中安装公共信任捆绑包时,没有运行 apt-get upgrade ca-certificates

trust-manager 的设计方式使得任何版本的默认 CA 包都应该能够与任何支持默认 CA 的 trust-manager 版本 (v0.4.0 及更高版本) 配合使用。升级不会对 trust-manager 的稳定性造成任何风险。

如果您使用的是 cert-manager 提供的官方 Debian CA 包(默认情况下使用此包),则应检查您拥有的版本,并将其与 最新包版本 进行比较。

可以通过 Helm 图表上的 .defaultPackageImage.tag 值来配置版本,并且该版本也会写入使用默认 CA 包的任何同步 Bundle 资源的 status 字段。

使用 Helm 升级默认 CA 包

假设我们要对默认 CA 包执行就地升级,将其升级到标记版本 XYZ,而无需升级 trust-manager。

我们假设 Helm 发行版名为“trust-manager”,并且我们已将其安装到 cert-manager 命名空间中。

⚠️ 此升级过程假定它是唯一正在运行的进程。如果在您执行此过程时,其他用户或进程更改了 Helm 值,则可能会覆盖他们的工作。

首先,我们将转储当前的 Helm 值,这样我们才不会丢失它们。

helm get values -n cert-manager trust-manager -oyaml > values.yaml

接下来,如果 defaultPackageImage.tag 已经设置在 values.yaml 中,则更新它。否则,添加它。您可以 quay.io 找到可用的标签。

# values.yaml
...
defaultPackageImage:
tag: XYZ

默认包镜像标签的这些版本直接源自 Debian 中 ca-certificates 包的版本。

最后,应用更改,务必手动指定安装的 trust-manager 版本,以避免在更新默认 CA 包时也更新 trust-manager 控制器。

# Get the currently installed version. You could do this manually if you find that easier.
TRUST_MANAGER_VER=$(helm list --filter "^trust-manager$" -n cert-manager -ojson | jq -r ".[0].app_version")
# Check the version makes sense
echo $TRUST_MANAGER_VER
# Run the upgrade
helm upgrade -f values.yaml -n cert-manager trust-manager jetstack/trust-manager --version $TRUST_MANAGER_VER

如果使用不正确的标签,则部署将失败,您可能需要使用 helm rollback 来恢复到工作状态。

准备投入生产

TLS 可能很复杂,并且有许多方法可能会误用 TLS 证书。

以下是您在生产环境中运行 trust-manager 之前需要了解的一些潜在问题。

如果您计划在生产环境中运行 trust-manager,并且使用的不仅仅是默认 CA 包,那么我们 **强烈** 建议您阅读并理解本节内容。这可以防止您在以后导致停机。

ℹ️ 这些问题并非特定于 trust-manager,您可能会在使用任何 TLS 信任管理方法时遇到任何问题!

捆绑中间证书

如果您曾经使用过 Let's Encrypt 客户端(例如 Certbot),您可能已经看到它会生成多个证书文件,例如 cert.pemchain.pemfullchain.pem

提供这些不同的文件是为了支持各种不同的应用程序,这些应用程序可能需要分别提供证书和证书链。对于大多数用户和应用程序来说,fullchain.pem 是唯一正确的选择。

不幸的是,这些文件的存在会导致人们有时会错误地认为 cert.pem 是正确选择,即使 fullchain.pem 才是正确选择。这意味着在使用证书时不会发送证书链的其余部分。

通常,一个看似可以解决此问题的快速修复方法是客户端将其证书链添加到其信任存储区中,这将使证书错误在短期内似乎得到解决。这种“修复”很容易最终被嵌入到某个地方作为解决方案,供其他人效仿。

这种“修复”非常危险;它意味着在更新包含中间证书的所有信任存储区之前,无法安全地轮换中间证书。

在这种情况下,中间证书实际上变成了根证书,这完全违背了使用中间证书的初衷。

在任何可能的情况下,请避免在任何信任存储区中使用中间证书,除非您绝对确定应该包含它们。可能允许使用中间证书的一个示例是交叉签名,但在一般情况下可能不需要交叉签名。

最好将根证书复制到一个新的 ConfigMap 中,并将其用作源,而不是信任中间证书。

cert-manager 集成:ca.crttls.crt

如果您将 trust-manager 指向包含 cert-manager 发行证书的 Secret,您会看到两个相关的字段:ca.crttls.crt。(我们忽略了 tls.key,因为 trust-manager 绝对不需要访问它)

这带来了一个显而易见的问题:在 ca.crttls.crt 之间,我应该为 trust-manager 使用哪个字段?

不幸的是,在一般情况下,不可能确定哪个字段是正确的选择,但我们可以提供一些指导。

tls.crt 通常包含多个证书,这些证书可能并非全部都是颁发者,其中一些很可能是中间证书。如果是这种情况,您不应该使用 tls.crt 作为源。(有关详细信息,请参阅上面的“捆绑中间证书”。)

ca.crt 似乎更可能是普遍正确的选择,但重要的是要记住,它只能根据最佳努力原则进行填充。 ca.crt 的内容取决于 Issuer 是否正确配置,某些颁发者类型可能无法为此字段提供有用或正确的条目。

作为一项规则,您应该优先创建仅使用根证书的 Bundles(同样,请参阅上面的内容),因此您应该只使用其中包含单个根证书的字段。请考虑阅读下面关于为什么您可能不想真正直接依赖 cert-manager 发行的证书的内容。

cert-manager 集成:有意复制 CA 证书

在 Kubernetes 世界中,建议有意添加一个似乎使基础设施自动化变得更难的步骤是非常奇怪的,但在 TLS 信任存储区的情况下,这可能是明智的选择。

假设您有一个 cert-manager Issuer,其 ca.crt 中包含您想要信任的根证书。您可能会倾向于直接使用 Secret 并指向 ca.crt,但最佳实践是将该根证书复制到一个单独的 ConfigMap(或 Secret)中。

原因是 - 与许多 TLS 问题一样 - 证书轮换。如果您轮换发行者,使其从新的根证书颁发,trust-manager 将看到Secret被更新,并自动更新您的信任捆绑包以包含新的根证书 - 立即不信任旧的根证书。

这意味着,如果任何服务仍在使用由旧根证书颁发的证书,它们将不受信任,并将出现故障。

轮换要求在一段时间内同时信任两个根证书,或者在旧根证书之前或同时轮换所有颁发的证书。

已知问题

kubectl describe

kubectl describe 内,useDefaultCAs 选项会遇到一个角落情况,并显示为 Use Default C As: true。这纯粹是美观的。