diff --git a/.gitignore b/.gitignore index 8557fc69bc1..dce94bf4e21 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ tools/clickhouse* tools/cockroach* clickhouse/ cockroachdb/ +smf/nexus/root.json diff --git a/Cargo.lock b/Cargo.lock index 42edb9666cf..7aef5c4070f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -267,6 +267,15 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + [[package]] name = "bumpalo" version = "3.7.1" @@ -722,6 +731,12 @@ dependencies = [ "num_enum", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "dof" version = "0.1.5" @@ -1112,6 +1127,19 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "globset" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + [[package]] name = "group" version = "0.10.0" @@ -1716,6 +1744,17 @@ dependencies = [ "syn", ] +[[package]] +name = "olpc-cjson" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ca49fe685014bbf124ee547da94ed7bb65a6eb9dc9c4711773c081af96a39c" +dependencies = [ + "serde", + "serde_json", + "unicode-normalization", +] + [[package]] name = "omicron-common" version = "0.1.0" @@ -1817,6 +1856,7 @@ dependencies = [ "tokio", "tokio-postgres", "toml", + "tough", "uuid", ] @@ -2190,6 +2230,35 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" +[[package]] +name = "path-absolutize" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b288298a7a3a7b42539e3181ba590d32f2d91237b0691ed5f103875c754b3bf5" +dependencies = [ + "path-dedot", +] + +[[package]] +name = "path-dedot" +version = "3.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bfa72956f6be8524f7f7e2b07972dda393cb0008a6df4451f658b7e1bd1af80" +dependencies = [ + "once_cell", +] + +[[package]] +name = "pem" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06673860db84d02a63942fa69cd9543f2624a5df3aea7f33173048fa7ad5cf1a" +dependencies = [ + "base64", + "once_cell", + "regex", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -3090,6 +3159,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_plain" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95455e7e29fada2052e72170af226fbe368a4ca33dee847875325d9fdb133858" +dependencies = [ + "serde", +] + [[package]] name = "serde_tokenstream" version = "0.1.2" @@ -3362,6 +3440,27 @@ dependencies = [ "thiserror", ] +[[package]] +name = "snafu" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "socket2" version = "0.4.2" @@ -3906,6 +4005,33 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tough" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99488309ba53ee931b6ccda1cde07feaab95f214d328e3a7244c0f7563b5909f" +dependencies = [ + "chrono", + "dyn-clone", + "globset", + "hex", + "log", + "olpc-cjson", + "path-absolutize", + "pem", + "percent-encoding", + "reqwest", + "ring", + "serde", + "serde_json", + "serde_plain", + "snafu", + "tempfile", + "untrusted", + "url", + "walkdir", +] + [[package]] name = "tower-service" version = "0.3.1" diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 03d4a9644d9..41d3e938f9a 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -484,6 +484,7 @@ pub enum ResourceType { Oximeter, MetricProducer, Zpool, + UpdateAvailableArtifact, } impl Display for ResourceType { @@ -509,6 +510,8 @@ impl Display for ResourceType { ResourceType::Oximeter => "oximeter", ResourceType::MetricProducer => "metric producer", ResourceType::Zpool => "zpool", + ResourceType::UpdateAvailableArtifact => + "available update artifact", } ) } diff --git a/common/src/sql/dbinit.sql b/common/src/sql/dbinit.sql index 2757b0f3d5b..c1807701073 100644 --- a/common/src/sql/dbinit.sql +++ b/common/src/sql/dbinit.sql @@ -46,7 +46,11 @@ CREATE TABLE omicron.public.rack ( /* Identity metadata (asset) */ id UUID PRIMARY KEY, time_created TIMESTAMPTZ NOT NULL, - time_modified TIMESTAMPTZ NOT NULL + time_modified TIMESTAMPTZ NOT NULL, + + /* Used to configure the updates service URLs */ + tuf_metadata_base_url STRING(512) NOT NULL, + tuf_targets_base_url STRING(512) NOT NULL ); /* @@ -644,6 +648,31 @@ CREATE INDEX ON omicron.public.console_session ( /*******************************************************************/ +CREATE TYPE omicron.public.update_artifact_kind AS ENUM ( + 'zone' +); + +CREATE TABLE omicron.public.update_available_artifact ( + name STRING(40) NOT NULL, + version INT NOT NULL, + kind omicron.public.update_artifact_kind NOT NULL, + + /* the version of the targets.json role this came from */ + targets_role_version INT NOT NULL, + + /* when the metadata this artifact was cached from expires */ + valid_until TIMESTAMPTZ NOT NULL, + + /* data about the target from the targets.json role */ + target_name STRING(512) NOT NULL, + target_sha256 STRING(64) NOT NULL, + target_length INT NOT NULL, + + PRIMARY KEY (name, version, kind) +); + +/*******************************************************************/ + /* * Metadata for the schema itself. This version number isn't great, as there's * nothing to ensure it gets bumped when it should be, but it's a start. diff --git a/nexus/Cargo.toml b/nexus/Cargo.toml index 35ab9382942..6d75c74c76d 100644 --- a/nexus/Cargo.toml +++ b/nexus/Cargo.toml @@ -40,6 +40,7 @@ sled-agent-client = { path = "../sled-agent-client" } structopt = "0.3" thiserror = "1.0" toml = "0.5.6" +tough = { version = "0.12", features = [ "http" ] } [dependencies.api_identity] path = "../api_identity" diff --git a/nexus/examples/config-file.toml b/nexus/examples/config-file.toml index fb4cd61c048..b0a285ec370 100644 --- a/nexus/examples/config-file.toml +++ b/nexus/examples/config-file.toml @@ -43,3 +43,7 @@ level = "info" mode = "file" path = "logs/server.log" if_exists = "append" + +[updates] +# If not present, accessing the TUF updates repository will fail +#tuf_trusted_root = "" diff --git a/nexus/examples/config.toml b/nexus/examples/config.toml index c4091e73266..45576ec51e0 100644 --- a/nexus/examples/config.toml +++ b/nexus/examples/config.toml @@ -47,3 +47,7 @@ mode = "stderr-terminal" #mode = "file" #path = "logs/server.log" #if_exists = "append" + +[updates] +# If not present, accessing the TUF updates repository will fail +#tuf_trusted_root = "" diff --git a/nexus/src/config.rs b/nexus/src/config.rs index 602ba266466..859d4d11add 100644 --- a/nexus/src/config.rs +++ b/nexus/src/config.rs @@ -40,6 +40,13 @@ pub struct ConsoleConfig { pub session_absolute_timeout_minutes: u32, } +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct UpdatesConfig { + /** Trusted root.json role for the TUF updates repository. If `None`, accessing the TUF + * repository will fail. */ + pub tuf_trusted_root: Option, +} + /** * Configuration for a nexus server */ @@ -59,6 +66,9 @@ pub struct Config { pub database: db::Config, /** Authentication-related configuration */ pub authn: AuthnConfig, + /** Updates-related configuration */ + #[serde(default)] + pub updates: UpdatesConfig, } #[derive(Debug)] @@ -164,7 +174,7 @@ impl Config { mod test { use super::{ AuthnConfig, Config, ConsoleConfig, LoadError, LoadErrorKind, - SchemeName, + SchemeName, UpdatesConfig, }; use crate::db; use dropshot::ConfigDropshot; @@ -293,6 +303,8 @@ mod test { level = "debug" path = "/nonexistent/path" if_exists = "fail" + [updates] + tuf_trusted_root = "/path/to/root.json" "##, ) .unwrap(); @@ -330,6 +342,9 @@ mod test { .parse() .unwrap() }, + updates: UpdatesConfig { + tuf_trusted_root: Some(PathBuf::from("/path/to/root.json")) + }, } ); diff --git a/nexus/src/context.rs b/nexus/src/context.rs index 0496b9bd22a..462659c3de0 100644 --- a/nexus/src/context.rs +++ b/nexus/src/context.rs @@ -69,8 +69,8 @@ impl ServerContext { * Create a new context with the given rack id and log. This creates the * underlying nexus as well. */ - pub fn new( - rack_id: &Uuid, + pub async fn new( + rack_id: Uuid, log: Logger, pool: db::Pool, config: &config::Config, @@ -140,7 +140,8 @@ impl ServerContext { pool, config, Arc::clone(&authz), - ), + ) + .await, log, external_authn, authz, diff --git a/nexus/src/db/datastore.rs b/nexus/src/db/datastore.rs index 7e20306592e..befb567b572 100644 --- a/nexus/src/db/datastore.rs +++ b/nexus/src/db/datastore.rs @@ -59,9 +59,9 @@ use crate::db::{ ConsoleSession, Dataset, Disk, DiskAttachment, DiskRuntimeState, Generation, Instance, InstanceRuntimeState, Name, Organization, OrganizationUpdate, OximeterInfo, ProducerEndpoint, Project, - ProjectUpdate, RouterRoute, RouterRouteUpdate, Sled, Vpc, - VpcFirewallRule, VpcRouter, VpcRouterUpdate, VpcSubnet, - VpcSubnetUpdate, VpcUpdate, Zpool, + ProjectUpdate, RouterRoute, RouterRouteUpdate, Sled, + UpdateAvailableArtifact, Vpc, VpcFirewallRule, VpcRouter, + VpcRouterUpdate, VpcSubnet, VpcSubnetUpdate, VpcUpdate, Zpool, }, pagination::paginated, update_and_check::{UpdateAndCheck, UpdateStatus}, @@ -1938,6 +1938,48 @@ impl DataStore { )) }) } + + pub async fn update_available_artifact_upsert( + &self, + artifact: UpdateAvailableArtifact, + ) -> CreateResult { + use db::schema::update_available_artifact::dsl; + diesel::insert_into(dsl::update_available_artifact) + .values(artifact.clone()) + .on_conflict((dsl::name, dsl::version, dsl::kind)) + .do_update() + .set(artifact.clone()) + .returning(UpdateAvailableArtifact::as_returning()) + .get_result_async(self.pool()) + .await + .map_err(|e| { + public_error_from_diesel_pool_create( + e, + ResourceType::UpdateAvailableArtifact, + &artifact.to_string(), + ) + }) + } + + pub async fn update_available_artifact_hard_delete_outdated( + &self, + current_targets_role_version: i64, + ) -> DeleteResult { + // We use the `targets_role_version` column in the table to delete any old rows, keeping + // the table in sync with the current copy of artifacts.json. + use db::schema::update_available_artifact::dsl; + diesel::delete(dsl::update_available_artifact) + .filter(dsl::targets_role_version.lt(current_targets_role_version)) + .execute_async(self.pool()) + .await + .map(|_rows_deleted| ()) + .map_err(|e| { + Error::internal_error(&format!( + "error deleting outdated available artifacts: {:?}", + e + )) + }) + } } #[cfg(test)] diff --git a/nexus/src/db/model.rs b/nexus/src/db/model.rs index a19dbbc216b..1196b39ca55 100644 --- a/nexus/src/db/model.rs +++ b/nexus/src/db/model.rs @@ -9,7 +9,8 @@ use crate::db::identity::{Asset, Resource}; use crate::db::schema::{ console_session, dataset, disk, instance, metric_producer, network_interface, organization, oximeter, project, rack, region, - router_route, sled, vpc, vpc_firewall_rule, vpc_router, vpc_subnet, zpool, + router_route, sled, update_available_artifact, vpc, vpc_firewall_rule, + vpc_router, vpc_subnet, zpool, }; use crate::external_api::params; use crate::internal_api; @@ -396,6 +397,9 @@ where pub struct Rack { #[diesel(embed)] pub identity: RackIdentity, + + pub tuf_metadata_base_url: String, + pub tuf_targets_base_url: String, } /// Database representation of a Sled. @@ -1769,3 +1773,38 @@ impl ConsoleSession { Self { token, user_id, time_last_used: now, time_created: now } } } + +impl_enum_type!( + #[derive(SqlType, Debug)] + #[postgres(type_name = "update_artifact_kind", type_schema = "public")] + pub struct UpdateArtifactKindEnum; + + #[derive(Clone, Debug, Display, AsExpression, FromSqlRow)] + #[display("{0}")] + #[sql_type = "UpdateArtifactKindEnum"] + pub struct UpdateArtifactKind(pub crate::updates::UpdateArtifactKind); + + // Enum values + Zone => b"zone" +); + +#[derive( + Queryable, Insertable, Clone, Debug, Display, Selectable, AsChangeset, +)] +#[table_name = "update_available_artifact"] +#[display("{kind} \"{name}\" v{version}")] +pub struct UpdateAvailableArtifact { + pub name: String, + /// Version of the artifact itself + pub version: i64, + pub kind: UpdateArtifactKind, + /// `version` field of targets.json from the repository + // FIXME this *should* be a NonZeroU64 + pub targets_role_version: i64, + pub valid_until: DateTime, + pub target_name: String, + // FIXME should this be [u8; 32]? + pub target_sha256: String, + // FIXME this *should* be a u64 + pub target_length: i64, +} diff --git a/nexus/src/db/schema.rs b/nexus/src/db/schema.rs index 7d601807df1..c51b73dd6a9 100644 --- a/nexus/src/db/schema.rs +++ b/nexus/src/db/schema.rs @@ -137,6 +137,8 @@ table! { id -> Uuid, time_created -> Timestamptz, time_modified -> Timestamptz, + tuf_metadata_base_url -> Text, + tuf_targets_base_url -> Text, } } @@ -289,6 +291,19 @@ table! { } } +table! { + update_available_artifact (name, version, kind) { + name -> Text, + version -> Int8, + kind -> crate::db::model::UpdateArtifactKindEnum, + targets_role_version -> Int8, + valid_until -> Timestamptz, + target_name -> Text, + target_sha256 -> Text, + target_length -> Int8, + } +} + allow_tables_to_appear_in_same_query!( disk, instance, diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 97b82d31ef4..3c00ce37f31 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -131,6 +131,8 @@ pub fn external_api() -> NexusApiDescription { api.register(hardware_sleds_get)?; api.register(hardware_sleds_get_sled)?; + api.register(updates_refresh)?; + api.register(sagas_get)?; api.register(sagas_get_saga)?; @@ -1868,6 +1870,27 @@ async fn hardware_sleds_get_sled( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +/* + * Updates + */ + +/** + * Refresh update metadata + */ +#[endpoint { + method = POST, + path = "/updates/refresh", +}] +async fn updates_refresh( + rqctx: Arc>>, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let handler = + async { Ok(HttpResponseOk(nexus.updates_refresh_metadata().await?)) }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + /* * Sagas */ diff --git a/nexus/src/lib.rs b/nexus/src/lib.rs index aa070cd3adf..5ca59e79d94 100644 --- a/nexus/src/lib.rs +++ b/nexus/src/lib.rs @@ -26,6 +26,7 @@ pub mod internal_api; // public for testing mod nexus; mod saga_interface; mod sagas; +mod updates; pub use config::Config; pub use context::ServerContext; @@ -87,7 +88,7 @@ impl Server { */ pub async fn start( config: &Config, - rack_id: &Uuid, + rack_id: Uuid, log: &Logger, ) -> Result { let log = log.new(o!("name" => config.id.to_string())); @@ -95,7 +96,7 @@ impl Server { let ctxlog = log.new(o!("component" => "ServerContext")); let pool = db::Pool::new(&config.database); - let apictx = ServerContext::new(rack_id, ctxlog, pool, &config)?; + let apictx = ServerContext::new(rack_id, ctxlog, pool, &config).await?; let http_server_starter_external = dropshot::HttpServerStarter::new( &config.dropshot_external, @@ -168,7 +169,7 @@ pub async fn run_server(config: &Config) -> Result<(), String> { .to_logger("nexus") .map_err(|message| format!("initializing logger: {}", message))?; let rack_id = Uuid::new_v4(); - let server = Server::start(config, &rack_id, &log).await?; + let server = Server::start(config, rack_id, &log).await?; server.register_as_producer().await; server.wait_for_finish().await } diff --git a/nexus/src/nexus.rs b/nexus/src/nexus.rs index 79d8d6479cf..5e06da74df6 100644 --- a/nexus/src/nexus.rs +++ b/nexus/src/nexus.rs @@ -127,6 +127,8 @@ pub struct Nexus { /** Task representing completion of recovered Sagas */ recovery_task: std::sync::Mutex>, + + tuf_trusted_root: Option>, } /* @@ -142,8 +144,8 @@ impl Nexus { * Create a new Nexus instance for the given rack id `rack_id` */ /* TODO-polish revisit rack metadata */ - pub fn new_with_id( - rack_id: &Uuid, + pub async fn new_with_id( + rack_id: Uuid, log: Logger, pool: db::Pool, config: &config::Config, @@ -166,12 +168,16 @@ impl Nexus { )); let nexus = Nexus { id: config.id, - rack_id: *rack_id, + rack_id, log: log.new(o!()), - api_rack_identity: db::model::RackIdentity::new(*rack_id), + api_rack_identity: db::model::RackIdentity::new(rack_id), db_datastore: Arc::clone(&db_datastore), sec_client: Arc::clone(&sec_client), recovery_task: std::sync::Mutex::new(None), + tuf_trusted_root: match &config.updates.tuf_trusted_root { + Some(root) => Some(tokio::fs::read(root).await.unwrap()), + None => None, + }, }; /* TODO-cleanup all the extra Arcs here seems wrong */ @@ -1973,7 +1979,11 @@ impl Nexus { */ fn as_rack(&self) -> db::model::Rack { - db::model::Rack { identity: self.api_rack_identity.clone() } + db::model::Rack { + identity: self.api_rack_identity.clone(), + tuf_metadata_base_url: "http://localhost:8000/metadata".to_string(), + tuf_targets_base_url: "http://localhost:8000/targets".to_string(), + } } pub async fn racks_list( @@ -2249,6 +2259,47 @@ impl Nexus { pub async fn session_hard_delete(&self, token: String) -> DeleteResult { self.db_datastore.session_hard_delete(token).await } + + pub async fn updates_refresh_metadata(&self) -> Result<(), Error> { + let rack = self.as_rack(); + let trust_root = self + .tuf_trusted_root + .as_ref() + .ok_or_else(|| Error::InvalidRequest { + message: "updates system not configured".into(), + })? + .clone(); + + let artifacts = tokio::task::spawn_blocking(move || { + crate::updates::read_artifacts(&rack, &trust_root) + }) + .await + .unwrap() + .map_err(|e| Error::InternalError { + internal_message: format!("error trying to refresh updates: {}", e), + })?; + + // FIXME: if we hit an error in any of these database calls, the available artifact table + // will be out of sync with the current artifacts.json. can we do a transaction or + // something? + + let mut current_version = None; + for artifact in artifacts { + current_version = Some(artifact.targets_role_version); + self.db_datastore + .update_available_artifact_upsert(artifact) + .await?; + } + + // ensure table is in sync with current copy of artifacts.json + if let Some(current_version) = current_version { + self.db_datastore + .update_available_artifact_hard_delete_outdated(current_version) + .await?; + } + + Ok(()) + } } fn generate_session_token() -> String { diff --git a/nexus/src/updates.rs b/nexus/src/updates.rs new file mode 100644 index 00000000000..6faf3b7c597 --- /dev/null +++ b/nexus/src/updates.rs @@ -0,0 +1,87 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use crate::db; +use parse_display::Display; +use serde::Deserialize; +use std::convert::TryInto; + +// Schema for the `artifacts.json` target in the TUF update repository. +#[derive(Clone, Debug, Deserialize)] +pub struct ArtifactsDocument { + pub artifacts: Vec, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct UpdateArtifact { + pub name: String, + pub version: i64, + pub kind: UpdateArtifactKind, + pub target: String, +} + +#[derive(Clone, Debug, Display, Deserialize)] +#[display(style = "kebab-case")] +#[serde(rename_all = "kebab-case")] +pub enum UpdateArtifactKind { + Zone, +} + +// TODO(iliana): make async/.await. awslabs/tough#213 +pub fn read_artifacts( + rack: &db::model::Rack, + tuf_trusted_root: &[u8], +) -> Result< + Vec, + Box, +> { + use std::io::Read; + + let repository = tough::RepositoryLoader::new( + tuf_trusted_root, + rack.tuf_metadata_base_url.parse()?, + rack.tuf_targets_base_url.parse()?, + ) + .load()?; + + let mut artifact_document = Vec::new(); + match repository.read_target(&"artifacts.json".parse()?)? { + Some(mut target) => target.read_to_end(&mut artifact_document)?, + None => return Err("artifacts.json missing".into()), + }; + let artifacts: ArtifactsDocument = + serde_json::from_slice(&artifact_document)?; + + let valid_until = repository + .root() + .signed + .expires + .min(repository.snapshot().signed.expires) + .min(repository.targets().signed.expires) + .min(repository.timestamp().signed.expires); + + let mut v = Vec::new(); + for artifact in artifacts.artifacts { + if let Some(target) = + repository.targets().signed.targets.get(&artifact.target.parse()?) + { + v.push(db::model::UpdateAvailableArtifact { + name: artifact.name, + version: artifact.version, + kind: db::model::UpdateArtifactKind(artifact.kind), + targets_role_version: repository + .targets() + .signed + .version + .get() + .try_into()?, + valid_until, + target_name: artifact.target, + target_sha256: hex::encode(&target.hashes.sha256), + target_length: target.length.try_into()?, + }); + } + } + Ok(v) +} diff --git a/nexus/tests/common/mod.rs b/nexus/tests/common/mod.rs index 9dfc9c0a117..2b5aa3a9613 100644 --- a/nexus/tests/common/mod.rs +++ b/nexus/tests/common/mod.rs @@ -101,7 +101,7 @@ pub async fn test_setup_with_config( let clickhouse = dev::clickhouse::ClickHouseInstance::new(0).await.unwrap(); config.database.url = database.pg_config().clone(); - let server = omicron_nexus::Server::start(&config, &rack_id, &logctx.log) + let server = omicron_nexus::Server::start(&config, rack_id, &logctx.log) .await .unwrap(); let testctx_external = ClientTestContext::new( diff --git a/openapi/nexus.json b/openapi/nexus.json index f139df5cc0c..38af7f9fb30 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -2703,6 +2703,17 @@ } } } + }, + "/updates/refresh": { + "post": { + "description": "Refresh update metadata", + "operationId": "updates_refresh", + "responses": { + "200": { + "description": "successful operation" + } + } + } } }, "components": { diff --git a/smf/nexus/config.toml b/smf/nexus/config.toml index 402d4d5bcf2..e849d6bb2d7 100644 --- a/smf/nexus/config.toml +++ b/smf/nexus/config.toml @@ -39,3 +39,7 @@ mode = "stderr-terminal" #mode = "file" #path = "logs/server.log" #if_exists = "append" + +[updates] +# If not present, accessing the TUF updates repository will fail +#tuf_trusted_root = "/opt/oxide/nexus/pkg/root.json" diff --git a/tools/oxapi_demo b/tools/oxapi_demo index 4afbb0f66ff..4bc14fd1395 100755 --- a/tools/oxapi_demo +++ b/tools/oxapi_demo @@ -72,6 +72,10 @@ HARDWARE sleds_list sled_get SLED_ID + +UPDATES + + updates_refresh EOF )" @@ -363,4 +367,9 @@ function cmd_sled_get do_curl "/hardware/sleds/$1" } +function cmd_updates_refresh +{ + do_curl /updates/refresh -X POST +} + main "$@"