checkId: check.smremote.remote-hsm.connectivity plugin: stellaops.doctor.security severity: fail tags: [security, crypto, hsm, smremote]

SmRemote Remote HSM Connectivity

This Doctor check verifies that the SmRemote remote-HSM forwarder (cn.sm.remote.http) is reachable and reports a healthy SM vendor-adapter contract. All SM2/SM3/SM4 signing and encryption rides this forwarder, so a dead remote HSM silently fails every SM crypto operation — and SmRemote validates the forwarder only at its own startup, leaving no runtime signal until this check was added (gap-matrix SmRemote-Remote-HSM, P0/P1 for SM-compliance deployments).

What It Checks

Resolves the SmRemote service endpoint from the canonical manifest entry integration.hsm.smremote — config keys SmRemote:Endpoint / Crypto:SmRemote:Endpoint, with the compose env alias STELLAOPS_SMREMOTE_URL accepted as a legacy fallback. The probe then GETs the SmRemote service’s own /healthroute:

ConditionResult
SmRemote not configured (SM regional crypto not deployed)skip (honest — never a false pass)
/health returns 200 with a healthy SM provider/adapter bodypass
/health returns 503 (or any non-success) — forwarder/remote-HSM unhealthyfail (remote-HSM-specific cause)
The SmRemote service is unreachablefail

The /health route is the deployment-correct aggregated signal: when the forwarder is configured (cn.sm.remote.http), that endpoint internally calls the remote HSM and returns its vendor-adapter contract (status / provider / adapterKind / contractVersion), answering 503 with a failureClass (adapter_http_error / adapter_timeout / a vendor-contract failure) when the remote HSM is unreachable, times out, or fails its contract. This is a state a bare “is the SmRemote service up?” probe would miss, because the SmRemote service itself is up while its remote HSM is dead.

Air-gap & non-destructive posture

The probe is reachability + capability/contract only. It never reaches the remote HSM directly — it asks the local SmRemote service for its already-computed health. It performs no signing operation, no key mint/delete, and no external network revocation (ADR-026 D5, air-gap invariant). On-prem / remote-provider only — never a cloud-managed KMS.

Secret safety

The SmRemote bearer API key (SM_REMOTE_HSM_API_KEY) is held by the SmRemote service, not by Doctor. This check never reads it, never sends it, and never places any secret in evidence — only the endpoint URL, the HTTP status, and public adapter/contract identifiers (provider, adapterKind, contractVersion) are recorded.

Why It Matters

When SM regional crypto is in use, every SM2 signature, SM3 hash, and SM4 encrypt/decrypt that the platform performs flows through this forwarder. A dead or contract-failing remote HSM means SM crypto operations fail closed platform-wide with no other Doctor signal. Catching it here turns a silent SM-compliance outage into a visible, actionable fail.

Common Causes

How to Fix

Docker Compose

# Inspect the SmRemote forwarder health (the aggregated remote-HSM signal):
curl -fsS http://smremote.stella-ops.local/health

# Verify the remote-HSM configuration (on-prem / remote provider only — never a cloud-managed KMS):
#   SM_REMOTE_HSM_URL, SM_REMOTE_HSM_TIMEOUT, SM_REMOTE_HSM_API_KEY
# Then re-establish the forwarder / HSM session:
docker compose -f devops/compose/docker-compose.crypto-provider.smremote.yml restart smremote

Bare Metal / systemd

# Confirm the SmRemote endpoint and probe its health:
curl -fsS "$STELLAOPS_SMREMOTE_URL/health"
# Restart the SmRemote service to re-establish the HSM session:
systemctl restart stellaops-smremote

Kubernetes / Helm

# Check the SmRemote pod and the remote-HSM config (Secret/ConfigMap):
kubectl get pods -l app=stellaops-smremote -n stellaops
kubectl logs -l app=stellaops-smremote -n stellaops --tail=100
# Roll the deployment to re-establish the forwarder once the HSM is reachable:
kubectl rollout restart deployment/stellaops-smremote -n stellaops

Verification

stella doctor run --check check.smremote.remote-hsm.connectivity