From 4dcfc07929f40d5719aabf2eeb539f88f0979255 Mon Sep 17 00:00:00 2001 From: Adam King Date: Tue, 21 Mar 2023 14:12:03 -0400 Subject: [PATCH] mgr/cephadm: allow configuring anonymous access for grafana In case users want to not allow anonymous viewers to view the grafana dashboard but don't want to use a full custom config Fixes: https://tracker.ceph.com/issues/59117 Signed-off-by: Adam King (cherry picked from commit e6690dde7b384d7d13e1a17119c648acfadee638) --- doc/cephadm/services/monitoring.rst | 22 ++++++++ src/pybind/mgr/cephadm/services/monitoring.py | 1 + .../templates/services/grafana/grafana.ini.j2 | 2 + src/pybind/mgr/cephadm/tests/test_services.py | 50 +++++++++++++++++++ src/pybind/mgr/cephadm/tests/test_spec.py | 1 + .../ceph/deployment/service_spec.py | 8 +++ .../ceph/tests/test_service_spec.py | 5 +- 7 files changed, 86 insertions(+), 3 deletions(-) diff --git a/doc/cephadm/services/monitoring.rst b/doc/cephadm/services/monitoring.rst index f7a6e19abbcd..3cbc8b594f09 100644 --- a/doc/cephadm/services/monitoring.rst +++ b/doc/cephadm/services/monitoring.rst @@ -458,6 +458,28 @@ Then apply this specification: Grafana will now create an admin user called ``admin`` with the given password. +Turning off anonymous access +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, cephadm allows anonymous users (users who have not provided any +login information) limited, viewer only access to the grafana dashboard. In +order to set up grafana to only allow viewing from logged in users, you can +set ``anonymous_access: False`` in your grafana spec. + +.. code-block:: yaml + + service_type: grafana + placement: + hosts: + - host1 + spec: + anonymous_access: False + initial_admin_password: "mypassword" + +Since deploying grafana with anonymous access set to false without an initial +admin password set would make the dashboard inaccessible, cephadm requires +setting the ``initial_admin_password`` when ``anonymous_access`` is set to false. + Setting up Alertmanager ----------------------- diff --git a/src/pybind/mgr/cephadm/services/monitoring.py b/src/pybind/mgr/cephadm/services/monitoring.py index 37c44a8304e6..2a707f4de946 100644 --- a/src/pybind/mgr/cephadm/services/monitoring.py +++ b/src/pybind/mgr/cephadm/services/monitoring.py @@ -77,6 +77,7 @@ class GrafanaService(CephadmService): GrafanaSpec, self.mgr.spec_store.active_specs[daemon_spec.service_name]) grafana_ini = self.mgr.template.render( 'services/grafana/grafana.ini.j2', { + 'anonymous_access': spec.anonymous_access, 'initial_admin_password': spec.initial_admin_password, 'http_port': daemon_spec.ports[0] if daemon_spec.ports else self.DEFAULT_SERVICE_PORT, 'http_addr': daemon_spec.ip if daemon_spec.ip else '' diff --git a/src/pybind/mgr/cephadm/templates/services/grafana/grafana.ini.j2 b/src/pybind/mgr/cephadm/templates/services/grafana/grafana.ini.j2 index e7e81d89a4b6..05c5b33ce6c3 100644 --- a/src/pybind/mgr/cephadm/templates/services/grafana/grafana.ini.j2 +++ b/src/pybind/mgr/cephadm/templates/services/grafana/grafana.ini.j2 @@ -1,10 +1,12 @@ # {{ cephadm_managed }} [users] default_theme = light +{% if anonymous_access %} [auth.anonymous] enabled = true org_name = 'Main Org.' org_role = 'Viewer' +{% endif %} [server] domain = 'bootstrap.storage.lab' protocol = https diff --git a/src/pybind/mgr/cephadm/tests/test_services.py b/src/pybind/mgr/cephadm/tests/test_services.py index 978d980995c0..c10b394be393 100644 --- a/src/pybind/mgr/cephadm/tests/test_services.py +++ b/src/pybind/mgr/cephadm/tests/test_services.py @@ -729,6 +729,56 @@ class TestMonitoring: 'certs/cert_file': ANY, 'certs/cert_key': ANY}}, []) + @patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + def test_grafana_no_anon_access(self, cephadm_module: CephadmOrchestrator): + # with anonymous_access set to False, expecting the [auth.anonymous] section + # to not be present in the grafana config. Note that we require an initial_admin_password + # to be provided when anonymous_access is False + with with_host(cephadm_module, 'test'): + with with_service(cephadm_module, ServiceSpec('mgr')) as _, \ + with_service(cephadm_module, GrafanaSpec(anonymous_access=False, initial_admin_password='secure')): + out = cephadm_module.cephadm_services['grafana'].generate_config( + CephadmDaemonDeploySpec('test', 'daemon', 'grafana')) + assert out == ( + { + 'files': + { + 'grafana.ini': + '# This file is generated by cephadm.\n' + '[users]\n' + ' default_theme = light\n' + '[server]\n' + " domain = 'bootstrap.storage.lab'\n" + ' protocol = https\n' + ' cert_file = /etc/grafana/certs/cert_file\n' + ' cert_key = /etc/grafana/certs/cert_key\n' + ' http_port = 3000\n' + ' http_addr = \n' + '[snapshots]\n' + ' external_enabled = false\n' + '[security]\n' + ' admin_user = admin\n' + ' admin_password = secure\n' + ' cookie_secure = true\n' + ' cookie_samesite = none\n' + ' allow_embedding = true', + 'provisioning/datasources/ceph-dashboard.yml': + "# This file is generated by cephadm.\n" + 'deleteDatasources:\n\n' + " - name: 'Loki'\n" + ' orgId: 2\n\n' + 'datasources:\n\n' + " - name: 'Loki'\n" + " type: 'loki'\n" + " access: 'proxy'\n" + ' orgId: 2\n' + " url: ''\n" + ' basicAuth: false\n' + ' isDefault: true\n' + ' editable: false', + 'certs/cert_file': ANY, + 'certs/cert_key': ANY}}, []) + @patch("cephadm.serve.CephadmServe._run_cephadm") def test_monitoring_ports(self, _run_cephadm, cephadm_module: CephadmOrchestrator): _run_cephadm.side_effect = async_side_effect(('{}', '', 0)) diff --git a/src/pybind/mgr/cephadm/tests/test_spec.py b/src/pybind/mgr/cephadm/tests/test_spec.py index c89d3b6c1ca6..c1125f698a5b 100644 --- a/src/pybind/mgr/cephadm/tests/test_spec.py +++ b/src/pybind/mgr/cephadm/tests/test_spec.py @@ -116,6 +116,7 @@ def test_spec_octopus(spec_json): ] j_c.pop('objectstore', None) j_c.pop('filter_logic', None) + j_c.pop('anonymous_access', None) return j_c assert spec_json == convert_to_old_style_json(spec.to_json()) diff --git a/src/python-common/ceph/deployment/service_spec.py b/src/python-common/ceph/deployment/service_spec.py index 0eefc0f11d3f..7556d788a27e 100644 --- a/src/python-common/ceph/deployment/service_spec.py +++ b/src/python-common/ceph/deployment/service_spec.py @@ -1283,6 +1283,7 @@ class GrafanaSpec(MonitoringSpec): networks: Optional[List[str]] = None, port: Optional[int] = None, initial_admin_password: Optional[str] = None, + anonymous_access: Optional[bool] = True, extra_container_args: Optional[List[str]] = None, extra_entrypoint_args: Optional[List[str]] = None, custom_configs: Optional[List[CustomConfig]] = None, @@ -1296,6 +1297,13 @@ class GrafanaSpec(MonitoringSpec): custom_configs=custom_configs) self.initial_admin_password = initial_admin_password + self.anonymous_access = anonymous_access + + if not self.anonymous_access and not self.initial_admin_password: + err_msg = ('Either initial_admin_password must be set or anonymous_access ' + 'must be set to true. Otherwise the grafana dashboard will ' + 'be inaccessible.') + raise SpecValidationError(err_msg) yaml.add_representer(GrafanaSpec, ServiceSpec.yaml_representer) diff --git a/src/python-common/ceph/tests/test_service_spec.py b/src/python-common/ceph/tests/test_service_spec.py index 28dc1680f0f0..3531b01edf1d 100644 --- a/src/python-common/ceph/tests/test_service_spec.py +++ b/src/python-common/ceph/tests/test_service_spec.py @@ -43,9 +43,6 @@ def test_parse_host_placement_specs(test_input, expected, require_network): assert ret == HostPlacementSpec.from_json(ret.to_json()) - - - @pytest.mark.parametrize( "test_input,expected", [ @@ -249,11 +246,13 @@ spec: service_type: grafana service_name: grafana spec: + anonymous_access: true port: 1234 --- service_type: grafana service_name: grafana spec: + anonymous_access: true initial_admin_password: secure port: 1234 --- -- 2.47.3