checkId: check.crypto.pack.admission plugin: stellaops.doctor.security severity: fail tags: [security, crypto, supply-chain, trust-root]
Crypto Provider Pack Admission
This Doctor check reports the admission state of the mounted, signed crypto-provider packs (com.stellaops.crypto.{hsm,fips,gost,sm,openpgp,eidas}) and their trust-root — the supply-chain boundary for all regional crypto providers. It is read-only (report-only). Before it existed, pack admission had zero Doctor visibility: Doctor’s own mounted loader (DoctorMountedRuntimePluginLoader) only loads module==doctor plugins, so the crypto packs admitted by MountedSmCryptoRuntimePluginLoader were never observed (gap-matrix check.crypto.pack.admission, P0).
This is the CRYPTO pack trust-root — a distinct mount and trust boundary from the Doctor plugin trust-root. The Doctor plugin trust-root (
integration.trust.cosign-root, default/app/etc/certificates/trust-roots/plugins/doctor/cosign.pub) is covered bycheck.core.plugins.trustroot. This check covers the crypto pack trust-root (default/app/etc/certificates/trust-roots/plugins/crypto/cosign.pub) and the packs it admits.
What It Checks
Resolves the pack profile root and trust-root from the manifest entry integration.crypto.pack / the SmRemote:RuntimePlugins config section (with container defaults applied when unset):
SmRemote:RuntimePlugins:RootPath(default/app/plugins/crypto) +:Profile(defaultcrypto-sm) → the profile root<RootPath>/<Profile>SmRemote:RuntimePlugins:TrustRootPath(default/app/etc/certificates/trust-roots/plugins/crypto/cosign.pub)SmRemote:RuntimePlugins:AllowUnsigned(defaultfalse)
It enumerates each mounted bundle directory and reports a structural admission outcome by mirroring the loader’s admission chain without importing the loader or activating any assembly: each bundle must carry a manifest.json and a detached <assembly>.sig signature, and the configured trust-root file must be present.
| Condition | Result |
|---|---|
| No pack root mounted (regional crypto not deployed) | skip (honest — never a false pass) |
| Pack root mounted but empty | skip |
AllowUnsigned=true (admission boundary disabled) | fail |
| Trust-root missing while packs are present (loader would reject every pack) | fail |
A bundle is missing its manifest.json or detached <assembly>.sig (unsigned) | fail (names the rejected pack) |
| All mounted packs are signed and the trust-root is present | pass |
Read-only & secret safety
The check never admits an unsigned pack, never mutates a pack, never activates an assembly, and never prints key material. The trust-root is reported by path + presence only — its key bytes are never read or emitted. The actual cryptographic signature verification remains the loader’s job at startup (fail-closed); Doctor reports the structural pre-conditions that determine whether the loader will admit each pack.
Why It Matters
The signed crypto packs are the supply-chain boundary for every regional crypto provider (eIDAS / FIPS / GOST / SM). If AllowUnsigned is left enabled, an unsigned or tampered provider pack could be admitted and execute. If the crypto trust-root goes missing, the loader silently rejects every pack — the exact “checks vanish → all green” hole (ADR-026 D6) but for crypto providers: the provider quietly fails closed and a 200-looking platform hides a broken regional-crypto posture. This check makes that state visible.
Common Causes
SmRemote:RuntimePlugins:AllowUnsigned(SmRemote__RuntimePlugins__AllowUnsigned) was lefttruein a non-dev deployment- The crypto trust-root file (
cosign.pub) is absent atSmRemote:RuntimePlugins:TrustRootPath, or the trust-root mount is missing - A pack was dropped into the mount without going through the signing producer, so it has no detached
<assembly>.sig - The pack root / profile path is misconfigured and points at an empty or wrong directory
How to Fix
Remediation is manual and report-only. Doctor never admits an unsigned pack, mutates pack state, or alters the trust-root — mount the signed pack, restore the trust-root, or re-sign the bundle yourself.
Docker Compose
# Disable the unsigned escape hatch (the default) so admission fails closed:
# SmRemote__RuntimePlugins__AllowUnsigned=false
# Restore the CRYPTO trust-root (distinct from the Doctor plugin trust-root):
# place cosign.pub at /app/etc/certificates/trust-roots/plugins/crypto/cosign.pub
# Re-package a rejected pack WITH a signature (producer):
devops/build/package-runtime-plugins.ps1 -Module crypto -Profile crypto-sm \
-UseOfflineDevSigner -SignCryptoBundles
# Re-mount the signed pack(s) and restart the crypto host:
docker compose -f devops/compose/docker-compose.crypto-provider.smremote.yml up -d smremote
Bare Metal / systemd
# Ensure AllowUnsigned=false and the trust-root + signed bundles are in the configured paths,
# then restart the crypto host service:
systemctl restart stellaops-smremote
Kubernetes / Helm
# Mount the signed crypto pack and the crypto trust-root as read-only volumes; keep
# SmRemote__RuntimePlugins__AllowUnsigned=false in the Helm values, then:
kubectl rollout restart deployment/stellaops-smremote -n stellaops
Verification
stella doctor run --check check.crypto.pack.admission
Related Checks
check.core.plugins.trustroot— the Doctor plugin trust-root (a distinct mount/boundary from this crypto pack trust-root)check.smremote.remote-hsm.connectivity— the SmRemote remote-HSM forwarder (the other SM crypto path)check.core.integration-coverage— enumerates every declared integration; this check is the live owner ofintegration.crypto.pack
