mongo-charms-single-kernel 1.8.8__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.
- {mongo_charms_single_kernel-1.8.8.dist-info → mongo_charms_single_kernel-1.8.9.dist-info}/METADATA +1 -1
- {mongo_charms_single_kernel-1.8.8.dist-info → mongo_charms_single_kernel-1.8.9.dist-info}/RECORD +27 -27
- single_kernel_mongo/config/literals.py +7 -0
- single_kernel_mongo/config/relations.py +2 -1
- single_kernel_mongo/config/statuses.py +127 -20
- single_kernel_mongo/core/operator.py +7 -0
- single_kernel_mongo/core/structured_config.py +2 -0
- single_kernel_mongo/core/workload.py +10 -4
- single_kernel_mongo/events/cluster.py +5 -0
- single_kernel_mongo/events/sharding.py +3 -1
- single_kernel_mongo/events/tls.py +183 -157
- single_kernel_mongo/exceptions.py +0 -8
- single_kernel_mongo/lib/charms/tls_certificates_interface/v4/tls_certificates.py +1995 -0
- single_kernel_mongo/managers/cluster.py +70 -28
- single_kernel_mongo/managers/config.py +14 -8
- single_kernel_mongo/managers/mongo.py +1 -1
- single_kernel_mongo/managers/mongodb_operator.py +44 -22
- single_kernel_mongo/managers/mongos_operator.py +16 -20
- single_kernel_mongo/managers/sharding.py +154 -127
- single_kernel_mongo/managers/tls.py +223 -206
- single_kernel_mongo/state/charm_state.py +39 -16
- single_kernel_mongo/state/cluster_state.py +8 -0
- single_kernel_mongo/state/config_server_state.py +9 -0
- single_kernel_mongo/state/tls_state.py +39 -12
- single_kernel_mongo/utils/helpers.py +4 -19
- single_kernel_mongo/lib/charms/tls_certificates_interface/v3/tls_certificates.py +0 -2123
- {mongo_charms_single_kernel-1.8.8.dist-info → mongo_charms_single_kernel-1.8.9.dist-info}/WHEEL +0 -0
- {mongo_charms_single_kernel-1.8.8.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
|
|
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
|
|
18
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
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 =
|
|
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
|
|
127
|
+
logging.debug(f"{scope} TLS disabled.")
|
|
191
128
|
return None, None
|
|
192
|
-
logging.debug(f"TLS *enabled
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
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(
|
|
190
|
+
logger.info(
|
|
191
|
+
f"Deleting {TLSType.PEER.value if internal else TLSType.CLIENT.value} TLS certificates from filesystem"
|
|
192
|
+
)
|
|
246
193
|
|
|
247
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
file.
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
|
277
|
-
self.state.paths.ext_ca_file.write_text(
|
|
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
|
|
280
|
-
self.state.paths.ext_pem_file.write_text(
|
|
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
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
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
|
|
268
|
+
def is_waiting_for_a_cert(self) -> bool:
|
|
345
269
|
"""Returns a boolean indicating whether additional certs are needed."""
|
|
346
|
-
if
|
|
347
|
-
logger.debug("Waiting for
|
|
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
|
-
|
|
350
|
-
|
|
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
|
|
356
|
-
"""
|
|
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
|
-
|
|
359
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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."""
|