-
Notifications
You must be signed in to change notification settings - Fork 44
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
Adds granular permissions for push repositories #207
Conversation
dkliban
commented
Jan 18, 2021
•
edited
Loading
edited
Attached issue: https://pulp.plan.io/issues/8101 Attached issue: https://pulp.plan.io/issues/8101 |
pulp_container/app/models.py
Outdated
@@ -186,7 +189,39 @@ class Meta: | |||
unique_together = (("name", "tagged_manifest"),) | |||
|
|||
|
|||
class ContainerNamespace(BaseModel, AutoAddObjPermsMixin, AutoDeleteObjPermsMixin): | |||
class CreateGroupAndAddPermissionsMixin(AutoAddObjPermsMixin): |
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.
Nice!
Should we have the same for group removal? To ensure that no perms are inherited from the removed objects with the same field value?
It's critical for namespaces if we use its name and not pk.
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.
+1 it would be good to remove groups and perms whenever namespace/distribution also gets deleted.
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.
We should. I only considered the removal of the permissions which already occurs. However, the groups themselves need to be removed also. I'll add another mixin for that.
27228db
to
00246f6
Compare
pulp_container/app/access_policy.py
Outdated
# check model permissions for namespace creation | ||
return user.has_perm("container.add_containernamespace") | ||
# Anyone can create a new namespace | ||
return True |
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.
Na, we don't want that. If anyone should be able to create a new namespace, than the access_policy needs to reflect that.
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, I'd appreciate some background behind the decision of allowing creation of namespaces by anyone.
pulp_container/app/models.py
Outdated
"""Delete all groups associated with this object and user object permissions.""" | ||
|
||
group_pks = [o.group_id for o in GroupObjectPermission.objects.filter(object_pk=self.pk)] | ||
Group.objects.filter(pk__in=group_pks).delete() |
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 will delete all groups with any permission on that object. Even the ones not created by us.
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'll add better filtering for 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.
How about deleting only groups which are created automatically by us? all the default groups: owner, collaborator, consumer?
I'm not sure we want to touch groups created by the users, even if the only permission which is left is for the object which just got removed.
Imagine I have a group of users, teamA, and I give them only read access to a specific object. When this object is removed, they no longer have access to anything but that's fine, I don't want to recreate the group and add all the users again, I just want to give them new permissions eventually.
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 agree, let's auto remove only groups we have created. Rest of the groups would need to be deleted by the owner of the groups 'manually'
pulp_container/app/models.py
Outdated
("namespace_view_containerdistribution", "View a distribution in a namespace"), | ||
("namespace_pull_containerdistribution", "Pull from all distributions in a namespace"), | ||
("namespace_push_containerdistribution", "Push to all distributions in a namespace"), | ||
("namespace_change_containerdistribution", "Change all distributions in a namespace"), |
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.
👍
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.
unfortunately, it's the maximum recursive depth for the thumb-ups 😞
7157f81
to
7286aa0
Compare
@@ -587,6 +589,29 @@ class ContainerPushRepositoryViewSet(ReadOnlyRepositoryViewSet): | |||
], | |||
} | |||
|
|||
def get_queryset(self): |
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 had to add custom filtering for the repositories so that we could consider permissions on the namespace and the private field on the distribution. We will need to do something similar for the distributions viewset.
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.
+1 not necessarily part of this pr, but commenting now for the sake of capturing a TODO
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 filed an issue https://pulp.plan.io/issues/8206
5744c5f
to
acd159e
Compare
@ipanova @mdellweg @goosemania Even though there are still some test failures, the functionality we discussed is there. I discovered that during a push the hook for ContainerPushRepositories is called before the on create hook for ContainerDistributions so the logic in the group permission assignment is a bit weird. Please provide feedback on how to improve this. |
pulp_container/app/models.py
Outdated
@@ -239,7 +326,9 @@ def finalize_new_version(self, new_version): | |||
validate_repo_version(new_version) | |||
|
|||
|
|||
class ContainerPushRepository(Repository, AutoAddObjPermsMixin, AutoDeleteObjPermsMixin): | |||
class ContainerPushRepository( | |||
Repository, CreateGroupAndAddPermissionsMixin, AutoDeleteObjPermsMixin |
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.
We are not deleting groups here because all groups are related to the ContainerDistribution, is my understanding correct?
pulp_container/app/models.py
Outdated
@@ -358,7 +447,7 @@ class Meta: | |||
|
|||
|
|||
class ContainerDistribution( | |||
RepositoryVersionDistribution, AutoAddObjPermsMixin, AutoDeleteObjPermsMixin | |||
RepositoryVersionDistribution, CreateGroupAndAddPermissionsMixin, AutoDeleteGroupsMixin |
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 we keep AutoAddObjPermsMixin
, AutoDeleteObjPermsMixin
everywhere as well?
So permission auto-assignment works for users who change the access policy.
And even without a change to access policy the permissions are cleaned properly from the groups created and assigned to by a user.
What do you think?
Can 2 hooks of the same type be performed? (2 before_delete)?
We can try flip the order here https://github.com/pulp/pulp_container/blob/master/pulp_container/app/registry_api.py#L284 if that won't help we could try having nested transaction where we'd be able to control the order of execution |
pulp_container/app/models.py
Outdated
|
||
The parameters are specified as a dictionary with the following keys: | ||
|
||
"name_prefix" - the first part of the group 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.
group_type is missing
pulp_container/app/authorization.py
Outdated
) | ||
# this is an existing distribution. User should have model or object perms | ||
if self.user.has_perm("container.push_containerdistribution") or self.user.has_perm( | ||
"container.push_containerdistribution", distribution | ||
if ( |
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.
we should probably add model perms of container.namespace_push_containerdistribution
to this logical statement
011bf7f
to
10fd005
Compare
"condition": [ | ||
"has_model_perms:container.add_containerdistribution", | ||
"has_manage_namespace_dist_perms:container.manage_namespace_distributions", | ||
], |
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.
Having no condition here, means every authenticated user can create whatever not yet existing distribution (even in existing namespaces).
It should be
(namespace does not exist) or (user.has_namespace_perm(create_distribution))
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 added the condition back
pulp_container/app/access_policy.py
Outdated
|
||
def obj_exists(self, request, view, action): | ||
""" | ||
Check if the distribution exists. | ||
""" | ||
return view.get_object() is not None | ||
|
||
def is_private(self, request, view, action): | ||
def is_public(self, request, view, action): |
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 think this is very confusing, that you need to set the private
flag on the distribution to trigger this is_public
check.
851cfdd
to
a6a3cdd
Compare
|
||
def tearDown(self): | ||
"""Delete the user.""" | ||
super(TestContainerDistributionSerializer, self).tearDown() |
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.
super(TestContainerDistributionSerializer, self).tearDown() | |
super().tearDown() |
Welcome to world of python 3! ;)
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.
Thank you!
.list(name="container.namespace.{}.{}".format(group_type, namespace_name)) | ||
.results[0] | ||
) | ||
as_user["group_users_api"].create( |
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 requires RBAC for the group endpoints, right?
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.
yes it does
Thank you for summarizing, this is very helpful! we should definitely capture this in our docs. Having |
1b08084
to
cd112a2
Compare
@ipanova The logic for adding |
@@ -21,8 +21,10 @@ def has_namespace_obj_perms(self, request, view, action, permission): | |||
if type(obj) == models.ContainerDistribution: | |||
namespace = obj.namespace | |||
else: | |||
distribution = models.ContainerDistribution.objects.get(repository=obj) | |||
namespace = distribution.namespace | |||
dists_qs = models.ContainerDistribution.objects.filter(repository=obj) |
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.
def has_namespace_obj_perms(self, request, view, action, permission):
"""
Check if a user has object-level perms on the namespace associated with the distribution
or repository.
"""
obj = view.get_object()
if type(obj) == models.ContainerDistribution:
namespace = obj.namespace
return request.user.has_perm(permission, namespace)
else:
dists_qs = models.ContainerDistribution.objects.filter(repository=obj)
for dist in dists_qs:
if request.user.has_perm(permission, dist.namespace):
return True
return False
@@ -184,26 +194,6 @@ def test_push_with_view_perms(self): | |||
with self.assertRaises(exceptions.CalledProcessError): | |||
self._push(image_path, local_url, self.user_reader) | |||
|
|||
def test_push_with_no_perms(self): |
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.
why this test got removed?
@dkliban this looks good to me! thank you forgetting this big chunk of work done! |
The RegistryAccessPolicy is a combination of AccessPolicies for ContainerNamespace, ContainerDistribution, and ContainerPushRepository viewsets. The default policy requires users to have "container.add_containernamespace" and "container.add_containerdistribution" permissions in order to push a new repository into a new namespace. The permissions also enable user to use the Pulp API to create a namespace and distribution. The ContainerNamespaceViewset provides a create_namespace_group() function that can be used to assign permissions to a group of users. The default permission assignment for a namespace creates three groups Owners, Collaborators, and Consumers. Namespace Owners get the following permissions: "container.view_containernamespace", "container.delete_containernamespace", "container.namespace_add_containerdistribution", "container.namespace_delete_containerdistribution", "container.namespace_view_containerdistribution", "container.namespace_pull_containerdistribution", "container.namespace_push_containerdistribution", "container.namespace_change_containerdistribution", "container.namespace_view_containerpushrepository", "container.namespace_modify_content_containerpushrepository" Namespace Collaborators get the following permissions: "container.view_containernamespace", "container.namespace_add_containerdistribution", "container.namespace_delete_containerdistribution", "container.namespace_view_containerdistribution", "container.namespace_pull_containerdistribution", "container.namespace_push_containerdistribution", "container.namespace_change_containerdistribution", "container.namespace_view_containerpushrepository", "container.namespace_modify_content_containerpushrepository" Namespace Consumers get the following permissions: "container.view_containernamespace", "container.namespace_view_containerdistribution", "container.namespace_pull_containerdistribution", "container.namespace_view_containerpushrepository", The ContainerDistributionsViewset provides a create_distribution_group() function that can be used to assign permissions to a group of users. The default permission assignment for a newly created ContainerDistribution consists of three groups: Owners, Collaborators, and Consumers. ContainerDistribution Owners get the following permissions: "container.view_containerdistribution", "container.pull_containerdistribution", "container.push_containerdistribution", "container.delete_containerdistribution", "container.change_containerdistribution", "container.view_containerpushrepository", "container.modify_content_containerpushrepository" ContainerDistribution Collaborators get the following permissions: "container.view_containerdistribution", "container.pull_containerdistribution", "container.push_containerdistribution", "container.view_containerpushrepository", "container.modify_content_containerpushrepository" ContainerDistribution Consumers get the following permissions: "container.view_containerdistribution", "container.pull_containerdistribution", "container.view_containerpushrepository", The ContainerPushRepositoryViewset provides an add_perms_to_distribution_group() function to assign ContainerPushRepository permissions to the groups associated with the ContainerDistribution that serves the specific ContainerPushRepository. closes: #8101 https://pulp.plan.io/issues/8101