1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

#4969: Remove user and group assignment from SecretRole

This commit is contained in:
Jeremy Stretch
2020-08-07 16:19:18 -04:00
parent aca3ca9d65
commit e6bc55af85
13 changed files with 93 additions and 117 deletions

View File

@ -7,5 +7,3 @@ Each secret is assigned a functional role which indicates what it is used for. S
* RADIUS/TACACS+ keys
* IKE key strings
* Routing protocol shared secrets
Roles are also used to control access to secrets. Each role is assigned an arbitrary number of groups and/or users. Only the users associated with a role have permission to decrypt the secrets assigned to that role. (A superuser has permission to decrypt all secrets, provided they have an active user key.)

View File

@ -38,7 +38,7 @@ class SecretRoleViewSet(ModelViewSet):
class SecretViewSet(ModelViewSet):
queryset = Secret.objects.prefetch_related(
'device__primary_ip4', 'device__primary_ip6', 'role', 'role__users', 'role__groups', 'tags',
'device__primary_ip4', 'device__primary_ip6', 'role', 'tags',
)
serializer_class = serializers.SecretSerializer
filterset_class = filters.SecretFilterSet
@ -84,8 +84,8 @@ class SecretViewSet(ModelViewSet):
secret = self.get_object()
# Attempt to decrypt the secret if the user is permitted and the master key is known
if secret.decryptable_by(request.user) and self.master_key is not None:
# Attempt to decrypt the secret if the master key is known
if self.master_key is not None:
secret.decrypt(self.master_key)
serializer = self.get_serializer(secret)
@ -102,8 +102,6 @@ class SecretViewSet(ModelViewSet):
if self.master_key is not None:
secrets = []
for secret in page:
# Enforce role permissions
if secret.decryptable_by(request.user):
secret.decrypt(self.master_key)
secrets.append(secret)
serializer = self.get_serializer(secrets, many=True)

View File

@ -46,13 +46,7 @@ class SecretRoleForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = SecretRole
fields = [
'name', 'slug', 'description', 'users', 'groups',
]
widgets = {
'users': StaticSelect2Multiple(),
'groups': StaticSelect2Multiple(),
}
fields = ('name', 'slug', 'description')
class SecretRoleCSVForm(CSVModelForm):

View File

@ -0,0 +1,20 @@
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('secrets', '0008_standardize_description'),
('users', '0009_replicate_permissions'),
]
operations = [
migrations.RemoveField(
model_name='secretrole',
name='groups',
),
migrations.RemoveField(
model_name='secretrole',
name='users',
),
]

View File

@ -239,9 +239,6 @@ class SecretRole(ChangeLoggedModel):
"""
A SecretRole represents an arbitrary functional classification of Secrets. For example, a user might define roles
such as "Login Credentials" or "SNMP Communities."
By default, only superusers will have access to decrypt Secrets. To allow other users to decrypt Secrets, grant them
access to the appropriate SecretRoles either individually or by group.
"""
name = models.CharField(
max_length=50,
@ -254,16 +251,6 @@ class SecretRole(ChangeLoggedModel):
max_length=200,
blank=True,
)
users = models.ManyToManyField(
to=User,
related_name='secretroles',
blank=True
)
groups = models.ManyToManyField(
to=Group,
related_name='secretroles',
blank=True
)
objects = RestrictedQuerySet.as_manager()
@ -285,14 +272,6 @@ class SecretRole(ChangeLoggedModel):
self.description,
)
def has_member(self, user):
"""
Check whether the given user has belongs to this SecretRole. Note that superusers belong to all roles.
"""
if user.is_superuser:
return True
return user in self.users.all() or user.groups.filter(pk__in=self.groups.all()).exists()
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
class Secret(ChangeLoggedModel, CustomFieldModel):
@ -453,9 +432,3 @@ class Secret(ChangeLoggedModel, CustomFieldModel):
if not self.hash:
raise Exception("Hash has not been generated for this secret.")
return check_password(plaintext, self.hash, preferred=SecretValidationHasher())
def decryptable_by(self, user):
"""
Check whether the given user has permission to decrypt this Secret.
"""
return self.role.has_member(user)

View File

@ -18,7 +18,7 @@ class SecretRoleTable(BaseTable):
class Meta(BaseTable.Meta):
model = SecretRole
fields = ('pk', 'name', 'secret_count', 'description', 'slug', 'users', 'groups', 'actions')
fields = ('pk', 'name', 'secret_count', 'description', 'slug', 'actions')
default_columns = ('pk', 'name', 'secret_count', 'description', 'actions')

View File

@ -1,12 +0,0 @@
from django import template
register = template.Library()
@register.filter()
def decryptable_by(secret, user):
"""
Determine whether a given User is permitted to decrypt a Secret.
"""
return secret.decryptable_by(user)

View File

@ -25,8 +25,6 @@ class SecretRoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
'name': 'Secret Role X',
'slug': 'secret-role-x',
'description': 'A secret role',
'users': [],
'groups': [],
}
cls.csv_data = (

View File

@ -1,10 +1,8 @@
{% load secret_helpers %}
<tr>
<td><a href="{% url 'secrets:secret' pk=secret.pk %}">{{ secret.role }}</a></td>
<td>{{ secret.name }}</td>
<td id="secret_{{ secret.pk }}">********</td>
<td class="text-right noprint">
{% if secret|decryptable_by:request.user %}
<button class="btn btn-xs btn-success unlock-secret" secret-id="{{ secret.pk }}">
<i class="fa fa-lock"></i> Unlock
</button>
@ -14,10 +12,5 @@
<button class="btn btn-xs btn-danger lock-secret collapse" secret-id="{{ secret.pk }}">
<i class="fa fa-unlock-alt"></i> Lock
</button>
{% else %}
<button class="btn btn-xs btn-default" disabled="disabled" title="Permission denied">
<i class="fa fa-lock"></i> Unlock
</button>
{% endif %}
</td>
</tr>

View File

@ -2,7 +2,6 @@
{% load buttons %}
{% load custom_links %}
{% load helpers %}
{% load secret_helpers %}
{% load static %}
{% load plugins %}
@ -70,7 +69,6 @@
{% plugin_left_page secret %}
</div>
<div class="col-md-6">
{% if secret|decryptable_by:request.user %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Secret Data</strong>
@ -96,12 +94,6 @@
</div>
</div>
</div>
{% else %}
<div class="alert alert-warning">
<i class="fa fa-warning" aria-hidden="true"></i>
You do not have permission to decrypt this secret.
</div>
{% endif %}
{% include 'extras/inc/tags_panel.html' with tags=secret.tags.all url='secrets:secret_list' %}
{% plugin_right_page secret %}
</div>

View File

@ -1,7 +1,6 @@
{% extends 'base.html' %}
{% load static %}
{% load form_helpers %}
{% load secret_helpers %}
{% block content %}
<form action="." method="post" class="form form-horizontal">
@ -30,7 +29,7 @@
<div class="panel panel-default">
<div class="panel-heading"><strong>Secret Data</strong></div>
<div class="panel-body">
{% if obj.pk and obj|decryptable_by:request.user %}
{% if obj.pk %}
<div class="form-group">
<label class="col-md-3 control-label required">Current Plaintext</label>
<div class="col-md-7">

View File

@ -1,5 +1,5 @@
from django.db import migrations
from django.db.models import Q
ACTIONS = ['view', 'add', 'change', 'delete']
@ -10,6 +10,7 @@ def replicate_permissions(apps, schema_editor):
"""
Permission = apps.get_model('auth', 'Permission')
ObjectPermission = apps.get_model('users', 'ObjectPermission')
SecretRole = apps.get_model('secrets', 'SecretRole')
# TODO: Optimize this iteration so that ObjectPermissions with identical sets of users and groups
# are combined into a single ObjectPermission instance.
@ -24,6 +25,27 @@ def replicate_permissions(apps, schema_editor):
action = perm.codename
if perm.group_set.exists() or perm.user_set.exists():
# Handle replication of SecretRole user/group assignments for Secrets
if perm.codename == 'view_secret':
for secretrole in SecretRole.objects.prefetch_related('users', 'groups'):
obj_perm = ObjectPermission(
name=f'{perm.content_type.app_label}.{perm.codename} ({secretrole.name})'[:100],
actions=[action],
constraints={'role__name': secretrole.name}
)
obj_perm.save()
obj_perm.object_types.add(perm.content_type)
# Assign only users/groups who both a) are assigned to the SecretRole and b) have the view_secret
# permission
obj_perm.groups.add(
*list(secretrole.groups.filter(permissions=perm))
)
obj_perm.users.add(*list(secretrole.users.filter(
Q(user_permissions=perm) | Q(groups__permissions=perm)
)))
else:
obj_perm = ObjectPermission(
# Copy name from original Permission object
name=f'{perm.content_type.app_label}.{perm.codename}'[:100],
@ -31,6 +53,7 @@ def replicate_permissions(apps, schema_editor):
)
obj_perm.save()
obj_perm.object_types.add(perm.content_type)
if perm.group_set.exists():
obj_perm.groups.add(*list(perm.group_set.all()))
if perm.user_set.exists():