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

Implement silo level images #2772

Merged
merged 34 commits into from
Apr 19, 2023
Merged

Implement silo level images #2772

merged 34 commits into from
Apr 19, 2023

Conversation

zephraph
Copy link
Contributor

@zephraph zephraph commented Apr 5, 2023

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 of ProjectImage and SiloImage to the images table. From an authz perspective there are actually three resources represented:SiloImage, ProjectImage, and Image. As the names suggest, SiloImage is the child of a silo and ProjectImage 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.

#[serde(flatten)]
pub parent_id: SiloOrProjectId,
/// ID of the parent project if the image is a project image
pub project_id: Option<Uuid>,
Copy link
Contributor

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)
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cute

Copy link
Contributor Author

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.

@zephraph zephraph changed the title [WIP] Implement silo level images Implement silo level images Apr 17, 2023
@zephraph zephraph marked this pull request as ready for review April 17, 2023 15:09
Comment on lines +929 to +930
silo_id,
project_id,
Copy link
Contributor Author

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.

Comment on lines +206 to +229
// 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,
),
Copy link
Contributor Author

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,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's notable that because project_image and silo_image are based on views they're not insertable resources (as noted by @luqmana in #2767)

Comment on lines +878 to +885
// 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,
}
Copy link
Contributor Author

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" ],
Copy link
Contributor Author

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

Copy link
Collaborator

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.

Copy link
Contributor Author

@zephraph zephraph Apr 18, 2023

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,
Copy link
Contributor Author

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.

Comment on lines +793 to +804
// 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>),
}

Copy link
Contributor Author

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.

Comment on lines -32 to +34
pub fn image_lookup<'a>(
pub async fn image_lookup<'a>(
Copy link
Contributor Author

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>> {
Copy link
Contributor Author

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.

Comment on lines +44 to +53
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))
},
};
Copy link
Contributor Author

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.

Comment on lines +34 to +44
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)
}
Copy link
Contributor Author

@zephraph zephraph Apr 17, 2023

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.

Copy link
Contributor

@david-crespo david-crespo left a 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.

@zephraph zephraph merged commit 9fafa2b into main Apr 19, 2023
@zephraph zephraph deleted the image-silo-views branch April 19, 2023 01:26
@zephraph zephraph mentioned this pull request Apr 19, 2023
silo_id,
diesel::insert_into(dsl::image)
.values(image)
.on_conflict(dsl::id)
Copy link
Collaborator

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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

zephraph added a commit that referenced this pull request May 9, 2023
Post the merge of #2772 we want to remove the concept of system level
images. This PR takes on that work.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants