Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
3752d57
add restrict_networking_actions code
charliepark Sep 12, 2025
420ed2b
More development of Polar-based change to permissions
charliepark Sep 15, 2025
ad106d6
Polar working, perhaps; lots of permission rules
charliepark Sep 16, 2025
9966d8e
refactor; add a few tests that might still need a bit of tweaking
charliepark Sep 17, 2025
a06feda
clean up migration files
charliepark Sep 19, 2025
062d441
small cleanup
charliepark Sep 19, 2025
9450227
fix clippy issues
charliepark Sep 19, 2025
9c709fd
safer migratino file
charliepark Sep 19, 2025
37f1e1e
merge main and resolve conflicts
charliepark Sep 19, 2025
91d7856
Update nexus, tests
charliepark Sep 19, 2025
759f3a6
formatting
charliepark Sep 19, 2025
1f54b26
remove unused method
charliepark Oct 2, 2025
01434b2
Move logic from silo to project
charliepark Oct 15, 2025
56af8c8
Remove accidentally committed .bak files
charliepark Oct 15, 2025
ced5348
cargo fmt
charliepark Oct 15, 2025
d8764d3
Merge main
charliepark Oct 15, 2025
6cad94e
fix clippy issues
charliepark Oct 15, 2025
7737776
cargo fmt again
charliepark Oct 15, 2025
ed3e5f4
Update tests
charliepark Oct 16, 2025
f3b34a9
Merge branch 'main' into restrict_networking_actions_4
charliepark Oct 16, 2025
6f41131
Update version number in dbint.sql
charliepark Oct 17, 2025
937d15e
Merge branch 'main' into restrict_networking_actions_4
charliepark Oct 17, 2025
51207ca
remove redundant Silo query
charliepark Oct 17, 2025
f3605a5
Update tests
charliepark Oct 17, 2025
d9b8bcd
cargo fmt
charliepark Oct 17, 2025
c0e922d
Merge branch 'main' into restrict_networking_actions_4
charliepark Oct 18, 2025
388e903
Move restriction check to actor silo policy, rather than project silo
charliepark Oct 20, 2025
4b4c392
cargo fmt
charliepark Oct 20, 2025
901e241
Merge branch 'main' into restrict_networking_actions_4
charliepark Oct 21, 2025
2dae549
Add test back in
charliepark Oct 21, 2025
109b966
Update checks for VPC update, more tests
charliepark Oct 21, 2025
4768df9
cargo fmt
charliepark Oct 21, 2025
15713f5
Add VPC subnet restriction and tests
charliepark Oct 21, 2025
7bb9e35
Add routers and router route checks and tests
charliepark Oct 21, 2025
bbf0c19
Add networking restrictions check to Internet Gateways and Firewall R…
charliepark Oct 21, 2025
1320eb2
Refactor tests
charliepark Oct 21, 2025
49cbce0
Add internet gateway attach/detach restrictions
charliepark Oct 21, 2025
7ce7cc1
Add tests for IP Pools / Addresses
charliepark Oct 21, 2025
a948699
Merge branch 'main' into restrict_networking_actions_4
charliepark Oct 21, 2025
2daa80e
Add bypass on VPC creation saga in restricted environments
charliepark Oct 22, 2025
5d2b21c
cargo fmt
charliepark Oct 22, 2025
49488e7
merge main and resolve conflicts
charliepark Oct 22, 2025
1ef5e62
Merge branch 'main' into restrict_networking_actions_4
charliepark Oct 22, 2025
680a1a1
Update dbint.sql version again
charliepark Oct 22, 2025
479a696
remove pub from method
charliepark Oct 22, 2025
b9b5465
Add missing Polar rules
charliepark Oct 22, 2025
28ba5f3
Use InProjectNetworking snippet instead of permissive InProject snippet
charliepark Oct 22, 2025
fefa847
Adjust VPC deletion
charliepark Oct 22, 2025
b56ff84
Use project:createChild check for VPC creation in lieu of creating a …
charliepark Oct 22, 2025
2331d3f
Comment out callsites for check_networking_restrictions; enable for V…
charliepark Oct 22, 2025
acc70af
Remove unneeded Rust checks and add missing Polar rules
charliepark Oct 23, 2025
0b82612
Add tests
charliepark Oct 23, 2025
79a849e
cargo fmt
charliepark Oct 23, 2025
57c79b6
Merge main and resolve conflicts
charliepark Oct 23, 2025
40fd6ba
Fix compilation errors
charliepark Oct 23, 2025
c0ac662
Refactor Polar rules
charliepark Oct 23, 2025
134af5a
Remove empty lines
charliepark Oct 23, 2025
14b721e
Merge branch 'main' into restrict_networking_actions_4
charliepark Oct 24, 2025
9d3e694
special case for collaborators to create children of subnets
charliepark Oct 27, 2025
852c340
merge in dap's VpcList synthetic resource
davepacheco Oct 24, 2025
11f0286
resolve merge conflict
charliepark Oct 27, 2025
62fe901
remove restrict_networking_actions code
charliepark Oct 27, 2025
cab95ce
A bit more cleanup from old implementation
charliepark Oct 27, 2025
2510870
use main branch's version of openapi/nexus.json
charliepark Oct 27, 2025
eb4e51b
re-add collaborator-no-networking to enum
charliepark Oct 27, 2025
fb03380
Update policy tests with new role permissions
charliepark Oct 27, 2025
1935250
Remove old implementation tests
charliepark Oct 27, 2025
27e29ac
Remove old implementation tests from vpc_routers
charliepark Oct 27, 2025
9a0e099
Remove old Polar rules
charliepark Oct 27, 2025
3e82cf7
Fix macro to have proper authz
charliepark Oct 27, 2025
cb2b99f
Remove unnecessary logic around create_default_vpc
charliepark Oct 27, 2025
b2a1bb5
Correct comment
charliepark Oct 28, 2025
b42393d
Improve integration test
charliepark Oct 28, 2025
9f4c70c
Change name of role from collaborator-no-networking to limited-collab…
charliepark Oct 28, 2025
bdbd67b
Update snapshots
charliepark Oct 28, 2025
cd3085a
Use InProjectLimited and InProjectFull in place of InProject and InPr…
charliepark Oct 28, 2025
fa56b6f
cargo fmt
charliepark Oct 28, 2025
5530a80
Remove delete from authz list
charliepark Oct 28, 2025
d54c6d7
Update a few more spots
charliepark Oct 28, 2025
af225b1
Merge branch 'main' into restrict_networking_actions_5
charliepark Oct 28, 2025
e2f495d
Merge branch 'main' into restrict_networking_actions_5
charliepark Oct 29, 2025
f9f953a
add silo level role with project inheritance
charliepark Oct 29, 2025
1049914
Switch authz for NIC to subnet Read vs CreateChild
charliepark Oct 29, 2025
76e9294
Add integration tests for limited-collaborator
charliepark Oct 30, 2025
af9c46b
Merge branch 'main' into restrict_networking_actions_5
charliepark Oct 30, 2025
8595c2a
cargo fmt
charliepark Oct 30, 2025
428438b
Add more comments about roles
charliepark Oct 30, 2025
06b388e
Update openapi/nexus.json with a maybe too long description
charliepark Oct 30, 2025
b511d18
update OpenAPI spec
charliepark Oct 30, 2025
1a195f3
Remove the ability to create projects, but keep the silo.limited_coll…
charliepark Oct 30, 2025
416c1d2
sigh; cargo fmt
charliepark Oct 30, 2025
ceaeadc
Update tests; add comments
charliepark Oct 30, 2025
b148207
Add tests to verify that cross-subnet NIC creation is not a risk for …
charliepark Nov 3, 2025
1fe93fe
Simplify tests
charliepark Nov 3, 2025
e7b0ac1
cargo fmt
charliepark Nov 3, 2025
45cbcfc
Use a more nuanced permission check for listing VPCs, rather than jus…
charliepark Nov 3, 2025
65c55dd
Merge branch 'main' into restrict_networking_actions_5
charliepark Nov 3, 2025
9324a17
Regenerate authz-roles.out after merge
charliepark Nov 3, 2025
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
2 changes: 1 addition & 1 deletion docs/adding-an-endpoint.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ this document should act as a jumping-off point.
* Add helper functions to `LookupPath` to make it possible to fetch the resource by either UUID or name (https://github.com/oxidecomputer/omicron/blob/1dfe47c1b3122bc4f32a9c517cb31b1600581ea2/nexus/src/db/lookup.rs#L225-L237[Example])
** These are often named `pub fn <my_resource>_name`, or `pub fn <my_resource>_id`
* Use the https://github.com/oxidecomputer/omicron/blob/main/nexus/authz-macros/src/lib.rs[`authz_resource!` macro] to define a new `authz::...` structure, which is returned from the **Lookup** functions (https://github.com/oxidecomputer/omicron/blob/1dfe47c1b3122bc4f32a9c517cb31b1600581ea2/nexus/src/authz/api_resources.rs#L758-L764[Example])
** If you define `polar_snippet = InProject` (for developer resources) or `polar_snippet = FleetChild` (for operator resources), most of the polar policy is automatically defined for you
** If you define `polar_snippet = InProjectLimited` or `polar_snippet = InProjectFull` (for developer resources) or `polar_snippet = FleetChild` (for operator resources), most of the polar policy is automatically defined for you
** If you define `polar_snippet = Custom`, you should edit the omicron.polar file to describe the authorization policy for your object (https://github.com/oxidecomputer/omicron/blob/1dfe47c1b3122bc4f32a9c517cb31b1600581ea2/nexus/src/authz/omicron.polar#L376-L393[Example])
* Either way, you should add reference the new resource when https://github.com/oxidecomputer/omicron/blob/1dfe47c1b3122bc4f32a9c517cb31b1600581ea2/nexus/src/authz/oso_generic.rs#L119-L148[constructing the Oso structure]

Expand Down
107 changes: 91 additions & 16 deletions nexus/auth/src/authz/api_resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,55 @@ impl AuthorizedResource for SiloUserTokenList {
}
}

/// Synthetic resource describing the list of VPCs in a Project
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VpcList(Project);

impl VpcList {
pub fn new(project: Project) -> VpcList {
VpcList(project)
}

pub fn project(&self) -> &Project {
&self.0
}
}

impl oso::PolarClass for VpcList {
fn get_polar_class_builder() -> oso::ClassBuilder<Self> {
oso::Class::builder()
.with_equality_check()
.add_attribute_getter("project", |list: &VpcList| list.0.clone())
}
}

impl AuthorizedResource for VpcList {
fn load_roles<'fut>(
&'fut self,
opctx: &'fut OpContext,
authn: &'fut authn::Context,
roleset: &'fut mut RoleSet,
) -> futures::future::BoxFuture<'fut, Result<(), Error>> {
// There are no roles on this resource, but we still need to load the
// Project-related roles.
self.project().load_roles(opctx, authn, roleset)
}

fn on_unauthorized(
&self,
_: &Authz,
error: Error,
_: AnyActor,
_: Action,
) -> Error {
error
}

fn polar_class(&self) -> oso::Class {
Self::get_polar_class()
}
}

#[derive(Clone, Copy, Debug)]
pub struct UpdateTrustRootList;

Expand Down Expand Up @@ -1130,124 +1179,150 @@ impl ApiResourceWithRolesType for Project {
type AllowedRoles = ProjectRole;
}

// ============================================================================
// Project Resources - Non-Networking (InProjectLimited)
//
// These resources can be created and modified by users with the
// "limited-collaborator" role and above. These are "regular" project resources
// that users can work with without needing permission to reconfigure network
// infrastructure.
//
// Examples: Instances, Disks, Snapshots, Images, Floating IPs
// ============================================================================

authz_resource! {
name = "Disk",
parent = "Project",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
polar_snippet = InProjectLimited,
}

authz_resource! {
name = "ProjectImage",
parent = "Project",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
polar_snippet = InProjectLimited,
}

authz_resource! {
name = "Snapshot",
parent = "Project",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
polar_snippet = InProjectLimited,
}

authz_resource! {
name = "Instance",
parent = "Project",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
polar_snippet = InProjectLimited,
}

authz_resource! {
name = "AffinityGroup",
parent = "Project",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
polar_snippet = InProjectLimited,
}

authz_resource! {
name = "AntiAffinityGroup",
parent = "Project",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
polar_snippet = InProjectLimited,
}

authz_resource! {
name = "InstanceNetworkInterface",
parent = "Instance",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
}
polar_snippet = InProjectLimited,
}

// ============================================================================
// Project Resources - Networking Infrastructure (InProjectFull)
//
// These resources require the full "collaborator" role to create or modify.
// Users with only the "limited-collaborator" role can *read* these resources
// (via viewer inheritance) but cannot create or modify them.
//
// This distinction allows organizations to restrict who can reconfigure the
// network topology while still allowing those users to work with compute
// resources (instances, disks, etc.) within the existing network.
//
// Resources in this category: VPCs, Subnets, Routers, Router Routes,
// Internet Gateways, and their child resources (IP pools, IP addresses)
// ============================================================================

authz_resource! {
name = "Vpc",
parent = "Project",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
polar_snippet = InProjectFull,
}

authz_resource! {
name = "VpcRouter",
parent = "Vpc",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
polar_snippet = InProjectFull,
}

authz_resource! {
name = "RouterRoute",
parent = "VpcRouter",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
polar_snippet = InProjectFull,
}

authz_resource! {
name = "VpcSubnet",
parent = "Vpc",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
polar_snippet = InProjectFull,
}

authz_resource! {
name = "InternetGateway",
parent = "Vpc",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
polar_snippet = InProjectFull,
}

authz_resource! {
name = "InternetGatewayIpPool",
parent = "InternetGateway",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
polar_snippet = InProjectFull,
}

authz_resource! {
name = "InternetGatewayIpAddress",
parent = "InternetGateway",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
polar_snippet = InProjectFull,
}

authz_resource! {
name = "FloatingIp",
parent = "Project",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
polar_snippet = InProjectLimited,
}

// Customer network integration resources nested below "Fleet"
Expand Down
44 changes: 36 additions & 8 deletions nexus/auth/src/authz/omicron.polar
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,15 @@ has_role(actor: AuthenticatedActor, role: String, resource: Resource)
# - fleet.collaborator (can manage Silos)
# - fleet.viewer (can read most non-siloed resources in the system)
# - silo.admin (superuser for the silo)
# - silo.collaborator (can create and own Organizations)
# - silo.viewer (can read most resources within the Silo)
# - silo.collaborator (can create and own Organizations; grants project.admin on all projects)
# - silo.limited-collaborator (grants project.limited-collaborator on all projects)
# - silo.viewer (can read most resources within the Silo; grants project.viewer)
# - organization.admin (complete control over an organization)
# - organization.collaborator (can manage Projects)
# - organization.viewer (can read most resources within the Organization)
# - project.admin (complete control over a Project)
# - project.collaborator (can manage all resources within the Project)
# - project.collaborator (can manage all resources within the Project, including networking)
# - project.limited-collaborator (can manage compute resources, but not networking resources)
# - project.viewer (can read most resources within the Project)
#
# Outside the Silo/Organization/Project hierarchy, we (currently) treat most
Expand Down Expand Up @@ -125,10 +127,11 @@ resource Silo {
"read",
"create_child",
];
roles = [ "admin", "collaborator", "viewer" ];
roles = [ "admin", "collaborator", "limited-collaborator", "viewer" ];

# Roles implied by other roles on this resource
"viewer" if "collaborator";
"viewer" if "limited-collaborator";
"limited-collaborator" if "collaborator";
"collaborator" if "admin";

# Permissions granted directly by roles on this resource
Expand Down Expand Up @@ -184,21 +187,29 @@ resource Project {
"read",
"create_child",
];
roles = [ "admin", "collaborator", "viewer" ];
roles = [ "admin", "collaborator", "limited-collaborator", "viewer" ];

# Roles implied by other roles on this resource
"viewer" if "collaborator";
# Role hierarchy: admin > collaborator > limited-collaborator > viewer
#
# The "limited-collaborator" role can create/modify non-networking
# resources (instances, disks, etc.) but cannot create/modify networking
# infrastructure (VPCs, subnets, routers, internet gateways).
# See nexus/authz-macros for InProjectLimited vs InProjectFull.
"viewer" if "limited-collaborator";
"limited-collaborator" if "collaborator";
"collaborator" if "admin";

# Permissions granted directly by roles on this resource
"list_children" if "viewer";
"read" if "viewer";
"create_child" if "collaborator";
"create_child" if "limited-collaborator";
"modify" if "admin";

# Roles implied by roles on this resource's parent (Silo)
relations = { parent_silo: Silo };
"admin" if "collaborator" on "parent_silo";
"limited-collaborator" if "limited-collaborator" on "parent_silo";
"viewer" if "viewer" on "parent_silo";
}
has_relation(silo: Silo, "parent_silo", project: Project)
Expand Down Expand Up @@ -797,3 +808,20 @@ has_relation(silo: Silo, "parent_silo", scim_client_bearer_token_list: ScimClien
if scim_client_bearer_token_list.silo = silo;
has_relation(fleet: Fleet, "parent_fleet", collection: ScimClientBearerTokenList)
if collection.silo.fleet = fleet;

# VpcList is a synthetic resource for controlling VPC creation.
# Unlike other project resources, VPC creation requires the full "collaborator"
# role rather than "limited-collaborator", enforcing the networking restriction.
# This allows organizations to restrict who can reconfigure the network topology
# while still allowing users with limited-collaborator to work with compute
# resources (instances, disks, etc.) within the existing network.
resource VpcList {
permissions = [ "list_children", "create_child" ];

relations = { containing_project: Project };

"list_children" if "read" on "containing_project";
"create_child" if "collaborator" on "containing_project";
}
has_relation(project: Project, "containing_project", collection: VpcList)
if collection.project = project;
1 change: 1 addition & 0 deletions nexus/auth/src/authz/oso_generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ pub fn make_omicron_oso(log: &slog::Logger) -> Result<OsoInit, anyhow::Error> {
Fleet::get_polar_class(),
Inventory::get_polar_class(),
IpPoolList::get_polar_class(),
VpcList::get_polar_class(),
ConsoleSessionList::get_polar_class(),
DeviceAuthRequestList::get_polar_class(),
QuiesceState::get_polar_class(),
Expand Down
2 changes: 1 addition & 1 deletion nexus/authz-macros/outputs/instance.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl Instance {
pub(super) fn init() -> Init {
use oso::PolarClass;
Init {
polar_snippet: "\n resource Instance {\n permissions = [\n \"list_children\",\n \"modify\",\n \"read\",\n \"create_child\",\n ];\n\n relations = { containing_project: Project };\n \"list_children\" if \"viewer\" on \"containing_project\";\n \"read\" if \"viewer\" on \"containing_project\";\n \"modify\" if \"collaborator\" on \"containing_project\";\n \"create_child\" if \"collaborator\" on \"containing_project\";\n }\n\n has_relation(parent: Project, \"containing_project\", child: Instance)\n if child.project = parent;\n ",
polar_snippet: "\n resource Instance {\n permissions = [\n \"list_children\",\n \"modify\",\n \"read\",\n \"create_child\",\n ];\n\n relations = { containing_project: Project };\n \"list_children\" if \"viewer\" on \"containing_project\";\n \"read\" if \"viewer\" on \"containing_project\";\n \"modify\" if \"limited-collaborator\" on \"containing_project\";\n \"create_child\" if \"limited-collaborator\" on \"containing_project\";\n }\n\n has_relation(parent: Project, \"containing_project\", child: Instance)\n if child.project = parent;\n ",
polar_class: Instance::get_polar_class(),
}
}
Expand Down
Loading
Loading