06
Authorize X2 node access
After every required connection is saved, the
X2 Admin UI displays one authorization
command for the entire X2 cluster. Node access material is
generated and stored automatically.
Set X2_KMS_ADDR and
X2_KMS_SERVER_CERT in the operator session. The
copied block securely prompts for X2_KMS_TOKEN,
runs on any unsealed KMS member, and removes the token from the
session afterward:
Illustrative shape — use the exact command generated by X2 after selecting the KMS host operating system
read -rsp 'KMS root token: ' X2_KMS_TOKEN; echo
export X2_KMS_TOKEN
/usr/lib/x2/x2-kms workloads authorize --workload-id "workload-name" --client-key "sha256:one-64-character-lowercase-hex-digest-per-node"
unset X2_KMS_TOKEN
$env:X2_KMS_TOKEN = Read-Host 'KMS root token' -MaskInput
& 'C:\Program Files\X2\x2-kms.exe' workloads authorize --workload-id "workload-name" --client-key "sha256:one-64-character-lowercase-hex-digest-per-node"
Remove-Item Env:X2_KMS_TOKEN
read -rs 'X2_KMS_TOKEN?KMS root token: '; echo
export X2_KMS_TOKEN
/usr/local/lib/x2/x2-kms workloads authorize --workload-id "workload-name" --client-key "sha256:one-64-character-lowercase-hex-digest-per-node"
unset X2_KMS_TOKEN
The command detects the Raft leader, authorizes every node
idempotently, creates the standard workload keys when needed,
grants the required generate, decrypt, and
encrypt-data-key operations, and commits the
encrypted authorization through consensus.
The root token remains in the operator shell and never enters
X2 Node or the Admin UI.
Run onAny unsealed KMS member
State changeReplicated workload authorization
Expected resultAuthorized through KMS consensus
No KMS YAML edit, service restart, or unseal cycle is required
for this authorization change.
Deploy an X2 KMS cluster
Start with one initialized voter and add members later without
creating a second keyspace. A joining member first enters as a
non-voter, receives the encrypted store while sealed, and uses
the original recovery shares. Promotion waits for Raft catch-up
and returns only after the member is a voter.
| Value | Meaning | Example |
KMS_API_HOST | Hostname used by X2 nodes and covered by API certificate SAN | kms.example.com |
KMS_API_PORT | KMS API port | 18200 |
KMS_OPERATOR_URL | URL used from this protected operator session | https://127.0.0.1:18200 |
KMS_MEMBER_ID | Unique joining Raft member ID | kms-2 |
KMS_PEER_HOST | Reachable hostname of that joining member | kms-2.example.com |
KMS_PEER_PORT | Joining member Raft port | 18201 |
X2_NODE_NAME | Storage node being connected to X2 KMS | storage-1 |
One local KMS instance per X2 storage node
Each X2 node may use its co-located KMS API endpoint, such as
https://127.0.0.1:18200. The KMS peer address must
still be uniquely reachable by the other KMS members. Local
unsealed replicas serve steady-state signing, data-key,
decrypt, and read operations from the shared keyspace. Send
membership, new-key creation, and rotation administration to
the current KMS leader.
Use an odd voter count across independent failure domains.
Additional co-located KMS instances may remain non-voters;
promote only the members selected for the voting set.
1. Configure and initialize the first member
In that member's kms.yaml, configure its unique
cluster.node_id, its routable
cluster.address (the Raft listen and advertised
address), an empty Raft storage directory, its peer
certificate/private key, and the shared peer CA. Start the
service with that configuration, confirm
members status reports the intended node ID and
peer address, then run step 4 exactly once on this first member.
2. Start a joining member without initializing it
Install X2 KMS on the new host and configure the same fields
with that member's own ID, peer address, certificates, encrypted
store path, and empty Raft directory. Copy the same peer CA and
workload authorization. Start the service and confirm
initialized=false. Never run
operator init on this member.
3. Add the joining member on the current leader
Run onCurrent KMS leader
State changeAdds one non-voter
Expected resultstate=non-voter
read -rsp 'Root token: ' X2_KMS_TOKEN; echo; export X2_KMS_TOKEN
export X2_KMS_SERVER_CERT=/var/lib/x2-kms/tls/kms-server.crt
/usr/lib/x2/x2-kms members add --address {{arg:KMS_OPERATOR_URL}} \
--node-id {{arg:KMS_MEMBER_ID}} \
--peer-address "{{plain:KMS_PEER_HOST}}:{{plain:KMS_PEER_PORT}}"
unset X2_KMS_TOKEN
$env:X2_KMS_TOKEN = Read-Host 'Root token' -MaskInput
$env:X2_KMS_SERVER_CERT = 'C:\ProgramData\X2KMS\tls\kms-server.crt'
& 'C:\Program Files\X2\x2-kms.exe' members add --address {{arg:KMS_OPERATOR_URL}} `
--node-id {{arg:KMS_MEMBER_ID}} `
--peer-address "{{plain:KMS_PEER_HOST}}:{{plain:KMS_PEER_PORT}}"
Remove-Item Env:X2_KMS_TOKEN
read -rsp 'Root token: ' X2_KMS_TOKEN; echo; export X2_KMS_TOKEN
export X2_KMS_SERVER_CERT=/usr/local/var/lib/x2-kms/tls/kms-server.crt
/usr/local/lib/x2/x2-kms members add --address {{arg:KMS_OPERATOR_URL}} \
--node-id {{arg:KMS_MEMBER_ID}} \
--peer-address "{{plain:KMS_PEER_HOST}}:{{plain:KMS_PEER_PORT}}"
unset X2_KMS_TOKEN
4. Confirm encrypted-state catch-up on the joiner
Run members status against the joiner's local API.
Continue when its node ID is correct, state is
Follower, suffrage is Nonvoter,
initialized=true, sealed=true,
caught_up=true, and
promotion_ready=true. The reported
applied_index must be at least
commit_index.
sudo env X2_KMS_SERVER_CERT=/var/lib/x2-kms/tls/kms-server.crt \
/usr/lib/x2/x2-kms members status --address https://127.0.0.1:{{plain:KMS_API_PORT}}
$env:X2_KMS_SERVER_CERT = 'C:\ProgramData\X2KMS\tls\kms-server.crt'
& 'C:\Program Files\X2\x2-kms.exe' members status --address https://127.0.0.1:{{plain:KMS_API_PORT}}
sudo env X2_KMS_SERVER_CERT=/usr/local/var/lib/x2-kms/tls/kms-server.crt \
/usr/local/lib/x2/x2-kms members status --address https://127.0.0.1:{{plain:KMS_API_PORT}}
Unseal the joiner locally with three distinct original shares.
It must not receive new shares and must not be initialized
independently.
5. Optionally promote the member
Skip this step when the co-located instance should remain a
non-voting local replica. To add it to the selected odd voting
set, run promotion on the leader. The command stages the member,
waits for Raft catch-up, and succeeds only after
members list reports Voter.
read -rsp 'Root token: ' X2_KMS_TOKEN; echo; export X2_KMS_TOKEN
export X2_KMS_SERVER_CERT=/var/lib/x2-kms/tls/kms-server.crt
/usr/lib/x2/x2-kms members promote --address {{arg:KMS_OPERATOR_URL}} --node-id {{arg:KMS_MEMBER_ID}}
/usr/lib/x2/x2-kms members list --address {{arg:KMS_OPERATOR_URL}}
unset X2_KMS_TOKEN
$env:X2_KMS_TOKEN = Read-Host 'Root token' -MaskInput
$env:X2_KMS_SERVER_CERT = 'C:\ProgramData\X2KMS\tls\kms-server.crt'
& 'C:\Program Files\X2\x2-kms.exe' members promote --address {{arg:KMS_OPERATOR_URL}} --node-id {{arg:KMS_MEMBER_ID}}
& 'C:\Program Files\X2\x2-kms.exe' members list --address {{arg:KMS_OPERATOR_URL}}
Remove-Item Env:X2_KMS_TOKEN
read -rsp 'Root token: ' X2_KMS_TOKEN; echo; export X2_KMS_TOKEN
export X2_KMS_SERVER_CERT=/usr/local/var/lib/x2-kms/tls/kms-server.crt
/usr/local/lib/x2/x2-kms members promote --address {{arg:KMS_OPERATOR_URL}} --node-id {{arg:KMS_MEMBER_ID}}
/usr/local/lib/x2/x2-kms members list --address {{arg:KMS_OPERATOR_URL}}
unset X2_KMS_TOKEN
Adding a member to an existing X2 KMS cluster is supported;
converting an independently initialized standalone KMS into a
member is not. Remove only empty, independently initialized
test state and restart the join procedure—never overwrite a
production encrypted store or Raft directory.
Evaluate X2 KMS locally
This disposable loopback route is separate from production. It
runs one foreground HTTP service, uses local paths, and must not
be exposed to another host.
Run onDisposable local workstation
ShellBashPowerShellBash on macOS
PrivilegeOrdinary local user
Expected resultForeground loopback service; no TLS
mkdir -p "$PWD/x2-kms-data"
./x2-kms server --address "127.0.0.1:{{plain:KMS_API_PORT}}" \
--storage "$PWD/x2-kms-data/store.json.enc"
$DataRoot = Join-Path (Get-Location) 'x2-kms-data'
New-Item -ItemType Directory -Force $DataRoot | Out-Null
.\x2-kms.exe server --address "127.0.0.1:{{plain:KMS_API_PORT}}" `
--storage "$DataRoot\store.json.enc"
mkdir -p "$PWD/x2-kms-data"
./x2-kms server --address "127.0.0.1:{{plain:KMS_API_PORT}}" \
--storage "$PWD/x2-kms-data/store.json.enc"
In another terminal, check
http://127.0.0.1:<selected-port>/health, initialize
once, and use the masked share procedure from step 4 with the
HTTP loopback address and no server-certificate variable. Delete
this disposable state only when it contains no data required by
any X2 object.