mongo-charms-single-kernel 1.8.7__py3-none-any.whl → 1.8.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mongo-charms-single-kernel might be problematic. Click here for more details.

Files changed (31) hide show
  1. {mongo_charms_single_kernel-1.8.7.dist-info → mongo_charms_single_kernel-1.8.9.dist-info}/METADATA +1 -1
  2. {mongo_charms_single_kernel-1.8.7.dist-info → mongo_charms_single_kernel-1.8.9.dist-info}/RECORD +30 -28
  3. single_kernel_mongo/config/literals.py +8 -3
  4. single_kernel_mongo/config/models.py +12 -0
  5. single_kernel_mongo/config/relations.py +2 -1
  6. single_kernel_mongo/config/statuses.py +127 -20
  7. single_kernel_mongo/core/operator.py +68 -1
  8. single_kernel_mongo/core/structured_config.py +2 -0
  9. single_kernel_mongo/core/workload.py +10 -4
  10. single_kernel_mongo/events/cluster.py +5 -0
  11. single_kernel_mongo/events/sharding.py +3 -1
  12. single_kernel_mongo/events/tls.py +183 -157
  13. single_kernel_mongo/exceptions.py +0 -8
  14. single_kernel_mongo/lib/charms/operator_libs_linux/v1/systemd.py +288 -0
  15. single_kernel_mongo/lib/charms/tls_certificates_interface/v4/tls_certificates.py +1995 -0
  16. single_kernel_mongo/managers/cluster.py +70 -28
  17. single_kernel_mongo/managers/config.py +14 -8
  18. single_kernel_mongo/managers/mongo.py +1 -1
  19. single_kernel_mongo/managers/mongodb_operator.py +53 -56
  20. single_kernel_mongo/managers/mongos_operator.py +18 -20
  21. single_kernel_mongo/managers/sharding.py +154 -127
  22. single_kernel_mongo/managers/tls.py +223 -206
  23. single_kernel_mongo/state/charm_state.py +39 -16
  24. single_kernel_mongo/state/cluster_state.py +8 -0
  25. single_kernel_mongo/state/config_server_state.py +9 -0
  26. single_kernel_mongo/state/tls_state.py +39 -12
  27. single_kernel_mongo/templates/enable-transparent-huge-pages.service.j2 +14 -0
  28. single_kernel_mongo/utils/helpers.py +4 -19
  29. single_kernel_mongo/lib/charms/tls_certificates_interface/v3/tls_certificates.py +0 -2123
  30. {mongo_charms_single_kernel-1.8.7.dist-info → mongo_charms_single_kernel-1.8.9.dist-info}/WHEEL +0 -0
  31. {mongo_charms_single_kernel-1.8.7.dist-info → mongo_charms_single_kernel-1.8.9.dist-info}/licenses/LICENSE +0 -0
@@ -9,26 +9,27 @@ Handles MongoDB TLS Files.
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- import json
12
+ import base64
13
13
  import logging
14
+ import re
14
15
  import socket
15
16
  from typing import TYPE_CHECKING, TypedDict
16
17
 
17
- from cryptography import x509
18
- from cryptography.hazmat.backends import default_backend
18
+ from data_platform_helpers.advanced_statuses.protocol import (
19
+ ManagerStatusProtocol,
20
+ Scope,
21
+ StatusObject,
22
+ )
23
+ from ops.model import ModelError, SecretNotFoundError
19
24
 
20
- from single_kernel_mongo.config.literals import Substrates
25
+ from single_kernel_mongo.config.literals import Substrates, TLSType
21
26
  from single_kernel_mongo.config.statuses import TLSStatuses
22
27
  from single_kernel_mongo.core.operator import OperatorProtocol
23
28
  from single_kernel_mongo.core.structured_config import MongoDBRoles
24
- from single_kernel_mongo.exceptions import (
25
- UnknownCertificateAvailableError,
26
- UnknownCertificateExpiringError,
27
- WorkloadServiceError,
28
- )
29
- from single_kernel_mongo.lib.charms.tls_certificates_interface.v3.tls_certificates import (
30
- generate_csr,
31
- generate_private_key,
29
+ from single_kernel_mongo.exceptions import WorkloadServiceError
30
+ from single_kernel_mongo.lib.charms.tls_certificates_interface.v4.tls_certificates import (
31
+ CertificateRequestAttributes,
32
+ PrivateKey,
32
33
  )
33
34
  from single_kernel_mongo.state.charm_state import CharmState
34
35
  from single_kernel_mongo.state.tls_state import (
@@ -37,9 +38,8 @@ from single_kernel_mongo.state.tls_state import (
37
38
  SECRET_CHAIN_LABEL,
38
39
  SECRET_CSR_LABEL,
39
40
  SECRET_KEY_LABEL,
40
- WAIT_CERT_UPDATE,
41
+ TlsManagementState,
41
42
  )
42
- from single_kernel_mongo.utils.helpers import parse_tls_file
43
43
  from single_kernel_mongo.workload.mongodb_workload import MongoDBWorkload
44
44
  from single_kernel_mongo.workload.mongos_workload import MongosWorkload
45
45
 
@@ -57,7 +57,7 @@ class Sans(TypedDict):
57
57
  logger = logging.getLogger(__name__)
58
58
 
59
59
 
60
- class TLSManager:
60
+ class TLSManager(ManagerStatusProtocol):
61
61
  """Manager for building necessary files for mongodb."""
62
62
 
63
63
  def __init__(
@@ -65,71 +65,27 @@ class TLSManager:
65
65
  dependent: OperatorProtocol,
66
66
  workload: MongoDBWorkload | MongosWorkload,
67
67
  state: CharmState,
68
- substrate: Substrates,
69
68
  ) -> None:
70
69
  self.dependent = dependent
71
70
  self.charm = dependent.charm
72
71
  self.workload = workload
73
- self.state = state
74
- self.substrate = substrate
75
-
76
- def generate_certificate_request(self, param: str | None, internal: bool) -> bytes:
77
- """Generate a TLS Certificate request."""
78
- key: bytes
79
- if param is None:
80
- key = generate_private_key()
81
- else:
82
- key = parse_tls_file(param)
72
+ self.state: CharmState = state
73
+ self.substrate = self.dependent.substrate
74
+ self.name = "tls"
83
75
 
84
- sans = self.get_new_sans()
76
+ def get_certificate_request_attributes(self) -> CertificateRequestAttributes:
77
+ """Generate a certificate signing request attributes."""
85
78
  subject_name = self.state.get_subject_name()
86
- csr = generate_csr(
87
- private_key=key,
88
- subject=subject_name,
89
- organization=subject_name,
90
- sans=sans["sans_dns"],
91
- sans_ip=sans["sans_ips"],
92
- )
93
- self.state.tls.set_secret(internal, SECRET_KEY_LABEL, key.decode("utf-8"))
94
- self.state.tls.set_secret(internal, SECRET_CSR_LABEL, csr.decode("utf-8"))
95
- self.state.tls.set_secret(internal, SECRET_CERT_LABEL, None)
96
-
97
- label = "int" if internal else "ext"
98
-
99
- self.state.unit_peer_data.update({f"{label}_certs_subject": subject_name})
100
- return csr
101
-
102
- def generate_new_csr(self, internal: bool) -> tuple[bytes, bytes]:
103
- """Requests the renewal of a certificate.
104
-
105
- Returns:
106
- old_csr: The old certificate signing request.
107
- new_csr: the new_certificate signing request.
108
- """
109
- key_str = self.state.tls.get_secret(internal, SECRET_KEY_LABEL)
110
- old_csr_str = self.state.tls.get_secret(internal, SECRET_CSR_LABEL)
111
- if not key_str or not old_csr_str:
112
- raise Exception("Trying to renew a non existent certificate. Please fix.")
113
-
114
- key = key_str.encode("utf-8")
115
- old_csr = old_csr_str.encode("utf-8")
116
79
  sans = self.get_new_sans()
117
- subject_name = self.state.get_subject_name()
118
- new_csr = generate_csr(
119
- private_key=key,
120
- subject=subject_name,
80
+ return CertificateRequestAttributes(
81
+ common_name=subject_name,
82
+ sans_ip=frozenset(sans["sans_ips"]),
83
+ sans_dns=frozenset(sans["sans_dns"]),
121
84
  organization=subject_name,
122
- sans=sans["sans_dns"],
123
- sans_ip=sans["sans_ips"],
124
85
  )
125
- logger.debug("Requesting a certificate renewal.")
126
-
127
- self.state.tls.set_secret(internal, SECRET_CSR_LABEL, new_csr.decode("utf-8"))
128
- self.set_waiting_for_cert_to_update(waiting=True, internal=internal)
129
- return old_csr, new_csr
130
86
 
131
87
  def get_new_sans(self) -> Sans:
132
- """Create a list of DNS names for a MongoDB unit.
88
+ """Create a list of DNS names and IPs for a MongoDB unit.
133
89
 
134
90
  Returns:
135
91
  A list representing the hostnames of the MongoDB unit.
@@ -149,6 +105,7 @@ class TLSManager:
149
105
  if self.state.is_role(MongoDBRoles.MONGOS) and self.state.is_external_client:
150
106
  if host := self.state.unit_host:
151
107
  sans["sans_ips"].append(host)
108
+
152
109
  if (
153
110
  self.state.is_role(MongoDBRoles.MONGOS)
154
111
  and self.substrate == Substrates.VM
@@ -158,38 +115,18 @@ class TLSManager:
158
115
 
159
116
  return sans
160
117
 
161
- def get_current_sans(self, internal: bool) -> Sans | None:
162
- """Gets the current SANs for the unit cert."""
163
- # if unit has no certificates do not proceed.
164
- if not self.state.tls.is_tls_enabled(internal=internal):
165
- return None
166
-
167
- if not (pem_file := self.state.tls.get_secret(internal, SECRET_CERT_LABEL)):
168
- logger.info("No PEM file but TLS enabled.")
169
- raise Exception("No PEM file but TLS enabled. Please, fix.")
170
- try:
171
- cert = x509.load_pem_x509_certificate(pem_file.encode(), default_backend())
172
- sans = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value
173
- sans_ips = [str(san) for san in sans.get_values_for_type(x509.IPAddress)]
174
- sans_dns = [str(san) for san in sans.get_values_for_type(x509.DNSName)]
175
- except x509.ExtensionNotFound:
176
- sans_ips = []
177
- sans_dns = []
178
-
179
- return Sans(sans_ips=sorted(sans_ips), sans_dns=sorted(sans_dns))
180
-
181
- def get_tls_files(self, internal: bool) -> tuple[str | None, str | None]:
118
+ def get_tls_file_contents(self, internal: bool) -> tuple[str | None, str | None]:
182
119
  """Prepare TLS files in special MongoDB way.
183
120
 
184
121
  MongoDB needs two files:
185
122
  — CA file should have a full chain.
186
123
  — PEM file should have private key and certificate without certificate chain.
187
124
  """
188
- scope = "internal" if internal else "external"
125
+ scope = TLSType.PEER.value if internal else TLSType.CLIENT.value
189
126
  if not self.state.tls.is_tls_enabled(internal):
190
- logging.debug(f"TLS disabled for {scope}")
127
+ logging.debug(f"{scope} TLS disabled.")
191
128
  return None, None
192
- logging.debug(f"TLS *enabled* for {scope}, fetching data for CA and PEM files ")
129
+ logging.debug(f"{scope} TLS *enabled*, fetching data for CA and PEM files ")
193
130
 
194
131
  ca = self.state.tls.get_secret(internal, SECRET_CA_LABEL)
195
132
  chain = self.state.tls.get_secret(internal, SECRET_CHAIN_LABEL)
@@ -203,23 +140,26 @@ class TLSManager:
203
140
 
204
141
  return ca_file, pem_file
205
142
 
206
- def disable_certificates_for_unit(self):
143
+ def disable_certificates_for_unit(self, internal: bool):
207
144
  """Disables the certificates on relation broken."""
208
- for internal in [True, False]:
209
- self.state.tls.set_secret(internal, SECRET_CA_LABEL, None)
210
- self.state.tls.set_secret(internal, SECRET_CERT_LABEL, None)
211
- self.state.tls.set_secret(internal, SECRET_CHAIN_LABEL, None)
145
+ self.state.tls.set_secret(internal, SECRET_CA_LABEL, None)
146
+ self.state.tls.set_secret(internal, SECRET_CERT_LABEL, None)
147
+ self.state.tls.set_secret(internal, SECRET_CSR_LABEL, None)
148
+ self.state.tls.set_secret(internal, SECRET_CHAIN_LABEL, None)
149
+ self.state.tls.set_secret(internal, SECRET_KEY_LABEL, None)
212
150
 
213
- self.state.update_ca_secrets(new_ca=None)
151
+ if internal:
152
+ self.state.update_peer_ca_secrets(new_ca=None)
153
+ else:
154
+ self.dependent.state.update_client_ca_secrets(new_ca=None)
214
155
 
215
- self.delete_certificates_from_workload()
156
+ self.delete_certificates_from_workload(internal)
216
157
  self.dependent.restart_charm_services(force=True)
217
158
 
218
- def enable_certificates_for_unit(self):
159
+ def enable_certificates_for_unit(self, internal: bool):
219
160
  """Enables the new certificates for this unit."""
220
- self.delete_certificates_from_workload()
221
-
222
- self.push_tls_files_to_workload()
161
+ self.delete_certificates_from_workload(internal)
162
+ self.push_tls_files_to_workload(internal)
223
163
 
224
164
  if not self.state.db_initialised and self.state.is_role(MongoDBRoles.MONGOS):
225
165
  logger.info(
@@ -233,6 +173,11 @@ class TLSManager:
233
173
  statuses_state=self.state.statuses,
234
174
  component_name=self.charm.name,
235
175
  )
176
+
177
+ if self.is_waiting_for_a_cert():
178
+ logger.info("Still waiting for a certificate, delaying restart.")
179
+ return
180
+
236
181
  try:
237
182
  self.dependent.restart_charm_services(force=True)
238
183
  except WorkloadServiceError as e:
@@ -240,152 +185,224 @@ class TLSManager:
240
185
  logger.error("An exception occurred when starting mongod agent, error: %s.", str(e))
241
186
  return
242
187
 
243
- def delete_certificates_from_workload(self):
188
+ def delete_certificates_from_workload(self, internal: bool) -> None:
244
189
  """Deletes the certificates from the workload."""
245
- logger.info("Deleting TLS certificates from filesystem")
190
+ logger.info(
191
+ f"Deleting {TLSType.PEER.value if internal else TLSType.CLIENT.value} TLS certificates from filesystem"
192
+ )
246
193
 
247
- for file in self.workload.paths.tls_files:
194
+ path = (
195
+ self.workload.paths.tls_peer_files if internal else self.workload.paths.tls_client_files
196
+ )
197
+ for file in path:
248
198
  if self.workload.exists(file):
249
199
  self.workload.delete(file)
250
200
 
251
201
  if self.substrate == Substrates.VM:
252
202
  return
253
203
 
254
- local_keyfile_file = self.state.paths.ext_pem_file
255
- local_ca_file = self.state.paths.ext_ca_file
256
- for file in (local_keyfile_file, local_ca_file):
257
- if file.exists() and file.is_file():
258
- file.unlink()
204
+ if not internal:
205
+ local_keyfile_file = self.state.paths.ext_pem_file
206
+ local_ca_file = self.state.paths.ext_ca_file
207
+ for file in (local_keyfile_file, local_ca_file):
208
+ if file.exists() and file.is_file():
209
+ file.unlink()
259
210
 
260
- def push_tls_files_to_workload(self) -> None:
211
+ def push_tls_files_to_workload(self, internal: bool) -> None:
261
212
  """Pushes the TLS files on the workload."""
262
- external_ca, external_pem = self.get_tls_files(internal=False)
263
- internal_ca, internal_pem = self.get_tls_files(internal=True)
264
- if external_ca is not None:
265
- self.workload.write(self.workload.paths.ext_ca_file, external_ca)
266
- if external_pem is not None:
267
- self.workload.write(self.workload.paths.ext_pem_file, external_pem)
268
- if internal_ca is not None:
269
- self.workload.write(self.workload.paths.int_ca_file, internal_ca)
270
- if internal_pem is not None:
271
- self.workload.write(self.workload.paths.int_pem_file, internal_pem)
213
+ logger.info(
214
+ f"Pushing {TLSType.PEER.value if internal else TLSType.CLIENT.value} TLS certificates to filesystem"
215
+ )
216
+ ca, pem = self.get_tls_file_contents(internal=internal)
217
+
218
+ if internal:
219
+ if ca:
220
+ self.workload.write(self.workload.paths.int_ca_file, ca)
221
+ if pem:
222
+ self.workload.write(self.workload.paths.int_pem_file, pem)
223
+ else:
224
+ if ca:
225
+ self.workload.write(self.workload.paths.ext_ca_file, ca)
226
+ if pem:
227
+ self.workload.write(self.workload.paths.ext_pem_file, pem)
272
228
 
273
229
  if self.substrate == Substrates.VM:
274
230
  return
275
231
 
276
- if external_ca:
277
- self.state.paths.ext_ca_file.write_text(external_ca)
232
+ if not internal and ca:
233
+ self.state.paths.ext_ca_file.write_text(ca)
278
234
  self.state.paths.ext_ca_file.chmod(600)
279
- if external_pem:
280
- self.state.paths.ext_pem_file.write_text(external_pem)
235
+ if not internal and pem:
236
+ self.state.paths.ext_pem_file.write_text(pem)
281
237
  self.state.paths.ext_ca_file.chmod(600)
282
238
 
283
- def is_internal(self, certificate_signing_request: str) -> bool:
284
- """Checks if the CSR is internal or external."""
285
- int_csr = self.state.tls.get_secret(internal=True, label_name=SECRET_CSR_LABEL)
286
- ext_csr = self.state.tls.get_secret(internal=False, label_name=SECRET_CSR_LABEL)
287
- if ext_csr and certificate_signing_request.rstrip() == ext_csr.rstrip():
288
- logger.debug("The external TLS certificate available.")
289
- return False
290
- if int_csr and certificate_signing_request.rstrip() == int_csr.rstrip():
291
- logger.debug("The internal TLS certificate available.")
292
- return True
293
- raise UnknownCertificateAvailableError
294
-
295
239
  def set_certificates(
296
240
  self,
297
- certificate_signing_request: str,
298
241
  secret_chain: list[str] | None,
299
242
  certificate: str | None,
243
+ csr: str | None,
300
244
  ca: str | None,
245
+ private_key: str | None,
246
+ internal: bool,
301
247
  ):
302
248
  """Sets the certificates."""
303
- internal = self.is_internal(certificate_signing_request)
304
-
305
249
  self.state.tls.set_secret(
306
250
  internal,
307
251
  SECRET_CHAIN_LABEL,
308
- "\n".join(secret_chain) if secret_chain is not None else None,
252
+ "\n".join(secret_chain) if secret_chain else None,
309
253
  )
254
+ self.state.tls.set_secret(internal, SECRET_KEY_LABEL, private_key)
255
+ self.state.tls.set_secret(internal, SECRET_CSR_LABEL, csr)
310
256
  self.state.tls.set_secret(internal, SECRET_CERT_LABEL, certificate)
311
257
  self.state.tls.set_secret(internal, SECRET_CA_LABEL, ca)
312
- self.set_waiting_for_cert_to_update(internal=internal, waiting=False)
313
-
314
- def renew_expiring_certificate(self, certificate: str) -> tuple[bytes, bytes]:
315
- """Renew the expiring certificate."""
316
- for internal in (False, True):
317
- charm_cert = (
318
- self.state.tls.get_secret(internal=internal, label_name=SECRET_CERT_LABEL) or ""
319
- )
320
- if certificate.rstrip() == charm_cert.rstrip():
321
- logger.debug(
322
- f"The {'internal' if internal else 'external'} TLS certificate is expiring."
323
- )
324
- logger.debug("Generating a new Certificate Signing Request.")
325
- return self.generate_new_csr(internal)
326
- raise UnknownCertificateExpiringError
327
-
328
- def set_waiting_for_cert_to_update(self, internal: bool, waiting: bool) -> None:
329
- """Sets the databag."""
330
- scope = "int" if internal else "ext"
331
- label_name = f"{scope}-{WAIT_CERT_UPDATE}"
332
- self.state.unit_peer_data.update({label_name: json.dumps(waiting)})
333
-
334
- def is_set_waiting_for_cert_to_update(
335
- self,
336
- internal: bool = False,
337
- ) -> bool:
338
- """Returns True if we are waiting for a cert to update."""
339
- scope = "int" if internal else "ext"
340
- label_name = f"{scope}-{WAIT_CERT_UPDATE}"
258
+ logger.info(
259
+ f"{TLSType.PEER.value if internal else TLSType.CLIENT.value} certificate secrets updated."
260
+ )
341
261
 
342
- return json.loads(self.state.unit_peer_data.get(label_name) or "false")
262
+ def is_certificate_available(self, internal: bool) -> bool:
263
+ """Checks if we've received the expected certificate."""
264
+ csr = self.get_certificate_request_attributes()
265
+ cert, key = self.dependent.tls_events.tls_mapping[internal].get_assigned_certificate(csr)
266
+ return bool(cert and key)
343
267
 
344
- def is_waiting_for_both_certs(self) -> bool:
268
+ def is_waiting_for_a_cert(self) -> bool:
345
269
  """Returns a boolean indicating whether additional certs are needed."""
346
- if not self.state.tls.get_secret(internal=True, label_name=SECRET_CERT_LABEL):
347
- logger.debug("Waiting for internal certificate.")
270
+ if self.state.peer_tls_relation and not self.is_certificate_available(internal=True):
271
+ logger.debug("Waiting for peer certificate.")
348
272
  return True
349
- if not self.state.tls.get_secret(internal=False, label_name=SECRET_CERT_LABEL):
350
- logger.debug("Waiting for external certificate.")
273
+
274
+ if self.state.client_tls_relation and not self.is_certificate_available(internal=False):
275
+ logger.debug("Waiting for client certificate.")
351
276
  return True
352
277
 
353
278
  return False
354
279
 
355
- def update_tls_sans(self) -> None:
356
- """Emits a certificate expiring event when sans in current certificates are out of date.
280
+ def get_tls_management_state(self) -> TlsManagementState:
281
+ """Pre-checks on TLS certificates management."""
282
+ if self.dependent.refresh_in_progress and self.initial_integration():
283
+ return TlsManagementState.UPGRADE_IN_PROGRESS
284
+ if self.state.is_role(MongoDBRoles.MONGOS) and self.state.config_server_name is None:
285
+ return TlsManagementState.MONGOS_MISSING_CONFIG_SERVER
286
+ if not self.state.db_initialised:
287
+ if self.state.is_role(MongoDBRoles.MONGOS):
288
+ return TlsManagementState.MONGOS_DB_NOT_INITIALIZED
289
+ return TlsManagementState.DB_NOT_INTIALIZED
290
+ return TlsManagementState.EMPTY
291
+
292
+ def update_private_keys(self):
293
+ """Updates the private keys."""
294
+ peer_private_key = None
295
+ client_private_key = None
296
+
297
+ self.state.statuses.delete(
298
+ TLSStatuses.INVALID_PEER_PRIVATE_KEY.value,
299
+ scope="unit",
300
+ component=self.dependent.name,
301
+ )
302
+ self.state.statuses.delete(
303
+ TLSStatuses.INVALID_CLIENT_PRIVATE_KEY.value,
304
+ scope="unit",
305
+ component=self.dependent.name,
306
+ )
357
307
 
358
- This can occur for a variety of reasons:
359
- 1. Node port has been toggled on
360
- 2. Node port has been toggled off
361
- 3. The public K8s IP has changed
308
+ initial_peer_private_key = self.state.tls.peer_private_key
309
+ initial_client_private_key = self.state.tls.client_private_key
362
310
 
363
- Mongos k8s only.
364
- """
365
- for internal in [True, False]:
366
- # if the certificate has already been requested, we do not want to re-request
367
- # another one and lead to an infinite chain of certificate events.
368
- if self.is_set_waiting_for_cert_to_update(internal):
369
- continue
370
- current_sans = self.get_current_sans(internal)
371
- current_sans_ip = set(current_sans["sans_ips"]) if current_sans else set()
372
- expected_sans_ip = set(self.get_new_sans()["sans_ips"]) if current_sans else set()
373
- sans_ip_changed = current_sans_ip ^ expected_sans_ip
374
-
375
- if not sans_ip_changed:
376
- continue
311
+ if tls_peer_private_key_id := self.dependent.config.tls_peer_private_key_id:
312
+ if peer_private_key := self.update_private_key(tls_peer_private_key_id, internal=True):
313
+ self.dependent.tls_events.peer_certificate._private_key = peer_private_key
377
314
 
378
- logger.info(
379
- f"Mongos {self.charm.unit.name.split('/')[1]} updating certificate SANs - "
380
- f"OLD SANs = {current_sans_ip - expected_sans_ip}, "
381
- f"NEW SANs = {expected_sans_ip - current_sans_ip}"
315
+ if tls_client_private_key_id := self.dependent.config.tls_client_private_key_id:
316
+ if client_private_key := self.update_private_key(
317
+ tls_client_private_key_id, internal=False
318
+ ):
319
+ self.dependent.tls_events.client_certificate._private_key = client_private_key
320
+
321
+ if tls_peer_private_key_id and not peer_private_key:
322
+ self.state.statuses.add(
323
+ TLSStatuses.INVALID_PEER_PRIVATE_KEY.value,
324
+ scope="unit",
325
+ component=self.dependent.name,
326
+ )
327
+
328
+ if tls_client_private_key_id and not client_private_key:
329
+ self.state.statuses.add(
330
+ TLSStatuses.INVALID_CLIENT_PRIVATE_KEY.value,
331
+ scope="unit",
332
+ component=self.dependent.name,
333
+ )
334
+
335
+ peer_private_key_updated = peer_private_key is not None and (
336
+ initial_peer_private_key != peer_private_key
337
+ )
338
+ client_private_key_updated = client_private_key is not None and (
339
+ initial_client_private_key != client_private_key
340
+ )
341
+
342
+ # refresh certificates only if the value was updated.
343
+ if peer_private_key_updated or client_private_key_updated:
344
+ self.dependent.tls_events.refresh_certificates()
345
+
346
+ def update_private_key(self, private_key_secret_id: str, internal: bool) -> PrivateKey | None:
347
+ """Stores the new private key in the relation."""
348
+ if private_key := self.read_and_validate_private_key(private_key_secret_id):
349
+ self.state.tls.set_secret(internal, SECRET_KEY_LABEL, private_key.raw)
350
+ return private_key
351
+
352
+ logger.error(
353
+ f"Invalid {'peer' if internal else 'client'} private key provided, cannot update TLS certificates."
354
+ )
355
+ return None
356
+
357
+ def read_and_validate_private_key(self, private_key_secret_id: str) -> PrivateKey | None:
358
+ """Reads the private key from the secret and validates it."""
359
+ try:
360
+ secret_content = self.dependent.state.get_secret_from_id(private_key_secret_id).get(
361
+ "private-key"
382
362
  )
363
+ except (ModelError, SecretNotFoundError) as e:
364
+ logger.error(e)
365
+ return None
366
+
367
+ if secret_content is None:
368
+ logger.error(f"Secret {private_key_secret_id} does not contain a private key.")
369
+ return None
383
370
 
384
- old_csr, new_csr = self.generate_new_csr(internal)
385
- self.dependent.tls_events.certs_client.request_certificate_renewal(
386
- old_certificate_signing_request=old_csr,
387
- new_certificate_signing_request=new_csr,
371
+ try:
372
+ _private_key = (
373
+ secret_content
374
+ if re.match(r"(-+(BEGIN|END) [A-Z ]+-+)", secret_content)
375
+ else base64.b64decode(secret_content).decode("utf-8").strip()
388
376
  )
377
+ except UnicodeDecodeError:
378
+ logger.error("base64 decoding error, invalid key.")
379
+ return None
380
+ private_key = PrivateKey(raw=_private_key)
381
+ if not private_key.is_valid():
382
+ logger.error("Invalid private key format.")
383
+ return None
384
+
385
+ return private_key
386
+
387
+ def get_statuses(self, scope: Scope, recompute: bool = False) -> list[StatusObject]:
388
+ """Returns the current status of the tls-manager."""
389
+ charm_statuses = []
390
+
391
+ if not recompute:
392
+ return self.state.statuses.get(scope=scope, component=self.name).root
393
+
394
+ if scope == "app":
395
+ return []
396
+
397
+ if tls_peer_private_key_id := self.dependent.config.tls_peer_private_key_id:
398
+ if not self.update_private_key(tls_peer_private_key_id, internal=True):
399
+ charm_statuses.append(TLSStatuses.INVALID_PEER_PRIVATE_KEY.value)
400
+
401
+ if tls_client_private_key_id := self.dependent.config.tls_client_private_key_id:
402
+ if not self.update_private_key(tls_client_private_key_id, internal=False):
403
+ charm_statuses.append(TLSStatuses.INVALID_CLIENT_PRIVATE_KEY.value)
404
+
405
+ return charm_statuses
389
406
 
390
407
  def initial_integration(self) -> bool:
391
408
  """Checks if the certificate available event runs for the first time or not."""