-
Notifications
You must be signed in to change notification settings - Fork 42
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
Implement silo level images #2772
Conversation
#[serde(flatten)] | ||
pub parent_id: SiloOrProjectId, | ||
/// ID of the parent project if the image is a project image | ||
pub project_id: Option<Uuid>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably worth going all the way and saying ", otherwise null", or even "On silo-scoped images this will be null.`
silo.lookup_for(authz::Action::CreateChild).await?; | ||
(authz_silo, None) | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cute
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It helps avoid repeating the rather complex logic contained below.
silo_id, | ||
project_id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that project images also include silo_id
. This is necessary to convert them back to the base image type.
// If this resource is directly inside a Silo, we only need to define | ||
// permissions that are contingent on having roles on that Silo. | ||
(PolarSnippet::InSilo, _) => format!( | ||
r#" | ||
resource {} {{ | ||
permissions = [ | ||
"list_children", | ||
"modify", | ||
"read", | ||
"create_child", | ||
]; | ||
|
||
relations = {{ containing_silo: Silo }}; | ||
"list_children" if "viewer" on "containing_silo"; | ||
"read" if "viewer" on "containing_silo"; | ||
"modify" if "collaborator" on "containing_silo"; | ||
"create_child" if "collaborator" on "containing_silo"; | ||
}} | ||
|
||
has_relation(parent: Silo, "containing_silo", child: {}) | ||
if child.silo = parent; | ||
"#, | ||
resource_name, resource_name, | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I patterned this off of InProject
to avoid have to create a one-off authz rule for SiloImage
. In the future if there's a normal resource that's a child of the silo we can use this polar snippet to avoid having to define custom rules for it. I haven't looked too closely at the other resources that live under the silo to see if any of them could be updated to use this snippet instead.
} | ||
|
||
#[derive( | ||
Queryable, Selectable, Clone, Debug, Resource, Serialize, Deserialize, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// This resource is a collection of _all_ images in a silo, including project images. | ||
authz_resource! { | ||
name = "Image", | ||
parent = "Silo", | ||
primary_key = Uuid, | ||
roles_allowed = false, | ||
polar_snippet = InSilo, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's notable here that I've used the InSilo
polar snippet to define auth for the base image when that's not strictly true. The only place Image
is referred to in the auth system is when we're looking up an image based on ID. In that case, we don't know if it's a project or silo image so we must use the generic form of the image lookup. For the current implementation that means a user must have read permissions on the Silo to even be able to look up a project image by ID. Subsequent authz checks validates the appropriate permissions on the project (if it's a project image) so we get the right checks on the resource eventually.
@@ -453,7 +483,7 @@ impl<'a> Root<'a> { | |||
lookup_resource! { | |||
name = "Silo", | |||
ancestors = [], | |||
children = [ "IdentityProvider", "SamlIdentityProvider", "Project" ], | |||
children = [ "IdentityProvider", "SamlIdentityProvider", "Project", "SiloImage" ], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should Image
be added here? cc @davepacheco
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the only purpose of putting things here is to generate selectors on this lookup type that will look up child resources. e.g., if you wanted to do LookupPath(...).silo_id(...).image_name(...)
, then you'd want to put that here. I haven't looked at the rest of this PR yet to know if you'd want that here but it seems like not since you should already have SiloImage
. But I think it's safe to leave it out if you're not sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This base Image
type will only ever be used to access an image by ID so it seems fine.
@@ -540,6 +579,15 @@ lookup_resource! { | |||
|
|||
lookup_resource! { | |||
name = "Image", | |||
ancestors = ["Silo"], | |||
children = [], | |||
lookup_by_name = false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's notable that this resource is only used for ID lookups. If there's a name it's always either SiloImage
or ProjectImage
instead.
// Helpers for unifying the interfaces around images | ||
|
||
pub enum ImageLookup<'a> { | ||
ProjectImage(ProjectImage<'a>), | ||
SiloImage(SiloImage<'a>), | ||
} | ||
|
||
pub enum ImageParentLookup<'a> { | ||
Project(Project<'a>), | ||
Silo(Silo<'a>), | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might not really be the best place for these little helper enums, but I wasn't sure where else to put them.
pub fn image_lookup<'a>( | ||
pub async fn image_lookup<'a>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unique in that it's the only lookup that's async
&'a self, | ||
opctx: &'a OpContext, | ||
image_selector: params::ImageSelector, | ||
) -> LookupResult<lookup::Image<'a>> { | ||
) -> LookupResult<ImageLookup<'a>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return type here is an enum that wraps either a ProjectImage
or a SiloImage
. It's important that we returned the appropriate type for authz purposes.
let (.., db_image) = LookupPath::new(opctx, &self.db_datastore) | ||
.image_id(id).fetch().await?; | ||
let lookup = match db_image.project_id { | ||
Some(_) => ImageLookup::ProjectImage(LookupPath::new(opctx, &self.db_datastore) | ||
.project_image_id(id)), | ||
None => { | ||
ImageLookup::SiloImage(LookupPath::new(opctx, &self.db_datastore) | ||
.silo_image_id(db_image.silo_id)) | ||
}, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit unique. Because there's only a single endpoint for lookup images by id (/v1/images/{image_id}
) we don't actually know if it's a silo or project image when we get the ID. We first have to lookup the image and see if project_id
is populated. If it is, we know it's a project id. Otherwise it's a silo id.
pub fn current_silo_lookup<'a>( | ||
&'a self, | ||
opctx: &'a OpContext, | ||
) -> LookupResult<lookup::Silo<'a>> { | ||
let silo = opctx | ||
.authn | ||
.silo_required() | ||
.internal_context("looking up current silo")?; | ||
let silo = self.silo_lookup(opctx, NameOrId::Id(silo.id()))?; | ||
Ok(silo) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a helpful utility to be able to quickly construct a silo lookup just given the opctx.
Co-authored-by: David Crespo <david-crespo@users.noreply.github.com>
Co-authored-by: David Crespo <david-crespo@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great. We went over this in detail on a call. This has been a long journey.
co-authored-by: David Crespo <david@oxidecomputer.com>
silo_id, | ||
diesel::insert_into(dsl::image) | ||
.values(image) | ||
.on_conflict(dsl::id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks more like an "upsert" than "create". I'm not sure if that's necessary here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you're right, this should be more of a regular insert. I'll create an issue to follow up on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The project images look like they're in the same boat
Post the merge of #2772 we want to remove the concept of system level images. This PR takes on that work.
Implements silo level images as a replacement for global images.
This implementation preserves a single API endpoint
/v1/images
and a single database table to store all images. It uses database views like #2767 to map the notion ofProjectImage
andSiloImage
to the images table. From an authz perspective there are actually three resources represented:SiloImage
,ProjectImage
, andImage
. As the names suggest,SiloImage
is the child of a silo andProjectImage
is the child of a project.Image
occupies an odd space where it's currently considered the child of a Silo though it technically straddles both positions in the hierarchy.