diff options
author | Zuul <zuul@review.opendev.org> | 2019-11-25 09:25:56 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2019-11-25 09:25:56 +0000 |
commit | f592481cc05abe0f061969a6cbeccf0989cf8b67 (patch) | |
tree | 5027b8d8b6f1fe36c15165d2d3d7da447b590897 | |
parent | 338a58fba695e736acce47fa7a0622244f2c46ac (diff) | |
parent | 4d1786c68724c91c2686cda89540fa91b9c21bcf (diff) | |
download | horizon-f592481cc05abe0f061969a6cbeccf0989cf8b67.tar.gz |
Merge "Add support for keystone access rules"
8 files changed, 112 insertions, 5 deletions
diff --git a/lower-constraints.txt b/lower-constraints.txt index cd7c01b7f..b8a6b9031 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -95,7 +95,7 @@ pyScss==1.3.4 python-cinderclient==5.0.0 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 diff --git a/openstack_dashboard/api/keystone.py b/openstack_dashboard/api/keystone.py index cbf2da107..fcb425f6c 100644 --- a/openstack_dashboard/api/keystone.py +++ b/openstack_dashboard/api/keystone.py @@ -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() diff --git a/openstack_dashboard/dashboards/identity/application_credentials/forms.py b/openstack_dashboard/dashboards/identity/application_credentials/forms.py index c261c6c31..e9f418b25 100644 --- a/openstack_dashboard/dashboards/identity/application_credentials/forms.py +++ b/openstack_dashboard/dashboards/identity/application_credentials/forms.py @@ -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( diff --git a/openstack_dashboard/dashboards/identity/application_credentials/templates/application_credentials/_create.html b/openstack_dashboard/dashboards/identity/application_credentials/templates/application_credentials/_create.html index e190825a1..4af9dd754 100644 --- a/openstack_dashboard/dashboards/identity/application_credentials/templates/application_credentials/_create.html +++ b/openstack_dashboard/dashboards/identity/application_credentials/templates/application_credentials/_create.html @@ -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 %} diff --git a/openstack_dashboard/dashboards/identity/application_credentials/templates/application_credentials/_detail_overview.html b/openstack_dashboard/dashboards/identity/application_credentials/templates/application_credentials/_detail_overview.html index 9c0f65ae0..c2dcb9000 100644 --- a/openstack_dashboard/dashboards/identity/application_credentials/templates/application_credentials/_detail_overview.html +++ b/openstack_dashboard/dashboards/identity/application_credentials/templates/application_credentials/_detail_overview.html @@ -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> diff --git a/openstack_dashboard/dashboards/identity/application_credentials/tests.py b/openstack_dashboard/dashboards/identity/application_credentials/tests.py index a184b03d8..d3b849b4e 100644 --- a/openstack_dashboard/dashboards/identity/application_credentials/tests.py +++ b/openstack_dashboard/dashboards/identity/application_credentials/tests.py @@ -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 diff --git a/releasenotes/notes/keystone-access-rules-35d00bd9cfe5d32e.yaml b/releasenotes/notes/keystone-access-rules-35d00bd9cfe5d32e.yaml new file mode 100644 index 000000000..28e7f0801 --- /dev/null +++ b/releasenotes/notes/keystone-access-rules-35d00bd9cfe5d32e.yaml @@ -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. diff --git a/requirements.txt b/requirements.txt index fa084b896..b537ad682 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,7 +33,7 @@ pymongo!=3.1,>=3.0.2 # Apache-2.0 pyScss!=1.3.5,>=1.3.4 # MIT License python-cinderclient>=5.0.0 # 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 |