Skip to content

Commit e70779b

Browse files
[DPE-6344] LDAP II: Include charm libs (#883)
1 parent 96a1117 commit e70779b

File tree

12 files changed

+1210
-60
lines changed

12 files changed

+1210
-60
lines changed

lib/charms/certificate_transfer_interface/v0/certificate_transfer.py

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

lib/charms/glauth_k8s/v0/ldap.py

Lines changed: 555 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
@@ -63,10 +63,17 @@ requires:
6363
interface: tls-certificates
6464
limit: 1
6565
optional: true
66+
receive-ca-cert:
67+
interface: certificate_transfer
68+
optional: true
6669
s3-parameters:
6770
interface: s3
6871
limit: 1
6972
optional: true
73+
ldap:
74+
interface: ldap
75+
limit: 1
76+
optional: true
7077
logging:
7178
interface: loki_push_api
7279
limit: 1

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ cosl = ">=0.0.50"
3232
opentelemetry-exporter-otlp-proto-http = "1.21.0"
3333
# tls_certificates_interface/v2/tls_certificates.py
3434
cryptography = "*"
35+
# certificate_transfer_interface/v0/certificate_transfer.py
36+
# tls_certificates_interface/v2/tls_certificates.py
3537
jsonschema = "*"
3638

3739
[tool.poetry.group.format]

src/charm.py

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ def __init__(self, *args):
216216
self.framework.observe(self.on.promote_to_primary_action, self._on_promote_to_primary)
217217
self.framework.observe(self.on.get_primary_action, self._on_get_primary)
218218
self.framework.observe(self.on.update_status, self._on_update_status)
219+
220+
self._certs_path = "/usr/local/share/ca-certificates"
219221
self._storage_path = self.meta.storages["pgdata"].location
220222
self.pgdata_path = f"{self._storage_path}/pgdata"
221223

@@ -1809,6 +1811,17 @@ def _peers(self) -> Relation:
18091811
"""
18101812
return self.model.get_relation(PEER)
18111813

1814+
def _push_file_to_workload(self, container: Container, file_path: str, file_data: str) -> None:
1815+
"""Uploads a file into the provided container."""
1816+
container.push(
1817+
file_path,
1818+
file_data,
1819+
make_dirs=True,
1820+
permissions=0o400,
1821+
user=WORKLOAD_OS_USER,
1822+
group=WORKLOAD_OS_GROUP,
1823+
)
1824+
18121825
def push_tls_files_to_workload(self, container: Container = None) -> bool:
18131826
"""Uploads TLS files to the workload container."""
18141827
if container is None:
@@ -1817,41 +1830,36 @@ def push_tls_files_to_workload(self, container: Container = None) -> bool:
18171830
key, ca, cert = self.tls.get_tls_files()
18181831

18191832
if key is not None:
1820-
container.push(
1821-
f"{self._storage_path}/{TLS_KEY_FILE}",
1822-
key,
1823-
make_dirs=True,
1824-
permissions=0o400,
1825-
user=WORKLOAD_OS_USER,
1826-
group=WORKLOAD_OS_GROUP,
1827-
)
1833+
self._push_file_to_workload(container, f"{self._storage_path}/{TLS_KEY_FILE}", key)
18281834
if ca is not None:
1829-
container.push(
1830-
f"{self._storage_path}/{TLS_CA_FILE}",
1831-
ca,
1832-
make_dirs=True,
1833-
permissions=0o400,
1834-
user=WORKLOAD_OS_USER,
1835-
group=WORKLOAD_OS_GROUP,
1836-
)
1837-
container.push(
1838-
"/usr/local/share/ca-certificates/ca.crt",
1839-
ca,
1840-
make_dirs=True,
1841-
permissions=0o400,
1842-
user=WORKLOAD_OS_USER,
1843-
group=WORKLOAD_OS_GROUP,
1844-
)
1835+
self._push_file_to_workload(container, f"{self._storage_path}/{TLS_CA_FILE}", ca)
1836+
self._push_file_to_workload(container, f"{self._certs_path}/ca.crt", ca)
18451837
container.exec(["update-ca-certificates"]).wait()
18461838
if cert is not None:
1847-
container.push(
1848-
f"{self._storage_path}/{TLS_CERT_FILE}",
1849-
cert,
1850-
make_dirs=True,
1851-
permissions=0o400,
1852-
user=WORKLOAD_OS_USER,
1853-
group=WORKLOAD_OS_GROUP,
1839+
self._push_file_to_workload(container, f"{self._storage_path}/{TLS_CERT_FILE}", cert)
1840+
1841+
return self.update_config()
1842+
1843+
def push_ca_file_into_workload(self, secret_name: str) -> bool:
1844+
"""Uploads CA certificate into the workload container."""
1845+
container = self.unit.get_container("postgresql")
1846+
certificates = self.get_secret(UNIT_SCOPE, secret_name)
1847+
1848+
if certificates is not None:
1849+
self._push_file_to_workload(
1850+
container=container,
1851+
file_path=f"{self._certs_path}/{secret_name}.crt",
1852+
file_data=certificates,
18541853
)
1854+
container.exec(["update-ca-certificates"]).wait()
1855+
1856+
return self.update_config()
1857+
1858+
def clean_ca_file_from_workload(self, secret_name: str) -> bool:
1859+
"""Cleans up CA certificate from the workload container."""
1860+
container = self.unit.get_container("postgresql")
1861+
container.remove_path(f"{self._certs_path}/{secret_name}.crt")
1862+
container.exec(["update-ca-certificates"]).wait()
18551863

18561864
return self.update_config()
18571865

tests/integration/helpers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -840,7 +840,9 @@ async def backup_operations(
840840
ops_test, charm, 2, database_app_name=database_app_name, wait_for_idle=False
841841
)
842842

843-
await ops_test.model.relate(database_app_name, tls_certificates_app_name)
843+
await ops_test.model.relate(
844+
f"{database_app_name}:certificates", f"{tls_certificates_app_name}:certificates"
845+
)
844846
async with ops_test.fast_forward(fast_interval="60s"):
845847
await ops_test.model.wait_for_idle(
846848
apps=[database_app_name], status="active", timeout=1000, raise_on_error=False

tests/integration/test_backups_pitr_aws.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ async def pitr_backup_operations(
6666
logger.info(
6767
"integrating self-signed-certificates with postgresql and waiting them to stabilize"
6868
)
69-
await ops_test.model.relate(database_app_name, tls_certificates_app_name)
69+
await ops_test.model.relate(
70+
f"{database_app_name}:certificates", f"{tls_certificates_app_name}:certificates"
71+
)
7072
async with ops_test.fast_forward(fast_interval="60s"):
7173
await ops_test.model.wait_for_idle(
7274
apps=[database_app_name, tls_certificates_app_name],

tests/integration/test_backups_pitr_gcp.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ async def pitr_backup_operations(
6666
logger.info(
6767
"integrating self-signed-certificates with postgresql and waiting them to stabilize"
6868
)
69-
await ops_test.model.relate(database_app_name, tls_certificates_app_name)
69+
await ops_test.model.relate(
70+
f"{database_app_name}:certificates", f"{tls_certificates_app_name}:certificates"
71+
)
7072
async with ops_test.fast_forward(fast_interval="60s"):
7173
await ops_test.model.wait_for_idle(
7274
apps=[database_app_name, tls_certificates_app_name],

tests/integration/test_tls.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ async def test_tls(ops_test: OpsTest) -> None:
7979
tls_certificates_app_name, config=tls_config, channel=tls_channel, base=CHARM_BASE
8080
)
8181
# Relate it to the PostgreSQL to enable TLS.
82-
await ops_test.model.relate(DATABASE_APP_NAME, tls_certificates_app_name)
82+
await ops_test.model.relate(
83+
f"{DATABASE_APP_NAME}:certificates", f"{tls_certificates_app_name}:certificates"
84+
)
8385
await ops_test.model.wait_for_idle(status="active", timeout=1000, raise_on_error=False)
8486

8587
# Wait for all units enabling TLS.

0 commit comments

Comments
 (0)