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.

Files changed (28) hide show
  1. {mongo_charms_single_kernel-1.8.8.dist-info → mongo_charms_single_kernel-1.8.9.dist-info}/METADATA +1 -1
  2. {mongo_charms_single_kernel-1.8.8.dist-info → mongo_charms_single_kernel-1.8.9.dist-info}/RECORD +27 -27
  3. single_kernel_mongo/config/literals.py +7 -0
  4. single_kernel_mongo/config/relations.py +2 -1
  5. single_kernel_mongo/config/statuses.py +127 -20
  6. single_kernel_mongo/core/operator.py +7 -0
  7. single_kernel_mongo/core/structured_config.py +2 -0
  8. single_kernel_mongo/core/workload.py +10 -4
  9. single_kernel_mongo/events/cluster.py +5 -0
  10. single_kernel_mongo/events/sharding.py +3 -1
  11. single_kernel_mongo/events/tls.py +183 -157
  12. single_kernel_mongo/exceptions.py +0 -8
  13. single_kernel_mongo/lib/charms/tls_certificates_interface/v4/tls_certificates.py +1995 -0
  14. single_kernel_mongo/managers/cluster.py +70 -28
  15. single_kernel_mongo/managers/config.py +14 -8
  16. single_kernel_mongo/managers/mongo.py +1 -1
  17. single_kernel_mongo/managers/mongodb_operator.py +44 -22
  18. single_kernel_mongo/managers/mongos_operator.py +16 -20
  19. single_kernel_mongo/managers/sharding.py +154 -127
  20. single_kernel_mongo/managers/tls.py +223 -206
  21. single_kernel_mongo/state/charm_state.py +39 -16
  22. single_kernel_mongo/state/cluster_state.py +8 -0
  23. single_kernel_mongo/state/config_server_state.py +9 -0
  24. single_kernel_mongo/state/tls_state.py +39 -12
  25. single_kernel_mongo/utils/helpers.py +4 -19
  26. single_kernel_mongo/lib/charms/tls_certificates_interface/v3/tls_certificates.py +0 -2123
  27. {mongo_charms_single_kernel-1.8.8.dist-info → mongo_charms_single_kernel-1.8.9.dist-info}/WHEEL +0 -0
  28. {mongo_charms_single_kernel-1.8.8.dist-info → mongo_charms_single_kernel-1.8.9.dist-info}/licenses/LICENSE +0 -0
@@ -212,9 +212,14 @@ class CharmState(Object, StatusesStateProtocol):
212
212
  return set(self.model.relations[RelationNames.CONFIG_SERVER.value])
213
213
 
214
214
  @property
215
- def tls_relation(self) -> Relation | None:
216
- """The TLS relation."""
217
- return self.model.get_relation(ExternalRequirerRelations.TLS.value)
215
+ def client_tls_relation(self) -> Relation | None:
216
+ """The client TLS relation if it exists."""
217
+ return self.model.get_relation(ExternalRequirerRelations.CLIENT_TLS.value)
218
+
219
+ @property
220
+ def peer_tls_relation(self) -> Relation | None:
221
+ """The peer TLS relation if it exists."""
222
+ return self.model.get_relation(ExternalRequirerRelations.PEER_TLS.value)
218
223
 
219
224
  @property
220
225
  def s3_relation(self) -> Relation | None:
@@ -342,7 +347,11 @@ class CharmState(Object, StatusesStateProtocol):
342
347
  @property
343
348
  def tls(self) -> TLSState:
344
349
  """A view of the TLS status from the local unit databag."""
345
- return TLSState(relation=self.peer_relation, secrets=self.secrets)
350
+ return TLSState(
351
+ peer_relation=self.peer_tls_relation,
352
+ client_relation=self.client_tls_relation,
353
+ secrets=self.secrets,
354
+ )
346
355
 
347
356
  @property
348
357
  def ldap(self) -> LdapState:
@@ -554,8 +563,8 @@ class CharmState(Object, StatusesStateProtocol):
554
563
  return f"{replica_set_name}/{','.join(hosts)}"
555
564
 
556
565
  # END: Helpers
557
- def update_ca_secrets(self, new_ca: str | None) -> None:
558
- """Updates the CA secret in the cluster and config-server relations."""
566
+ def _update_ca_secrets(self, new_ca: str | None, cluster_key: str, sharding_key: str) -> None:
567
+ """Updates the CA secret for the right values on the right fields."""
559
568
  # Only the leader can update the databag
560
569
  if not self.charm.unit.is_leader():
561
570
  return
@@ -564,22 +573,36 @@ class CharmState(Object, StatusesStateProtocol):
564
573
  for relation in self.cluster_relations:
565
574
  if new_ca is None:
566
575
  self.cluster_provider_data_interface.delete_relation_data(
567
- relation.id, [ClusterStateKeys.INT_CA_SECRET.value]
576
+ relation.id, [cluster_key]
568
577
  )
569
578
  else:
570
579
  self.cluster_provider_data_interface.update_relation_data(
571
- relation.id, {ClusterStateKeys.INT_CA_SECRET.value: new_ca}
580
+ relation.id, {cluster_key: new_ca}
572
581
  )
573
582
  for relation in self.config_server_relation:
574
583
  if new_ca is None:
575
- self.config_server_data_interface.delete_relation_data(
576
- relation.id, [AppShardingComponentKeys.INT_CA_SECRET.value]
577
- )
584
+ self.config_server_data_interface.delete_relation_data(relation.id, [sharding_key])
578
585
  else:
579
586
  self.config_server_data_interface.update_relation_data(
580
- relation.id, {AppShardingComponentKeys.INT_CA_SECRET.value: new_ca}
587
+ relation.id, {sharding_key: new_ca}
581
588
  )
582
589
 
590
+ def update_peer_ca_secrets(self, new_ca: str | None) -> None:
591
+ """Updates the peer CA secret in the cluster and config-server relations."""
592
+ self._update_ca_secrets(
593
+ new_ca=new_ca,
594
+ cluster_key=ClusterStateKeys.INT_CA_SECRET.value,
595
+ sharding_key=AppShardingComponentKeys.INT_CA_SECRET.value,
596
+ )
597
+
598
+ def update_client_ca_secrets(self, new_ca: str | None) -> None:
599
+ """Updates the client CA secret in the cluster and config-server relations."""
600
+ self._update_ca_secrets(
601
+ new_ca=new_ca,
602
+ cluster_key=ClusterStateKeys.EXT_CA_SECRET.value,
603
+ sharding_key=AppShardingComponentKeys.EXT_CA_SECRET.value,
604
+ )
605
+
583
606
  def is_scaling_down(self, rel_id: int) -> bool:
584
607
  """Returns True if the application is scaling down."""
585
608
  rel_departed_key = generate_relation_departed_key(rel_id)
@@ -606,7 +629,7 @@ class CharmState(Object, StatusesStateProtocol):
606
629
  return False
607
630
 
608
631
  # We can't check if we don't have a valid certificate
609
- if self.shard_state.internal_ca_secret is not None and not self.tls.external_enabled:
632
+ if self.shard_state.external_ca_secret is not None and not self.tls.client_enabled:
610
633
  return False
611
634
 
612
635
  try:
@@ -665,7 +688,7 @@ class CharmState(Object, StatusesStateProtocol):
665
688
  hosts=hosts or user.hosts,
666
689
  port=MongoPorts.MONGODB_PORT.value,
667
690
  roles=user.roles,
668
- tls_enabled=self.tls.external_enabled,
691
+ tls_enabled=self.tls.client_enabled,
669
692
  tls_external_keyfile=self.paths.ext_pem_file,
670
693
  tls_external_ca=self.paths.ext_ca_file,
671
694
  standalone=standalone,
@@ -695,7 +718,7 @@ class CharmState(Object, StatusesStateProtocol):
695
718
  hosts=hosts or user.hosts,
696
719
  port=MongoPorts.MONGOS_PORT.value,
697
720
  roles=user.roles,
698
- tls_enabled=self.tls.external_enabled,
721
+ tls_enabled=self.tls.client_enabled,
699
722
  tls_external_keyfile=self.paths.ext_pem_file,
700
723
  tls_external_ca=self.paths.ext_ca_file,
701
724
  )
@@ -751,7 +774,7 @@ class CharmState(Object, StatusesStateProtocol):
751
774
  # unlike the vm mongos charm, the K8s charm does not communicate with the unix socket
752
775
  port=port,
753
776
  roles={RoleNames.ADMIN},
754
- tls_enabled=self.tls.external_enabled,
777
+ tls_enabled=self.tls.client_enabled,
755
778
  tls_external_keyfile=self.paths.ext_pem_file,
756
779
  tls_external_ca=self.paths.ext_ca_file,
757
780
  )
@@ -23,6 +23,7 @@ class ClusterStateKeys(str, Enum):
23
23
  CONFIG_SERVER_DB = "config-server-db"
24
24
  KEYFILE = "key-file"
25
25
  INT_CA_SECRET = "int-ca-secret"
26
+ EXT_CA_SECRET = "ext-ca-secret"
26
27
  LDAP_USER_TO_DN_MAPPING = "ldap-user-to-dn-mapping"
27
28
  LDAP_HASH = "ldap-hash"
28
29
 
@@ -74,6 +75,13 @@ class ClusterState(AbstractRelationState[Data]):
74
75
  return None
75
76
  return self.relation_data.get(ClusterStateKeys.INT_CA_SECRET.value, None)
76
77
 
78
+ @property
79
+ def external_ca_secret(self) -> str | None:
80
+ """Returns the external CA secret."""
81
+ if not self.relation:
82
+ return None
83
+ return self.relation_data.get(ClusterStateKeys.EXT_CA_SECRET.value, None)
84
+
77
85
  @property
78
86
  def ldap_user_to_dn_mapping(self) -> str | None:
79
87
  """Returns the userToDNMapping config option shared by the config-server."""
@@ -23,6 +23,7 @@ class AppShardingComponentKeys(str, Enum):
23
23
  HOST = "host"
24
24
  KEY_FILE = "key-file"
25
25
  INT_CA_SECRET = "int-ca-secret"
26
+ EXT_CA_SECRET = "ext-ca-secret"
26
27
  BACKUP_CA_SECRET = "backup-ca-secret"
27
28
 
28
29
  # We don't use those except to check if we've received credentials
@@ -35,6 +36,7 @@ SECRETS_FIELDS = [
35
36
  "backup-password",
36
37
  "key-file",
37
38
  "int-ca-secret",
39
+ "ext-ca-secret",
38
40
  "backup-ca-secret",
39
41
  ]
40
42
 
@@ -75,6 +77,13 @@ class AppShardingComponentState(AbstractRelationState[Data]):
75
77
  return None
76
78
  return self.relation_data.get(AppShardingComponentKeys.INT_CA_SECRET.value, None)
77
79
 
80
+ @property
81
+ def external_ca_secret(self) -> str | None:
82
+ """Returns the external CA secret."""
83
+ if not self.relation:
84
+ return None
85
+ return self.relation_data.get(AppShardingComponentKeys.EXT_CA_SECRET.value, None)
86
+
78
87
  @property
79
88
  def keyfile(self) -> str | None:
80
89
  """Returns the keyfile."""
@@ -4,44 +4,59 @@
4
4
 
5
5
  """The TLS state."""
6
6
 
7
+ from enum import Enum
8
+
7
9
  from ops import Relation
8
10
  from ops.model import Unit
9
11
 
10
12
  from single_kernel_mongo.config.literals import Scope
11
13
  from single_kernel_mongo.core.secrets import SecretCache
14
+ from single_kernel_mongo.lib.charms.tls_certificates_interface.v4.tls_certificates import PrivateKey
12
15
 
13
16
  SECRET_KEY_LABEL = "key-secret"
14
17
  SECRET_CA_LABEL = "ca-secret"
15
- SECRET_CERT_LABEL = "cert-secret"
16
18
  SECRET_CSR_LABEL = "csr-secret"
19
+ SECRET_CERT_LABEL = "cert-secret"
17
20
  SECRET_CHAIN_LABEL = "chain-secret"
18
- WAIT_CERT_UPDATE = "wait-cert-updated"
19
21
  INT_CERT_SECRET_KEY = "int-cert-secret"
20
22
  EXT_CERT_SECRET_KEY = "ext-cert-secret"
21
23
 
22
24
 
25
+ class TlsManagementState(Enum):
26
+ """TLS management state that can be mapped to a status."""
27
+
28
+ EMPTY = ""
29
+ UPGRADE_IN_PROGRESS = "Upgrade in progress."
30
+ DB_NOT_INTIALIZED = "DB is not initialized."
31
+ MONGOS_MISSING_CONFIG_SERVER = "mongos is not running (not integrated to config-server)."
32
+ MONGOS_DB_NOT_INITIALIZED = "mongos DB is not initialized."
33
+
34
+
23
35
  class TLSState:
24
36
  """The stored state for the TLS relation."""
25
37
 
26
38
  component: Unit
27
39
 
28
- def __init__(self, relation: Relation | None, secrets: SecretCache):
29
- self.relation = relation
40
+ def __init__(
41
+ self, peer_relation: Relation | None, client_relation: Relation | None, secrets: SecretCache
42
+ ):
43
+ self.peer_relation = peer_relation
44
+ self.client_relation = client_relation
30
45
  self.secrets = secrets
31
46
 
32
47
  @property
33
- def internal_enabled(self) -> bool:
34
- """Is internal TLS enabled."""
48
+ def peer_enabled(self) -> bool:
49
+ """Is peer TLS enabled."""
35
50
  return (
36
- self.relation is not None
51
+ self.peer_relation is not None
37
52
  and self.secrets.get_for_key(Scope.UNIT, INT_CERT_SECRET_KEY) is not None
38
53
  )
39
54
 
40
55
  @property
41
- def external_enabled(self) -> bool:
42
- """Is external TLS enabled."""
56
+ def client_enabled(self) -> bool:
57
+ """Is client TLS enabled."""
43
58
  return (
44
- self.relation is not None
59
+ self.client_relation is not None
45
60
  and self.secrets.get_for_key(Scope.UNIT, EXT_CERT_SECRET_KEY) is not None
46
61
  )
47
62
 
@@ -49,9 +64,9 @@ class TLSState:
49
64
  """Is TLS enabled for ::internal."""
50
65
  match internal:
51
66
  case True:
52
- return self.internal_enabled
67
+ return self.peer_enabled
53
68
  case False:
54
- return self.external_enabled
69
+ return self.client_enabled
55
70
 
56
71
  def set_secret(self, internal: bool, label_name: str, contents: str | None) -> None:
57
72
  """Sets TLS secret, based on whether or not it is related to internal connections."""
@@ -67,3 +82,15 @@ class TLSState:
67
82
  scope = "int" if internal else "ext"
68
83
  label_name = f"{scope}-{label_name}"
69
84
  return self.secrets.get_for_key(Scope.UNIT, label_name)
85
+
86
+ @property
87
+ def client_private_key(self) -> PrivateKey | None:
88
+ """Private key for the client relation."""
89
+ private_key_str = self.get_secret(internal=False, label_name=SECRET_KEY_LABEL)
90
+ return PrivateKey(private_key_str) if private_key_str else None
91
+
92
+ @property
93
+ def peer_private_key(self) -> PrivateKey | None:
94
+ """Private key for the peer relation."""
95
+ private_key_str = self.get_secret(internal=True, label_name=SECRET_KEY_LABEL)
96
+ return PrivateKey(private_key_str) if private_key_str else None
@@ -4,8 +4,6 @@
4
4
 
5
5
  """Some helpers functions that doesn't belong anywhere else."""
6
6
 
7
- import base64
8
- import re
9
7
  from functools import partial
10
8
  from logging import getLogger
11
9
 
@@ -18,21 +16,6 @@ from single_kernel_mongo.exceptions import InvalidCharmKindError
18
16
  logger = getLogger(__name__)
19
17
 
20
18
 
21
- def parse_tls_file(raw_content: str) -> bytes:
22
- """Parse TLS files from both plain text or base64 format."""
23
- if re.match(r"(-+(BEGIN|END) [A-Z ]+-+)", raw_content):
24
- return (
25
- re.sub(
26
- r"(-+(BEGIN|END) [A-Z ]+-+)",
27
- "\\1",
28
- raw_content,
29
- )
30
- .rstrip()
31
- .encode("utf-8")
32
- )
33
- return base64.b64decode(raw_content)
34
-
35
-
36
19
  def generate_relation_departed_key(rel_id: int) -> str: # noqa
37
20
  return f"relation_{rel_id}_departed"
38
21
 
@@ -61,7 +44,7 @@ def hostname_from_shardname(host: str) -> str:
61
44
  return host.split("/")[0]
62
45
 
63
46
 
64
- def is_valid_ldapusertodnmapping(ldap_user_to_dn_mapping: str) -> bool:
47
+ def is_valid_ldapusertodnmapping(ldap_user_to_dn_mapping: str | None) -> bool:
65
48
  """Validates the mapping, returning a boolean."""
66
49
  if not ldap_user_to_dn_mapping:
67
50
  return True
@@ -73,7 +56,9 @@ def is_valid_ldapusertodnmapping(ldap_user_to_dn_mapping: str) -> bool:
73
56
  return False
74
57
 
75
58
 
76
- def is_valid_ldap_options(ldap_user_to_dn_mapping: str, ldap_query_template: str) -> bool:
59
+ def is_valid_ldap_options(
60
+ ldap_user_to_dn_mapping: str | None, ldap_query_template: str | None
61
+ ) -> bool:
77
62
  """Validates the combination of the two LDAP options.
78
63
 
79
64
  Rules are the following: