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

Role Based Access Control #815

Merged
merged 1 commit into from Aug 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES/7160.feature
@@ -0,0 +1,5 @@
The `/pulp/api/v3/access_policies/` endpoint is available for reading and modifying the AccessPolicy
used for Role Based Access Control for all Pulp endpoints. This allows for complete customization
of the Authorization policies.

NOTE: this endpoint is in tech-preview and may change in backwards incompatible ways in the future.
3 changes: 3 additions & 0 deletions CHANGES/7210.feature
@@ -0,0 +1,3 @@
The `/pulp/api/v3/access_policies/` endpoint also includes a `permissions_assignment` section which
customizes the permissions assigned to new objects. This allows for complete customization for how
new objects work with custom define Authorization policies.
5 changes: 5 additions & 0 deletions CHANGES/7301.feature
@@ -0,0 +1,5 @@
The `/pulp/api/v3/tasks/` endpoint now provides a user-isolation behavior for non-admin users. This
policy is controllable at the `/pulp/api/v3/access_policies/` endpoint.

NOTE: The user-isolation behavior is in "tech preview" and production systems are recommended to
continue using the build-in ``admin`` user only.
2 changes: 2 additions & 0 deletions CHANGES/plugin_api/7151.feature
@@ -0,0 +1,2 @@
`BaseModel` now inherits from `LifecycleModel` provided by `django-lifecycle` allowing any subclass
to also use it instead of signals.
4 changes: 4 additions & 0 deletions CHANGES/plugin_api/7157.feature
@@ -0,0 +1,4 @@
A new `pulpcore.plugin.models.AutoDeleteObjPermsMixin` object can be added to models to
automatically delete all user and group permissions for an object just before the object is deleted.
This provides an easy cleanup mechanism and can be added to models as a mixin. Note that your model
must support `django-lifecycle` to use this mixin.
7 changes: 7 additions & 0 deletions CHANGES/plugin_api/7158.feature
@@ -0,0 +1,7 @@
A new model `pulpcore.plugin.models.AccessPolicy` is available to store AccessPolicy statements in
the database. The model's `statements` field stores the list of policy statements as a JSON field.
The `name` field stores the name of the Viewset the `AccessPolicy` is protecting.

Additionally, the `pulpcore.plugin.access_policy.AccessPolicyFromDB` is a drf-access-policy which
viewsets can use to protect their viewsets with. See the :ref:`viewset_enforcement` for more
information on this.
5 changes: 5 additions & 0 deletions CHANGES/plugin_api/7210.feature
@@ -0,0 +1,5 @@
A new `pulpcore.plugin.models.AutoAddObjPermsMixin` object can be added to models to automatically
add permissions for an object just after the object is created. This is controlled by data saved in
the `permissions_assignment` attribute of the `pulpcore.plugin.models.AccessPolicy` allowing users
to control what permissions are created. Note that your model must support `django-lifecycle` to use
this mixin.
3 changes: 3 additions & 0 deletions CHANGES/plugin_api/7300.feature
@@ -0,0 +1,3 @@
Viewsets that subclass ``pulpcore.plugin.viewsets.NamedModelViewSet` can declare the
``queryset_filtering_required_permission`` class attribute naming the permission required to view
an object. See the :ref:`queryset_scoping` documentation for more information.
33 changes: 31 additions & 2 deletions docs/plugins/plugin-writer/concepts/index.rst
Expand Up @@ -145,13 +145,42 @@ Sync Pipeline
sync_pipeline/sync_pipeline


Role Based Access Control
-------------------------

Pulp uses a policy-based approach for Role Based Access Control (RBAC).

Plugin writers can:

* Enable authorization for a viewset
* Ship a default access policy
* Express what default object-level and model-level permissions created for new objects
* Check permissions at various points in task code as needed


This allows users to then:

* Modify the default access policy on their installation for custom authorization
* Modify the default object-level and model-level permissions that are created for new objects

.. toctree::
:maxdepth: 2

rbac/overview
rbac/permissions
rbac/users_groups
rbac/access_policy
rbac/adding_automatic_permissions
rbac/queryset_scoping


Content Protection
------------------

Users can configure a ``ContentGuard`` to protect a ``Distribution`` on their own, but some plugins
want to offer built-in content protection features. For example pulp_container may only want a user
to download container images they have rights to based on some permissions system pulp_container could
provide.
to download container images they have rights to based on some permissions system pulp_container
could provide.

For more information see the :ref:`ContentGuard Usage by Plugin Writers
<plugin-writers-use-content-protection>` documentation.
Expand Down
217 changes: 217 additions & 0 deletions docs/plugins/plugin-writer/concepts/rbac/access_policy.rst
@@ -0,0 +1,217 @@
.. _defining_access_policy:

Defining an Access Policy
=========================

The Access Policy controls the authorization of a given request and is enforced at the
viewset-level. Access policies are based on the AccessPolicy from `drf-access-policy
<https://rsinger86.github.io/drf-access-policy/policy_logic/>`_ which uses `policy statements
described here <https://rsinger86.github.io/drf-access-policy/policy_logic/>`_.

Example Policy
--------------

Below is an example policy used by ``FileRemote``, with an explanation of its effect below that::

[
{
"action": ["list"],
"principal": "authenticated",
"effect": "allow",
},
{
"action": ["create"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_perms:file.add_fileremote",
},
{
"action": ["retrieve"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_obj_perms:file.view_fileremote",
},
{
"action": ["update", "partial_update"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_obj_perms:file.change_fileremote",
},
{
"action": ["destroy"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_obj_perms:file.delete_fileremote",
},
]

The above policy allows the following four cases, and denies all others by default. Overall this
goosemania marked this conversation as resolved.
Show resolved Hide resolved
creates a "user isolation policy" whereby users with the ``file.add_fileremote`` permission can
create ``FileRemote`` objects, and users can only read/modify/delete ``FileRemote`` objects they
created.

Here's a written explanation of the policy statements:

* ``list`` is allowed by any authenticated user. Although users are allowed to perform an operation
what they can list will still be restricted to :ref:`only the objects that user can view
<queryset_scoping>`.
* ``create`` is allowed by any authenticated user with the ``file.add_fileremote`` permission.
* ``retrieve`` (the detail view of an object) is allowed by an authenticated user who has the
``file.view_fileremote`` permission. Although users are allowed to perform an operation what they
can list will still be restricted to :ref:`only the objects that user can view
<queryset_scoping>`.
Comment on lines +60 to +62
Copy link
Member

Choose a reason for hiding this comment

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

👍

* ``update`` or ``partial_update`` is allowed by an authenticated user who has the
``file.change_fileremote`` permission.
* ``destroy`` is allowed by any authenticated user with the ``file.delete_fileremote`` permission.

These names correspond with the `default DRF viewset action names
<https://www.django-rest-framework.org/api-guide/viewsets/#viewset-actions>`_.


Authorization Conditions
------------------------

Each policy statement can contain `drf-access-policy conditions <https://rsinger86.github.io/
drf-access-policy/statement_elements/#condition>`_ which is useful for verifying a user has one or
more permissions. Pulp ships many built-in checks. See the :ref:`permission_checking_machinery`
documentation for more information on available checks.

When multiple conditions are present, **all** of them must return True for the request to be
authorized.

.. warning::

The ``admin`` user created on installations prior to RBAC being enabled has
``is_superuser=True``. Django assumes a superuser has any model-level permission even without it
being assigned. Additionally, django-guardian when checking object-level permissions defaults to
assuming the same although it is configurable. Generally, superusers are expected to bypass
authorization checks.


Custom ViewSet Actions
----------------------

The ``action`` part of a policy statement can reference `any custom action your viewset has
<https://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing>`_. For
example ``FileRepositoryViewSet`` has a ``sync`` custom action used by users to sync a given
``FileRepository``. Below is an example of the default policy used to guard that action::

{
"action": ["sync"],
"principal": "authenticated",
"effect": "allow",
"condition": [
"has_model_or_obj_perms:file.modify_repo_content",
"has_remote_param_model_or_obj_perms:file.view_fileremote",
]
}


.. _storing_access_policy_in_db:

Storing an Access Policy in the DB
----------------------------------

All access policies are stored in the database in the `pulpcore.plugin.models.AccessPolicy` model,
which stores the policy statements described above. Here is a look at the ``AccessPolicy`` model:

.. autoclass:: pulpcore.plugin.models.AccessPolicy
:members: viewset_name, statements, permissions_assignment

By storing these in the database they are readable to users with a GET to
``/pulp/api/v3/access_policies/``. Additionally users can PUT/PATCH modify them at
``/pulp/api/v3/access_policies/:uuid/``. Users cannot modify create or delete an Access Policy in
the database because only plugin writers create them and their viewset code expects a specific
AccessPolicy instance to exist.


.. _shipping_default_access_policy:

Shipping a Default Access Policy
--------------------------------

To ship an access policy, write a data migration that creates an ``AccessPolicy`` instance. Here's
an example of code to create an instance, which would be contained in a data migration.

.. code-block:: python

from pulpcore.plugin.models import AccessPolicy

FILE_REMOTE_STATEMENTS = [
{
"action": ["list"],
"principal": "authenticated",
"effect": "allow",
},
{
"action": ["create"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_perms:file.add_fileremote",
},
{
"action": ["retrieve"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_obj_perms:file.view_fileremote",
},
{
"action": ["update", "partial_update"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_obj_perms:file.change_fileremote",
},
{
"action": ["destroy"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_obj_perms:file.delete_fileremote",
},
]

FILE_REMOTE_PERMISSIONS_ASSIGNMENT = [
{
"function": "add_for_object_creator",
"parameters": None,
"permissions": [
"file.view_fileremote", "file.change_fileremote", "file.delete_fileremote"
]
}
]

AccessPolicy.objects.create(
viewset_name="FileRemoteViewSet",
statements=FILE_REMOTE_STATEMENTS,
permissions_assignment=FILE_REMOTE_PERMISSIONS_ASSIGNMENT
)
Copy link
Member

Choose a reason for hiding this comment

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

nice!


The actual ``AccessPolicy`` statement is created at the end. The other data structures store the
default policy. For en explanation of the ``permissions_assignment`` see the
:ref:`shipping_a_default_new_object_policy` documentation.


.. _handling_objects_created_prior_to_RBAC:

Handling Objects created prior to RBAC
--------------------------------------

Prior to RBAC being enabled, ``admin`` was the only user and they have ``is_superuser=True`` which
generally causes them to pass any permission check even without explicit permissions being assigned.


.. _viewset_enforcement:

Viewset Enforcement
-------------------

Protecting a viewset with your saved AccessPolicy is done by declaring a ``permission_classes``
class attribute on your ViewSet that points to ``pulpcore.plugin.access_policy.AccessPolicyFromDB``.
Copy link
Member

Choose a reason for hiding this comment

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

Since plugin writer's will no longer subclass the AccessPolicyFromDB, this might need an adjustment

Copy link
Member Author

Choose a reason for hiding this comment

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

this is up to date.


For example, here is the FileRemoteViewSet which enables authorization enforcement as follows:

.. code-block:: python

class FileRemoteViewSet(NamedModelViewSet):
...
permission_classes = (FileRemoteAccessPolicy,)
...