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

Adapted the web UI to work with the new secrets API

This commit is contained in:
Jeremy Stretch
2017-02-03 16:14:42 -05:00
parent a9fe39459a
commit 616ca4fe1f
4 changed files with 114 additions and 81 deletions

View File

@ -4,13 +4,16 @@ $(document).ready(function() {
$('button.unlock-secret').click(function (event) {
var secret_id = $(this).attr('secret-id');
// Retrieve from storage or prompt for private key
var private_key = sessionStorage.getItem('private_key');
if (!private_key) {
$('#privkey_modal').modal('show');
// 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);
// Otherwise, prompt the user for a private key so we can request a session key.
} else {
unlock_secret(secret_id, private_key);
console.log("No session key found. Prompt user for private key.");
$('#privkey_modal').modal('show');
}
});
// Locking a secret
@ -18,31 +21,72 @@ $(document).ready(function() {
var secret_id = $(this).attr('secret-id');
var secret_div = $('#secret_' + secret_id);
// Delete the plaintext
// Delete the plaintext from the DOM element.
secret_div.html('********');
$(this).hide();
$(this).siblings('button.unlock-secret').show();
});
// Adding/editing a secret
private_key_field = $('#id_private_key');
private_key_field.parents('form').submit(function(event) {
console.log("form submitted");
var private_key = sessionStorage.getItem('private_key');
if (private_key) {
private_key_field.val(private_key);
} else if ($('form .requires-private-key:first').val()) {
console.log("we need a key!");
$('#privkey_modal').modal('show');
return false;
}
// Retrieve a session key
$('#request_session_key').click(function() {
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);
});
// Saving a private RSA key locally
$('#submit_privkey').click(function() {
var private_key = $('#user_privkey').val();
sessionStorage.setItem('private_key', private_key);
});
// Retrieve a secret via the API
function unlock_secret(secret_id) {
$.ajax({
url: netbox_api_path + 'secrets/secrets/' + secret_id + '/',
type: 'GET',
dataType: 'json',
success: function (response, status) {
console.log("Secret retrieved successfully");
$('#secret_' + secret_id).html(response.plaintext);
$('button.unlock-secret[secret-id=' + secret_id + ']').hide();
$('button.lock-secret[secret-id=' + secret_id + ']').show();
},
error: function (xhr, ajaxOptions, thrownError) {
console.log("Error: " + xhr.responseText);
if (xhr.status == 403) {
alert("Permission denied");
} else {
var json = jQuery.parseJSON(xhr.responseText);
alert("Secret retrieval failed: " + json['error']);
}
}
});
}
// Request a session key via the API
function get_session_key(private_key) {
var csrf_token = $('input[name=csrfmiddlewaretoken]').val();
$.ajax({
url: netbox_api_path + 'secrets/get-session-key/',
type: 'POST',
data: {
private_key: private_key
},
dataType: 'json',
beforeSend: function(xhr, settings) {
xhr.setRequestHeader("X-CSRFToken", csrf_token);
},
success: function (response, status) {
console.log("Received a new session key; valid until " + response.expiration_time);
alert('Session key received! You may now unlock secrets.');
},
error: function (xhr, ajaxOptions, thrownError) {
if (xhr.status == 403) {
alert("Permission denied");
} else {
var json = jQuery.parseJSON(xhr.responseText);
alert("Failed to retrieve a session key: " + json['error']);
}
}
});
}
// Generate a new public/private key pair via the API
$('#generate_keypair').click(function() {
@ -63,41 +107,13 @@ $(document).ready(function() {
});
});
// Enter a newly generated public key
// Accept a new RSA key pair generated via the API
$('#use_new_pubkey').click(function() {
var new_pubkey = $('#new_pubkey');
if (new_pubkey.val()) {
$('#id_public_key').val(new_pubkey.val());
}
});
// Retrieve a secret via the API
function unlock_secret(secret_id, private_key) {
var csrf_token = $('input[name=csrfmiddlewaretoken]').val();
$.ajax({
url: netbox_api_path + 'secrets/secrets/' + secret_id + '/',
type: 'POST',
data: {
private_key: private_key
},
dataType: 'json',
beforeSend: function(xhr, settings) {
xhr.setRequestHeader("X-CSRFToken", csrf_token);
},
success: function (response, status) {
$('#secret_' + secret_id).html(response.plaintext);
$('button.unlock-secret[secret-id=' + secret_id + ']').hide();
$('button.lock-secret[secret-id=' + secret_id + ']').show();
},
error: function (xhr, ajaxOptions, thrownError) {
if (xhr.status == 403) {
alert("Permission denied");
} else {
var json = jQuery.parseJSON(xhr.responseText);
alert("Decryption failed: " + json['error']);
}
}
});
}
});

View File

@ -47,9 +47,8 @@ class SecretRoleForm(BootstrapMixin, forms.ModelForm):
#
class SecretForm(BootstrapMixin, forms.ModelForm):
private_key = forms.CharField(required=False, widget=forms.HiddenInput())
plaintext = forms.CharField(max_length=65535, required=False, label='Plaintext',
widget=forms.PasswordInput(attrs={'class': 'requires-private-key'}))
widget=forms.PasswordInput())
plaintext2 = forms.CharField(max_length=65535, required=False, label='Plaintext (verify)',
widget=forms.PasswordInput())
@ -59,9 +58,6 @@ class SecretForm(BootstrapMixin, forms.ModelForm):
def clean(self):
if self.cleaned_data['plaintext']:
validate_rsa_key(self.cleaned_data['private_key'])
if self.cleaned_data['plaintext'] != self.cleaned_data['plaintext2']:
raise forms.ValidationError({
'plaintext2': "The two given plaintext values do not match. Please check your input."
@ -86,8 +82,7 @@ class SecretFromCSVForm(forms.ModelForm):
class SecretImportForm(BootstrapMixin, BulkImportForm):
private_key = forms.CharField(widget=forms.HiddenInput())
csv = CSVDataField(csv_form=SecretFromCSVForm, widget=forms.Textarea(attrs={'class': 'requires-private-key'}))
csv = CSVDataField(csv_form=SecretFromCSVForm)
class SecretBulkEditForm(BootstrapMixin, BulkEditForm):

View File

@ -1,3 +1,5 @@
import base64
from django.contrib import messages
from django.contrib.auth.decorators import permission_required, login_required
from django.contrib.auth.mixins import PermissionRequiredMixin
@ -12,7 +14,7 @@ from utilities.views import BulkDeleteView, BulkEditView, ObjectDeleteView, Obje
from . import filters, forms, tables
from .decorators import userkey_required
from .models import SecretRole, Secret, UserKey
from .models import SecretRole, Secret, SessionKey, UserKey
#
@ -110,32 +112,44 @@ def secret_add(request, pk):
def secret_edit(request, pk):
secret = get_object_or_404(Secret, pk=pk)
uk = UserKey.objects.get(user=request.user)
if request.method == 'POST':
form = forms.SecretForm(request.POST, instance=secret)
if form.is_valid():
# Re-encrypt the Secret if a plaintext has been specified.
if form.cleaned_data['plaintext']:
# Re-encrypt the Secret if a plaintext and session key have been provided.
session_key = request.COOKIES.get('session_key', None)
if form.cleaned_data['plaintext'] and session_key is not None:
# Retrieve the master key from the current user's UserKey
master_key = uk.get_master_key(form.cleaned_data['private_key'])
if master_key is None:
form.add_error(None, "Invalid private key! Unable to encrypt secret data.")
# Retrieve the master key using the provided session key
session_key = base64.b64decode(session_key)
master_key = None
try:
sk = SessionKey.objects.get(user=request.user)
master_key = sk.get_master_key(session_key)
except SessionKey.DoesNotExist:
form.add_error(None, "No session key found for this user.")
# Create and encrypt the new Secret
else:
if master_key is not None:
secret = form.save(commit=False)
secret.plaintext = str(form.cleaned_data['plaintext'])
secret.encrypt(master_key)
secret.save()
messages.success(request, u"Modified secret {}.".format(secret))
return redirect('secrets:secret', pk=secret.pk)
else:
form.add_error(None, "Invalid session key. Unable to encrypt secret data.")
# We can't save the plaintext without a session key.
elif form.cleaned_data['plaintext']:
form.add_error(None, "No session key was provided with the request. Unable to encrypt secret data.")
# If no new plaintext was specified, a session key is not needed.
else:
secret = form.save()
messages.success(request, u"Modified secret {}.".format(secret))
return redirect('secrets:secret', pk=secret.pk)
messages.success(request, u"Modified secret {}.".format(secret))
return redirect('secrets:secret', pk=secret.pk)
else:
form = forms.SecretForm(instance=secret)
@ -157,19 +171,28 @@ class SecretDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@userkey_required()
def secret_import(request):
uk = UserKey.objects.get(user=request.user)
session_key = request.COOKIES.get('session_key', None)
if request.method == 'POST':
form = forms.SecretImportForm(request.POST)
if session_key is None:
form.add_error(None, "No session key was provided with the request. Unable to encrypt secret data.")
if form.is_valid():
new_secrets = []
# Retrieve the master key from the current user's UserKey
master_key = uk.get_master_key(form.cleaned_data['private_key'])
session_key = base64.b64decode(session_key)
master_key = None
try:
sk = SessionKey.objects.get(user=request.user)
master_key = sk.get_master_key(session_key)
except SessionKey.DoesNotExist:
form.add_error(None, "No session key found for this user.")
if master_key is None:
form.add_error(None, "Invalid private key! Unable to encrypt secret data.")
else:
try:
with transaction.atomic():

View File

@ -10,16 +10,15 @@
</div>
<div class="modal-body">
<p>
Your private RSA key is needed to complete this action. Once you've provided your key, it will
remain cached locally until you close this browser tab.
You do not have an active session key. To request one, please provide your private RSA key below.
Once retrieved, your session key will be saved for future requests.
</p>
<div class="form-group">
<textarea class="form-control" id="user_privkey" style="height: 300px;"></textarea>
</div>
<div class="form-group text-right">
<button id="submit_privkey" class="btn btn-primary unlock-secret" data-dismiss="modal">
<i class="fa fa-save" aria-hidden="True"></i>
Save RSA Key
<button id="request_session_key" class="btn btn-primary unlock-secret" data-dismiss="modal">
Request session key
</button>
</div>
</div>