传输安全模型

保护传输中的数据安全

etcd 支持自动 TLS 以及通过客户端证书进行身份验证,既包括客户端到服务器的身份验证,也包括对等(服务器到服务器/集群)通信的身份验证。请注意,默认情况下 etcd 不启用基于 RBAC 的身份验证 或传输层的身份验证功能,以减少用户开始使用数据库时的摩擦。此外,更改此默认设置将是对自 2013 年以来建立的项目的破坏性更改。未启用安全功能的 etcd 集群可能会将其数据暴露给任何客户端。

要开始运行,请首先为一个成员准备一个 CA 证书和一个签名的密钥对。建议为集群中的每个成员创建并签署一个新的密钥对。

为了方便起见,cfssl 工具提供了一个简单的证书生成接口,我们在此提供了使用该工具的示例。或者,可以尝试这个生成自签名密钥对的指南。

下面列出的标志可能由于持续的开发更改而不再是最新的。要获取最新的可用标志,请运行 etcd --help 或参考etcd 帮助文档。

基本设置

etcd 提供了几个与证书相关的配置选项,可以通过命令行标志或环境变量来设置:

客户端到服务器的通信:

--cert-file=<path>: 用于 SSL/TLS 连接到 etcd 的证书。当设置了此选项时,advertise-client-urls 可以使用 HTTPS 协议。

--key-file=<path>: 证书的密钥。必须是未加密的。

--client-cert-auth: 当设置此选项时,etcd 将检查所有传入的 HTTPS 请求是否包含由受信任的 CA 签名的客户端证书,不提供有效客户端证书的请求将失败。如果启用了身份验证,证书将为 Common Name 字段中提供的用户名提供凭据。

--trusted-ca-file=<path>: 受信任的证书颁发机构。

--auto-tls: 使用自动生成的自签名证书与客户端进行 TLS 连接。

对等(服务器到服务器/集群)通信:

对等选项的工作方式与客户端到服务器选项相同:

--peer-cert-file=<path>: 用于对等之间 SSL/TLS 连接的证书。这将用于监听对等地址以及向其他对等发送请求。

--peer-key-file=<path>: 证书的密钥。必须是未加密的。

--peer-client-cert-auth: 设置后,etcd 将检查来自集群的所有传入对等请求,以验证由提供的 CA 签名的有效客户端证书。

--peer-trusted-ca-file=<path>: 受信任的证书颁发机构。

--peer-auto-tls: 使用自动生成的自签名证书进行对等之间的 TLS 连接。

如果提供了客户端到服务器或对等证书,则也必须设置密钥。所有这些配置选项也可以通过环境变量 ETCD_CA_FILE, ETCD_PEER_CA_FILE 等进行设置。

常用选项:

--cipher-suites: 服务器/客户端和对等之间支持的 TLS 密码套件的逗号分隔列表(为空时将由 Go 自动填充)。

--tls-min-version=<version> 设置 etcd 支持的最小 TLS 版本。

--tls-max-version=<version> 设置 etcd 支持的最大 TLS 版本。如果不设置,则使用 Go 支持的最大版本。

示例 1:使用 HTTPS 的客户端到服务器传输安全

为此,请准备好 CA 证书 (ca.crt) 和签名的密钥对 (server.crt, server.key)。

让我们逐步配置 etcd 以提供简单的 HTTPS 传输安全:

$ etcd --name infra0 --data-dir infra0 \
  --cert-file=/path/to/server.crt --key-file=/path/to/server.key \
  --advertise-client-urls=https://127.0.0.1:2379 --listen-client-urls=https://127.0.0.1:2379

这应该可以正常启动,并且可以通过与 etcd 的 HTTPS 通信来测试配置:

$ curl --cacert /path/to/ca.crt https://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar -v

该命令应显示握手成功。由于我们使用的是自签名证书和自己的证书颁发机构,因此必须使用 --cacert 选项将 CA 传递给 curl。另一种方法是将 CA 证书添加到系统的受信任证书目录中(通常在 /etc/pki/tls/certs 或 /etc/ssl/certs 中)。

OSX 10.9+ 用户: OSX 10.9+ 上的 curl 7.30.0 不理解从命令行传递的证书。相反,可以直接将 dummy ca.crt 导入密钥链,或者向 curl 添加 -k 标志以忽略错误。要不带 -k 标志进行测试,请运行 open ./tests/fixtures/ca/ca.crt 并按照提示操作。请在测试后删除此证书!如果有解决方法,请告知我们。

示例 2:使用 HTTPS 客户端证书的客户端到服务器身份验证

现在我们已经赋予了 etcd 客户端验证服务器身份并提供传输安全的能力。然而,我们还可以使用客户端证书来防止未经授权访问 etcd。

客户端将向服务器提供其证书,服务器将检查该证书是否由提供的 CA 签名,并决定是否处理请求。

除了第一个示例中提到的文件外,还需要一个由同一证书颁发机构签名的客户端密钥对 (client.crt, client.key)。

$ etcd --name infra0 --data-dir infra0 \
  --client-cert-auth --trusted-ca-file=/path/to/ca.crt --cert-file=/path/to/server.crt --key-file=/path/to/server.key \
  --advertise-client-urls https://127.0.0.1:2379 --listen-client-urls https://127.0.0.1:2379

现在尝试向此服务器发出与上述相同的请求:

$ curl --cacert /path/to/ca.crt https://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar -v

请求应被服务器拒绝:

...
routines:SSL3_READ_BYTES:sslv3 alert bad certificate
...

要使其成功,我们需要将由 CA 签名的客户端证书提供给服务器:

$ curl --cacert /path/to/ca.crt --cert /path/to/client.crt --key /path/to/client.key \
  -L https://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar -v

输出应包括:

...
SSLv3, TLS handshake, CERT verify (15):
...
TLS handshake, Finished (20)

以及来自服务器的响应:

{
    "action": "set",
    "node": {
        "createdIndex": 12,
        "key": "/foo",
        "modifiedIndex": 12,
        "value": "bar"
    }
}

指定密码套件以阻止弱 TLS 密码套件。

当客户端问候请求包含无效的密码套件时,TLS 握手将失败。

例如:

$ etcd \
  --cert-file ./server.crt \
  --key-file ./server.key \
  --trusted-ca-file ./ca.crt \
  --cipher-suites TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

然后,客户端请求必须指定服务器中指定的其中一个密码套件:

# valid cipher suite
$ curl \
  --cacert /path/to/ca.crt \
  --cert /path/to/client.crt \
  --key /path/to/client.key \
  -L [CLIENT-URL]/metrics \
  --ciphers ECDHE-RSA-AES128-GCM-SHA256

# request succeeds
etcd_server_version{server_version="3.2.22"} 1
...
# invalid cipher suite
$ curl \
  --cacert /path/to/ca.crt \
  --cert /path/to/client.crt \
  --key /path/to/client.key \
  -L [CLIENT-URL]/metrics \
  --ciphers ECDHE-RSA-DES-CBC3-SHA

# request fails with
(35) error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure

示例 3:集群中的传输安全和客户端证书

etcd 对对等通信也支持相同的模型,即集群中 etcd 成员之间的通信。

假设我们有ca.crt以及两个由该 CA 签名的成员及其各自的密钥对(member1.crt & member1.key,member2.crt & member2.key),我们可以按如下方式启动 etcd:

DISCOVERY_URL=... # from https://discovery.etcd.io/new

# member1
$ etcd --name infra1 --data-dir infra1 \
  --peer-client-cert-auth --peer-trusted-ca-file=/path/to/ca.crt --peer-cert-file=/path/to/member1.crt --peer-key-file=/path/to/member1.key \
  --initial-advertise-peer-urls=https://10.0.1.10:2380 --listen-peer-urls=https://10.0.1.10:2380 \
  --discovery ${DISCOVERY_URL}

# member2
$ etcd --name infra2 --data-dir infra2 \
  --peer-client-cert-auth --peer-trusted-ca-file=/path/to/ca.crt --peer-cert-file=/path/to/member2.crt --peer-key-file=/path/to/member2.key \
  --initial-advertise-peer-urls=https://10.0.1.11:2380 --listen-peer-urls=https://10.0.1.11:2380 \
  --discovery ${DISCOVERY_URL}

etcd 成员将形成一个集群,并且集群中成员之间的所有通信都将使用客户端证书进行加密和身份验证。etcd 的输出将显示它连接到的地址使用 HTTPS。

示例 4:自动自签名传输安全

注意: 当您指定 ClientAutoTLS 和 PeerAutoTLS 时,etcd 自动生成的客户端证书和对等证书的有效期仅为 1 年。您可以使用--self-signed-cert-validity 标志来设置证书的有效期(以年为单位)。

对于只需要通信加密而不需要身份验证的情况,etcd 支持使用自动生成的自签名证书对其消息进行加密。这简化了部署,因为无需在 etcd 之外管理证书和密钥。通过使用--auto-tls和--peer-auto-tls标志配置 etcd,使其在客户端和对等连接中使用自签名证书:

DISCOVERY_URL=... # from https://discovery.etcd.io/new

# member1
$ etcd --name infra1 --data-dir infra1 \
  --auto-tls --peer-auto-tls \
  --initial-advertise-peer-urls=https://10.0.1.10:2380 --listen-peer-urls=https://10.0.1.10:2380 \
  --discovery ${DISCOVERY_URL}

# member2
$ etcd --name infra2 --data-dir infra2 \
  --auto-tls --peer-auto-tls \
  --initial-advertise-peer-urls=https://10.0.1.11:2380 --listen-peer-urls=https://10.0.1.11:2380 \
  --discovery ${DISCOVERY_URL}

自签名证书不验证身份,因此 curl 会返回错误:

curl: (60) SSL certificate problem: Invalid certificate chain

要禁用证书链检查,请使用-k标志调用 curl:

$ curl -k https://127.0.0.1:2379/v2/keys/foo -Xput -d value=bar -v

DNS SRV 注意事项

从 v3.1.0 版本开始(除了 v3.2.9),发现 SRV 引导认证ServerName与--discovery-srv标志中的根域名。这是为了避免中间人证书攻击,要求证书在其主题备用名称(SAN)字段中有匹配的根域名。例如,etcd --discovery-srv=etcd.local仅在提供的证书的主题备用名称(SAN)字段中有etcd.local作为条目时才认证对等/客户端。

etcd 代理注意事项

如果连接是安全的,etcd 代理将终止来自其客户端的 TLS,并使用--peer-key-file和--peer-cert-file中指定的代理自己的密钥/证书与 etcd 成员通信。

代理通过给定成员的--advertise-client-urls和--advertise-peer-urls与 etcd 成员通信。它将客户端请求转发到 etcd 成员的已发布客户端 URL,并通过 etcd 成员的已发布对等 URL 同步初始集群配置。

当为 etcd 成员启用客户端身份验证时,管理员必须确保在代理的--peer-cert-file选项中指定的对等证书对该身份验证有效。如果启用了对等身份验证,代理的对等证书也必须对该身份验证有效。

TLS 身份验证注意事项

从v3.2.0开始,TLS 证书在每次客户端连接时都会重新加载。这对于在不停止 etcd 服务器的情况下替换过期证书非常有用;可以通过用新证书覆盖旧证书来实现。每次连接刷新证书不应有太多开销,但将来可以通过缓存层进一步改进。示例测试可以在这里找到这里。

从v3.2.0开始,服务器拒绝带有错误 IP SAN的传入对等证书。例如,如果对等证书包含任何 IP 地址在主题备用名称(SAN)字段中,服务器只有在远程 IP 地址与这些 IP 地址之一匹配时才会认证对等方。这是为了防止未经授权的端点加入集群。例如,对等 B 的 CSR(使用cfssl)是:

{
  "CN": "etcd peer",
  "hosts": [
    "*.example.default.svc",
    "*.example.default.svc.cluster.local",
    "10.138.0.27"
  ],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "CA",
      "ST": "San Francisco"
    }
  ]
}

当对等节点 B 的实际 IP 地址是 10.138.0.2,而不是 10.138.0.27。当对等节点 B 尝试加入集群时,对等节点 A 会以错误 x509: certificate is valid for 10.138.0.27, not 10.138.0.2 拒绝 B,因为 B 的远程 IP 地址与 Subject Alternative Name (SAN) 字段中的 IP 地址不匹配。

自 v3.2.0 起,服务器在检查 SAN 时解析 TLS DNSNames。例如,如果对等节点证书的 Subject Alternative Name (SAN) 字段中只包含 DNS 名称(没有 IP 地址),则只有当这些 DNS 名称的正向查询(如 dig b.com)返回的 IP 地址与远程 IP 地址匹配时,服务器才会验证该对等节点。例如,对等节点 B 的 CSR(使用 cfssl)为:

{
  "CN": "etcd peer",
  "hosts": [
    "b.com"
  ],

当对等节点 B 的远程 IP 地址是 10.138.0.2。当对等节点 B 尝试加入集群时,对等节点 A 会查询传入主机 b.com 以获取 IP 地址列表(例如 dig b.com)。如果该列表不包含 IP 地址 10.138.0.2,则会以错误 tls: 10.138.0.2 does not match any of DNSNames ["b.com"] 拒绝 B。

自 v3.2.2 起,如果 IP 地址匹配,服务器将接受连接,而不检查 DNS 条目。例如,如果对等节点证书的 Subject Alternative Name (SAN) 字段中包含 IP 地址和 DNS 名称,并且远程 IP 地址与其中一个 IP 地址匹配,则服务器将直接接受连接,而不会进一步检查 DNS 名称。例如,对等节点 B 的 CSR(使用 cfssl)为:

{
  "CN": "etcd peer",
  "hosts": [
    "invalid.domain",
    "10.138.0.2"
  ],

当对等节点 B 的远程 IP 地址是 10.138.0.2 且 invalid.domain 是一个无效的主机。当对等节点 B 尝试加入集群时,对等节点 A 成功验证了 B,因为 Subject Alternative Name (SAN) 字段中有匹配的 IP 地址。有关更多详细信息,请参阅 issue#8206。

自 v3.2.5 起,服务器支持通配符 DNS SAN 的反向查询。例如,如果对等节点证书的 Subject Alternative Name (SAN) 字段中只包含 DNS 名称(没有 IP 地址),服务器首先会对远程 IP 地址进行反向查询,以获取映射到该地址的名称列表(例如 nslookup IPADDR)。然后,如果这些名称与对等节点证书的 DNS 名称(通过精确匹配或通配符匹配)匹配,则接受连接。如果没有匹配项,服务器将对对等节点证书中的每个 DNS 条目进行正向查询(例如,查询 example.default.svc 当条目是 *.example.default.svc 时),并且仅当主机解析的地址与对等节点的远程 IP 地址匹配时才接受连接。例如,对等节点 B 的 CSR(使用 cfssl)为:

{
  "CN": "etcd peer",
  "hosts": [
    "*.example.default.svc",
    "*.example.default.svc.cluster.local"
  ],

当对等节点 B 的远程 IP 地址是 10.138.0.2。当对等节点 B 尝试加入集群时,对等节点 A 会对 IP 10.138.0.2 进行反向查询以获取主机名列表。然后,通过精确匹配或通配符匹配这些主机名与对等节点 B 证书的 Subject Alternative Name (SAN) 字段中的 DNS 名称。如果反向/正向查询均未成功,则返回错误 "tls: "10.138.0.2" does not match any of DNSNames ["*.example.default.svc","*.example.default.svc.cluster.local"]。有关更多详细信息,请参阅 issue#8268。

v3.3.0 添加了 etcd --peer-cert-allowed-cn 标志,以支持 基于 CN(通用名称)的对等连接认证。Kubernetes 的 TLS 引导过程涉及为 etcd 成员和其他系统组件(例如 API 服务器、kubelet 等)生成动态证书。为每个组件维护不同的 CA 可以提供更严格的访问控制,但通常会很繁琐。当指定了 --peer-cert-allowed-cn 标志时,即使使用共享的 CA,节点也必须具有匹配的通用名称才能加入。例如,在一个 3 节点集群中,每个成员都使用 CSRs(通过 cfssl)设置如下:

{
  "CN": "etcd.local",
  "hosts": [
    "m1.etcd.local",
    "127.0.0.1",
    "localhost"
  ],
{
  "CN": "etcd.local",
  "hosts": [
    "m2.etcd.local",
    "127.0.0.1",
    "localhost"
  ],
{
  "CN": "etcd.local",
  "hosts": [
    "m3.etcd.local",
    "127.0.0.1",
    "localhost"
  ],

只有在指定了 --peer-cert-allowed-cn etcd.local 时,具有匹配通用名称的对等节点才会被认证。而具有不同 CSR 中的 CN 或不同 --peer-cert-allowed-cn 的节点将被拒绝:

$ etcd --peer-cert-allowed-cn m1.etcd.local

I | embed: rejected connection from "127.0.0.1:48044" (error "CommonName authentication failed", ServerName "m1.etcd.local")
I | embed: rejected connection from "127.0.0.1:55702" (error "remote error: tls: bad certificate", ServerName "m3.etcd.local")

每个进程应以以下方式启动:

etcd --peer-cert-allowed-cn etcd.local

I | pkg/netutil: resolving m3.etcd.local:32380 to 127.0.0.1:32380
I | pkg/netutil: resolving m2.etcd.local:22380 to 127.0.0.1:22380
I | pkg/netutil: resolving m1.etcd.local:2380 to 127.0.0.1:2380
I | etcdserver: published {Name:m3 ClientURLs:[https://m3.etcd.local:32379]} to cluster 9db03f09b20de32b
I | embed: ready to serve client requests
I | etcdserver: published {Name:m1 ClientURLs:[https://m1.etcd.local:2379]} to cluster 9db03f09b20de32b
I | embed: ready to serve client requests
I | etcdserver: published {Name:m2 ClientURLs:[https://m2.etcd.local:22379]} to cluster 9db03f09b20de32b
I | embed: ready to serve client requests
I | embed: serving client requests on 127.0.0.1:32379
I | embed: serving client requests on 127.0.0.1:22379
I | embed: serving client requests on 127.0.0.1:2379

v3.2.19 和 v3.3.4 修复了当 证书 SAN 字段仅包含 IP 地址而不包含域名 时的 TLS 重新加载问题。例如,一个成员使用 CSRs(通过 cfssl)设置如下:

{
  "CN": "etcd.local",
  "hosts": [
    "127.0.0.1"
  ],

在 Go 语言中,服务器仅在 (*tls.Config).Certificates 字段非空或 (*tls.ClientHelloInfo).ServerName 非空且客户端提供了有效的 SNI 时调用 (*tls.Config).GetCertificate 进行 TLS 重新加载。以前,etcd 在初始客户端 TLS 握手时总是填充 (*tls.Config).Certificates 字段,使其非空。因此,客户端始终需要提供匹配的 SNI 才能通过 TLS 验证并触发 (*tls.Config).GetCertificate 以重新加载 TLS 资源。

然而,如果证书的 SAN 字段 不包含任何域名而只包含 IP 地址,则会请求 *tls.ClientHelloInfo 具有空的 ServerName 字段,从而无法在初始 TLS 握手时触发 TLS 重新加载;这在需要在线替换过期证书时成为一个问题。

现在,在初始 TLS 客户端握手时,(*tls.Config).Certificates 被创建为空,首先触发 (*tls.Config).GetCertificate,然后在每次新的 TLS 连接时填充其余证书,即使客户端 SNI 为空(例如,证书仅包含 IP 地址)。

主机白名单注意事项

etcd --host-whitelist 标志指定了从 HTTP 客户端请求中接受的主机名。客户端来源策略可以防止对不安全的 etcd 服务器进行 “DNS 重绑定” 攻击。也就是说,任何网站都可以简单地创建一个授权的 DNS 名称,并将其指向 "localhost"(或其他地址)。然后,监听 "localhost" 的 etcd 服务器的所有 HTTP 端点都将变得可访问,从而容易受到 DNS 重绑定攻击。有关详细信息,请参阅 CVE-2018-5702。

客户端来源策略的工作原理如下:

  1. 如果客户端连接是通过 HTTPS 安全连接的,则允许任何主机名。
  2. 如果客户端连接不是安全的,并且 "HostWhitelist" 不为空,则只允许 Host 字段在白名单中的 HTTP 请求。

请注意,无论是否启用了身份验证,都会强制执行客户端来源策略,以实现更严格的控制。

默认情况下,etcd --host-whitelist 和 embed.Config.HostWhitelist 设置为空,以允许所有主机名。请注意,在指定主机名时,不会自动添加回环地址。要允许回环接口,请手动将它们添加到白名单中(例如 "localhost","127.0.0.1" 等)。

常见问题解答

在使用 TLS 客户端身份验证时,我遇到了 SSLv3 握手失败的问题?

golang 的 crypto/tls 包在使用证书公钥之前会检查其密钥用途。要使用证书公钥进行客户端身份验证,我们需要在创建证书公钥时将 clientAuth 添加到 Extended Key Usage 中。

以下是具体操作方法:

在 openssl.cnf 文件中添加以下部分:

[ ssl_client ]
...
  extendedKeyUsage = clientAuth
...

在创建证书时,请确保在 -extensions 标志中引用它:

$ openssl ca -config openssl.cnf -policy policy_anything -extensions ssl_client -out certs/machine.crt -infiles machine.csr

在使用对等证书身份验证时,我收到了“证书适用于 127.0.0.1,而不是 $MY_IP”的错误信息。

确保使用成员的公共 IP 地址作为主题名称来签署证书。例如,etcd-ca 工具为其 new-cert 命令提供了 --ip= 选项。

证书需要以其 FQDN 作为主题名称进行签名,并使用 Subject Alternative Names(简称 IP SANs)添加 IP 地址。etcd-ca 工具为其 new-cert 命令提供了 --domain= 选项,openssl 也可以实现这一点。

etcd 是否加密存储在磁盘驱动器上的数据?

不,etcd 不会对存储在磁盘驱动器上的键值数据进行加密。如果用户需要对存储在 etcd 中的数据进行加密,有以下几种选择:

  • 让客户端应用程序自行加密和解密数据
  • 使用底层存储系统的功能来加密存储的数据,例如 dm-crypt

当 etcd 创建某些新目录时,会将文件权限设置为 700 以尽可能防止非特权访问。但是,如果用户已经创建了一个具有自定义权限的目录,etcd 将使用该现有目录,并在权限与 700 不同时记录一条警告消息。


最后更新于 2025 年 6 月 3 日:递归地将 v3.6 的内容复制到 v3.7(a90b2a6)