Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

use clap::Parser;

use crate::component_manager::common::SwitchTargetArgs;

#[derive(Parser, Debug)]
#[command(after_long_help = "\
EXAMPLES:

Rotate the NVOS mTLS certificate on one switch via Maintenance:
$ nico-admin-cli component-manager configure-switch-certificate \
--switch-id 12345678-1234-5678-90ab-cdef01234567

Rotate certificates on multiple switches:
$ nico-admin-cli component-manager configure-switch-certificate \
--switch-id 12345678-1234-5678-90ab-cdef01234567,abcdef01-2345-6789-abcd-ef0123456789

Dispatch directly to the component backend, bypassing the switch state controller:
$ nico-admin-cli component-manager configure-switch-certificate \
--switch-id 12345678-1234-5678-90ab-cdef01234567 --bypass-state-controller

")]
pub struct Args {
#[clap(flatten)]
pub ids: SwitchTargetArgs,

#[clap(
long = "domain-name",
help = "Optional certificate domain passed through to RMS; omit to use the RMS default"
)]
pub domain_name: Option<String>,

#[clap(
long = "bypass-state-controller",
help = "Bypass the switch state controller and dispatch directly to the component backend"
)]
pub bypass_state_controller: bool,
}

impl From<Args> for rpc::forge::ComponentConfigureSwitchCertificateRequest {
fn from(args: Args) -> Self {
Self {
switch_ids: Some(args.ids.into()),
domain_name: args.domain_name,
bypass_state_controller: args.bypass_state_controller,
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

use ::rpc::admin_cli::OutputFormat;
use prettytable::{Cell, Row, Table};

use super::args::Args;
use crate::component_manager::common;
use crate::errors::CarbideCliError;
use crate::rpc::ApiClient;

pub async fn configure_switch_certificate(
opts: Args,
format: OutputFormat,
api_client: &ApiClient,
) -> Result<(), CarbideCliError> {
let request: rpc::forge::ComponentConfigureSwitchCertificateRequest = opts.into();
let response = api_client
.0
.component_configure_switch_certificate(request)
.await
.map_err(CarbideCliError::from)?;

if format == OutputFormat::Json {
let results = response
.results
.iter()
.map(|result| common::component_result_json(Some(result)))
.collect::<Vec<_>>();
println!("{}", serde_json::to_string_pretty(&results)?);
} else {
let mut table = Table::new();
table.set_titles(Row::new(vec![
Cell::new("Component ID"),
Cell::new("Status"),
Cell::new("Error"),
]));

for result in &response.results {
let (component_id, status, error) = common::component_result_fields(Some(result));
table.add_row(Row::new(vec![
Cell::new(&component_id),
Cell::new(&status),
Cell::new(&error),
]));
}

table.printstd();
}

let (failures, failure_summary) =
common::component_failure_count_and_summary(response.results.iter().map(Some));

if failures > 0 {
return Err(CarbideCliError::GenericError(format!(
"{failures} switch certificate rotation request(s) failed{failure_summary}"
)));
}

Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

pub mod args;
pub mod cmd;

pub use args::Args;

use crate::cfg::run::Run;
use crate::cfg::runtime::RuntimeContext;
use crate::errors::CarbideCliResult;

impl Run for Args {
async fn run(self, ctx: &mut RuntimeContext) -> CarbideCliResult<()> {
cmd::configure_switch_certificate(self, ctx.config.format, &ctx.api_client).await?;
Ok(())
}
}
7 changes: 7 additions & 0 deletions crates/admin-cli/src/component_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

pub(crate) mod common;
mod configure_switch_certificate;
mod power_control;
mod status;
mod update_firmware;
Expand Down Expand Up @@ -47,4 +48,10 @@ pub enum Cmd {
visible_alias = "power-control"
)]
ComponentPowerControl(power_control::Args),

#[clap(
about = "Rotate or reinstall switch NVOS mTLS certificates via the switch Maintenance phase",
visible_alias = "rotate-switch-certificate"
)]
ConfigureSwitchCertificate(configure_switch_certificate::Args),
}
8 changes: 8 additions & 0 deletions crates/api-core/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3384,6 +3384,14 @@ impl Forge for Api {
crate::handlers::component_manager::component_power_control(self, request).await
}

async fn component_configure_switch_certificate(
&self,
request: Request<rpc::ComponentConfigureSwitchCertificateRequest>,
) -> Result<Response<rpc::ComponentConfigureSwitchCertificateResponse>, Status> {
crate::handlers::component_manager::component_configure_switch_certificate(self, request)
.await
}

async fn get_component_inventory(
&self,
request: Request<rpc::GetComponentInventoryRequest>,
Expand Down
4 changes: 4 additions & 0 deletions crates/api-core/src/auth/internal_rbac_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,10 @@ impl InternalRBACRules {
x.perm("ComponentPowerControl", vec![ForgeAdminCLI, Flow]);
x.perm("GetComponentInventory", vec![ForgeAdminCLI, Flow]);
x.perm("UpdateComponentFirmware", vec![ForgeAdminCLI, Flow]);
x.perm(
"ComponentConfigureSwitchCertificate",
vec![ForgeAdminCLI, Flow],
);
x.perm("GetComponentFirmwareStatus", vec![ForgeAdminCLI, Flow]);
x.perm("ListComponentFirmwareVersions", vec![ForgeAdminCLI, Flow]);
x.perm("GetDPFHostSnapshot", vec![ForgeAdminCLI]);
Expand Down
56 changes: 56 additions & 0 deletions crates/api-core/src/cfg/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1755,6 +1755,34 @@ pub struct RackStateControllerConfig {
/// Common state controller configs
#[serde(default = "StateControllerConfig::default")]
pub controller: StateControllerConfig,

/// Switch mTLS services configured on scoped switches before NMX cluster
/// setup proceeds. When omitted or empty, defaults to ScaleUpFabric manager
/// and telemetry interface services.
///
/// Configured in `nico-api-config.toml`:
///
/// ```toml
/// [rack_state_controller]
/// nmx_cluster_switch_mtls_services = [
/// "scale_up_fabric_manager",
/// "scale_up_fabric_telemetry_interface",
/// ]
/// ```
#[serde(default)]
pub nmx_cluster_switch_mtls_services: Vec<component_manager::config::SwitchMtlsService>,
}

impl RackStateControllerConfig {
/// Returns configured NMX cluster switch mTLS services, or the ScaleUpFabric
/// defaults when the field was omitted or left empty in config.
pub fn effective_nmx_cluster_switch_mtls_services_as_i32(&self) -> Vec<i32> {
component_manager::config::switch_mtls_services_as_i32(
&component_manager::config::effective_nmx_cluster_switch_mtls_services(
&self.nmx_cluster_switch_mtls_services,
),
)
}
}

/// SwitchStateController related config
Expand All @@ -1763,6 +1791,34 @@ pub struct SwitchStateControllerConfig {
/// Common state controller configs
#[serde(default = "StateControllerConfig::default")]
pub controller: StateControllerConfig,

/// Switch services that receive installed mTLS certificates during RMS
/// `configure_switch_certificate` calls initiated by the switch state
/// machine.
///
/// When this field is omitted or empty, all supported services are used.
///
/// Configured in `nico-api-config.toml`:
///
/// ```toml
/// [switch_state_controller]
/// switch_mtls_services = [
/// "nvue_api",
/// "scale_up_fabric_telemetry",
/// ]
/// ```
#[serde(default)]
pub switch_mtls_services: Vec<component_manager::config::SwitchMtlsService>,
}

impl SwitchStateControllerConfig {
/// Returns the configured switch mTLS services, or all supported services
/// when the field was omitted or left empty in config.
pub fn effective_switch_mtls_services_as_i32(&self) -> Vec<i32> {
component_manager::config::switch_mtls_services_as_i32(
&component_manager::config::effective_switch_mtls_services(&self.switch_mtls_services),
)
}
}

/// SpdmStateController related config
Expand Down
Loading
Loading