From eea4481e5e3e77293416ebf1a3a414b1b31174df Mon Sep 17 00:00:00 2001 From: Rain Date: Tue, 11 Aug 2026 17:59:20 -0700 Subject: [PATCH] [spr] changes to main this commit is based on Created using spr 1.3.6-beta.1 [skip ci] --- Cargo.lock | 4 +- Cargo.toml | 2 +- clients/wicketd-client/src/lib.rs | 3 - openapi/wicketd.json | 467 ------------------ wicket/src/cli/command.rs | 14 +- wicket/src/cli/rack_setup.rs | 46 +- wicket/src/cli/upload.rs | 18 +- wicket/src/dispatch.rs | 24 +- wicket/src/lib.rs | 1 + wicket/src/runner.rs | 17 +- wicket/src/wicketd.rs | 45 +- wicketd-api/Cargo.toml | 1 - wicketd-api/src/lib.rs | 116 ----- wicketd-commission-types/versions/Cargo.toml | 1 + .../versions/src/initial/rack_setup.rs | 8 +- wicketd/Cargo.toml | 1 + wicketd/src/commission/http_entrypoints.rs | 4 +- wicketd/src/commission/mod.rs | 2 +- wicketd/src/http_entrypoints.rs | 149 ------ wicketd/src/rss_config.rs | 29 +- wicketd/tests/integration_tests/commission.rs | 5 +- wicketd/tests/integration_tests/inventory.rs | 2 +- wicketd/tests/integration_tests/setup.rs | 8 +- wicketd/tests/integration_tests/updates.rs | 14 +- workspace-hack/Cargo.toml | 4 +- 25 files changed, 132 insertions(+), 853 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53e74662661..df568944ea6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17623,6 +17623,7 @@ dependencies = [ "wicketd-commission-client", "wicketd-commission-types", "wicketd-commission-types-versions", + "zeroize", ] [[package]] @@ -17633,7 +17634,6 @@ dependencies = [ "dropshot", "gateway-client", "omicron-common", - "omicron-passwords", "omicron-uuid-kinds", "omicron-workspace-hack", "schemars 0.8.22", @@ -17730,6 +17730,7 @@ dependencies = [ "thiserror 2.0.18", "toml 0.8.23", "uuid", + "zeroize", ] [[package]] @@ -18550,6 +18551,7 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ + "serde", "zeroize_derive", ] diff --git a/Cargo.toml b/Cargo.toml index 74254298895..73572208baf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -901,7 +901,7 @@ wicketd-commission-types-versions = { path = "wicketd-commission-types/versions" x509-cert = { version = "0.2.5", default-features = false } xshell = "0.2.7" zerocopy = "0.8.26" -zeroize = { version = "1.8.1", features = ["zeroize_derive", "std"] } +zeroize = { version = "1.8.1", features = ["zeroize_derive", "serde", "std"] } zfs-test-harness = { path = "sled-storage/zfs-test-harness" } zip = { version = "4.2.0", default-features = false, features = ["deflate","bzip2"] } zone = { version = "0.3.1", default-features = false, features = ["async"] } diff --git a/clients/wicketd-client/src/lib.rs b/clients/wicketd-client/src/lib.rs index 4815734b807..6a7a83cc7f2 100644 --- a/clients/wicketd-client/src/lib.rs +++ b/clients/wicketd-client/src/lib.rs @@ -39,7 +39,6 @@ progenitor::generate_api!( ArtifactId = wicket_common::artifact::ArtifactId, Baseboard = sled_hardware_types::Baseboard, BaseboardId = sled_hardware_types::BaseboardId, - BgpAuthKey = wicketd_commission_types_versions::latest::rack_setup::BgpAuthKey, BgpAuthKeyId = wicketd_commission_types_versions::latest::rack_setup::BgpAuthKeyId, BgpAuthKeyInfo = wicket_common::rack_setup::BgpAuthKeyInfo, BgpAuthKeyStatus = wicket_common::rack_setup::BgpAuthKeyStatus, @@ -47,7 +46,6 @@ progenitor::generate_api!( BgpPeerAuthKind = wicket_common::rack_setup::BgpPeerAuthKind, BgpPeerConfig = omicron_common::api::internal::shared::BgpPeerConfig, BootstrapSledDescription = wicket_common::rack_setup::BootstrapSledDescription, - CertificateUploadResponse = wicketd_commission_types_versions::latest::rack_setup::CertificateUploadResponse, ClearUpdateStateOptions = wicket_common::rack_update::ClearUpdateStateOptions, ClearUpdateStateResponse = wicketd_commission_types_versions::latest::update::ClearUpdateStateResponse, CurrentRssUserConfigInsensitive = wicket_common::rack_setup::CurrentRssUserConfigInsensitive, @@ -66,7 +64,6 @@ progenitor::generate_api!( ProgressEventForInstallinatorSpec = installinator_common::ProgressEvent, ProgressEventForUplinkPreflightSpec = wicket_common::preflight_check::ProgressEvent, ProgressEventForWicketdEngineSpec = wicket_common::update_events::ProgressEvent, - PutRssUserConfigInsensitive = wicketd_commission_types_versions::latest::rack_setup::PutRssUserConfigInsensitive, RackV1Inventory = wicket_common::inventory::RackV1Inventory, RotInventory = wicket_common::inventory::RotInventory, RotSlot = wicket_common::inventory::RotSlot, diff --git a/openapi/wicketd.json b/openapi/wicketd.json index 5b7a800aefe..8eb274c39d3 100644 --- a/openapi/wicketd.json +++ b/openapi/wicketd.json @@ -319,29 +319,6 @@ "$ref": "#/components/responses/Error" } } - }, - "post": { - "summary": "Run rack setup.", - "description": "Will return an error if not all of the rack setup configuration has been populated.", - "operationId": "post_run_rack_setup", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RackInitUuid" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } } }, "/rack-setup/config": { @@ -368,32 +345,6 @@ } } }, - "put": { - "summary": "Update (a subset of) the current RSS configuration.", - "description": "Sensitive values (certificates and password hash) are not set through this endpoint.", - "operationId": "put_rss_config", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PutRssUserConfigInsensitive" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "resource updated" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - }, "delete": { "summary": "Reset all RSS configuration to their default values.", "operationId": "delete_rss_config", @@ -445,122 +396,6 @@ } } }, - "/rack-setup/config/bgp/auth-key/{key_id}": { - "put": { - "summary": "Set the BGP authentication key for a particular key ID.", - "operationId": "put_bgp_auth_key", - "parameters": [ - { - "in": "path", - "name": "key_id", - "required": true, - "schema": { - "$ref": "#/components/schemas/BgpAuthKeyId" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PutBgpAuthKeyBody" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PutBgpAuthKeyResponse" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/rack-setup/config/cert": { - "post": { - "summary": "Add an external certificate.", - "description": "This must be paired with its private key. They may be posted in either order, but one cannot post two certs in a row (or two keys in a row).", - "operationId": "post_rss_config_cert", - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "String", - "type": "string" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CertificateUploadResponse" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/rack-setup/config/key": { - "post": { - "summary": "Add the private key of an external certificate.", - "description": "This must be paired with its certificate. They may be posted in either order, but one cannot post two keys in a row (or two certs in a row).", - "operationId": "post_rss_config_key", - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "String", - "type": "string" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CertificateUploadResponse" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, "/rack-setup/config/multirack": { "get": { "summary": "Get the current status of the multirack join configuration.", @@ -611,33 +446,6 @@ } } }, - "/rack-setup/config/recovery-user-password-hash": { - "put": { - "summary": "Update the RSS config recovery silo user password hash.", - "operationId": "put_rss_config_recovery_user_password_hash", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PutRssRecoveryUserPasswordHash" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "resource updated" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, "/reload-config": { "post": { "summary": "Instruct wicketd to reload its SMF config properties.", @@ -656,35 +464,6 @@ } } }, - "/repository": { - "put": { - "summary": "Upload a TUF repository to the server.", - "description": "At any given time, wicketd will keep at most one TUF repository in memory. Any previously-uploaded repositories will be discarded.", - "operationId": "put_repository", - "requestBody": { - "content": { - "application/octet-stream": { - "schema": { - "type": "string", - "format": "binary" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "resource updated" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, "/update": { "post": { "summary": "An endpoint to start updating one or more sleds, switches and PSCs.", @@ -1071,31 +850,6 @@ "serial_number" ] }, - "BgpAuthKey": { - "description": "Describes the actual authentication key to use with a BGP peer.\n\nCurrently, only TCP-MD5 authentication is supported.", - "oneOf": [ - { - "description": "TCP-MD5 authentication.", - "type": "object", - "properties": { - "key": { - "description": "The pre-shared key.", - "type": "string" - }, - "kind": { - "type": "string", - "enum": [ - "tcp_md5" - ] - } - }, - "required": [ - "key", - "kind" - ] - } - ] - }, "BgpAuthKeyId": { "description": "The key identifier for authentication to use with a BGP peer.", "allOf": [ @@ -1275,71 +1029,6 @@ "sleds" ] }, - "CertificateUploadResponse": { - "description": "The result of uploading half of a certificate/key pair.", - "oneOf": [ - { - "description": "The key has been uploaded, but we're waiting on its corresponding certificate chain.", - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "waiting_on_cert" - ] - } - }, - "required": [ - "status" - ] - }, - { - "description": "The cert chain has been uploaded, but we're waiting on its corresponding private key.", - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "waiting_on_key" - ] - } - }, - "required": [ - "status" - ] - }, - { - "description": "A cert chain and its key have been accepted.", - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "cert_key_accepted" - ] - } - }, - "required": [ - "status" - ] - }, - { - "description": "A cert chain and its key are valid, but have already been uploaded.", - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "cert_key_duplicate_ignored" - ] - } - }, - "required": [ - "status" - ] - } - ] - }, "ClearUpdateStateOptions": { "type": "object", "properties": { @@ -2626,11 +2315,6 @@ "minLength": 1, "maxLength": 63 }, - "NewPasswordHash": { - "title": "A password hash in PHC string format", - "description": "Password hashes must be in PHC (Password Hashing Competition) string format. Passwords must be hashed with Argon2id. Password hashes may be rejected if the parameters appear not to be secure enough.", - "type": "string" - }, "Oui": { "description": "An Organization Unique Identifier.", "type": "array", @@ -3460,132 +3144,6 @@ }, "type": "string" }, - "PutBgpAuthKeyBody": { - "type": "object", - "properties": { - "key": { - "$ref": "#/components/schemas/BgpAuthKey" - } - }, - "required": [ - "key" - ] - }, - "PutBgpAuthKeyResponse": { - "type": "object", - "properties": { - "status": { - "$ref": "#/components/schemas/SetBgpAuthKeyStatus" - } - }, - "required": [ - "status" - ] - }, - "PutRssRecoveryUserPasswordHash": { - "type": "object", - "properties": { - "hash": { - "$ref": "#/components/schemas/NewPasswordHash" - } - }, - "required": [ - "hash" - ] - }, - "PutRssUserConfigInsensitive": { - "description": "The portion of the RSS configuration that can be posted in one shot.\n\nIt is provided by the operator uploading a TOML file. Sensitive values (certificates, the recovery password hash, and BGP authentication keys) are set separately.\n\nThis version replaces the flat `internal_services_ip_pool_ranges` of the initial version with fully-specified [`service_ip_pools`], letting operators name and describe each pool.\n\n[`service_ip_pools`]: Self::service_ip_pools", - "type": "object", - "properties": { - "allowed_source_ips": { - "description": "IPs or subnets allowed to make requests to user-facing services.", - "allOf": [ - { - "$ref": "#/components/schemas/AllowedSourceIps" - } - ] - }, - "bootstrap_sleds": { - "description": "The slot numbers of the sleds to bring up during RSS.\n\nwicketd maps these back to sleds with the correct identifiers based on the bootstrap sleds it reports.", - "type": "array", - "items": { - "type": "integer", - "format": "uint16", - "minimum": 0 - }, - "uniqueItems": true - }, - "dns_servers": { - "description": "The external DNS server addresses.", - "type": "array", - "items": { - "type": "string", - "format": "ip" - } - }, - "external_dns_ips": { - "description": "Service IP addresses on which external DNS servers are run.", - "type": "array", - "items": { - "type": "string", - "format": "ip" - } - }, - "external_dns_zone_name": { - "description": "The DNS zone name delegated to the rack for external DNS.", - "type": "string" - }, - "external_jumbo_frames_opt_in_enabled": { - "description": "Enable the fleet-wide jumbo-frames opt-in.", - "default": false, - "type": "boolean" - }, - "ntp_servers": { - "description": "The external NTP server addresses.", - "type": "array", - "items": { - "type": "string" - } - }, - "rack_network_config": { - "description": "The user-specified rack network configuration.", - "allOf": [ - { - "$ref": "#/components/schemas/UserSpecifiedRackNetworkConfig" - } - ] - }, - "service_ip_pools": { - "title": "IdOrdMap", - "description": "The service IP pools which may be used for internal services.", - "x-rust-type": { - "crate": "iddqd", - "parameters": [ - { - "$ref": "#/components/schemas/ServiceIpPoolConfig" - } - ], - "path": "iddqd::IdOrdMap", - "version": "*" - }, - "type": "array", - "items": { - "$ref": "#/components/schemas/ServiceIpPoolConfig" - }, - "uniqueItems": true - } - }, - "required": [ - "allowed_source_ips", - "bootstrap_sleds", - "dns_servers", - "external_dns_ips", - "external_dns_zone_name", - "ntp_servers", - "rack_network_config", - "service_ip_pools" - ] - }, "RackInitUuid": { "x-rust-type": { "crate": "omicron-uuid-kinds", @@ -4306,31 +3864,6 @@ "ranges" ] }, - "SetBgpAuthKeyStatus": { - "oneOf": [ - { - "description": "The key was accepted and replaced an old key.", - "type": "string", - "enum": [ - "replaced" - ] - }, - { - "description": "The key was accepted, and is the same as the existing key.", - "type": "string", - "enum": [ - "unchanged" - ] - }, - { - "description": "The key was accepted and is new.", - "type": "string", - "enum": [ - "added" - ] - } - ] - }, "Sff8636Datapath": { "description": "The datapath of an SFF-8636 module.\n\nThis describes the state of a single lane in an SFF module. It includes information about input and output signals, faults, and controls.", "type": "object", diff --git a/wicket/src/cli/command.rs b/wicket/src/cli/command.rs index 7fba229e99a..061f00032b0 100644 --- a/wicket/src/cli/command.rs +++ b/wicket/src/cli/command.rs @@ -4,7 +4,6 @@ //! Code that manages command dispatch from a shell for wicket. -use std::net::SocketAddrV6; use std::process::ExitCode; use anyhow::Result; @@ -14,6 +13,7 @@ use super::{ inventory::InventoryArgs, preflight::PreflightArgs, rack_setup::SetupArgs, rack_update::RackUpdateArgs, upload::UploadArgs, }; +use crate::wicketd::WicketdAddrs; pub(crate) struct CommandOutput<'a> { pub(crate) stdout: &'a mut dyn std::io::Write, @@ -36,27 +36,27 @@ impl ShellApp { pub(crate) async fn exec( self, log: slog::Logger, - wicketd_addr: SocketAddrV6, + addrs: WicketdAddrs, output: CommandOutput<'_>, ) -> Result { match self.command { ShellCommand::UploadRepo(args) => { - args.exec(log, wicketd_addr).await?; + args.exec(log, addrs.commission).await?; Ok(ExitCode::SUCCESS) } ShellCommand::RackUpdate(args) => { - args.exec(log, wicketd_addr, self.global_opts, output).await + args.exec(log, addrs.wicketd, self.global_opts, output).await } ShellCommand::Setup(args) => { - args.exec(log, wicketd_addr, self.global_opts).await?; + args.exec(log, addrs, self.global_opts).await?; Ok(ExitCode::SUCCESS) } ShellCommand::Preflight(args) => { - args.exec(log, wicketd_addr).await?; + args.exec(log, addrs.wicketd).await?; Ok(ExitCode::SUCCESS) } ShellCommand::Inventory(args) => { - args.exec(log, wicketd_addr, output).await?; + args.exec(log, addrs.wicketd, output).await?; Ok(ExitCode::SUCCESS) } } diff --git a/wicket/src/cli/rack_setup.rs b/wicket/src/cli/rack_setup.rs index 092d0384c07..6481e8811ee 100644 --- a/wicket/src/cli/rack_setup.rs +++ b/wicket/src/cli/rack_setup.rs @@ -7,6 +7,8 @@ use crate::ui::defaults::style::BULLET_ICON; use crate::ui::defaults::style::CHECK_ICON; use crate::ui::defaults::style::WARN_ICON; +use crate::wicketd::WicketdAddrs; +use crate::wicketd::create_commission_client; use crate::wicketd::create_wicketd_client; use anyhow::Context; use anyhow::Result; @@ -24,20 +26,20 @@ use std::fmt; use std::io; use std::io::Read; use std::mem; -use std::net::SocketAddrV6; use std::time::Duration; use wicket_common::rack_setup::BgpAuthKeyInfo; use wicket_common::rack_setup::BgpAuthKeyStatus; use wicket_common::rack_setup::DisplaySlice; use wicketd_client::types::GetBgpAuthKeyParams; -use wicketd_client::types::NewPasswordHash; -use wicketd_client::types::PutBgpAuthKeyBody; -use wicketd_client::types::PutRssRecoveryUserPasswordHash; -use wicketd_client::types::SetBgpAuthKeyStatus; use wicketd_commission_types::rack_setup::BgpAuthKey; use wicketd_commission_types::rack_setup::BgpAuthKeyId; +use wicketd_commission_types::rack_setup::CertificatePem; use wicketd_commission_types::rack_setup::CertificateUploadResponse; +use wicketd_commission_types::rack_setup::NewPasswordHash; +use wicketd_commission_types::rack_setup::PrivateKeyPem; +use wicketd_commission_types::rack_setup::PutRecoveryUserPasswordHash; use wicketd_commission_types::rack_setup::PutRssUserConfigInsensitive; +use wicketd_commission_types::rack_setup::SetBgpAuthKeyStatus; use zeroize::Zeroizing; mod config_toml; @@ -87,10 +89,13 @@ impl SetupArgs { pub(crate) async fn exec( self, log: Logger, - wicketd_addr: SocketAddrV6, + addrs: WicketdAddrs, global_opts: GlobalOpts, ) -> Result<()> { - let client = create_wicketd_client(&log, wicketd_addr, WICKETD_TIMEOUT); + let client = + create_wicketd_client(&log, addrs.wicketd, WICKETD_TIMEOUT); + let commission_client = + create_commission_client(&log, addrs.commission, WICKETD_TIMEOUT); match self { SetupArgs::GetConfig => { @@ -119,7 +124,7 @@ impl SetupArgs { .context("failed to parse config TOML")?; slog::info!(log, "uploading config to wicketd..."); - client + commission_client .put_rss_config(&config) .await .context("error uploading config to wicketd")?; @@ -139,16 +144,17 @@ impl SetupArgs { let hash = NewPasswordHash(hash.to_string()); slog::info!(log, "uploading password hash to wicketd..."); - client + commission_client .put_rss_config_recovery_user_password_hash( - &PutRssRecoveryUserPasswordHash { hash }, + &PutRecoveryUserPasswordHash { hash }, ) .await .context("failed to upload password hash to wicketd")?; slog::info!(log, "password set"); } SetupArgs::SetBgpAuthKey(args) => { - args.exec(&log, &client, global_opts).await?; + args.exec(&log, &client, &commission_client, global_opts) + .await?; } SetupArgs::UploadCert => { slog::info!(log, "reading cert from stdin..."); @@ -158,8 +164,8 @@ impl SetupArgs { .context("failed to read certificate from stdin")?; slog::info!(log, "uploading cert to wicketd..."); - let result = client - .post_rss_config_cert(&cert) + let result = commission_client + .post_rss_config_cert(&CertificatePem(cert)) .await .context("failed to upload cert to wicketd")? .into_inner(); @@ -202,8 +208,8 @@ impl SetupArgs { .context("failed to read key from stdin")?; slog::info!(log, "uploading key to wicketd..."); - let result = client - .post_rss_config_key(&key) + let result = commission_client + .post_rss_config_key(&PrivateKeyPem(key)) .await .context("failed to upload key to wicketd")? .into_inner(); @@ -287,6 +293,7 @@ impl SetBgpAuthKeyArgs { self, log: &Logger, client: &wicketd_client::Client, + commission_client: &wicketd_commission_client::Client, global_opts: GlobalOpts, ) -> Result<()> { let mut styles = Styles::default(); @@ -396,12 +403,11 @@ impl SetBgpAuthKeyArgs { let key = read_bgp_md5_key(&prompt)?; let info = BgpAuthKeyInfo::for_key(&key) .to_string_styled(styles.bold); - let response = client - .put_bgp_auth_key(&key_id, &PutBgpAuthKeyBody { key }) + let status = commission_client + .put_bgp_auth_key(key_id, &key) .await - .context("failed to set BGP auth key")?; - - let status = response.into_inner().status; + .context("failed to set BGP auth key")? + .into_inner(); match status { SetBgpAuthKeyStatus::Added => { eprintln!( diff --git a/wicket/src/cli/upload.rs b/wicket/src/cli/upload.rs index c9ec4ea2bb1..9ce2a57e8cc 100644 --- a/wicket/src/cli/upload.rs +++ b/wicket/src/cli/upload.rs @@ -17,7 +17,7 @@ use futures::StreamExt; use reqwest::Body; use tokio_util::io::ReaderStream; -use crate::wicketd::create_wicketd_client; +use crate::wicketd::create_commission_client; // We have observed wicketd running in a switch zone under load take ~60 seconds // to accept a repository; set the timeout to double that to give some headroom. @@ -34,15 +34,7 @@ impl UploadArgs { pub(crate) async fn exec( self, log: slog::Logger, - wicketd_addr: SocketAddrV6, - ) -> Result<()> { - self.do_upload(log, wicketd_addr).await - } - - async fn do_upload( - &self, - log: slog::Logger, - wicketd_addr: SocketAddrV6, + commission_addr: SocketAddrV6, ) -> Result<()> { let repo_bytes = Self::read_repository_from_stdin(&log).await?; let repository_bytes_len = repo_bytes.num_bytes(); @@ -56,16 +48,16 @@ impl UploadArgs { ); } else { slog::info!(log, "uploading repository to wicketd"); - let wicketd_client = create_wicketd_client( + let commission_client = create_commission_client( &log, - wicketd_addr, + commission_addr, WICKETD_UPLOAD_TIMEOUT, ); let body = Body::wrap_stream(futures::stream::iter( repo_bytes.into_iter().map(Ok::<_, Infallible>), )); - wicketd_client + commission_client .put_repository(body) .await .context("error uploading repository to wicketd")?; diff --git a/wicket/src/dispatch.rs b/wicket/src/dispatch.rs index 2cbbecca9ef..c8a26564c03 100644 --- a/wicket/src/dispatch.rs +++ b/wicket/src/dispatch.rs @@ -19,13 +19,15 @@ use slog::Drain; use crate::{ Runner, cli::{CommandOutput, ShellApp}, + wicketd::WicketdAddrs, }; pub fn exec() -> Result { - let wicketd_addr = - SocketAddrV6::new(Ipv6Addr::LOCALHOST, WICKETD_PORT, 0, 0); - let commission_addr = - SocketAddrV6::new(Ipv6Addr::LOCALHOST, WICKETD_COMMISSION_PORT, 0, 0); + let localhost = Ipv6Addr::LOCALHOST; + let addrs = WicketdAddrs { + wicketd: SocketAddrV6::new(localhost, WICKETD_PORT, 0, 0), + commission: SocketAddrV6::new(localhost, WICKETD_COMMISSION_PORT, 0, 0), + }; // SSH_ORIGINAL_COMMAND contains additional arguments, if any. match std::env::var("SSH_ORIGINAL_COMMAND") { @@ -36,17 +38,13 @@ pub fn exec() -> Result { let runtime = tokio::runtime::Runtime::new() .context("creating tokio runtime")?; - runtime.block_on(exec_with_args( - wicketd_addr, - args, - OutputKind::Terminal, - )) + runtime.block_on(exec_with_args(addrs, args, OutputKind::Terminal)) } Err(_) => { // Do not expose log messages via standard error since they'll show up // on top of the TUI. let log = setup_log(&log_path()?, WithStderr::No)?; - Runner::new(log, wicketd_addr, commission_addr).run()?; + Runner::new(log, addrs).run()?; Ok(ExitCode::SUCCESS) } } @@ -66,7 +64,7 @@ pub enum OutputKind<'a> { } pub async fn exec_with_args( - wicketd_addr: SocketAddrV6, + addrs: WicketdAddrs, args: Vec, output: OutputKind<'_>, ) -> Result @@ -82,7 +80,7 @@ where match output { OutputKind::Captured { log, stdout, stderr } => { let output = CommandOutput { stdout, stderr }; - app.exec(log, wicketd_addr, output).await + app.exec(log, addrs, output).await } OutputKind::Terminal => { let log = setup_log( @@ -93,7 +91,7 @@ where let mut stderr = std::io::stderr(); let output = CommandOutput { stdout: &mut stdout, stderr: &mut stderr }; - app.exec(log, wicketd_addr, output).await + app.exec(log, addrs, output).await } } } diff --git a/wicket/src/lib.rs b/wicket/src/lib.rs index 5e09cb91f45..732cf9988ec 100644 --- a/wicket/src/lib.rs +++ b/wicket/src/lib.rs @@ -25,3 +25,4 @@ pub use events::{Action, Event, Recorder, Snapshot}; pub use keymap::{Cmd, KeyHandler}; pub use state::State; pub use ui::{Control, Screen}; +pub use wicketd::WicketdAddrs; diff --git a/wicket/src/runner.rs b/wicket/src/runner.rs index f3fbd102945..f26ec0c98b1 100644 --- a/wicket/src/runner.rs +++ b/wicket/src/runner.rs @@ -17,7 +17,6 @@ use slog::Logger; use slog::{debug, error, info}; use slog_error_chain::InlineErrorChain; use std::io::{Stdout, stdout}; -use std::net::SocketAddrV6; use std::time::Instant; use tokio::sync::mpsc::{ UnboundedReceiver, UnboundedSender, unbounded_channel, @@ -30,7 +29,7 @@ use crate::helpers::get_update_test_error; use crate::state::CreateClearUpdateStateOptions; use crate::state::CreateStartUpdateOptions; use crate::ui::Screen; -use crate::wicketd::{self, WicketdHandle, WicketdManager}; +use crate::wicketd::{self, WicketdAddrs, WicketdHandle, WicketdManager}; use crate::{Action, Cmd, Event, KeyHandler, Recorder, State, TICK_INTERVAL}; // We can avoid a bunch of unnecessary type parameters by picking them ahead of time. @@ -283,22 +282,14 @@ pub struct Runner { #[allow(clippy::new_without_default)] impl Runner { - pub fn new( - log: slog::Logger, - wicketd_addr: SocketAddrV6, - commission_addr: SocketAddrV6, - ) -> Runner { + pub fn new(log: slog::Logger, addrs: WicketdAddrs) -> Runner { let (events_tx, events_rx) = unbounded_channel(); let tokio_rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap(); - let (wicketd, wicketd_manager) = WicketdManager::new( - &log, - events_tx.clone(), - wicketd_addr, - commission_addr, - ); + let (wicketd, wicketd_manager) = + WicketdManager::new(&log, events_tx.clone(), addrs); let core = RunnerCore::new(log); Runner { core, diff --git a/wicket/src/wicketd.rs b/wicket/src/wicketd.rs index 16fd123def8..6540e9420de 100644 --- a/wicket/src/wicketd.rs +++ b/wicket/src/wicketd.rs @@ -25,6 +25,15 @@ use crate::keymap::ShowPopupCmd; use crate::state::ComponentId; use crate::{Cmd, Event}; +/// The addresses of the wicketd server. +#[derive(Clone, Copy, Debug)] +pub struct WicketdAddrs { + /// The address of the lockstep wicketd API. + pub wicketd: SocketAddrV6, + /// The address of the stable commission API. + pub commission: SocketAddrV6, +} + impl From for SpIdentifier { fn from(id: ComponentId) -> Self { match id { @@ -78,27 +87,19 @@ pub struct WicketdManager { log: Logger, rx: mpsc::Receiver, events_tx: UnboundedSender, - wicketd_addr: SocketAddrV6, - commission_addr: SocketAddrV6, + addrs: WicketdAddrs, } impl WicketdManager { pub fn new( log: &Logger, events_tx: UnboundedSender, - wicketd_addr: SocketAddrV6, - commission_addr: SocketAddrV6, + addrs: WicketdAddrs, ) -> (WicketdHandle, WicketdManager) { let log = log.new(o!("component" => "WicketdManager")); let (tx, rx) = tokio::sync::mpsc::channel(CHANNEL_CAPACITY); let handle = WicketdHandle { tx }; - let manager = WicketdManager { - log, - rx, - events_tx, - wicketd_addr, - commission_addr, - }; + let manager = WicketdManager { log, rx, events_tx, addrs }; (handle, manager) } @@ -164,7 +165,7 @@ impl WicketdManager { options: StartUpdateOptions, ) { let log = self.log.clone(); - let addr = self.wicketd_addr; + let addr = self.addrs.wicketd; let events_tx = self.events_tx.clone(); tokio::spawn(async move { let update_client = @@ -197,7 +198,7 @@ impl WicketdManager { options: AbortUpdateOptions, ) { let log = self.log.clone(); - let addr = self.wicketd_addr; + let addr = self.addrs.wicketd; let events_tx = self.events_tx.clone(); tokio::spawn(async move { let update_client = @@ -229,7 +230,7 @@ impl WicketdManager { options: ClearUpdateStateOptions, ) { let log = self.log.clone(); - let addr = self.wicketd_addr; + let addr = self.addrs.wicketd; let events_tx = self.events_tx.clone(); tokio::spawn(async move { let update_client = @@ -266,7 +267,7 @@ impl WicketdManager { poll_inventory_now: mpsc::Sender, ) { let log = self.log.clone(); - let addr = self.wicketd_addr; + let addr = self.addrs.wicketd; tokio::spawn(async move { let client = create_wicketd_client(&log, addr, WICKETD_TIMEOUT); let sp: SpIdentifier = component_id.into(); @@ -290,10 +291,10 @@ impl WicketdManager { fn start_rack_initialization(&self) { let log = self.log.clone(); - let addr = self.wicketd_addr; + let addr = self.addrs.commission; let events_tx = self.events_tx.clone(); tokio::spawn(async move { - let client = create_wicketd_client(&log, addr, WICKETD_TIMEOUT); + let client = create_commission_client(&log, addr, WICKETD_TIMEOUT); let response = match client.post_run_rack_setup().await { Ok(_) => Ok(()), Err(error) => Err(error.to_string()), @@ -309,7 +310,7 @@ impl WicketdManager { fn poll_rack_setup_status(&self) { let log = self.log.clone(); let tx = self.events_tx.clone(); - let addr = self.wicketd_addr; + let addr = self.addrs.wicketd; tokio::spawn(async move { let client = create_wicketd_client(&log, addr, WICKETD_TIMEOUT); let mut ticker = interval(WICKETD_POLL_INTERVAL * 2); @@ -335,7 +336,7 @@ impl WicketdManager { fn poll_location(&self) { let log = self.log.clone(); let tx = self.events_tx.clone(); - let addr = self.commission_addr; + let addr = self.addrs.commission; tokio::spawn(async move { let client = create_commission_client(&log, addr, WICKETD_TIMEOUT); let mut ticker = interval(WICKETD_POLL_INTERVAL * 2); @@ -393,7 +394,7 @@ impl WicketdManager { fn poll_rack_setup_config(&self) { let log = self.log.clone(); let tx = self.events_tx.clone(); - let addr = self.wicketd_addr; + let addr = self.addrs.wicketd; tokio::spawn(async move { let client = create_wicketd_client(&log, addr, WICKETD_TIMEOUT); let mut ticker = interval(WICKETD_POLL_INTERVAL * 2); @@ -426,7 +427,7 @@ impl WicketdManager { fn poll_artifacts_and_event_reports(&self) { let log = self.log.clone(); let tx = self.events_tx.clone(); - let addr = self.wicketd_addr; + let addr = self.addrs.wicketd; tokio::spawn(async move { let client = create_wicketd_client(&log, addr, WICKETD_TIMEOUT); let mut ticker = interval(WICKETD_POLL_INTERVAL * 2); @@ -455,7 +456,7 @@ impl WicketdManager { fn poll_inventory(&self, mut poll_now: mpsc::Receiver) { let log = self.log.clone(); let tx = self.events_tx.clone(); - let addr = self.wicketd_addr; + let addr = self.addrs.wicketd; tokio::spawn(async move { let client = create_wicketd_client(&log, addr, WICKETD_TIMEOUT); diff --git a/wicketd-api/Cargo.toml b/wicketd-api/Cargo.toml index fefc7af99ab..811b4625b9e 100644 --- a/wicketd-api/Cargo.toml +++ b/wicketd-api/Cargo.toml @@ -11,7 +11,6 @@ bootstrap-agent-lockstep-types.workspace = true dropshot.workspace = true gateway-client.workspace = true omicron-common.workspace = true -omicron-passwords.workspace = true omicron-uuid-kinds.workspace = true omicron-workspace-hack.workspace = true schemars.workspace = true diff --git a/wicketd-api/src/lib.rs b/wicketd-api/src/lib.rs index 1e560596ee9..a9a7347fb3f 100644 --- a/wicketd-api/src/lib.rs +++ b/wicketd-api/src/lib.rs @@ -8,10 +8,8 @@ use dropshot::HttpResponseOk; use dropshot::HttpResponseUpdatedNoContent; use dropshot::Path; use dropshot::RequestContext; -use dropshot::StreamingBody; use dropshot::TypedBody; use gateway_client::types::IgnitionCommand; -use omicron_uuid_kinds::RackInitUuid; use schemars::JsonSchema; use semver::Version; use serde::Deserialize; @@ -33,18 +31,10 @@ use wicket_common::rack_update::AbortUpdateOptions; use wicket_common::rack_update::ClearUpdateStateOptions; use wicket_common::rack_update::StartUpdateOptions; use wicket_common::update_events::EventReport; -use wicketd_commission_types::rack_setup::BgpAuthKey; use wicketd_commission_types::rack_setup::BgpAuthKeyId; -use wicketd_commission_types::rack_setup::CertificateUploadResponse; -use wicketd_commission_types::rack_setup::PutRssUserConfigInsensitive; -use wicketd_commission_types::rack_setup::SetBgpAuthKeyStatus; use wicketd_commission_types::update::ClearUpdateStateResponse; use wicketd_commission_types::update::UpdateTargets; -/// Full release repositories are currently (Dec 2024) 1.8 GiB and are likely to -/// continue growing. -const PUT_REPOSITORY_MAX_BYTES: usize = 4 * 1024 * 1024 * 1024; - #[dropshot::api_description] pub trait WicketdApi { type Context; @@ -68,19 +58,6 @@ pub trait WicketdApi { rqctx: RequestContext, ) -> Result, HttpError>; - /// Update (a subset of) the current RSS configuration. - /// - /// Sensitive values (certificates and password hash) are not set through - /// this endpoint. - #[endpoint { - method = PUT, - path = "/rack-setup/config" - }] - async fn put_rss_config( - rqctx: RequestContext, - body: TypedBody, - ) -> Result; - /// Get the current status of the multirack join configuration. #[endpoint { method = GET, @@ -103,32 +80,6 @@ pub trait WicketdApi { body: TypedBody, ) -> Result; - /// Add an external certificate. - /// - /// This must be paired with its private key. They may be posted in either - /// order, but one cannot post two certs in a row (or two keys in a row). - #[endpoint { - method = POST, - path = "/rack-setup/config/cert" - }] - async fn post_rss_config_cert( - rqctx: RequestContext, - body: TypedBody, - ) -> Result, HttpError>; - - /// Add the private key of an external certificate. - /// - /// This must be paired with its certificate. They may be posted in either - /// order, but one cannot post two keys in a row (or two certs in a row). - #[endpoint { - method = POST, - path = "/rack-setup/config/key" - }] - async fn post_rss_config_key( - rqctx: RequestContext, - body: TypedBody, - ) -> Result, HttpError>; - // -- BGP authentication key management /// Return information about BGP authentication keys, including checking @@ -147,27 +98,6 @@ pub trait WicketdApi { params: TypedBody, ) -> Result, HttpError>; - /// Set the BGP authentication key for a particular key ID. - #[endpoint { - method = PUT, - path = "/rack-setup/config/bgp/auth-key/{key_id}" - }] - async fn put_bgp_auth_key( - rqctx: RequestContext, - params: Path, - body: TypedBody, - ) -> Result, HttpError>; - - /// Update the RSS config recovery silo user password hash. - #[endpoint { - method = PUT, - path = "/rack-setup/config/recovery-user-password-hash" - }] - async fn put_rss_config_recovery_user_password_hash( - rqctx: RequestContext, - body: TypedBody, - ) -> Result; - /// Reset all RSS configuration to their default values. #[endpoint { method = DELETE, @@ -186,18 +116,6 @@ pub trait WicketdApi { rqctx: RequestContext, ) -> Result, HttpError>; - /// Run rack setup. - /// - /// Will return an error if not all of the rack setup configuration has - /// been populated. - #[endpoint { - method = POST, - path = "/rack-setup" - }] - async fn post_run_rack_setup( - rqctx: RequestContext, - ) -> Result, HttpError>; - /// A status endpoint used to report high level information known to /// wicketd. /// @@ -215,20 +133,6 @@ pub trait WicketdApi { body_params: TypedBody, ) -> Result, HttpError>; - /// Upload a TUF repository to the server. - /// - /// At any given time, wicketd will keep at most one TUF repository in - /// memory. Any previously-uploaded repositories will be discarded. - #[endpoint { - method = PUT, - path = "/repository", - request_body_max_bytes = PUT_REPOSITORY_MAX_BYTES, - }] - async fn put_repository( - rqctx: RequestContext, - body: StreamingBody, - ) -> Result; - /// An endpoint used to report all available artifacts and event reports. /// /// The order of the returned artifacts is unspecified, and may change between @@ -401,26 +305,6 @@ pub struct GetBgpAuthKeyParams { pub check_valid: BTreeSet, } -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct PutBgpAuthKeyParams { - pub key_id: BgpAuthKeyId, -} - -#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Eq)] -pub struct PutBgpAuthKeyBody { - pub key: BgpAuthKey, -} - -#[derive(Clone, Debug, Serialize, JsonSchema, PartialEq)] -pub struct PutBgpAuthKeyResponse { - pub status: SetBgpAuthKeyStatus, -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct PutRssRecoveryUserPasswordHash { - pub hash: omicron_passwords::NewPasswordHash, -} - #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] pub struct GetInventoryParams { /// Refresh the state of these SPs from MGS prior to returning (instead of diff --git a/wicketd-commission-types/versions/Cargo.toml b/wicketd-commission-types/versions/Cargo.toml index b1328d0885a..90d9eb3bc55 100644 --- a/wicketd-commission-types/versions/Cargo.toml +++ b/wicketd-commission-types/versions/Cargo.toml @@ -22,6 +22,7 @@ sled-hardware-types.workspace = true slog-error-chain.workspace = true thiserror.workspace = true uuid.workspace = true +zeroize.workspace = true [dev-dependencies] gateway-types-versions = { workspace = true, features = ["testing"] } diff --git a/wicketd-commission-types/versions/src/initial/rack_setup.rs b/wicketd-commission-types/versions/src/initial/rack_setup.rs index 6ed29cadd86..bd14bad8d6e 100644 --- a/wicketd-commission-types/versions/src/initial/rack_setup.rs +++ b/wicketd-commission-types/versions/src/initial/rack_setup.rs @@ -19,6 +19,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize, Serializer}; use slog_error_chain::InlineErrorChain; use uuid::Uuid; +use zeroize::Zeroizing; // Re-exports of pinned types from sled-agent-types-versions. pub use sled_agent_types_versions::v1::early_networking::{ @@ -653,7 +654,12 @@ pub struct CertificatePem(pub String); /// /// The key material is redacted from the `Debug` output. #[derive(Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -pub struct PrivateKeyPem(pub String); +pub struct PrivateKeyPem( + // Zeroizing wipes the backing allocation on drop. Serialization and + // deserialization are transparent, so schemars(with = "String") is + // reasonable. + #[schemars(with = "String")] pub Zeroizing, +); impl fmt::Debug for PrivateKeyPem { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/wicketd/Cargo.toml b/wicketd/Cargo.toml index 30525bba07b..a1bb6dfa7d1 100644 --- a/wicketd/Cargo.toml +++ b/wicketd/Cargo.toml @@ -110,3 +110,4 @@ subprocess.workspace = true tar.workspace = true tokio = { workspace = true, features = ["test-util"] } wicket.workspace = true +zeroize.workspace = true diff --git a/wicketd/src/commission/http_entrypoints.rs b/wicketd/src/commission/http_entrypoints.rs index eb56519bb6c..4b911765a8a 100644 --- a/wicketd/src/commission/http_entrypoints.rs +++ b/wicketd/src/commission/http_entrypoints.rs @@ -368,7 +368,7 @@ impl WicketdCommissionApi for WicketdCommissionApiImpl { )?; let response = rss_config - .push_cert(body.into_inner().0) + .push_cert(body.into_inner()) .map_err(|err| HttpError::for_bad_request(None, err))?; Ok(HttpResponseOk(response)) @@ -386,7 +386,7 @@ impl WicketdCommissionApi for WicketdCommissionApiImpl { )?; let response = rss_config - .push_key(body.into_inner().0) + .push_key(body.into_inner()) .map_err(|err| HttpError::for_bad_request(None, err))?; Ok(HttpResponseOk(response)) diff --git a/wicketd/src/commission/mod.rs b/wicketd/src/commission/mod.rs index bbd03d2398e..a3c41734d03 100644 --- a/wicketd/src/commission/mod.rs +++ b/wicketd/src/commission/mod.rs @@ -5,7 +5,7 @@ //! The stable wicketd commissioning API. //! //! This API is used by automated tooling such as rkdeploy to commission new -//! racks. It is a reduced subset of the full, unstable wicketd API. +//! racks. //! //! **Automation must always use the stable API!** //! diff --git a/wicketd/src/http_entrypoints.rs b/wicketd/src/http_entrypoints.rs index 064d88fc335..df18ab68f27 100644 --- a/wicketd/src/http_entrypoints.rs +++ b/wicketd/src/http_entrypoints.rs @@ -15,7 +15,6 @@ use crate::mgs::GetInventoryResponse as GetMgsInventoryResponse; use crate::mgs::records_to_mgs_inventory; use crate::multirack_config::CurrentMultirackJoinConfig; use crate::transceivers::GetTransceiversResponse; -use bootstrap_agent_lockstep_client::ClientInfo as _; use bootstrap_agent_lockstep_types::RackOperationStatus; use dropshot::ApiDescription; use dropshot::HttpError; @@ -23,10 +22,8 @@ use dropshot::HttpResponseOk; use dropshot::HttpResponseUpdatedNoContent; use dropshot::Path; use dropshot::RequestContext; -use dropshot::StreamingBody; use dropshot::TypedBody; use internal_dns_resolver::Resolver; -use omicron_uuid_kinds::RackInitUuid; use sled_agent_types::early_networking::SwitchSlot; use slog::o; use std::sync::Arc; @@ -41,8 +38,6 @@ use wicket_common::rack_setup::GetBgpAuthKeyInfoResponse; use wicket_common::rack_update::AbortUpdateOptions; use wicket_common::update_events::EventReport; use wicketd_api::*; -use wicketd_commission_types::rack_setup::CertificateUploadResponse; -use wicketd_commission_types::rack_setup::PutRssUserConfigInsensitive; use wicketd_commission_types::update::ClearUpdateStateResponse; use crate::ServerContext; @@ -121,35 +116,6 @@ impl WicketdApi for WicketdApiImpl { Ok(HttpResponseOk(config.into())) } - async fn put_rss_config( - rqctx: RequestContext, - body: TypedBody, - ) -> Result { - let ctx = rqctx.context(); - - // We can't run RSS if we don't have an inventory from MGS yet; we always - // need to fill in the bootstrap sleds first. - let inventory = mgs_inventory_or_unavail(&ctx.mgs_handle).await?; - - let mut config = ctx.rss_or_multirack_join_config.lock().unwrap(); - - // Overwrite any non-rss config - let rss_config = config.rss_config_mut_or_default(); - - let ddm_discovered_sleds = &ctx.bootstrap_peers.sleds(); - rss_config - .update( - body.into_inner(), - &ctx.baseboard_id, - &inventory, - &ddm_discovered_sleds, - &ctx.log, - ) - .map_err(|err| HttpError::for_bad_request(None, err))?; - - Ok(HttpResponseUpdatedNoContent()) - } - async fn put_multirack_join_config( rqctx: RequestContext, body: TypedBody, @@ -192,43 +158,6 @@ impl WicketdApi for WicketdApiImpl { Ok(HttpResponseUpdatedNoContent()) } - async fn post_rss_config_cert( - rqctx: RequestContext, - body: TypedBody, - ) -> Result, HttpError> { - let ctx = rqctx.context(); - - let mut config = ctx.rss_or_multirack_join_config.lock().unwrap(); - - let rss_config = config.rss_config_mut_or_conflict( - "cannot post certificates when not preparing for RSS", - )?; - - let response = rss_config - .push_cert(body.into_inner()) - .map_err(|err| HttpError::for_bad_request(None, err))?; - - Ok(HttpResponseOk(response)) - } - - async fn post_rss_config_key( - rqctx: RequestContext, - body: TypedBody, - ) -> Result, HttpError> { - let ctx = rqctx.context(); - - let mut config = ctx.rss_or_multirack_join_config.lock().unwrap(); - let rss_config = config.rss_config_mut_or_conflict( - "cannot post private keys when not preparing for RSS", - )?; - - let response = rss_config - .push_key(body.into_inner()) - .map_err(|err| HttpError::for_bad_request(None, err))?; - - Ok(HttpResponseOk(response)) - } - async fn get_bgp_auth_key_info( rqctx: RequestContext, // A bit weird for a GET request to have a TypedBody, but there's no other @@ -247,39 +176,6 @@ impl WicketdApi for WicketdApiImpl { Ok(HttpResponseOk(GetBgpAuthKeyInfoResponse { data })) } - async fn put_bgp_auth_key( - rqctx: RequestContext, - params: Path, - body: TypedBody, - ) -> Result, HttpError> { - let ctx = rqctx.context(); - let params = params.into_inner(); - - let mut config = ctx.rss_or_multirack_join_config.lock().unwrap(); - let status = config - .set_bgp_auth_key(params.key_id, body.into_inner().key) - .map_err(|err| HttpError::for_bad_request(None, err.to_string()))?; - - Ok(HttpResponseOk(PutBgpAuthKeyResponse { status })) - } - - async fn put_rss_config_recovery_user_password_hash( - rqctx: RequestContext, - body: TypedBody, - ) -> Result { - let ctx = rqctx.context(); - - let mut config = ctx.rss_or_multirack_join_config.lock().unwrap(); - - let rss_config = config.rss_config_mut_or_conflict( - "cannot put recovery user password when not preparing for RSS", - )?; - - rss_config.set_recovery_user_password_hash(body.into_inner().hash); - - Ok(HttpResponseUpdatedNoContent()) - } - async fn delete_rss_config( rqctx: RequestContext, ) -> Result { @@ -310,40 +206,6 @@ impl WicketdApi for WicketdApiImpl { Ok(HttpResponseOk(op_status)) } - async fn post_run_rack_setup( - rqctx: RequestContext, - ) -> Result, HttpError> { - let ctx = rqctx.context(); - let log = &rqctx.log; - - let client = ba_lockstep_client(ctx); - let request = { - let mut config = ctx.rss_or_multirack_join_config.lock().unwrap(); - - let rss_config = config.rss_config_mut_or_conflict( - "cannot run rack setup when not preparing for RSS", - )?; - - rss_config.start_rss_request(&ctx.bootstrap_peers, log).map_err( - |err| HttpError::for_bad_request(None, format!("{err:#}")), - )? - }; - - slog::info!( - ctx.log, - "Sending RSS initialize request to {}", - client.baseurl() - ); - - let init_id = client - .rack_initialize(&request) - .await - .map_err(|err| ba_lockstep_error_to_http(err, "rack setup"))? - .into_inner(); - - Ok(HttpResponseOk(init_id)) - } - async fn get_inventory( rqctx: RequestContext, body_params: TypedBody, @@ -426,17 +288,6 @@ impl WicketdApi for WicketdApiImpl { Ok(HttpResponseOk(GetInventoryResponse::Response { inventory })) } - async fn put_repository( - rqctx: RequestContext, - body: StreamingBody, - ) -> Result { - let rqctx = rqctx.context(); - - rqctx.update_tracker.put_repository(body.into_stream()).await?; - - Ok(HttpResponseUpdatedNoContent()) - } - async fn get_artifacts_and_event_reports( rqctx: RequestContext, ) -> Result, HttpError> diff --git a/wicketd/src/rss_config.rs b/wicketd/src/rss_config.rs index a67cec7fcf1..ed67392e4f4 100644 --- a/wicketd/src/rss_config.rs +++ b/wicketd/src/rss_config.rs @@ -44,8 +44,10 @@ use wicket_common::rack_setup::GetBgpAuthKeyInfoResponse; use wicketd_api::CurrentRssUserConfig; use wicketd_api::CurrentRssUserConfigSensitive; use wicketd_commission_types::rack_setup::BgpAuthKey; +use wicketd_commission_types::rack_setup::CertificatePem; use wicketd_commission_types::rack_setup::CertificateUploadResponse; use wicketd_commission_types::rack_setup::ManualPortConfig; +use wicketd_commission_types::rack_setup::PrivateKeyPem; use wicketd_commission_types::rack_setup::PutRssUserConfigInsensitive; use wicketd_commission_types::rack_setup::UserSpecifiedRackNetworkConfig; use wicketd_commission_types::rack_setup::UserSpecifiedRouterPeerAddr; @@ -247,17 +249,17 @@ impl CurrentRssConfig { pub(crate) fn push_cert( &mut self, - cert: String, + cert: CertificatePem, ) -> Result { - self.partial_external_certificate.cert = Some(cert); + self.partial_external_certificate.cert = Some(cert.0); self.maybe_promote_external_certificate() } pub(crate) fn push_key( &mut self, - key: String, + key: PrivateKeyPem, ) -> Result { - self.partial_external_certificate.key = Some(key); + self.partial_external_certificate.key = Some(key.0.to_string()); self.maybe_promote_external_certificate() } @@ -685,6 +687,7 @@ mod tests { use wicket_common::rack_setup::BgpAuthKeyStatus; use wicketd_commission_types::rack_setup::BgpAuthKeyId; use wicketd_commission_types::rack_setup::SetBgpAuthKeyStatus; + use zeroize::Zeroizing; use super::*; @@ -1064,11 +1067,13 @@ mod tests { let mut config = CurrentRssConfig::default(); assert_eq!( - config.push_cert(cert.clone()).unwrap(), + config.push_cert(CertificatePem(cert.clone())).unwrap(), CertificateUploadResponse::WaitingOnKey, ); assert_eq!( - config.push_key(key.clone()).unwrap(), + config + .push_key(PrivateKeyPem(Zeroizing::new(key.clone()))) + .unwrap(), CertificateUploadResponse::CertKeyAccepted, ); assert_eq!(config.external_certificates.len(), 1); @@ -1076,11 +1081,13 @@ mod tests { // Re-uploading the same pair reports CertKeyDuplicateIgnored and adds // no second entry. assert_eq!( - config.push_cert(cert.clone()).unwrap(), + config.push_cert(CertificatePem(cert.clone())).unwrap(), CertificateUploadResponse::WaitingOnKey, ); assert_eq!( - config.push_key(key.clone()).unwrap(), + config + .push_key(PrivateKeyPem(Zeroizing::new(key.clone()))) + .unwrap(), CertificateUploadResponse::CertKeyDuplicateIgnored, ); assert_eq!(config.external_certificates.len(), 1); @@ -1093,11 +1100,13 @@ mod tests { let other_key = other.end_cert_private_key_as_pem(); assert_ne!(other_cert, cert); assert_eq!( - config.push_cert(other_cert.clone()).unwrap(), + config.push_cert(CertificatePem(other_cert.clone())).unwrap(), CertificateUploadResponse::WaitingOnKey, ); assert_eq!( - config.push_key(other_key.clone()).unwrap(), + config + .push_key(PrivateKeyPem(Zeroizing::new(other_key.clone()))) + .unwrap(), CertificateUploadResponse::CertKeyAccepted, ); assert_eq!(config.external_certificates.len(), 2); diff --git a/wicketd/tests/integration_tests/commission.rs b/wicketd/tests/integration_tests/commission.rs index 1201b3ae2e5..9439328a96c 100644 --- a/wicketd/tests/integration_tests/commission.rs +++ b/wicketd/tests/integration_tests/commission.rs @@ -33,6 +33,7 @@ use wicketd_commission_types_versions::latest::rack_setup::{ use wicketd_commission_types_versions::latest::update::{ StartUpdateOptions, StartUpdateParams, UpdateState, UpdateTargets, }; +use zeroize::Zeroizing; /// Wait for the SP inventory to become ready. async fn wait_for_sp_inventory( @@ -399,7 +400,9 @@ async fn test_commission_rss_config() { // 400. let err = ctx .commission_client - .post_rss_config_key(&PrivateKeyPem("a garbage key".to_string())) + .post_rss_config_key(&PrivateKeyPem(Zeroizing::new( + "a garbage key".to_string(), + ))) .await .expect_err("post_rss_config_key rejects an invalid pair"); assert_client_error(&err, StatusCode::BAD_REQUEST); diff --git a/wicketd/tests/integration_tests/inventory.rs b/wicketd/tests/integration_tests/inventory.rs index 6c34e42e902..d00c214a47a 100644 --- a/wicketd/tests/integration_tests/inventory.rs +++ b/wicketd/tests/integration_tests/inventory.rs @@ -98,7 +98,7 @@ async fn test_inventory() { stderr: &mut stderr, }; - wicket::exec_with_args(wicketd_testctx.wicketd_addr, args, output) + wicket::exec_with_args(wicketd_testctx.wicketd_addrs, args, output) .await .expect("wicket inventory configured-bootstrap-sleds failed"); diff --git a/wicketd/tests/integration_tests/setup.rs b/wicketd/tests/integration_tests/setup.rs index 6377f2974ef..c1a0ca6da55 100644 --- a/wicketd/tests/integration_tests/setup.rs +++ b/wicketd/tests/integration_tests/setup.rs @@ -10,6 +10,7 @@ use gateway_test_utils::setup::GatewayTestContext; use http::StatusCode; use omicron_test_utils::dev::poll::{CondCheckError, wait_for_condition}; use sled_hardware_types::BaseboardId; +use wicket::WicketdAddrs; use wicketd_commission_client::Error; use wicketd_commission_types_versions::latest::inventory::{ SpIdentifier, SpType, @@ -17,7 +18,7 @@ use wicketd_commission_types_versions::latest::inventory::{ use wicketd_commission_types_versions::latest::update::SpUpdateProgress; pub struct WicketdTestContext { - pub wicketd_addr: SocketAddrV6, + pub wicketd_addrs: WicketdAddrs, pub wicketd_client: wicketd_client::Client, // This is not currently used but is kept here because it's easier to debug // this way. @@ -114,7 +115,10 @@ impl WicketdTestContext { }; Self { - wicketd_addr, + wicketd_addrs: WicketdAddrs { + wicketd: wicketd_addr, + commission: commission_addr, + }, wicketd_client, wicketd_raw_client, artifact_addr, diff --git a/wicketd/tests/integration_tests/updates.rs b/wicketd/tests/integration_tests/updates.rs index 8aad9a2cfc1..7ae66ed24fc 100644 --- a/wicketd/tests/integration_tests/updates.rs +++ b/wicketd/tests/integration_tests/updates.rs @@ -87,7 +87,7 @@ async fn test_updates() { .await .unwrap(); wicketd_testctx - .wicketd_client + .commission_client .put_repository(zip_bytes) .await .expect("bytes read and archived"); @@ -301,7 +301,7 @@ async fn test_updates() { stderr: &mut stderr, }; - wicket::exec_with_args(wicketd_testctx.wicketd_addr, args, output) + wicket::exec_with_args(wicketd_testctx.wicketd_addrs, args, output) .await .expect("wicket rack-update clear failed"); @@ -371,7 +371,7 @@ async fn get_rack_update_status( stdout: &mut stdout, stderr: &mut stderr, }; - wicket::exec_with_args(wicketd_testctx.wicketd_addr, args, output) + wicket::exec_with_args(wicketd_testctx.wicketd_addrs, args, output) .await .expect("wicket rack-update status failed to run"); serde_json::from_slice(&stdout) @@ -400,7 +400,7 @@ async fn test_installinator_fetch() { .await .unwrap(); wicketd_testctx - .wicketd_client + .commission_client .put_repository(zip_bytes) .await .expect("bytes read and archived"); @@ -697,7 +697,7 @@ async fn test_update_races() { .await .unwrap(); wicketd_testctx - .wicketd_client + .commission_client .put_repository(zip_bytes.clone()) .await .expect("bytes read and archived"); @@ -739,7 +739,7 @@ async fn test_update_races() { // An update is now running. Try uploading the repository again -- this time // it should fail. wicketd_testctx - .wicketd_client + .commission_client .put_repository(zip_bytes.clone()) .await .expect_err("failed because update is currently running"); @@ -942,7 +942,7 @@ async fn test_update_races() { // Try uploading the repository again -- since no updates are running, this // should succeed. wicketd_testctx - .wicketd_client + .commission_client .put_repository(zip_bytes) .await .expect("no updates currently running"); diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 9cb4bf7f785..334d38aa4ff 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -161,7 +161,7 @@ usdt-impl-d8f496e17d97b5cb = { package = "usdt-impl", version = "0.5.0", default uuid = { version = "1.23.4", features = ["serde", "v4"] } x509-cert = { version = "0.2.5" } zerocopy = { version = "0.8.40", default-features = false, features = ["derive", "simd"] } -zeroize = { version = "1.9.0", features = ["aarch64", "std", "zeroize_derive"] } +zeroize = { version = "1.9.0", features = ["aarch64", "serde", "std", "zeroize_derive"] } zip-164d15cefe24d7eb = { package = "zip", version = "4.6.1", default-features = false, features = ["bzip2", "deflate", "jiff-02", "zstd"] } zip-3b31131e45eafb45 = { package = "zip", version = "0.6.6", default-features = false, features = ["bzip2", "deflate"] } @@ -316,7 +316,7 @@ vergen = { version = "9.1.0", features = ["cargo", "rustc"] } vergen-lib = { version = "9.1.0", features = ["cargo", "git", "rustc"] } x509-cert = { version = "0.2.5" } zerocopy = { version = "0.8.40", default-features = false, features = ["derive", "simd"] } -zeroize = { version = "1.9.0", features = ["aarch64", "std", "zeroize_derive"] } +zeroize = { version = "1.9.0", features = ["aarch64", "serde", "std", "zeroize_derive"] } zip-164d15cefe24d7eb = { package = "zip", version = "4.6.1", default-features = false, features = ["bzip2", "deflate", "jiff-02", "zstd"] } zip-3b31131e45eafb45 = { package = "zip", version = "0.6.6", default-features = false, features = ["bzip2", "deflate"] }