mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Secrets UI work
This commit is contained in:
@ -1,38 +1,20 @@
|
|||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
// Unlocking a secret
|
// Unlocking a secret
|
||||||
$('button.unlock-secret').click(function (event) {
|
$('button.unlock-secret').click(function() {
|
||||||
var secret_id = $(this).attr('secret-id');
|
var secret_id = $(this).attr('secret-id');
|
||||||
|
|
||||||
// If we have an active cookie containing a session key, send the API request.
|
|
||||||
if (document.cookie.indexOf('session_key') > 0) {
|
|
||||||
console.log("Retrieving secret...");
|
|
||||||
unlock_secret(secret_id);
|
unlock_secret(secret_id);
|
||||||
// Otherwise, prompt the user for a private key so we can request a session key.
|
|
||||||
} else {
|
|
||||||
console.log("No session key found. Prompt user for private key.");
|
|
||||||
$('#privkey_modal').modal('show');
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Locking a secret
|
// Locking a secret
|
||||||
$('button.lock-secret').click(function (event) {
|
$('button.lock-secret').click(function() {
|
||||||
var secret_id = $(this).attr('secret-id');
|
var secret_id = $(this).attr('secret-id');
|
||||||
var secret_div = $('#secret_' + secret_id);
|
lock_secret(secret_id);
|
||||||
|
|
||||||
// Delete the plaintext from the DOM element.
|
|
||||||
secret_div.html('********');
|
|
||||||
$(this).hide();
|
|
||||||
$(this).siblings('button.unlock-secret').show();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Retrieve a session key
|
// Retrieve a session key
|
||||||
$('#request_session_key').click(function() {
|
$('#request_session_key').click(function() {
|
||||||
var private_key = $('#user_privkey').val();
|
var private_key = $('#user_privkey').val();
|
||||||
|
|
||||||
// POST the user's private key to request a temporary session key.
|
|
||||||
console.log("Requesting a session key...");
|
|
||||||
get_session_key(private_key);
|
get_session_key(private_key);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -43,23 +25,35 @@ $(document).ready(function() {
|
|||||||
type: 'GET',
|
type: 'GET',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function (response, status) {
|
success: function (response, status) {
|
||||||
|
if (response.plaintext) {
|
||||||
console.log("Secret retrieved successfully");
|
console.log("Secret retrieved successfully");
|
||||||
$('#secret_' + secret_id).html(response.plaintext);
|
$('#secret_' + secret_id).html(response.plaintext);
|
||||||
$('button.unlock-secret[secret-id=' + secret_id + ']').hide();
|
$('button.unlock-secret[secret-id=' + secret_id + ']').hide();
|
||||||
$('button.lock-secret[secret-id=' + secret_id + ']').show();
|
$('button.lock-secret[secret-id=' + secret_id + ']').show();
|
||||||
|
} else {
|
||||||
|
console.log("Secret was not decrypted. Prompt user for private key.");
|
||||||
|
$('#privkey_modal').modal('show');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
error: function (xhr, ajaxOptions, thrownError) {
|
error: function (xhr, ajaxOptions, thrownError) {
|
||||||
console.log("Error: " + xhr.responseText);
|
console.log("Error: " + xhr.responseText);
|
||||||
if (xhr.status == 403) {
|
if (xhr.status == 403) {
|
||||||
alert("Permission denied");
|
alert("Permission denied");
|
||||||
} else {
|
} else {
|
||||||
var json = jQuery.parseJSON(xhr.responseText);
|
alert(xhr.responseText);
|
||||||
alert("Secret retrieval failed: " + json['error']);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove secret data from the DOM
|
||||||
|
function lock_secret(secret_id) {
|
||||||
|
var secret_div = $('#secret_' + secret_id);
|
||||||
|
secret_div.html('********');
|
||||||
|
$('button.lock-secret[secret-id=' + secret_id + ']').hide();
|
||||||
|
$('button.unlock-secret[secret-id=' + secret_id + ']').show();
|
||||||
|
}
|
||||||
|
|
||||||
// Request a session key via the API
|
// Request a session key via the API
|
||||||
function get_session_key(private_key) {
|
function get_session_key(private_key) {
|
||||||
var csrf_token = $('input[name=csrfmiddlewaretoken]').val();
|
var csrf_token = $('input[name=csrfmiddlewaretoken]').val();
|
||||||
@ -74,7 +68,7 @@ $(document).ready(function() {
|
|||||||
xhr.setRequestHeader("X-CSRFToken", csrf_token);
|
xhr.setRequestHeader("X-CSRFToken", csrf_token);
|
||||||
},
|
},
|
||||||
success: function (response, status) {
|
success: function (response, status) {
|
||||||
console.log("Received a new session key; valid until " + response.expiration_time);
|
console.log("Received a new session key");
|
||||||
alert('Session key received! You may now unlock secrets.');
|
alert('Session key received! You may now unlock secrets.');
|
||||||
},
|
},
|
||||||
error: function (xhr, ajaxOptions, thrownError) {
|
error: function (xhr, ajaxOptions, thrownError) {
|
||||||
|
@ -11,6 +11,7 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.viewsets import ViewSet, ModelViewSet
|
from rest_framework.viewsets import ViewSet, ModelViewSet
|
||||||
|
|
||||||
from extras.api.renderers import FormlessBrowsableAPIRenderer, FreeRADIUSClientsRenderer
|
from extras.api.renderers import FormlessBrowsableAPIRenderer, FreeRADIUSClientsRenderer
|
||||||
|
from secrets.exceptions import InvalidSessionKey
|
||||||
from secrets.filters import SecretFilter
|
from secrets.filters import SecretFilter
|
||||||
from secrets.models import Secret, SecretRole, SessionKey, UserKey
|
from secrets.models import Secret, SecretRole, SessionKey, UserKey
|
||||||
from utilities.api import WritableSerializerMixin
|
from utilities.api import WritableSerializerMixin
|
||||||
@ -53,42 +54,50 @@ class SecretViewSet(WritableSerializerMixin, ModelViewSet):
|
|||||||
authentication_classes = [BasicAuthentication, SessionAuthentication]
|
authentication_classes = [BasicAuthentication, SessionAuthentication]
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
def _get_master_key(self, request):
|
def _read_session_key(self, request):
|
||||||
|
|
||||||
# Check for a session key provided as a cookie or header
|
# Check for a session key provided as a cookie or header
|
||||||
if 'session_key' in request.COOKIES:
|
if 'session_key' in request.COOKIES:
|
||||||
session_key = base64.b64decode(request.COOKIES['session_key'])
|
return base64.b64decode(request.COOKIES['session_key'])
|
||||||
elif 'HTTP_X_SESSION_KEY' in request.META:
|
elif 'HTTP_X_SESSION_KEY' in request.META:
|
||||||
session_key = base64.b64decode(request.META['HTTP_X_SESSION_KEY'])
|
return base64.b64decode(request.META['HTTP_X_SESSION_KEY'])
|
||||||
else:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Retrieve session key cipher (if any) for the current user
|
|
||||||
try:
|
|
||||||
sk = SessionKey.objects.get(user=request.user)
|
|
||||||
except SessionKey.DoesNotExist:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Recover master key
|
|
||||||
# TODO: Exception handling
|
|
||||||
master_key = sk.get_master_key(session_key)
|
|
||||||
|
|
||||||
return master_key
|
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
master_key = self._get_master_key(request)
|
|
||||||
secret = self.get_object()
|
|
||||||
|
|
||||||
if master_key is not None:
|
secret = self.get_object()
|
||||||
|
session_key = self._read_session_key(request)
|
||||||
|
|
||||||
|
# Retrieve session key cipher (if any) for the current user
|
||||||
|
if session_key is not None:
|
||||||
|
try:
|
||||||
|
sk = SessionKey.objects.get(user=request.user)
|
||||||
|
master_key = sk.get_master_key(session_key)
|
||||||
secret.decrypt(master_key)
|
secret.decrypt(master_key)
|
||||||
|
except SessionKey.DoesNotExist:
|
||||||
|
return HttpResponseBadRequest("No active session key for current user.")
|
||||||
|
except InvalidSessionKey:
|
||||||
|
return HttpResponseBadRequest("Invalid session key.")
|
||||||
|
|
||||||
serializer = self.get_serializer(secret)
|
serializer = self.get_serializer(secret)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
def list(self, request, *args, **kwargs):
|
def list(self, request, *args, **kwargs):
|
||||||
master_key = self._get_master_key(request)
|
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
|
|
||||||
|
# Attempt to retrieve the master key for decryption
|
||||||
|
session_key = self._read_session_key(request)
|
||||||
|
master_key = None
|
||||||
|
if session_key is not None:
|
||||||
|
try:
|
||||||
|
sk = SessionKey.objects.get(user=request.user)
|
||||||
|
master_key = sk.get_master_key(session_key)
|
||||||
|
except SessionKey.DoesNotExist:
|
||||||
|
return HttpResponseBadRequest("No active session key for current user.")
|
||||||
|
except InvalidSessionKey:
|
||||||
|
return HttpResponseBadRequest("Invalid session key.")
|
||||||
|
|
||||||
# Pagination
|
# Pagination
|
||||||
page = self.paginate_queryset(queryset)
|
page = self.paginate_queryset(queryset)
|
||||||
if page is not None:
|
if page is not None:
|
||||||
|
5
netbox/secrets/exceptions.py
Normal file
5
netbox/secrets/exceptions.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
class InvalidSessionKey(Exception):
|
||||||
|
"""
|
||||||
|
Raised when the a provided session key is invalid.
|
||||||
|
"""
|
||||||
|
pass
|
@ -13,6 +13,7 @@ from django.utils.encoding import force_bytes, python_2_unicode_compatible
|
|||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from utilities.models import CreatedUpdatedModel
|
from utilities.models import CreatedUpdatedModel
|
||||||
|
|
||||||
|
from .exceptions import InvalidSessionKey
|
||||||
from .hashers import SecretValidationHasher
|
from .hashers import SecretValidationHasher
|
||||||
|
|
||||||
|
|
||||||
@ -220,7 +221,7 @@ class SessionKey(models.Model):
|
|||||||
|
|
||||||
# Validate the provided session key
|
# Validate the provided session key
|
||||||
if not check_password(session_key, self.hash):
|
if not check_password(session_key, self.hash):
|
||||||
raise Exception("Invalid session key")
|
raise InvalidSessionKey()
|
||||||
|
|
||||||
# Decrypt master key using provided session key
|
# Decrypt master key using provided session key
|
||||||
master_key = xor_keys(session_key, self.cipher)
|
master_key = xor_keys(session_key, self.cipher)
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<textarea class="form-control" id="user_privkey" style="height: 300px;"></textarea>
|
<textarea class="form-control" id="user_privkey" style="height: 300px;"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group text-right">
|
<div class="form-group text-right">
|
||||||
<button id="request_session_key" class="btn btn-primary unlock-secret" data-dismiss="modal">
|
<button id="request_session_key" class="btn btn-primary" data-dismiss="modal">
|
||||||
Request session key
|
Request session key
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user