Clustering

备注

该功能目前为技术预览版,有以下临时限制:

  • 如果集群丢失(法定人数丢失),唯一的恢复方法就是工厂重置+还原。确保经常备份。未来的版本将包括从磁盘数据恢复的方法。

  • 通过使用 etcd Learner 或 Mirror 来支持双节点集群的主动/被动设置尚未推出。

  • 节点之间的系统时间目前必须手动同步。未来的版本将包括自动时钟同步功能。

NetHSM 4.0 以后的版本支持集群,可在多个 NetHSM 之间直接同步数据。这支持高频率的密钥生成,实现了高可用性和负载平衡。NetHSM 集群基于`etcd<https://etcd.io>`__,它使用`Raft 共识算法<https://raft.github.io/>`__,以实现强一致性。这可以确保所有 NetHSM 中的数据(如密钥)在任何时候都是正确的。

在建立 NetHSM 集群之前,请先熟悉这项技术及其限制条件,以避免意外中断和数据丢失。除本文档外,您可能还需要参考`etcd 的文档<https://etcd.io/docs/latest/learning/>`__。

Operational Redundancy

我们将 "节点 "称为预计成为群集一部分的 NetHSM。一个由 ``N``**节点组成的群集将继续运行,只要至少有** ``(N/2)+1``**节点是健康和可连接的。** 健康、可连接节点的最小数量称为**法定人数** 。

这意味着会出现以下情况。

一个节点宕机,但仍能达到法定人数

在 3 节点集群中,如果一个节点出现故障(崩溃或因网络状况无法访问),其他两个节点将继续工作并为请求提供服务。

如果故障节点仍然健康(例如只是网络问题),它在隔离时将无法运行(甚至不能只读)。

但是,如果节点恢复,它将与群集的其他节点重新同步,并在不丢失数据的情况下恢复运行。

如果始终无法恢复,则必须将其从群集中删除(见下一节)、出厂重置,并从头开始重新进行连接过程。

发生网络分区,但仍能达到法定人数

这只是前一种情况的概括。在一个 5 节点集群中,3 个节点位于一个物理位置 A,2 个节点位于另一个物理位置 B:

  • 位置 A 中的 3 个节点符合法定人数(本例中为 3),因此它们继续运行。

  • 位置 B 中的 2 个节点**不符合** 法定人数(仍为 3),因此它们将停止运行(甚至只读)。

  • 如果网络问题得到解决,这 2 个节点将与其他 3 个节点顺利连接。

法定人数永久丧失

如果故障导致群集的所有子集失去法定人数,除非故障得到解决,否则群集及其数据将完全丢失。在这种情况下,必须在工厂重置节点并恢复备份。

例如,在一个 2 节点集群(法定人数为 2)中,如果单个节点发生故障,就会出现这种情况。在这种情况下,事后无法将故障节点从群集中清除,因为剩余的健康节点已经无法运行,因为它已经失去了法定人数。

因此,建议集群中的节点数始终为奇数,并经常备份。

To be clear, temporarily losing quorum (for example, if you are restarting all nodes of a cluster together, or a temporary network failure isolates nodes) is not a problem: once enough nodes are reconnected (without having to manually re-join) to reach quorum, the cluster will resume its normal operation. Only permanent failures such as network partitions, network misconfigurations, authentication issues or hardware failures, will require manual action.

更多信息,请参阅`etcd 的常见问题<https://etcd.io/docs/v3.6/faq/#why-an-odd-number-of-cluster-members>`__。

2 节点集群

目前还不支持双节点主动/被动集群,将在未来版本中添加。我们建议引入第 3 个节点,即第 3 个 NetHSM 或可在任何主机上运行的 etcd "见证"。请参见下一节 "见证"。

见证人

``etcd``集群的性质使集群中节点越多越可靠。如`Operational Redundancy`_ 部分所述,群集最好至少有 3 个节点,以便有发生故障的余地,因为如果只有一个节点发生故障,2 个节点的群集就会完全失效。

不过,该功能的设计使您不需要为集群添加完整、真实的 NetHSM 设备,就能达到稳定的节点数。相反,您可以自行部署并添加一个 "见证 "节点。这种节点只是``etcd`` 的一个实例,运行在您选择的机器上(或容器中),并连接到群集。群集中的真实设备会将其视为一个正常节点,并接收来自设备的所有数据和更新(当然,您无法对其执行任何 HSM 操作 - 它只能存储数据)。

Security Considerations

见证节点(或任何有访问权限的人)可直接访问群集中所有节点的存储后台(例如,您可以使用``etcdctl get "/" "0"`` 转储所有条目和相应值)。

不过,除了配置版本(/config/version,应始终为 "1")外,严格来说,所有值都经过加密(节点特定值使用设备密钥,其他值使用域密钥),以确保敏感数据的机密性。

但请注意,恶意节点可以

  • 将垃圾写入存储中的任何条目,这将导致节点解密失败(可能会导致某些系统条目崩溃)。

  • 列出条目名称,如用户、命名空间和键,你可能会认为这些名称比较敏感。

节点之间共享的内容

拥有 NetHSM 集群意味着它们之间共享大部分数据。对一个节点上的键、用户或命名空间的任何添加、修改或删除,最终都会反映到其他所有节点上。一般来说,任何修改状态的操作都会修改每个节点的状态。这包括备份**还原** 操作,该操作与正常操作一样。

下面几节将详细介绍哪些数据是完全本地的,哪些数据存储在共享的``etcd`` 存储器中,但仍是特定于节点的,以及哪些数据是跨节点完全共享的。

未存储在 etcd 中

每个节点的**设备密钥** 只存储在本地,不会在节点间共享。

存储在 etcd 中,但特定于节点

以下数据存储在``etcd`` 中,每个节点的作用域不同。因此,每个节点都可以访问** ,但在各节点之间,并不统一 (每个节点可以有不同的数据值)。

Configuration:

  • TLS certificates

  • clock configuration

  • network configuration

  • logging configuration

  • unattended boot configuration

  • 解锁盐(以便每个节点都有自己的解锁口令)

  • 锁定域键

请注意,虽然每个节点都有自己版本的锁定域密钥(因为每个节点都用自己的设备密钥或解锁口令锁定域密钥),但底层域密钥是**跨节点共享的** (用于访问它们共享的 HSM 数据,如密钥)。

存储在 etcd 中并共享

以下所有数据都存储在全局范围内的``etcd`` 中,因此在集群的所有节点上都是统一的:

HSM 数据:

  • keys

  • users

  • namespaces

Configuration:

  • config/domain 商店版本

  • 集群 CA(用于跨集群验证节点)

  • backup passphrase and backup salt

请注意,目前配置/域存储版本只能是版本 1(如果您的软件版本支持群集,那么您拥有的就是版本 1)。有关在群集中安装软件更新的安全性,请参阅`"群集中的软件更新 "部分。

Creating a Cluster

任何集群最初都是从一个节点开始的。新节点会一个接一个地加入集群。

Preparing Nodes

节点之间的网络通信使用 TLS 证书进行加密和验证。

预计将成为同一群集一部分的所有节点必须首先安装一个共同的证书颁发机构 (CA),以便验证其他节点是否合法。

在下文中,我们假定所有节点都是全新配置和运行的。

Networking

Nodes must first be reconfigured with their expected final network configuration using the /config/network endpoint (refer to the API documentation).

创建和安装 CA

用户应根据自己的操作限制,通过自己的方式创建 CA,确保它至少允许使用``keyCertSign`` 密钥。

例如,可使用``openssl`` 创建最小 CA:

$ openssl genrsa -out CA.key 2048 # create a key
$ openssl req -x509 -new -nodes -key CA.key -sha256 -days 1825 -out CA.pem -addext keyUsage=critical,keyCertSign

现在必须在每个节点上安装该 CA。

To do this, first generate a Certificate Signing Request (CSR) from the node with the /config/tls/csr.pem endpoint (refer to the API documentation).

备注

为了正确验证节点,集群后端(etcd)希望每个节点都有一个包含正确填写的主题别名(SAN)字段的证书。特别是,预计只能通过其 IP 访问的节点需要在证书中正确填写 IP SAN。IP SAN 可通过在名称前添加 "IP: "来请求 CSR,如``openssl``:

"subjectAltNames": [ "normalname.org", "IP:192.168.1.1" ]

有了获得的 CSR(我们称之为``nethsm.csr``),我们就可以为其生成证书,准备安装。例如``openssl``:

$ openssl x509 -req -days 1825 -in nethsm.csr -CA CA.pem -copy_extensions copy \
    -CAkey CA.key -out new_cert.pem -set_serial 01 -sha256

Then install the obtained new_cert.pem with the /config/tls/cert.pem endpoint (refer to the API documentation).

最后,现在可以通过``/config/tls/cluster-ca.pem`` 端点安装 CA (CA.pem)(请参阅`API 文档<https://nethsmdemo.nitrokey.com/api_docs/index.html>`__ )。这只有在安装的 TLS 证书已由其签名后才能实现。否则,操作将被拒绝。

备注

每个节点都必须重复这一过程。

时钟同步

确保每个节点都已配置准确的系统时间。如果没有,请使用``/config/time`` 端点调整它们的时钟。

Adding a New Node

向群集添加节点分两步进行:

  • 将新成员注册到群集(通过群集的任何一个成员)

  • 告诉新节点加入

Configure a Backup Passphrase

首先确保在用于注册新加入者的节点上配置了备份口令(请参阅``/config/backup-passphrase`` 端点的 API 文档)。

注册新节点

警告

注册节点会立即在群集中引入一个新节点,并修改法定人数阈值,即使该节点尚未实际加入。这会导致现有节点无法运行,直到新节点实际加入。请参阅`API 文档<https://nethsmdemo.nitrokey.com/api_docs/index.html>`__和本文档的`Operational Redundancy`_ 部分。

准备好要加入的节点的 IP 地址。该节点的完整*URL* (在``etcd`` 术语中也称为*peer URL* )将是``https://<IP_of_node>:2380``(例如``https://192.168.1.1:2380``)。的端口必须是 2380,因此要确保节点之间的防火墙允许该端口的 TCP 流量。

您可以在预期加入的节点上调用``GET /cluster/members``,仔细检查 URL 是否正确。这将只列出一个成员:它自己。

然后在群集的任何现有节点上注册该预期 URL(如果还没有群集,则在将作为群集初始节点的 NetHSM 上进行注册)。这项工作是通过``POST /cluster/members`` 端点完成的(请参阅`API 文档<https://nethsmdemo.nitrokey.com/api_docs/index.html>`__ ),将包含 URL 的 JSON 主体传递给它。

如果成功,则返回表单的 JSON 正文:

{
  "members": [
    {
      "name": "",
      "urls": [
        "https://172.22.1.3:2380"
      ]
    },
    {
      "name": "9ZVNM2MNWP",
      "urls": [
        "https://172.22.1.2:2380"
      ]
    }
  ],
  "joinerKit": "eyJiYWNrdXBfc2FsdCI6IkVlUzNPOEhHSEc5NnlNRktrdG1NZmc9PSIsInVubG9ja19zYWx0IjoiU3phMkEvYW13NlhxVWsrdHZMMmFubm5SZFlWd2ZQUjdpZ3IxK1RSdTdVaU14dmh3d0x2NWIvYVNkY2c9IiwibG9ja2VkX2RvbWFpbl9rZXkiOiIyMnNGVlkyelhQUVZ6S1pQenI3MmkwTk1WM3lmQ2k5dGwzeDhUbGtuOXM0WjFOd3JoZkRQTFZIVHp1WVl0YkQxaVZCMlovV3JHUHJlMXlwN0t4U0w4WkxjY2ZUTmUzcFg0WXE4YXNlY0wwREhXNGlIaXlPMlZnPT0ifQ=="
}

其中包含新节点加入集群所需的信息。特别是,它列出了群集的所有成员(其中名称为空的成员为新加入者)。它还包含由解锁口令和备份口令加密的域密钥,因此之前必须配置备份口令。

下一步请保留该回复。

实际加入群集

获取上一步的响应,并在其中附加一个``backupPassphrase`` 字段,该字段中包含新加入者所注册节点的备份口令,然后将该数据传递给预期加入节点上的``POST /cluster/join`` 调用(请参阅`API 文档<https://nethsmdemo.nitrokey.com/api_docs/index.html>`__ )。

假设群集和节点都能相互联系,这将执行实际的加入操作,擦除新加入者的数据,使其状态与群集状态同步。

根据网络和群集情况,此操作可能需要几十秒。如果此操作立即失败(如群集无法访问或身份验证失败),该节点的状态将不会被清除,加入将被恢复。但是,一旦首次加入成功,该操作就是最终操作,只能通过出厂重置来恢复。

如果连接成功,节点将处于``Locked`` 状态,必须使用注册时使用的节点的解锁口令才能解锁。之后,可以更改解锁口令(解锁口令仍针对特定节点,不会在节点间共享)。

备注

即使在加入成功后,如果集群的数据库很大或集群很忙,新加入者也可能需要一些时间才能完全同步其状态。在这段时间内,所有节点(尤其包括新加入的节点)的响应速度可能会降低或无响应。例如,在尝试解锁时,新加入的节点最初可能会返回错误。在这种情况下,请给它一些时间,然后再试一次。

Adding a Witness Node

Prepare a Witness

您需要一个``etcd`` v3.6 可用的环境,该环境的 IPv4 地址(至少)可以被群集的其他成员访问。需要允许往来于 2380 端口的 TCP 流量。

在``etcd`` 中创建一个存放数据的空目录,并写下其路径(我们将使用``/var/etcd/data``)。确保将启动进程的用户拥有读写该目录的权限。

将用于验证群集中节点的 CA 证书传输到计算机。您应该已在`创建和安装 CA`_ 部分创建了一个。我们将把它存储在``/var/etcd/CA.pem``。

然后,您需要为见证者创建一个证书,并在 CA 上签名,这样它就可以与同行通信了。例如,可以通过``openssl`` 来完成:

# Create a key
$ openssl genrsa -out witness.key 2048
# Create a CSR with a SAN that corresponds to the witness's IP or hostname
$ openssl req -new -sha256 -key own.key -subj "/C=US/ST=CA/O=MyOrg, Inc./CN=witness" \
    -addext "subjectAltName=IP:172.22.1.3" --out witness.csr
# Sign it
$ openssl x509 -req -days 1825 -in witness.csr -CA CA.pem -copy_extensions copy \
    -CAkey CA.key -out witness.pem -set_serial 01 -sha256

将生成的``witness.key`` 和``witness.pem`` 也存储在``/var/etcd`` 中。

Register Witness to Cluster

按照`Registering a New Node`_ 章节中的正常说明,用给定的 URL 向现有群集发出新成员加入的信号。

写下群集的回复:其中应包含群集成员名单和一个加入者工具包(您不需要这部分)。

Configure etcd

与 NetHSM 自动为自己选择节点名称(使用设备 ID)不同,您必须为添加的每个见证者选择一个名称,,确保名称是唯一的 。在以下示例中,我们将使用 "witness1"。

有了 NetHSM 登记证人的回复,准备表格的变量:

export ETCD_NAME="witness1"
export ETCD_DATA_DIR="/var/etcd/data"
export ETCD_INITIAL_CLUSTER="peer1=url1,peer1=url2,peer2=url1,peer2=url2,..."
export ETCD_INITIAL_ADVERTISE_PEER_URLS="my_url1,my_url2,..."

假设 NetHSM 的响应存储在``response.json`` 文件中,则可以通过以下``jq`` 表达式自动生成最后两个变量:

export ETCD_INITIAL_CLUSTER=$(jq --raw-output '[.members[] | ["\(if .name == "" then "witness1" else .name end)=\(.urls[])"]] | flatten | join(",")' < response.json)
export ETCD_INITIAL_ADVERTISE_PEER_URLS=$(jq --raw-output '.members[] | select(.name=="") | .urls | join(",")' < response.json)

例如,使用`Registering a New Node`_ 章节中提供的响应示例,您将得到以下结果:

ETCD_NAME="witness1"
ETCD_DATA_DIR="/var/etcd/data"
ETCD_INITIAL_CLUSTER="witness1=https://172.22.1.3:2380,9ZVNM2MNWP=https://172.22.1.2:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://172.22.1.3:2380"

最后,使用``docs/etcd_witness.conf.template`` 中提供的模板文件创建``etcd.conf.yml`` 文件:

$ envsubst < NETHSM_ROOT/docs/etcd_witness.conf.template > /var/etcd/witness.conf.yml
$ cat witness.conf.yml

这样你就能得到一个表格文件:

name: witness1
data-dir: /var/etcd/data
log-level: warn
log-format: console

listen-peer-urls: https://0.0.0.0:2380
listen-client-urls: http://localhost:2379

initial-advertise-peer-urls: https://172.22.1.3:2380
advertise-client-urls: http://localhost:2379
initial-cluster: witness1=https://172.22.1.3:2380,9ZVNM2MNWP=https://172.22.1.2:2380
initial-cluster-state: 'existing'

peer-transport-security:
  cert-file: witness.pem
  key-file: witness.key
  client-cert-auth: true
  trusted-ca-file: CA.pem
  skip-client-san-verification: true

Start etcd

以您喜欢的方式(手动、systemd 服务、容器等)启动``etcd``,将其指向上一步创建的配置文件:

$ cd /var/etcd
$ etcd --config-file witness.conf.yml

你应该能看到它启动、加入群集并获取数据。一段时间后,你应该可以通过``etcdctl`` 客户端检查它是否健康:

etcdctl get /config/version

该键应存在并包含 "1"。

确保该进程继续运行,因为它现在是群集的正式成员。如果需要退出,请首先将其从群集中删除(请参阅专用部分)。如果其可及 IP 发生变化,请从群集中更新其 URL。

Operating a Cluster

备份和恢复

备份操作与不使用群集时相同,可从群集的任何节点请求。它将备份整个群集的数据,包括节点特定字段(不过,除非在未配置的节点上恢复备份,否则这些字段将被忽略)。

在一个群集上完成的备份可以在同一个群集上还原,即使此后添加或删除了一些节点。在运行中的群集上进行的此类还原不会影响配置值(只有键、用户和命名空间),就像其他任何部分还原一样。

在未配置的节点上恢复备份将恢复用于创建备份的节点的特定节点字段(如网络配置、证书等)。

在应用还原的节点将更改转发给其他节点时,还原大型备份可能会使群集在一段时间内不堪重负。

此操作仍与 NetHSM 以前版本的备份兼容。

备注

在节点 A 上还原在另一个节点 Z 上使用不同域密钥制作的备份,会像以前一样正确重写 A 的域密钥。但是,如果 A 与节点 B 在一个群集中,B 将无法运行,因为 Z 的域密钥无法在 B 上还原。

换句话说,只能在具有在同一群集中完成的备份的群集中执行还原操作(尽管此后节点可能已被移除或添加)。如果要在某个节点上还原外来备份,首先要安全地将其从群集中移除,然后进行出厂重置并还原备份。

干净利落地删除节点

只要集群的某些部分仍符合法定人数,其任何成员都可以用来将另一个节点从集群中删除,无论该节点是已经无法访问还是预计将无法访问。

首先必须知道要删除的节点的 ID,通过``GET /cluster/members`` 列出所有节点,然后查找正确的节点。

然后,可以通过调用``DELETE /cluster/members/<id>`` 将其删除。如果有关节点仍然健康,这将使其与群集的其他节点隔离,并使其无法运行。

Software Updates in Clusters

未来的更新将被标记为 "集群安全"(这应该是大多数)或 "集群不安全"。

群集安全更新可应用于群集中的节点,而无需先将它们从群集中移除。不过,与所有操作一样,应确保一次只在一个节点上进行,并且在群集中删除一个节点不会低于法定人数(例如,如果更新失败)。

群集不安全更新必须应用于隔离节点。您应该拆除群集(逐个删除节点),出厂重置除一个节点外的所有节点,将更新应用到每个节点,然后让所有重置节点加入剩余节点。

确保在进行此类操作前进行备份。

重新配置现有群集

Changing the Cluster CA

现有群集(有两个或两个以上节点)**,在运行过程中不能** 更改其群集 CA。如果需要更改此证书:选择一个节点,移除所有其他节点,更新 CA,然后让其他成员重新加入。

Changing the Network Configuration of Nodes

修改一个节点的网络配置(如更改其 IP)会自动将更新通知其他节点。但应确保每次只在单个节点上执行此类更新,而且在群集中失去该节点不会失去法定人数。