Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
4610d1a
first cut
davepacheco Mar 20, 2022
ef932a1
rename Lookup
davepacheco Mar 20, 2022
9cc52b0
formatting nit
davepacheco Mar 20, 2022
5845149
formatting
davepacheco Mar 20, 2022
49a10e3
fix
davepacheco Mar 20, 2022
491f30c
some notes, PoC diesel impl
davepacheco Mar 20, 2022
037b78f
get rid of Root
davepacheco Mar 21, 2022
ebaefb1
make LookupPath reachable through the whole path
davepacheco Mar 21, 2022
ce4b362
some cleanup
davepacheco Mar 21, 2022
aae2da4
flesh out macros
davepacheco Mar 21, 2022
73f0f4b
more macro cleanup
davepacheco Mar 21, 2022
68b51cf
more cleanup
davepacheco Mar 21, 2022
6d265bb
more commonizing
davepacheco Mar 21, 2022
e4a5844
cleanup
davepacheco Mar 21, 2022
0e73f25
more commonizing
davepacheco Mar 21, 2022
a414c1f
lookup done enough to start trying it out
davepacheco Mar 21, 2022
c7941b3
prototype a few call sites in Nexus
davepacheco Mar 21, 2022
5690db3
some basic tests are working by accident
davepacheco Mar 21, 2022
295182b
cleanup
davepacheco Mar 21, 2022
bcf57a6
fix up prototyping
davepacheco Mar 21, 2022
43d0102
replace one XXX with a comment
davepacheco Mar 21, 2022
0a494ae
in-progress update to return entire path -- cannot work with macro_ru…
davepacheco Mar 22, 2022
013e981
it works with macro_rules!
davepacheco Mar 22, 2022
785eda2
half-finished effort at proc macro approah
davepacheco Mar 25, 2022
a48722a
more progress
davepacheco Mar 25, 2022
dd5ce40
more work
davepacheco Mar 26, 2022
1f1615c
closer to compiling
davepacheco Mar 26, 2022
4fede01
closer to compiling
davepacheco Mar 26, 2022
30ecdc8
nearly fixed
davepacheco Mar 28, 2022
9f557db
compiles!
davepacheco Mar 28, 2022
064e64d
generate child lookup functions
davepacheco Mar 28, 2022
f2d5770
rip out dead code, fix test
davepacheco Mar 28, 2022
c222c7b
clippy
davepacheco Mar 29, 2022
019d15f
make it a function-like macro
davepacheco Mar 29, 2022
de35510
remove dead code
davepacheco Mar 29, 2022
872ea9a
re-add Nexus changes
davepacheco Mar 29, 2022
44acb43
add some docs
davepacheco Mar 29, 2022
51b0af8
documentation for `LookupPath`
davepacheco Mar 30, 2022
9897ca2
more docs
davepacheco Mar 30, 2022
af6fca3
more docs
davepacheco Mar 30, 2022
977b9c0
more documentation
davepacheco Mar 30, 2022
58b03ed
more cleanup
davepacheco Mar 30, 2022
d68e1c5
macro impl cleanup: part 1 (naming)
davepacheco Mar 30, 2022
cf6591e
more extraction
davepacheco Mar 30, 2022
718b0a0
more cleanup
davepacheco Mar 30, 2022
ce6066d
more cleanup
davepacheco Mar 30, 2022
2d5353d
macro impl: commonize Resource
davepacheco Mar 30, 2022
a71e1a1
macro impl: no need to special-case authz names
davepacheco Mar 30, 2022
e25e9aa
macro impl: minor cleanup
davepacheco Mar 30, 2022
7230871
macro impl: no need to first-class model type
davepacheco Mar 30, 2022
07eaab8
macro impl: authz type need not be first-classed
davepacheco Mar 30, 2022
ea67303
clippy
davepacheco Mar 30, 2022
62e5eea
macro impl: add comment
davepacheco Mar 30, 2022
32eab6e
Merge branch 'main' into new-lookup
davepacheco Mar 30, 2022
fa0f1d6
fix mismerge
davepacheco Mar 30, 2022
6f9411c
convert route endpoints to new lookup API
davepacheco Mar 31, 2022
d3c9f1d
fixes
davepacheco Mar 31, 2022
1cb9235
fix style
davepacheco Mar 31, 2022
386549a
oh right, we can remove the hand-written database functions now
davepacheco Mar 31, 2022
342771c
convert routers to new lookup API
davepacheco Mar 31, 2022
6910716
update VPCs and everything beneath them
davepacheco Mar 31, 2022
a98bb72
remove various lookup_by_id functions
davepacheco Mar 31, 2022
ccebf63
Merge branch 'main' into convert-new-lookup-more
davepacheco Mar 31, 2022
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
288 changes: 1 addition & 287 deletions nexus/src/db/datastore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,34 +626,6 @@ impl DataStore {
})
}

/// Fetch an [`authz::Organization`] based on its id
pub async fn organization_lookup_by_id(
&self,
// TODO: OpContext, to verify actor has permission to lookup
organization_id: Uuid,
) -> LookupResult<authz::Organization> {
use db::schema::organization::dsl;
// We only do this database lookup to verify that the Organization with
// this id exists and hasn't been deleted.
let _: Uuid = dsl::organization
.filter(dsl::time_deleted.is_null())
.filter(dsl::id.eq(organization_id))
.select(dsl::id)
.first_async(self.pool())
.await
.map_err(|e| {
public_error_from_diesel_pool(
e,
ErrorHandler::NotFoundByLookup(
ResourceType::Organization,
LookupType::ById(organization_id),
),
)
})?;
Ok(authz::FLEET
.organization(organization_id, LookupType::ById(organization_id)))
}

/// Look up the id for an organization based on its name
///
/// Returns an [`authz::Organization`] (which makes the id available).
Expand Down Expand Up @@ -882,32 +854,6 @@ impl DataStore {
})
}

/// Fetch an [`authz::Project`] based on its id
pub async fn project_lookup_by_id(
&self,
project_id: Uuid,
) -> LookupResult<authz::Project> {
use db::schema::project::dsl;
let organization_id = dsl::project
.filter(dsl::time_deleted.is_null())
.filter(dsl::id.eq(project_id))
.select(dsl::organization_id)
.first_async(self.pool())
.await
.map_err(|e| {
public_error_from_diesel_pool(
e,
ErrorHandler::NotFoundByLookup(
ResourceType::Project,
LookupType::ById(project_id),
),
)
})?;
let authz_organization =
self.organization_lookup_by_id(organization_id).await?;
Ok(authz_organization.project(project_id, LookupType::ById(project_id)))
}

/// Look up the id for a Project based on its name
///
/// Returns an [`authz::Project`] (which makes the id available).
Expand Down Expand Up @@ -1074,35 +1020,6 @@ impl DataStore {
})
}

/// Fetch an [`authz::Instance`] based on its id
pub async fn instance_lookup_by_id(
&self,
instance_id: Uuid,
) -> LookupResult<authz::Instance> {
use db::schema::instance::dsl;
let project_id = dsl::instance
.filter(dsl::time_deleted.is_null())
.filter(dsl::id.eq(instance_id))
.select(dsl::project_id)
.first_async(self.pool())
.await
.map_err(|e| {
public_error_from_diesel_pool(
e,
ErrorHandler::NotFoundByLookup(
ResourceType::Instance,
LookupType::ById(instance_id),
),
)
})?;
let authz_project = self.project_lookup_by_id(project_id).await?;
Ok(authz_project.child_generic(
ResourceType::Instance,
instance_id,
LookupType::ById(instance_id),
))
}

/// Look up the id for an Instance based on its name
///
/// Returns an [`authz::Instance`] (which makes the id available).
Expand Down Expand Up @@ -1381,35 +1298,6 @@ impl DataStore {
})
}

/// Fetch an [`authz::Disk`] based on its id
pub async fn disk_lookup_by_id(
&self,
disk_id: Uuid,
) -> LookupResult<authz::Disk> {
use db::schema::disk::dsl;
let project_id = dsl::disk
.filter(dsl::time_deleted.is_null())
.filter(dsl::id.eq(disk_id))
.select(dsl::project_id)
.first_async(self.pool())
.await
.map_err(|e| {
public_error_from_diesel_pool(
e,
ErrorHandler::NotFoundByLookup(
ResourceType::Disk,
LookupType::ById(disk_id),
),
)
})?;
let authz_project = self.project_lookup_by_id(project_id).await?;
Ok(authz_project.child_generic(
ResourceType::Disk,
disk_id,
LookupType::ById(disk_id),
))
}

/// Look up the id for a Disk based on its name
///
/// Returns an [`authz::Disk`] (which makes the id available).
Expand All @@ -1420,7 +1308,7 @@ impl DataStore {
// Projects), we don't do an authz check in the "lookup_by_path" functions
// because we don't know if the caller has access to do the lookup. For
// leaf resources (like Instances and Disks), though, we do. We could do
// the authz check here, and in disk_lookup_by_id() too. Should we?
// the authz check here. Should we?
pub async fn disk_lookup_by_path(
&self,
organization_name: &Name,
Expand Down Expand Up @@ -2144,78 +2032,6 @@ impl DataStore {

// VPCs

/// Fetches a Vpc from the database and returns both the database row
/// and an [`authz::Vpc`] for doing authz checks
///
/// See [`DataStore::organization_lookup_noauthz()`] for intended use cases
/// and caveats.
// TODO-security See the note on organization_lookup_noauthz().
async fn vpc_lookup_noauthz(
&self,
authz_project: &authz::Project,
vpc_name: &Name,
) -> LookupResult<(authz::Vpc, Vpc)> {
use db::schema::vpc::dsl;
dsl::vpc
.filter(dsl::time_deleted.is_null())
.filter(dsl::project_id.eq(authz_project.id()))
.filter(dsl::name.eq(vpc_name.clone()))
.select(Vpc::as_select())
.first_async(self.pool())
.await
.map_err(|e| {
public_error_from_diesel_pool(
e,
ErrorHandler::NotFoundByLookup(
ResourceType::Vpc,
LookupType::ByName(vpc_name.as_str().to_owned()),
),
)
})
.map(|d| {
(
authz_project.child_generic(
ResourceType::Vpc,
d.id(),
LookupType::from(&vpc_name.0),
),
d,
)
})
}

/// Look up the id for a Vpc based on its name
///
/// Returns an [`authz::Vpc`] (which makes the id available).
///
/// Like the other "lookup_by_path()" functions, this function does no authz
/// checks.
pub async fn vpc_lookup_by_path(
&self,
organization_name: &Name,
project_name: &Name,
vpc_name: &Name,
) -> LookupResult<authz::Vpc> {
let authz_project = self
.project_lookup_by_path(organization_name, project_name)
.await?;
self.vpc_lookup_noauthz(&authz_project, vpc_name).await.map(|(v, _)| v)
}

/// Lookup a Vpc by name and return the full database record, along
/// with an [`authz::Vpc`] for subsequent authorization checks
pub async fn vpc_fetch(
&self,
opctx: &OpContext,
authz_project: &authz::Project,
name: &Name,
) -> LookupResult<(authz::Vpc, Vpc)> {
let (authz_vpc, db_vpc) =
self.vpc_lookup_noauthz(authz_project, name).await?;
opctx.authorize(authz::Action::Read, &authz_vpc).await?;
Ok((authz_vpc, db_vpc))
}

pub async fn project_list_vpcs(
&self,
opctx: &OpContext,
Expand Down Expand Up @@ -2292,33 +2108,6 @@ impl DataStore {
})
}

// TODO-security TODO-cleanup Remove this function. Update callers to use
// vpc_lookup_by_path() or vpc_fetch() instead.
pub async fn vpc_fetch_by_name(
&self,
project_id: &Uuid,
vpc_name: &Name,
) -> LookupResult<Vpc> {
use db::schema::vpc::dsl;

dsl::vpc
.filter(dsl::time_deleted.is_null())
.filter(dsl::project_id.eq(*project_id))
.filter(dsl::name.eq(vpc_name.clone()))
.select(Vpc::as_select())
.get_result_async(self.pool())
.await
.map_err(|e| {
public_error_from_diesel_pool(
e,
ErrorHandler::NotFoundByLookup(
ResourceType::Vpc,
LookupType::ByName(vpc_name.as_str().to_owned()),
),
)
})
}

pub async fn project_delete_vpc(
&self,
opctx: &OpContext,
Expand Down Expand Up @@ -2489,81 +2278,6 @@ impl DataStore {
.map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server))
}

/// Fetches a VpcSubnet from the database and returns both the database row
/// and an [`authz::VpcSubnet`] for doing authz checks
///
/// See [`DataStore::organization_lookup_noauthz()`] for intended use cases
/// and caveats.
// TODO-security See the note on organization_lookup_noauthz().
async fn vpc_subnet_lookup_noauthz(
&self,
authz_vpc: &authz::Vpc,
subnet_name: &Name,
) -> LookupResult<(authz::VpcSubnet, VpcSubnet)> {
use db::schema::vpc_subnet::dsl;
dsl::vpc_subnet
.filter(dsl::time_deleted.is_null())
.filter(dsl::vpc_id.eq(authz_vpc.id()))
.filter(dsl::name.eq(subnet_name.clone()))
.select(VpcSubnet::as_select())
.first_async(self.pool())
.await
.map_err(|e| {
public_error_from_diesel_pool(
e,
ErrorHandler::NotFoundByLookup(
ResourceType::VpcSubnet,
LookupType::ByName(subnet_name.as_str().to_owned()),
),
)
})
.map(|d| {
(
authz_vpc.child_generic(
ResourceType::VpcSubnet,
d.id(),
LookupType::from(&subnet_name.0),
),
d,
)
})
}

/// Look up the id for a VpcSubnet based on its name
///
/// Returns an [`authz::VpcSubnet`] (which makes the id available).
///
/// Like the other "lookup_by_path()" functions, this function does no authz
/// checks.
pub async fn vpc_subnet_lookup_by_path(
&self,
organization_name: &Name,
project_name: &Name,
vpc_name: &Name,
subnet_name: &Name,
) -> LookupResult<authz::Vpc> {
let authz_vpc = self
.vpc_lookup_by_path(organization_name, project_name, vpc_name)
.await?;
self.vpc_subnet_lookup_noauthz(&authz_vpc, subnet_name)
.await
.map(|(v, _)| v)
}

/// Lookup a VpcSubnet by name and return the full database record, along
/// with an [`authz::VpcSubnet`] for subsequent authorization checks
pub async fn vpc_subnet_fetch(
&self,
opctx: &OpContext,
authz_vpc: &authz::Vpc,
name: &Name,
) -> LookupResult<(authz::VpcSubnet, VpcSubnet)> {
let (authz_vpc_subnet, db_vpc_subnet) =
self.vpc_subnet_lookup_noauthz(authz_vpc, name).await?;
opctx.authorize(authz::Action::Read, &authz_vpc_subnet).await?;
Ok((authz_vpc_subnet, db_vpc_subnet))
}

/// Insert a VPC Subnet, checking for unique IP address ranges.
pub async fn vpc_create_subnet(
&self,
Expand Down
14 changes: 13 additions & 1 deletion nexus/src/db/lookup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ impl<'a> LookupPath<'a> {
Vpc { key: Key::Id(Root { lookup_root: self }, id) }
}

/// Select a resource of type VpcSubnet, identified by its id
pub fn vpc_subnet_id(self, id: Uuid) -> VpcSubnet<'a> {
VpcSubnet { key: Key::Id(Root { lookup_root: self }, id) }
}

/// Select a resource of type VpcRouter, identified by its id
pub fn vpc_router_id(self, id: Uuid) -> VpcRouter<'a> {
VpcRouter { key: Key::Id(Root { lookup_root: self }, id) }
Expand Down Expand Up @@ -280,7 +285,14 @@ lookup_resource! {
lookup_resource! {
name = "Vpc",
ancestors = [ "Organization", "Project" ],
children = [ "VpcRouter" ],
children = [ "VpcRouter", "VpcSubnet" ],
authz_kind = Generic
}

lookup_resource! {
name = "VpcSubnet",
ancestors = [ "Organization", "Project", "Vpc" ],
children = [ ],
authz_kind = Generic
}

Expand Down
Loading