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.
Authorization Conditions¶
Each policy statement can contain drf-access-policy conditions which is useful for verifying a user has one or more permissions. Pulp ships many built-in checks. See the 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. 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
permissions_assignment (JSONField) – A list of dictionaries identifying callables on the
pulpcore.plugin.access_policy.AccessPolicyFromDB
which add user or group permissions for newly created objects.statements (JSONField) – A list of
drf-access-policy
statements.viewset_name (models.CharField) – The name of the viewset this instance controls authorization for.
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 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.
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
)
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
Shipping a Default New Object Policy documentation.
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¶
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
.
For example, here is the FileRemoteViewSet which enables authorization enforcement as follows:
class FileRemoteViewSet(NamedModelViewSet):
...
permission_classes = (FileRemoteAccessPolicy,)
...