Skip to content

Commit 3c2c894

Browse files
[DPE-6345] LDAP II: Include charm libs (#824)
1 parent 0ebf12d commit 3c2c894

File tree

12 files changed

+1174
-17
lines changed

12 files changed

+1174
-17
lines changed

lib/charms/certificate_transfer_interface/v0/certificate_transfer.py

Lines changed: 432 additions & 0 deletions
Large diffs are not rendered by default.

lib/charms/glauth_k8s/v0/ldap.py

Lines changed: 571 additions & 0 deletions
Large diffs are not rendered by default.

lib/charms/postgresql_k8s/v0/postgresql_tls.py

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
This class handles certificate request and renewal through
77
the interaction with the TLS Certificates Operator.
88
9-
This library needs that https://charmhub.io/tls-certificates-interface/libraries/tls_certificates
10-
library is imported to work.
9+
This library needs that the following libraries are imported to work:
10+
- https://charmhub.io/certificate-transfer-interface/libraries/certificate_transfer
11+
- https://charmhub.io/tls-certificates-interface/libraries/tls_certificates
1112
1213
It also needs the following methods in the charm class:
1314
— get_hostname_by_unit: to retrieve the DNS hostname of the unit.
@@ -24,6 +25,15 @@
2425
import socket
2526
from typing import List, Optional
2627

28+
from charms.certificate_transfer_interface.v0.certificate_transfer import (
29+
CertificateAvailableEvent as CertificateAddedEvent,
30+
)
31+
from charms.certificate_transfer_interface.v0.certificate_transfer import (
32+
CertificateRemovedEvent as CertificateRemovedEvent,
33+
)
34+
from charms.certificate_transfer_interface.v0.certificate_transfer import (
35+
CertificateTransferRequires,
36+
)
2737
from charms.tls_certificates_interface.v2.tls_certificates import (
2838
CertificateAvailableEvent,
2939
CertificateExpiringEvent,
@@ -45,11 +55,12 @@
4555

4656
# Increment this PATCH version before using `charmcraft publish-lib` or reset
4757
# to 0 if you are raising the major API version.
48-
LIBPATCH = 13
58+
LIBPATCH = 14
4959

5060
logger = logging.getLogger(__name__)
5161
SCOPE = "unit"
52-
TLS_RELATION = "certificates"
62+
TLS_CREATION_RELATION = "certificates"
63+
TLS_TRANSFER_RELATION = "receive-ca-cert"
5364

5465

5566
class PostgreSQLTLS(Object):
@@ -63,18 +74,29 @@ def __init__(
6374
self.charm = charm
6475
self.peer_relation = peer_relation
6576
self.additional_dns_names = additional_dns_names or []
66-
self.certs = TLSCertificatesRequiresV2(self.charm, TLS_RELATION)
77+
self.certs_creation = TLSCertificatesRequiresV2(self.charm, TLS_CREATION_RELATION)
78+
self.certs_transfer = CertificateTransferRequires(self.charm, TLS_TRANSFER_RELATION)
6779
self.framework.observe(
6880
self.charm.on.set_tls_private_key_action, self._on_set_tls_private_key
6981
)
7082
self.framework.observe(
71-
self.charm.on[TLS_RELATION].relation_joined, self._on_tls_relation_joined
83+
self.charm.on[TLS_CREATION_RELATION].relation_joined, self._on_tls_relation_joined
84+
)
85+
self.framework.observe(
86+
self.charm.on[TLS_CREATION_RELATION].relation_broken, self._on_tls_relation_broken
87+
)
88+
self.framework.observe(
89+
self.certs_creation.on.certificate_available, self._on_certificate_available
90+
)
91+
self.framework.observe(
92+
self.certs_creation.on.certificate_expiring, self._on_certificate_expiring
7293
)
7394
self.framework.observe(
74-
self.charm.on[TLS_RELATION].relation_broken, self._on_tls_relation_broken
95+
self.certs_transfer.on.certificate_available, self._on_certificate_added
96+
)
97+
self.framework.observe(
98+
self.certs_transfer.on.certificate_removed, self._on_certificate_removed
7599
)
76-
self.framework.observe(self.certs.on.certificate_available, self._on_certificate_available)
77-
self.framework.observe(self.certs.on.certificate_expiring, self._on_certificate_expiring)
78100

79101
def _on_set_tls_private_key(self, event: ActionEvent) -> None:
80102
"""Set the TLS private key, which will be used for requesting the certificate."""
@@ -93,8 +115,8 @@ def _request_certificate(self, param: Optional[str]):
93115
self.charm.set_secret(SCOPE, "key", key.decode("utf-8"))
94116
self.charm.set_secret(SCOPE, "csr", csr.decode("utf-8"))
95117

96-
if self.charm.model.get_relation(TLS_RELATION):
97-
self.certs.request_certificate_creation(certificate_signing_request=csr)
118+
if self.charm.model.get_relation(TLS_CREATION_RELATION):
119+
self.certs_creation.request_certificate_creation(certificate_signing_request=csr)
98120

99121
@staticmethod
100122
def _parse_tls_file(raw_content: str) -> bytes:
@@ -117,6 +139,7 @@ def _on_tls_relation_broken(self, event: RelationBrokenEvent) -> None:
117139
self.charm.set_secret(SCOPE, "ca", None)
118140
self.charm.set_secret(SCOPE, "cert", None)
119141
self.charm.set_secret(SCOPE, "chain", None)
142+
120143
if not self.charm.update_config():
121144
logger.debug("Cannot update config at this moment")
122145
event.defer()
@@ -163,12 +186,52 @@ def _on_certificate_expiring(self, event: CertificateExpiringEvent) -> None:
163186
subject=self.charm.get_hostname_by_unit(self.charm.unit.name),
164187
**self._get_sans(),
165188
)
166-
self.certs.request_certificate_renewal(
189+
self.certs_creation.request_certificate_renewal(
167190
old_certificate_signing_request=old_csr,
168191
new_certificate_signing_request=new_csr,
169192
)
170193
self.charm.set_secret(SCOPE, "csr", new_csr.decode("utf-8"))
171194

195+
def _on_certificate_added(self, event: CertificateAddedEvent) -> None:
196+
"""Enable TLS when TLS certificate is added."""
197+
relation = self.charm.model.get_relation(TLS_TRANSFER_RELATION, event.relation_id)
198+
if relation is None:
199+
logger.error("Relationship not established anymore.")
200+
return
201+
202+
secret_name = f"ca-{relation.app.name}"
203+
self.charm.set_secret(SCOPE, secret_name, event.ca)
204+
205+
try:
206+
if not self.charm.push_ca_file_into_workload(secret_name):
207+
logger.debug("Cannot push TLS certificates at this moment")
208+
event.defer()
209+
return
210+
except (PebbleConnectionError, PathError, ProtocolError, RetryError) as e:
211+
logger.error("Cannot push TLS certificates: %r", e)
212+
event.defer()
213+
return
214+
215+
def _on_certificate_removed(self, event: CertificateRemovedEvent) -> None:
216+
"""Disable TLS when TLS certificate is removed."""
217+
relation = self.charm.model.get_relation(TLS_TRANSFER_RELATION, event.relation_id)
218+
if relation is None:
219+
logger.error("Relationship not established anymore.")
220+
return
221+
222+
secret_name = f"ca-{relation.app.name}"
223+
self.charm.set_secret(SCOPE, secret_name, None)
224+
225+
try:
226+
if not self.charm.clean_ca_file_from_workload(secret_name):
227+
logger.debug("Cannot clean CA certificates at this moment")
228+
event.defer()
229+
return
230+
except (PebbleConnectionError, PathError, ProtocolError, RetryError) as e:
231+
logger.error("Cannot clean CA certificates: %r", e)
232+
event.defer()
233+
return
234+
172235
def _get_sans(self) -> dict:
173236
"""Create a list of Subject Alternative Names for a PostgreSQL unit.
174237

metadata.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,17 @@ requires:
4949
interface: tls-certificates
5050
limit: 1
5151
optional: true
52+
receive-ca-cert:
53+
interface: certificate_transfer
54+
optional: true
5255
s3-parameters:
5356
interface: s3
5457
limit: 1
5558
optional: true
59+
ldap:
60+
interface: ldap
61+
limit: 1
62+
optional: true
5663
tracing:
5764
interface: tracing
5865
limit: 1

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ pydantic = "^1.10"
3030
cosl = ">=0.0.50"
3131
# tls_certificates_interface/v2/tls_certificates.py
3232
cryptography = "*"
33+
# certificate_transfer_interface/v0/certificate_transfer.py
34+
# tls_certificates_interface/v2/tls_certificates.py
3335
jsonschema = "*"
3436
# tempo_coordinator_k8s/v0/charm_tracing.py
3537
opentelemetry-exporter-otlp-proto-http = "1.21.0"

src/charm.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
TLS_KEY_FILE,
9797
TRACING_PROTOCOL,
9898
UNIT_SCOPE,
99+
UPDATE_CERTS_BIN_PATH,
99100
USER,
100101
USER_PASSWORD_KEY,
101102
)
@@ -191,6 +192,8 @@ def __init__(self, *args):
191192
self.framework.observe(self.on.update_status, self._on_update_status)
192193
self.cluster_name = self.app.name
193194
self._member_name = self.unit.name.replace("/", "-")
195+
196+
self._certs_path = "/usr/local/share/ca-certificates"
194197
self._storage_path = self.meta.storages["pgdata"].location
195198

196199
self.upgrade = PostgreSQLUpgrade(
@@ -1803,6 +1806,33 @@ def push_tls_files_to_workload(self) -> bool:
18031806
logger.exception("TLS files failed to push. Error in config update")
18041807
return False
18051808

1809+
def push_ca_file_into_workload(self, secret_name: str) -> bool:
1810+
"""Move CA certificates file into the PostgreSQL storage path."""
1811+
certs = self.get_secret(UNIT_SCOPE, secret_name)
1812+
if certs is not None:
1813+
certs_file = Path(self._certs_path, f"{secret_name}.crt")
1814+
certs_file.write_text(certs)
1815+
subprocess.check_call([UPDATE_CERTS_BIN_PATH]) # noqa: S603
1816+
1817+
try:
1818+
return self.update_config()
1819+
except Exception:
1820+
logger.exception("CA file failed to push. Error in config update")
1821+
return False
1822+
1823+
def clean_ca_file_from_workload(self, secret_name: str) -> bool:
1824+
"""Cleans up CA certificates from the PostgreSQL storage path."""
1825+
certs_file = Path(self._certs_path, f"{secret_name}.crt")
1826+
certs_file.unlink()
1827+
1828+
subprocess.check_call([UPDATE_CERTS_BIN_PATH]) # noqa: S603
1829+
1830+
try:
1831+
return self.update_config()
1832+
except Exception:
1833+
logger.exception("CA file failed to clean. Error in config update")
1834+
return False
1835+
18061836
def _reboot_on_detached_storage(self, event: EventBase) -> None:
18071837
"""Reboot on detached storage.
18081838

src/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
POSTGRESQL_DATA_PATH = f"{SNAP_DATA_PATH}/postgresql"
5757
POSTGRESQL_LOGS_PATH = f"{SNAP_LOGS_PATH}/postgresql"
5858

59+
UPDATE_CERTS_BIN_PATH = "/usr/sbin/update-ca-certificates"
60+
5961
PGBACKREST_CONFIGURATION_FILE = f"--config={PGBACKREST_CONF_PATH}/pgbackrest.conf"
6062

6163
METRICS_PORT = "9187"

tests/integration/helpers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1131,7 +1131,9 @@ async def backup_operations(
11311131
config={"profile": "testing"},
11321132
)
11331133

1134-
await ops_test.model.relate(database_app_name, tls_certificates_app_name)
1134+
await ops_test.model.relate(
1135+
f"{database_app_name}:certificates", f"{tls_certificates_app_name}:certificates"
1136+
)
11351137
async with ops_test.fast_forward(fast_interval="60s"):
11361138
await ops_test.model.wait_for_idle(apps=[database_app_name], status="active", timeout=1000)
11371139

tests/integration/test_backups_pitr_aws.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ async def pitr_backup_operations(
6868
logger.info(
6969
"integrating self-signed-certificates with postgresql and waiting them to stabilize"
7070
)
71-
await ops_test.model.relate(database_app_name, tls_certificates_app_name)
71+
await ops_test.model.relate(
72+
f"{database_app_name}:certificates", f"{tls_certificates_app_name}:certificates"
73+
)
7274
async with ops_test.fast_forward(fast_interval="60s"):
7375
await ops_test.model.wait_for_idle(
7476
apps=[database_app_name, tls_certificates_app_name], status="active", timeout=1000

tests/integration/test_backups_pitr_gcp.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ async def pitr_backup_operations(
6868
logger.info(
6969
"integrating self-signed-certificates with postgresql and waiting them to stabilize"
7070
)
71-
await ops_test.model.relate(database_app_name, tls_certificates_app_name)
71+
await ops_test.model.relate(
72+
f"{database_app_name}:certificates", f"{tls_certificates_app_name}:certificates"
73+
)
7274
async with ops_test.fast_forward(fast_interval="60s"):
7375
await ops_test.model.wait_for_idle(
7476
apps=[database_app_name, tls_certificates_app_name], status="active", timeout=1000

0 commit comments

Comments
 (0)