Skip to content

Commit de1fa54

Browse files
authored
nexus: allow IpPoolList access via silo conferred roles as well (#3999)
We were missing loading silo conferred roles for authz checks against `IpPoolList`. Also rename and make load_roles_for_resource private to try to limit same issue in the future.
1 parent 4289ad2 commit de1fa54

File tree

4 files changed

+91
-60
lines changed

4 files changed

+91
-60
lines changed

nexus/db-queries/src/authz/api_resources.rs

Lines changed: 10 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@
2929
use super::actor::AnyActor;
3030
use super::context::AuthorizedResource;
3131
use super::oso_generic::Init;
32-
use super::roles::{
33-
load_roles_for_resource, load_roles_for_resource_tree, RoleSet,
34-
};
32+
use super::roles::{load_roles_for_resource_tree, RoleSet};
3533
use super::Action;
3634
use super::{actor::AuthenticatedActor, Authz};
3735
use crate::authn;
@@ -290,15 +288,8 @@ impl AuthorizedResource for ConsoleSessionList {
290288
'd: 'f,
291289
'e: 'f,
292290
{
293-
load_roles_for_resource(
294-
opctx,
295-
datastore,
296-
authn,
297-
ResourceType::Fleet,
298-
*FLEET_ID,
299-
roleset,
300-
)
301-
.boxed()
291+
load_roles_for_resource_tree(&FLEET, opctx, datastore, authn, roleset)
292+
.boxed()
302293
}
303294

304295
fn on_unauthorized(
@@ -353,15 +344,8 @@ impl AuthorizedResource for DnsConfig {
353344
'd: 'f,
354345
'e: 'f,
355346
{
356-
load_roles_for_resource(
357-
opctx,
358-
datastore,
359-
authn,
360-
ResourceType::Fleet,
361-
*FLEET_ID,
362-
roleset,
363-
)
364-
.boxed()
347+
load_roles_for_resource_tree(&FLEET, opctx, datastore, authn, roleset)
348+
.boxed()
365349
}
366350

367351
fn on_unauthorized(
@@ -418,16 +402,9 @@ impl AuthorizedResource for IpPoolList {
418402
{
419403
// There are no roles on the IpPoolList, only permissions. But we still
420404
// need to load the Fleet-related roles to verify that the actor has the
421-
// "admin" role on the Fleet.
422-
load_roles_for_resource(
423-
opctx,
424-
datastore,
425-
authn,
426-
ResourceType::Fleet,
427-
*FLEET_ID,
428-
roleset,
429-
)
430-
.boxed()
405+
// "admin" role on the Fleet (possibly conferred from a Silo role).
406+
load_roles_for_resource_tree(&FLEET, opctx, datastore, authn, roleset)
407+
.boxed()
431408
}
432409

433410
fn on_unauthorized(
@@ -477,15 +454,8 @@ impl AuthorizedResource for DeviceAuthRequestList {
477454
// There are no roles on the DeviceAuthRequestList, only permissions. But we
478455
// still need to load the Fleet-related roles to verify that the actor has the
479456
// "admin" role on the Fleet.
480-
load_roles_for_resource(
481-
opctx,
482-
datastore,
483-
authn,
484-
ResourceType::Fleet,
485-
*FLEET_ID,
486-
roleset,
487-
)
488-
.boxed()
457+
load_roles_for_resource_tree(&FLEET, opctx, datastore, authn, roleset)
458+
.boxed()
489459
}
490460

491461
fn on_unauthorized(

nexus/db-queries/src/authz/policy_test/mod.rs

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ async fn test_iam_roles_behavior() {
160160
&logctx.log,
161161
&user_contexts,
162162
&test_resources,
163+
true,
163164
)
164165
.await
165166
.unwrap();
@@ -185,6 +186,7 @@ async fn authorize_everything<W: Write>(
185186
log: &slog::Logger,
186187
user_contexts: &[Arc<(String, OpContext)>],
187188
test_resources: &ResourceSet,
189+
print_actions: bool,
188190
) -> std::io::Result<()> {
189191
// Run the per-resource tests in parallel. Since the caller will be
190192
// checking the overall output against some expected output, it's important
@@ -204,11 +206,18 @@ async fn authorize_everything<W: Write>(
204206
write!(out, "{}", o)?;
205207
}
206208

207-
write!(out, "ACTIONS:\n\n")?;
208-
for action in authz::Action::iter() {
209-
write!(out, " {:>2} = {:?}\n", action_abbreviation(action), action)?;
209+
if print_actions {
210+
write!(out, "ACTIONS:\n\n")?;
211+
for action in authz::Action::iter() {
212+
write!(
213+
out,
214+
" {:>2} = {:?}\n",
215+
action_abbreviation(action),
216+
action
217+
)?;
218+
}
219+
write!(out, "\n")?;
210220
}
211-
write!(out, "\n")?;
212221

213222
Ok(())
214223
}
@@ -343,19 +352,27 @@ async fn test_conferred_roles() {
343352
.await
344353
.unwrap();
345354

346-
// Assemble the list of resources that we'll use for testing. This is much
347-
// more limited than the main policy test because we only care about the
348-
// behavior on the Fleet itself. We also create a Silo because the
349-
// ResourceBuilder will create for us users that we can use to test the
350-
// behavior of each role.
351355
let exemptions = resources::exempted_authz_classes();
352356
let mut coverage = Coverage::new(&logctx.log, exemptions);
357+
358+
// Assemble the list of resources that we'll use for testing. This is much
359+
// more limited than the main policy test because we only care about the
360+
// behavior on the Fleet itself, as well as some top-level resources that
361+
// exist outside of a silo.
353362
let mut builder =
354363
ResourceBuilder::new(&opctx, &datastore, &mut coverage, main_silo_id);
355364
builder.new_resource(authz::FLEET);
356-
builder.new_resource_with_users(main_silo).await;
365+
builder.new_resource(authz::IP_POOL_LIST);
357366
let test_resources = builder.build();
358367

368+
// We also create a Silo because the ResourceBuilder will create for us
369+
// users that we can use to test the behavior of each role.
370+
let mut silo_builder =
371+
ResourceBuilder::new(&opctx, &datastore, &mut coverage, main_silo_id);
372+
silo_builder.new_resource(authz::FLEET);
373+
silo_builder.new_resource_with_users(main_silo).await;
374+
let silo_resources = silo_builder.build();
375+
359376
// Up to this point, this looks similar to the main policy test. Here's
360377
// where things get different.
361378
//
@@ -404,7 +421,7 @@ async fn test_conferred_roles() {
404421
write!(out, "policy: {:?}\n", policy).unwrap();
405422
let policy = SiloAuthnPolicy::new(policy);
406423

407-
let user_contexts: Vec<Arc<(String, OpContext)>> = test_resources
424+
let user_contexts: Vec<Arc<(String, OpContext)>> = silo_resources
408425
.users()
409426
.map(|(username, user_id)| {
410427
let user_id = *user_id;
@@ -426,13 +443,15 @@ async fn test_conferred_roles() {
426443
})
427444
.collect();
428445

429-
let o = authorize_one_resource(
430-
logctx.log.clone(),
431-
user_contexts,
432-
Arc::new(authz::FLEET),
446+
authorize_everything(
447+
&mut out,
448+
&logctx.log,
449+
&user_contexts,
450+
&test_resources,
451+
false,
433452
)
434-
.await;
435-
write!(out, "{}", o).unwrap();
453+
.await
454+
.unwrap();
436455
}
437456
}
438457

nexus/db-queries/src/authz/roles.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ where
9898
if let Some(with_roles) = resource.as_resource_with_roles() {
9999
let resource_type = resource.resource_type();
100100
let resource_id = with_roles.resource_id();
101-
load_roles_for_resource(
101+
load_directly_attached_roles(
102102
opctx,
103103
datastore,
104104
authn,
@@ -113,7 +113,7 @@ where
113113
if let Some((resource_type, resource_id)) =
114114
with_roles.conferred_roles_by(authn)?
115115
{
116-
load_roles_for_resource(
116+
load_directly_attached_roles(
117117
opctx,
118118
datastore,
119119
authn,
@@ -141,7 +141,7 @@ where
141141
Ok(())
142142
}
143143

144-
pub async fn load_roles_for_resource(
144+
async fn load_directly_attached_roles(
145145
opctx: &OpContext,
146146
datastore: &DataStore,
147147
authn: &authn::Context,

nexus/db-queries/tests/output/authz-conferred-roles.out

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ resource: Fleet id "001de000-1334-4000-8000-000000000000"
66
silo-collaborator ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘
77
silo-viewer ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘
88

9+
resource: authz::IpPoolList
10+
11+
USER Q R LC RP M MP CC D
12+
silo-admin ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘
13+
silo-collaborator ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘
14+
silo-viewer ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘
15+
916
policy: {Admin: {Admin}}
1017
resource: Fleet id "001de000-1334-4000-8000-000000000000"
1118

@@ -14,6 +21,13 @@ resource: Fleet id "001de000-1334-4000-8000-000000000000"
1421
silo-collaborator ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘
1522
silo-viewer ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘
1623

24+
resource: authz::IpPoolList
25+
26+
USER Q R LC RP M MP CC D
27+
silo-admin ✘ ✘ ✔ ✘ ✔ ✔ ✔ ✔
28+
silo-collaborator ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘
29+
silo-viewer ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘
30+
1731
policy: {Viewer: {Viewer}}
1832
resource: Fleet id "001de000-1334-4000-8000-000000000000"
1933

@@ -22,6 +36,13 @@ resource: Fleet id "001de000-1334-4000-8000-000000000000"
2236
silo-collaborator ✘ ✔ ✔ ✔ ✘ ✘ ✘ ✘
2337
silo-viewer ✘ ✔ ✔ ✔ ✘ ✘ ✘ ✘
2438

39+
resource: authz::IpPoolList
40+
41+
USER Q R LC RP M MP CC D
42+
silo-admin ✘ ✘ ✔ ✘ ✘ ✘ ✘ ✘
43+
silo-collaborator ✘ ✘ ✔ ✘ ✘ ✘ ✘ ✘
44+
silo-viewer ✘ ✘ ✔ ✘ ✘ ✘ ✘ ✘
45+
2546
policy: {Admin: {Viewer}}
2647
resource: Fleet id "001de000-1334-4000-8000-000000000000"
2748

@@ -30,6 +51,13 @@ resource: Fleet id "001de000-1334-4000-8000-000000000000"
3051
silo-collaborator ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘
3152
silo-viewer ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘
3253

54+
resource: authz::IpPoolList
55+
56+
USER Q R LC RP M MP CC D
57+
silo-admin ✘ ✘ ✔ ✘ ✘ ✘ ✘ ✘
58+
silo-collaborator ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘
59+
silo-viewer ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘
60+
3361
policy: {Viewer: {Admin, Viewer}}
3462
resource: Fleet id "001de000-1334-4000-8000-000000000000"
3563

@@ -38,6 +66,13 @@ resource: Fleet id "001de000-1334-4000-8000-000000000000"
3866
silo-collaborator ✘ ✔ ✔ ✔ ✔ ✔ ✔ ✔
3967
silo-viewer ✘ ✔ ✔ ✔ ✔ ✔ ✔ ✔
4068

69+
resource: authz::IpPoolList
70+
71+
USER Q R LC RP M MP CC D
72+
silo-admin ✘ ✘ ✔ ✘ ✔ ✔ ✔ ✔
73+
silo-collaborator ✘ ✘ ✔ ✘ ✔ ✔ ✔ ✔
74+
silo-viewer ✘ ✘ ✔ ✘ ✔ ✔ ✔ ✔
75+
4176
policy: {Admin: {Admin}, Viewer: {Viewer}}
4277
resource: Fleet id "001de000-1334-4000-8000-000000000000"
4378

@@ -46,3 +81,10 @@ resource: Fleet id "001de000-1334-4000-8000-000000000000"
4681
silo-collaborator ✘ ✔ ✔ ✔ ✘ ✘ ✘ ✘
4782
silo-viewer ✘ ✔ ✔ ✔ ✘ ✘ ✘ ✘
4883

84+
resource: authz::IpPoolList
85+
86+
USER Q R LC RP M MP CC D
87+
silo-admin ✘ ✘ ✔ ✘ ✔ ✔ ✔ ✔
88+
silo-collaborator ✘ ✘ ✔ ✘ ✘ ✘ ✘ ✘
89+
silo-viewer ✘ ✘ ✔ ✘ ✘ ✘ ✘ ✘
90+

0 commit comments

Comments
 (0)