Drop EC2 credential download/recreation

These features have been available only when EC2 API is deployed.

The EC2 API project was retired multiple cycles ago, so these features
can no longer be used in recent versions of OpenStack.

Change-Id: I61fd129f03faac15dac121900aaa46f68fb5b2e2
Signed-off-by: Takashi Kajinami <kajinamit@oss.nttdata.com>
This commit is contained in:
Takashi Kajinami
2025-07-09 21:37:17 +09:00
parent 69dad6e6d6
commit 44d6691d50
11 changed files with 37 additions and 393 deletions

View File

@@ -840,35 +840,6 @@ def get_default_role(request):
return DEFAULT_ROLE
def ec2_manager(request):
client = keystoneclient(request)
if hasattr(client, 'ec2'):
return client.ec2
from keystoneclient.v3 import ec2
return ec2.EC2Manager(client)
@profiler.trace
def list_ec2_credentials(request, user_id):
return ec2_manager(request).list(user_id)
@profiler.trace
def create_ec2_credentials(request, user_id, tenant_id):
return ec2_manager(request).create(user_id, tenant_id)
@profiler.trace
def get_user_ec2_credentials(request, user_id, access_token):
return ec2_manager(request).get(user_id, access_token)
@profiler.trace
def delete_user_ec2_credentials(request, user_id, access_token):
return ec2_manager(request).delete(user_id, access_token)
def keystone_can_edit_domain():
can_edit_domain = setting_utils.get_dict_config(
'OPENSTACK_KEYSTONE_BACKEND', 'can_edit_domain')

View File

@@ -1,68 +0,0 @@
# Copyright 2016 NEC Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from openstack_dashboard import api
from openstack_dashboard import policy
def get_ec2_credentials(request):
if not policy.check((("identity", "identity:ec2_list_credentials"),),
request):
return None
project_id = request.user.project_id
all_keys = api.keystone.list_ec2_credentials(request,
request.user.id)
keys = [x for x in all_keys if x.tenant_id == project_id]
if not keys:
return None
return {'ec2_access_key': keys[0].access,
'ec2_secret_key': keys[0].secret}
class RecreateCredentials(forms.SelfHandlingForm):
def handle(self, request, context):
try:
credential = get_ec2_credentials(request)
if credential:
api.keystone.delete_user_ec2_credentials(
request,
request.user.id,
credential['ec2_access_key'])
except Exception:
exceptions.handle(
request, _('Unable to recreate ec2 credentials. '
'Failed to delete ec2 credentials.'))
return False
try:
api.keystone.create_ec2_credentials(
request,
request.user.id,
request.user.project_id)
message = _('Successfully recreated ec2 credentials.')
messages.success(request, message)
return True
except Exception:
exceptions.handle(
request, _('Unable to recreate ec2 credentials. '
'Failed to create ec2 credentials.'))
return False

View File

@@ -17,31 +17,17 @@ from django.template.defaultfilters import title
from django.utils.translation import gettext_lazy as _
from horizon import tables
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.api_access import forms
from openstack_dashboard import policy
def pretty_service_names(name):
name = name.replace('-', ' ')
if name in ['ec2', 's3']:
if name in ('s3',):
name = name.upper()
else:
name = title(name)
return name
class DownloadEC2(tables.LinkAction):
name = "download_ec2"
verbose_name = _("EC2 Credentials")
verbose_name_plural = _("EC2 Credentials")
icon = "download"
url = "horizon:project:api_access:ec2"
def allowed(self, request, datum=None):
return api.base.is_service_enabled(request, 'ec2')
class DownloadCloudsYaml(tables.LinkAction):
name = "download_clouds_yaml"
verbose_name = _("OpenStack clouds.yaml File")
@@ -72,29 +58,6 @@ class ViewCredentials(tables.LinkAction):
url = "horizon:project:api_access:view_credentials"
class RecreateCredentials(tables.LinkAction):
name = "recreate_credentials"
verbose_name = _("Recreate EC2 Credentials")
classes = ("ajax-modal",)
icon = "refresh"
url = "horizon:project:api_access:recreate_credentials"
policy_rules = (("compute", "os_compute_api:certificates:create"))
action_type = "danger"
def allowed(self, request, datum=None):
try:
target = {"target.credential.user_id": request.user.id}
if (api.base.is_service_enabled(request, 'ec2') and
forms.get_ec2_credentials(request) and
policy.check((("identity", "identity:ec2_create_credential"),
("identity", "identity:ec2_delete_credential")),
request, target=target)):
return True
except Exception:
pass
return False
class EndpointsTable(tables.DataTable):
api_name = tables.Column('type',
verbose_name=_("Service"),
@@ -106,8 +69,7 @@ class EndpointsTable(tables.DataTable):
name = "endpoints"
verbose_name = _("API Endpoints")
multi_select = False
table_actions = (ViewCredentials, RecreateCredentials)
table_actions = (ViewCredentials,)
table_actions_menu = (DownloadCloudsYaml,
DownloadOpenRC,
DownloadEC2)
DownloadOpenRC)
table_actions_menu_label = _('Download OpenStack RC File')

View File

@@ -4,57 +4,31 @@
{% block modal-body %}
<form>
{% if openrc_creds %}
<div class="{% if ec2_creds %}left{% endif %}">
<div class="form-group">
<label for="openrc-user">{% trans "User Name" %}</label>
<input type="text" class="form-control" id="openrc-user" readonly value="{{ openrc_creds.user.username }}">
</div>
<div class="form-group">
<label for="openrc-user">{% trans "User ID" %}</label>
<input type="text" class="form-control" id="openrc-userid" readonly value="{{ openrc_creds.user.id }}">
</div>
{% if "user_domain_name" in openrc_creds %}
<div class="form-group">
<label for="openrc-domain">{% trans "Domain Name" %}</label>
<input type="text" class="form-control" id="openrc-domain" readonly value="{{ openrc_creds.user_domain_name }}">
</div>
{% endif %}
<div class="form-group">
<label for="openrc-project-name">{% trans "Project Name" %}</label>
<input type="text" class="form-control" id="openrc-project-name" readonly value="{{ openrc_creds.tenant_name }}">
</div>
<div class="form-group">
<label for="openrc-project-id">{% trans "Project ID" %}</label>
<input type="text" class="form-control" id="openrc-project-id" readonly value="{{ openrc_creds.tenant_id }}">
</div>
<div class="form-group">
<label for="openrc-auth">{% trans "Authentication URL" %}</label>
<input type="text" id="openrc-auth" class="form-control" readonly value="{{ openrc_creds.auth_url }}">
</div>
<div class="form-group">
<label for="openrc-user">{% trans "User Name" %}</label>
<input type="text" class="form-control" id="openrc-user" readonly value="{{ openrc_creds.user.username }}">
</div>
{% endif %}
{% if ec2_creds %}
<div class="{% if openrc_creds %}right{% endif %}">
<div class="form-group">
<label for="ec2-url">{% trans "EC2 URL" %}</label>
<input type="text" id="ec2-url" class="form-control" readonly value="{{ ec2_creds.ec2_endpoint }}">
</div>
<div class="form-group">
<label for="s3-url">{% trans "S3 URL" %}</label>
<input type="text" id="s3-url" class="form-control" readonly value="{{ ec2_creds.s3_endpoint }}">
</div>
{% if ec2_creds.ec2_access_key %}
<div class="form-group">
<label for="ec2-access">{% trans "EC2 Access Key" %}</label>
<input type="text" id="ec2-access" class="form-control" readonly value="{{ ec2_creds.ec2_access_key }}">
</div>
{% endif %}
{% if ec2_creds.ec2_secret_key %}
<div class="form-group">
<label for="ec2-secret">{% trans "EC2 Secret Key" %}</label>
<input type="password" id="ec2-secret" class="form-control" readonly value="{{ ec2_creds.ec2_secret_key }}">
</div>
{% endif %}
<div class="form-group">
<label for="openrc-user">{% trans "User ID" %}</label>
<input type="text" class="form-control" id="openrc-userid" readonly value="{{ openrc_creds.user.id }}">
</div>
{% if "user_domain_name" in openrc_creds %}
<div class="form-group">
<label for="openrc-domain">{% trans "Domain Name" %}</label>
<input type="text" class="form-control" id="openrc-domain" readonly value="{{ openrc_creds.user_domain_name }}">
</div>
{% endif %}
<div class="form-group">
<label for="openrc-project-name">{% trans "Project Name" %}</label>
<input type="text" class="form-control" id="openrc-project-name" readonly value="{{ openrc_creds.tenant_name }}">
</div>
<div class="form-group">
<label for="openrc-project-id">{% trans "Project ID" %}</label>
<input type="text" class="form-control" id="openrc-project-id" readonly value="{{ openrc_creds.tenant_id }}">
</div>
<div class="form-group">
<label for="openrc-auth">{% trans "Authentication URL" %}</label>
<input type="text" id="openrc-auth" class="form-control" readonly value="{{ openrc_creds.auth_url }}">
</div>
{% endif %}
</form>

View File

@@ -1,15 +0,0 @@
#!/bin/bash
NOVARC=$(readlink -f "${BASH_SOURCE:-${0}}" 2>/dev/null) || NOVARC=$(python -c 'import os,sys; print os.path.abspath(os.path.realpath(sys.argv[1]))' "${BASH_SOURCE:-${0}}")
NOVA_KEY_DIR=${NOVARC%/*}
export EC2_ACCESS_KEY={{ ec2_access_key }}
export EC2_SECRET_KEY={{ ec2_secret_key }}
export EC2_URL={{ ec2_endpoint }}
export EC2_USER_ID=42 # nova does not use user id, but bundling requires it
export EC2_PRIVATE_KEY=${NOVA_KEY_DIR}/pk.pem
export EC2_CERT=${NOVA_KEY_DIR}/cert.pem
export NOVA_CERT=${NOVA_KEY_DIR}/cacert.pem
export EUCALYPTUS_CERT=${NOVA_CERT} # euca-bundle-image seems to require this set
{% if s3_endpoint %}export S3_URL={{ s3_endpoint }}{% endif %}
alias ec2-bundle-image="ec2-bundle-image --cert ${EC2_CERT} --privatekey ${EC2_PRIVATE_KEY} --user 42 --ec2cert ${NOVA_CERT}"
alias ec2-upload-bundle="ec2-upload-bundle -a ${EC2_ACCESS_KEY} -s ${EC2_SECRET_KEY} --url ${S3_URL} --ec2cert ${NOVA_CERT}"

View File

@@ -1,5 +0,0 @@
{% extends 'base.html' %}
{% block main %}
{% include 'project/api_access/_recreate_credentials.html' %}
{% endblock %}

View File

@@ -19,34 +19,16 @@ from django.template import loader
from django.test.utils import override_settings
from django.urls import reverse
from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
INDEX_URL = reverse('horizon:project:api_access:index')
API_URL = "horizon:project:api_access"
EC2_URL = reverse(API_URL + ":ec2")
OPENRC_URL = reverse(API_URL + ":openrc")
CREDS_URL = reverse(API_URL + ":view_credentials")
RECREATE_CREDS_URL = reverse(API_URL + ":recreate_credentials")
class APIAccessTests(test.TestCase):
@test.create_mocks({api.keystone: ('create_ec2_credentials',
'list_ec2_credentials')})
def test_ec2_download_view(self):
creds = self.ec2.first()
self.mock_list_ec2_credentials.return_value = []
self.mock_create_ec2_credentials.return_value = creds
res = self.client.get(EC2_URL)
self.assertEqual(res.status_code, 200)
self.assertEqual(res['content-type'], 'application/zip')
self.mock_list_ec2_credentials.assert_called_once_with(
test.IsHttpRequest(), self.user.id)
self.mock_create_ec2_credentials.assert_called_once_with(
test.IsHttpRequest(), self.user.id, self.tenant.id)
@override_settings(OPENSTACK_API_VERSIONS={"identity": 3})
def test_openrc_credentials(self):
@@ -62,59 +44,6 @@ class APIAccessTests(test.TestCase):
self.assertIn(p_id.encode('utf-8'), res.content)
self.assertIn(domain.encode('utf-8'), res.content)
@test.create_mocks({api.keystone: ('list_ec2_credentials',)})
def test_credential_api(self):
certs = self.ec2.list()
self.mock_list_ec2_credentials.return_value = certs
res = self.client.get(CREDS_URL)
self.assertEqual(res.status_code, 200)
credentials = 'project/api_access/credentials.html'
self.assertTemplateUsed(res, credentials)
self.assertEqual(self.user.id, res.context['openrc_creds']['user'].id)
self.assertEqual(certs[0].access,
res.context['ec2_creds']['ec2_access_key'])
self.mock_list_ec2_credentials.assert_called_once_with(
test.IsHttpRequest(), self.user.id)
@test.create_mocks({api.keystone: ('create_ec2_credentials',
'list_ec2_credentials',
'delete_user_ec2_credentials')})
def _test_recreate_user_credentials(self, exists_credentials=True):
old_creds = self.ec2.list() if exists_credentials else []
new_creds = self.ec2.first()
self.mock_list_ec2_credentials.return_value = old_creds
if exists_credentials:
self.mock_delete_user_ec2_credentials.return_value = []
self.mock_create_ec2_credentials.return_value = new_creds
res_get = self.client.get(RECREATE_CREDS_URL)
self.assertEqual(res_get.status_code, 200)
credentials = \
'project/api_access/recreate_credentials.html'
self.assertTemplateUsed(res_get, credentials)
res_post = self.client.post(RECREATE_CREDS_URL)
self.assertNoFormErrors(res_post)
self.assertRedirectsNoFollow(res_post, INDEX_URL)
self.mock_list_ec2_credentials.assert_called_once_with(
test.IsHttpRequest(), self.user.id)
if exists_credentials:
self.mock_delete_user_ec2_credentials.assert_called_once_with(
test.IsHttpRequest(), self.user.id, old_creds[0].access)
else:
self.mock_delete_user_ec2_credentials.assert_not_called()
self.mock_create_ec2_credentials.assert_called_once_with(
test.IsHttpRequest(), self.user.id, self.tenant.id)
def test_recreate_user_credentials(self):
self._test_recreate_user_credentials()
def test_recreate_user_credentials_with_no_existing_creds(self):
self._test_recreate_user_credentials(exists_credentials=False)
class ASCIITenantNameRCTests(test.TestCase):
TENANT_NAME = 'tenant'

View File

@@ -22,13 +22,9 @@ from openstack_dashboard.dashboards.project.api_access import views
urlpatterns = [
re_path(r'^$', views.IndexView.as_view(), name='index'),
re_path(r'^ec2/$', views.download_ec2_bundle, name='ec2'),
re_path(r'^clouds.yaml/$',
views.download_clouds_yaml_file, name='clouds.yaml'),
re_path(r'^openrc/$', views.download_rc_file, name='openrc'),
re_path(r'^view_credentials/$', views.CredentialsView.as_view(),
name='view_credentials'),
re_path(r'^recreate_ec2_credentials/$',
views.RecreateCredentialsView.as_view(),
name='recreate_credentials'),
]

View File

@@ -12,16 +12,12 @@
# License for the specific language governing permissions and limitations
# under the License.
from contextlib import closing
import logging
import tempfile
import zipfile
from django.conf import settings
from django import http
from django import shortcuts
from django.template.loader import render_to_string
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
@@ -31,44 +27,12 @@ from horizon import tables
from horizon import views
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.api_access \
import forms as api_access_forms
from openstack_dashboard.dashboards.project.api_access \
import tables as api_access_tables
LOG = logging.getLogger(__name__)
def _get_ec2_credentials(request):
tenant_id = request.user.tenant_id
all_keys = api.keystone.list_ec2_credentials(request,
request.user.id)
key = next((x for x in all_keys if x.tenant_id == tenant_id), None)
if not key:
key = api.keystone.create_ec2_credentials(request,
request.user.id,
tenant_id)
try:
s3_endpoint = api.base.url_for(request,
's3',
endpoint_type='publicURL')
except exceptions.ServiceCatalogException:
s3_endpoint = None
try:
ec2_endpoint = api.base.url_for(request,
'ec2',
endpoint_type='publicURL')
except exceptions.ServiceCatalogException:
ec2_endpoint = None
return {'ec2_access_key': key.access,
'ec2_secret_key': key.secret,
'ec2_endpoint': ec2_endpoint,
's3_endpoint': s3_endpoint}
def _get_openrc_credentials(request):
keystone_url = api.base.url_for(request,
'identity',
@@ -85,40 +49,6 @@ def _get_openrc_credentials(request):
}
# TODO(stephenfin): Migrate to CBV
def download_ec2_bundle(request):
tenant_name = request.user.tenant_name
# Gather or create our EC2 credentials
try:
context = _get_ec2_credentials(request)
except Exception:
exceptions.handle(request,
_('Unable to fetch EC2 credentials.'),
redirect=request.build_absolute_uri())
# Create our file bundle
template = 'project/api_access/ec2rc.sh.template'
try:
# pylint: disable-next=consider-using-with
temp_zip = tempfile.NamedTemporaryFile(delete=True)
with closing(zipfile.ZipFile(temp_zip.name, mode='w')) as archive:
archive.writestr('ec2rc.sh', render_to_string(template, context))
except Exception:
exceptions.handle(request,
_('Error writing zipfile: %(exc)s'),
redirect=request.build_absolute_uri())
# Send it back
response = http.HttpResponse(content_type='application/zip')
response.write(temp_zip.read())
response['Content-Disposition'] = ('attachment; '
'filename="%s-x509.zip"'
% tenant_name)
response['Content-Length'] = temp_zip.tell()
return response
# TODO(stephenfin): Migrate to CBV
def download_rc_file(request):
template = settings.OPENRC_CUSTOM_TEMPLATE
@@ -186,27 +116,9 @@ class CredentialsView(forms.ModalFormMixin, views.HorizonTemplateView):
except Exception:
exceptions.handle(self.request,
_('Unable to get openrc credentials'))
if api.base.is_service_enabled(self.request, 'ec2'):
try:
context['ec2_creds'] = _get_ec2_credentials(self.request)
except Exception:
exceptions.handle(self.request,
_('Unable to get EC2 credentials'))
return context
class RecreateCredentialsView(forms.ModalFormView):
form_class = api_access_forms.RecreateCredentials
form_id = "recreate_credentials"
page_title = _("Recreate EC2 Credentials")
template_name = \
'project/api_access/recreate_credentials.html'
submit_label = _("Recreate EC2 Credentials")
submit_url = reverse_lazy(
"horizon:project:api_access:recreate_credentials")
success_url = reverse_lazy('horizon:project:api_access:index')
class IndexView(tables.DataTableView):
table_class = api_access_tables.EndpointsTable
page_title = _("API Access")

View File

@@ -19,7 +19,6 @@ from django.conf import settings
from django.utils import datetime_safe
from keystoneclient import access
from keystoneclient.v2_0 import ec2
from keystoneclient.v2_0 import roles
from keystoneclient.v2_0 import tenants
from keystoneclient.v2_0 import users
@@ -145,21 +144,7 @@ SERVICE_CATALOG = [
{"region": "RegionOne",
"interface": "public",
"url": "http://public.neutron.example.com:9696/"}
]},
{"type": "ec2",
"name": "EC2 Service",
"endpoints_links": [],
"endpoints": [
{"region": "RegionOne",
"interface": "admin",
"url": "http://admin.nova.example.com:8773/services/Admin"},
{"region": "RegionOne",
"interface": "public",
"url": "http://public.nova.example.com:8773/services/Cloud"},
{"region": "RegionOne",
"interface": "internal",
"url": "http://int.nova.example.com:8773/services/Cloud"}
]},
]}
]
@@ -175,7 +160,6 @@ def data(TEST):
TEST.tenants = utils.TestDataContainer()
TEST.role_assignments = utils.TestDataContainer()
TEST.roles = utils.TestDataContainer()
TEST.ec2 = utils.TestDataContainer()
TEST.identity_providers = utils.TestDataContainer()
TEST.idp_mappings = utils.TestDataContainer()
@@ -439,11 +423,6 @@ def data(TEST):
TEST.tokens.scoped_token = scoped_token
TEST.tokens.unscoped_token = unscoped_token
access_secret = ec2.EC2(ec2.CredentialsManager, {"access": "access",
"secret": "secret",
"tenant_id": tenant.id})
TEST.ec2.add(access_secret)
idp_dict_1 = {'id': 'idp_1',
'description': 'identity provider 1',
'enabled': True,

View File

@@ -0,0 +1,9 @@
---
upgrade:
- |
The following features are no longer supported. These have been disabled
unless EC2-API service is deployed, but EC2-API project was already
retired.
- Download EC2 credentials
- Recreate EC2 credentials