Skip to content

chore(test-utils): regenerate attestation fixtures with a retained signer key - #4109

Merged
pbeza merged 24 commits into
mainfrom
3787-regenerate-fixtures
Aug 14, 2026
Merged

chore(test-utils): regenerate attestation fixtures with a retained signer key#4109
pbeza merged 24 commits into
mainfrom
3787-regenerate-fixtures

Conversation

@pbeza

@pbeza pbeza commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Needed by #4110, stacked on top: its sandbox tests submit a real attestation and must sign as the fixture node, which is impossible without the node's signer secret key.

The attestation fixtures in crates/test-utils/assets/ shipped the fixture node's public keys but not that key: it was never committed when they were captured, and the node it came from is long gone. Since the quote's report_data binds it, no test could sign as that node. The whole set is regenerated from a fresh capture, with near_account_secret_key committed alongside it and the verifier's expected values updated to match.

Exporting the key needs a pre_launch_script, which production verification rejects, so attestation/allow-pre-launch-script relaxes that one field where test code is built. The hook is committed as export-signer-key-prelaunch.sh so the capture can be repeated.

The rest is collection tooling and docs.

Closes #4111

This comment was marked as outdated.

@pbeza pbeza changed the title test(test-utils): regenerate attestation fixtures with a retained signer key chore(test-utils): regenerate attestation fixtures with a retained signer key Aug 10, 2026
@near near deleted a comment from claude Bot Aug 10, 2026
@near near deleted a comment from claude Bot Aug 10, 2026
@near near deleted a comment from claude Bot Aug 10, 2026
@near near deleted a comment from claude Bot Aug 10, 2026
@pbeza

pbeza commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

@claude review

@pbeza
pbeza requested a balanced review from Copilot August 10, 2026 16:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 30 out of 32 changed files in this pull request and generated no new comments.

@claude

This comment was marked as outdated.

@pbeza

pbeza commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

This comment was marked as outdated.

@pbeza
pbeza force-pushed the 3787-regenerate-fixtures branch from ca0eac4 to 6d48d11 Compare August 11, 2026 09:46
pbeza added 11 commits August 11, 2026 13:45
…tion

The MPC node generates its NEAR signer key inside the enclave and writes it
only to the CVM's encrypted disk. Collecting test assets needs that key
alongside the quote it is bound to, and an app-compose pre-launch script is
the only way in: the dev image ships no sshd and the guest agent serves only
node info and container logs.

vmm-cli already supports --prelaunch-script; this just plumbs an optional
PRELAUNCH_SCRIPT through. Attestation verification rejects any app-compose
carrying a script, so a CVM deployed this way cannot join a network - the
deploy guide, default.env and the script itself now say so loudly.
Collecting the attestation fixtures requires a pre-launch script to export
the node's in-enclave signer key, which makes the resulting app-compose one
that production verification rejects as arbitrary root code. The new
allow-pre-launch-script feature relaxes exactly that field for test builds;
init_script and bash_script stay rejected, and the production wasm never
enables it.

The check takes the policy as an argument so the strict behaviour keeps its
unit test in test builds too, and a further test pins the compiled-in policy
at the call site.
…ner key

Regenerated from a fresh localnet CVM on a TDX host, keeping the node's NEAR
signer key this time so tests can sign submit_participant_info as the fixture
node. The quote's report_data binds that key and the contract reads it from
the transaction signer, so without it the Verified store path is unreachable
(#3787).

create-assets.sh had been producing collateral our own parser cannot read
since the tee-verifier-interface refactor: nodes serialize the DER and
signature fields as JSON byte arrays while collateral_from_json expects hex
strings. It now hex-encodes those fields, and strips the NUL that terminates
the PEM chains so the fixture stays valid PEM. The regeneration reminder is
unconditional, since the committed key is normally present but stale.

.editorconfig no longer asserts the borsh fixture's final byte: it is
whatever the encoded collateral ends with.
… released artifacts

Implying allow-pre-launch-script from test-utils meant anything wanting only the
collateral_from_json parser also disabled the arbitrary-root-code check. The
feature is now named on the dependency edges that build tests, so every
enablement is greppable, and a new check fails the build if it reaches the
feature graph of the contract wasm, the node, attestation-cli (which verifies
attestations locally) or the verifier wasm.

Two tests replace one that could not fail: the committed keypair is checked
against near_account_public_key.pub, since create-assets.sh rewrites the public
key but cannot rewrite the secret; and the fixture app-compose is pinned as
satisfying the production policy once its export hook is removed, which is the
only place real attestation data meets the strict check.

Also drops doc claims about sandbox tests and a contract feature that live in
the follow-up, and records why the signer key has to be exported rather than
supplied to the node.
Replaces the helper that took the compile-time policy as a runtime argument
with `PRE_LAUNCH_SCRIPT_ALLOWED`, so every app-compose invariant reads inline
in `validate_app_compose_config`.

Drops the relaxation from this crate's own dev-dependency edge, so its unit
tests compile the production check. The two tests that only existed to pass
both values of the former argument go with it; CI builds `--all-features`, so
the strict branch was never compiled there anyway, and the feature-leak guard
is what keeps the relaxation out of released artifacts.
Multi-path updates replace the eight per-field pipeline stages, the hex
alphabet is bound once, and splitting on the NUL replaces the trim loop.

Records why the two committed fixtures encode the same bytes differently:
`public_data.json` is the endpoint response verbatim, so its byte fields are
JSON arrays, while the fixture parser reads hex.
Restores the Given/When/Then split in the fixture test, and stops the
localnet runbook from restating the collateral's `nextUpdate` date, which
goes stale on regeneration.
Removes `scripts/check-attestation-feature-leak.sh` and its `check-all-fast`
task. The comments that named it as the control now state the invariant it
checked: the relaxation is enabled only on dependency edges that no released
artifact builds.

Also corrects the `local-verify` comment, which claimed `mpc-contract` enables
it; the off-chain callers are the node, the attestation CLI and the TEE
authority.
Adds `export-signer-key-prelaunch.sh`, the `PRELAUNCH_SCRIPT` that exports the
node's in-enclave signer key, so the extraction is reproducible instead of
recoverable only from the fixture's measured app-compose.

`single-node.sh` now resolves a relative `PRELAUNCH_SCRIPT` against the repo
root: `deploy-launcher.sh` reads it from its own directory, so the documented
path was looked up two directories away and the deploy failed.

The runbook drops the steps duplicated from the asset README and gains the
paths the exported key actually lands in.
@barakeinav1

Copy link
Copy Markdown
Contributor

allow-pre-launch-script is a public feature, so a --all-features contract build would enable it. The comment says no released WASM can reach it — worth making that enforced rather than trusted:

#[cfg(all(target_arch = "wasm32", feature = "allow-pre-launch-script"))]
compile_error!("never enable in a wasm build");

Same for sandbox-test-hooks in #4110.

Comment thread crates/test-utils/assets/near_account_secret_key Outdated
@haiyuechen-nearone

haiyuechen-nearone commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

allow-pre-launch-script is a public feature, so a --all-features contract build would enable it.

Vibed with Claude to come up with this POC to gate the relaxation differently: #4131
@pbeza PTAL and see if that would be better

Comment thread localnet/tee/scripts/rust-launcher/export-signer-key-prelaunch.sh Outdated

@gilcu3 gilcu3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some minor comments and suggestions. Main concern, already expressed on slack, is that it is tricky to avoid including this escape hatch in prod images in the future. There is apparently an alternative by modifying the launcher docker compose that could achieve the same (extracting the near key) without having to change anything in the verifier. If that's correct, it makes sense to try that solution as it would avoid the foot gun here.

Comment on lines +42 to +46
jq -r 'def tohex:
if type == "array" then "0123456789abcdef" as $h
| map($h[(. / 16 | floor):(. / 16 | floor) + 1] + $h[(. % 16):(. % 16) + 1]) | join("")
else . end;
def strip_nul: if type == "string" then split("\u0000")[0] else . end;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would try to avoid this jq scripting if possible. Can we do something about this?

@pbeza pbeza Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but I don’t think it’s trivial. We should probably handle it in a follow-up issue since it’s a separate, debatable thing: #4140. I already addressed it in this stacked PR: #4143. Feel free to review.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this file we lost a couple of hex values and now have arrays. Should we try and get the opposite? Was this a change in dstack?

@pbeza pbeza Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(...) Was this a change in dstack?

Yeah, good question. I was trying to figure that out before asking for a review, but I couldn’t find any specific commit or PR where it changed. :(

Comment thread crates/test-utils/src/attestation.rs
Comment thread .editorconfig
[crates/tee-verifier/tests/fixtures/*.borsh]
end_of_line = unset
insert_final_newline = false
insert_final_newline = unset

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the difference?

@pbeza pbeza Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you revert it, you'll get this error:

rates/tee-verifier/tests/fixtures/verify_quote_args.borsh:
	No final newline expected
1 errors found

Explained by Claude:

The reason is a one-byte coincidence: main's borsh fixture happens to end with 00, so insert_final_newline = false was satisfiable there. Today's regenerated fixture ends with 0a, and the checker reads that trailing byte as a final newline. Since it's encoded payload, "fixing" it would corrupt the fixture.

pbeza added 2 commits August 13, 2026 12:46
Verification hashes the launcher compose and never reads it, so a service that
prints the node's `secrets.json` needs nothing relaxed. That replaces the
app-compose `pre_launch_script`, which production rejects as arbitrary root
code, so `allow-pre-launch-script` and its plumbing are gone from all five
crates and the check is unconditional again — with the rejection test the
feature had made impossible.

Fixtures recaptured on a TDX host with the collection compose. The app-compose
now carries no scripts at all, which a new test asserts.

The contract derives the one legal compose hash from a template compiled into
it, so the fixture's compose is not derivable and five contract tests register
its hash directly. The accommodation is test data rather than a policy switch in
shared code, so nothing can leak into a released artifact.

Also drops three test names from the asset runbook that no longer exist, in
favour of the crates that consume the fixtures.
@pbeza

pbeza commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

This comment was marked as outdated.

Comparing the second half of the secret key only read back the public key it
already carries, so a malformed seed passed. Deriving it proves the pair.

Also notes in the asset runbook that the committed key is test-only, since
scanners flagging it will otherwise keep raising the question.
@pbeza
pbeza requested review from barakeinav1 and gilcu3 August 13, 2026 15:43
pbeza added 2 commits August 13, 2026 21:11
The rust-launcher README pointed readers at step 3 of the asset regeneration
steps, whose command is relative to the directory step 1 changes into, and the
curl snippet used $AGENT_PORT as if single-node.sh exported it. The test command
also omitted tee-authority, which consumes the fixture.
@pbeza

pbeza commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

@gilcu3 @haiyuechen-nearone @barakeinav1 could you please re-review? Thanks!

gilcu3
gilcu3 previously approved these changes Aug 14, 2026

@gilcu3 gilcu3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

Comment thread crates/test-utils/assets/README.md Outdated
Comment thread localnet/tee/scripts/rust-launcher/single-node-readme.md Outdated
pbeza added 2 commits August 14, 2026 09:35
The test command runs tests in those crates rather than the crates themselves,
and "the collection variant" named a concept the single-node readme never
introduces.
/// Test-only: allows one more compose hash for an already-allowed launcher. The attestation
/// fixture is captured from a CVM whose launcher compose carries a key-export service, so
/// [`get_docker_compose_hash`] cannot derive its hash.
#[cfg(test)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

@pbeza
pbeza added this pull request to the merge queue Aug 14, 2026
Merged via the queue into main with commit 50328c6 Aug 14, 2026
15 checks passed
@pbeza
pbeza deleted the 3787-regenerate-fixtures branch August 14, 2026 11:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regenerate the attestation test fixtures with a retained signer key

5 participants