Closed costrouc closed 11 months ago
First step with the DB impacts is here in branch 491-role-mapping
Then I'm kind of unsure how to progress with get_namespace_role_bindings
. Whats the best way to get access to the DB from this class that doesn't have access to it ?
I'd just supply the db session as a function argument. Would that work?
Ok, some parts around the authentication were still blurry but I see clearer now.
I have explored the solution you have described. Let me know if I get this correctly, because I have a slightly different approach to suggest:
My understanding is that all the filter_*
functions in class Authentication ( Authentication::filter_builds, Authentication::filter_environments, Authentication::filter_namespaces ), will need to rely on get_namespace_role_bindings
, directly or indirectly, to filter the results from the API according to the role bindings.
I noticed that all these functions receive an entity
parameter, which is an AuthenticationToken that contains a role_bindings
property.
So couldn't we rely on entity.role_bindings
instead ? The property would have to be set up when the AuthenticationToken
is built. This approach would have less impacts in the codebase (as far as I understand it, but I might be missing something that prevents this approach). An extra feature would be required : updating the role bindings regularly based on the DB.
Let me know what you think :)
2023 07 06 - Sync with Chris :
database_role_bindings
that will return a structure similar to authenticated_role_bindings
and unauthenticated_role_bindings
get_entity_bindings
- fix the merge of the sets (lines 159-168)authorize_request / authenticate_request
, might require to add a DB handle to the authentication objectGreat summary @pierrotsmnrd. After checking the code more I think that several methods will require the db
argument.
auth.authorization.get_entity_bindings
auth.authorization.get_entity_binding_permissions
auth.filter_namespaces
auth.authorize_request
api.list_environments
The more I think about it the RBACAuthorization class just generally needs a db session object injected into it when being created. . It would be difficult to pass the db instance in all methods.
Might be easiest to add after https://github.com/Quansight/conda-store/blob/main/conda-store-server/conda_store_server/server/app.py#L189 the following:
self.authentication._authorization.db = conda_store.db
Probably do in a more official way :smile:
This issue is partially implmented @pierrotsmnrd. Now we need to add api methods mentioned in this issue.
I think this issue is fully resolved now that we have merged the PR #508. Will re-open if issues occur.
Reopening since we need to make get namespace return the api endpoint data.
This is an important issue for the role mappings in the database to be done properly but is not a priority for JATIC work.
@nkaretnikov assigning this to you
This is a complex issue that I think is worth talking about over a call with @pierrotsmnrd and I. I'll put the current status here.
I think we slightly missed how this should be implemented.
The entire use case:
I chris
would have namespace-role-mapping::create
and namespace-role-mapping::delete
permissions on a particular namespace say quansight
. This means I should have enough permissions to "grant" user pierre
a role on the quansight
.
We have a database table orm.NamespaceRoleMapping
which is used to track these permissions. I think the entity
field should be instead be something like target_namespace_id
. Meaning user namespace_id
has role role
on specific namespace target_namespace_id
. I don't think that entity helps us here and @pierrotsmnrd sorry for the wrong direction here.
Also I have doubts on us having PUT /namespace/{namespace}/
as the route for updating role_mappings. The more I think about it I think I'd prefer something like an explicit domain e.g. PUT /namespace/{namespace}/role_mappings/
.
So I think that most of the hard work is already done but we need to tweak the REST api and fixup how we represent things in the DB. @pierrotsmnrd do you have thoughts? Also we should schedule a call.
Working on this. I found the discussion here pretty difficult to follow, so I decided to focus on this bit, which is actionable:
I chris would have namespace-role-mapping::create and namespace-role-mapping::delete permissions on a particular namespace say quansight. This means I should have enough permissions to "grant" user pierre a role on the quansight.
It was tricky to understand how this would even look in the UI, so I started from that direction.
I have a UI mockup here: https://github.com/nkaretnikov/conda-store/commits/role-mappings-491
It looks like this:
Dropdowns update when you click on them. Delete shows a popup warning before executing. The style is similar to how we manage builds, but I use a table here to keep it aligned. We could add stuff like text field auto-complete later.
We could move it somewhere else, but currently:
The main thing I need to figure out is how role assignment will look like, such that I could actually list some users in this table above. Eve is just a hardcoded placeholder at the moment. IIUC, we don't store usernames, it's all token-based (JWT). I'll update other APIs as needed.
Actually this is the wrong way around we first need to define how the API should look like, followed by the workflow and the UI is the very last thing that needs doing. The UI simply exposed the workflow to the user. This issue should be on hold until we get a chance to brainstorm/agree on the underlying design decisions (API/workflow).
@trallard There's a huge gap between the discussion above (about entity being a wrong abstraction or the DB attribute idea) and what Chris wrote about being able to share namespace permissions. It wasn't clear to me how those things help (or not) reaching that end goal. I wanted to have a mockup to test real-world UX, to see which handlers are needed for those buttons to work, or what info is needed in the DB to be shown in the UI.
Now I understand constraints better, the UI is a bonus. To me, this highlighted a need to have access to usernames, which we cannot get easily IIUC. You need a readable way to distinguish users, in case you want to update their permissions later or remove them.
OK, I stop work on this and will schedule a discussion.
Meeting has been scheduled to discuss this. Blocked until the meeting happens
@trallard I wont be in the meeting but I think as we think through these roles, we need to identify some usecases or user profiles:
As a team manager
, I create and manage a set of 5 environments for my team to work in. The team can view and use these environments, but they don't have access to edit the environments. Any changes to the environments are requested and vetted by me, then once I update the environment, I notify the team of the changes. However, I am NOT a sys admin and I dont control anything on the backend (or have admin rights on nebari)
As an individual developer
, I create and manage my own environments. Sometimes I need to share a notebook with my team. To do this, I create a shared environment in our user group and make sure the notebook can run on that environment. I dont mind if my teammate edits the environment.
As a sys admin
, I'm responsible for the backend. I have much the same usecase as the team manager, but I DO have admin rights.
As an intern
, I'm very new to conda and environments. I don't need access to edit ANY environments outside of my personal namespace.
Based on what I currently know about the role mappings, this raises a couple of issues:
team manager
needs to create an environment in a shared namespace, but they want to lock it down so that only them and admins can edit it. To do this, we need to add a distinction between a user's view access to environment namespaces and their write access to individual environments.disclaimer: there are so many issues on this topic, I apologize if I've posted this in the wrong place.
Note: you don't need to read this message unless you want to provide early feedback on the model.
After talking to people and reading the code, I present my current plan below. I might still make changes to it if I run into implementation issues, but I hope there won't be any.
This is motivated by how we want sharing to work, as well as what we want to have in the UI.
alice | admin [update][delete] # role here is a dropdown, value is saved when clicking update
bob | viewer [update][delete]
[delete all]
<namespace> <role> [add] # gives access to this namespace for namespace 'namespace' with role 'role'
# (table entries) = GET /namespace/{namespace}/roles
# [delete all] = DELETE /namespace/{namespace}/roles
# [add] = POST /namespace/{namespace}/role, params: other_namespace, role
# (GET is not used here, but can be used for partial UI updates - redraw one row)
# [update] = PUT /namespace/{namespace}/role, params: other_namespace, role
# [delete] = DELETE /namespace/{namespace}/role, params: other_namespace
CREATE - no, handled one-by-one by CREATE on /role
READ {namespace}/roles - lists all role mappings (used to show all roles in the UI)
UPDATE - no, handled one-by-one by UPDATE on /role
DELETE {namespace}/roles - deletes all role mappings (used to delete all roles in the UI)
To detect potential API errors, I've opted for the strict CRUD model. This helps avoiding additional work in some cases, which would turn everything into one big update method. That is, an error is raised when there's a mismatch. For example, when CREATE attempts to create data that already exists in the DB.
CREATE {namespace}/role, params: other_namespace, role - insert into the DB, error if duplicate (enforced by uniqueness constraint on the table)
READ {namespace}/role, params: other_namespace - get role of other_namespace, error if not found
- can be skipped in the UI, but useful for testing
UPDATE {namespace}/role, params: other_namespace, role - search for entry matching (namespace, other_namespace) and update its role, error if not found
DELETE {namespace}/role, params: other_namespace - delete entry matching (namespace, other_namespace) (return deleted row), error if not found
class NamespaceRoleMapping(Base):
"""Mapping between roles and namespaces"""
__tablename__ = "namespace_role_mapping"
id = Column(Integer, primary_key=True)
# Provides access to this namespace
namespace_id = Column(Integer, ForeignKey("namespace.id"), nullable=False)
namespace = relationship(Namespace, back_populates="role_mappings")
# ... for other namespace
other_namespace_id = Column(Integer, ForeignKey("namespace.id"), nullable=False)
# ... with this role, like 'viewer'
role = Column(Unicode(255), nullable=False)
@validates("role")
def validate_role(self, key, role):
if role not in ["admin", "viewer", "developer"]:
raise ValueError(f"invalid entity={role}")
return role
__table_args__ = (
# Ensures no duplicates can be added with this combination of fields
UniqueConstraint('namespace_id', 'other_namespace_id', 'role', name='_uc'),
)
I've kept 'namespace' here to keep this table related to Namespace
since there it has:
role_mappings = relationship("NamespaceRoleMapping", back_populates="namespace")
entity
is removed from the table.
Also, the current model is stricter and not as flexible as before, but it is less confusing and allows to implement namespace sharing.
In this model, when I have namespace foo
and environments there, I give the same level of access to all those environments, depending on role
assigned to other_namespace
. Need different permissions for some of these environments? Create a new namespace.
We could do it by environment instead, but it would complicate things in the UI and DB. Because then people would ask to share multiple environments together, too. So I think it's simpler to just do it by namespace from the start, and that's it.
Important: this is not compatible with the previous DB model, so my plan is to drop namespace mappings when migrating, which will require users to re-add other namespaces (for sharing, I mean). But I don't think we even use this feature yet, do we?
Also, we have versioned API, but I'm just modifying v1. I don't think we've made any claims about stability of this one yet. I'm also not sure if anyone uses these APIs either.
Because this touches the UI and considering that @smeragoel is our UI/UX expert I'd like her to review and provide feedback on the UX workflows and UI for this.
This will require also identifying where in the UI these settings should be presented and how.
Status update for PR #607:
@nkaretnikov has opened a draft PR and needs my review.
@smeragoel and I had a call about the design part of this. I also talked to @costrouc after that.
http://localhost:8080/conda-store/admin/namespace/
)/api/v2
, see conda-store-server/conda_store_server/server/views/api.py
):
post("/namespace/{namespace}/role"
- give namespace access to this namespace with some role (viewer, developer, admin)
get("/namespace/{namespace}/roles"
- view all users/namespaces who have access this namespace
get("/namespace/{namespace}/role"
- same but for one user
put("/namespace/{namespace}/role"
- change access role of namespace (to viewer, developer, admin)
delete("/namespace/{namespace}/roles"
- delete all namespaces who had access to this namespacedelete("/namespace/{namespace}/role"
- same but for one userStatus update:
I’ll get a first draft if the designs ready for review by the end of this week!
This is a first draft of the designs required for the workflow described above.
This is a completely new page that contains details about a particular namespace.
It is accessed by clicking on the namespace name
(Kim Pevey's Namespace) on the left panel. This introduces a new behaviour. Right now, clicking on Namespace Name
expands the dropdown on the left panel to show all the environments. According to the proposed design, clicking on it will expand the dropdown and show the namespace page also.
I am having some trouble with this. While the flow is pretty straightforward, I am unsure of the positioning of the Create New Namespace
button. Here are some design considerations for the button:
Namespace
to avoid confusion with the New Environment
button.Shared Namespaces
heading to avoid signalling that you can only created shared namespaces.name
and user access
, but it can also contain other properties/metadata.@smeragoel these look great! Thank you!
New Namespace button - I think the placement looks good. The label is kind of long for a button, but I agree with you that its necessary to include the word "namespace" so I dont think anything can be done about it.
What is the checkbox next to the trashcan for? - Ohhh now I see that line is what it looks like if you change the role. On the New Namespace page, the changing role highlighting and checkmark doesn't make sense though, right? Since everything will be new?
One thing we might have to think through is adding Users to the new Namespace.
These questions are mixed backend and frontend and I realize they may initiate more work so I want to say that I'm happen to consider these ideas as enhancements once we get the POC out.
From meeting today, it'll be intuitive to rename the "developer" role to "editor"
From meeting today, it'll be intuitive to rename the "developer" role to "editor"
After making this change locally, I realized it introduces too many changes. It requires updating the default role mappings, the docs, the DB validation functions, and the tests (to support both of these roles). The current role mappings PR is already too big. This will be done separately. New issue: #675.
From the meeting today:
Migrating data from v1 to v2 role mappings will be done in a separate PR because it can cause issues during migration, which might require manual intervention. So a standalone function will be provided for those who need it. #681
Status update: asked Chris and Chuck to review PR #607. Also, allocated time for a meeting to answer questions to speed up review process.
Motivation
For authentication currently the user to role and permissions has been managed in traitlets. Via a few important attributes
Developers were responsible for creating a function
authenticate(...)
which handled the user to role mapping. With this configuration it was possible to conda-store to not know anything about the users.Proposal
I propose adding one new database table to conda-store. We leverage the fact that the username will match a namespace name which is created on the backend.
Then add a method to
RBACAuthorizationBackend
Additionally there should be rest api methods and permissions for CRUD. I am least sure about this part.
schema.Permissions
namespace::update
permission which will allow updating the metadatanamespace::update
permissions. If you havenamespace::read
it should be sufficient to view members.@pierrotsmnrd I will be gone until Thursday but I think this should be scoped well enough to start. I will periodically be checking github so I should be able to respond to comments.