Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

flesh out authz policy #405

Merged
merged 15 commits into from
Nov 22, 2021
Merged
2 changes: 1 addition & 1 deletion common/src/api/external/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ impl TryFrom<i64> for Generation {
/**
* Identifies a type of API resource
*/
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum ResourceType {
Organization,
Project,
Expand Down
9 changes: 6 additions & 3 deletions nexus/src/authz/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use std::sync::Arc;
mod oso_types;
pub use oso_types::Action;
pub use oso_types::Organization;
pub use oso_types::Project;
pub use oso_types::ProjectChild;
pub use oso_types::DATABASE;
pub use oso_types::FLEET;

Expand Down Expand Up @@ -85,7 +87,8 @@ impl Context {
mod test {
/*
* These are essentially unit tests for the policy itself.
* TODO-coverage This is just a start.
* TODO-coverage This is just a start. But we need roles to do a more
* comprehensive test.
* TODO If this gets any more complicated, we could consider automatically
* generating the test cases. We could precreate a bunch of resources and
* some users with different roles. Then we could run through a table that
Expand Down Expand Up @@ -134,11 +137,11 @@ mod test {
fn test_organization() {
let authz_privileged =
authz_context_for_actor(TEST_USER_UUID_PRIVILEGED);
authz_privileged.authorize(Action::CreateOrganization, FLEET).expect(
authz_privileged.authorize(Action::CreateChild, FLEET).expect(
"expected privileged user to be able to create organization",
);
let authz_nobody = authz_context_for_actor(TEST_USER_UUID_UNPRIVILEGED);
authz_nobody.authorize(Action::CreateOrganization, FLEET).expect_err(
authz_nobody.authorize(Action::CreateChild, FLEET).expect_err(
"expected unprivileged user not to be able to create organization",
);
let authz_noauth = authz_context_noauth();
Expand Down
166 changes: 145 additions & 21 deletions nexus/src/authz/omicron.polar
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,6 @@ allow(actor: AnyActor, action: Action, resource) if
actor.authenticated and
has_permission(actor.authn_actor.unwrap(), action.to_perm(), resource);

# For now, we hardcode some fixed actor ids and what roles they have.
# This will eventually need to be replaced with data that comes from the
# database.
# NOTE: this user uuid is duplicated in authn/mod.rs.
has_role(actor: AuthenticatedActor, _role: String, _resource: Resource) if
actor.is_test_user;



#
# Resources
#
Expand All @@ -44,24 +35,157 @@ resource Database {
}

# All authenticated users have the "user" role on the database.
# XXX This rule doesn't seem to get used for some reason.
has_role(_actor: AuthenticatedActor, "user", _resource: Database);

#
# Permissions and predefined roles
#
# For now, we define the following permissions for most resources in the system:
#
# - "create_child": required to create child resources.
#
# - "list_children": required to list children (of all types) of a resources
#
# - "modify": required to modify or delete a resource or any of its children
#
# - "read": required to read a resource
#
# We define the following predefined roles for only a few high-level resources:
#
# - "admin": has all permissions on the resource
#
# - "collaborator": has "list_children" and "create_$child" for all children.
# They'll inherit the "admin" role for any resources that they create.
#
# - "viewer": has "read" and "list_children" on a resource
#
# Below the project level, permissions are granted at the Project level. For
# example, for someone to be able to create, modify, or delete any Instances,
# they must be granted project.collaborator, which means they can create,
# modify, or delete _all_ resources in the Project.
#
# The complete set of predefined roles:
#
# - fleet.admin (superuser for the whole system)
# - fleet.collaborator (can create and own orgs)
# - organization.admin (complete control over an organization)
# - organization.collaborator (can create, modify, and delete projects)
# - project.admin (complete control over a project)
# - project.collaborator (can create, modify, and delete all resources within
# the project, but cannot modify or delete the project
# itself)
# - project.viewer (can see everything in the project, but cannot modify
# anything)
#

# At the top level is the "Fleet" resource. Fleet administrators can create
# Organizations, which essentially gives them permissions to do anything with
# the Fleet. (They're not exactly superusers, though. They only inherit an
# "admin" role on Organizations they create. They cannot necessarily even see
# Organizations created by other Fleet administrators.)
# At the top level is the "Fleet" resource.
resource Fleet {
permissions = [ "create_organization" ];
roles = [ "admin" ];
"create_organization" if "admin";
permissions = [
"list_children",
"modify",
"read",
"create_child",
];

roles = [ "admin", "collaborator" ];

# Fleet collaborators can create Organizations and see fleet-wide
# information, including Organizations that they don't have permissions
# on. (They cannot list projects within those organizations, however.)
# They cannot modify fleet-wide information.
"list_children" if "collaborator";
"read" if "collaborator";
"create_child" if "collaborator";

# Fleet administrators are whole-system superusers.
"collaborator" if "admin";
"modify" if "admin";
}

resource Organization {
permissions = [ "create_project" ];
roles = [ "admin" ];
permissions = [
"list_children",
"modify",
"read",
"create_child",
];
roles = [ "admin", "collaborator" ];

# Organization collaborators can create Projects and see
# organization-wide information, including Projects that they don't have
# permissions on. (They cannot see anything inside those Projects,
# though.) They cannot modify or delete the organization itself.
"list_children" if "collaborator";
"read" if "collaborator";
"create_child" if "collaborator";

# Organization administrators can modify and delete the Organization
# itself. They can also see and administer everything in the
# Organization (recursively).
"collaborator" if "admin";
"modify" if "admin";

relations = { parent_fleet: Fleet };
"admin" if "admin" on "parent_fleet";
}

# Administrator permissions
"create_project" if "admin";
resource Project {
permissions = [
"list_children",
"modify",
"read",
"create_child",
];
roles = [ "admin", "collaborator", "viewer" ];

# Project viewers can see everything in the Project.
"list_children" if "viewer";
"read" if "viewer";

# Project collaborators can see, modify, and delete everything inside
# the Project recursively. (This is different from Fleet and
# Organization-level collaborators, who can only modify and delete child
# resources that they have specific permissions on. That's because
# we're not implementing fine-grained permissions within Projects yet.)
# They cannot modify or delete the Project itself.
"viewer" if "collaborator";
"create_child" if "collaborator";

# Project administrators can modify and delete the Project" itself.
"collaborator" if "admin";
"modify" if "admin";

relations = { parent_organization: Organization };
"admin" if "admin" on "parent_organization";
}

# For now, we use one generic resource to represent every kind of thing inside
# the Project. That's because they all have the same behavior.
resource ProjectChild {
permissions = [
"list_children",
"modify",
"read",
"create_child",
];

relations = { parent_project: Project };
"list_children" if "viewer" on "parent_project";
"read" if "viewer" on "parent_project";

"modify" if "collaborator" on "parent_project";
"create_child" if "collaborator" on "parent_project";
}

# Define relationships
has_relation(fleet: Fleet, "parent_fleet", organization: Organization)
if organization.fleet = fleet;
has_relation(organization: Organization, "parent_organization", project: Project)
if project.organization = organization;
has_relation(project: Project, "parent_project", project_child: ProjectChild)
if project_child.project = project;

# Define role relationships
has_role(actor: AuthenticatedActor, role: String, resource: Resource)
if resource.has_role(actor, role);
Loading