Skip to content

Commit aec3cd8

Browse files
authored
move the TUF trust store to the database (#8488)
Closes #3578 Depends on oxidecomputer/tufaceous#30 This removes the `[updates]` section of the Nexus config, instead opting to allow operators to configure which TUF root roles they trust via the API. See [RFD 447](https://rfd.shared.oxide.computer/rfd/447). This deliberately punts on a few choices: any identifying information about the roots themselves; which trusted root was used to verify a repository is not recorded; deleting a trusted root does not impact any uploaded repositories; synchronizing the Nexus trust store to Wicket; whether to automatically add new versions of roots to the trust store (or possibly replace the older version, to allow for a real key rotation scenario). A future RFD and implementation PR should cover these.
1 parent 67c25df commit aec3cd8

File tree

35 files changed

+1258
-357
lines changed

35 files changed

+1258
-357
lines changed

Cargo.lock

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/src/api/external/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,7 @@ pub enum ResourceType {
979979
RoleBuiltin,
980980
TufRepo,
981981
TufArtifact,
982+
TufTrustRoot,
982983
SwitchPort,
983984
UserBuiltin,
984985
Zpool,

dev-tools/reconfigurator-cli/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ use tufaceous_artifact::ArtifactHash;
6666
use tufaceous_artifact::ArtifactVersion;
6767
use tufaceous_artifact::ArtifactVersionError;
6868
use tufaceous_lib::assemble::ArtifactManifest;
69-
use update_common::artifacts::{ArtifactsWithPlan, ControlPlaneZonesMode};
69+
use update_common::artifacts::{
70+
ArtifactsWithPlan, ControlPlaneZonesMode, VerificationMode,
71+
};
7072

7173
mod log_capture;
7274

@@ -2195,6 +2197,7 @@ fn extract_tuf_repo_description(
21952197
None,
21962198
repo_hash,
21972199
ControlPlaneZonesMode::Split,
2200+
VerificationMode::BlindlyTrustAnything,
21982201
log,
21992202
)
22002203
.await

nexus-config/src/nexus_config.rs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,6 @@ pub struct ConsoleConfig {
238238
pub session_absolute_timeout_minutes: u32,
239239
}
240240

241-
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
242-
pub struct UpdatesConfig {
243-
/// Trusted root.json role for the TUF updates repository.
244-
pub trusted_root: Utf8PathBuf,
245-
}
246-
247241
/// Options to tweak database schema changes.
248242
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
249243
pub struct SchemaConfig {
@@ -841,10 +835,6 @@ pub struct PackageConfig {
841835
/// Timeseries database configuration.
842836
#[serde(default)]
843837
pub timeseries_db: TimeseriesDbConfig,
844-
/// Updates-related configuration. Updates APIs return 400 Bad Request when
845-
/// this is unconfigured.
846-
#[serde(default)]
847-
pub updates: Option<UpdatesConfig>,
848838
/// Describes how to handle and perform schema changes.
849839
#[serde(default)]
850840
pub schema: Option<SchemaConfig>,
@@ -1036,8 +1026,6 @@ mod test {
10361026
if_exists = "fail"
10371027
[timeseries_db]
10381028
address = "[::1]:9000"
1039-
[updates]
1040-
trusted_root = "/path/to/root.json"
10411029
[tunables]
10421030
max_vpc_ipv4_subnet_prefix = 27
10431031
[deployment]
@@ -1179,9 +1167,6 @@ mod test {
11791167
0,
11801168
))),
11811169
},
1182-
updates: Some(UpdatesConfig {
1183-
trusted_root: Utf8PathBuf::from("/path/to/root.json"),
1184-
}),
11851170
schema: None,
11861171
tunables: Tunables {
11871172
max_vpc_ipv4_subnet_prefix: 27,
@@ -1511,9 +1496,6 @@ mod test {
15111496
if_exists = "fail"
15121497
[timeseries_db]
15131498
address = "[::1]:9000"
1514-
[updates]
1515-
trusted_root = "/path/to/root.json"
1516-
default_base_url = "http://example.invalid/"
15171499
[tunables]
15181500
max_vpc_ipv4_subnet_prefix = 100
15191501
[deployment]

nexus/auth/src/authz/api_resources.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,57 @@ impl AuthorizedResource for SiloUserList {
668668
}
669669
}
670670

671+
#[derive(Clone, Copy, Debug)]
672+
pub struct UpdateTrustRootList;
673+
674+
/// Singleton representing the [`UpdateTrustRootList`] itself for authz purposes
675+
pub const UPDATE_TRUST_ROOT_LIST: UpdateTrustRootList = UpdateTrustRootList;
676+
677+
impl Eq for UpdateTrustRootList {}
678+
679+
impl PartialEq for UpdateTrustRootList {
680+
fn eq(&self, _: &Self) -> bool {
681+
true
682+
}
683+
}
684+
685+
impl oso::PolarClass for UpdateTrustRootList {
686+
fn get_polar_class_builder() -> oso::ClassBuilder<Self> {
687+
oso::Class::builder()
688+
.with_equality_check()
689+
.add_attribute_getter("fleet", |_: &UpdateTrustRootList| FLEET)
690+
}
691+
}
692+
693+
impl AuthorizedResource for UpdateTrustRootList {
694+
fn load_roles<'fut>(
695+
&'fut self,
696+
opctx: &'fut OpContext,
697+
authn: &'fut authn::Context,
698+
roleset: &'fut mut RoleSet,
699+
) -> futures::future::BoxFuture<'fut, Result<(), Error>> {
700+
// There are no roles on the UpdateTrustRootList, only permissions.
701+
// But we still need to load the Fleet-related roles to verify that the
702+
// actor has the "admin" role on the Fleet (possibly conferred from a
703+
// Silo role).
704+
load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed()
705+
}
706+
707+
fn on_unauthorized(
708+
&self,
709+
_: &Authz,
710+
error: Error,
711+
_: AnyActor,
712+
_: Action,
713+
) -> Error {
714+
error
715+
}
716+
717+
fn polar_class(&self) -> oso::Class {
718+
Self::get_polar_class()
719+
}
720+
}
721+
671722
/// System software target version configuration
672723
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
673724
pub struct TargetReleaseConfig;
@@ -1117,6 +1168,14 @@ authz_resource! {
11171168
polar_snippet = FleetChild,
11181169
}
11191170

1171+
authz_resource! {
1172+
name = "TufTrustRoot",
1173+
parent = "Fleet",
1174+
primary_key = { uuid_kind = TufTrustRootKind },
1175+
roles_allowed = false,
1176+
polar_snippet = FleetChild,
1177+
}
1178+
11201179
authz_resource! {
11211180
name = "Certificate",
11221181
parent = "Silo",

nexus/auth/src/authz/omicron.polar

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,16 @@ resource BlueprintConfig {
383383
has_relation(fleet: Fleet, "parent_fleet", list: BlueprintConfig)
384384
if list.fleet = fleet;
385385

386+
# Describes the policy for accessing "/v1/system/update/trust-roots" in the API
387+
resource UpdateTrustRootList {
388+
permissions = [ "list_children", "create_child" ];
389+
relations = { parent_fleet: Fleet };
390+
"list_children" if "viewer" on "parent_fleet";
391+
"create_child" if "admin" on "parent_fleet";
392+
}
393+
has_relation(fleet: Fleet, "parent_fleet", collection: UpdateTrustRootList)
394+
if collection.fleet = fleet;
395+
386396
# Describes the policy for accessing blueprints
387397
resource TargetReleaseConfig {
388398
permissions = [

nexus/auth/src/authz/oso_generic.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ pub fn make_omicron_oso(log: &slog::Logger) -> Result<OsoInit, anyhow::Error> {
114114
SiloCertificateList::get_polar_class(),
115115
SiloIdentityProviderList::get_polar_class(),
116116
SiloUserList::get_polar_class(),
117+
UpdateTrustRootList::get_polar_class(),
117118
TargetReleaseConfig::get_polar_class(),
118119
AlertClassList::get_polar_class(),
119120
];
@@ -163,6 +164,7 @@ pub fn make_omicron_oso(log: &slog::Logger) -> Result<OsoInit, anyhow::Error> {
163164
Sled::init(),
164165
TufRepo::init(),
165166
TufArtifact::init(),
167+
TufTrustRoot::init(),
166168
Alert::init(),
167169
AlertReceiver::init(),
168170
WebhookSecret::init(),

nexus/db-lookup/src/lookup.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use omicron_uuid_kinds::PhysicalDiskUuid;
3434
use omicron_uuid_kinds::SupportBundleUuid;
3535
use omicron_uuid_kinds::TufArtifactKind;
3636
use omicron_uuid_kinds::TufRepoKind;
37+
use omicron_uuid_kinds::TufTrustRootUuid;
3738
use omicron_uuid_kinds::TypedUuid;
3839
use omicron_uuid_kinds::WebhookSecretUuid;
3940
use slog::{error, trace};
@@ -300,6 +301,10 @@ impl<'a> LookupPath<'a> {
300301
SupportBundle::PrimaryKey(Root { lookup_root: self }, id)
301302
}
302303

304+
pub fn tuf_trust_root(self, id: TufTrustRootUuid) -> TufTrustRoot<'a> {
305+
TufTrustRoot::PrimaryKey(Root { lookup_root: self }, id)
306+
}
307+
303308
pub fn silo_image_id(self, id: Uuid) -> SiloImage<'a> {
304309
SiloImage::PrimaryKey(Root { lookup_root: self }, id)
305310
}
@@ -823,6 +828,14 @@ lookup_resource! {
823828
primary_key_columns = [ { column_name = "id", uuid_kind = TufArtifactKind } ]
824829
}
825830

831+
lookup_resource! {
832+
name = "TufTrustRoot",
833+
ancestors = [],
834+
lookup_by_name = false,
835+
soft_deletes = true,
836+
primary_key_columns = [ { column_name = "id", uuid_kind = TufTrustRootKind } ]
837+
}
838+
826839
lookup_resource! {
827840
name = "UserBuiltin",
828841
ancestors = [],

nexus/db-model/src/schema_versions.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use std::{collections::BTreeMap, sync::LazyLock};
1616
///
1717
/// This must be updated when you change the database schema. Refer to
1818
/// schema/crdb/README.adoc in the root of this repository for details.
19-
pub const SCHEMA_VERSION: Version = Version::new(159, 0, 0);
19+
pub const SCHEMA_VERSION: Version = Version::new(160, 0, 0);
2020

2121
/// List of all past database schema versions, in *reverse* order
2222
///
@@ -28,6 +28,7 @@ static KNOWN_VERSIONS: LazyLock<Vec<KnownVersion>> = LazyLock::new(|| {
2828
// | leaving the first copy as an example for the next person.
2929
// v
3030
// KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"),
31+
KnownVersion::new(160, "tuf-trust-root"),
3132
KnownVersion::new(159, "sled-config-desired-host-phase-2"),
3233
KnownVersion::new(158, "drop-builtin-roles"),
3334
KnownVersion::new(157, "user-data-export"),

0 commit comments

Comments
 (0)