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

Fleet privileges should not cascade into Silos #1580

Merged
merged 38 commits into from
Aug 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
516719c
some work
davepacheco May 25, 2022
dad744a
roll in proposed fix
davepacheco May 25, 2022
f46c708
nits
davepacheco May 26, 2022
8fd19f1
Merge branch 'main' into authz-policy-test
davepacheco Jun 15, 2022
a1d12c0
fix after merge with main
davepacheco Jun 15, 2022
bbdcb79
parallelize test (ugly)
davepacheco Jun 16, 2022
57136c5
add database tests
davepacheco Jun 16, 2022
ad3e233
test unauthenticated user
davepacheco Jun 16, 2022
553487c
use clearer symbols
davepacheco Jun 16, 2022
2b14b1a
existing context tests are obviated by the new test
davepacheco Jun 16, 2022
16a46c6
Merge branch 'main' into authz-policy-test
davepacheco Aug 9, 2022
7381322
add coverage test for IAM role policy test
davepacheco Aug 10, 2022
98a9c57
break up the policy test into more manageable modules
davepacheco Aug 10, 2022
8b8cc53
move polar_class stuff around -- use dynamic dispatch
davepacheco Aug 10, 2022
02d3911
refactor resource generation to be less ad hoc
davepacheco Aug 10, 2022
de3b399
move specific resources out of generic builder stuff
davepacheco Aug 10, 2022
d56269a
edits, docs
davepacheco Aug 11, 2022
e2c48bc
more docs, more cleanup
davepacheco Aug 11, 2022
2decfdd
move concrete resources to a separate module
davepacheco Aug 11, 2022
ab6d3be
move exemption list
davepacheco Aug 11, 2022
c20ba05
this rule is tested
davepacheco Aug 11, 2022
086bf94
remaining XXX-dap are covered elsewhere or already done
davepacheco Aug 11, 2022
b580aa1
add some more global resources
davepacheco Aug 11, 2022
f4fc02c
remove last XXX
davepacheco Aug 11, 2022
7950489
add racks, sleds, NICs
davepacheco Aug 11, 2022
dbdddcd
Merge remote-tracking branch 'origin/main' into authz-policy-test
davepacheco Aug 11, 2022
3209cf2
Fleet privileges should not cascade into Silos
davepacheco Aug 11, 2022
f3a0a80
fix style
davepacheco Aug 11, 2022
7d99ea3
remove unused service balancer role
davepacheco Aug 11, 2022
3377e50
fix test
davepacheco Aug 11, 2022
5771c3c
update to 0.26.2 for fix for oso#1592
davepacheco Aug 12, 2022
6f2dec3
Merge remote-tracking branch 'origin/authz-policy-test' into isolate-…
davepacheco Aug 12, 2022
b7838ef
Merge commit 'eef656' into isolate-silos
davepacheco Aug 12, 2022
d2766c6
Fleet privileges should not cascade into Silos
davepacheco Aug 11, 2022
c0958cb
fix style
davepacheco Aug 11, 2022
fb3a0bd
remove unused service balancer role
davepacheco Aug 11, 2022
06dbe49
fix test
davepacheco Aug 11, 2022
d5babba
Merge branch 'isolate-silos-merge' into isolate-silos
davepacheco Aug 12, 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
9 changes: 8 additions & 1 deletion nexus/src/app/silo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@ impl super::Nexus {
opctx: &OpContext,
new_silo_params: params::SiloCreate,
) -> CreateResult<db::model::Silo> {
self.datastore().silo_create(&opctx, new_silo_params).await
// Silo group creation happens as Nexus's "external authn" context,
// not the user's context here. The user may not have permission to
// create arbitrary groups in the Silo, but we allow them to create
// this one in this case.
let external_authn_opctx = self.opctx_external_authn();
self.datastore()
.silo_create(&opctx, &external_authn_opctx, new_silo_params)
.await
}

pub async fn silos_list_by_name(
Expand Down
14 changes: 10 additions & 4 deletions nexus/src/authz/omicron.polar
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ has_role(actor: AuthenticatedActor, role: String, resource: Resource)
#
# - fleet.admin (superuser for the whole system)
# - fleet.collaborator (can manage Silos)
# - fleet.viewer (can read most resources in the system)
# - 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)
Expand Down Expand Up @@ -132,10 +132,16 @@ resource Silo {
"create_child" if "collaborator";
"modify" if "admin";

# Roles implied by roles on this resource's parent (Fleet)
# Permissions implied by roles on this resource's parent (Fleet). Fleet
# privileges allow a user to see and potentially administer the Silo,
# but they do not give anyone permission to look at anything inside the
# Silo. To achieve this, we use permission rules here. (If we granted
# Fleet administrators _roles_ on the Silo, then those would cascade
# into the Silo as well.)
relations = { parent_fleet: Fleet };
"admin" if "collaborator" on "parent_fleet";
"viewer" if "viewer" on "parent_fleet";
"read" if "viewer" on "parent_fleet";
"list_identity_providers" if "viewer" on "parent_fleet";
"modify" if "collaborator" on "parent_fleet";

# external authenticator has to create silo users
"list_children" if "external-authenticator" on "parent_fleet";
Expand Down
28 changes: 27 additions & 1 deletion nexus/src/authz/policy_test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,19 @@ mod coverage;
mod resource_builder;
mod resources;

use super::SiloRole;
use crate::authn;
use crate::authz;
use crate::context::OpContext;
use crate::db;
use authn::USER_TEST_PRIVILEGED;
use coverage::Coverage;
use futures::StreamExt;
use nexus_test_utils::db::test_setup_database;
use nexus_types::external_api::shared;
use nexus_types::identity::Asset;
use omicron_common::api::external::Error;
use omicron_common::api::external::LookupType;
use omicron_test_utils::dev;
use resource_builder::DynAuthorizedResource;
use resource_builder::ResourceBuilder;
Expand Down Expand Up @@ -55,13 +60,34 @@ async fn test_iam_roles_behavior() {
let mut db = test_setup_database(&logctx.log).await;
let (opctx, datastore) = db::datastore::datastore_test(&logctx, &db).await;

// Before we can create the resources, users, and role assignments that we
// need, we must grant the "test-privileged" user privileges to fetch and
// modify policies inside the "main" Silo (the one we create users in).
let main_silo_id = Uuid::new_v4();
let main_silo = authz::Silo::new(
authz::FLEET,
main_silo_id,
LookupType::ById(main_silo_id),
);
datastore
.role_assignment_replace_visible(
&opctx,
&main_silo,
&[shared::RoleAssignment {
identity_type: shared::IdentityType::SiloUser,
identity_id: USER_TEST_PRIVILEGED.id(),
role_name: SiloRole::Admin,
}],
)
.await
.unwrap();

// Assemble the list of resources that we'll use for testing. As we create
// these resources, create the users and role assignments needed for the
// exhaustive test. `Coverage` is used to help verify that all resources
// are tested or explicitly opted out.
let exemptions = resources::exempted_authz_classes();
let mut coverage = Coverage::new(&logctx.log, exemptions);
let main_silo_id = Uuid::new_v4();
let builder =
ResourceBuilder::new(&opctx, &datastore, &mut coverage, main_silo_id);
let test_resources = resources::make_resources(builder, main_silo_id).await;
Expand Down
15 changes: 5 additions & 10 deletions nexus/src/authz/policy_test/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,28 +98,23 @@ async fn make_silo(
silo_id: Uuid,
first_branch: bool,
) {
let silo1 = authz::Silo::new(
let silo = authz::Silo::new(
authz::FLEET,
silo_id,
LookupType::ByName(silo_name.to_string()),
);
if first_branch {
builder.new_resource_with_users(silo1.clone()).await;
builder.new_resource_with_users(silo.clone()).await;
} else {
builder.new_resource(silo1.clone());
builder.new_resource(silo.clone());
}

let norganizations = if first_branch { 2 } else { 1 };
for i in 0..norganizations {
let organization_name = format!("{}-org{}", silo_name, i + 1);
let org_first_branch = first_branch && i == 0;
make_organization(
builder,
&silo1,
&organization_name,
org_first_branch,
)
.await;
make_organization(builder, &silo, &organization_name, org_first_branch)
.await;
}
}

Expand Down
3 changes: 2 additions & 1 deletion nexus/src/db/datastore/silo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ impl DataStore {
pub async fn silo_create(
&self,
opctx: &OpContext,
group_opctx: &OpContext,
new_silo_params: params::SiloCreate,
) -> CreateResult<Silo> {
let silo_id = Uuid::new_v4();
Expand All @@ -90,7 +91,7 @@ impl DataStore {
{
let silo_admin_group_ensure_query =
DataStore::silo_group_ensure_query(
opctx,
&group_opctx,
&authz_silo,
db::model::SiloGroup::new(
silo_group_id,
Expand Down
9 changes: 1 addition & 8 deletions nexus/src/db/fixed_data/role_assignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ lazy_static! {
pub static ref BUILTIN_ROLE_ASSIGNMENTS: Vec<RoleAssignment> =
vec![
// The "internal-api" user gets the "admin" role on the sole Fleet.
// This will grant them (nearly) all permissions on all resources.
// This is a pretty elevated privilege.
// TODO-security We should scope this down (or, really, figure out a
// better internal authn/authz story).
RoleAssignment::new(
Expand All @@ -24,13 +24,6 @@ lazy_static! {
*FLEET_ID,
role_builtin::FLEET_ADMIN.role_name,
),
RoleAssignment::new(
IdentityType::UserBuiltin,
user_builtin::USER_SERVICE_BALANCER.id,
role_builtin::FLEET_ADMIN.resource_type,
*FLEET_ID,
role_builtin::FLEET_ADMIN.role_name,
),

// The "internal-read" user gets the "viewer" role on the sole
// Fleet. This will grant them the ability to read various control
Expand Down
11 changes: 6 additions & 5 deletions nexus/src/db/fixed_data/role_builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ lazy_static! {
role_name: "collaborator",
description: "Organization Collaborator",
};
pub static ref SILO_ADMIN: RoleBuiltinConfig = RoleBuiltinConfig {
resource_type: api::external::ResourceType::Silo,
role_name: "admin",
description: "Silo Administrator",
};
pub static ref BUILTIN_ROLES: Vec<RoleBuiltinConfig> = vec![
FLEET_ADMIN.clone(),
FLEET_AUTHENTICATOR.clone(),
Expand All @@ -50,11 +55,7 @@ lazy_static! {
role_name: "collaborator",
description: "Fleet Collaborator",
},
RoleBuiltinConfig {
resource_type: api::external::ResourceType::Silo,
role_name: "admin",
description: "Silo Administrator",
},
SILO_ADMIN.clone(),
RoleBuiltinConfig {
resource_type: api::external::ResourceType::Silo,
role_name: "collaborator",
Expand Down
10 changes: 9 additions & 1 deletion nexus/src/db/fixed_data/silo_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,22 @@ lazy_static! {
pub static ref ROLE_ASSIGNMENTS_PRIVILEGED:
Vec<db::model::RoleAssignment> = vec![
// The "test-privileged" user gets the "admin" role on the sole
// Fleet. This will grant them all permissions on all resources.
// Fleet as well as the default Silo.
db::model::RoleAssignment::new(
db::model::IdentityType::SiloUser,
USER_TEST_PRIVILEGED.id(),
role_builtin::FLEET_ADMIN.resource_type,
*db::fixed_data::FLEET_ID,
role_builtin::FLEET_ADMIN.role_name,
),

db::model::RoleAssignment::new(
db::model::IdentityType::SiloUser,
USER_TEST_PRIVILEGED.id(),
role_builtin::SILO_ADMIN.resource_type,
*db::fixed_data::silo::SILO_ID,
role_builtin::SILO_ADMIN.role_name,
),
];

/// Test user that's granted no privileges, used for automated testing
Expand Down
79 changes: 78 additions & 1 deletion nexus/tests/integration_tests/saml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@ use omicron_nexus::TestInterfaces;

use http::method::Method;
use http::StatusCode;
use nexus_test_utils::resource_helpers::{create_silo, object_create};
use nexus_test_utils::resource_helpers::{
create_silo, grant_iam, object_create,
};

use nexus_test_utils::ControlPlaneTestContext;
use nexus_test_utils_macros::nexus_test;

use httptest::{matchers::*, responders::*, Expectation, Server};
use omicron_nexus::authn::USER_TEST_PRIVILEGED;
use omicron_nexus::authz::SiloRole;
use omicron_nexus::db::identity::Asset;

// Valid SAML IdP entity descriptor from https://en.wikipedia.org/wiki/SAML_metadata#Identity_provider_metadata
// note: no signing keys
Expand All @@ -34,6 +39,14 @@ async fn test_create_a_saml_idp(cptestctx: &ControlPlaneTestContext) {
const SILO_NAME: &str = "saml-silo";
create_silo(&client, SILO_NAME, true, shared::UserProvisionType::Fixed)
.await;
grant_iam(
&client,
&format!("/silos/{}", SILO_NAME),
SiloRole::Admin,
USER_TEST_PRIVILEGED.id(),
AuthnMode::PrivilegedUser,
)
.await;

let silo: Silo =
NexusRequest::object_get(&client, &format!("/silos/{}", SILO_NAME,))
Expand Down Expand Up @@ -152,6 +165,14 @@ async fn test_create_a_saml_idp_invalid_descriptor_truncated(
const SILO_NAME: &str = "saml-silo";
create_silo(&client, SILO_NAME, true, shared::UserProvisionType::Fixed)
.await;
grant_iam(
&client,
&format!("/silos/{}", SILO_NAME),
SiloRole::Admin,
USER_TEST_PRIVILEGED.id(),
AuthnMode::PrivilegedUser,
)
.await;

let saml_idp_descriptor = {
let mut saml_idp_descriptor = SAML_IDP_DESCRIPTOR.to_string();
Expand Down Expand Up @@ -212,6 +233,14 @@ async fn test_create_a_saml_idp_invalid_descriptor_no_redirect_binding(
const SILO_NAME: &str = "saml-silo";
create_silo(&client, SILO_NAME, true, shared::UserProvisionType::Fixed)
.await;
grant_iam(
&client,
&format!("/silos/{}", SILO_NAME),
SiloRole::Admin,
USER_TEST_PRIVILEGED.id(),
AuthnMode::PrivilegedUser,
)
.await;

let saml_idp_descriptor = {
let saml_idp_descriptor = SAML_IDP_DESCRIPTOR.to_string();
Expand Down Expand Up @@ -282,6 +311,14 @@ async fn test_create_a_hidden_silo_saml_idp(

create_silo(&client, "hidden", false, shared::UserProvisionType::Fixed)
.await;
grant_iam(
&client,
"/silos/hidden",
SiloRole::Admin,
USER_TEST_PRIVILEGED.id(),
AuthnMode::PrivilegedUser,
)
.await;

// Valid IdP descriptor
let saml_idp_descriptor = SAML_IDP_DESCRIPTOR.to_string();
Expand Down Expand Up @@ -351,6 +388,14 @@ async fn test_saml_idp_metadata_url_404(cptestctx: &ControlPlaneTestContext) {
const SILO_NAME: &str = "saml-silo";
create_silo(&client, SILO_NAME, true, shared::UserProvisionType::Fixed)
.await;
grant_iam(
&client,
&format!("/silos/{}", SILO_NAME),
SiloRole::Admin,
USER_TEST_PRIVILEGED.id(),
AuthnMode::PrivilegedUser,
)
.await;

let server = Server::run();
server.expect(
Expand Down Expand Up @@ -405,6 +450,14 @@ async fn test_saml_idp_metadata_url_invalid(
const SILO_NAME: &str = "saml-silo";
create_silo(&client, SILO_NAME, true, shared::UserProvisionType::Fixed)
.await;
grant_iam(
&client,
&format!("/silos/{}", SILO_NAME),
SiloRole::Admin,
USER_TEST_PRIVILEGED.id(),
AuthnMode::PrivilegedUser,
)
.await;

NexusRequest::new(
RequestBuilder::new(
Expand Down Expand Up @@ -561,6 +614,14 @@ async fn test_saml_idp_rsa_keypair_ok(cptestctx: &ControlPlaneTestContext) {
const SILO_NAME: &str = "saml-silo";
create_silo(&client, SILO_NAME, true, shared::UserProvisionType::Fixed)
.await;
grant_iam(
&client,
&format!("/silos/{}", SILO_NAME),
SiloRole::Admin,
USER_TEST_PRIVILEGED.id(),
AuthnMode::PrivilegedUser,
)
.await;

NexusRequest::new(
RequestBuilder::new(
Expand Down Expand Up @@ -932,6 +993,14 @@ async fn test_post_saml_response(cptestctx: &ControlPlaneTestContext) {

const SILO_NAME: &str = "saml-silo";
create_silo(&client, SILO_NAME, true, shared::UserProvisionType::Jit).await;
grant_iam(
&client,
&format!("/silos/{}", SILO_NAME),
SiloRole::Admin,
USER_TEST_PRIVILEGED.id(),
AuthnMode::PrivilegedUser,
)
.await;

let _silo_saml_idp: views::SamlIdentityProvider = object_create(
client,
Expand Down Expand Up @@ -1025,6 +1094,14 @@ async fn test_post_saml_response_with_relay_state(

const SILO_NAME: &str = "saml-silo";
create_silo(&client, SILO_NAME, true, shared::UserProvisionType::Jit).await;
grant_iam(
&client,
&format!("/silos/{}", SILO_NAME),
SiloRole::Admin,
USER_TEST_PRIVILEGED.id(),
AuthnMode::PrivilegedUser,
)
.await;

let _silo_saml_idp: views::SamlIdentityProvider = object_create(
client,
Expand Down
Loading