Repository navigation
fix: serve resolves provider and Auto node identities again (TC-1442) - #359
Conversation
main.rs and lib.rs lost `pub mod cli` and the `cli::run` dispatch in the PR #145 merge (aa6b161), so `tinycloud node ...`, `serve`, `--help` and `--version` started the server. Restore the dispatch for those tokens only. No arguments and `--validate-config` keep the existing server start path (container entrypoint, deploy preflight). Adds tests/node_cli.rs.
Every other argv goes to clap, so help, -v, -x -h, --config x, servee and --validate-config --help no longer boot a server. Drop the CLI's no-subcommand arm and run_legacy_server, unreachable from main now. Tests drive the built binary: exact usage errors, node status meaning, --validate-config valid and invalid, no-arg server path.
aa6b161 replaced app's key_provider::resolve_identity_state(.., Serve) with resolve_keys, whose Provider arm bails and whose Auto arm bails when no TEE is present. `node service install` writes type = "provider", so an installed node's `serve` exited 1, and a config without a key section could not start either. resolve_keys now returns None for those cases (Provider, Auto with TINYCLOUD_TEE_MODE=off, Auto with no dstack socket) and app_with_control resolves the node's stored identity through the key provider, generating it on first start, then publishes it to the control plane with set_identity_snapshot so node status reports identityReady. Static and dstack keys resolve exactly as before. node_cli's no-argument test relied on Auto failing; it now makes the server start fail on a share-email config error instead, which is still reported by the server start path before any key or socket is opened.
…identities (TC-1442) Auto now creates a local node identity only when nothing indicates a dstack TEE. With TINYCLOUD_TEE_MODE=dstack, tee.mode = "Dstack", TINYCLOUD_KEYS_TYPE=Dstack, DSTACK_SIMULATOR_ENDPOINT, or a dstack socket in a build without dstack, and no dstack key, startup fails with an error naming the indication and writes no identity, so a TEE node can never come up under a new DID. TINYCLOUD_TEE_MODE=off is still checked first and opts out of detection. Provider keys are unaffected. Creating an identity now logs once at WARN, "created a new node identity at <location> (<did>)", replacing the INFO "generated node identity secret on first run". A restart that loads the identity logs nothing. No key material is logged; KeyProvider gains location() for the message.
|
APPROVE: see TC-1442 (reviewed exact head 570cb45) |
keys.type = provider now runs the same dstack_indication check Auto uses and fails startup, writing no identity, when the environment indicates a dstack TEE, including production's TINYCLOUD_KEYS_TYPE=Dstack overridden by TINYCLOUD_KEYS__TYPE=provider. A provider node with no marker starts as before. dstack_indication now matches variable names case-insensitively, as figment's TINYCLOUD_ prefix does, so tinycloud_keys_type=Dstack counts.
|
APPROVE: see TC-1442 (reviewed exact head 08dc5a1) |
…to fix/tc-1442-serve-provider-identity
|
APPROVE: see TC-1442 (main-merge only; patch-id unchanged vs 08dc5a1; head 50099b1) Detail: merged #355's updated head 5d22a77 (which contains origin/main 9573f0e) into the approved head 08dc5a1 with a merge commit, no conflict. The PR diff against #355's head is unchanged: patch-id --stable 8a3cf020ca444dc0969c4b344404994c66d1f63a before and after, and the added/removed lines are byte-identical per file (lib.rs, node_control/key_provider.rs, routes/node_keys.rs, tests/node_cli.rs, tests/startup_identity.rs). Waiver per the release plan. A clean textual merge does not prove behaviour on the new main (main changed lib.rs by ~580 lines), so CI must be green at this head, and the startup_identity tests must have run, before merge. |
TC-1442 S0a'. Stacked on #355 (base
fix/tc-1442-rewire-node-cli, head bb04778); the two should ship together.Problem
aa6b161 (#145) replaced
key_provider::resolve_identity_state(.., IdentityPurpose::Serve)inappwithresolve_keys. Two of its arms bail:Keys::Provider: "provider key mode must be resolved through the node control key-provider path".node service installwrites[global.keys] type = "provider"(service.rs,write_bootstrap_config_if_absent), sotinycloud serve --config <that file>exits 1 and the installed unit (Restart=on-failure) crash-loops.Keys::Auto(the default) with no dstack socket, or withTINYCLOUD_TEE_MODE=off: "No key source configured" / "TEE mode disabled ...". At 8722442 these generated and stored the node identity.ControlPlaneHandle::set_identity_snapshotalso lost its only caller, so the control plane kept theProbesnapshot taken at spawn and reportedidentityReady: falsefor a freshly generated identity.Change
resolve_keysreturnsResult<Option<StaticSecret>>.Nonereplaces the Provider bail and the two Auto bails. The Static and dstack arms are unchanged apart from theSomewrapping.On
None,app_with_controlcallskey_provider::resolve_identity_state(Some(&keys), datadir, IdentityPurpose::Serve), the same call 8722442 made. It generates and stores the identity on first start, then handsidentity_snapshotto the control plane when one is attached.Auto fails closed where a TEE is indicated. Auto creates a local identity when
TINYCLOUD_TEE_MODEis notoffand the dstack paths returned no key: no dstack socket, or a build without dstack. In that caseresolve_keysnow refuses (Failed to start tinycloud-node, exit 1, no identity written) if any of these is present:TINYCLOUD_TEE_MODE=dstack(case-insensitive). A dstack build already asks the socket for this value and fails if it is missing; a build without dstack used to fall through and mint.tee.mode = "Dstack"(TINYCLOUD_TEE__MODE=Dstack)TINYCLOUD_KEYS_TYPE/TINYCLOUD_KEYS__TYPE= Dstack, for Auto reached through an override. Variable names match case-insensitively, as figment'sTINYCLOUD_prefix does, sotinycloud_keys_type=Dstackcounts too.DSTACK_SIMULATOR_ENDPOINTsetExample:
refusing to create a new node identity: tee.mode = "Dstack" indicates a dstack TEE, but no dstack key is available (socket /var/run/dstack.sock). Start the node with its dstack socket, or remove tee.mode = "Dstack" if this host is not a TEE.Auto creates a local identity only when none of these is present.Provider keys refuse under the same check, whether or not an identity already exists:
refusing keys.type = provider: TINYCLOUD_KEYS_TYPE=Dstack indicates a dstack TEE, where the node key must come from dstack. Use keys.type = Dstack, or remove TINYCLOUD_KEYS_TYPE=Dstack if this host is not a TEE.(exit 1, no identity written).TINYCLOUD_TEE_MODE=offdoes not exempt provider keys. A provider node with no marker starts as before.Production's signal is
TINYCLOUD_KEYS_TYPE=Dstackplus the mounted/var/run/dstack.sock(every dstack compose andops/rollback/*; none setsTINYCLOUD_TEE_MODE). That selectsKeys::Dstack, never Auto, so production does not reach this branch. A build without dstack rejectstype = Dstackat config load.TINYCLOUD_TEE_MODE=offkeeps today's meaning. It is checked first, skips socket detection and every indication above, and creates or loads a local identity. On its own it does not load, because figment also maps it totee.mode = "off", which is not aTeeModevariant. That is unchanged, and it is whyscripts/tc959-docker.mjsalso setsTINYCLOUD_TEE__MODE=Off.Creating an identity logs once, at WARN:
created a new node identity at <location> (<did>). The location is the encrypted-file path, or the keychain item on macOS, and no key material is logged. This replaces the old INFOgenerated node identity secret on first run. A restart that loads the identity logs nothing. The node DID is the public value served at/.well-known/tinycloud/node-keys. Only Provider and Auto nodes create identities, and production uses dstack keys, so this line does not appear in production logs.tests/node_cli.rsno_arguments_keep_the_server_start_pathdepended on Auto failing. With Auto restored, a baretinycloudin an empty HOME generates an identity and serves. The test now makes the server start fail on a share-email config error, which the server start path reports before it opens a key or binds. The assertions are unchanged.Nothing else changes. No shipped deploy config uses Provider or Auto: the dstack composes,
ops/rollback/*,scripts/ci/fixtures/tc959-n0-compose.yamlanddocker.yml:784set Dstack;tinycloud.toml,deploy/personal-node/*,deploy/share-email/*andtest/docker-compose.ymlset Static.Tests (
tests/startup_identity.rs, built binary)installed_provider_config_serves_a_stable_identity(Linux)node service install(stubsystemctl, temp HOME/XDG, encrypted-file provider), thenserve --config <installed file>. Asserts node-keys answers, the log has exactly onecreated a new node identity at <installed data>/keys/identity.key.enc (<did>),node status --jsonhasidentityReady: trueand the samenodeDid, and a restart serves the same DID with no creation line.installed_config_without_a_key_section_serves_a_stable_identity(Linux)[global.keys]section removed (Auto)bare_start_without_a_tee_creates_one_identity_and_reloads_it(Linux)tinycloud, no config, no TEE indication: serves, writesdata/keys/identity.key.enc, logs creation once; restart serves the same DID with no creation lineauto_refuses_to_create_an_identity_where_a_tee_is_indicated(Linux)tinycloudwith Auto keys under each indication above, plus production's plainTINYCLOUD_KEYS_TYPE=Dstack, no socket: exits non-zero, nodata/keys, no creation line, the refusal names the indicationprovider_refuses_where_a_tee_is_indicated(Linux)tinycloudwithTINYCLOUD_KEYS__TYPE=providerplus each ofTINYCLOUD_KEYS_TYPE=Dstack,TINYCLOUD_TEE_MODE=Dstack,TINYCLOUD_TEE__MODE=Dstack,DSTACK_SIMULATOR_ENDPOINT, no socket: exits non-zero, nodata/keys, no creation line, the refusal names the markershipped_config_with_a_static_secret_keeps_its_didtinycloud.toml+TINYCLOUD_KEYS__SECRETfixture, baretinycloudandserve --configdstack::production_dstack_config_keeps_its_did(--features dstack)tinycloud.toml+TINYCLOUD_KEYS_TYPE=Dstack(production's env), baretinycloud, stub guest agent onDSTACK_SIMULATOR_ENDPOINTtinycloud/keys/primaryThe pinned DIDs were read from the base build, not computed by the new code. Each test also checks that its pin is the fixture key's
node_did(). All fixture keys are throwaway constants.Mutation check: with the
set_identity_snapshotcall removed, the provider test fails onidentityReady: false, nodeDid: null.CI's default job does not enable
dstack, so the dstack DID test runs only withcargo test -p tinycloud-node --features dstack --test startup_identity. I ran that locally.Local runs (with
CIunset):cargo fmt --all -- --check,cargo clippy --workspace --all-targets -- -D warnings,cargo clippy -p tinycloud-node --all-targets --features dstack -- -D warnings,cargo test -p tinycloud-node(all pass; the Postgres tests skip because no URL is set), andcargo test -p tinycloud-node --features dstack --lib --test startup_identity --test node_cli(all pass).Interaction with TC-887 (#325, #326, #327)
app_with_controlafter this change's lines:refuse_unknown_teein the dstackget_infoerror arm,AtRestRuntime::new,at_rest::setup(.., key_setup.as_bytes(), ..), and.manage(at_rest_runtime). A trial merge (git merge-tree) with each PR head adds no conflicts beyond the ones those PRs already have against the current tree (tinycloud-core/src/migrations/mod.rs, andconfig.rsfor feat(TC-887): node wiring for at-rest keys and escrow recovery #327).lib.rsmerges cleanly.Not changed
servebuilds its tokio runtime incli.rsblock_on, soROCKET_WORKERSandROCKET_MAX_BLOCKINGare ignored on that path, andservereads--config(or the profile path), notTINYCLOUD_CONFIG_FILE. For service-installed nodes this makes no difference today. The generated unit sets noEnvironment=and always passes--config, and with those variables unset the tokio defaults (one worker per CPU, 512 blocking threads) match Rocket's. It would matter only if an operator added those variables to the unit. The production container runs with no arguments, which goes throughrocket::executeand honours the Dockerfile'sROCKET_MAX_BLOCKING=1024. Not changed here.