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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions nexus/src/authz/api_resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ impl ApiResourceError for ProjectChild {

pub type Disk = ProjectChild;
pub type Instance = ProjectChild;
pub type RouterRoute = ProjectChild;
pub type Vpc = ProjectChild;
pub type VpcRouter = ProjectChild;
pub type VpcSubnet = ProjectChild;
1 change: 1 addition & 0 deletions nexus/src/authz/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ pub use api_resources::FleetChild;
pub use api_resources::Instance;
pub use api_resources::Organization;
pub use api_resources::Project;
pub use api_resources::RouterRoute;
pub use api_resources::Vpc;
pub use api_resources::VpcRouter;
pub use api_resources::VpcSubnet;
Expand Down
138 changes: 105 additions & 33 deletions nexus/src/db/datastore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2651,7 +2651,7 @@ impl DataStore {
}

/// Lookup a VpcRouter by name and return the full database record, along
/// with an [`authz::Vpc`] for subsequent authorization checks
/// with an [`authz::VpcRouter`] for subsequent authorization checks
pub async fn vpc_router_fetch(
&self,
opctx: &OpContext,
Expand Down Expand Up @@ -2690,7 +2690,7 @@ impl DataStore {
opctx: &OpContext,
authz_vpc: &authz::Vpc,
router: VpcRouter,
) -> CreateResult<VpcRouter> {
) -> CreateResult<(authz::VpcRouter, VpcRouter)> {
opctx.authorize(authz::Action::CreateChild, authz_vpc).await?;

use db::schema::vpc_router::dsl;
Expand All @@ -2711,7 +2711,14 @@ impl DataStore {
),
)
})?;
Ok(router)
Ok((
authz_vpc.child_generic(
ResourceType::VpcRouter,
router.id(),
LookupType::ById(router.id()),
),
router,
))
}

pub async fn vpc_delete_router(
Expand Down Expand Up @@ -2765,30 +2772,39 @@ impl DataStore {

pub async fn router_list_routes(
&self,
router_id: &Uuid,
opctx: &OpContext,
authz_router: &authz::VpcRouter,
pagparams: &DataPageParams<'_, Name>,
) -> ListResultVec<RouterRoute> {
use db::schema::router_route::dsl;
opctx.authorize(authz::Action::ListChildren, authz_router).await?;

use db::schema::router_route::dsl;
paginated(dsl::router_route, dsl::name, pagparams)
.filter(dsl::time_deleted.is_null())
.filter(dsl::router_id.eq(*router_id))
.filter(dsl::router_id.eq(authz_router.id()))
.select(RouterRoute::as_select())
.load_async::<db::model::RouterRoute>(self.pool())
.load_async::<db::model::RouterRoute>(
self.pool_authorized(opctx).await?,
)
.await
.map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server))
}

pub async fn router_route_fetch_by_name(
/// Fetches a RouterRoute from the database and returns both the database
/// row and an [`authz::RouterRoute`] 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 route_lookup_noauthz(
&self,
router_id: &Uuid,
authz_vpc_router: &authz::VpcRouter,
route_name: &Name,
) -> LookupResult<RouterRoute> {
) -> LookupResult<(authz::RouterRoute, RouterRoute)> {
use db::schema::router_route::dsl;

dsl::router_route
.filter(dsl::time_deleted.is_null())
.filter(dsl::router_id.eq(*router_id))
.filter(dsl::router_id.eq(authz_vpc_router.id()))
.filter(dsl::name.eq(route_name.clone()))
.select(RouterRoute::as_select())
.get_result_async(self.pool())
Expand All @@ -2802,12 +2818,68 @@ impl DataStore {
),
)
})
.map(|r| {
(
authz_vpc_router.child_generic(
ResourceType::RouterRoute,
r.id(),
LookupType::ByName(route_name.to_string()),
),
r,
)
})
}

/// Lookup a RouterRoute by name and return the full database record, along
/// with an [`authz::RouterRoute`] for subsequent authorization checks
pub async fn route_fetch(
&self,
opctx: &OpContext,
authz_vpc_router: &authz::VpcRouter,
name: &Name,
) -> LookupResult<(authz::RouterRoute, RouterRoute)> {
let (authz_route, db_route) =
self.route_lookup_noauthz(authz_vpc_router, name).await?;
opctx.authorize(authz::Action::Read, &authz_route).await?;
Ok((authz_route, db_route))
}

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

pub async fn router_create_route(
&self,
opctx: &OpContext,
authz_router: &authz::VpcRouter,
route: RouterRoute,
) -> CreateResult<RouterRoute> {
assert_eq!(authz_router.id(), route.router_id);
opctx.authorize(authz::Action::CreateChild, authz_router).await?;

use db::schema::router_route::dsl;
let router_id = route.router_id;
let name = route.name().clone();
Expand All @@ -2816,7 +2888,7 @@ impl DataStore {
router_id,
diesel::insert_into(dsl::router_route).values(route),
)
.insert_and_get_result_async(self.pool())
.insert_and_get_result_async(self.pool_authorized(opctx).await?)
.await
.map_err(|e| match e {
AsyncInsertError::CollectionNotFound => Error::ObjectNotFound {
Expand All @@ -2835,52 +2907,52 @@ impl DataStore {
})
}

pub async fn router_delete_route(&self, route_id: &Uuid) -> DeleteResult {
use db::schema::router_route::dsl;
pub async fn router_delete_route(
&self,
opctx: &OpContext,
authz_route: &authz::RouterRoute,
) -> DeleteResult {
opctx.authorize(authz::Action::Delete, authz_route).await?;

use db::schema::router_route::dsl;
let now = Utc::now();
diesel::update(dsl::router_route)
.filter(dsl::time_deleted.is_null())
.filter(dsl::id.eq(*route_id))
.filter(dsl::id.eq(authz_route.id()))
.set(dsl::time_deleted.eq(now))
.returning(RouterRoute::as_returning())
.get_result_async(self.pool())
.execute_async(self.pool_authorized(opctx).await?)
.await
.map_err(|e| {
public_error_from_diesel_pool(
e,
ErrorHandler::NotFoundByLookup(
ResourceType::RouterRoute,
LookupType::ById(*route_id),
),
ErrorHandler::NotFoundByResource(authz_route),
)
})?;
Ok(())
}

pub async fn router_update_route(
&self,
route_id: &Uuid,
opctx: &OpContext,
authz_route: &authz::RouterRoute,
route_update: RouterRouteUpdate,
) -> Result<(), Error> {
use db::schema::router_route::dsl;
) -> UpdateResult<RouterRoute> {
opctx.authorize(authz::Action::Modify, authz_route).await?;

use db::schema::router_route::dsl;
diesel::update(dsl::router_route)
.filter(dsl::time_deleted.is_null())
.filter(dsl::id.eq(*route_id))
.filter(dsl::id.eq(authz_route.id()))
.set(route_update)
.execute_async(self.pool())
.returning(RouterRoute::as_returning())
.get_result_async(self.pool_authorized(opctx).await?)
.await
.map_err(|e| {
public_error_from_diesel_pool(
e,
ErrorHandler::NotFoundByLookup(
ResourceType::RouterRoute,
LookupType::ById(*route_id),
),
ErrorHandler::NotFoundByResource(authz_route),
)
})?;
Ok(())
})
}

// TODO-correctness: fix session method errors. the map_errs turn all errors
Expand Down
18 changes: 14 additions & 4 deletions nexus/src/external_api/http_entrypoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1949,8 +1949,10 @@ async fn routers_routes_get(
let query = query_params.into_inner();
let path = path_params.into_inner();
let handler = async {
let opctx = OpContext::for_external_api(&rqctx).await?;
let routes = nexus
.router_list_routes(
&opctx,
&path.organization_name,
&path.project_name,
&path.vpc_name,
Expand Down Expand Up @@ -1991,8 +1993,10 @@ async fn routers_routes_get_route(
let nexus = &apictx.nexus;
let path = path_params.into_inner();
let handler = async {
let opctx = OpContext::for_external_api(&rqctx).await?;
let route = nexus
.router_lookup_route(
.route_fetch(
&opctx,
&path.organization_name,
&path.project_name,
&path.vpc_name,
Expand Down Expand Up @@ -2020,8 +2024,10 @@ async fn routers_routes_post(
let nexus = &apictx.nexus;
let path = path_params.into_inner();
let handler = async {
let opctx = OpContext::for_external_api(&rqctx).await?;
let route = nexus
.router_create_route(
&opctx,
&path.organization_name,
&path.project_name,
&path.vpc_name,
Expand Down Expand Up @@ -2049,8 +2055,10 @@ async fn routers_routes_delete_route(
let nexus = &apictx.nexus;
let path = path_params.into_inner();
let handler = async {
let opctx = OpContext::for_external_api(&rqctx).await?;
nexus
.router_delete_route(
&opctx,
&path.organization_name,
&path.project_name,
&path.vpc_name,
Expand All @@ -2073,13 +2081,15 @@ async fn routers_routes_put_route(
rqctx: Arc<RequestContext<Arc<ServerContext>>>,
path_params: Path<RouterRoutePathParam>,
router_params: TypedBody<RouterRouteUpdateParams>,
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
) -> Result<HttpResponseOk<RouterRoute>, HttpError> {
let apictx = rqctx.context();
let nexus = &apictx.nexus;
let path = path_params.into_inner();
let handler = async {
nexus
let opctx = OpContext::for_external_api(&rqctx).await?;
let router_route = nexus
.router_update_route(
&opctx,
&path.organization_name,
&path.project_name,
&path.vpc_name,
Expand All @@ -2088,7 +2098,7 @@ async fn routers_routes_put_route(
&router_params.into_inner(),
)
.await?;
Ok(HttpResponseUpdatedNoContent())
Ok(HttpResponseOk(router_route.into()))
};
apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await
}
Expand Down
Loading