Repository navigation
acl-agent: make node_name source (hostname vs kubelet cert CN) configurable - #839
bfjelds (bfjelds) wants to merge 6 commits into
Conversation
…urable AKS does not guarantee the lowercased-hostname convention trident-acl-agent currently assumes for node_name (per the AKS team). Add TRIDENT_ACL_AGENT_KUBERNETES_NODE_NAME_SOURCE (hostname | kubelet-cert) to opt into deriving node_name from the Subject CN (system:node:<name>) of kubelet own client certificate (/var/lib/kubelet/pki/kubelet-client-current.pem) instead - the identity kubelet itself authenticates to the API server with, so it is guaranteed to match the real Node name. Default stays hostname for backward compatibility. kubelet-cert falls back to hostname (with a warning) if the cert cannot be read or parsed, rather than failing startup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
Adds run-node-name-source to the storm-trident aclagent scenario, proving TRIDENT_ACL_AGENT_KUBERNETES_NODE_NAME_SOURCE actually switches node_name resolution between hostname and kubelet-cert. Uses one-shot \ trident-acl-agent --validate-connection kubernetes\ CLI invocations against a self-signed fake kubelet client cert rather than restarting trident-acl-agent.service, so the check stays fast: no service restart, no reboot. Seeds the single-node fake apiserver with the cert Subject CNs node name (distinct from the VMs real hostname), so resolving to the wrong source always 404s - the two sources cannot accidentally agree. Covers: kubelet-cert resolves correctly, hostname/unset resolves to the (non-matching) real hostname and fails, an explicit NODE_NAME override still wins regardless of source, and a missing/unreadable cert degrades kubelet-cert to the hostname behavior with a logged warning instead of erroring out some other way. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Added to the scenario: proves actually switches node_name resolution between bfjelds-xubuntu and , via one-shot CLI invocations against a self-signed fake kubelet client cert - no service restart or reboot needed, so it stays fast relative to the rest of the scenario.\n\nCovers: kubelet-cert resolves correctly; hostname/unset resolves to the VMs real hostname and fails against this test ' s single-node fake apiserver; an explicit override still wins regardless of source; a missing/unreadable cert degrades kubelet-cert to the hostname behavior with a logged warning rather than erroring out some other way.\n\nVerified: (unchanged), and clean for . |
|
Added Covers: kubelet-cert resolves correctly; hostname/unset resolves to the VM's real hostname and fails against this test's single-node fake apiserver; an explicit Verified: |
…warning stormssh.SshCommandCombinedOutput discards the captured remote output on a non-zero exit (innerSshCommand returns (\ \, err)); the actual stdout/stderr only survives inside the wrapped errors own message. Check err.Error() instead of the (always-empty-on-error) output string. Verified end-to-end on bfjelds-xubuntu: full storm-trident aclagent scenario now passes 8/8, including run-node-name-source. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Rebuilt binaries/RPMs/VM images and ran the full Along the way, fixed a bug in |
…nd exact-value assertions
Replaces the single shared fake apiserver (one node, mixed
pass/fail-based assertions) with three sequential phases, each its own
single-node apiserver seeded with exactly the identity that phase is
expected to resolve to:
- kubelet-cert source -> the certs Subject CN
- hostname source (explicit + default), and kubelet-cert with a
missing/unreadable cert -> the VMs real hostname, read live via
SSH rather than assumed equal to --node-name
- explicit NODE_NAME override -> a third, unrelated literal
Every check now asserts the *exact* Node name validate-connections
own success output reports fetching (via a new
expectValidateConnectionOutput helper), not just pass/fail - a phase
can only pass by resolving to the value it claims, never by
coincidentally matching another phases answer or by the absence of a
negative case. Addresses feedback that the explicit-override check
previously reused the same literal as the cert-derived name, and that
the hostname checks should assert the VMs actual hostname rather than
only prove source selection via expected failures.
Verified end-to-end on bfjelds-xubuntu: full storm-trident aclagent
scenario passes 8/8.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed review feedback on Redesigned into three sequential phases, each with its own single-node fake apiserver (the fake only ever serves one Node name at a time) seeded with exactly the identity that phase expects:
Every check now asserts the exact Node name Verified end-to-end on |
|
CI validation: build 1221319 on
All three distinct literals resolved correctly and independently, matching the per-phase apiserver design. No further changes needed. |
…ostname Per feedback: if TRIDENT_ACL_AGENT_KUBERNETES_NODE_NAME_SOURCE=kubelet-cert is explicitly requested and the cert cant be read/parsed, treat it as a fatal config error - not a silent degrade to hostname. An operator who opted into kubelet-cert did so because hostname is not a trustworthy Node identity on their platform; silently falling back to it on cert failure risks the agent reconciling against the wrong Node entirely, same reasoning the agent already applies to its own Node disappearing (NodeGone is fatal, not papered over). - default_node_name() is now fallible (Result<String, Error>); AgentConfig::from_vars propagates kubelet-cert resolution failure as a wrapped failed to determine node_name config error, the same severity as any other malformed/unsatisfiable TRIDENT_ACL_AGENT_* setting. - impl Default for KubernetesConfig calls hostname_node_name() directly (NodeNameSource::default() is always Hostname, which cant fail), avoiding a fake unwrap on a Default impl that must be infallible. - Updated storm run-node-name-source test case: missing/unreadable cert with kubelet-cert source now asserts a fatal failed to determine node_name error (via err.Error(), since stormssh.SshCommandCombinedOutput discards output on non-zero exit) instead of a successful fallback-to-hostname resolution. - Updated docs (Trident-ACL-Agent.md, storm README, TridentAclAgent-Tests.md) to describe fail-hard behavior instead of the old fallback. Verified end-to-end on bfjelds-xubuntu: cargo test/fmt/clippy clean (16 config tests pass), full storm-trident aclagent scenario 8/8 PASS. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed feedback: Changed:
Verified on |
… handling Per feedback: a missing/unreadable kubelet-cert at startup should be treated the same way as the agents own Node object not existing yet - retried with capped exponential backoff, not a fatal process crash. - KubernetesConfig.node_name renamed to node_name_override (Option<String>, explicit override only); node_name is no longer eagerly resolved during AgentConfig::from_vars, which is infallible again for this field. - New KubernetesConfig::resolve_node_name_once(): single, fail-fast resolution (override, else node_name_source). Used directly by --validate-connection kubernetes (connection_check.rs), where fail-fast remains correct for a one-shot diagnostic. - Orchestrator gains its own resolved node_name: String field and Orchestrator::resolve_node_name_with_retry(), which calls resolve_node_name_once() in a loop with the same NODE_READ_BACKOFF/NODE_READ_BACKOFF_MAX capped-exponential-backoff policy as get_node_with_retry, racing every sleep against shutdown - exactly the same treatment already given to a Node 404 (NodeGone). Orchestrator::from_config now takes &CancellationToken and returns Result<Option<Self>, Error> (None on shutdown during retry), mirroring recover_from_trident_state/await_node_recreations own Ok(None)/Ok(false) early-exit shape. - main.rs updated for the new from_config signature/return type. - New Rust unit tests in orchestrator.rs prove the retry loop actually retries (Ok(None) on a cancelled shutdown, not an immediate Err) for KubeletCert, and resolves without any retry for Hostname/explicit override. - Updated run-node-name-source storm test: missing-cert phase now checks --validate-connections fail-fast error text (failed to resolve node_name) instead of the old fatal-config-error text: deliberately does NOT attempt to prove the long-running retry loop from Go/storm, since that needs a controllable cert path (out of scope here) to deny-then-grant without flaking on real timing. - Updated docs (Trident-ACL-Agent.md, storm README, TridentAclAgent-Tests.md) to describe the retry behavior. Verified end-to-end on bfjelds-xubuntu: cargo test -p trident-acl-agent (206 tests, incl. 3 new retry-behavior tests) + fmt + clippy all clean; full storm-trident aclagent scenario 8/8 PASS. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
To directly answer the question: previously, no — exponential backoff retry was NOT happening. A missing/unreadable Fixed: an unreadable Changes:
Verified on |
|
CI validation: build 1221392 on Full
|
There was a problem hiding this comment.
🟡 Changes recommended
The description contradicts the final behavior, and several tests depend on the host lacking a readable kubelet certificate.
2 open findings
What changed in this PR
Adds configurable ACL-agent Kubernetes node-name resolution using either the hostname or kubelet certificate CN.
Changes:
- Adds node-name source configuration and certificate parsing.
- Adds retry behavior and connection-validation handling.
- Adds unit, Storm integration, and documentation coverage.
| File | Description |
|---|---|
tools/storm/aclagent/trident.go |
Registers the new Storm test. |
tools/storm/aclagent/tests/node_name_source.go |
Tests node-name source behavior end to end. |
tools/storm/aclagent/README.md |
Documents the Storm test. |
docs/Explanation/Trident-ACL-Agent.md |
Documents the environment variable. |
docs/Development/Testing/TridentAclAgent-Tests.md |
Updates scenario documentation. |
crates/trident-acl-agent/src/main.rs |
Handles shutdown during resolution. |
crates/trident-acl-agent/src/core/config.rs |
Implements source configuration and certificate parsing. |
crates/trident-acl-agent/src/connection_check.rs |
Resolves node names for diagnostics. |
crates/trident-acl-agent/src/annotations/orchestrator.rs |
Adds retrying node-name resolution. |
crates/trident-acl-agent/Cargo.toml |
Adds the OpenSSL dependency. |
Cargo.lock |
Records the dependency update. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| fn default_node_name_errors_when_cert_source_requested_but_unreadable() { | ||
| // DEFAULT_KUBELET_CLIENT_CERT won't exist on a dev/test machine, so | ||
| // requesting KubeletCert must error out - no silent fallback to | ||
| // Hostname (see NodeNameSource::KubeletCert's doc comment for why). | ||
| let err = default_node_name(NodeNameSource::KubeletCert).unwrap_err(); |
There was a problem hiding this comment.
🟡 Changes recommended
Certificate validation and Storm test isolation contain unresolved correctness issues.
5 open findings
Empty node certificate CN is accepted as a valid node name · New Test setup restarts a service despite one-shot isolation · New Fake certificate replacement loses and fails to restore original state · New Make certificate resolution tests independent of host state Update PR description for final no-fallback behavior
🧠 Review effort: Balanced
| cn.strip_prefix(KUBELET_CERT_CN_NODE_PREFIX) | ||
| .map(ToString::to_string) | ||
| .ok_or_else(|| { | ||
| anyhow!( | ||
| "certificate at {cert_path:?} has Subject CN {cn:?}, \ | ||
| which doesn't start with {KUBELET_CERT_CN_NODE_PREFIX:?}" | ||
| ) | ||
| }) | ||
| } |
| // needs to happen once, up front. trident-acl-agent.service itself is | ||
| // never touched by this test, since every check is a direct one-shot | ||
| // CLI invocation. | ||
| if err := prepareVmForAclAgent(vmConfig.VMConfig, vmIP, testConfig, nil); err != nil { |


Why
Allow deployments to derive the Node name from the kubelet client certificate instead of relying on the hostname convention.
Changes
TRIDENT_ACL_AGENT_KUBERNETES_NODE_NAME_SOURCE:hostname(default) orkubelet-cert. An explicitNODE_NAMEoverride takes precedence.system:node:<name>CN andsystem:nodesorganization.--validate-connection kubernetesdiagnostic fails immediately on resolution errors.Validation at
777fb02e206 ACL-agent unit tests passed. CI build 1221392 succeeded with Storm 8/8; the retry loop is covered by unit tests, not the one-shot Storm checks.
Follow-up
The kubelet certificate path remains fixed at
/var/lib/kubelet/pki/kubelet-client-current.pem.