Skip to content

Commit

Permalink
Added RBAC (pulp#1290)
Browse files Browse the repository at this point in the history
fixes: pulp#1290
  • Loading branch information
bmclaughlin committed Mar 17, 2023
1 parent 26e327b commit 1252049
Show file tree
Hide file tree
Showing 13 changed files with 1,204 additions and 126 deletions.
7 changes: 7 additions & 0 deletions CHANGES/1290.feature
@@ -0,0 +1,7 @@
Added Role Based Access Control.
New default roles (creator, owner, viewer) have been added for ``AnsibleRepository``, ``AnsibleDistribution``,
``CollectionRemote``, ``RoleRemote``, and ``GitRemote``.
New detail role management endpoints (``my_permissions``, ``list_roles``, ``add_role``,
``remove_role``) have been added to ``AnsibleRepository``, ``AnsibleDistribution``, ``CollectionRemote``,
``GitRemote``, and ``RoleRemote``.

74 changes: 69 additions & 5 deletions pulp_ansible/app/galaxy/v3/views.py
Expand Up @@ -72,9 +72,17 @@

from pulp_ansible.app.tasks.deletion import delete_collection_version, delete_collection


_CAN_VIEW_REPO_CONTENT = {
"action": ["list", "retrieve", "download"],
"principal": "authenticated",
"effect": "allow",
"condition": "v3_can_view_repo_content",
}

_PERMISSIVE_ACCESS_POLICY = {
"statements": [
{"action": "*", "principal": "*", "effect": "allow"},
_CAN_VIEW_REPO_CONTENT,
],
"creation_hooks": [],
}
Expand Down Expand Up @@ -203,7 +211,23 @@ class CollectionViewSet(
filterset_class = CollectionFilter
pagination_class = LimitOffsetPagination

DEFAULT_ACCESS_POLICY = _PERMISSIVE_ACCESS_POLICY
DEFAULT_ACCESS_POLICY = {
"statements": [
_CAN_VIEW_REPO_CONTENT,
{
"action": "destroy",
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_obj_perms:ansible.delete_collection",
},
{
"action": ["update", "partial_update"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_obj_perms:ansible.change_collection",
},
],
}

def urlpattern(*args, **kwargs):
"""Return url pattern for RBAC."""
Expand Down Expand Up @@ -482,7 +506,20 @@ class CollectionUploadViewSet(
serializer_class = CollectionVersionUploadSerializer
pulp_tag_name = "Pulp_Ansible: Artifacts Collections V3"

DEFAULT_ACCESS_POLICY = _PERMISSIVE_ACCESS_POLICY
DEFAULT_ACCESS_POLICY = {
"statements": [
_CAN_VIEW_REPO_CONTENT,
{
"action": ["create"],
"principal": "authenticated",
"effect": "allow",
"condition": [
"has_model_perms:ansible.add_collection",
"has_model_or_obj_perms:ansible.view_ansiblerepository",
],
},
],
}

def urlpattern(*args, **kwargs):
"""Return url pattern for RBAC."""
Expand Down Expand Up @@ -673,7 +710,17 @@ class AnsibleNamespaceViewSet(
"metadata_sha256": ["exact", "in"],
}

DEFAULT_ACCESS_POLICY = _PERMISSIVE_ACCESS_POLICY
# TODO: write an actual access policy
DEFAULT_ACCESS_POLICY = {
"statements": [
_CAN_VIEW_REPO_CONTENT,
{
"action": "*",
"principal": "*",
"effect": "allow",
},
],
}

def get_queryset(self):
if getattr(self, "swagger_fake_view", False):
Expand Down Expand Up @@ -776,7 +823,20 @@ class CollectionVersionViewSet(

lookup_field = "version"

DEFAULT_ACCESS_POLICY = _PERMISSIVE_ACCESS_POLICY
DEFAULT_ACCESS_POLICY = {
"statements": [
_CAN_VIEW_REPO_CONTENT,
{
"action": ["destroy"],
"principal": "authenticated",
"effect": "allow",
"condition": [
"has_model_or_obj_perms:ansible.delete_collection",
"has_model_or_obj_perms:ansible.view_collection",
],
},
],
}

def urlpattern(*args, **kwargs):
"""Return url pattern for RBAC."""
Expand Down Expand Up @@ -925,6 +985,8 @@ class CollectionImportViewSet(
queryset = CollectionImport.objects.prefetch_related("task").all()
serializer_class = CollectionImportDetailSerializer

queryset_filtering_required_permission = "ansible.view_ansiblerepository"

DEFAULT_ACCESS_POLICY = _PERMISSIVE_ACCESS_POLICY

since_filter = OpenApiParameter(
Expand Down Expand Up @@ -1111,6 +1173,8 @@ class ClientConfigurationView(views.APIView):

DEFAULT_ACCESS_POLICY = _PERMISSIVE_ACCESS_POLICY

action = "retrieve"

@extend_schema(responses=ClientConfigurationSerializer)
def get(self, request, *args, **kwargs):
"""Get the client configs."""
Expand Down
15 changes: 15 additions & 0 deletions pulp_ansible/app/global_access_conditions.py
@@ -0,0 +1,15 @@
def v3_can_view_repo_content(request, view, action):
"""
Check if the repo is private, only let users with view repository permissions
view the collections here.
"""

# TODO: add when private repositories are a thing
# if "distro_base_path" in view.kwargs:
# distro_base_path = view.kwargs["distro_base_path"]
# repo = models.AnsibleDistribution.objects.get(base_path=distro_base_path).repository

# if repo.is_private:
# return request.user.has_perm("ansible.view_ansiblerepository")

return True
33 changes: 33 additions & 0 deletions pulp_ansible/app/migrations/0049_rbac_permissions.py
@@ -0,0 +1,33 @@
# Generated by Django 3.2.18 on 2023-03-01 23:11

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('ansible', '0048_collectionversionmark'),
]

operations = [
migrations.AlterModelOptions(
name='ansibledistribution',
options={'default_related_name': '%(app_label)s_%(model_name)s', 'permissions': [('manage_roles_ansibledistribution', 'Can manage roles on distributions')]},
),
migrations.AlterModelOptions(
name='ansiblerepository',
options={'default_related_name': '%(app_label)s_%(model_name)s', 'permissions': [('rebuild_metadata_ansiblerepository', 'Can rebuild metadata on the repository'), ('repair_ansiblerepository', 'Can repair the repository'), ('sign_ansiblerepository', 'Can sign content on the repository'), ('sync_ansiblerepository', 'Can start a sync task on the repository'), ('manage_roles_ansiblerepository', 'Can manage roles on repositories'), ('modify_ansible_repo_content', 'Can modify repository content')]},
),
migrations.AlterModelOptions(
name='collectionremote',
options={'default_related_name': '%(app_label)s_%(model_name)s', 'permissions': (('manage_roles_collectionremote', 'Can manage roles on collection remotes'),)},
),
migrations.AlterModelOptions(
name='gitremote',
options={'default_related_name': '%(app_label)s_%(model_name)s', 'permissions': [('manage_roles_gitremote', 'Can manage roles on git remotes')]},
),
migrations.AlterModelOptions(
name='roleremote',
options={'default_related_name': '%(app_label)s_%(model_name)s', 'permissions': [('manage_roles_roleremote', 'Can manage roles on role remotes')]},
),
]
27 changes: 21 additions & 6 deletions pulp_ansible/app/models.py
Expand Up @@ -11,6 +11,7 @@
from django_lifecycle import AFTER_UPDATE, BEFORE_SAVE, BEFORE_UPDATE, hook

from pulpcore.plugin.models import (
AutoAddObjPermsMixin,
BaseModel,
Content,
Remote,
Expand Down Expand Up @@ -355,7 +356,7 @@ class DownloadLog(BaseModel):
)


class RoleRemote(Remote):
class RoleRemote(Remote, AutoAddObjPermsMixin):
"""
A Remote for Ansible content.
"""
Expand All @@ -364,14 +365,15 @@ class RoleRemote(Remote):

class Meta:
default_related_name = "%(app_label)s_%(model_name)s"
permissions = [("manage_roles_roleremote", "Can manage roles on role remotes")]


def _get_last_sync_task(pk):
sync_tasks = Task.objects.filter(name__contains="sync", reserved_resources_record__icontains=pk)
return sync_tasks.order_by("-pulp_created").first()


class CollectionRemote(Remote):
class CollectionRemote(Remote, AutoAddObjPermsMixin):
"""
A Remote for Collection content.
"""
Expand Down Expand Up @@ -421,9 +423,10 @@ def _reset_repository_last_synced_metadata_time(self):

class Meta:
default_related_name = "%(app_label)s_%(model_name)s"
permissions = (("manage_roles_collectionremote", "Can manage roles on collection remotes"),)


class GitRemote(Remote):
class GitRemote(Remote, AutoAddObjPermsMixin):
"""
A Remote for Collection content hosted in Git repositories.
"""
Expand All @@ -435,6 +438,9 @@ class GitRemote(Remote):

class Meta:
default_related_name = "%(app_label)s_%(model_name)s"
permissions = [
("manage_roles_gitremote", "Can manage roles on git remotes"),
]


class AnsibleCollectionDeprecated(Content):
Expand All @@ -452,7 +458,7 @@ class Meta:
unique_together = ("namespace", "name")


class AnsibleRepository(Repository):
class AnsibleRepository(Repository, AutoAddObjPermsMixin):
"""
Repository for "ansible" content.
Expand Down Expand Up @@ -482,7 +488,14 @@ def last_sync_task(self):
class Meta:
default_related_name = "%(app_label)s_%(model_name)s"

permissions = (("modify_ansible_repo_content", "Can modify ansible repository content"),)
permissions = [
("rebuild_metadata_ansiblerepository", "Can rebuild metadata on the repository"),
("repair_ansiblerepository", "Can repair the repository"),
("sign_ansiblerepository", "Can sign content on the repository"),
("sync_ansiblerepository", "Can start a sync task on the repository"),
("manage_roles_ansiblerepository", "Can manage roles on repositories"),
("modify_ansible_repo_content", "Can modify repository content"),
]

def finalize_new_version(self, new_version):
"""Finalize repo version."""
Expand Down Expand Up @@ -532,7 +545,7 @@ def _reset_repository_last_synced_metadata_time(self):
self.last_synced_metadata_time = None


class AnsibleDistribution(Distribution):
class AnsibleDistribution(Distribution, AutoAddObjPermsMixin):
"""
A Distribution for Ansible content.
"""
Expand All @@ -541,3 +554,5 @@ class AnsibleDistribution(Distribution):

class Meta:
default_related_name = "%(app_label)s_%(model_name)s"

permissions = [("manage_roles_ansibledistribution", "Can manage roles on distributions")]
5 changes: 5 additions & 0 deletions pulp_ansible/app/settings.py
Expand Up @@ -27,3 +27,8 @@
ANSIBLE_DEFAULT_DISTRIBUTION_PATH = None
ANSIBLE_URL_NAMESPACE = ""
ANSIBLE_COLLECT_DOWNLOAD_LOG = False

DRF_ACCESS_POLICY = {
"dynaconf_merge_unique": True,
"reusable_conditions": ["pulp_ansible.app.global_access_conditions"],
}

0 comments on commit 1252049

Please sign in to comment.