From f381f4dd3cd751d42519b4e69e31391356744abc Mon Sep 17 00:00:00 2001 From: Claudio Pisa Date: Wed, 29 May 2019 12:04:16 +0000 Subject: [PATCH] Downloadable Kubernetes configuration file Kubernetes can use OpenStack application credentials for authentication. Generate a kubeconfig file and make it available to download along with the openrc and clouds.yaml files. blueprint kubernetes-config-gen Change-Id: I298370e7abf4f0d480bd5199060f24bab6d6daaa --- doc/source/configuration/settings.rst | 52 ++++++++++++++++++- .../identity/application_credentials/forms.py | 10 ++++ .../application_credentials/_create.html | 8 +++ .../application_credentials/_success.html | 6 +++ .../kubeconfig.template | 26 ++++++++++ .../identity/application_credentials/urls.py | 2 + .../identity/application_credentials/views.py | 36 ++++++++++--- openstack_dashboard/defaults.py | 8 +++ ...ubernetes-config-gen-bcebcbd8f9fb9991.yaml | 8 +++ 9 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 openstack_dashboard/dashboards/identity/application_credentials/templates/application_credentials/kubeconfig.template create mode 100644 releasenotes/notes/bp-kubernetes-config-gen-bcebcbd8f9fb9991.yaml diff --git a/doc/source/configuration/settings.rst b/doc/source/configuration/settings.rst index d8906e738f..a9b2c97fc9 100644 --- a/doc/source/configuration/settings.rst +++ b/doc/source/configuration/settings.rst @@ -7,7 +7,7 @@ Settings Reference Introduction ============ -Horizon's settings broadly fall into three categories: +Horizon's settings broadly fall into four categories: * `General Settings`_: this includes visual settings like the modal backdrop style, bug url and theme configuration, as well as settings that affect every @@ -21,6 +21,8 @@ Horizon's settings broadly fall into three categories: should read the `Django settings documentation `_ to see the other options available to you. +* `Other Settings`_: settings which do not fall into any of the above + categories. To modify your settings, you have two options: @@ -2521,3 +2523,51 @@ Default: Absolute paths for `horizon/locale`, `openstack_auth/locale` and Django uses relative paths by default so it causes localization issues depending on your runtime settings. To avoid this we recommend to use absolute paths for directories with locales. + +Other Settings +============== + +KUBECONFIG_ENABLED +------------------ + +.. versionadded:: TBD + +Default: ``False`` + +Kubernetes clusters can use Keystone as an external identity provider. +Horizon can generate a ``kubeconfig`` file from the application credentials +control panel which can be used for authenticating with a Kubernetes cluster. +This setting enables this behavior. + +.. seealso:: + + `KUBECONFIG_KUBERNETES_URL`_ and `KUBECONFIG_CERTIFICATE_AUTHORITY_DATA`_ + to provide parameters for the ``kubeconfig`` file. + +KUBECONFIG_KUBERNETES_URL +------------------------- + +.. versionadded:: TBD + +Default: ``""`` + +A Kubernetes API endpoint URL to be included in the generated ``kubeconfig`` +file. + +.. seealso:: + + `KUBECONFIG_ENABLED`_ to enable the ``kubeconfig`` file generation. + +KUBECONFIG_CERTIFICATE_AUTHORITY_DATA +------------------------------------- + +.. versionadded:: TBD + +Default: ``""`` + +Kubernetes API endpoint certificate authority data to be included in the +generated ``kubeconfig`` file. + +.. seealso:: + + `KUBECONFIG_ENABLED`_ to enable the ``kubeconfig`` file generation. diff --git a/openstack_dashboard/dashboards/identity/application_credentials/forms.py b/openstack_dashboard/dashboards/identity/application_credentials/forms.py index 10d094d02d..ed2e87fac5 100644 --- a/openstack_dashboard/dashboards/identity/application_credentials/forms.py +++ b/openstack_dashboard/dashboards/identity/application_credentials/forms.py @@ -15,6 +15,8 @@ import datetime import logging +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 @@ -49,6 +51,10 @@ class CreateApplicationCredentialForm(forms.SelfHandlingForm): required=False) unrestricted = forms.BooleanField(label=_("Unrestricted (dangerous)"), required=False) + kubernetes_namespace = forms.CharField(max_length=255, + label=_("Kubernetes Namespace"), + initial="default", + required=False) def __init__(self, request, *args, **kwargs): self.next_view = kwargs.pop('next_view', None) @@ -58,6 +64,8 @@ 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 + if not settings.KUBECONFIG_ENABLED: + self.fields['kubernetes_namespace'].widget = widgets.HiddenInput() # We have to protect the entire "data" dict because it contains the # secret string. @@ -98,6 +106,8 @@ class CreateApplicationCredentialForm(forms.SelfHandlingForm): ) self.request.session['application_credential'] = \ new_app_cred.to_dict() + (self.request.session['application_credential'] + ['kubernetes_namespace']) = data['kubernetes_namespace'] request.method = 'GET' return self.next_view.as_view()(request) except exceptions.Conflict: 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 ac781abc52..e190825a1b 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 @@ -40,4 +40,12 @@ actions, check "unrestricted". {% endblocktrans %}

+

+ {% if kubeconfig_enabled %} + {% blocktrans trimmed %} + You can optionally provide a Kubernetes Namespace. It will be included in the + kubeconfig file which can be downloaded from the next screen. + {% endblocktrans %} + {% endif %} +

{% endblock %} diff --git a/openstack_dashboard/dashboards/identity/application_credentials/templates/application_credentials/_success.html b/openstack_dashboard/dashboards/identity/application_credentials/templates/application_credentials/_success.html index 1f56049c79..54c36ab55d 100644 --- a/openstack_dashboard/dashboards/identity/application_credentials/templates/application_credentials/_success.html +++ b/openstack_dashboard/dashboards/identity/application_credentials/templates/application_credentials/_success.html @@ -31,5 +31,11 @@ {{ download_clouds_yaml_label }} + {% if download_kubeconfig_url %} + + + {{ download_kubeconfig_label }} + + {% endif %} {{ cancel_label }} {% endblock %} diff --git a/openstack_dashboard/dashboards/identity/application_credentials/templates/application_credentials/kubeconfig.template b/openstack_dashboard/dashboards/identity/application_credentials/templates/application_credentials/kubeconfig.template new file mode 100644 index 0000000000..49693a37c6 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/application_credentials/templates/application_credentials/kubeconfig.template @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: Config +clusters: +- name: kubernetes + cluster: + server: {{ kubernetes_url }} + certificate-authority-data: {{ kubernetes_certificate_authority_data }} +contexts: +- name: kubernetes + context: + cluster: kubernetes + user: {{ user }} + namespace: {{ kubernetes_namespace }} +current-context: kubernetes +users: + - name: {{ user }} + user: + exec: + apiVersion: client.authentication.k8s.io/v1beta1 + command: bin/kubectl-keystone-auth + args: + - "--keystone-url={{ auth_url }} + - "--domain-name=none" + - "--user-name={{ user }}" + - "--application-credential-id={{ application_credential_id }}" + - "--application-credential-secret={{ application_credential_secret }}" diff --git a/openstack_dashboard/dashboards/identity/application_credentials/urls.py b/openstack_dashboard/dashboards/identity/application_credentials/urls.py index 3de9d13114..24e3061db7 100644 --- a/openstack_dashboard/dashboards/identity/application_credentials/urls.py +++ b/openstack_dashboard/dashboards/identity/application_credentials/urls.py @@ -28,6 +28,8 @@ urlpatterns = [ views.CreateSuccessfulView.as_view(), name='success'), url(r'^download_openrc/$', views.download_rc_file, name='download_openrc'), + url(r'^download_kubeconfig/$', + views.download_kubeconfig_file, name='download_kubeconfig'), url(r'^download_clouds_yaml/$', views.download_clouds_yaml_file, name='download_clouds_yaml'), ] diff --git a/openstack_dashboard/dashboards/identity/application_credentials/views.py b/openstack_dashboard/dashboards/identity/application_credentials/views.py index b5e61f7fc8..7a3866968b 100644 --- a/openstack_dashboard/dashboards/identity/application_credentials/views.py +++ b/openstack_dashboard/dashboards/identity/application_credentials/views.py @@ -86,6 +86,11 @@ class CreateView(forms.ModalFormView): kwargs['next_view'] = CreateSuccessfulView return kwargs + def get_context_data(self, **kwargs): + context = super(CreateView, self).get_context_data(**kwargs) + context['kubeconfig_enabled'] = settings.KUBECONFIG_ENABLED + return context + class CreateSuccessfulView(forms.ModalFormView): template_name = 'identity/application_credentials/success.html' @@ -97,15 +102,20 @@ class CreateSuccessfulView(forms.ModalFormView): cancel_label = _("Close") download_openrc_label = _("Download openrc file") download_clouds_yaml_label = _("Download clouds.yaml") + download_kubeconfig_label = _("Download kubeconfig file") def get_context_data(self, **kwargs): context = super(CreateSuccessfulView, self).get_context_data(**kwargs) context['download_openrc_label'] = self.download_openrc_label context['download_clouds_yaml_label'] = self.download_clouds_yaml_label + context['download_kubeconfig_label'] = self.download_kubeconfig_label context['download_openrc_url'] = reverse( 'horizon:identity:application_credentials:download_openrc') context['download_clouds_yaml_url'] = reverse( 'horizon:identity:application_credentials:download_clouds_yaml') + if settings.KUBECONFIG_ENABLED: + context['download_kubeconfig_url'] = reverse( + 'horizon:identity:application_credentials:download_kubeconfig') return context def get_initial(self): @@ -125,12 +135,18 @@ def _get_context(request): interface = 'public' region = getattr(request.user, 'services_region', '') app_cred = request.session['application_credential'] - context = dict(auth_url=auth_url, - interface=interface, - region=region, - application_credential_id=app_cred['id'], - application_credential_name=app_cred['name'], - application_credential_secret=app_cred['secret']) + context = { + 'auth_url': auth_url, + 'interface': interface, + 'region': region, + 'user': request.user, + 'application_credential_id': app_cred['id'], + 'application_credential_name': app_cred['name'], + 'application_credential_secret': app_cred['secret'], + 'kubernetes_namespace': app_cred['kubernetes_namespace'], + 'kubernetes_url': settings.KUBECONFIG_KUBERNETES_URL, + 'kubernetes_certificate_authority_data': + settings.KUBECONFIG_CERTIFICATE_AUTHORITY_DATA} return context @@ -166,6 +182,14 @@ def download_clouds_yaml_file(request): return _render_attachment(filename, template, context, request) +def download_kubeconfig_file(request): + context = _get_context(request) + template = 'identity/application_credentials/kubeconfig.template' + filename = 'app-cred-%s-kubeconfig' % context['application_credential_name'] + response = _render_attachment(filename, template, context, request) + return response + + class DetailView(views.HorizonTemplateView): template_name = 'identity/application_credentials/detail.html' page_title = "{{ application_credential.name }}" diff --git a/openstack_dashboard/defaults.py b/openstack_dashboard/defaults.py index f93c0f8130..fa081afc17 100644 --- a/openstack_dashboard/defaults.py +++ b/openstack_dashboard/defaults.py @@ -374,3 +374,11 @@ REST_API_REQUIRED_SETTINGS = [ # and are not encrypted on the browser. This is an experimental API and # may be deprecated in the future without notice. REST_API_ADDITIONAL_SETTINGS = [] + +# Kubernetes clusters can use Keystone as an external identity provider. +# Horizon can generate a 'kubeconfig' file from the application credentials +# control panel which can be used for authenticating with a Kubernetes cluster. +# These settings control the kubeconfig parameters. +KUBECONFIG_ENABLED = False +KUBECONFIG_KUBERNETES_URL = "" +KUBECONFIG_CERTIFICATE_AUTHORITY_DATA = "" diff --git a/releasenotes/notes/bp-kubernetes-config-gen-bcebcbd8f9fb9991.yaml b/releasenotes/notes/bp-kubernetes-config-gen-bcebcbd8f9fb9991.yaml new file mode 100644 index 0000000000..d2758d64b2 --- /dev/null +++ b/releasenotes/notes/bp-kubernetes-config-gen-bcebcbd8f9fb9991.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + [`blueprint kubernetes-config-gen `_] + Horizon now supports the optional automatic generation of a Kubernetes + configuration file (kubeconfig) based on application credentials. Adds + a new download button for this purpose in the application credentials + creation dialog.