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 which uses policy statements described here.
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
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 only the objects that user can view.create
is allowed by any authenticated user with thefile.add_fileremote
permission.retrieve
(the detail view of an object) is allowed by an authenticated user who has thefile.view_fileremote
permission. Although users are allowed to perform an operation what they can list will still be restricted to only the objects that user can view.update
orpartial_update
is allowed by an authenticated user who has thefile.change_fileremote
permission.destroy
is allowed by any authenticated user with thefile.delete_fileremote
permission.
These names correspond with the default DRF viewset action names.
Custom ViewSet Actions¶
The action
part of a policy statement can reference any custom action your viewset has. 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 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:
- class pulpcore.plugin.models.AccessPolicy(*args, **kwargs)¶
A model storing a viewset authorization policy and permission assignment of new objects created.
- Fields
creation_hooks (models.JSONField) – A list of dictionaries identifying callables on the
pulpcore.plugin.access_policy.AccessPolicyFromDB
which can add user or group roles for newly created objects. This is a nullable field due to not all endpoints creating objects.statements (models.JSONField) – A list of
drf-access-policy
statements.viewset_name (models.CharField) – The name of the viewset this instance controls authorization for.
customized (BooleanField) – False if the AccessPolicy has been user-modified. True otherwise. Defaults to False.
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 a Default Access Policy¶
To ship a default access policy, define a dictionary named DEFAULT_ACCESS_POLICY
as a class
attribute on a subclass of NamedModelViewSet
containing all of statements
and
creation_hooks
. The AccessPolicy
instance will then be created in the pulp_migrate
signal handler. In the same way you might want to specify a LOCKED_ROLES
dictionary that will
define roles as lists of permissions to be used in the access policy.
Here’s an example of code to define a default policy:
class FileRemoteViewSet(RemoteViewSet):
<...>
DEFAULT_ACCESS_POLICY = {
"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",
},
],
"creation_hooks": [
{
"function": "add_roles_for_object_creator",
"parameters": {
"roles": "file.fileremote_owner",
},
},
],
}
LOCKED_ROLES = {
"file.fileremote_owner": [
"file.view_fileremote", "file.change_fileremote", "file.delete_fileremote"
],
"file.fileremote_viewer": ["file.view_fileremote"],
}
<...>
For an explanation of the creation_hooks
see the
Shipping a Default New Object Policy documentation.
The attribute LOCKED_ROLES
contains roles that are managed by the plugin author. Their name
needs to be prefixed by the plugins app_label
with a dot to prevent collisions. Roles defined
there will be replicated and updated in the database after every migration. They are also
marked locked=True
to prevent being modified by users. The primary purpose of these roles is to
allow plugin writers to refer to them in the default access policy.
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¶
Pulp configures the DEFAULT_PERMISSION_CLASSES
in the settings file to use
pulpcore.plugin.access_policy.AccessPolicyFromDB
by default. This ensures that by defining a
DEFAULT_ACCESS_POLICY
on your Viewset, Pulp will automatically save it to the database at
migration-time, and your Viewset will be protected without additional effort.
This strategy allows users to completely customize or disable the DRF Permission checks Pulp uses like any typical DRF project would.
Also like a typical DRF project, individual Viewsets or views can also be customized to use a
different Permission check by declaring the permission_classes
check. For example, here is the
StatusView
which disables permission checks entirely as follows:
class StatusView(APIView):
...
permission_classes = tuple()
...