Add support for keystone access rules
Keystone implemented the ability to apply fine-grained access control restrictions to application credentials[1]. This patch adds new fields to the application credential creation form and detail view so that horizon users can use this feature. [1] http://specs.openstack.org/openstack/keystone-specs/specs/keystone/train/capabilities-app-creds.html Depends-on: https://review.opendev.org/677585 Change-Id: I2d71392eb8569ffb8cb15af29eea76e120a158cc
This commit is contained in:
parent
307e884eaa
commit
4d1786c687
@ -96,7 +96,7 @@ pyScss==1.3.4
|
||||
python-cinderclient==4.0.1
|
||||
python-dateutil==2.5.3
|
||||
python-glanceclient==2.8.0
|
||||
python-keystoneclient==3.15.0
|
||||
python-keystoneclient==3.22.0
|
||||
python-memcached==1.59
|
||||
python-mimeparse==1.6.0
|
||||
python-neutronclient==6.7.0
|
||||
|
@ -1003,12 +1003,14 @@ def application_credential_delete(request, application_credential_id):
|
||||
@profiler.trace
|
||||
def application_credential_create(request, name, secret=None,
|
||||
description=None, expires_at=None,
|
||||
roles=None, unrestricted=False):
|
||||
roles=None, unrestricted=False,
|
||||
access_rules=None):
|
||||
user = request.user.id
|
||||
manager = keystoneclient(request).application_credentials
|
||||
try:
|
||||
return manager.create(name=name, user=user, secret=secret,
|
||||
description=description, expires_at=expires_at,
|
||||
roles=roles, unrestricted=unrestricted)
|
||||
roles=roles, unrestricted=unrestricted,
|
||||
access_rules=access_rules)
|
||||
except keystone_exceptions.Conflict:
|
||||
raise exceptions.Conflict()
|
||||
|
@ -19,6 +19,7 @@ from django.conf import settings
|
||||
from django.forms import widgets
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.decorators.debug import sensitive_variables
|
||||
import yaml
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
@ -49,6 +50,10 @@ class CreateApplicationCredentialForm(forms.SelfHandlingForm):
|
||||
widget=forms.widgets.SelectMultiple(),
|
||||
label=_("Roles"),
|
||||
required=False)
|
||||
access_rules = forms.CharField(
|
||||
widget=forms.Textarea(attrs={'rows': 5}),
|
||||
label=_("Access Rules"),
|
||||
required=False)
|
||||
unrestricted = forms.BooleanField(label=_("Unrestricted (dangerous)"),
|
||||
required=False)
|
||||
kubernetes_namespace = forms.CharField(max_length=255,
|
||||
@ -64,6 +69,9 @@ class CreateApplicationCredentialForm(forms.SelfHandlingForm):
|
||||
role_names = [role['name'] for role in role_list]
|
||||
role_choices = ((name, name) for name in role_names)
|
||||
self.fields['roles'].choices = role_choices
|
||||
keystone_version = api.keystone.get_identity_api_version(request)
|
||||
if keystone_version < (3, 13):
|
||||
del self.fields['access_rules']
|
||||
if not settings.KUBECONFIG_ENABLED:
|
||||
self.fields['kubernetes_namespace'].widget = widgets.HiddenInput()
|
||||
|
||||
@ -95,6 +103,10 @@ class CreateApplicationCredentialForm(forms.SelfHandlingForm):
|
||||
roles = [{'name': role_name} for role_name in data['roles']]
|
||||
else:
|
||||
roles = None
|
||||
if data.get('access_rules'):
|
||||
access_rules = data['access_rules']
|
||||
else:
|
||||
access_rules = None
|
||||
new_app_cred = api.keystone.application_credential_create(
|
||||
request,
|
||||
name=data['name'],
|
||||
@ -102,6 +114,7 @@ class CreateApplicationCredentialForm(forms.SelfHandlingForm):
|
||||
secret=data['secret'] or None,
|
||||
expires_at=expiration or None,
|
||||
roles=roles,
|
||||
access_rules=access_rules,
|
||||
unrestricted=data['unrestricted']
|
||||
)
|
||||
self.request.session['application_credential'] = \
|
||||
@ -118,6 +131,16 @@ class CreateApplicationCredentialForm(forms.SelfHandlingForm):
|
||||
exceptions.handle(
|
||||
request, _('Unable to create application credential: %s') % ex)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(CreateApplicationCredentialForm, self).clean()
|
||||
try:
|
||||
cleaned_data['access_rules'] = yaml.safe_load(
|
||||
cleaned_data['access_rules'])
|
||||
except yaml.YAMLError:
|
||||
msg = (_('Access rules must be a valid JSON or YAML list.'))
|
||||
raise forms.ValidationError(msg)
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class CreateSuccessfulForm(forms.SelfHandlingForm):
|
||||
app_cred_id = forms.CharField(
|
||||
|
@ -12,6 +12,7 @@
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
<b>Secret</b>:
|
||||
You may provide your own secret, or one will be generated for you. Once your
|
||||
application credential is created, the secret will be revealed once. If you
|
||||
lose the secret, you will have to generate a new application credential.
|
||||
@ -19,6 +20,7 @@
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
<b>Expiration Date/Time</b>:
|
||||
You may give the application credential an expiration. The expiration will
|
||||
be in UTC. If you provide an expiration date with no expiration time, the
|
||||
time will be assumed to be 00:00:00. If you provide an expiration time with
|
||||
@ -27,6 +29,7 @@
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
<b>Roles</b>:
|
||||
You may select one or more roles for this application credential. If you do
|
||||
not select any, all of the roles you have assigned on the current project
|
||||
will be applied to the application credential.
|
||||
@ -34,6 +37,38 @@
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
<b>Access Rules</b>:
|
||||
If you want more fine-grained access control delegation, you can create one
|
||||
or more access rules for this application credential. The list of access
|
||||
rules must be a JSON- or YAML-formatted list of rules each containing a service type,
|
||||
an HTTP method, and a URL path, for example:
|
||||
<br />
|
||||
<code>
|
||||
[
|
||||
<br />
|
||||
{"service": "compute",
|
||||
<br />
|
||||
"method": "POST",
|
||||
<br />
|
||||
"path": "/v2.1/servers"}
|
||||
<br />
|
||||
]
|
||||
<br />
|
||||
</code>
|
||||
or:
|
||||
<br />
|
||||
<code>
|
||||
- service: compute
|
||||
<br />
|
||||
method: POST
|
||||
<br />
|
||||
path: /v2.1/servers
|
||||
</code>
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
<b>Unrestricted</b>:
|
||||
By default, for security reasons, application credentials are forbidden from
|
||||
being used for creating additional application credentials or keystone
|
||||
trusts. If your application credential needs to be able to perform these
|
||||
@ -43,6 +78,7 @@
|
||||
<p>
|
||||
{% if kubeconfig_enabled %}
|
||||
{% blocktrans trimmed %}
|
||||
<b>Kubernetes Namespace</b>:
|
||||
You can optionally provide a Kubernetes Namespace. It will be included in the
|
||||
kubeconfig file which can be downloaded from the next screen.
|
||||
{% endblocktrans %}
|
||||
|
@ -26,10 +26,36 @@
|
||||
<td class="word-wrap">{{ role.name }}</td>
|
||||
<td>{{ role.id }}</td>
|
||||
<td>{{ role.domain_id | default:_("-") }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</dd>
|
||||
<dt>{% trans "Access Rules" %}</dt>
|
||||
{% if application_credential.access_rules %}
|
||||
<dd>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><strong>{% trans "Service" %}</strong></th>
|
||||
<th><strong>{% trans "Method" %}</strong></th>
|
||||
<th><strong>{% trans "Path" %}</strong></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for rule in application_credential.access_rules %}
|
||||
<tr>
|
||||
<td>{{ rule.service }}</td>
|
||||
<td>{{ rule.method }}</td>
|
||||
<td>{{ rule.path }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</dd>
|
||||
{% else %}
|
||||
<dd>{{ _("-")}}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Expires" %}</dt>
|
||||
<dd>{{ application_credential.expires_at | default:_("-") }}</dd>
|
||||
<dt>{% trans "Unrestricted" %}</dt>
|
||||
|
@ -25,7 +25,9 @@ APP_CREDS_INDEX_URL = reverse('horizon:identity:application_credentials:index')
|
||||
|
||||
|
||||
class ApplicationCredentialViewTests(test.TestCase):
|
||||
def test_application_credential_create_get(self):
|
||||
@mock.patch.object(api.keystone, 'get_identity_api_version')
|
||||
def test_application_credential_create_get(self, mock_identity_version):
|
||||
mock_identity_version.return_value = (3, 13)
|
||||
url = reverse('horizon:identity:application_credentials:create')
|
||||
res = self.client.get(url)
|
||||
|
||||
@ -33,11 +35,14 @@ class ApplicationCredentialViewTests(test.TestCase):
|
||||
'identity/application_credentials/create.html')
|
||||
|
||||
@mock.patch.object(api.keystone, 'application_credential_create')
|
||||
@mock.patch.object(api.keystone, 'get_identity_api_version')
|
||||
@mock.patch.object(api.keystone, 'application_credential_list')
|
||||
def test_application_credential_create(self, mock_app_cred_list,
|
||||
mock_identity_version,
|
||||
mock_app_cred_create):
|
||||
new_app_cred = self.application_credentials.first()
|
||||
mock_app_cred_create.return_value = new_app_cred
|
||||
mock_identity_version.return_value = (3, 13)
|
||||
data = {
|
||||
'name': new_app_cred.name,
|
||||
'description': new_app_cred.description
|
||||
@ -47,6 +52,7 @@ class ApplicationCredentialViewTests(test.TestCase):
|
||||
'description': new_app_cred.description,
|
||||
'expires_at': new_app_cred.expires_at,
|
||||
'roles': None,
|
||||
'access_rules': None,
|
||||
'unrestricted': False,
|
||||
'secret': None
|
||||
}
|
||||
@ -91,12 +97,15 @@ class ApplicationCredentialViewTests(test.TestCase):
|
||||
six.text_type(app_cred.id))
|
||||
|
||||
@mock.patch.object(api.keystone, 'application_credential_create')
|
||||
@mock.patch.object(api.keystone, 'get_identity_api_version')
|
||||
@mock.patch.object(api.keystone, 'application_credential_list')
|
||||
def test_application_credential_openrc(self, mock_app_cred_list,
|
||||
mock_identity_version,
|
||||
mock_app_cred_create):
|
||||
|
||||
new_app_cred = self.application_credentials.first()
|
||||
mock_app_cred_create.return_value = new_app_cred
|
||||
mock_identity_version.return_value = (3, 13)
|
||||
data = {
|
||||
'name': new_app_cred.name,
|
||||
'description': new_app_cred.description
|
||||
@ -114,12 +123,15 @@ class ApplicationCredentialViewTests(test.TestCase):
|
||||
res, 'identity/application_credentials/openrc.sh.template')
|
||||
|
||||
@mock.patch.object(api.keystone, 'application_credential_create')
|
||||
@mock.patch.object(api.keystone, 'get_identity_api_version')
|
||||
@mock.patch.object(api.keystone, 'application_credential_list')
|
||||
def test_application_credential_cloudsyaml(self, mock_app_cred_list,
|
||||
mock_identity_version,
|
||||
mock_app_cred_create):
|
||||
|
||||
new_app_cred = self.application_credentials.first()
|
||||
mock_app_cred_create.return_value = new_app_cred
|
||||
mock_identity_version.return_value = (3, 13)
|
||||
data = {
|
||||
'name': new_app_cred.name,
|
||||
'description': new_app_cred.description
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds support for access rules for application credentials. Fine-grained
|
||||
restrictions can now be applied to application credentials by supplying a
|
||||
list of access rules upon creation. See the `keystone documentation
|
||||
<https://docs.openstack.org/api-ref/identity/v3/#application-credentials>`_
|
||||
for more information.
|
@ -33,7 +33,7 @@ pymongo!=3.1,>=3.0.2 # Apache-2.0
|
||||
pyScss!=1.3.5,>=1.3.4 # MIT License
|
||||
python-cinderclient>=4.0.1 # Apache-2.0
|
||||
python-glanceclient>=2.8.0 # Apache-2.0
|
||||
python-keystoneclient>=3.15.0 # Apache-2.0
|
||||
python-keystoneclient>=3.22.0 # Apache-2.0
|
||||
python-neutronclient>=6.7.0 # Apache-2.0
|
||||
python-novaclient>=9.1.0 # Apache-2.0
|
||||
python-swiftclient>=3.2.0 # Apache-2.0
|
||||
|
Loading…
Reference in New Issue
Block a user