mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-21 11:37:21 -06:00
Introduced per-role decryption permissions
This commit is contained in:
parent
8a39f254ad
commit
2cb99c6012
@ -4,7 +4,7 @@ from django.shortcuts import get_object_or_404
|
|||||||
|
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.permissions import IsAuthenticated, BasePermission
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
@ -20,11 +20,6 @@ ERR_USERKEY_INACTIVE = "UserKey has not been activated for decryption."
|
|||||||
ERR_PRIVKEY_INVALID = "Invalid private key."
|
ERR_PRIVKEY_INVALID = "Invalid private key."
|
||||||
|
|
||||||
|
|
||||||
class SecretViewPermission(BasePermission):
|
|
||||||
def has_permission(self, request, view):
|
|
||||||
return request.user.has_perm('secrets.view_secret')
|
|
||||||
|
|
||||||
|
|
||||||
class SecretRoleListView(generics.ListAPIView):
|
class SecretRoleListView(generics.ListAPIView):
|
||||||
"""
|
"""
|
||||||
List all secret roles
|
List all secret roles
|
||||||
@ -47,10 +42,10 @@ class SecretListView(generics.GenericAPIView):
|
|||||||
|
|
||||||
e.g. curl -X GET http://netbox/api/secrets/secrets/ --data-binary "@/path/to/private_key"
|
e.g. curl -X GET http://netbox/api/secrets/secrets/ --data-binary "@/path/to/private_key"
|
||||||
"""
|
"""
|
||||||
queryset = Secret.objects.select_related('device__primary_ip', 'role')
|
queryset = Secret.objects.select_related('device__primary_ip', 'role')\
|
||||||
|
.prefetch_related('role__users', 'role__groups')
|
||||||
serializer_class = SecretSerializer
|
serializer_class = SecretSerializer
|
||||||
filter_class = SecretFilter
|
filter_class = SecretFilter
|
||||||
permission_classes = [SecretViewPermission]
|
|
||||||
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [FreeRADIUSClientsRenderer]
|
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [FreeRADIUSClientsRenderer]
|
||||||
|
|
||||||
def get(self, request, private_key=None, *args, **kwargs):
|
def get(self, request, private_key=None, *args, **kwargs):
|
||||||
@ -74,6 +69,7 @@ class SecretListView(generics.GenericAPIView):
|
|||||||
master_key = uk.get_master_key(private_key)
|
master_key = uk.get_master_key(private_key)
|
||||||
if master_key is not None:
|
if master_key is not None:
|
||||||
for s in queryset:
|
for s in queryset:
|
||||||
|
if s.decryptable_by(request.user):
|
||||||
s.decrypt(master_key)
|
s.decrypt(master_key)
|
||||||
else:
|
else:
|
||||||
return Response(
|
return Response(
|
||||||
@ -91,9 +87,9 @@ class SecretDetailView(generics.GenericAPIView):
|
|||||||
|
|
||||||
e.g. curl -X GET http://netbox/api/secrets/secrets/123/ --data-binary "@/path/to/private_key"
|
e.g. curl -X GET http://netbox/api/secrets/secrets/123/ --data-binary "@/path/to/private_key"
|
||||||
"""
|
"""
|
||||||
queryset = Secret.objects.select_related('device__primary_ip', 'role')
|
queryset = Secret.objects.select_related('device__primary_ip', 'role')\
|
||||||
|
.prefetch_related('role__users', 'role__groups')
|
||||||
serializer_class = SecretSerializer
|
serializer_class = SecretSerializer
|
||||||
permission_classes = [SecretViewPermission]
|
|
||||||
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [FreeRADIUSClientsRenderer]
|
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [FreeRADIUSClientsRenderer]
|
||||||
|
|
||||||
def get(self, request, pk, *args, **kwargs):
|
def get(self, request, pk, *args, **kwargs):
|
||||||
@ -114,14 +110,14 @@ class SecretDetailView(generics.GenericAPIView):
|
|||||||
{'error': ERR_USERKEY_INACTIVE},
|
{'error': ERR_USERKEY_INACTIVE},
|
||||||
status=status.HTTP_400_BAD_REQUEST
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
)
|
)
|
||||||
|
if secret.decryptable_by(request.user):
|
||||||
master_key = uk.get_master_key(private_key)
|
master_key = uk.get_master_key(private_key)
|
||||||
if master_key is not None:
|
if master_key is None:
|
||||||
secret.decrypt(master_key)
|
|
||||||
else:
|
|
||||||
return Response(
|
return Response(
|
||||||
{'error': ERR_PRIVKEY_INVALID},
|
{'error': ERR_PRIVKEY_INVALID},
|
||||||
status=status.HTTP_400_BAD_REQUEST
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
)
|
)
|
||||||
|
secret.decrypt(master_key)
|
||||||
|
|
||||||
serializer = self.get_serializer(secret)
|
serializer = self.get_serializer(secret)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
32
netbox/secrets/migrations/0004_auto_20160407_1548.py
Normal file
32
netbox/secrets/migrations/0004_auto_20160407_1548.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.1 on 2016-04-07 15:48
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auth', '0007_alter_validators_add_error_messages'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('secrets', '0003_auto_20160321_1524'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='secret',
|
||||||
|
options={'ordering': ['device', 'role', 'name']},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='secretrole',
|
||||||
|
name='groups',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='secretroles', to='auth.Group'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='secretrole',
|
||||||
|
name='users',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='secretroles', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
@ -4,7 +4,7 @@ from Crypto.PublicKey import RSA
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.hashers import make_password, check_password
|
from django.contrib.auth.hashers import make_password, check_password
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import Group, User
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@ -164,6 +164,8 @@ class SecretRole(models.Model):
|
|||||||
"""
|
"""
|
||||||
name = models.CharField(max_length=50, unique=True)
|
name = models.CharField(max_length=50, unique=True)
|
||||||
slug = models.SlugField(unique=True)
|
slug = models.SlugField(unique=True)
|
||||||
|
users = models.ManyToManyField(User, related_name='secretroles', blank=True)
|
||||||
|
groups = models.ManyToManyField(Group, related_name='secretroles', blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
@ -189,9 +191,6 @@ class Secret(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['device', 'role', 'name']
|
ordering = ['device', 'role', 'name']
|
||||||
permissions = (
|
|
||||||
('view_secret', "Can view secrets"),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.plaintext = kwargs.pop('plaintext', None)
|
self.plaintext = kwargs.pop('plaintext', None)
|
||||||
@ -279,3 +278,9 @@ class Secret(models.Model):
|
|||||||
if not self.hash:
|
if not self.hash:
|
||||||
raise Exception("Hash has not been generated for this secret.")
|
raise Exception("Hash has not been generated for this secret.")
|
||||||
return check_password(plaintext, self.hash, preferred=SecretValidationHasher())
|
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 user in self.role.users.all() or user.groups.filter(pk__in=self.role.groups.all()).exists()
|
||||||
|
Loading…
Reference in New Issue
Block a user