Skip to content

refactor(redfish): move BMC set_bmc_root_password + add probe_bmc_vendor to RedfishClientPool for rotation reuse#3113

Open
spydaNVIDIA wants to merge 2 commits into
NVIDIA:mainfrom
spydaNVIDIA:rotate_creds
Open

refactor(redfish): move BMC set_bmc_root_password + add probe_bmc_vendor to RedfishClientPool for rotation reuse#3113
spydaNVIDIA wants to merge 2 commits into
NVIDIA:mainfrom
spydaNVIDIA:rotate_creds

Conversation

@spydaNVIDIA

Copy link
Copy Markdown
Contributor

refactor(redfish): move BMC set_bmc_root_password + add probe_bmc_vendor to RedfishClientPool for rotation reuse

Related issues

Type of Change

  • Add - New feature or capability
  • Change - Changes in existing functionality
  • Fix - Bug fixes
  • Remove - Removed features or deprecated functionality
  • Internal - Internal changes (refactoring, tests, docs, etc.)

Breaking Changes

  • This PR contains breaking changes

Testing

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • No testing required (docs, internal refactor, etc.)

Additional Notes

@spydaNVIDIA spydaNVIDIA requested review from a team and polarweasel as code owners July 2, 2026 23:21
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Summary by CodeRabbit

  • New Features
    • Added bmc-machine probe-vendor to resolve a BMC’s Redfish vendor.
    • Added bmc-machine set-root-password to update a BMC root password out of band.
    • Expanded the admin API to support vendor probing and root password updates.
  • Documentation
    • Added command reference pages and updated the BMC machine command overview.
  • Bug Fixes
    • Improved BMC targeting by supporting IP address, MAC address, or machine ID for these commands.

Walkthrough

This PR adds two new BMC administration capabilities—setting a BMC root password out-of-band and probing a BMC's Redfish vendor—spanning proto definitions, Redfish client pool logic, site-explorer wiring, API handlers, admin-cli commands, and documentation.

Changes

BMC Root Password & Vendor Probing

Layer / File(s) Summary
Proto contract for new RPCs
crates/rpc/proto/forge.proto
Adds SetBmcRootPassword and ProbeBmcVendor RPCs with their request/response messages to the Forge service.
Redfish client pool vendor dispatch and probing
crates/redfish/src/libredfish/mod.rs, crates/redfish/src/libredfish/test_support.rs, crates/secrets/src/credentials.rs
Implements two-client credential rotation with vendor-specific dispatch and fallback logic, service-root/chassis-based vendor probing, a Credentials::new constructor, and simulator overrides plus tests.
Site-explorer trait, RedfishClient, and BmcEndpointExplorer wiring
crates/site-explorer/src/endpoint_explorer.rs, crates/site-explorer/src/redfish.rs, crates/site-explorer/src/bmc_endpoint_explorer.rs, crates/site-explorer/src/test_support/mock_endpoint_explorer.rs
Extends EndpointExplorer trait, delegates RedfishClient methods to the pool, wires vault credential retrieval/persistence around vendor probing and password rotation, and updates the mock.
API RPC handlers
crates/api-core/src/api.rs, crates/api-core/src/handlers/bmc_endpoint_explorer.rs
Adds Forge trait methods and handler functions resolving machine/BMC endpoints and dispatching to the endpoint explorer.
Admin CLI commands
crates/admin-cli/src/bmc_machine/mod.rs, crates/admin-cli/src/bmc_machine/probe_vendor/*, crates/admin-cli/src/bmc_machine/set_root_password/*
Adds probe-vendor and set-root-password CLI subcommands with argument parsing, request conversion, and command execution.
Command documentation
docs/manuals/nico-admin-cli/commands/bmc-machine/*
Adds manual pages for the two new commands and updates the subcommand index.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AdminCli
  participant Api
  participant BmcEndpointExplorer
  participant Vault
  participant RedfishClient

  AdminCli->>Api: SetBmcRootPassword(request)
  Api->>BmcEndpointExplorer: set_bmc_root_password(address, interface, new_password)
  BmcEndpointExplorer->>Vault: load current root credentials
  Vault-->>BmcEndpointExplorer: credentials
  BmcEndpointExplorer->>RedfishClient: probe_bmc_vendor(address, credentials)
  RedfishClient-->>BmcEndpointExplorer: vendor
  BmcEndpointExplorer->>RedfishClient: set_bmc_root_password(address, vendor, credentials, new_password)
  RedfishClient-->>BmcEndpointExplorer: Ok(())
  BmcEndpointExplorer->>Vault: persist new credentials
  BmcEndpointExplorer-->>Api: Ok(())
  Api-->>AdminCli: SetBmcRootPasswordResponse
Loading

Compact metadata

  • Related issues: Not specified in the provided context.
  • Related PRs: Not specified in the provided context.
  • Suggested labels: feature, bmc, redfish, admin-cli, documentation
  • Suggested reviewers: Not specified in the provided context.

Poem

A rabbit taps the BMC's door,
"Your root password? Rotate once more!"
It sniffs the vendor, Lenovo or AMI,
Then stores new secrets, safe and calmly.
Docs and CLI hop along the trail—
a tidy PR, no bugs to derail. 🐇🔐

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific, concise, and accurately summarizes the main refactor and new vendor probe capability.
Description check ✅ Passed The description is related to the changeset and reiterates the refactor and new capability, even though it is mostly boilerplate.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🔐 TruffleHog Secret Scan

No secrets or credentials found!

Your code has been scanned for 700+ types of secrets and credentials. All clear! 🎉

🔗 View scan details

🕐 Last updated: 2026-07-02 23:24:26 UTC | Commit: 4504c6d

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 7

🧹 Nitpick comments (4)
crates/admin-cli/src/bmc_machine/set_root_password/args.rs (1)

51-68: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate bmc_endpoint_request construction logic.

This From<Args> body is byte-for-byte identical (modulo the extra new_password field) to the one in crates/admin-cli/src/bmc_machine/probe_vendor/args.rs (lines 45-61). Consider extracting a shared helper (e.g. fn bmc_endpoint_request_from(ip: Option<String>, mac: Option<MacAddress>) -> Option<forgerpc::BmcEndpointRequest>) in a common module for both commands. This also gives a single place to fix the MAC-only bug noted above instead of two.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/admin-cli/src/bmc_machine/set_root_password/args.rs` around lines 51 -
68, The `From<Args>` implementation for `SetBmcRootPasswordRequest` duplicates
the same `bmc_endpoint_request` building logic used by
`probe_vendor::args::From<Args>`, so factor that shared logic into a common
helper and reuse it from both command conversions. Introduce a reusable function
for constructing `forgerpc::BmcEndpointRequest` from the IP/MAC inputs, then
call it from `Args::from` here and in the probe vendor args conversion, keeping
`machine_id` and `new_password` handling local to `SetBmcRootPasswordRequest`.
docs/manuals/nico-admin-cli/commands/bmc-machine/bmc-machine-probe-vendor.md (1)

54-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the generic BMC IP placeholder for --ip-address.

This flag targets the BMC's own IP (not a host-targeted flag), so it should use 192.0.2.10 rather than 192.0.2.20, which is reserved for host-targeted commands such as mlx-devices --host.

📝 Proposed fix
-nico-admin-cli bmc-machine probe-vendor --ip-address 192.0.2.20
+nico-admin-cli bmc-machine probe-vendor --ip-address 192.0.2.10

Based on learnings, "Use 192.0.2.10 as the canonical placeholder for the DPU/generic BMC IP address" while "192.0.2.20" is reserved for host-targeted flags.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/manuals/nico-admin-cli/commands/bmc-machine/bmc-machine-probe-vendor.md`
at line 54, Update the example for nico-admin-cli bmc-machine probe-vendor so
the --ip-address placeholder uses the generic BMC/DPU IP value 192.0.2.10
instead of 192.0.2.20. The issue is in the probe-vendor command example, and the
fix is to replace the host-targeted placeholder with the canonical BMC IP
placeholder consistently in this document.

Source: Learnings

docs/manuals/nico-admin-cli/commands/bmc-machine/bmc-machine-set-root-password.md (1)

60-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Same BMC IP placeholder inconsistency as probe-vendor.md.

--ip-address targets the BMC directly here too, so 192.0.2.10 (generic/DPU BMC IP) should be used instead of 192.0.2.20 (reserved for host-targeted commands).

📝 Proposed fix
-nico-admin-cli bmc-machine set-root-password --ip-address 192.0.2.20 --new-password mynewpassword
+nico-admin-cli bmc-machine set-root-password --ip-address 192.0.2.10 --new-password mynewpassword

Based on learnings, "Use 192.0.2.10 as the canonical placeholder for the DPU/generic BMC IP address" while "192.0.2.20" is the host BMC placeholder.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/manuals/nico-admin-cli/commands/bmc-machine/bmc-machine-set-root-password.md`
at line 60, The BMC IP placeholder in the set-root-password example is
inconsistent with the canonical BMC-targeted placeholder. Update the example
under the set-root-password command so it uses the generic/DPU BMC address
placeholder 192.0.2.10 for the --ip-address argument, matching the convention
used for direct BMC commands. Keep the command text otherwise unchanged and
ensure this aligns with the placeholder usage in the related BMC docs, including
the set-root-password example.

Source: Learnings

crates/api-core/src/handlers/bmc_endpoint_explorer.rs (1)

988-1055: 📐 Maintainability & Code Quality | 🔵 Trivial

Consider adding table-driven tests for the new request→response adapters.

No tests are visible for set_bmc_root_password or probe_bmc_vendor in this diff. Both are straightforward request-validation/adapter functions well-suited to table-driven coverage of the various validate_and_complete_bmc_endpoint_request / resolve_bmc_interface failure paths (missing machine, IP/MAC mismatch, missing BMC IP, etc.).

As per path instructions, "prefer table-driven tests for functions that map inputs to outputs/errors (useful for request->response adapters introduced by the RPC contract)."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/api-core/src/handlers/bmc_endpoint_explorer.rs` around lines 988 -
1055, Add table-driven tests for the new request-to-response adapter handlers
`set_bmc_root_password` and `probe_bmc_vendor`, since the diff adds
validation/translation logic without visible coverage. Exercise the main success
path plus the failure cases from `validate_and_complete_bmc_endpoint_request`
and `resolve_bmc_interface`, including missing machine ID, IP/MAC mismatch, and
missing BMC IP. Keep the tests focused on the adapter behavior and expected
mapped responses/errors so regressions in these request handlers are caught.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/admin-cli/src/bmc_machine/probe_vendor/args.rs`:
- Around line 32-33: The `probe-vendor` help text currently shows a MAC-only
example even though `probe_vendor` always populates
`bmc_endpoint_request.ip_address` and the API requires it. Update the example in
`args.rs` to include `--ip-address`, or remove the `--mac-address`-only example
entirely, so the usage shown by `ProbeVendorArgs` matches the actual
`probe-vendor` behavior.

In `@crates/admin-cli/src/bmc_machine/set_root_password/args.rs`:
- Around line 34-36: The `set_root_password::args` example currently suggests
`--mac-address` can be used alone, but the `BmcEndpointRequest` built by this
flow still ends up with an empty `ip_address` and is rejected. Update the
`set_root_password` argument handling so the BMC IP is resolved before
constructing the request, using the MAC-based lookup path in the relevant
`BmcEndpointRequest`/handler flow, or remove the MAC-only example if that path
is not supported.

In `@crates/admin-cli/src/bmc_machine/set_root_password/cmd.rs`:
- Around line 22-26: The `set_root_password` function is passing the local
`Args` type directly into `set_bmc_root_password`, but the RPC client expects a
`::rpc::forge::SetBmcRootPasswordRequest`. Update the call in
`set_root_password` to convert `args` using its existing `Into` conversion
before invoking `api_client.0.set_bmc_root_password`, so the request type
matches what the client method requires.

In `@crates/api-core/src/handlers/bmc_endpoint_explorer.rs`:
- Around line 1012-1015: The handler logic in the endpoint explorer is
collapsing all EndpointExplorationError cases into CarbideError::internal, which
loses the user-input vs system-failure distinction. Update the error mapping in
the root password flow and the other affected handler to inspect the underlying
EndpointExplorationError and return the appropriate CarbideError variant for bad
password/missing credential cases versus genuine BMC/network failures. Follow
the same pattern used by map_redfish_client_creation_error and map_redfish_error
so callers can reliably distinguish actionable 4xx-like errors from internal
5xx-like errors.
- Around line 988-993: The request logging in set_bmc_root_password is exposing
the plaintext new_password because log_request_data(&request) logs the full
Debug payload before redaction. Update this path to use
log_request_data_redacted(...) or first call into_inner() and log a redacted
copy of rpc::SetBmcRootPasswordRequest so the sensitive field is never written
to logs.

In `@crates/rpc/proto/forge.proto`:
- Around line 7897-7905: The SetBmcRootPasswordRequest currently logs sensitive
data because set_bmc_root_password calls log_request_data(&request), which
captures the full Debug output including new_password. Update the
bmc_endpoint_explorer::set_bmc_root_password path to use the redacted logging
helper or explicitly omit/filter new_password before logging, so the request
still has context without exposing the plaintext BMC root password.

In `@crates/site-explorer/src/bmc_endpoint_explorer.rs`:
- Around line 1215-1240: The `set_bmc_root_password` flow can leave the BMC
rotated but the vault stale if `credential_client.set_bmc_root_credentials`
fails after the device password change succeeds. Update this path in
`set_bmc_root_password` to handle post-rotation persistence failures explicitly,
either by retrying the credential write with backoff or by returning a dedicated
error such as `EndpointExplorationError::CredentialPersistFailedAfterRotation`.
Make sure the error includes enough context to distinguish this from a generic
explorer failure so operators can quickly identify and remediate the
partial-failure case.

---

Nitpick comments:
In `@crates/admin-cli/src/bmc_machine/set_root_password/args.rs`:
- Around line 51-68: The `From<Args>` implementation for
`SetBmcRootPasswordRequest` duplicates the same `bmc_endpoint_request` building
logic used by `probe_vendor::args::From<Args>`, so factor that shared logic into
a common helper and reuse it from both command conversions. Introduce a reusable
function for constructing `forgerpc::BmcEndpointRequest` from the IP/MAC inputs,
then call it from `Args::from` here and in the probe vendor args conversion,
keeping `machine_id` and `new_password` handling local to
`SetBmcRootPasswordRequest`.

In `@crates/api-core/src/handlers/bmc_endpoint_explorer.rs`:
- Around line 988-1055: Add table-driven tests for the new request-to-response
adapter handlers `set_bmc_root_password` and `probe_bmc_vendor`, since the diff
adds validation/translation logic without visible coverage. Exercise the main
success path plus the failure cases from
`validate_and_complete_bmc_endpoint_request` and `resolve_bmc_interface`,
including missing machine ID, IP/MAC mismatch, and missing BMC IP. Keep the
tests focused on the adapter behavior and expected mapped responses/errors so
regressions in these request handlers are caught.

In
`@docs/manuals/nico-admin-cli/commands/bmc-machine/bmc-machine-probe-vendor.md`:
- Line 54: Update the example for nico-admin-cli bmc-machine probe-vendor so the
--ip-address placeholder uses the generic BMC/DPU IP value 192.0.2.10 instead of
192.0.2.20. The issue is in the probe-vendor command example, and the fix is to
replace the host-targeted placeholder with the canonical BMC IP placeholder
consistently in this document.

In
`@docs/manuals/nico-admin-cli/commands/bmc-machine/bmc-machine-set-root-password.md`:
- Line 60: The BMC IP placeholder in the set-root-password example is
inconsistent with the canonical BMC-targeted placeholder. Update the example
under the set-root-password command so it uses the generic/DPU BMC address
placeholder 192.0.2.10 for the --ip-address argument, matching the convention
used for direct BMC commands. Keep the command text otherwise unchanged and
ensure this aligns with the placeholder usage in the related BMC docs, including
the set-root-password example.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f547ec4d-ef1f-407e-97bd-f6d1f3e3640d

📥 Commits

Reviewing files that changed from the base of the PR and between 12ef832 and 4504c6d.

📒 Files selected for processing (20)
  • crates/admin-cli/src/bmc_machine/mod.rs
  • crates/admin-cli/src/bmc_machine/probe_vendor/args.rs
  • crates/admin-cli/src/bmc_machine/probe_vendor/cmd.rs
  • crates/admin-cli/src/bmc_machine/probe_vendor/mod.rs
  • crates/admin-cli/src/bmc_machine/set_root_password/args.rs
  • crates/admin-cli/src/bmc_machine/set_root_password/cmd.rs
  • crates/admin-cli/src/bmc_machine/set_root_password/mod.rs
  • crates/api-core/src/api.rs
  • crates/api-core/src/handlers/bmc_endpoint_explorer.rs
  • crates/redfish/src/libredfish/mod.rs
  • crates/redfish/src/libredfish/test_support.rs
  • crates/rpc/proto/forge.proto
  • crates/secrets/src/credentials.rs
  • crates/site-explorer/src/bmc_endpoint_explorer.rs
  • crates/site-explorer/src/endpoint_explorer.rs
  • crates/site-explorer/src/redfish.rs
  • crates/site-explorer/src/test_support/mock_endpoint_explorer.rs
  • docs/manuals/nico-admin-cli/commands/bmc-machine/bmc-machine-probe-vendor.md
  • docs/manuals/nico-admin-cli/commands/bmc-machine/bmc-machine-set-root-password.md
  • docs/manuals/nico-admin-cli/commands/bmc-machine/bmc-machine.md

Comment on lines +32 to +33
Target the BMC by MAC address:
$ nico-admin-cli bmc-machine probe-vendor --mac-address 00:11:22:33:44:55

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does Rust's std::net::IpAddr FromStr implementation error on an empty string?

💡 Result:

Yes, Rust's std::net::IpAddr FromStr implementation errors on an empty string [1][2]. The FromStr implementation for IpAddr parses the input string by calling internal parsing routines (specifically parse_ascii) [1][2]. Because an empty string does not contain a valid IPv4 or IPv6 address format, it fails to parse and returns an AddrParseError [1][2]. In Rust, calling "".parse:: will return Err(AddrParseError).

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant files and inspect the specific region mentioned in the review.
git ls-files 'crates/admin-cli/src/bmc_machine/probe_vendor/**' 'crates/admin-cli/src/**' | sed -n '1,120p'

echo '--- args.rs ---'
wc -l crates/admin-cli/src/bmc_machine/probe_vendor/args.rs
cat -n crates/admin-cli/src/bmc_machine/probe_vendor/args.rs | sed -n '1,220p'

echo '--- search bmc_endpoint_request usage ---'
rg -n "bmc_endpoint_request|probe_vendor|ip_address.parse\\(" crates -g '*.rs'

Repository: NVIDIA/infra-controller

Length of output: 23532


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1090,1165p' crates/api-core/src/handlers/bmc_endpoint_explorer.rs

echo '--- tests or callers around mac-only/bmc endpoint request ---'
rg -n "mac_address: Some|ip_address: \"\"|Can supply _only_ ip_address or all fields|mac-only|probe-vendor" crates/api-core/src crates/admin-cli/src -g '*.rs'

Repository: NVIDIA/infra-controller

Length of output: 6933


Remove the MAC-only example or require --ip-address
probe-vendor always sends bmc_endpoint_request.ip_address, and the API parses it unconditionally. With only --mac-address, that field becomes "", so the request is rejected. The help text should either include --ip-address here or drop this example.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/admin-cli/src/bmc_machine/probe_vendor/args.rs` around lines 32 - 33,
The `probe-vendor` help text currently shows a MAC-only example even though
`probe_vendor` always populates `bmc_endpoint_request.ip_address` and the API
requires it. Update the example in `args.rs` to include `--ip-address`, or
remove the `--mac-address`-only example entirely, so the usage shown by
`ProbeVendorArgs` matches the actual `probe-vendor` behavior.

Comment on lines +34 to +36
Target the BMC by MAC address:
$ nico-admin-cli bmc-machine set-root-password \
--mac-address 00:11:22:33:44:55 --new-password mynewpassword

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Files ==\n'
git ls-files 'crates/admin-cli/src/bmc_machine/**' | sort

printf '\n== Search for BmcEndpointRequest and relevant builders ==\n'
rg -n "BmcEndpointRequest|ip_address|mac_address|new-password|probe_vendor|set_root_password" crates/admin-cli/src -S

printf '\n== Outline candidate files ==\n'
ast-grep outline crates/admin-cli/src/bmc_machine/set_root_password/args.rs --view expanded || true
ast-grep outline crates/admin-cli/src/bmc_machine/probe_vendor/args.rs --view expanded || true

Repository: NVIDIA/infra-controller

Length of output: 39455


🏁 Script executed:

#!/bin/bash
set -euo pipefail

files=(
  crates/admin-cli/src/bmc_machine/set_root_password/args.rs
  crates/admin-cli/src/bmc_machine/probe_vendor/args.rs
)

for f in "${files[@]}"; do
  echo "===== $f ====="
  wc -l "$f"
  sed -n '1,220p' "$f" | cat -n
  echo
done

printf '\n===== request-building search =====\n'
rg -n "struct BmcEndpointRequest|impl .*BmcEndpointRequest|from_args|Into<BmcEndpointRequest>|ip_address:|mac_address:" crates/admin-cli/src -S

Repository: NVIDIA/infra-controller

Length of output: 23931


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "===== set_root_password args ====="
sed -n '1,220p' crates/admin-cli/src/bmc_machine/set_root_password/args.rs | cat -n

echo "===== probe_vendor args ====="
sed -n '1,220p' crates/admin-cli/src/bmc_machine/probe_vendor/args.rs | cat -n

echo "===== all BmcEndpointRequest references ====="
rg -n "BmcEndpointRequest|ip_address|mac_address" crates/admin-cli/src -S

Repository: NVIDIA/infra-controller

Length of output: 40350


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the server-side handling of BmcEndpointRequest and how empty ip_address is treated.
rg -n "bmc_endpoint_request|BmcEndpointRequest|ip_address\.parse|mac_address" crates api -S

# Inspect the request type definitions around the relevant fields in admin-cli rpc wrappers.
sed -n '630,700p' crates/admin-cli/src/rpc.rs | cat -n
sed -n '2470,2515p' crates/admin-cli/src/rpc.rs | cat -n

Repository: NVIDIA/infra-controller

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "bmc_endpoint_request|BmcEndpointRequest|ip_address\.parse|mac_address" crates api -S

printf '\n===== rpc.rs excerpts =====\n'
sed -n '630,700p' crates/admin-cli/src/rpc.rs | cat -n
printf '\n===== rpc.rs BMC methods =====\n'
sed -n '2470,2515p' crates/admin-cli/src/rpc.rs | cat -n

Repository: NVIDIA/infra-controller

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "SetBmcRootPasswordRequest|ProbeBmcVendorRequest|BmcEndpointRequest|set_root_password|probe_vendor" crates -S

Repository: NVIDIA/infra-controller

Length of output: 12602


🏁 Script executed:

#!/bin/bash
set -euo pipefail

f=crates/api-core/src/handlers/bmc_endpoint_explorer.rs

printf '\n== outline ==\n'
ast-grep outline "$f" --view expanded || true

printf '\n== request resolution excerpt ==\n'
sed -n '1090,1195p' "$f" | cat -n

printf '\n== set/probe handlers excerpt ==\n'
sed -n '980,1045p' "$f" | cat -n

Repository: NVIDIA/infra-controller

Length of output: 10255


🏁 Script executed:

#!/bin/bash
set -euo pipefail

f=crates/api-core/src/handlers/bmc_endpoint_explorer.rs

printf '\n== request resolution excerpt ==\n'
sed -n '1090,1195p' "$f" | cat -n

printf '\n== set/probe handlers excerpt ==\n'
sed -n '980,1045p' "$f" | cat -n

Repository: NVIDIA/infra-controller

Length of output: 7873


Resolve the BMC IP before building the root-password request. --mac-address alone still builds BmcEndpointRequest with ip_address: "", and this handler rejects requests without a populated IP. Either look up the IP from the MAC here or remove the MAC-only example.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/admin-cli/src/bmc_machine/set_root_password/args.rs` around lines 34 -
36, The `set_root_password::args` example currently suggests `--mac-address` can
be used alone, but the `BmcEndpointRequest` built by this flow still ends up
with an empty `ip_address` and is rejected. Update the `set_root_password`
argument handling so the BMC IP is resolved before constructing the request,
using the MAC-based lookup path in the relevant `BmcEndpointRequest`/handler
flow, or remove the MAC-only example if that path is not supported.

Comment on lines +22 to +26
pub async fn set_root_password(args: Args, api_client: &ApiClient) -> CarbideCliResult<()> {
api_client.0.set_bmc_root_password(args).await?;
println!("BMC root password updated");
Ok(())
}

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.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify ApiClient's set_bmc_root_password signature and how other cmd.rs files call similar RPCs.
rg -n "struct ApiClient" -A 20 crates/admin-cli/src
rg -n "fn set_bmc_root_password" -B2 -A 10
rg -n "api_client\.0\.\w+\(" -A 1 crates/admin-cli/src

Repository: NVIDIA/infra-controller

Length of output: 1762


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== cmd.rs ==\n'
cat -n crates/admin-cli/src/bmc_machine/set_root_password/cmd.rs

printf '\n== args.rs ==\n'
cat -n crates/admin-cli/src/bmc_machine/set_root_password/args.rs

printf '\n== rpc.rs relevant section ==\n'
sed -n '1,220p' crates/admin-cli/src/rpc.rs

printf '\n== search similar call patterns ==\n'
rg -n "set_bmc_root_password|\.into\(\)\)\.await\?|\.into\(\)\)" crates/admin-cli/src -S

Repository: NVIDIA/infra-controller

Length of output: 21341


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== locate set_bmc_root_password definitions ==\n'
rg -n "set_bmc_root_password" .

printf '\n== locate ForgeApiClient definition source ==\n'
rg -n "struct ForgeApiClient|impl ForgeApiClient" .

Repository: NVIDIA/infra-controller

Length of output: 3648


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== forge_api_client.rs relevant section ==\n'
rg -n "set_bmc_root_password" -A 12 -B 8 crates/rpc/src/forge_api_client.rs

Repository: NVIDIA/infra-controller

Length of output: 205


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== all references to SetBmcRootPasswordRequest ==\n'
rg -n "SetBmcRootPasswordRequest|set_bmc_root_password" .

printf '\n== rpc crate file list around forge client ==\n'
fd -a "forge_api_client.rs|forge.*client|generated" crates/rpc -t f

printf '\n== forge_api_client.rs outline ==\n'
ast-grep outline crates/rpc/src/forge_api_client.rs --view expanded

Repository: NVIDIA/infra-controller

Length of output: 5136


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== forge_api_client.rs first 260 lines ==\n'
sed -n '1,260p' crates/rpc/src/forge_api_client.rs

printf '\n== forge_tls_client.rs first 260 lines ==\n'
sed -n '1,260p' crates/rpc/src/forge_tls_client.rs

printf '\n== any generated client type names in rpc crate ==\n'
rg -n "ForgeApiClient|set_bmc_root_password|IntoRequest<.*SetBmcRootPasswordRequest" crates/rpc/src crates/rpc/proto -S

Repository: NVIDIA/infra-controller

Length of output: 17184


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== search generated proto client in repo ==\n'
rg -n "pub async fn set_bmc_root_password|fn set_bmc_root_password|SetBmcRootPasswordRequest" crates/rpc -g'!**/target/**' -S

printf '\n== list proto/gen files under crates/rpc ==\n'
fd -a -t f . crates/rpc

printf '\n== inspect tonic IntoRequest blanket impl in toolchain docs? ==\n'
python3 - <<'PY'
import sys, pkgutil
print("python ok")
PY

Repository: NVIDIA/infra-controller

Length of output: 6775


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== all set_bmc_root_password symbols ==\n'
rg -n "set_bmc_root_password|SetBmcRootPasswordRequest" crates -S

printf '\n== rpc crate tree ==\n'
fd -a -t f . crates/rpc

printf '\n== generated proto module hints ==\n'
rg -n "mod protos|pub mod protos|include_proto|tonic::include_proto" crates/rpc/src crates/rpc -S

Repository: NVIDIA/infra-controller

Length of output: 10381


🌐 Web query:

tonic IntoRequest blanket impl From IntoRequest Request<T> generated client method accepts custom type implementing From

💡 Result:

In tonic, the client methods generated for RPC calls accept any type that implements the IntoRequest trait [1][2]. This trait is designed to allow convenience when calling RPC methods, enabling you to pass either the raw message type T or a pre-constructed Request [1]. Tonic provides a blanket implementation of IntoRequest for all types T [1][3]. Specifically, the implementation impl<T> IntoRequest<T> for T allows a raw message to be automatically wrapped in a Request via Request::new(self) [1]. There is also a separate blanket implementation for Request<T> itself, which simply returns the request instance unchanged [1][3]. Regarding your question about using a custom type that implements From: The IntoRequest trait does not natively use the standard From or Into traits for its conversion logic [1][4]. Because the blanket implementation impl<T> IntoRequest<T> for T is provided for all types, attempting to implement IntoRequest for a custom type that also implements From for the underlying message type may conflict with the existing blanket implementation if not handled correctly. If you have a custom type and want to pass it to a generated tonic client method, you have two primary options: 1. Manual Conversion: Convert your custom type to the expected message type T (using your From/Into implementation) before passing it to the client method. 2. Implement IntoRequest: While you can theoretically implement IntoRequest for your custom type, doing so requires care because of potential coherence issues with the existing blanket implementation in the tonic crate. In summary, tonic does not rely on a From-based blanket implementation for IntoRequest; it uses a direct blanket implementation for any type T to wrap it in a Request [1]. Custom type support is best achieved by converting to the target message type before the call, rather than relying on an automatic trait-based conversion at the client method boundary [1].

Citations:


Convert Args before calling the RPC client
crates/admin-cli/src/bmc_machine/set_root_password/cmd.rs:23set_bmc_root_password takes ::rpc::forge::SetBmcRootPasswordRequest, so pass args.into() here; Args only defines the conversion, it is not the request type itself.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/admin-cli/src/bmc_machine/set_root_password/cmd.rs` around lines 22 -
26, The `set_root_password` function is passing the local `Args` type directly
into `set_bmc_root_password`, but the RPC client expects a
`::rpc::forge::SetBmcRootPasswordRequest`. Update the call in
`set_root_password` to convert `args` using its existing `Into` conversion
before invoking `api_client.0.set_bmc_root_password`, so the request type
matches what the client method requires.

Comment on lines +988 to +993
pub(crate) async fn set_bmc_root_password(
api: &Api,
request: Request<rpc::SetBmcRootPasswordRequest>,
) -> Result<Response<rpc::SetBmcRootPasswordResponse>, Status> {
log_request_data(&request);
let req = request.into_inner();

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.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -A 15 'fn log_request_data' --type=rust
rg -n 'new_password' crates/rpc/proto/forge.proto

Repository: NVIDIA/infra-controller

Length of output: 161


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- bmc_endpoint_explorer outline ---'
ast-grep outline crates/api-core/src/handlers/bmc_endpoint_explorer.rs --view expanded || true

echo '--- handler slice ---'
sed -n '980,1065p' crates/api-core/src/handlers/bmc_endpoint_explorer.rs | cat -n

echo '--- log_request_data search ---'
rg -n -A 25 -B 5 'fn log_request_data|log_request_data\(' crates/api-core crates -g '!target' || true

echo '--- proto field search ---'
rg -n -A 5 -B 5 'new_password|SetBmcRootPasswordRequest|SetBmcRootPasswordResponse' crates -g '!target' || true

Repository: NVIDIA/infra-controller

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- handler excerpt ---'
sed -n '988,1065p' crates/api-core/src/handlers/bmc_endpoint_explorer.rs | cat -n

echo '--- log_request_data definition(s) ---'
rg -n -A 40 -B 10 'fn log_request_data' crates/api-core crates -g '!target' | sed -n '1,220p'

echo '--- request schema for password field ---'
rg -n -A 30 -B 10 'message SetBmcRootPasswordRequest|new_password|current_password|password' crates/rpc crates -g '!target' | sed -n '1,220p'

echo '--- EndpointExplorationError mapping ---'
rg -n -A 40 -B 10 'enum EndpointExplorationError|match .*EndpointExplorationError|CarbideError::internal\(e\.to_string\(\)\)' crates/api-core/src/handlers/bmc_endpoint_explorer.rs crates -g '!target' | sed -n '1,260p'

Repository: NVIDIA/infra-controller

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -A 40 -B 10 'enum EndpointExplorationError|type EndpointExplorationError|struct .*EndpointExploration|impl .*EndpointExplorationError|set_bmc_root_password\(' crates/api-core crates -g '!target' | sed -n '1,240p'

Repository: NVIDIA/infra-controller

Length of output: 22184


Redact new_password before request logging. log_request_data(&request) records request.get_ref() via Debug here, so the plaintext BMC root password can end up in the span. Switch to log_request_data_redacted(...) or log a redacted copy after into_inner() so credentials never reach logs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/api-core/src/handlers/bmc_endpoint_explorer.rs` around lines 988 -
993, The request logging in set_bmc_root_password is exposing the plaintext
new_password because log_request_data(&request) logs the full Debug payload
before redaction. Update this path to use log_request_data_redacted(...) or
first call into_inner() and log a redacted copy of
rpc::SetBmcRootPasswordRequest so the sensitive field is never written to logs.

Source: Path instructions

Comment on lines +1012 to +1015
api.endpoint_explorer
.set_bmc_root_password(bmc_addr, &machine_interface, &req.new_password)
.await
.map_err(|e| CarbideError::internal(e.to_string()))?;

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Blanket CarbideError::internal mapping loses the invalid-argument vs system-error distinction.

Both new handlers collapse every EndpointExplorationError (e.g. bad current password/Unauthorized, missing vault entry/MissingCredentials, genuine network/BMC failure) into CarbideError::internal. Callers (admin-cli, other services) can no longer distinguish "you supplied the wrong current password" (a 4xx-like, user-actionable condition) from "the BMC/network is down" (a genuine 5xx). This is the exact "avoid — constructing Status directly, bypassing NicoError error mapping" anti-pattern flagged in the referenced STYLE_GUIDE, applied in spirit to error variant selection.

Consider mapping the underlying EndpointExplorationError to the appropriate CarbideError variant (similar to how map_redfish_client_creation_error/map_redfish_error differentiate causes elsewhere in redfish.rs) instead of a single catch-all .internal().

As per path instructions, "Prefer NicoError-based error construction inside handlers... choose the NicoError variant based on whether failures are user-input/invalid-argument (4xx-like) vs internal/system issues."

Also applies to: 1044-1048

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/api-core/src/handlers/bmc_endpoint_explorer.rs` around lines 1012 -
1015, The handler logic in the endpoint explorer is collapsing all
EndpointExplorationError cases into CarbideError::internal, which loses the
user-input vs system-failure distinction. Update the error mapping in the root
password flow and the other affected handler to inspect the underlying
EndpointExplorationError and return the appropriate CarbideError variant for bad
password/missing credential cases versus genuine BMC/network failures. Follow
the same pattern used by map_redfish_client_creation_error and map_redfish_error
so callers can reliably distinguish actionable 4xx-like errors from internal
5xx-like errors.

Source: Path instructions

Comment on lines +7897 to +7905
// Must provide either machine_id or ip/mac pair
message SetBmcRootPasswordRequest {
optional BmcEndpointRequest bmc_endpoint_request = 1;
optional string machine_id = 2;
// New BMC root password to set. The server authenticates with the currently
// stored root credentials, probes the vendor, sets the new password, and
// records it as the device's per-BMC credential.
string new_password = 3;
}

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect log_request_data and confirm SetBmcRootPasswordRequest.new_password is filtered.
rg -nP --type=rust -C4 'fn\s+log_request_data'
rg -nP --type=rust -C6 'set_bmc_root_password' crates/api-core/src/handlers/bmc_endpoint_explorer.rs

Repository: NVIDIA/infra-controller

Length of output: 161


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== locate log_request_data and handler ==="
rg -n --hidden --glob '!target' 'log_request_data|set_bmc_root_password|SetBmcRootPasswordRequest' crates .github book 2>/dev/null || true

echo
echo "=== find likely handler files ==="
fd -a 'bmc_endpoint_explorer.rs|log_request_data|forge.proto' crates . 2>/dev/null || true

Repository: NVIDIA/infra-controller

Length of output: 39922


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== crate file list around api-core handlers ==="
fd -a . crates/api-core/src/handlers 2>/dev/null | sed -n '1,200p'

echo
echo "=== search for request logging helpers ==="
rg -n --hidden --glob '!target' 'fn\s+log_request_data|log_request_data\(' crates/api-core crates/api-model crates/api-db 2>/dev/null || true

Repository: NVIDIA/infra-controller

Length of output: 33862


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== api.rs log helpers ==="
sed -n '3510,3555p' crates/api-core/src/api.rs | cat -n

echo
echo "=== bmc_endpoint_explorer set_bmc_root_password ==="
sed -n '980,1020p' crates/api-core/src/handlers/bmc_endpoint_explorer.rs | cat -n

echo
echo "=== request type definition in proto ==="
sed -n '7890,7915p' crates/rpc/proto/forge.proto | cat -n

Repository: NVIDIA/infra-controller

Length of output: 4604


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== locate generated/request type Rust definitions ==="
fd -a 'forge.rs|mod.rs' crates/rpc crates/api-core 2>/dev/null | sed -n '1,120p'

echo
echo "=== search for SetBmcRootPasswordRequest in Rust sources ==="
rg -n --hidden --glob '!target' 'SetBmcRootPasswordRequest|new_password' crates/rpc crates/api-core crates/admin-cli crates/site-explorer 2>/dev/null | sed -n '1,220p'

Repository: NVIDIA/infra-controller

Length of output: 5407


Redact new_password before logging this request. crates/api-core/src/handlers/bmc_endpoint_explorer.rs::set_bmc_root_password calls log_request_data(&request), and that helper records the request’s full Debug form. This will emit the plaintext BMC root password into structured logs. Use the redacted logging path here or filter the field explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/rpc/proto/forge.proto` around lines 7897 - 7905, The
SetBmcRootPasswordRequest currently logs sensitive data because
set_bmc_root_password calls log_request_data(&request), which captures the full
Debug output including new_password. Update the
bmc_endpoint_explorer::set_bmc_root_password path to use the redacted logging
helper or explicitly omit/filter new_password before logging, so the request
still has context without exposing the plaintext BMC root password.

Source: Path instructions

Comment on lines +1215 to +1240
// Resolve the dispatch vendor `set_bmc_root_password` branches on using
// the current credentials, then set the new password on the device.
let vendor = self
.redfish_client
.probe_bmc_vendor(bmc_ip_address, current_credentials.clone())
.await?;
let new_credentials = self
.set_bmc_root_password(
bmc_ip_address,
vendor,
current_credentials,
new_password.to_string(),
)
.await?;

// Persist the new per-device credential so NICo can still reach the BMC.
// Deliberately does NOT record rotation convergence (unlike
// `set_bmc_root_credentials`): this is an out-of-band set, so the
// credential-rotation engine will reassert the site-wide password on
// its next pass rather than treating this device as converged.
self.credential_client
.set_bmc_root_credentials(bmc_mac_address, &new_credentials)
.await?;

Ok(())
}

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.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Partial-failure risk: BMC lockout if vault persistence fails after device rotation.

Lines 1221-1237 perform two dependent external writes: rotate the password on the physical BMC, then persist the new credential to the vault. If the vault write in Line 1236 fails (network blip, vault outage) after the on-device rotation in Line 1222 already succeeded, the stored credential becomes stale and every subsequent get_bmc_root_credentials call will authenticate with the wrong password — effectively locking NICo out of that BMC until an operator manually intervenes.

Consider retrying the vault persistence step with backoff before propagating the error, or emitting a distinct, actionable error/alert (e.g. EndpointExplorationError::CredentialPersistFailedAfterRotation) so operators can immediately recognize and remediate this specific failure mode rather than treating it as a generic explorer error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/site-explorer/src/bmc_endpoint_explorer.rs` around lines 1215 - 1240,
The `set_bmc_root_password` flow can leave the BMC rotated but the vault stale
if `credential_client.set_bmc_root_credentials` fails after the device password
change succeeds. Update this path in `set_bmc_root_password` to handle
post-rotation persistence failures explicitly, either by retrying the credential
write with backoff or by returning a dedicated error such as
`EndpointExplorationError::CredentialPersistFailedAfterRotation`. Make sure the
error includes enough context to distinguish this from a generic explorer
failure so operators can quickly identify and remediate the partial-failure
case.

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown

🔍 Container Scan Summary

Service Total Critical High Medium Low Other
boot-artifacts-aarch64 3 0 0 3 0 0
boot-artifacts-x86_64 3 0 0 3 0 0
forge-admin-cli-x86_64 272 5 27 89 7 144
machine-validation-runner 769 25 209 278 36 221
machine_validation 769 25 209 278 36 221
machine_validation-aarch64 769 25 209 278 36 221
TOTAL 2585 80 654 929 115 807

Per-CVE detail lives in the per-service grype-* artifacts (JSON + SARIF). Severity counts only — no CVE IDs published here.

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.

1 participant