exlab_wizard.lims.keyring_store#
OS keyring storage with encrypted-at-rest fallback. Backend Spec §7.4.
Two backends, automatic dispatch:
OS keyring – the
keyringpackage routes to Keychain (macOS), Credential Manager (Windows), or Secret Service (Linux desktop) per §7.4. This is the preferred path because the OS already owns lifecycle (encryption-at-rest, scoping to the OS user).Encrypted-at-rest fallback – when no keyring backend is available (Linux headless acquisition machines per §7.4.4), the store writes a single Fernet-encrypted JSON document at
<state_dir>/exlab-wizard/secrets.enc. The Fernet key is derived from a master passphrase via Argon2id (time_cost=3,memory_cost=64 MiB,parallelism=4). The salt lives next to the ciphertext in the same file (the salt is not secret; the passphrase is).
The store API is intentionally credential-agnostic: it indexes secrets
by (KEYRING_SERVICE, username) so the LIMS credential
(username="lims") and per-equipment NAS credentials
(username="nas:<equipment_id>") share one store.
The passphrase_provider callable is invoked lazily on the first
fallback-mode operation so that working keyring environments never
prompt the operator. The launcher wires it to getpass.getpass per
§7.4.4 step 3; tests can pass a constant-returning lambda.
Classes
|
OS keyring with encrypted-at-rest fallback. |
- class exlab_wizard.lims.keyring_store.KeyringStore(*, state_dir, passphrase_provider=None)[source]#
Bases:
objectOS keyring with encrypted-at-rest fallback.
Backend Spec §7.4. Tries the OS keyring first via the
keyringmodule. When that backend raiseskeyring.errors.KeyringError(or one of its subclasses for “no backend installed”), falls back to<state_dir>/exlab-wizard/secrets.encencrypted with Fernet (AES-128-CBC + HMAC-SHA256), keyed by an Argon2id KDF over a user-supplied master passphrase.The fallback path requires a
passphrase_providercallable; if the keyring is available this argument may beNone– it is only invoked when the fallback engages.- delete_password(*, username)[source]#
Remove the secret stored under
(KEYRING_SERVICE, username).Missing entries are silent in both backends. Keyring errors defer to the fallback.
- get_password(*, username)[source]#
Look up the secret for
(KEYRING_SERVICE, username).Returns
Nonewhen the entry is absent in both backends. Errors from the OS keyring are caught and the fallback is consulted; errors from the fallback propagate asexlab_wizard.errors.KeyringUnavailableError.
- is_keyring_available()[source]#
Best-effort probe of the OS keyring backend.
Implemented as a round-trip
set+deleteof a sentinel value. A backend that raises on either side is considered unavailable. Per §7.4.4 thekeyringpackage’s “fail” backends raisekeyring.errors.KeyringErrorfrom these calls.- Return type: