Skip to content

Commit

Permalink
Adds RBAC for UpstreamPulp API
Browse files Browse the repository at this point in the history
fixes: #3994
  • Loading branch information
dkliban committed Sep 19, 2023
1 parent bc9ca51 commit e39e34b
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGES/3994.feature
@@ -0,0 +1 @@
Added RBAC for UpstreamPulp APIs.
17 changes: 17 additions & 0 deletions pulpcore/app/migrations/0112_alter_upstreampulp_options.py
@@ -0,0 +1,17 @@
# Generated by Django 4.2.4 on 2023-08-25 19:53

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('core', '0111_task_enc_args_task_enc_kwargs'),
]

operations = [
migrations.AlterModelOptions(
name='upstreampulp',
options={'permissions': [('replicate_upstreampulp', 'Can start a replication task'), ('manage_roles_upstreampulp', 'Can manage roles on upstream pulps')]},
),
]
8 changes: 6 additions & 2 deletions pulpcore/app/models/replica.py
Expand Up @@ -6,11 +6,11 @@
"""

from django.db import models
from pulpcore.plugin.models import BaseModel, EncryptedTextField
from pulpcore.plugin.models import BaseModel, EncryptedTextField, AutoAddObjPermsMixin
from pulpcore.app.util import get_domain_pk


class UpstreamPulp(BaseModel):
class UpstreamPulp(BaseModel, AutoAddObjPermsMixin):
name = models.TextField(db_index=True)
pulp_domain = models.ForeignKey("Domain", default=get_domain_pk, on_delete=models.PROTECT)

Expand All @@ -30,3 +30,7 @@ class UpstreamPulp(BaseModel):

class Meta:
unique_together = ("name", "pulp_domain")
permissions = [
("replicate_upstreampulp", "Can start a replication task"),
("manage_roles_upstreampulp", "Can manage roles on upstream pulps"),
]
81 changes: 80 additions & 1 deletion pulpcore/app/viewsets/replica.py
Expand Up @@ -8,7 +8,7 @@

from pulpcore.app.models import TaskGroup, UpstreamPulp
from pulpcore.app.serializers import TaskGroupOperationResponseSerializer, UpstreamPulpSerializer
from pulpcore.app.viewsets import NamedModelViewSet
from pulpcore.app.viewsets import NamedModelViewSet, RolesMixin
from pulpcore.app.response import TaskGroupOperationResponse
from pulpcore.app.tasks import replicate_distributions
from pulpcore.tasking.tasks import dispatch
Expand All @@ -21,13 +21,92 @@ class UpstreamPulpViewSet(
mixins.ListModelMixin,
mixins.DestroyModelMixin,
mixins.UpdateModelMixin,
RolesMixin,
):
"""API for configuring an upstream Pulp to replicate. This API is provided as a tech preview."""

queryset = UpstreamPulp.objects.all()
endpoint_name = "upstream-pulps"
serializer_class = UpstreamPulpSerializer
ordering = "-pulp_created"
queryset_filtering_required_permission = "core.view_upstreampulp"

DEFAULT_ACCESS_POLICY = {
"statements": [
{
"action": ["list", "my_permissions"],
"principal": "authenticated",
"effect": "allow",
},
{
"action": ["create"],
"principal": "authenticated",
"effect": "allow",
"condition": [
"has_model_or_domain_perms:core.add_upstreampulp",
],
},
{
"action": ["retrieve"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_domain_or_obj_perms:core.view_upstreampulp",
},
{
"action": ["destroy"],
"principal": "authenticated",
"effect": "allow",
"condition": [
"has_model_or_domain_or_obj_perms:core.delete_upstreampulp",
],
},
{
"action": ["update", "partial_update"],
"principal": "authenticated",
"effect": "allow",
"condition": [
"has_model_or_domain_or_obj_perms:core.change_upstreampulp",
],
},
{
"action": ["replicate"],
"principal": "authenticated",
"effect": "allow",
"condition": [
"has_model_or_domain_or_obj_perms:core.replicate_upstreampulp",
],
},
{
"action": ["list_roles", "add_role", "remove_role"],
"principal": "authenticated",
"effect": "allow",
"condition": ["has_model_or_domain_or_obj_perms:core.manage_roles_upstreampulp"],
},
],
"creation_hooks": [
{
"function": "add_roles_for_object_creator",
"parameters": {"roles": "core.upstreampulp_owner"},
},
],
"queryset_scoping": {"function": "scope_queryset"},
}

LOCKED_ROLES = {
"core.upstreampulp_creator": ["core.add_upstreampulp"],
"core.upstreampulp_owner": [
"core.view_upstreampulp",
"core.change_upstreampulp",
"core.delete_upstreampulp",
"core.replicate_upstreampulp",
"core.manage_roles_upstreampulp",
],
"core.upstreampulp_viewer": ["core.view_upstreampulp"],
"core.upstreampulp_user": [
"core.view_upstreampulp",
"core.replicate_upstreampulp",
],
}

@extend_schema(
summary="Replicate",
Expand Down
87 changes: 87 additions & 0 deletions pulpcore/tests/functional/api/test_replication.py
@@ -1,6 +1,9 @@
import pytest
import uuid

from pulpcore.client.pulpcore import ApiException
from pulpcore.client.pulpcore import AsyncOperationResponse

from pulpcore.tests.functional.utils import PulpTaskGroupError


Expand Down Expand Up @@ -112,3 +115,87 @@ def test_replication_with_wrong_ca_cert(
task_group = monitor_task_group(response.task_group)
for task in task_group.tasks:
assert task.state == "completed"


@pytest.fixture()
def gen_users(gen_user):
"""Returns a user generator function for the tests."""

def _gen_users(role_names=list()):
if isinstance(role_names, str):
role_names = [role_names]
viewer_roles = [f"core.{role}_viewer" for role in role_names]
creator_roles = [f"core.{role}_creator" for role in role_names]
user_roles = [f"core.{role}_user" for role in role_names]
alice = gen_user(model_roles=viewer_roles)
bob = gen_user(model_roles=creator_roles)
charlie = gen_user()
dean = gen_user(model_roles=user_roles)
return alice, bob, charlie, dean

return _gen_users


@pytest.fixture
def try_action(monitor_task):
def _try_action(user, client, action, outcome, *args, **kwargs):
action_api = getattr(client, f"{action}_with_http_info")
try:
with user:
response, status, _ = action_api(*args, **kwargs, _return_http_data_only=False)
if isinstance(response, AsyncOperationResponse):
response = monitor_task(response.task)
except ApiException as e:
assert e.status == outcome, f"{e}"
else:
assert status == outcome, f"User performed {action} when they shouldn't been able to"
return response

return _try_action


@pytest.mark.parallel
def test_replicate_rbac(
gen_users,
try_action,
domain_factory,
bindings_cfg,
upstream_pulp_api_client,
pulp_settings,
gen_object_with_cleanup,
):
alice, bob, charlie, dean = gen_users(["upstreampulp"])
# Create a non-default domain
non_default_domain = domain_factory()

with bob:
upstream_pulp_body = {
"name": str(uuid.uuid4()),
"base_url": bindings_cfg.host,
"api_root": pulp_settings.API_ROOT,
"domain": "default",
"username": bindings_cfg.username,
"password": bindings_cfg.password,
"pulp_label_select": str(uuid.uuid4()),
}
upstream_pulp = gen_object_with_cleanup(
upstream_pulp_api_client, upstream_pulp_body, pulp_domain=non_default_domain.name
)

# Assert that Alice (upstream pulp viewer) gets a 403
try_action(alice, upstream_pulp_api_client, "replicate", 403, upstream_pulp.pulp_href)

# Assert that B (upstream pulp owner) gets a 202
try_action(bob, upstream_pulp_api_client, "replicate", 202, upstream_pulp.pulp_href)

# Assert that Charlie (no role) get a 404
try_action(charlie, upstream_pulp_api_client, "replicate", 404, upstream_pulp.pulp_href)

# Assert that Dean can run replication
try_action(dean, upstream_pulp_api_client, "replicate", 202, upstream_pulp.pulp_href)

# Assert that Dean can view the upstream pulp
try_action(dean, upstream_pulp_api_client, "read", 200, upstream_pulp.pulp_href)

# Assert that Dean can't update the upstream pulp
try_action(dean, upstream_pulp_api_client, "partial_update", 403, upstream_pulp.pulp_href, {})

0 comments on commit e39e34b

Please sign in to comment.