Decrypt and display VM generated password
Add optional Retrieve Password action on instance that allows users to decrypt, on client side, the password generated for the Admin session. Change-Id: I37cc93ae7871bf78e90513f58e169883fbfd1621 Implements: blueprint decrypt-and-display-vm-generated-password
This commit is contained in:
parent
015aff2630
commit
ee83ba3ac8
@ -216,6 +216,15 @@ icon names are based on the default icon theme provided by Bootstrap.
|
||||
|
||||
Example: ``[{'text': 'Official', 'tenant': '27d0058849da47c896d205e2fc25a5e8', 'icon': 'icon-ok'}]``
|
||||
|
||||
``OPENSTACK_ENABLE_PASSWORD_RETRIEVE``
|
||||
---------------------------
|
||||
|
||||
Default: ``"False"``
|
||||
|
||||
When set, enables the instance action "Retrieve password" allowing password retrieval
|
||||
from metadata service.
|
||||
|
||||
|
||||
``OPENSTACK_ENDPOINT_TYPE``
|
||||
---------------------------
|
||||
|
||||
|
@ -213,4 +213,62 @@ horizon.addInitFunction(function () {
|
||||
$(document).on('change', '.workflow #id_image_id', function (evt) {
|
||||
update_image_id_fields(this);
|
||||
});
|
||||
|
||||
horizon.instances.decrypt_password = function(encrypted_password, private_key) {
|
||||
var crypt = new JSEncrypt();
|
||||
crypt.setKey(private_key);
|
||||
return crypt.decrypt(encrypted_password);
|
||||
};
|
||||
|
||||
$(document).on('change', '#id_private_key_file', function (evt) {
|
||||
var file = evt.target.files[0];
|
||||
var reader = new FileReader();
|
||||
if (file) {
|
||||
reader.onloadend = function(event) {
|
||||
$("#id_private_key").val(event.target.result);
|
||||
};
|
||||
reader.onerror = function(event) {
|
||||
horizon.clearErrorMessages();
|
||||
horizon.alert('error', gettext('Could not read the file'));
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
else {
|
||||
horizon.clearErrorMessages();
|
||||
horizon.alert('error', gettext('Could not decrypt the password'));
|
||||
}
|
||||
});
|
||||
/*
|
||||
The font-family is changed because with the default policy the major I
|
||||
and minor the l cannot be distinguished.
|
||||
*/
|
||||
$(document).on('show', '#password_instance_modal', function (evt) {
|
||||
$("#id_decrypted_password").css("font-family","monospace");
|
||||
$("#id_decrypted_password").css("cursor","text");
|
||||
$("#id_encrypted_password").css("cursor","text");
|
||||
$("#id_keypair_name").css("cursor","text");
|
||||
});
|
||||
|
||||
$(document).on('click', '#decryptpassword_button', function (evt) {
|
||||
encrypted_password = $("#id_encrypted_password").val();
|
||||
private_key = $('#id_private_key').val();
|
||||
if (!private_key) {
|
||||
evt.preventDefault();
|
||||
$(this).closest('.modal').modal('hide');
|
||||
}
|
||||
else {
|
||||
if (private_key.length > 0) {
|
||||
evt.preventDefault();
|
||||
decrypted_password = horizon.instances.decrypt_password(encrypted_password, private_key);
|
||||
if (decrypted_password === false || decrypted_password === null) {
|
||||
horizon.clearErrorMessages();
|
||||
horizon.alert('error', gettext('Could not decrypt the password'));
|
||||
}
|
||||
else {
|
||||
$("#id_decrypted_password").val(decrypted_password);
|
||||
$("#decryptpassword_button").hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
99
horizon/static/horizon/lib/jsencrypt/LICENSE.txt
Normal file
99
horizon/static/horizon/lib/jsencrypt/LICENSE.txt
Normal file
@ -0,0 +1,99 @@
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 AllPlayers.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
CONTAINS CODE FROM YUI LIBRARY SEE LICENSE @ http://yuilibrary.com/license/
|
||||
|
||||
The 'jsrsasign'(RSA-Sign JavaScript Library) License
|
||||
|
||||
Copyright (c) 2010-2013 Kenji Urushima
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Licensing
|
||||
---------
|
||||
|
||||
This software is covered under the following copyright:
|
||||
|
||||
/*
|
||||
* Copyright (c) 2003-2005 Tom Wu
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
|
||||
* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*
|
||||
* IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
|
||||
* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
|
||||
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
|
||||
* THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
|
||||
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
* In addition, the following condition applies:
|
||||
*
|
||||
* All redistributions must retain an intact copy of this copyright notice
|
||||
* and disclaimer.
|
||||
*/
|
||||
|
||||
Address all questions regarding this license to:
|
||||
|
||||
Tom Wu
|
||||
tjw@cs.Stanford.EDU
|
||||
|
||||
ASN.1 JavaScript decoder
|
||||
Copyright (c) 2008-2013 Lapo Luchini <lapo@lapo.it>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
23
horizon/static/horizon/lib/jsencrypt/README.md
Normal file
23
horizon/static/horizon/lib/jsencrypt/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
JSEncrypt
|
||||
==============
|
||||
|
||||
This directory contains RSA Javascript encryption library.
|
||||
|
||||
Source code
|
||||
==============
|
||||
|
||||
The library source code can be found here:
|
||||
- https://github.com/travist/jsencrypt/
|
||||
|
||||
Version
|
||||
==============
|
||||
|
||||
The library version included here is based on github commit
|
||||
cc1109283b5f9944f77410e80e6789dc298827e1
|
||||
|
||||
Files
|
||||
==============
|
||||
|
||||
- README.md
|
||||
- LICENCE.txt
|
||||
- jsencrypt.js
|
4681
horizon/static/horizon/lib/jsencrypt/jsencrypt.js
Normal file
4681
horizon/static/horizon/lib/jsencrypt/jsencrypt.js
Normal file
File diff suppressed because it is too large
Load Diff
65
horizon/static/horizon/tests/instances.js
Normal file
65
horizon/static/horizon/tests/instances.js
Normal file
@ -0,0 +1,65 @@
|
||||
module("Instances (horizon.instances.js)");
|
||||
|
||||
test("decrypt password ", function () {
|
||||
var enc_password, private_key, password;
|
||||
enc_password = "dusPDCoY0u7PqDgVE6M+XicV+8V1qQkuPipM+KoCJ5cS" +
|
||||
"i8Bo64WOspsgjBQwC9onGX5pHwbgZdtintG1QNiDTafNbtNNbRoZQwO" +
|
||||
"4Zm3Liiw9ymDdiy1GNwMduFiRP9WG5N4QE3TP3ChnWnVGYQE/QoHqa/" +
|
||||
"7e43LXYvLULQA7tQ7JxhJruRZVt/tskPJGEbgpyjiA3gECjFi12BAKD" +
|
||||
"3RKF2dA+kMzv65ZeKi/ux/2cTQEu83hk1kgWihx2jl0+5rnWSOrl6WR" +
|
||||
"LXZhGaZgMRVKnKREkkTxfmLWtdY5lsWP4dnvHama+k9Ku8LQ+n4qB07" +
|
||||
"jFVAUmRkpbdDPJ9Nxtlep0g==";
|
||||
|
||||
private_key = "-----BEGIN RSA PRIVATE KEY-----" +
|
||||
"MIIEpAIBAAKCAQEAtY2Be8SoiE5XD/p7WaO2dKUES5iI4l4YAJ1FfpLGsT5mkC1t" +
|
||||
"7Zl0QTMVMdUNYH7ERIKNv8OSZ/wmm716iStiYPzwjyXUA8uVQuoprUr8hPOeNeHK" +
|
||||
"f1Nt7F87EPHk/n0VkLsUGZnwxVV1X3hgKS/f2gyPjkKwC+LOTMx81k65kp0a0Qt4" +
|
||||
"1HnjxrUYmuD+NhOtvzkR5slz4QFD5fiHCdw42IfkyM2az8aeLfg+4OxRJ1xA+6tD" +
|
||||
"oslI0IpurUzbdGOiE19m1OVjYazL2i007Y2mjviH7na7JlMH4Hfhtf5ZqXf8+XD/" +
|
||||
"Os1jbUT9//cbju2l2iHFqphiWm9QbHEnoB/2CQIDAQABAoIBAA2Yp1XJiIWMuGBt" +
|
||||
"9cbkx8k8gnHW3ol1Wn7RSF8ORusHLU8m19CvaVForfGpbvMHC1PGIy91SgWXkJyh" +
|
||||
"OAgFw7xXtPxDbPlLycXVG4Hq17ZtOC/41N1sNhM5nobKVsfoPjE0kXDJYoqkt8GK" +
|
||||
"lkj/WNhPkICq5dw+BA0kU0UJaERed0LoJ2/C35xnhyOap69Eeu/8jowQ5N/6zEBI" +
|
||||
"BmDp9BQSEuocxpDUK/CWErXQEBdLO1PLizvN0r6PDfaVsDMZt4s623We8130dg4D" +
|
||||
"WW9mBW0UgU7OSzWimj2iqdXWMA6dvKRokh7rnlyhT1VpG1z8CwhQ5kjLWHP1vuiJ" +
|
||||
"F2y2y3ECgYEA5nEO908ZSss6/gFoF0NAUhUJJ72EU2tl+MTMq7LzZ7e7GSlBBjeX" +
|
||||
"IG7q6EPa3/MFHUdDR7fy8GyCrCEEvlq+7RHItOEUPY2p5nvoFme5OcQT0EYYtwOb" +
|
||||
"bUOaT9nzUdqFyCOUGGc2arC5CivLMucAr1ZJYDBSy8HS6C7PKwlSw9cCgYEAybBX" +
|
||||
"xH+fo6kcCBNut0dQ1/1AeUFK62tmfuuJZZ4/JET9q3ut7WQXdScO1eOm7+HBMzHb" +
|
||||
"aXye7Eu7/y0pFwItBN2T17DtQzLzdMhk3HMUpIvIop7/0JsB4soXzsGLdAzRfR8I" +
|
||||
"KqklMk7R2TCTWna3wlYoK2M8jT5dP6VTil6P2R8CgYBeX/8ZGbPqBcFrNXhDzq8Q" +
|
||||
"7ryJIfyHjXx9nVuVFfzJhV2CuHqA6VNjXQmnheKlxQlbLExJmvRLsqTxibQ/oTqA" +
|
||||
"LMBeE7AOZW4njqdGRcR9++eBbLPCgB+vZ/hSq5gS9cPEa43DUMHgf+/IUpctiZ2m" +
|
||||
"MVhrpF7EQ+T0YfdGUNMskQKBgQCdiHx1Qc36Mhtv/2WqCC0QF4Jlc2dGTIQpPGX8" +
|
||||
"FkdxV+XfLGJkmppr6g7/Z6o7kdSq3RVo5mrnXBxCKw7+JrftJfjVLx+TLlfUbrXB" +
|
||||
"Lq3//CLBSnm7gWdOsdU4rBn1khGKrlNdpvIjwkbMYtGlhjbvtwX3JbLlC8If9U00" +
|
||||
"NbobtwKBgQCxp5+NmeU+NHXeG4wFLyT+hkZncapmV8QvlYmqMuEC6G2rjmplobgX" +
|
||||
"5DZi8zMWcWxq1j9GycJQUnFKMTMR8NMYiCstH/NDi3iiswYXTgeL2zuQy+XAQ8my" +
|
||||
"3ns5u8JfZ0JobJ5JxiKHS3UOqfe9DV2pvVSyF3nLl8I0WPMgoEXrLw==" +
|
||||
"-----END RSA PRIVATE KEY-----";
|
||||
|
||||
password = horizon.instances.decrypt_password(enc_password, private_key);
|
||||
ok(password === "kLhfIDlK5e7v12");
|
||||
});
|
||||
|
||||
test("decrypt password fake key", function () {
|
||||
var enc_password, private_key, password;
|
||||
enc_password = "dusPDCoY0u7PqDgVE6M+XicV+8V1qQkuPipM+KoCJ5cS" +
|
||||
"i8Bo64WOspsgjBQwC9onGX5pHwbgZdtintG1QNiDTafNbtNNbRoZQwO" +
|
||||
"4Zm3Liiw9ymDdiy1GNwMduFiRP9WG5N4QE3TP3ChnWnVGYQE/QoHqa/" +
|
||||
"7e43LXYvLULQA7tQ7JxhJruRZVt/tskPJGEbgpyjiA3gECjFi12BAKD" +
|
||||
"3RKF2dA+kMzv65ZeKi/ux/2cTQEu83hk1kgWihx2jl0+5rnWSOrl6WR" +
|
||||
"LXZhGaZgMRVKnKREkkTxfmLWtdY5lsWP4dnvHama+k9Ku8LQ+n4qB07" +
|
||||
"jFVAUmRkpbdDPJ9Nxtlep0g==";
|
||||
|
||||
private_key = "-----BEGIN RSA PRIVATE KEY-----" +
|
||||
"MIIEpAIBAAKCAQEAtY2Be8SoiE5XD/p7WaO2dKUES5iI4l4YAJ1FfpLGsT5mkC1t" +
|
||||
"Lq3//CLBSnm7gWdOsdU4rBn1khGKrlNdpvIjwkbMYtGlhjbvtwX3JbLlC8If9U00" +
|
||||
"NbobtwKBgQCxp5+NmeU+NHXeG4wFLyT+hkZncapmV8QvlYmqMuEC6G2rjmplobgX" +
|
||||
"5DZi8zMWcWxq1j9GycJQUnFKMTMR8NMYiCstH/NDi3iiswYXTgeL2zuQy+XAQ8my" +
|
||||
"3ns5u8JfZ0JobJ5JxiKHS3UOqfe9DV2pvVSyF3nLl8I0WPMgoEXrLw==" +
|
||||
"-----END RSA PRIVATE KEY-----";
|
||||
|
||||
password = horizon.instances.decrypt_password(enc_password, private_key);
|
||||
ok(password === false || password === null);
|
||||
});
|
@ -48,6 +48,7 @@
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.d3linechart.js' type='text/javascript' charset='utf-8'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.d3barchart.js' type='text/javascript' charset='utf-8'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.firewalls.js' type='text/javascript' charset='utf-8'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/lib/jsencrypt/jsencrypt.js' type='text/javascript' charset='utf-8'></script>
|
||||
|
||||
{% block custom_js_files %}{% endblock %}
|
||||
{% endcompress %}
|
||||
|
@ -12,6 +12,7 @@
|
||||
<script type="text/javascript" src="{{ STATIC_URL }}horizon/tests/modals.js"></script>
|
||||
<script type="text/javascript" src="{{ STATIC_URL }}horizon/tests/templates.js"></script>
|
||||
<script type="text/javascript" src="{{ STATIC_URL }}horizon/tests/tables.js"></script>
|
||||
<script type="text/javascript" src="{{ STATIC_URL }}horizon/tests/instances.js"></script>
|
||||
{% comment %}End test modules.{% endcomment %}
|
||||
|
||||
{% include "horizon/_scripts.html" %}
|
||||
|
@ -635,6 +635,10 @@ def get_x509_root_certificate(request):
|
||||
return novaclient(request).certs.get()
|
||||
|
||||
|
||||
def get_password(request, instance_id, private_key=None):
|
||||
return novaclient(request).servers.get_password(instance_id, private_key)
|
||||
|
||||
|
||||
def instance_volume_attach(request, volume_id, instance_id, device):
|
||||
return novaclient(request).volumes.create_server_volume(instance_id,
|
||||
volume_id,
|
||||
|
@ -100,3 +100,58 @@ class RebuildInstanceForm(forms.SelfHandlingForm):
|
||||
exceptions.handle(request, _("Unable to rebuild instance."),
|
||||
redirect=redirect)
|
||||
return True
|
||||
|
||||
|
||||
class DecryptPasswordInstanceForm(forms.SelfHandlingForm):
|
||||
instance_id = forms.CharField(widget=forms.HiddenInput())
|
||||
_keypair_name_label = _("Key Pair Name")
|
||||
_keypair_name_help = _("The Key Pair name that "
|
||||
"was associated with the instance")
|
||||
_attrs = {'readonly': 'readonly'}
|
||||
keypair_name = forms.CharField(widget=forms.widgets.TextInput(_attrs),
|
||||
label=_keypair_name_label,
|
||||
help_text=_keypair_name_help,
|
||||
required=False)
|
||||
_encrypted_pwd_help = _("The instance password encrypted "
|
||||
"with your public key.")
|
||||
encrypted_password = forms.CharField(widget=forms.widgets.Textarea(_attrs),
|
||||
label=_("Encrypted Password"),
|
||||
help_text=_encrypted_pwd_help,
|
||||
required=False)
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(DecryptPasswordInstanceForm, self).__init__(request,
|
||||
*args,
|
||||
**kwargs)
|
||||
instance_id = kwargs.get('initial', {}).get('instance_id')
|
||||
self.fields['instance_id'].initial = instance_id
|
||||
keypair_name = kwargs.get('initial', {}).get('keypair_name')
|
||||
self.fields['keypair_name'].initial = keypair_name
|
||||
try:
|
||||
result = api.nova.get_password(request, instance_id)
|
||||
if not result:
|
||||
_unavailable = _("Instance Password is not set"
|
||||
" or is not yet available")
|
||||
self.fields['encrypted_password'].initial = _unavailable
|
||||
else:
|
||||
self.fields['encrypted_password'].initial = result
|
||||
self.fields['private_key_file'] = forms.FileField(
|
||||
label=_('Private Key File'),
|
||||
widget=forms.FileInput(),
|
||||
required=True)
|
||||
self.fields['private_key'] = forms.CharField(
|
||||
widget=forms.widgets.Textarea(),
|
||||
label=_("OR Copy/Paste your Private Key"),
|
||||
required=True)
|
||||
_attrs = {'readonly': 'readonly'}
|
||||
self.fields['decrypted_password'] = forms.CharField(
|
||||
widget=forms.widgets.TextInput(_attrs),
|
||||
label=_("Password:"),
|
||||
required=False)
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:instances:index')
|
||||
_error = _("Unable to retrieve instance password.")
|
||||
exceptions.handle(request, _error, redirect=redirect)
|
||||
|
||||
def handle(self, request, data):
|
||||
return True
|
||||
|
@ -439,6 +439,29 @@ class RebuildInstance(tables.LinkAction):
|
||||
return urlresolvers.reverse(self.url, args=[instance_id])
|
||||
|
||||
|
||||
class DecryptInstancePassword(tables.LinkAction):
|
||||
name = "decryptpassword"
|
||||
verbose_name = _("Retrieve Password")
|
||||
classes = ("btn-decrypt", "ajax-modal")
|
||||
url = "horizon:project:instances:decryptpassword"
|
||||
|
||||
def allowed(self, request, instance):
|
||||
enable = getattr(settings,
|
||||
'OPENSTACK_ENABLE_PASSWORD_RETRIEVE',
|
||||
False)
|
||||
return (enable
|
||||
and (instance.status in ACTIVE_STATES
|
||||
or instance.status == 'SHUTOFF')
|
||||
and not is_deleting(instance)
|
||||
and get_keyname(instance) is not None)
|
||||
|
||||
def get_link_url(self, datum):
|
||||
instance_id = self.table.get_object_id(datum)
|
||||
keypair_name = get_keyname(datum)
|
||||
return urlresolvers.reverse(self.url, args=[instance_id,
|
||||
keypair_name])
|
||||
|
||||
|
||||
class AssociateIP(tables.LinkAction):
|
||||
name = "associate"
|
||||
verbose_name = _("Associate Floating IP")
|
||||
@ -741,7 +764,7 @@ class InstancesTable(tables.DataTable):
|
||||
row_actions = (StartInstance, ConfirmResize, RevertResize,
|
||||
CreateSnapshot, SimpleAssociateIP, AssociateIP,
|
||||
SimpleDisassociateIP, EditInstance,
|
||||
EditInstanceSecurityGroups, ConsoleLink, LogLink,
|
||||
TogglePause, ToggleSuspend, ResizeLink,
|
||||
SoftRebootInstance, RebootInstance, StopInstance,
|
||||
RebuildInstance, TerminateInstance)
|
||||
DecryptInstancePassword, EditInstanceSecurityGroups,
|
||||
ConsoleLink, LogLink, TogglePause, ToggleSuspend,
|
||||
ResizeLink, SoftRebootInstance, RebootInstance,
|
||||
StopInstance, RebuildInstance, TerminateInstance)
|
||||
|
@ -0,0 +1,35 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}password_instance_form{% endblock %}
|
||||
|
||||
{% block form_action %}{% url "horizon:project:instances:decryptpassword" instance_id keypair_name%}{% endblock %}
|
||||
|
||||
{% block modal_id %}password_instance_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Retrieve Instance Password" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description" %}:</h3>
|
||||
<p>{% trans "To decrypt your password you will need your key pair for this instance. Select your key pair file, or copy and paste the content of your private key file into the text area below, then click Decrypt Password."%}</p>
|
||||
<p><b>{% trans "Note: " %} </b> {% trans "The private key will be only used in your browser and will not be sent to the server" %}</p>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block modal-footer %}
|
||||
|
||||
{% for f in form %}
|
||||
{% if f.id_for_label|stringformat:"s" == "id_private_key" %}
|
||||
<input class="btn btn-primary pull-right" type="submit" id="decryptpassword_button" value="{% trans "Decrypt Password" %}" />
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<a href="{% url "horizon:project:instances:index" %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Instance Admin Password" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Retrieve Instance Password") %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include "project/instances/_decryptpassword.html" %}
|
||||
{% endblock %}
|
@ -41,7 +41,6 @@ from openstack_dashboard.dashboards.project.instances import tables
|
||||
from openstack_dashboard.dashboards.project.instances import tabs
|
||||
from openstack_dashboard.dashboards.project.instances import workflows
|
||||
|
||||
|
||||
INDEX_URL = reverse('horizon:project:instances:index')
|
||||
SEC_GROUP_ROLE_PREFIX = \
|
||||
workflows.update_instance.INSTANCE_SEC_GROUP_SLUG + "_role_"
|
||||
@ -893,6 +892,32 @@ class InstanceTests(test.TestCase):
|
||||
res = self.client.post(url, formData)
|
||||
self.assertRedirects(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.nova: ('get_password',)})
|
||||
def test_decrypt_instance_password(self):
|
||||
server = self.servers.first()
|
||||
enc_password = "azerty"
|
||||
api.nova.get_password(IsA(http.HttpRequest), server.id)\
|
||||
.AndReturn(enc_password)
|
||||
self.mox.ReplayAll()
|
||||
url = reverse('horizon:project:instances:decryptpassword',
|
||||
args=[server.id,
|
||||
server.key_name])
|
||||
res = self.client.get(url)
|
||||
self.assertTemplateUsed(res, 'project/instances/decryptpassword.html')
|
||||
|
||||
@test.create_stubs({api.nova: ('get_password',)})
|
||||
def test_decrypt_instance_get_exception(self):
|
||||
server = self.servers.first()
|
||||
keypair = self.keypairs.first()
|
||||
api.nova.get_password(IsA(http.HttpRequest), server.id)\
|
||||
.AndRaise(self.exceptions.nova)
|
||||
self.mox.ReplayAll()
|
||||
url = reverse('horizon:project:instances:decryptpassword',
|
||||
args=[server.id,
|
||||
keypair])
|
||||
res = self.client.get(url)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
instance_update_get_stubs = {
|
||||
api.nova: ('server_get',),
|
||||
api.network: ('security_group_list',
|
||||
|
@ -25,6 +25,7 @@ from openstack_dashboard.dashboards.project.instances import views
|
||||
|
||||
|
||||
INSTANCES = r'^(?P<instance_id>[^/]+)/%s$'
|
||||
INSTANCES_KEYPAIR = r'^(?P<instance_id>[^/]+)/(?P<keypair_name>[^/]+)/%s$'
|
||||
VIEW_MOD = 'openstack_dashboard.dashboards.project.instances.views'
|
||||
|
||||
|
||||
@ -39,4 +40,6 @@ urlpatterns = patterns(VIEW_MOD,
|
||||
url(INSTANCES % 'vnc', 'vnc', name='vnc'),
|
||||
url(INSTANCES % 'spice', 'spice', name='spice'),
|
||||
url(INSTANCES % 'resize', views.ResizeView.as_view(), name='resize'),
|
||||
url(INSTANCES_KEYPAIR % 'decryptpassword',
|
||||
views.DecryptPasswordView.as_view(), name='decryptpassword'),
|
||||
)
|
||||
|
@ -214,6 +214,22 @@ class RebuildView(forms.ModalFormView):
|
||||
return {'instance_id': self.kwargs['instance_id']}
|
||||
|
||||
|
||||
class DecryptPasswordView(forms.ModalFormView):
|
||||
form_class = project_forms.DecryptPasswordInstanceForm
|
||||
template_name = 'project/instances/decryptpassword.html'
|
||||
success_url = reverse_lazy('horizon:project:instances:index')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DecryptPasswordView, self).get_context_data(**kwargs)
|
||||
context['instance_id'] = self.kwargs['instance_id']
|
||||
context['keypair_name'] = self.kwargs['keypair_name']
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
return {'instance_id': self.kwargs['instance_id'],
|
||||
'keypair_name': self.kwargs['keypair_name']}
|
||||
|
||||
|
||||
class DetailView(tabs.TabView):
|
||||
tab_group_class = project_tabs.InstanceDetailTabs
|
||||
template_name = 'project/instances/detail.html'
|
||||
|
@ -150,6 +150,10 @@ OPENSTACK_KEYSTONE_BACKEND = {
|
||||
'can_edit_role': True
|
||||
}
|
||||
|
||||
#Setting this to True, will add a new "Retrieve Password" action on instance,
|
||||
#allowing Admin session password retrieval/decryption.
|
||||
#OPENSTACK_ENABLE_PASSWORD_RETRIEVE = False
|
||||
|
||||
# The Xen Hypervisor has the ability to set the mount point for volumes
|
||||
# attached to instances (other Hypervisors currently do not). Setting
|
||||
# can_set_mount_point to True will add the option to set the mount point
|
||||
|
Loading…
Reference in New Issue
Block a user