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

Fixes #4146: Fix SecretRole permissions enforcement

This commit is contained in:
Jeremy Stretch
2020-02-12 11:13:32 -05:00
parent e4b910fe87
commit 5bf85597ed
5 changed files with 49 additions and 18 deletions

View File

@ -20,6 +20,7 @@
* [#4108](https://github.com/netbox-community/netbox/issues/4108) - Avoid extraneous database queries when rendering search forms * [#4108](https://github.com/netbox-community/netbox/issues/4108) - Avoid extraneous database queries when rendering search forms
* [#4134](https://github.com/netbox-community/netbox/issues/4134) - Device power ports and outlets should inherit type from the parent device type * [#4134](https://github.com/netbox-community/netbox/issues/4134) - Device power ports and outlets should inherit type from the parent device type
* [#4137](https://github.com/netbox-community/netbox/issues/4137) - Disable occupied terminations when connecting a cable to a circuit * [#4137](https://github.com/netbox-community/netbox/issues/4137) - Disable occupied terminations when connecting a cable to a circuit
* [#4146](https://github.com/netbox-community/netbox/issues/4146) - Fix enforcement of secret role assignment for secret decryption
--- ---

View File

@ -93,8 +93,8 @@ class SecretViewSet(ModelViewSet):
secret = self.get_object() secret = self.get_object()
# Attempt to decrypt the secret if the master key is known # Attempt to decrypt the secret if the user is permitted and the master key is known
if self.master_key is not None: if secret.decryptable_by(request.user) and self.master_key is not None:
secret.decrypt(self.master_key) secret.decrypt(self.master_key)
serializer = self.get_serializer(secret) serializer = self.get_serializer(secret)
@ -111,6 +111,8 @@ class SecretViewSet(ModelViewSet):
if self.master_key is not None: if self.master_key is not None:
secrets = [] secrets = []
for secret in page: for secret in page:
# Enforce role permissions
if secret.decryptable_by(request.user):
secret.decrypt(self.master_key) secret.decrypt(self.master_key)
secrets.append(secret) secrets.append(secret)
serializer = self.get_serializer(secrets, many=True) serializer = self.get_serializer(secrets, many=True)

View File

@ -5,7 +5,8 @@ from rest_framework import status
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
from secrets.models import Secret, SecretRole, SessionKey, UserKey from secrets.models import Secret, SecretRole, SessionKey, UserKey
from utilities.testing import APITestCase from users.models import Token
from utilities.testing import APITestCase, create_test_user
from .constants import PRIVATE_KEY, PUBLIC_KEY from .constants import PRIVATE_KEY, PUBLIC_KEY
@ -131,7 +132,15 @@ class SecretTest(APITestCase):
def setUp(self): def setUp(self):
super().setUp() # Create a non-superuser test user
self.user = create_test_user('testuser', permissions=(
'secrets.add_secret',
'secrets.change_secret',
'secrets.delete_secret',
'secrets.view_secret',
))
self.token = Token.objects.create(user=self.user)
self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(self.token.key)}
userkey = UserKey(user=self.user, public_key=PUBLIC_KEY) userkey = UserKey(user=self.user, public_key=PUBLIC_KEY)
userkey.save() userkey.save()
@ -144,11 +153,11 @@ class SecretTest(APITestCase):
'HTTP_X_SESSION_KEY': base64.b64encode(session_key.key), 'HTTP_X_SESSION_KEY': base64.b64encode(session_key.key),
} }
self.plaintext = { self.plaintexts = (
'secret1': 'Secret #1 Plaintext', 'Secret #1 Plaintext',
'secret2': 'Secret #2 Plaintext', 'Secret #2 Plaintext',
'secret3': 'Secret #3 Plaintext', 'Secret #3 Plaintext',
} )
site = Site.objects.create(name='Test Site 1', slug='test-site-1') site = Site.objects.create(name='Test Site 1', slug='test-site-1')
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1') manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
@ -160,17 +169,17 @@ class SecretTest(APITestCase):
self.secretrole1 = SecretRole.objects.create(name='Test Secret Role 1', slug='test-secret-role-1') self.secretrole1 = SecretRole.objects.create(name='Test Secret Role 1', slug='test-secret-role-1')
self.secretrole2 = SecretRole.objects.create(name='Test Secret Role 2', slug='test-secret-role-2') self.secretrole2 = SecretRole.objects.create(name='Test Secret Role 2', slug='test-secret-role-2')
self.secret1 = Secret( self.secret1 = Secret(
device=self.device, role=self.secretrole1, name='Test Secret 1', plaintext=self.plaintext['secret1'] device=self.device, role=self.secretrole1, name='Test Secret 1', plaintext=self.plaintexts[0]
) )
self.secret1.encrypt(self.master_key) self.secret1.encrypt(self.master_key)
self.secret1.save() self.secret1.save()
self.secret2 = Secret( self.secret2 = Secret(
device=self.device, role=self.secretrole1, name='Test Secret 2', plaintext=self.plaintext['secret2'] device=self.device, role=self.secretrole1, name='Test Secret 2', plaintext=self.plaintexts[1]
) )
self.secret2.encrypt(self.master_key) self.secret2.encrypt(self.master_key)
self.secret2.save() self.secret2.save()
self.secret3 = Secret( self.secret3 = Secret(
device=self.device, role=self.secretrole1, name='Test Secret 3', plaintext=self.plaintext['secret3'] device=self.device, role=self.secretrole1, name='Test Secret 3', plaintext=self.plaintexts[2]
) )
self.secret3.encrypt(self.master_key) self.secret3.encrypt(self.master_key)
self.secret3.save() self.secret3.save()
@ -178,16 +187,32 @@ class SecretTest(APITestCase):
def test_get_secret(self): def test_get_secret(self):
url = reverse('secrets-api:secret-detail', kwargs={'pk': self.secret1.pk}) url = reverse('secrets-api:secret-detail', kwargs={'pk': self.secret1.pk})
response = self.client.get(url, **self.header)
self.assertEqual(response.data['plaintext'], self.plaintext['secret1']) # Secret plaintext not be decrypted as the user has not been assigned to the role
response = self.client.get(url, **self.header)
self.assertIsNone(response.data['plaintext'])
# The plaintext should be present once the user has been assigned to the role
self.secretrole1.users.add(self.user)
response = self.client.get(url, **self.header)
self.assertEqual(response.data['plaintext'], self.plaintexts[0])
def test_list_secrets(self): def test_list_secrets(self):
url = reverse('secrets-api:secret-list') url = reverse('secrets-api:secret-list')
response = self.client.get(url, **self.header)
# Secret plaintext not be decrypted as the user has not been assigned to the role
response = self.client.get(url, **self.header)
self.assertEqual(response.data['count'], 3) self.assertEqual(response.data['count'], 3)
for secret in response.data['results']:
self.assertIsNone(secret['plaintext'])
# The plaintext should be present once the user has been assigned to the role
self.secretrole1.users.add(self.user)
response = self.client.get(url, **self.header)
self.assertEqual(response.data['count'], 3)
for i, secret in enumerate(response.data['results']):
self.assertEqual(secret['plaintext'], self.plaintexts[i])
def test_create_secret(self): def test_create_secret(self):

View File

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

View File

@ -21,11 +21,13 @@ def post_data(data):
return ret return ret
def create_test_user(username='testuser', permissions=list()): def create_test_user(username='testuser', permissions=None):
""" """
Create a User with the given permissions. Create a User with the given permissions.
""" """
user = User.objects.create_user(username=username) user = User.objects.create_user(username=username)
if permissions is None:
permissions = ()
for perm_name in permissions: for perm_name in permissions:
app, codename = perm_name.split('.') app, codename = perm_name.split('.')
perm = Permission.objects.get(content_type__app_label=app, codename=codename) perm = Permission.objects.get(content_type__app_label=app, codename=codename)