Clustering

注釈

この機能は現在テクニカルプレビューで、以下の一時的な制限があります:

  • クラスタが失われた場合(クォーラムが失われた場合)、唯一の復旧手段はファクトリーリセット+リストアです。こまめにバックアップを取るようにしてください。将来のリリースでは、ディスク上のデータから復旧する手段が含まれる予定です。

  • etcd LearnerまたはMirrorを利用した、2ノードクラスタをサポートするアクティブ/パッシブセットアップはまだ利用できない。

  • ノード間のシステム時刻は、今のところ手動で同期する必要があります。将来のリリースでは、自動時刻同期機能が追加される予定です。

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``**ノードが健康で到達可能である限り運用を継続する。** 健康で到達可能なノードの最小数を**クォーラム** と呼びます。

これは次のようなシナリオを意味する。

1つのノードがダウンしても定足数に達する

3ノードクラスタでは、1つのノードに障害が発生しても(クラッシュしたり、ネットワーク状況により到達できなくなったりしても)、他の2つのノードは動作を継続し、リクエストに対応する。

障害が発生したノードがまだ健全である場合(例えば、単なるネットワークの問題であった場合)、隔離されている間は操作不能となる(読み取り専用にもならない)。

しかし、ノードが回復すれば、データを失うことなく、クラスタの残りの部分ときれいに再同期し、再び操作可能になる。

回復しない場合は、クラスターから外し(次のセクションを参照)、ファクトリーリセットして、もう一度ゼロから参加プロセスをやり直す必要がある。

ネットワーク・パーティションが発生しても定足数に達する

これは前のシナリオの一般化である。例えば3ノードがある物理的な場所Aにあり、2ノードが別の場所Bにある5ノードのクラスタでは、AとBを分離するネットワーク問題は次のようになります:

  • ロケーションAの3ノードは定足数(この場合は3)を満たしているので、運用を継続する。

  • ** ロケーションBの2ノードは、**、定足数(まだ3)を満たしていないため、(読み取り専用でも)動作を停止する。

  • ネットワークの問題が解決すれば、2つのノードは他の3つのノードときれいに結合する。

定足数は確実に失われる

クラスタのすべてのサブセットがクォーラムを失うような障害が発生すると、障害が解決されない限り、クラスタとそのデータは完全に失われます。この場合、ノードを工場出荷時にリセットし、バックアップを復元する必要があります。

これは例えば、2ノードクラスタ(クォーラムが2)で1つのノードが故障した場合に起こり得ます。このような状況では、障害が発生したノードを事後的にクラスタからきれいに削除することはできません。なぜなら、残っている健全なノードはクォーラムを失っているため、すでに操作不能になっているからです。

したがって、クラスタ内のノード数は常に奇数にし、頻繁にバックアップを取ることをお勧めする。

はっきりさせておくと、一時的に クォーラムを失っても(例えば、クラスタの全ノードをまとめて再起動する場合や、一時的なネットワーク障害でノードが孤立した場合など)問題はありません。クォーラムに達するだけのノードが(手動で再接続しなくても)再接続されれば、クラスタは通常の動作を再開します。ネットワーク・パーティション、ネットワークの誤設定、認証の問題、ハードウェアの故障などの恒久的な障害に限り、手動での対処が必要になります。

詳しくは、`etcdのよくあるご質問<https://etcd.io/docs/v3.6/faq/#why-an-odd-number-of-cluster-members>`__を参照のこと。

2ノードクラスタ

2ノードのアクティブ/パッシブクラスターはまだサポートされておらず、将来のバージョンで追加される予定です。3台目のNetHSMかetcdの "ウィットネス "を導入することをお勧めします。次のセクション「ウィットネス」を参照してください。

証人

``etcd``によるクラスタリングの性質上、クラスタ内のノード数が多いほど信頼性が高くなります。`Operational Redundancy`_のセクションで説明したように、クラスターは理想的には少なくとも3つのノードが故障する余地を持つべきです。

しかし、この機能の設計では、安定したノード数に到達するために、クラスタに完全な本物の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:

  • 設定/ドメインストア・バージョン

  • クラスタCA(クラスタ間のノード認証に使用)

  • backup passphrase and backup salt

今のところ、コンフィグ/ドメインストアのバージョンはバージョン1のみであることに注意してください(ソフトウェアのバージョンがクラスタリングをサポートしている場合、それがあなたのものです)。クラスタ内でソフトウェアアップデートをインストールする際の安全性についての詳細は、Software Updates in Clusters セクションを参照してください。

Creating a Cluster

どのクラスターも最初は1つのノードから始まります。新しいノードは1つずつクラスタに参加します。

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の作成とインストール

利用者は、少なくとも``keyCertSign``鍵の使用を許可することを確認した上で、自らの手段で、自らの運用上の制約に従ってCAを作成すべきである。

例えば、最小のCAは``openssl`` で作成できる:

$ 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(Subject Alt Names)フィールドが記入された証明書を持っていることを期待する。特に、IP経由でのみ到達することが期待されるノードは、証明書に適切なIP SANを持つ必要があります。IP SANは、``openssl``のように、名前の前に "IP: "を付けることでCSRに要求できる:

"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).

最後に、CA (CA.pem) を``/config/tls/cluster-ca.pem`` エンドポイントでインストールできるようになった (`API documentation<https://nethsmdemo.nitrokey.com/api_docs/index.html>`__ を参照)。これは、インストールされたTLS証明書がCAによって署名された場合にのみ可能です。そうでない場合、操作は拒否されます。

注釈

このプロセスをノードごとに繰り返さなければならない。

クロック同期

各ノードが正確なシステム時刻でプロビジョニングされていることを確認する。そうでない場合は、/config/time エンドポイントを使用してクロックを調整してください。

Adding a New Node

クラスタへのノードの追加は2つのステップで行う:

  • クラスタに追加を登録する(メンバーのいずれかを通して)

  • 新しいノードに参加するよう伝える

Configure a Backup Passphrase

まず、新しいジョイナーの登録に使用するノードでバックアップパスフレーズが設定されていることを確認する(``/config/backup-passphrase``エンドポイントのAPIドキュメントを参照)。

新規ノードの登録

警告

ノードを登録すると、たとえそのノードがまだ実際に参加していなくても、すぐに新しいノードがクラスタに導入され、クォーラムのしきい値が変更されます。このため、新しいノードが実際に参加するまで、既存のノードが操作不能になることがあります。このドキュメントの`API ドキュメント<https://nethsmdemo.nitrokey.com/api_docs/index.html>`__ および`Operational Redundancy`_ セクションを参照してください。

参加するノードのIPを手元に用意する。そのノードの完全な*URL* (etcd``の用語では*ピアURL* とも呼ばれる)は``https://<IP_of_node>:2380``(例:``https://192.168.1.1:2380)となる。ポート**** は2380でなければならないので、ノード間のファイアウォールがそのポートのTCPトラフィックを許可していることを確認してください。

URL が正しいかどうかは、GET /cluster/members を参加予定のノードで呼び出すことで再確認できる。これによって1つのメンバーだけがリストアップされるはずです。

次に、その URL をクラスタの既存のノードに登録します(まだクラスタがない場合は、クラスタの初期ノードとなる NetHSM で行います)。これには``POST /cluster/members`` エンドポイント(`API documentation<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 documentation<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

`新しいノードの登録`_セクションの通常の手順に従って、指定されたURL(s)を持つ新しいメンバーが追加されたことを既存のクラスタに通知します。

クラスタからの応答を書き留めてください。クラスタメンバーのリストとジョイナーキットが含まれているはずです(この部分は必要ありません)。

Configure etcd

デバイス ID を使用して)自動的にノード名を選択する NetHSM とは異なり、追加する各証人の名前を選択する必要があります。名前が一意であることを確認してください 。以下の例では「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`` 式で最後の2つの変数を自動的に生成することができます:

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)

例えば、`新しいノードを登録する`_セクションで提供されたレスポンスの例では、次のようになります:

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

etcd をお好みの方法(手動、systemd サービス、コンテナなど)で起動し、前のステップで作成した設定ファイルを指定する:

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

起動してクラスタに参加し、データに追いつくのを確認できるはずだ。しばらくすると、etcdctl クライアントで正常であることを確認できるはずだ:

etcdctl get /config/version

このキーは存在し、"1 "を含んでいなければならない。

クラスタの適切なメンバーになったので、このプロセスが実行され続けていることを確認してください。このプロセスを廃止する必要がある場合は、まずクラスタから適切に削除します(専用のセクションを参照)。到達可能なIPが変更された場合は、クラスタからそのURLを更新します。

Operating a Cluster

バックアップとリストア

バックアップ操作はクラスタがない場合と同じように動作し、クラスタのどのノードからでも要求できます。ノード固有のフィールドを含むクラスタ全体のデータがバックアップされます(ただし、プロビジョニングされていないノードでバックアップをリストアしない限り、これらのフィールドは無視されます)。

クラスタ上で実行されたバックアップは、その後ノードが追加または削除された場合でも、同じクラスタ上でリストアできます。運用クラスタ上で行われたこのようなリストアは、他の部分リストアと同様に、設定値には影響しません(キー、ユーザ、ネームスペースのみ)。

プロビジョニングされていないノードにバックアップをリストアすると、バックアップの作成に使用されたノードのノード固有のフィールド(ネットワーク構成、証明書など)がリストアされます。

大きなバックアップをリストアすると、リストアを適用するノードが他のノードに変更を転送している間、しばらくの間クラスタに負荷がかかることがあります。

この操作は、旧バージョンの NetHSM で作成されたバックアップと互換性があります。

注釈

異なるドメイン鍵で別のノードZに作成されたバックアップをノードAにリストア すると、Aのドメイン鍵は以前と同様に正しく書き換えられる。しかし、AがノードBとクラスタ内にあった場合、ZのドメインキーがB上にリストアされないため、Bは操作不能になる。

言い換えれば、同じクラスタでバックアップが行われたクラスタでのみリストアを実行してください(ただし、その後ノードが削除または追加されている可能性があります)。あるノードの外部バックアップをリストアしたい場合は、まずそのノードをクラスタから安全に削除し、ファクトリーリセットしてからバックアップをリストアします。

ノードをきれいに削除する

クラスタのどこかがまだ定足数を満たしている限り、そのメンバーのいずれかを使って、他のノードをクラスタから取り除くことができる。

まず、削除したいノードのIDを知る必要がある。GET /cluster/members を通してすべてのノードをリストアップし、適切なノードを探す。

その後、DELETE /cluster/members/<id> を呼び出して削除することができる。問題のノードがまだ健全であれば、これでクラスタの他のノードから切り離され、操作不能になる。

Software Updates in Clusters

今後のアップデートは、"cluster-safe"(これが大多数であるべきだ)または "cluster-unsafe "としてマークされる。

クラスタセーフ・アップデートは、クラスタの一部であるノードに対して、そのノードをクラスタから削除することなく適用できます。ただし、すべての操作と同様に、一度に1つのノードで、ノードを削除してもクォーラムを下回らないクラスタ内で(アップデートに失敗した場合など)、これを実行するようにしてください。

クラスタ安全でないアップデートは、分離されたノードに適用する必要があります。クラスタを解体し(ノードを1つずつ削除し)、1つのノードを除くすべてのノードをファクトリーリセットし、すべてのノードにアップデートを適用し、リセットしたすべてのノードを残りのノードに参加させる必要があります。

このような操作の前には、必ずバックアップを取ってください。

既存のクラスタの再構成

Changing the Cluster CA

** 既存のクラスタ(2つ以上のノードを持つ)**、運用中にクラスタCAを変更することはできません。この証明書を変更する必要がある場合は、ノードを選択し、他のノードをすべて削除し、CAを更新してから、他のメンバーを再参加させます。

Changing the Network Configuration of Nodes

あるノードのネットワーク設定を変更すると(IPを変更するなど)、他のノードにその更新が自動的に通知されます。ただし、このような更新は一度に1つのノードに対してのみ行い、そのノードを失ってもクォーラムが失われないクラスタ内で行うようにしてください。