Documentation menu

Configuration

AutoHSM configuration

AutoHSM reads one YAML file, strictly validated at startup. Unknown keys, loose file permissions, plaintext URLs and ambiguous PIN sources are refused.

Commands and flags

autohsm watch    [--config PATH]              run the daemon (systemd entrypoint)
autohsm status   [--config PATH]              print seal state; exit 2 if sealed
autohsm wrap     [--config PATH] --index N    wrap a share read from stdin
autohsm selftest [--config PATH]              verify config, TLS pin, HSM, and shares
FlagDefaultNotes
--config PATH/etc/autohsm/autohsm.yamlAlso accepted as --config=PATH
--index Nrequired for wrapShare index, 1 or higher. Bound into the wrapped envelope.

status never opens the HSM and prints sealed=… initialized=… threshold=… shares=… progress=… version=…. selftest checks the config, the pinned TLS connection, HSM login, that every configured share unwraps for this node, and refuses a node that holds enough shares to meet the unseal threshold alone.

Configuration file rules

RuleDetail
PermissionsRegular file, either mode 0600 owned by the user running autohsm, or mode 0640 owned by root. The same rule applies to the PIN file and wrapped shares.
Size1 MiB maximum
Strict keysUnknown keys are an error, so a mistyped security setting cannot be silently ignored
Single documentMultiple YAML documents are rejected
FIFOs and devicesRefused; symlinks are followed but the target must pass the same checks

Configuration reference

Top level

KeyTypeDefaultNotes
node_idstringrequiredOperator-chosen context label bound into every wrapped share. Not hardware identity. Changing it means re-wrapping the shares.

vault

KeyTypeDefaultNotes
vault.addressstringrequiredMust start with https://
vault.ca_cert_pathpathrequiredCA or self-signed leaf that signs the server certificate. No fallback to system roots.
vault.timeoutduration10sMust be positive
vault.require_tls13booltrueSet false only for a server that cannot negotiate TLS 1.3

keys

KeyTypeDefaultNotes
keys.sourcestringpkcs11pkcs11 for production, file for development only
keys.allow_insecure_file_sourceboolfalseMust be true to use source: file
keys.pkcs11.module_pathpathrequired for pkcs11PKCS#11 module, for example /usr/lib/softhsm/libsofthsm2.so
keys.pkcs11.token_labelstringfirst tokenOptional
keys.pkcs11.key_labelstringrequired for pkcs11Label of the non-extractable AES-256 wrapping key
keys.pkcs11.pin_filepathnot setFile holding the HSM PIN (4096 bytes max, trailing newline trimmed). Re-read for every login.
keys.pkcs11.pin_envstringnot setName of an environment variable holding the PIN. Refused by watch; for interactive provisioning only.
keys.shareslistrequiredShares this node holds; at least one
keys.shares[].indexintrequired1 or higher, unique
keys.shares[].pathpathrequiredFile holding the wrapped share

Exactly one of pin_file or pin_env must be set; inline PINs are not supported. At startup the selected key must be AES-256, sensitive, always-sensitive, non-extractable, never-extractable and allowed to encrypt and decrypt with AES-GCM, or the daemon fails closed.

watch

KeyTypeDefaultNotes
watch.intervalduration30sPolling interval, 1s minimum
watch.max_unseal_attemptsint5Consecutive rejected unseal cycles before the daemon stops trying. 1 minimum.

alarm

KeyTypeDefaultNotes
alarm.webhook_urlstringnot setOptional. https only, no credentials in the URL. Redirects are not followed; 5 second timeout.

Example configuration

/etc/autohsm/autohsm.yaml

# Install root:autohsm with mode 0640. Owner-only 0600 is also accepted.
node_id: node-a

vault:
  address: https://vault.example.com:8200
  ca_cert_path: /etc/autohsm/vault-ca.pem
  timeout: 10s
  require_tls13: true

keys:
  source: pkcs11
  pkcs11:
    module_path: /usr/lib/softhsm/libsofthsm2.so
    token_label: autohsm
    key_label: autohsm-wrap
    pin_file: /etc/autohsm/pin       # root:autohsm, mode 0640
    # pin_env: AUTOHSM_PIN           # interactive provisioning only
  # Keep this list shorter than the unseal threshold.
  shares:
    - index: 1
      path: /etc/autohsm/share-1.wrapped

watch:
  interval: 30s
  max_unseal_attempts: 5

alarm:
  # webhook_url: https://alerts.example.com/hooks/sealed

Development key source

keys:
  source: file
  allow_insecure_file_source: true   # no hardware protection
  shares:
    - index: 1
      path: ./share-1.wrapped

The file source reads a 64 hex character AES-256 key from AUTOHSM_DEV_KEY. It exercises the same envelope and binding logic but offers no hardware protection.

Environment variables

VariableUsed byNotes
Name set in pin_envstatus, wrap, selftestHSM PIN. Removed from the process environment after reading. Not allowed for watch.
AUTOHSM_DEV_KEYkeys.source: file64 hex characters (32 bytes)
AUTOHSM_TEST_MODULEmake test-integrationPath to libsofthsm2.so for the PKCS#11 integration test

Files and paths

PathPurpose
/etc/autohsm/autohsm.yamlConfiguration (default --config)
/etc/autohsm/pinHSM PIN file
/etc/autohsm/vault-ca.pemPinned CA for the server certificate
/etc/autohsm/share-N.wrappedWrapped share, format autohsm-v1.<nonce>.<ciphertext||tag>
/run/autohsm/submitted-sharesShare indexes accepted during the current unseal attempt, keyed to the server's unseal nonce, so a crashed daemon does not resubmit
/usr/local/bin/autohsmBinary location used by the shipped unit

Alarm webhook

With alarm.webhook_url set, the daemon POSTs JSON with only non-secret state:

{
  "event": "vault_sealed",
  "node_id": "node-a",
  "sealed": true,
  "threshold": 3,
  "shares": 5,
  "progress": 0,
  "timestamp": "2026-09-28T12:00:00Z"
}
eventWhen
vault_sealedEvery poll that finds the server sealed, even if the unseal then succeeds
vault_unreachableEvery poll that cannot reach the server
hsm_unavailableOnce when the HSM session is lost and cannot be reopened. Reconnects back off up to 5 minutes.
hsm_recoveredWhen the HSM session is back
shares_staleThe server rejected the key material itself. Submissions stop; reminders back off up to hourly.
autohsm_failedJust before the daemon exits on an error

error is present only on failure events. Delivery is best effort; pair it with an external monitor such as autohsm status from cron.

Exit codes

CodeMeaning
0Success
1General error (restarted by systemd)
2status found the server sealed
78Terminal failure in watch: rejected PIN, unloadable module, token without AES-GCM, missing or unsafe key, configuration error, stale shares or unsafe layout. The shipped unit does not restart on 78.
A wrong PIN exits 78 and is never retried, so a typo cannot count a hardware token down to lockout.

systemd unit

deploy/autohsm.service (excerpt)

[Unit]
After=network-online.target
StartLimitIntervalSec=5min
StartLimitBurst=5

[Service]
ExecStart=/usr/local/bin/autohsm watch --config /etc/autohsm/autohsm.yaml
Restart=on-failure
RestartPreventExitStatus=78
RestartSec=10s
User=autohsm
Group=autohsm
NoNewPrivileges=yes
ProtectSystem=strict
ReadOnlyPaths=/etc/autohsm
RuntimeDirectory=autohsm
RuntimeDirectoryMode=0700
UMask=0077
LimitCORE=0

For hardware HSMs, add only what the vendor module needs in a drop-in (systemctl edit autohsm): ReadWritePaths= for client state, ProcSubset=all for FIPS libraries, AF_NETLINK in RestrictAddressFamilies= for network HSM clients, and SupplementaryGroups= for a USB or PCIe token's udev group. Run selftest under the same properties with systemd-run before enabling.