From af80051429960db357c51e8966fc2bf9f0c220f8 Mon Sep 17 00:00:00 2001 From: Luciano Lo Giudice Date: Mon, 28 Mar 2022 15:15:49 -0300 Subject: [PATCH] Add SAML support to ceph-dashboard This patchset adds support to setup authentication via the SAML protocol for the ceph-dashboard. Change-Id: I96c0d856d173a76739a6d2a9d4ad4811d3d196c3 func-test-pr: https://github.com/openstack-charmers/zaza-openstack-tests/pull/741 --- config.yaml | 20 ++++ metadata.yaml | 1 + src/charm.py | 39 +++++++- tests/bundles/focal-yoga.yaml | 122 ++++++++++++++++++++++++ unit_tests/test_ceph_dashboard_charm.py | 28 ++++++ 5 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 tests/bundles/focal-yoga.yaml diff --git a/config.yaml b/config.yaml index 8fcbd1f..1460d02 100644 --- a/config.yaml +++ b/config.yaml @@ -83,6 +83,26 @@ options: default: "" description: | Message of the day settings. Should be in the format "severity|expires|message". Set to "" to disable. + saml-base-url: + type: string + default: "" + description: | + The base URL from where the Ceph dashboard is accessed. Must support the SAML protocol. + saml-idp-metadata: + type: string + default: "" + description: | + URL that points to the IdP metadata XML. Can be remote or local. + saml-username-attribute: + type: string + default: "" + description: | + The attribute that is used to get the username from the authentication response. + saml-idp-entity-id: + type: string + default: "uid" + description: | + Unique ID to disambiguate when more than one entity id exists on the IdP metadata. ssl_cert: type: string default: diff --git a/metadata.yaml b/metadata.yaml index ea47f3e..a35e9ce 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -18,6 +18,7 @@ series: - focal - groovy - hirsute +- jammy requires: dashboard: interface: ceph-dashboard diff --git a/src/charm.py b/src/charm.py index 650dcc2..a3d3a2f 100755 --- a/src/charm.py +++ b/src/charm.py @@ -14,6 +14,7 @@ from ops.framework import StoredState from ops.main import main from ops.model import ActiveStatus, BlockedStatus, StatusBase from ops.charm import ActionEvent +from ops_openstack.core import charm_class, get_charm_class_for_release from typing import List, Union, Tuple import base64 @@ -425,6 +426,7 @@ class CephDashboardCharm(ops_openstack.core.OSBaseCharm): ceph_utils.mgr_enable_dashboard() self._apply_ceph_config_from_charm_config() self._configure_tls() + self._configure_saml() ceph_utils.mgr_config_set( 'mgr/dashboard/{hostname}/server_addr'.format( hostname=socket.gethostname()), @@ -568,6 +570,26 @@ class CephDashboardCharm(ops_openstack.core.OSBaseCharm): self.TLS_KEY_PATH) self.kick_dashboard() + def _configure_saml(self) -> None: + if 'python3-onelogin-saml2' not in self.PACKAGES: + return + + base_url = self.config.get('saml-base-url') + idp_metadata = self.config.get('saml-idp-metadata') + if not base_url or not idp_metadata: + return + + cmd = ['ceph', 'dashboard', 'sso', 'setup', 'saml2', + base_url, idp_metadata] + username_attr = self.config.get('saml-username-attribute') + if username_attr: + cmd.append(username_attr) + idp_entity_id = self.config.get('saml-idp-entity-id') + if idp_entity_id: + cmd.append(idp_entity_id) + + self._run_cmd(cmd) + def _gen_user_password(self, length: int = 12) -> str: """Generate a password""" alphabet = ( @@ -604,5 +626,20 @@ class CephDashboardCharm(ops_openstack.core.OSBaseCharm): event.fail(exc.output) +@charm_class +class CephDashboardCharmOctopus(CephDashboardCharm): + + _stored = StoredState() + release = 'octopus' + + +@charm_class +class CephDashboardCharmQuincy(CephDashboardCharm): + + _stored = StoredState() + PACKAGES = ['ceph-mgr-dashboard', 'python3-onelogin-saml2'] + release = 'quincy' + + if __name__ == "__main__": - main(CephDashboardCharm) + main(get_charm_class_for_release()) diff --git a/tests/bundles/focal-yoga.yaml b/tests/bundles/focal-yoga.yaml new file mode 100644 index 0000000..d0b688c --- /dev/null +++ b/tests/bundles/focal-yoga.yaml @@ -0,0 +1,122 @@ +local_overlay_enabled: False +series: focal +variables: + openstack-origin: &openstack-origin cloud:focal-yoga +applications: + ceph-osd: + charm: ch:ceph-osd + num_units: 6 + storage: + osd-devices: 'cinder,10G' + options: + osd-devices: '/dev/test-non-existent' + source: *openstack-origin + channel: quincy/edge + ceph-mon: + charm: ch:ceph-mon + num_units: 3 + options: + monitor-count: '3' + source: *openstack-origin + channel: quincy/edge + vault: + num_units: 1 + charm: ch:vault + channel: latest/edge + mysql-innodb-cluster: + charm: ch:mysql-innodb-cluster + constraints: mem=3072M + num_units: 3 + options: + source: *openstack-origin + channel: latest/edge + vault-mysql-router: + charm: ch:mysql-router + channel: latest/edge + ceph-dashboard: + charm: ../../ceph-dashboard.charm + options: + public-hostname: 'ceph-dashboard.zaza.local' + source: *openstack-origin + prometheus: + charm: cs:prometheus2 + num_units: 1 + grafana: + # SSL and allow_embedding are not released into cs:grafana yet, due + # October 2021 + charm: ch:grafana + num_units: 1 + options: + anonymous: True + install_plugins: https://storage.googleapis.com/plugins-community/vonage-status-panel/release/1.0.11/vonage-status-panel-1.0.11.zip,https://storage.googleapis.com/plugins-community/grafana-piechart-panel/release/1.6.2/grafana-piechart-panel-1.6.2.zip + install_method: snap + allow_embedding: True + telegraf: + charm: telegraf + channel: stable + options: + hostname: "{host}" + prometheus-alertmanager: + charm: cs:prometheus-alertmanager + num_units: 1 + ceph-radosgw: + charm: ch:ceph-radosgw + num_units: 3 + channel: latest/edge + ceph-fs: + charm: ch:ceph-fs + num_units: 1 + channel: latest/edge + ceph-iscsi: + charm: ch:ceph-iscsi + num_units: 2 + options: + gateway-metadata-pool: iscsi-foo-metadata + channel: latest/edge +relations: + - - 'ceph-osd:mon' + - 'ceph-mon:osd' + - - 'vault:shared-db' + - 'vault-mysql-router:shared-db' + - - 'vault-mysql-router:db-router' + - 'mysql-innodb-cluster:db-router' + - - 'ceph-dashboard:dashboard' + - 'ceph-mon:dashboard' + - - 'ceph-dashboard:certificates' + - 'vault:certificates' + - - 'ceph-mon:prometheus' + - 'prometheus:target' + - - 'grafana:grafana-source' + - 'prometheus:grafana-source' + - - 'grafana:certificates' + - 'vault:certificates' + - - 'ceph-osd:juju-info' + - 'telegraf:juju-info' + - - 'ceph-mon:juju-info' + - 'telegraf:juju-info' + - - 'telegraf:prometheus-client' + - 'prometheus:target' + - - 'telegraf:dashboards' + - 'grafana:dashboards' + - - 'ceph-dashboard:grafana-dashboard' + - 'grafana:dashboards' + - - 'ceph-dashboard:alertmanager-service' + - 'prometheus-alertmanager:alertmanager-service' + - - 'ceph-dashboard:prometheus' + - 'prometheus:website' + - - 'prometheus:alertmanager-service' + - 'prometheus-alertmanager:alertmanager-service' + - - 'ceph-radosgw:mon' + - 'ceph-mon:radosgw' + - - 'ceph-radosgw:certificates' + - 'vault:certificates' + - - 'ceph-dashboard:radosgw-dashboard' + - 'ceph-radosgw:radosgw-user' + - - 'ceph-mon:mds' + - 'ceph-fs:ceph-mds' + - - 'ceph-mon:client' + - 'ceph-iscsi:ceph-client' + - - 'vault:certificates' + - 'ceph-iscsi:certificates' + - - 'ceph-dashboard:iscsi-dashboard' + - 'ceph-iscsi:admin-access' diff --git a/unit_tests/test_ceph_dashboard_charm.py b/unit_tests/test_ceph_dashboard_charm.py index 5542120..96c749c 100644 --- a/unit_tests/test_ceph_dashboard_charm.py +++ b/unit_tests/test_ceph_dashboard_charm.py @@ -679,3 +679,31 @@ class TestCephDashboardCharmBase(CharmTestCase): self.subprocess.check_output.assert_called_once_with( ['ceph', 'dashboard', 'ac-user-delete', 'auser'], stderr=self.subprocess.STDOUT) + + def test_saml(self): + self.subprocess.check_output.return_value = b'' + self.harness.begin() + self.harness.charm.PACKAGES.append('python3-onelogin-saml2') + self.harness.charm._configure_saml() + self.subprocess.check_output.assert_not_called() + + base_url = 'https://saml-base' + idp_meta = 'file://idp.xml' + username_attr = 'uid' + entity_id = 'some_id' + + self.harness.update_config( + key_values={ + 'saml-base-url': base_url, + 'saml-idp-metadata': idp_meta, + 'saml-username-attribute': username_attr, + 'saml-idp-entity-id': entity_id, + } + ) + + self.harness.charm._configure_saml() + self.subprocess.check_output.assert_called_with( + ['ceph', 'dashboard', 'sso', 'setup', 'saml2', + base_url, idp_meta, username_attr, entity_id], + stderr=ANY + )