You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
trident-acl-agent: verify Node systemUUID matches local hardware - #837
Fixes trident-acl-agent so a stale/recycled Node name can't be mistaken for this machine's own Node object. Opt-in, disabled by default.
Change
NodeClient::get_node and NodeClient::watch_node can verify the fetched Node's status.nodeInfo.systemUUID against this host's own /sys/class/dmi/id/product_uuid (the same source kubelet uses to populate systemUUID). On mismatch, logs a warning with both UUIDs and returns K8sClientError::NodeGone, so every caller - connection check, startup recovery, reconcile loop, and the long-lived watch stream - treats a misidentified Node exactly like a missing one.
Verification is skipped (logged at warn), not treated as a mismatch, when the Node's systemUUID is empty or the local product_uuid can't be read - either case only means the UUID is undetermined, not that the Node describes a different machine.
Gated behind TRIDENT_ACL_AGENT_KUBERNETES_VALIDATE_NODE_UUID, a real boolean config field (loaded via envy, like every other trident-acl-agent setting) that must be set to the literal string true to enable verification. Disabled (false) by default: the assumption that kubelet's systemUUID always traces back to this machine's product_uuid hasn't been confirmed across every environment this agent runs in, so it's opt-in per-deployment rather than unconditional.
Extracted the DMI product_uuid path/read into a shared osutils::dmi helper, now reused by trident-acl-agent and trident's tracestream metadata instead of each defining its own copy of the path and read-with-fallback logic.
Documented the config field and behavior (including watch-stream coverage) in docs/Explanation/Trident-ACL-Agent.md.
Re-validated end-to-end against a live VM after the env-var-to-envy-config migration: rebuilt the trident-acl RPM and VM image, ran the full storm-trident run aclagent suite - 7/7 PASS, including run-node-resilience's systemUUID mismatch phases.
NodeClient::get_node now compares the fetched Node's
status.nodeInfo.systemUUID against this host's own hardware
identity (/sys/class/dmi/id/product_uuid, the same source kubelet
uses to populate systemUUID). On mismatch, logs a warning with both
UUIDs and returns K8sClientError::NodeGone, so the agent treats a
misidentified Node the same as a missing one (retry/await recreation)
instead of acting on a Node object that does not describe this
machine.
If the local product UUID cannot be read, verification is skipped
(logged at warn) rather than failing closed.
NodeClient's new systemUUID verification (previous commit) treats
any Node whose status.nodeInfo.systemUUID doesn't match the local
/sys/class/dmi/id/product_uuid as not found. The storm E2E harness's
fake apiserver never set systemUUID on its seeded Node, so every
get_node call against it would now return NodeGone and run-ab-update,
run-rollback, and run-node-resilience would all fail immediately.
Fix: NewSeedNode takes the real systemUUID to seed onto the fake
Node, and each of the three call sites (RunABUpdate, RunRollback,
RunNodeResilience) reads it from the VM over SSH
(/sys/class/dmi/id/product_uuid) before constructing the node store.
Adds phase 3 to run-node-resilience: sets the fake Node's
status.nodeInfo.systemUUID to a value that does not match the VM's
real /sys/class/dmi/id/product_uuid, restarts trident-acl-agent, and
confirms the startup Node read (a) logs the mismatch distinctly from
a plain 404 and (b) still funnels into the same NodeGone /
await_node_recreation path as phases 1-2 (no crash, same PID,
resumes once the systemUUID matches again).
NodeStore gains SetSystemUUID for this, mirroring SetReadyCondition.
New fast unit tests in apiserver_test.go cover the fake apiserver
seeding/serving systemUUID correctly, independent of the full VM
harness.
Added run-node-resilience phase 3 (storm E2E) + fast Go unit tests covering the systemUUID-mismatch case end-to-end: sets a mismatched systemUUID on the fake Node, restarts trident-acl-agent, and confirms it (a) logs the mismatch distinctly from a plain 404 and (b) treats it the same as NodeGone — no crash, same PID, resumes once the systemUUID matches again. See tools/storm/aclagent/tests/node_resilience.go and proxies/apiserver_test.go.
cadvisor (which kubelet uses to populate status.nodeInfo.systemUUID)
reads /sys/class/dmi/id/product_uuid directly, same as our local check.
But if cadvisor fails to read that file on the kubelet side, it logs an
error and still lets node registration proceed with an empty
systemUUID rather than failing - an empty value is evidence kubelet
could not determine the UUID, not evidence the Node describes a
different machine. Treat it the same as an unreadable local file: skip
verification instead of treating it as a mismatch, to avoid a false
positive that would otherwise lock us out of a healthy node forever.
Pushed a follow-up fix after investigating whether AKS nodes guarantee status.nodeInfo.systemUUID matches /sys/class/dmi/id/product_uuid:
Confirmed via cadvisor source that kubelet populates systemUUID by reading the exact same file we check against. However, if cadvisor fails to read that file on the kubelet side, it logs an error but still lets node registration proceed with an empty systemUUID rather than failing. An empty value only means kubelet could not determine the UUID - it is not evidence the Node describes a different machine.
Updated verify_node_identity to skip verification (warn + Ok) when the Node reports an empty systemUUID, same as when our local file is unreadable, instead of treating it as a mismatch. Without this, a rare kubelet-side DMI read failure could have falsely locked us out of an otherwise-healthy node forever.
Updated/added unit tests accordingly (missing_node_system_info_skips_verification, empty_node_system_uuid_skips_verification).
/sys/class/dmi/id/product_uuid is root-only (0400). readVmProductUUID
ran a plain cat over SSH as the non-root test user, which failed with
Permission denied and aborted run-node-resilience before it could even
start (build 1220454: \cat: /sys/class/dmi/id/product_uuid: Permission
denied\). Every other privileged command in this harness is already
prefixed with sudo; this one was missed.
Fixed the storm run-node-resilience failure from build 1220454: readVmProductUUID ran a plain cat /sys/class/dmi/id/product_uuid over SSH as the non-root test user, which hit Permission denied (file is 0400 root-only) and aborted the whole aclagent scenario before any assertions ran. Every other privileged command in this harness already uses sudo; added it here too.
cargo fmt --check failed in CI (build 1220474, Check amd64 job) on the
single-line warn! added in the previous commit - rustfmt collapses it
onto one line rather than wrapping it.
Make the systemUUID-vs-local-product_uuid check opt-in via
TRIDENT_ACL_AGENT_VALIDATE_NODE_UUID (presence-only, same convention as
osutils::container::DOCKER_ENVIRONMENT). Disabled by default: the
assumption that kubelet systemUUID always traces back to this machines
own product_uuid is well-supported in general but has not been
confirmed across every environment this agent runs in, so it should be
validated and opted into per-deployment rather than enabled
unconditionally.
Enabled it for the storm aclagent E2E scenario via the
trident-acl-agent.service override.conf drop-in baked into both the
base and update VM images, so run-node-resilience Phase 3 (and the
general NodeGone handling) continues to exercise this path.
Documented the new env var and behavior in
docs/Explanation/Trident-ACL-Agent.md.
Addresses the remaining open item on PR #837: RunNodeResilience now has
a Phase 4 that triggers a systemUUID mismatch via NodeStore.SetSystemUUID
with the agent already running and watching - no restart - proving
NodeClient::watch_node catches it via verify_node_identity exactly like
phase 3 proves for the explicit-GET path, and that the same
NodeGone/recovery handling applies with a provably stable MainPID
throughout (no restart happened).
Validated end-to-end against a live VM: rebuilt trident-acl RPM from
this branch (v9882e27c), rebuilt trident-vm-acl-agent-testimage.qcow2
via Image Customizer (the prior artifact predated
TRIDENT_ACL_AGENT_VALIDATE_NODE_UUID being baked into the image config
and could not have exercised phase 3 or 4 at all), and ran the full
aclagent storm suite:
deploy-vm..........: PASS
check-deployment...: PASS
run-node-resilience: PASS (all 4 phases, including the new one)
run-ab-update......: PASS
run-rollback.......: PASS
collect-logs.......: PASS
cleanup-vm.........: PASS
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Issue: This presence-only gate still depends on the value being valid Unicode. Evidence:std::env::var returns Err(VarError::NotUnicode) for a present non-UTF-8 value, so get_node silently disables validation despite the documented “value is ignored” contract. Suggestion: use var_os(...).is_some() to test presence without decoding the value.
This issue also appears on line 170 of the same file.
Addresses review feedback on PR #837:
TRIDENT_ACL_AGENT_VALIDATE_NODE_UUID was implemented as a raw
std::env::var(...).is_ok() presence check in k8s.rs, bypassing the
envy-based AgentConfig/KubernetesConfig system every other
trident-acl-agent setting goes through. Two consequences:
- Non-Unicode safety: std::env::var() returns Err for a present value
that isn't valid UTF-8, silently disabling verification despite the
documented "presence-only, value ignored" contract.
- The var's name didn't follow the established
TRIDENT_ACL_AGENT_<SECTION>_<FIELD> convention (no section segment),
because there was no existing boolean-flag precedent in this config
system to follow in the first place.
Renamed to TRIDENT_ACL_AGENT_KUBERNETES_VALIDATE_NODE_UUID and added a
real `validate_node_uuid: bool` field to KubernetesConfig/
RawKubernetesConfig, loaded through the same envy::prefixed(...)
mechanism as every other Kubernetes setting (explicit "true"/"false",
default false, empty value falls back to the default like every other
field here). NodeClient now stores this as a plain bool at
construction time instead of re-reading the environment on every
get_node/watch_node call.
Updated the baked-in systemd override.conf, docs, and storm e2e
harness comments to the new name. Validated end-to-end against a live
VM: rebuilt the trident-acl RPM and trident-vm-acl-agent-testimage.qcow2
with this change, and re-ran the full aclagent storm suite - all 7
test cases pass, including run-node-resilience's systemUUID mismatch
phases (3 and 4), now driven by the new config field instead of the
old env var.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addressed in 7dc0304 — and more thoroughly than just the var_os swap originally suggested. Migrated TRIDENT_ACL_AGENT_VALIDATE_NODE_UUID onto this crate's established envy-based config system: renamed to TRIDENT_ACL_AGENT_KUBERNETES_VALIDATE_NODE_UUID, added a real validate_node_uuid: bool field to KubernetesConfig, loaded through envy::prefixed(...) like every other Kubernetes setting. NodeClient now stores this as a plain bool at construction instead of re-reading the environment on every get_node/watch_node call, which also fixes the Unicode-safety gap for free (envy's std::env::vars()-based collection skips non-UTF8 pairs rather than erroring, consistent with "malformed/absent" handling everywhere else in this config system).
Validated end-to-end against a live VM: rebuilt the trident-acl RPM and trident-vm-acl-agent-testimage.qcow2 with this change, and re-ran the full storm-trident run aclagent suite - all 7 test cases pass, including run-node-resilience's systemUUID mismatch phases (3 and 4), now driven by the new config field.
Issue: The identity check protects GET/watch inputs but not writes, so a Node name recycled during an in-flight operation can still be mutated by this host. Evidence: servicing pauses watch consumption while reconcile_node runs, and run_with_status_heartbeat calls publish_status repeatedly; patch_node_metadata patches by name without verifying identity or using a precondition. A replacement can therefore receive this machine's status annotations before its queued watch event is checked. Suggestion: when validation is enabled, base each PATCH on a verified read and add an atomic server-side precondition (for example, the verified object's resourceVersion), mapping mismatch/conflict to NodeGone.
Issue: This changes the existing telemetry asset_id representation instead of only sharing its reader. Evidence:dmi::read_product_uuid parses the raw text into OsUuid, whose Display canonicalizes valid UUIDs to lowercase (crates/sysdefs/src/osuuid.rs:36-40,57-62); the removed reader returned the trimmed sysfs text verbatim. An uppercase product UUID therefore acquires a different asset ID after upgrade, and PLATFORM_INFO is reused in telemetry and diagnostics. Suggestion: preserve the trimmed raw value for the telemetry path and parse/normalize only for the Kubernetes identity comparison, or expose separate raw and typed DMI readers.
🧠 Review effort: Balanced
This branch has not been deployed
No deployments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes trident-acl-agent so a stale/recycled Node name can't be mistaken for this machine's own Node object. Opt-in, disabled by default.
Change
trueto enable verification. Disabled (false) by default: the assumption that kubelet's systemUUID always traces back to this machine's product_uuid hasn't been confirmed across every environment this agent runs in, so it's opt-in per-deployment rather than unconditional.Testing
storm-trident run aclagentsuite - 7/7 PASS, including run-node-resilience's systemUUID mismatch phases.