3232Since this library uses [Juju Secrets](https://juju.is/docs/juju/secret) it requires Juju >= 3.0.3.
3333"""
3434import abc
35+ import hashlib
3536import ipaddress
3637import json
3738import socket
6768
6869LIBID = "b5cd5cd580f3428fa5f59a8876dcbe6a"
6970LIBAPI = 1
70- LIBPATCH = 13
71+ LIBPATCH = 14
7172
7273VAULT_SECRET_LABEL = "cert-handler-private-vault"
7374
@@ -301,14 +302,11 @@ def __init__(
301302 Must match metadata.yaml.
302303 cert_subject: Custom subject. Name collisions are under the caller's responsibility.
303304 sans: DNS names. If none are given, use FQDN.
304- refresh_events: an optional list of bound events which
305- will be observed to replace the current CSR with a new one
306- if there are changes in the CSR's DNS SANs or IP SANs.
307- Then, subsequently, replace its corresponding certificate with a new one.
305+ refresh_events: [DEPRECATED].
308306 """
309307 super ().__init__ (charm , key )
310308 # use StoredState to store the hash of the CSR
311- # to potentially trigger a CSR renewal on `refresh_events`
309+ # to potentially trigger a CSR renewal
312310 self ._stored .set_default (
313311 csr_hash = None ,
314312 )
@@ -320,8 +318,9 @@ def __init__(
320318
321319 # Use fqdn only if no SANs were given, and drop empty/duplicate SANs
322320 sans = list (set (filter (None , (sans or [socket .getfqdn ()]))))
323- self .sans_ip = list (filter (is_ip_address , sans ))
324- self .sans_dns = list (filterfalse (is_ip_address , sans ))
321+ # sort SANS lists to avoid unnecessary csr renewals during reconciliation
322+ self .sans_ip = sorted (filter (is_ip_address , sans ))
323+ self .sans_dns = sorted (filterfalse (is_ip_address , sans ))
325324
326325 if self ._check_juju_supports_secrets ():
327326 vault_backend = _SecretVaultBackend (charm , secret_label = VAULT_SECRET_LABEL )
@@ -367,13 +366,15 @@ def __init__(
367366 )
368367
369368 if refresh_events :
370- for ev in refresh_events :
371- self .framework .observe (ev , self ._on_refresh_event )
369+ logger .warn (
370+ "DEPRECATION WARNING. `refresh_events` is now deprecated. CertHandler will automatically refresh the CSR when necessary."
371+ )
372372
373- def _on_refresh_event (self , _ ):
374- """Replace the latest current CSR with a new one if there are any SANs changes."""
375- if self ._stored .csr_hash != self ._csr_hash :
376- self ._generate_csr (renew = True )
373+ self ._reconcile ()
374+
375+ def _reconcile (self ):
376+ """Run all logic that is independent of what event we're processing."""
377+ self ._refresh_csr_if_needed ()
377378
378379 def _on_upgrade_charm (self , _ ):
379380 has_privkey = self .vault .get_value ("private-key" )
@@ -388,6 +389,11 @@ def _on_upgrade_charm(self, _):
388389 # this will call `self.private_key` which will generate a new privkey.
389390 self ._generate_csr (renew = True )
390391
392+ def _refresh_csr_if_needed (self ):
393+ """Refresh the current CSR with a new one if there are any SANs changes."""
394+ if self ._stored .csr_hash is not None and self ._stored .csr_hash != self ._csr_hash :
395+ self ._generate_csr (renew = True )
396+
391397 def _migrate_vault (self ):
392398 peer_backend = _RelationVaultBackend (self .charm , relation_name = "peers" )
393399
@@ -440,13 +446,17 @@ def enabled(self) -> bool:
440446 return True
441447
442448 @property
443- def _csr_hash (self ) -> int :
449+ def _csr_hash (self ) -> str :
444450 """A hash of the config that constructs the CSR.
445451
446452 Only include here the config options that, should they change, should trigger a renewal of
447453 the CSR.
448454 """
449- return hash (
455+
456+ def _stable_hash (data ):
457+ return hashlib .sha256 (str (data ).encode ()).hexdigest ()
458+
459+ return _stable_hash (
450460 (
451461 tuple (self .sans_dns ),
452462 tuple (self .sans_ip ),
0 commit comments