Skip to content

Commit 7a376f6

Browse files
Fix tls.py abstraction
1 parent 81076fc commit 7a376f6

File tree

7 files changed

+80
-46
lines changed

7 files changed

+80
-46
lines changed

kubernetes/metadata.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ requires:
4747
optional: true
4848
limit: 1
4949
peers:
50+
tls:
51+
interface: tls
5052
cos:
5153
interface: cos
5254
refresh-v-three:

kubernetes/src/abstract_charm.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,14 @@ def is_externally_accessible(self, *, event) -> typing.Optional[bool]:
137137
Only defined in vm charm to return True/False. In k8s charm, returns None.
138138
"""
139139

140+
@abc.abstractmethod
141+
def tls_sans_ip(self, *, event) -> typing.Optional[typing.List[str]]:
142+
"""TLS IP subject alternative names"""
143+
144+
@abc.abstractmethod
145+
def tls_sans_dns(self, *, event) -> typing.Optional[typing.List[str]]:
146+
"""TLS DNS subject alternative names"""
147+
140148
@abc.abstractmethod
141149
def _status(self, *, event) -> typing.Optional[ops.StatusBase]:
142150
"""Status of the charm."""

kubernetes/src/charm.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,34 @@ def _read_write_endpoints(self, *, event) -> str:
411411
def _read_only_endpoints(self, *, event) -> str:
412412
return self._get_hosts_ports("ro")
413413

414+
def tls_sans_ip(self, *, event) -> typing.Optional[typing.List[str]]:
415+
_, extra_ips = self.get_all_k8s_node_hostnames_and_ips()
416+
return [
417+
str(self.model.get_binding("juju-info").network.bind_address),
418+
"127.0.0.1",
419+
*extra_ips,
420+
]
421+
422+
def tls_sans_dns(self, *, event) -> typing.Optional[typing.List[str]]:
423+
service_name = self.service_name
424+
unit_name = self.unit.name.replace("/", "-")
425+
extra_hosts, _ = self.get_all_k8s_node_hostnames_and_ips()
426+
return [
427+
socket.getfqdn(),
428+
service_name,
429+
f"{service_name}.{self.model_service_domain}",
430+
unit_name,
431+
f"{unit_name}.{self.app.name}-endpoints",
432+
f"{unit_name}.{self.app.name}-endpoints.{self.model_service_domain}",
433+
self.app.name,
434+
f"{self.app.name}.{self.app.name}-endpoints",
435+
f"{self.app.name}.{self.app.name}-endpoints.{self.model_service_domain}"
436+
f"{self.app.name}-endpoints",
437+
f"{self.app.name}-endpoints.{self.model_service_domain}",
438+
f"{self.app.name}.{self.model_service_domain}",
439+
*extra_hosts,
440+
]
441+
414442
def get_all_k8s_node_hostnames_and_ips(
415443
self,
416444
) -> typing.Tuple[typing.List[str], typing.List[str]]:

kubernetes/src/relations/tls.py

Lines changed: 19 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2023 Canonical Ltd.
1+
# Copyright 2024 Canonical Ltd.
22
# See LICENSE file for licensing details.
33

44
"""Relation to TLS certificate provider"""
@@ -17,11 +17,11 @@
1717
import relations.secrets
1818

1919
if typing.TYPE_CHECKING:
20-
import charm
20+
import abstract_charm
2121

2222
logger = logging.getLogger(__name__)
2323

24-
_PEER_RELATION_ENDPOINT_NAME = "mysql-router-peers"
24+
_PEER_RELATION_ENDPOINT_NAME = "tls"
2525

2626
_TLS_REQUESTED_CSR = "tls-requested-csr"
2727
_TLS_ACTIVE_CSR = "tls-active-csr"
@@ -48,7 +48,7 @@ def _generate_private_key() -> str:
4848
class _Relation:
4949
"""Relation to TLS certificate provider"""
5050

51-
_charm: "charm.KubernetesRouterCharm"
51+
_charm: "abstract_charm.MySQLRouterCharm"
5252
_interface: tls_certificates.TLSCertificatesRequiresV2
5353
_secrets: relations.secrets.RelationSecrets
5454

@@ -110,56 +110,35 @@ def save_certificate(self, event: tls_certificates.CertificateAvailableEvent) ->
110110
logger.debug(f"Saved TLS certificate {event=}")
111111
self._charm.reconcile(event=None)
112112

113-
def _generate_csr(self, key: bytes) -> bytes:
113+
def _generate_csr(self, *, event, key: bytes) -> bytes:
114114
"""Generate certificate signing request (CSR)."""
115-
service_name = self._charm.service_name
116-
unit_name = self._charm.unit.name.replace("/", "-")
117-
extra_hosts, extra_ips = self._charm.get_all_k8s_node_hostnames_and_ips()
118115
return tls_certificates.generate_csr(
119116
private_key=key,
120117
# X.509 CommonName has a limit of 64 characters
121118
# (https://github.com/pyca/cryptography/issues/10553)
122119
subject=socket.getfqdn()[:64],
123120
organization=self._charm.app.name,
124-
sans_dns=[
125-
socket.getfqdn(),
126-
service_name,
127-
f"{service_name}.{self._charm.model_service_domain}",
128-
unit_name,
129-
f"{unit_name}.{self._charm.app.name}-endpoints",
130-
f"{unit_name}.{self._charm.app.name}-endpoints.{self._charm.model_service_domain}",
131-
self._charm.app.name,
132-
f"{self._charm.app.name}.{self._charm.app.name}-endpoints",
133-
f"{self._charm.app.name}.{self._charm.app.name}-endpoints.{self._charm.model_service_domain}"
134-
f"{self._charm.app.name}-endpoints",
135-
f"{self._charm.app.name}-endpoints.{self._charm.model_service_domain}",
136-
f"{self._charm.app.name}.{self._charm.model_service_domain}",
137-
*extra_hosts,
138-
],
139-
sans_ip=[
140-
str(self._charm.model.get_binding("juju-info").network.bind_address),
141-
"127.0.0.1",
142-
*extra_ips,
143-
],
121+
sans_ip=self._charm.tls_sans_ip(event=event),
122+
sans_dns=self._charm.tls_sans_dns(event=event),
144123
)
145124

146-
def request_certificate_creation(self):
125+
def request_certificate_creation(self, *, event):
147126
"""Request new TLS certificate from related provider charm."""
148127
logger.debug("Requesting TLS certificate creation")
149-
csr = self._generate_csr(self.key.encode("utf-8"))
128+
csr = self._generate_csr(event=event, key=self.key.encode("utf-8"))
150129
self._interface.request_certificate_creation(certificate_signing_request=csr)
151130
self._secrets.set_value(
152131
relations.secrets.UNIT_SCOPE, _TLS_REQUESTED_CSR, csr.decode("utf-8")
153132
)
154133
logger.debug("Requested TLS certificate creation")
155134

156-
def request_certificate_renewal(self):
135+
def request_certificate_renewal(self, *, event):
157136
"""Request TLS certificate renewal from related provider charm."""
158137
logger.debug("Requesting TLS certificate renewal")
159138
old_csr = self._secrets.get_value(relations.secrets.UNIT_SCOPE, _TLS_ACTIVE_CSR).encode(
160139
"utf-8"
161140
)
162-
new_csr = self._generate_csr(self.key.encode("utf-8"))
141+
new_csr = self._generate_csr(event=event, key=self.key.encode("utf-8"))
163142
self._interface.request_certificate_renewal(
164143
old_certificate_signing_request=old_csr, new_certificate_signing_request=new_csr
165144
)
@@ -174,13 +153,15 @@ class RelationEndpoint(ops.Object):
174153

175154
NAME = "certificates"
176155

177-
def __init__(self, charm_: "charm.KubernetesRouterCharm") -> None:
156+
def __init__(self, charm_: "abstract_charm.MySQLRouterCharm") -> None:
178157
super().__init__(charm_, self.NAME)
179158
self._charm = charm_
180159
self._interface = tls_certificates.TLSCertificatesRequiresV2(self._charm, self.NAME)
181160

182161
self._secrets = relations.secrets.RelationSecrets(
183-
charm_, self._interface.relationship_name, unit_secret_fields=[_TLS_PRIVATE_KEY]
162+
charm_,
163+
_PEER_RELATION_ENDPOINT_NAME,
164+
unit_secret_fields=[_TLS_PRIVATE_KEY],
184165
)
185166

186167
self.framework.observe(
@@ -269,7 +250,7 @@ def _on_set_tls_private_key(self, event: ops.ActionEvent) -> None:
269250
logger.debug("No TLS certificate relation active. Skipped certificate request")
270251
else:
271252
try:
272-
self._relation.request_certificate_creation()
253+
self._relation.request_certificate_creation(event=event)
273254
except Exception as e:
274255
event.fail(f"Failed to request certificate: {e}")
275256
logger.exception(
@@ -278,9 +259,9 @@ def _on_set_tls_private_key(self, event: ops.ActionEvent) -> None:
278259
raise
279260
logger.debug("Handled set TLS private key action")
280261

281-
def _on_tls_relation_created(self, _) -> None:
262+
def _on_tls_relation_created(self, event) -> None:
282263
"""Request certificate when TLS relation created."""
283-
self._relation.request_certificate_creation()
264+
self._relation.request_certificate_creation(event=event)
284265

285266
def _on_tls_relation_broken(self, _) -> None:
286267
"""Delete TLS certificate."""
@@ -300,4 +281,4 @@ def _on_certificate_expiring(self, event: tls_certificates.CertificateExpiringEv
300281
logger.warning("Unknown certificate expiring")
301282
return
302283

303-
self._relation.request_certificate_renewal()
284+
self._relation.request_certificate_renewal(event=event)

machines/src/abstract_charm.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,14 @@ def is_externally_accessible(self, *, event) -> typing.Optional[bool]:
137137
Only defined in vm charm to return True/False. In k8s charm, returns None.
138138
"""
139139

140+
@abc.abstractmethod
141+
def tls_sans_ip(self, *, event) -> typing.Optional[typing.List[str]]:
142+
"""TLS IP subject alternative names"""
143+
144+
@abc.abstractmethod
145+
def tls_sans_dns(self, *, event) -> typing.Optional[typing.List[str]]:
146+
"""TLS DNS subject alternative names"""
147+
140148
@abc.abstractmethod
141149
def _status(self, *, event) -> typing.Optional[ops.StatusBase]:
142150
"""Status of the charm."""

machines/src/charm.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,15 @@ def _status(self, *, event) -> typing.Optional[ops.StatusBase]:
127127
def _logrotate(self) -> machine_logrotate.LogRotate:
128128
return machine_logrotate.LogRotate(container_=self._container)
129129

130+
def tls_sans_ip(self, *, event) -> typing.Optional[typing.List[str]]:
131+
sans_ip = ["127.0.0.1"] # needed for the HTTP server when related with COS
132+
if self.is_externally_accessible(event=event):
133+
sans_ip.append(self.host_address)
134+
return sans_ip
135+
136+
def tls_sans_dns(self, *, event) -> typing.Optional[typing.List[str]]:
137+
return None
138+
130139
@property
131140
def host_address(self) -> str:
132141
"""The host address for the machine."""

machines/src/relations/tls.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ def _generate_private_key() -> str:
4444
return tls_certificates.generate_private_key().decode("utf-8")
4545

4646

47-
# TODO python3.10 min version: Add `(kw_only=True)`
48-
@dataclasses.dataclass
47+
@dataclasses.dataclass(kw_only=True)
4948
class _Relation:
5049
"""Relation to TLS certificate provider"""
5150

@@ -113,15 +112,14 @@ def save_certificate(self, event: tls_certificates.CertificateAvailableEvent) ->
113112

114113
def _generate_csr(self, *, event, key: bytes) -> bytes:
115114
"""Generate certificate signing request (CSR)."""
116-
sans_ip = ["127.0.0.1"] # needed for the HTTP server when related with COS
117-
if self._charm.is_externally_accessible(event=event):
118-
sans_ip.append(self._charm.host_address)
119-
120115
return tls_certificates.generate_csr(
121116
private_key=key,
122-
subject=socket.getfqdn(),
117+
# X.509 CommonName has a limit of 64 characters
118+
# (https://github.com/pyca/cryptography/issues/10553)
119+
subject=socket.getfqdn()[:64],
123120
organization=self._charm.app.name,
124-
sans_ip=sans_ip,
121+
sans_ip=self._charm.tls_sans_ip(event=event),
122+
sans_dns=self._charm.tls_sans_dns(event=event),
125123
)
126124

127125
def request_certificate_creation(self, *, event):

0 commit comments

Comments
 (0)