From dfda5adeeecd3a83a04e4821d20c2aa49f4d173e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Zamora=20Mart=C3=ADnez?= <76525382+zmraul@users.noreply.github.com> Date: Tue, 26 Sep 2023 09:29:22 +0200 Subject: [PATCH] [DPE-2195] Add `extra_sans` config option (#62) --- config.yaml | 4 +++ src/structured_config.py | 1 + src/tls.py | 17 ++++++++++- tests/unit/test_tls.py | 66 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_tls.py diff --git a/config.yaml b/config.yaml index 4f022911..1c1ba407 100644 --- a/config.yaml +++ b/config.yaml @@ -74,3 +74,7 @@ options: description: Specifies the enabled cipher suites to be used in ZooKeeper TLS negotiation (csv). Overrides any explicit value set via the zookeeper.ssl.ciphersuites system property (note the single word "ciphersuites"). The default value of null means the list of enabled cipher suites is determined by the Java runtime being used. type: string default: "" + certificate_extra_sans: + description: Config options to add extra-sans to the ones used when requesting server certificates. The extra-sans are specified by comma-separated names to be added when requesting signed certificates. Use "{unit}" as a placeholder to be filled with the unit number, e.g. "worker-{unit}" will be translated as "worker-0" for unit 0 and "worker-1" for unit 1 when requesting the certificate. + type: string + default: "" diff --git a/src/structured_config.py b/src/structured_config.py index 3c6c36a9..5dd0e807 100644 --- a/src/structured_config.py +++ b/src/structured_config.py @@ -67,6 +67,7 @@ class CharmConfig(BaseConfigModel): ssl_cipher_suites: Optional[str] replication_quota_window_num: int zookeeper_ssl_cipher_suites: Optional[str] + certificate_extra_sans: Optional[str] @validator("*", pre=True) @classmethod diff --git a/src/tls.py b/src/tls.py index d5c2285f..421a93c5 100644 --- a/src/tls.py +++ b/src/tls.py @@ -349,6 +349,20 @@ def remove_stores(self) -> None: logger.error(e.stdout) raise + @property + def _extra_sans(self) -> List[str]: + """Parse the certificate_extra_sans config option.""" + extra_sans = self.charm.config.certificate_extra_sans or "" + parsed_sans = [] + + if extra_sans == "": + return parsed_sans + + for sans in extra_sans.split(","): + parsed_sans.append(sans.replace("{unit}", self.charm.unit.name.split("/")[1])) + + return parsed_sans + @property def _sans(self) -> Dict[str, List[str]]: """Builds a SAN dict of DNS names and IPs for the unit.""" @@ -365,5 +379,6 @@ def _sans(self) -> Dict[str, List[str]]: f"{self.charm.app.name}-{unit_id}", f"{self.charm.app.name}-{unit_id}.{self.charm.app.name}-endpoints", socket.getfqdn(), - ], + ] + + self._extra_sans, } diff --git a/tests/unit/test_tls.py b/tests/unit/test_tls.py new file mode 100644 index 00000000..254748e7 --- /dev/null +++ b/tests/unit/test_tls.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. + +from pathlib import Path + +import pytest +import yaml +from ops.testing import Harness + +from charm import KafkaK8sCharm +from literals import CHARM_KEY, PEER, ZOOKEEPER_REL_NAME + +CONFIG = str(yaml.safe_load(Path("./config.yaml").read_text())) +ACTIONS = str(yaml.safe_load(Path("./actions.yaml").read_text())) +METADATA = str(yaml.safe_load(Path("./metadata.yaml").read_text())) + + +@pytest.fixture +def harness(): + harness = Harness(KafkaK8sCharm, meta=METADATA) + harness.add_relation("restart", CHARM_KEY) + harness._update_config( + { + "log_retention_ms": "-1", + "compression_type": "producer", + } + ) + harness.begin() + + # Relate to ZK with tls enabled + zk_relation_id = harness.add_relation(ZOOKEEPER_REL_NAME, ZOOKEEPER_REL_NAME) + harness.add_relation_unit(zk_relation_id, "zookeeper/0") + harness.update_relation_data( + zk_relation_id, + harness.charm.app.name, + { + "username": "relation-1", + "password": "mellon", + "endpoints": "123.123.123", + "chroot": "/kafka", + "uris": "123.123.123/kafka", + "tls": "enabled", + }, + ) + + return harness + + +def test_extra_sans_config(harness: Harness): + # Create peer relation + peer_relation_id = harness.add_relation(PEER, CHARM_KEY) + harness.update_relation_data( + peer_relation_id, + "kafka-k8s/0", + {"private-address": "treebeard", "tls": "enabled"}, + ) + + harness.update_config({"certificate_extra_sans": ""}) + assert harness.charm.tls._extra_sans == [] + + harness.update_config({"certificate_extra_sans": "worker{unit}.com"}) + assert harness.charm.tls._extra_sans == ["worker0.com"] + + harness.update_config({"certificate_extra_sans": "worker{unit}.com,{unit}.example"}) + assert harness.charm.tls._extra_sans == ["worker0.com", "0.example"]