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:
Ala Rezmerita 2013-12-10 09:05:00 +01:00
parent 015aff2630
commit ee83ba3ac8
17 changed files with 5118 additions and 5 deletions

View File

@ -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``
---------------------------

View File

@ -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();
}
}
}
});
});

View 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.

View 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

File diff suppressed because it is too large Load Diff

View 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);
});

View File

@ -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 %}

View File

@ -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" %}

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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',

View File

@ -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'),
)

View File

@ -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'

View File

@ -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