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
This commit is contained in:
Luciano Lo Giudice 2022-03-28 15:15:49 -03:00
parent 16653c75e1
commit af80051429
5 changed files with 209 additions and 1 deletions

View File

@ -83,6 +83,26 @@ options:
default: "" default: ""
description: | description: |
Message of the day settings. Should be in the format "severity|expires|message". Set to "" to disable. 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: ssl_cert:
type: string type: string
default: default:

View File

@ -18,6 +18,7 @@ series:
- focal - focal
- groovy - groovy
- hirsute - hirsute
- jammy
requires: requires:
dashboard: dashboard:
interface: ceph-dashboard interface: ceph-dashboard

View File

@ -14,6 +14,7 @@ from ops.framework import StoredState
from ops.main import main from ops.main import main
from ops.model import ActiveStatus, BlockedStatus, StatusBase from ops.model import ActiveStatus, BlockedStatus, StatusBase
from ops.charm import ActionEvent from ops.charm import ActionEvent
from ops_openstack.core import charm_class, get_charm_class_for_release
from typing import List, Union, Tuple from typing import List, Union, Tuple
import base64 import base64
@ -425,6 +426,7 @@ class CephDashboardCharm(ops_openstack.core.OSBaseCharm):
ceph_utils.mgr_enable_dashboard() ceph_utils.mgr_enable_dashboard()
self._apply_ceph_config_from_charm_config() self._apply_ceph_config_from_charm_config()
self._configure_tls() self._configure_tls()
self._configure_saml()
ceph_utils.mgr_config_set( ceph_utils.mgr_config_set(
'mgr/dashboard/{hostname}/server_addr'.format( 'mgr/dashboard/{hostname}/server_addr'.format(
hostname=socket.gethostname()), hostname=socket.gethostname()),
@ -568,6 +570,26 @@ class CephDashboardCharm(ops_openstack.core.OSBaseCharm):
self.TLS_KEY_PATH) self.TLS_KEY_PATH)
self.kick_dashboard() 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: def _gen_user_password(self, length: int = 12) -> str:
"""Generate a password""" """Generate a password"""
alphabet = ( alphabet = (
@ -604,5 +626,20 @@ class CephDashboardCharm(ops_openstack.core.OSBaseCharm):
event.fail(exc.output) 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__": if __name__ == "__main__":
main(CephDashboardCharm) main(get_charm_class_for_release())

View File

@ -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'

View File

@ -679,3 +679,31 @@ class TestCephDashboardCharmBase(CharmTestCase):
self.subprocess.check_output.assert_called_once_with( self.subprocess.check_output.assert_called_once_with(
['ceph', 'dashboard', 'ac-user-delete', 'auser'], ['ceph', 'dashboard', 'ac-user-delete', 'auser'],
stderr=self.subprocess.STDOUT) 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
)