Skip to content

Commit

Permalink
chore: update charm libraries
Browse files Browse the repository at this point in the history
  • Loading branch information
Github Actions committed Jul 20, 2023
1 parent 6a14f0d commit a2cc755
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 26 deletions.
16 changes: 12 additions & 4 deletions lib/charms/observability_libs/v0/cert_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@

LIBID = "b5cd5cd580f3428fa5f59a8876dcbe6a"
LIBAPI = 0
LIBPATCH = 3
LIBPATCH = 4


class CertChanged(EventBase):
Expand Down Expand Up @@ -108,7 +108,10 @@ def __init__(
self.charm = charm
self.cert_subject = cert_subject or charm.unit.name
self.cert_subject = charm.unit.name if not cert_subject else cert_subject
self.extra_sans_dns = list(filter(None, extra_sans_dns or [])) # drop empty list items

# Auto-include the fqdn and drop empty/duplicate sans
self.sans_dns = list(set(filter(None, (extra_sans_dns or []) + [socket.getfqdn()])))

self.peer_relation_name = peer_relation_name
self.certificates_relation_name = certificates_relation_name

Expand Down Expand Up @@ -224,7 +227,7 @@ def _generate_csr(
csr = generate_csr(
private_key=private_key.encode(),
subject=self.cert_subject,
sans_dns=[socket.getfqdn()] + self.extra_sans_dns,
sans_dns=self.sans_dns,
)

if renew and self._csr:
Expand All @@ -233,6 +236,7 @@ def _generate_csr(
new_certificate_signing_request=csr,
)
else:
logger.info("Creating CSR for %s with DNS %s", self.cert_subject, self.sans_dns)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (certificate)
as clear text.
self.certificates.request_certificate_creation(certificate_signing_request=csr)

# Note: CSR is being replaced with a new one, so until we get the new cert, we'd have
Expand All @@ -257,7 +261,11 @@ def _on_certificate_available(self, event: CertificateAvailableEvent) -> None:
# relation-changed. If that is not the case, we would need more guards and more paths.

# Process the cert only if it belongs to the unit that requested it (this unit)
event_csr = event.certificate_signing_request.strip() if event.certificate_signing_request else None
event_csr = (
event.certificate_signing_request.strip()
if event.certificate_signing_request
else None
)
if event_csr == self._csr:
self._ca_cert = event.ca
self._server_cert = event.certificate
Expand Down
49 changes: 33 additions & 16 deletions lib/charms/prometheus_k8s/v0/prometheus_scrape.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ def _on_scrape_targets_changed(self, event):

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

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -602,15 +602,22 @@ def render_alertmanager_static_configs(alertmanagers: List[str]):
# Create a mapping from paths to netlocs
# Group alertmanager targets into a dictionary of lists:
# {path: [netloc1, netloc2]}
paths = defaultdict(list) # type: Dict[str, List[str]]
paths = defaultdict(list) # type: Dict[Tuple[str, str], List[str]]
for parsed in map(urlparse, sanitized):
path = parsed.path or "/"
paths[path].append(parsed.netloc)
paths[(parsed.scheme, path)].append(parsed.netloc)

return {
"alertmanagers": [
{"path_prefix": path_prefix, "static_configs": [{"targets": netlocs}]}
for path_prefix, netlocs in paths.items()
{
"scheme": scheme,
"path_prefix": path_prefix,
"static_configs": [{"targets": netlocs}],
# FIXME figure out how to get alertmanager's ca_file into here
# Without this, prom errors: "x509: certificate signed by unknown authority"
"tls_config": {"insecure_skip_verify": True},
}
for (scheme, path_prefix), netlocs in paths.items()
]
}

Expand Down Expand Up @@ -1353,29 +1360,39 @@ def _static_scrape_config(self, relation) -> list:
if not relation.units:
return []

scrape_jobs = json.loads(relation.data[relation.app].get("scrape_jobs", "[]"))
scrape_configs = json.loads(relation.data[relation.app].get("scrape_jobs", "[]"))

if not scrape_jobs:
if not scrape_configs:
return []

scrape_metadata = json.loads(relation.data[relation.app].get("scrape_metadata", "{}"))

if not scrape_metadata:
return scrape_jobs
return scrape_configs

topology = JujuTopology.from_dict(scrape_metadata)

job_name_prefix = "juju_{}_prometheus_scrape".format(topology.identifier)
scrape_jobs = PrometheusConfig.prefix_job_names(scrape_jobs, job_name_prefix)
scrape_jobs = PrometheusConfig.sanitize_scrape_configs(scrape_jobs)
scrape_configs = PrometheusConfig.prefix_job_names(scrape_configs, job_name_prefix)
scrape_configs = PrometheusConfig.sanitize_scrape_configs(scrape_configs)

hosts = self._relation_hosts(relation)

scrape_jobs = PrometheusConfig.expand_wildcard_targets_into_individual_jobs(
scrape_jobs, hosts, topology
scrape_configs = PrometheusConfig.expand_wildcard_targets_into_individual_jobs(
scrape_configs, hosts, topology
)

return scrape_jobs
# If scheme is https but no ca section present, then auto add "insecure_skip_verify",
# otherwise scraping errors out with "x509: certificate signed by unknown authority".
# https://prometheus.io/docs/prometheus/latest/configuration/configuration/#tls_config
for scrape_config in scrape_configs:
tls_config = scrape_config.get("tls_config", {})
ca_present = "ca" in tls_config or "ca_file" in tls_config
if scrape_config.get("scheme") == "https" and not ca_present:
tls_config["insecure_skip_verify"] = True
scrape_config["tls_config"] = tls_config

return scrape_configs

def _relation_hosts(self, relation: Relation) -> Dict[str, Tuple[str, str]]:
"""Returns a mapping from unit names to (address, path) tuples, for the given relation."""
Expand Down Expand Up @@ -1793,10 +1810,10 @@ def _scrape_jobs(self) -> list:
A list of dictionaries, where each dictionary specifies a
single scrape job for Prometheus.
"""
jobs = self._jobs if self._jobs else [DEFAULT_JOB]
jobs = self._jobs or []
if callable(self._lookaside_jobs):
return jobs + PrometheusConfig.sanitize_scrape_configs(self._lookaside_jobs())
return jobs
jobs.extend(PrometheusConfig.sanitize_scrape_configs(self._lookaside_jobs()))
return jobs or [DEFAULT_JOB]

@property
def _scrape_metadata(self) -> dict:
Expand Down
34 changes: 31 additions & 3 deletions lib/charms/tls_certificates_interface/v2/tls_certificates.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ def _on_all_certificates_invalidated(self, event: AllCertificatesInvalidatedEven
import json
import logging
import uuid
from collections import defaultdict
from contextlib import suppress
from datetime import datetime, timedelta
from ipaddress import IPv4Address
Expand Down Expand Up @@ -308,7 +309,7 @@ def _on_all_certificates_invalidated(self, event: AllCertificatesInvalidatedEven

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

PYDEPS = ["cryptography", "jsonschema"]

Expand Down Expand Up @@ -1059,6 +1060,32 @@ def remove_certificate(self, certificate: str) -> None:
for certificate_relation in certificates_relation:
self._remove_certificate(certificate=certificate, relation_id=certificate_relation.id)

def get_issued_certificates(
self, relation_id: Optional[int] = None
) -> Dict[str, Dict[str, str]]:
"""Returns a dictionary of issued certificates.
It returns certificates from all relations if relation_id is not specified.
Certificates are returned per application name and CSR.
Returns:
dict: Certificates per application name.
"""
certificates: Dict[str, Dict[str, str]] = defaultdict(dict)
relations = (
[self.model.relations[self.relationship_name][relation_id]]
if relation_id
else self.model.relations.get(self.relationship_name, [])
)
for relation in relations:
provider_relation_data = _load_relation_data(relation.data[self.charm.app])
provider_certificates = provider_relation_data.get("certificates", [])
for certificate in provider_certificates:
certificates[relation.app.name].update( # type: ignore[union-attr]
{certificate["certificate_signing_request"]: certificate["certificate"]}
)
return certificates

def _on_relation_changed(self, event: RelationChangedEvent) -> None:
"""Handler triggered on relation changed event.
Expand Down Expand Up @@ -1178,7 +1205,7 @@ def _requirer_csrs(self) -> List[Dict[str, str]]:

@property
def _provider_certificates(self) -> List[Dict[str, str]]:
"""Returns list of provider CSRs from relation data."""
"""Returns list of certificates from the provider's relation data."""
relation = self.model.get_relation(self.relationship_name)
if not relation:
logger.debug("No relation: %s", self.relationship_name)
Expand Down Expand Up @@ -1364,7 +1391,7 @@ def _on_relation_changed(self, event: RelationChangedEvent) -> None:
),
)
except SecretNotFoundError:
secret = self.charm.app.add_secret(
secret = self.charm.unit.add_secret(
{"certificate": certificate["certificate"]},
label=f"{LIBID}-{certificate['certificate_signing_request']}",
expire=self._get_next_secret_expiry_time(
Expand Down Expand Up @@ -1465,6 +1492,7 @@ def _on_secret_expired(self, event: SecretExpiredEvent) -> None:
event.secret.remove_all_revisions()

def _find_certificate_in_relation_data(self, csr: str) -> Optional[Dict[str, Any]]:
"""Returns the certificate that match the given CSR."""
for certificate_dict in self._provider_certificates:
if certificate_dict["certificate_signing_request"] != csr:
continue
Expand Down
6 changes: 3 additions & 3 deletions lib/charms/traefik_k8s/v1/ingress.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def _on_ingress_revoked(self, event: IngressPerAppRevokedEvent):

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

DEFAULT_RELATION_NAME = "ingress"
RELATION_INTERFACE = "ingress"
Expand Down Expand Up @@ -114,7 +114,7 @@ def _on_ingress_revoked(self, event: IngressPerAppRevokedEvent):
try:
from typing import TypedDict
except ImportError:
from typing_extensions import TypedDict # py35 compat
from typing_extensions import TypedDict # py35 compatibility

# Model of the data a unit implementing the requirer will need to provide.
RequirerData = TypedDict(
Expand Down Expand Up @@ -156,7 +156,7 @@ class _IngressPerAppBase(Object):
"""Base class for IngressPerUnit interface classes."""

def __init__(self, charm: CharmBase, relation_name: str = DEFAULT_RELATION_NAME):
super().__init__(charm, relation_name)
super().__init__(charm, relation_name + "_V1")

self.charm: CharmBase = charm
self.relation_name = relation_name
Expand Down

0 comments on commit a2cc755

Please sign in to comment.