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

Adds granular permissions for push repositories #207

Merged
merged 1 commit into from
Feb 4, 2021
Merged

Conversation

dkliban
Copy link
Member

@dkliban dkliban commented Jan 18, 2021

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",

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",

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",

ContainerDistribution Collaborators get the following permissions:

     "container.view_containerdistribution",
     "container.pull_containerdistribution",
     "container.push_containerdistribution",
     "container.view_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

@pulpbot
Copy link
Member

pulpbot commented Jan 18, 2021

Attached issue: https://pulp.plan.io/issues/8101

Attached issue: https://pulp.plan.io/issues/8101

@@ -186,7 +189,39 @@ class Meta:
unique_together = (("name", "tagged_manifest"),)


class ContainerNamespace(BaseModel, AutoAddObjPermsMixin, AutoDeleteObjPermsMixin):
class CreateGroupAndAddPermissionsMixin(AutoAddObjPermsMixin):
Copy link
Member

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.

Copy link
Member

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.

Copy link
Member Author

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.

Comment on lines 20 to 24
# check model permissions for namespace creation
return user.has_perm("container.add_containernamespace")
# Anyone can create a new namespace
return True
Copy link
Member

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.

Copy link
Member

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.

"""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()
Copy link
Member

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.

Copy link
Member Author

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

Copy link
Member

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.

Copy link
Member

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'

("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"),
Copy link
Member

Choose a reason for hiding this comment

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

👍

Copy link
Member

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 😞

@@ -587,6 +589,29 @@ class ContainerPushRepositoryViewSet(ReadOnlyRepositoryViewSet):
],
}

def get_queryset(self):
Copy link
Member Author

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.

Copy link
Member

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

Copy link
Member

Choose a reason for hiding this comment

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

@dkliban
Copy link
Member Author

dkliban commented Jan 29, 2021

@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.

@@ -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
Copy link
Member

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?

@@ -358,7 +447,7 @@ class Meta:


class ContainerDistribution(
RepositoryVersionDistribution, AutoAddObjPermsMixin, AutoDeleteObjPermsMixin
RepositoryVersionDistribution, CreateGroupAndAddPermissionsMixin, AutoDeleteGroupsMixin
Copy link
Member

@goosemania goosemania Jan 29, 2021

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)?

@ipanova
Copy link
Member

ipanova commented Jan 29, 2021

@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.

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


The parameters are specified as a dictionary with the following keys:

"name_prefix" - the first part of the group name
Copy link
Member

Choose a reason for hiding this comment

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

group_type is missing

)
# 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 (
Copy link
Member

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

"condition": [
"has_model_perms:container.add_containerdistribution",
"has_manage_namespace_dist_perms:container.manage_namespace_distributions",
],
Copy link
Member

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))

Copy link
Member Author

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


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):
Copy link
Member

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.

@dkliban dkliban changed the title Adds Groups with object level permissions for Namespaces and Distributions Adds granular permissions for push repositories Feb 3, 2021

def tearDown(self):
"""Delete the user."""
super(TestContainerDistributionSerializer, self).tearDown()
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
super(TestContainerDistributionSerializer, self).tearDown()
super().tearDown()

Welcome to world of python 3! ;)

Copy link
Member Author

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(
Copy link
Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes it does

@ipanova
Copy link
Member

ipanova commented Feb 3, 2021

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",

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",

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",

ContainerDistribution Collaborators get the following permissions:

     "container.view_containerdistribution",
     "container.pull_containerdistribution",
     "container.push_containerdistribution",
     "container.view_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

Thank you for summarizing, this is very helpful! we should definitely capture this in our docs.
Couple of questions/suggestions:
Namespace Owners and Namespace Collaborators should also get container.namespace_modify_content_containerpushrepository otherwise they would not be able to tag/untag repos

Having container.view_containerpushrepository on the distribution viewset. how does that work? since this permission is defined on the push repository model and you can;t really re-use on another model

@dkliban
Copy link
Member Author

dkliban commented Feb 3, 2021

@ipanova The logic for adding container.view_containerpushrepository permission is a bit confusing in the code. I actually now think that I should add another method to the ContainerDistribution model that will accept ContainerPushRepository related permissions separately. Right now the create_distribution_group() method on the ContainerDistribution model checks if the permission name ends with containerpushrepository and separates those permissions into a list of their own. Then it checks if there is a ContainerPushRepository associated with the distribution. If there is, it adds the permissions for that object.

@@ -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)
Copy link
Member

@ipanova ipanova Feb 4, 2021

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):
Copy link
Member

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?

@ipanova
Copy link
Member

ipanova commented Feb 4, 2021

@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
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.

5 participants