Skip to content

Commit

Permalink
[DPE-3650] TLS now supported on sharded deployments (#370)
Browse files Browse the repository at this point in the history
Future PR:
 Add Useful TLS error messages i.e.
        1. Shard has TLS but config-server does not 
        2. Config-server has TLS but shard does not 
        3. CA mismatch

---------

Co-authored-by: Mehdi Bendriss <bendrissmehdi@gmail.com>
  • Loading branch information
MiaAltieri and Mehdi-Bendriss committed Mar 12, 2024
1 parent c2744cf commit 905ffca
Show file tree
Hide file tree
Showing 6 changed files with 429 additions and 79 deletions.
47 changes: 31 additions & 16 deletions lib/charms/mongodb/v0/mongodb_tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 8
LIBPATCH = 9

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -76,34 +76,38 @@ def _on_set_tls_private_key(self, event: ActionEvent) -> None:
"""Set the TLS private key, which will be used for requesting the certificate."""
logger.debug("Request to set TLS private key received.")
try:
self._request_certificate(event.params.get("external-key", None), internal=False)
self._request_certificate(event.params.get("internal-key", None), internal=True)
self.request_certificate(event.params.get("external-key", None), internal=False)
self.request_certificate(event.params.get("internal-key", None), internal=True)
logger.debug("Successfully set TLS private key.")
except ValueError as e:
event.fail(str(e))

def _request_certificate(
def request_certificate(
self,
param: Optional[str],
internal: bool,
):
"""Request TLS certificate."""
if param is None:
key = generate_private_key()
else:
key = self._parse_tls_file(param)

csr = generate_csr(
private_key=key,
subject=self.get_host(self.charm.unit),
organization=self.charm.app.name,
subject=self._get_subject_name(),
organization=self._get_subject_name(),
sans=self._get_sans(),
sans_ip=[str(self.charm.model.get_binding(self.peer_relation).network.bind_address)],
)

self.set_tls_secret(internal, Config.TLS.SECRET_KEY_LABEL, key.decode("utf-8"))
self.set_tls_secret(internal, Config.TLS.SECRET_CSR_LABEL, csr.decode("utf-8"))
self.set_tls_secret(internal, Config.TLS.SECRET_CERT_LABEL, None)

label = "int" if internal else "ext"
self.charm.unit_peer_data[f"{label}_certs_subject"] = self._get_subject_name()
self.charm.unit_peer_data[f"{label}_certs_subject"] = self._get_subject_name()

if self.charm.model.get_relation(Config.TLS.TLS_PEER_RELATION):
self.certs.request_certificate_creation(certificate_signing_request=csr)

Expand All @@ -124,8 +128,8 @@ def _parse_tls_file(raw_content: str) -> bytes:

def _on_tls_relation_joined(self, _: RelationJoinedEvent) -> None:
"""Request certificate when TLS relation joined."""
self._request_certificate(None, internal=True)
self._request_certificate(None, internal=False)
self.request_certificate(None, internal=True)
self.request_certificate(None, internal=False)

def _on_tls_relation_broken(self, event: RelationBrokenEvent) -> None:
"""Disable TLS when TLS relation broken."""
Expand All @@ -149,7 +153,7 @@ def _on_certificate_available(self, event: CertificateAvailableEvent) -> None:

if ext_csr and event.certificate_signing_request.rstrip() == ext_csr.rstrip():
logger.debug("The external TLS certificate available.")
internal = False # external crs
internal = False
elif int_csr and event.certificate_signing_request.rstrip() == int_csr.rstrip():
logger.debug("The internal TLS certificate available.")
internal = True
Expand All @@ -165,7 +169,7 @@ def _on_certificate_available(self, event: CertificateAvailableEvent) -> None:
self.set_tls_secret(internal, Config.TLS.SECRET_CERT_LABEL, event.certificate)
self.set_tls_secret(internal, Config.TLS.SECRET_CA_LABEL, event.ca)

if self._waiting_for_certs():
if self.waiting_for_certs():
logger.debug(
"Defer till both internal and external TLS certificates available to avoid second restart."
)
Expand All @@ -185,13 +189,13 @@ def _on_certificate_available(self, event: CertificateAvailableEvent) -> None:
else:
self.charm.unit.status = ActiveStatus()

def _waiting_for_certs(self):
def waiting_for_certs(self):
"""Returns a boolean indicating whether additional certs are needed."""
if not self.get_tls_secret(internal=True, label_name=Config.TLS.SECRET_CERT_LABEL):
logger.debug("Waiting for application certificate.")
logger.debug("Waiting for internal certificate.")
return True
if not self.get_tls_secret(internal=False, label_name=Config.TLS.SECRET_CERT_LABEL):
logger.debug("Waiting for application certificate.")
logger.debug("Waiting for external certificate.")
return True

return False
Expand Down Expand Up @@ -222,8 +226,8 @@ def _on_certificate_expiring(self, event: CertificateExpiringEvent) -> None:
old_csr = self.get_tls_secret(internal, Config.TLS.SECRET_CSR_LABEL).encode("utf-8")
new_csr = generate_csr(
private_key=key,
subject=self.get_host(self.charm.unit),
organization=self.charm.app.name,
subject=self._get_subject_name(),
organization=self._get_subject_name(),
sans=self._get_sans(),
sans_ip=[str(self.charm.model.get_binding(self.peer_relation).network.bind_address)],
)
Expand Down Expand Up @@ -293,3 +297,14 @@ def get_tls_secret(self, internal: bool, label_name: str) -> str:
scope = "int" if internal else "ext"
label_name = f"{scope}-{label_name}"
return self.charm.get_secret(UNIT_SCOPE, label_name)

def _get_subject_name(self) -> str:
"""Generate the subject name for CSR."""
# In sharded MongoDB deployments it is a requirement that all subject names match across
# all cluster components
if self.charm.is_role(Config.Role.SHARD):
# until integrated with config-server use current app name as
# subject name
return self.charm.shard.get_config_server_name() or self.charm.app.name

return self.charm.app.name
36 changes: 32 additions & 4 deletions lib/charms/mongodb/v1/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 4
LIBPATCH = 5

# path to store mongodb ketFile
KEY_FILE = "keyFile"
Expand Down Expand Up @@ -121,12 +121,40 @@ def get_mongos_args(
f"--configdb {config_server_db}",
# config server is already using 27017
f"--port {Config.MONGOS_PORT}",
f"--keyFile={full_conf_dir}/{KEY_FILE}",
"\n",
]

# TODO Future PR: support TLS on mongos
# TODO : generalise these into functions to be re-used
if config.tls_external:
cmd.extend(
[
f"--tlsCAFile={full_conf_dir}/{TLS_EXT_CA_FILE}",
f"--tlsCertificateKeyFile={full_conf_dir}/{TLS_EXT_PEM_FILE}",
# allow non-TLS connections
"--tlsMode=preferTLS",
"--tlsDisabledProtocols=TLS1_0,TLS1_1",
]
)

# internal TLS can be enabled only if external is enabled
if config.tls_internal and config.tls_external:
cmd.extend(
[
"--clusterAuthMode=x509",
"--tlsAllowInvalidCertificates",
f"--tlsClusterCAFile={full_conf_dir}/{TLS_INT_CA_FILE}",
f"--tlsClusterFile={full_conf_dir}/{TLS_INT_PEM_FILE}",
]
)
else:
# keyFile used for authentication replica set peers if no internal tls configured.
cmd.extend(
[
"--clusterAuthMode=keyFile",
f"--keyFile={full_conf_dir}/{KEY_FILE}",
]
)

cmd.append("\n")
return " ".join(cmd)


Expand Down
Loading

0 comments on commit 905ffca

Please sign in to comment.