Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 33 additions & 8 deletions lib/charms/postgresql_k8s/v0/postgresql_tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"""

import base64
import ipaddress
import logging
import re
import socket
Expand All @@ -44,7 +45,7 @@

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

logger = logging.getLogger(__name__)
SCOPE = "unit"
Expand All @@ -54,11 +55,14 @@
class PostgreSQLTLS(Object):
"""In this class we manage certificates relation."""

def __init__(self, charm, peer_relation):
def __init__(
self, charm, peer_relation: str, additional_dns_names: Optional[List[str]] = None
):
"""Manager of PostgreSQL relation with TLS Certificates Operator."""
super().__init__(charm, "client-relations")
self.charm = charm
self.peer_relation = peer_relation
self.additional_dns_names = additional_dns_names or []
self.certs = TLSCertificatesRequiresV1(self.charm, TLS_RELATION)
self.framework.observe(
self.charm.on.set_tls_private_key_action, self._on_set_tls_private_key
Expand Down Expand Up @@ -86,8 +90,8 @@ def _request_certificate(self, param: Optional[str]):
csr = generate_csr(
private_key=key,
subject=self.charm.get_hostname_by_unit(self.charm.unit.name),
sans=self._get_sans(),
additional_critical_extensions=self._get_tls_extensions(),
**self._get_sans(),
)

self.charm.set_secret(SCOPE, "key", key.decode("utf-8"))
Expand Down Expand Up @@ -149,28 +153,49 @@ def _on_certificate_expiring(self, event: CertificateExpiringEvent) -> None:
new_csr = generate_csr(
private_key=key,
subject=self.charm.get_hostname_by_unit(self.charm.unit.name),
sans=self._get_sans(),
additional_critical_extensions=self._get_tls_extensions(),
**self._get_sans(),
)
self.certs.request_certificate_renewal(
old_certificate_signing_request=old_csr,
new_certificate_signing_request=new_csr,
)
self.charm.set_secret(SCOPE, "csr", new_csr.decode("utf-8"))

def _get_sans(self) -> List[str]:
"""Create a list of DNS names for a PostgreSQL unit.
def _get_sans(self) -> dict:
"""Create a list of Subject Alternative Names for a PostgreSQL unit.

Returns:
A list representing the hostnames of the PostgreSQL unit.
A list representing the IP and hostnames of the PostgreSQL unit.
"""

def is_ip_address(address: str) -> bool:
"""Returns whether and address is an IP address."""
try:
ipaddress.ip_address(address)
return True
except (ipaddress.AddressValueError, ValueError):
return False

unit_id = self.charm.unit.name.split("/")[1]
return [

# Create a list of all the Subject Alternative Names.
sans = [
f"{self.charm.app.name}-{unit_id}",
self.charm.get_hostname_by_unit(self.charm.unit.name),
socket.getfqdn(),
str(self.charm.model.get_binding(self.peer_relation).network.bind_address),
]
sans.extend(self.additional_dns_names)

# Separate IP addresses and DNS names.
sans_ip = [san for san in sans if is_ip_address(san)]
sans_dns = [san for san in sans if not is_ip_address(san)]

return {
"sans_ip": sans_ip,
"sans_dns": sans_dns,
}

@staticmethod
def _get_tls_extensions() -> Optional[List[ExtensionType]]:
Expand Down
24 changes: 18 additions & 6 deletions lib/charms/tls_certificates_interface/v1/tls_certificates.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ def _on_certificate_expiring(self, event: CertificateExpiringEvent) -> None:
import logging
import uuid
from datetime import datetime, timedelta
from ipaddress import IPv4Address
from typing import Dict, List, Optional

from cryptography import x509
Expand Down Expand Up @@ -657,7 +658,9 @@ def generate_csr(
email_address: str = None,
country_name: str = None,
private_key_password: Optional[bytes] = None,
sans: Optional[List[str]] = None,
sans_oid: Optional[str] = None,
sans_ip: Optional[List[str]] = None,
sans_dns: Optional[List[str]] = None,
additional_critical_extensions: Optional[List] = None,
) -> bytes:
"""Generates a CSR using private key and subject.
Expand All @@ -672,7 +675,9 @@ def generate_csr(
email_address (str): Email address.
country_name (str): Country Name.
private_key_password (bytes): Private key password
sans (list): List of subject alternative names
sans_dns (list): List of DNS subject alternative names
sans_ip (list): List of IP subject alternative names
sans_oid (str): Additional OID
additional_critical_extensions (list): List if critical additional extension objects.
Object must be a x509 ExtensionType.

Expand All @@ -693,10 +698,17 @@ def generate_csr(
if country_name:
subject_name.append(x509.NameAttribute(x509.NameOID.COUNTRY_NAME, country_name))
csr = x509.CertificateSigningRequestBuilder(subject_name=x509.Name(subject_name))
if sans:
csr = csr.add_extension(
x509.SubjectAlternativeName([x509.DNSName(san) for san in sans]), critical=False
)

_sans = []
if sans_oid:
_sans.append(x509.RegisteredID(x509.ObjectIdentifier(sans_oid)))
if sans_ip:
_sans.extend([x509.IPAddress(IPv4Address(san)) for san in sans_ip])
if sans_dns:
_sans.extend([x509.DNSName(san) for san in sans_dns])
if _sans:
csr = csr.add_extension(x509.SubjectAlternativeName(_sans), critical=False)

if additional_critical_extensions:
for extension in additional_critical_extensions:
csr = csr.add_extension(extension, critical=True)
Expand Down
2 changes: 1 addition & 1 deletion src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def __init__(self, *args):
self.postgresql_client_relation = PostgreSQLProvider(self)
self.legacy_db_relation = DbProvides(self, admin=False)
self.legacy_db_admin_relation = DbProvides(self, admin=True)
self.tls = PostgreSQLTLS(self, PEER)
self.tls = PostgreSQLTLS(self, PEER, [self.primary_endpoint, self.replicas_endpoint])
self.restart_manager = RollingOpsManager(
charm=self, relation="restart", callback=self._restart
)
Expand Down
16 changes: 10 additions & 6 deletions tests/unit/test_postgresql_tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,16 @@ def test_get_sans(self):
sans = self.charm.tls._get_sans()
self.assertEqual(
sans,
[
"postgresql-k8s-0",
"postgresql-k8s-0.postgresql-k8s-endpoints",
socket.getfqdn(),
"1.1.1.1",
],
{
"sans_ip": ["1.1.1.1"],
"sans_dns": [
"postgresql-k8s-0",
"postgresql-k8s-0.postgresql-k8s-endpoints",
socket.getfqdn(),
"postgresql-k8s-primary.None.svc.cluster.local",
"postgresql-k8s-replicas.None.svc.cluster.local",
],
},
)

def test_get_tls_extensions(self):
Expand Down
14 changes: 7 additions & 7 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ commands =
[testenv:charm-integration]
description = Run charm integration tests
deps =
juju
juju==2.9.11 # juju 3.0 has issues with retrieving action results and deploying charms
pytest
pytest-operator
psycopg2-binary
Expand All @@ -79,7 +79,7 @@ commands =
[testenv:database-relation-integration]
description = Run database relation integration tests
deps =
juju
juju==2.9.11 # juju 3.0 has issues with retrieving action results and deploying charms
pytest
pytest-operator
psycopg2-binary
Expand All @@ -90,7 +90,7 @@ commands =
[testenv:db-relation-integration]
description = Run db relation integration tests
deps =
juju
juju==2.9.11 # juju 3.0 has issues with retrieving action results and deploying charms
pytest
pytest-operator
psycopg2-binary
Expand All @@ -101,7 +101,7 @@ commands =
[testenv:db-admin-relation-integration]
description = Run db-admin relation integration tests
deps =
juju
juju==2.9.11 # juju 3.0 has issues with retrieving action results and deploying charms
pytest
pytest-operator
psycopg2-binary
Expand All @@ -112,7 +112,7 @@ commands =
[testenv:password-rotation-integration]
description = Run password rotation integration tests
deps =
juju
juju==2.9.11 # juju 3.0 has issues with retrieving action results and deploying charms
pytest
pytest-operator
psycopg2-binary
Expand All @@ -123,7 +123,7 @@ commands =
[testenv:tls-integration]
description = Run TLS integration tests
deps =
juju
juju==2.9.11 # juju 3.0 has issues with retrieving action results and deploying charms
pytest
pytest-operator
psycopg2-binary
Expand All @@ -134,7 +134,7 @@ commands =
[testenv:integration]
description = Run all integration tests
deps =
juju
juju==2.9.11 # juju 3.0 has issues with retrieving action results and deploying charms
pytest
pytest-operator
psycopg2-binary
Expand Down