From 3ceee3ad3ffcbac4ded9da2060633d7bec0ea39a Mon Sep 17 00:00:00 2001 From: Redouane Kachach Date: Tue, 7 Jul 2026 12:37:53 +0200 Subject: [PATCH] mgr/cephadm: pass RGW zonegroup hostnames as custom SANs Fix RGW certificate generation so `zonegroup_hostnames` and wildcard SANs are passed to `get_certificates()` using the correct keyword argument. Previously, RGW built the expected SAN list from `zonegroup_hostnames` and `wildcard_enabled`, but passed it positionally so it was interpreted as ips list. Also add a unit test to cover the RGW `zonegroup_hostnames` + `wildcard_enabled` path and ensure SANs are passed as `custom_sans`. Fixed other calls to enforce the usage of keywords for arguments just to avoid similar issues in the future. Fixes: https://tracker.ceph.com/issues/77980 Signed-off-by: Redouane Kachach --- .../mgr/cephadm/services/cephadmservice.py | 2 +- .../mgr/cephadm/services/mgmt_gateway.py | 2 +- src/pybind/mgr/cephadm/services/monitoring.py | 6 ++-- .../mgr/cephadm/tests/services/test_rgw.py | 36 +++++++++++++++++++ 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/pybind/mgr/cephadm/services/cephadmservice.py b/src/pybind/mgr/cephadm/services/cephadmservice.py index cdc254c8678..f1b3a56da7a 100644 --- a/src/pybind/mgr/cephadm/services/cephadmservice.py +++ b/src/pybind/mgr/cephadm/services/cephadmservice.py @@ -1568,7 +1568,7 @@ class RgwService(CephService): if spec.ssl: san_list = spec.zonegroup_hostnames or [] custom_sans = san_list + [f"*.{h}" for h in san_list] if spec.wildcard_enabled else san_list - tls_creds = self.get_certificates(daemon_spec, custom_sans) + tls_creds = self.get_certificates(daemon_spec, custom_sans=custom_sans) pem = f'{tls_creds.key.rstrip()}\n{tls_creds.cert.lstrip()}' rgw_cert_name = daemon_spec.name() if spec.generate_cert else spec.service_name() ret, out, err = self.mgr.check_mon_command({ diff --git a/src/pybind/mgr/cephadm/services/mgmt_gateway.py b/src/pybind/mgr/cephadm/services/mgmt_gateway.py index 801a76b281e..8dfa7730041 100644 --- a/src/pybind/mgr/cephadm/services/mgmt_gateway.py +++ b/src/pybind/mgr/cephadm/services/mgmt_gateway.py @@ -143,7 +143,7 @@ class MgmtGatewayService(CephadmService): if svc_spec.ssl: ip = self.get_mgmt_gw_ip(svc_spec, daemon_spec) - tls_pair = self.get_certificates(daemon_spec, [ip]) + tls_pair = self.get_certificates(daemon_spec, ips=[ip]) daemon_config["files"]["nginx.crt"] = tls_pair.cert daemon_config["files"]["nginx.key"] = tls_pair.key diff --git a/src/pybind/mgr/cephadm/services/monitoring.py b/src/pybind/mgr/cephadm/services/monitoring.py index 6d0008f0ef6..f80cab0bd70 100644 --- a/src/pybind/mgr/cephadm/services/monitoring.py +++ b/src/pybind/mgr/cephadm/services/monitoring.py @@ -159,7 +159,7 @@ class GrafanaService(CephadmService): def get_grafana_certificates(self, daemon_spec: CephadmDaemonDeploySpec) -> TLSCredentials: host_ips = [self.mgr.inventory.get_addr(daemon_spec.host)] host_fqdns = [self.mgr.get_fqdn(daemon_spec.host), 'grafana_servers'] - return self.get_certificates(daemon_spec, host_ips, host_fqdns) + return self.get_certificates(daemon_spec, ips=host_ips, fqdns=host_fqdns) def generate_config(self, daemon_spec: CephadmDaemonDeploySpec) -> Tuple[Dict[str, Any], List[str]]: assert self.TYPE == daemon_spec.daemon_type @@ -301,7 +301,7 @@ class AlertmanagerService(CephadmService): def get_alertmanager_certificates(self, daemon_spec: CephadmDaemonDeploySpec) -> TLSCredentials: host_ips = [self.mgr.inventory.get_addr(daemon_spec.host)] host_fqdns = [self.mgr.get_fqdn(daemon_spec.host), 'alertmanager_servers'] - return self.get_certificates(daemon_spec, host_ips, host_fqdns) + return self.get_certificates(daemon_spec, ips=host_ips, fqdns=host_fqdns) @classmethod def get_dependencies(cls, mgr: "CephadmOrchestrator", @@ -524,7 +524,7 @@ class PrometheusService(CephadmService): def get_prometheus_certificates(self, daemon_spec: CephadmDaemonDeploySpec) -> TLSCredentials: host_ips = [self.mgr.inventory.get_addr(daemon_spec.host)] host_fqdns = [self.mgr.get_fqdn(daemon_spec.host), 'prometheus_servers'] - return self.get_certificates(daemon_spec, host_ips, host_fqdns) + return self.get_certificates(daemon_spec, ips=host_ips, fqdns=host_fqdns) def get_service_discovery_cfg(self, security_enabled: bool, mgmt_gw_enabled: bool) -> Dict[str, List[str]]: """ diff --git a/src/pybind/mgr/cephadm/tests/services/test_rgw.py b/src/pybind/mgr/cephadm/tests/services/test_rgw.py index ecf9510885b..cec0197d64d 100644 --- a/src/pybind/mgr/cephadm/tests/services/test_rgw.py +++ b/src/pybind/mgr/cephadm/tests/services/test_rgw.py @@ -496,3 +496,39 @@ class TestRGWService: # Inline certs must still be present — the service is still running on host2 assert cm.get_cert(rgw_svc.cert_name, service_name=svc_name) is not None assert cm.get_key(rgw_svc.key_name, service_name=svc_name) is not None + + @patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}')) + def test_rgw_zonegroup_hostnames_are_passed_as_custom_sans( + self, cephadm_module: CephadmOrchestrator): + """RGW zonegroup hostnames must be passed as custom DNS SANs. + + Verifies that zonegroup hostnames are routed to get_certificates() as + the custom_sans keyword argument, so that values like s3.example.com + and *.s3.example.com are registered as DNS SANs on the generated + certificate. + """ + with with_host(cephadm_module, 'host1'): + s = RGWSpec( + service_id='foo', + ssl=True, + zonegroup_hostnames=['s3.example.com'], + wildcard_enabled=True, + rgw_frontend_type='beast', + ) + rgw_svc = service_registry.get_service('rgw') + tls_creds = TLSCredentials( + cert=ceph_generated_cert, + key=ceph_generated_key, + ) + with patch.object(rgw_svc, 'get_certificates', + return_value=tls_creds) as get_certificates: + with with_service(cephadm_module, s): + # The serve loop may invoke get_certificates more than once; + # assert on the arguments of every call, not the count. + get_certificates.assert_called() + for args, kwargs in get_certificates.call_args_list: + # custom_sans must be a kwarg, never the positional ips arg + assert len(args) == 1 + assert kwargs == { + 'custom_sans': ['s3.example.com', '*.s3.example.com'], + } -- 2.47.3