feat(cli): let ca2a start demand caller attestation from config - #161
Conversation
PeerNode has carried require_caller_attestation, caller_verifier and challenge_ttl_seconds since the mutual-attestation work, but none of them were reachable from a config file, so a callee run through ca2a start could never appraise its callers. The attestation block now accepts all three. caller_verifier names a platform and a PEM roots file; only tpm can be built today (via ca2a_verify.tpm.tpm_verifier), and sev-snp or tdx fail at startup with the reason rather than appraising nothing. A hardware rung with no verifier is a CONFIG_ERROR naming the field. The startup line prints the rung, a node with no verifier is told hardware offers will be refused as unappraisable, and ConfigError.detail is now printed on the start path so the reason reaches the operator. Closes agentrust-io#160 Signed-off-by: Susan Poudel <susanpdl77@gmail.com>
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
imran-siddique
left a comment
There was a problem hiding this comment.
Approving. The gap is real and you found it in work you wrote yourself, which is the part I would keep doing: the knobs landed on PeerNode in #89 and #90, LIMITATIONS said mutual attestation was off by default, and through ca2a start it was off with no switch at all. Those are different statements and only one of them was true.
What makes this safe to take is the shape of the refusals rather than the plumbing.
The default stays REQUIRE_NONE, so nothing changes for an existing config and the LIMITATIONS wording remains accurate. An unrecognised require_caller_attestation raises ConfigError at load rather than falling through to the permissive value, which is the failure mode that matters here: a typo in a security knob must not read as "off".
The caller_verifier split is the better decision in this PR. The config vocabulary knows tpm, sev-snp and tdx, and only tpm can actually be built, so naming either of the others is a startup error carrying the specific reason: the SNP collector drops the auxblob rather than guess at its format, and TDX has no AttestationReport-level wrapper. The alternative, accepting the name and appraising nothing, produces a callee that looks like it verifies its callers and does not, and nothing downstream could tell the difference. Refusing at startup with the reason attached is the same principle as provider selection failing closed rather than downgrading to software-only, and it keeps an unavailable capability from reading as a satisfied one.
Green on all twelve real checks across the six matrix rows, vouch-gate included. The red gate in the rollup is a stale entry with a later success beside it. Tests in test_config.py, test_bootstrap.py and test_cli_start.py cover the three layers separately.
Closes #160.
What
ca2a startcan now turn on mutual attestation. Theattestationblock of the config acceptsrequire_caller_attestation(none|any|hardware),caller_verifier(platformplus atrusted_roots_pathPEM bundle), andchallenge_ttl_seconds, andbootstrap.build_peer_nodepasses them to thePeerNodearguments that already existed.Why
Closes #160. The knobs landed on
PeerNodein #89 and #90 but were never threaded through the config, so a callee run from the CLI could not appraise its callers and could not be told to. LIMITATIONS calls mutual attestation "off by default"; throughca2a startit was off with no switch. This is a gap in #52, which I wrote.Everything fails closed:
CONFIG_ERRORhardwarewith nocaller_verifieris aCONFIG_ERRORat load, naming the field to add (PeerNoderefuses this too, but the config layer can say which line)CONFIG_ERRORcaller_verifier.platform: sev-snportdxis refused at startup with the reason.verify_sev_snp_reportandverify_tdx_quoteexist but take raw evidence plus a certificate chain, and the SNP collector drops the auxblob rather than guess at its format, so neither can be built from a report and a roots file yet. Refusing is better than a config that looks like it appraises and does not. Onlytpmcan be built today, viaca2a_verify.tpm.tpm_verifier.Two small CLI changes came with it. The startup line prints the rung alongside the provider, and a node with no verifier is told that hardware offers will be refused as unappraisable (at every rung, per the spec), so the first hardware-attested caller does not look like a bug.
ConfigError.detailis now printed on thestartpath; before, the part of the message that says what to change was dropped.require_holder_proofis deliberately not exposed. It is on for every nodeca2a startbuilds; turning it off is a downgrade with no attestation rationale.Security impact
Positive: a config-driven callee can now demand caller attestation, which it previously could not. No change to the wire format, the record hash, or what
PeerNodeaccepts; the new config paths only add refusals. The SNP and TDX report-level verifiers are follow-up work and are named as such in the error.Test plan
pytestpasses (69 in the touched modules, full suite 614 passed, 2 skipped; the one local failure was a venv issue on my machine,test_docs_quickstartpasses with the path set)ruff checkandruff format --checkpassmypypassesbanditcleanca2a startwithrequire_caller_attestation: anyand no verifier prints the rung and the unappraisable note and binds; withhardwareand no verifier exits 1 namingattestation.caller_verifier; withplatform: sev-snpexits 1 with the VCEK reason; withplatform: tpmand the fixture root binds withrequire_caller_attestation=hardwareon the startup lineNew tests: config validation for each field and each rejection;
build_caller_verifierbuilds a real TPM verifier that fails closed on a bare report, refuses missing and empty roots, refusessev-snp/tdxwith a detail;build_peer_nodecarries the knobs; a node built from a config withrequire_caller_attestation: anyrefuses a caller offering nothing (ATTESTATION_FAILED) and accepts one attesting with the software provider (caller_attestation: software-only) on a live HTTP call; CLI tests for the startup line, the two refusals, and that the detail reaches stderr.DCO sign-off