Skip to main content

certificate

Compute TLS certificate lifecycle: CSR generation and Hub-issued cert renewal.

Once a compute has heartbeated with a private IP address, the hub registers a "compute" record and returns a computeId/domain pair (see BitfountHub.do_pod_heartbeat). This module builds a CSR for that domain, submits it to BitfountHub.request_compute_certificate, polls BitfountHub.get_compute_certificate until issued, persists the result, and later detects when the issued certificate is two-thirds of the way through its validity period so a renewal can be requested. If the hub hands back a new computeId/domain pair; a persisted certificate for the old domain is treated as absent (rather than just not-yet- due-for-renewal) so a certificate for the new domain is requested straight away instead of waiting out the old one's remaining validity. A certificate issued to a key this pod no longer holds is treated the same way: it cannot be served, and its validity window says nothing about that.

Module​

Functions​

build_csr_pem​

def build_csr_pem(private_key: RSAPrivateKey, domain: str) ‑> str:

Builds a PEM-encoded certificate signing request for domain.

The hub requires the CSR's public key to be RSA, its subject common name to equal domain exactly, and exactly one subject alternative name (a DNS name equal to domain).

Arguments

  • private_key: The RSA private key to sign the CSR with. Its public key is embedded in the CSR.
  • domain: The domain the certificate is being requested for (the domain returned alongside computeId from the heartbeat).

Returns The PEM-encoded CSR.

certificate_domain​

def certificate_domain(certificate_pem: str) ‑> str | None:

Returns the leaf certificate's subject common name, or None if absent.

build_csr_pem sets the CSR's common name to the domain it was issued for, and the hub is required to preserve it, so this is what identifies which domain a persisted certificate belongs to.

Arguments

  • certificate_pem: A PEM-encoded certificate (the leaf certificate is sufficient; any following CA bundle entries are ignored).

certificate_fingerprint​

def certificate_fingerprint(certificate_pem: str) ‑> str:

Returns the leaf certificate's SHA-256 fingerprint, as hex.

This identifies a certificate independently of the PEM text carrying it. The same certificate reaches this module as two different strings - the persisted fullchain, which is the leaf followed by the CA bundle, and the hub's leaf-only certificatePem - so comparing the text answers "is this the same certificate?" with "no" for a certificate that is in fact the same one.

Arguments

  • certificate_pem: A PEM-encoded certificate (the leaf certificate is sufficient; any following CA bundle entries are ignored).

certificate_matches_private_key​

def certificate_matches_private_key(    certificate_pem: str, private_key: RSAPrivateKey,) ‑> bool:

Returns whether the leaf certificate was issued to private_key.

A certificate is only servable alongside the key it was issued to. OpenSSL rejects the pair when the TLS context is built, with [X509: KEY_VALUES_MISMATCH] raised out of the serving library rather than out of anything holding the certificate - so nothing on the path that persists or selects a certificate notices, and the failure only reaches the logs as a server that would not start.

The public halves are compared as DER rather than as key objects, which holds regardless of how the two were loaded.

Arguments

  • certificate_pem: A PEM-encoded certificate (the leaf certificate is sufficient; any following CA bundle entries are ignored).
  • private_key: The key the certificate should carry the public half of.

Returns True if the certificate's public key is private_key's public half.

certificate_validity​

def certificate_validity(    certificate_pem: str,) ‑> tuple[datetime.datetime, datetime.datetime]:

Returns the (not-valid-before, not-valid-after) UTC datetimes.

Arguments

  • certificate_pem: A PEM-encoded certificate (the leaf certificate is sufficient; any following CA bundle entries are ignored).

renewal_due_at​

def renewal_due_at(    not_before: datetime, not_after: datetime, fraction: float = 0.6666666666666666,) ‑> datetime.datetime:

Returns the point at which a certificate should be renewed.

Defaults to two-thirds of the way through the certificate's validity period. This is the same heuristic used by ACME clients.

Classes​

ComputeCertificateManager​

class ComputeCertificateManager(    hub: BitfountHub | None, key_directory: Path, private_key: RSAPrivateKey,):

Manages the lifecycle of a compute's hub-issued TLS certificate.

Persists the issued certificate (leaf + CA bundle) alongside the pod's existing RSA identity key, under key_directory / "compute.pem".

Arguments

  • hub: The BitfountHub client to request/poll certificates through. None makes the manager read-only: it can say which certificate is on disk and servable, but cannot request or renew one. That is all the patient-API child needs; the Compute does the rest.
  • key_directory: The pod's key directory (the same directory pod_rsa.pem lives in).
  • private_key: The pod's existing RSA identity key. Reused as the CSR key rather than generating a separate one.

Variables​

  • fullchain_path : pathlib.Path - Path to the persisted leaf-certificate + CA-bundle PEM file.
  • tls_keyfile_path : pathlib.Path - Path to a PEM/PKCS8 copy of the private key, for TLS.

    Written lazily, on first access. ComputeCertificateManager is constructed for every pod regardless of whether TLS is ever actually used, and callers (e.g. tests) may pass a mocked private_key.

Methods​


current_fullchain_path​

def current_fullchain_path(self) ‑> pathlib.Path | None:

Returns fullchain_path if a servable certificate is persisted.

A certificate that cannot be served is reported as absent rather than handed out: one issued to a key this pod no longer holds, or one that will not parse. OpenSSL rejects such a pair only when the TLS context is built, which takes down the server that was given it - where reporting it as absent leaves the caller on its no-certificate path, which is a state it already handles.

A key mismatch is warned about once per certificate rather than on every read: this is read on every certificate-lifecycle tick, and a mismatch lasts until a replacement is issued.

poll​

def poll(self, compute_id: str | None, domain: str | None) ‑> None:

Runs one idempotent certificate-lifecycle tick.

Safe to call repeatedly (e.g. from a background thread). Never raises: hub/network failures are logged and left for the next tick, matching how Pod._pod_heartbeat handles the same failure modes.

Arguments

  • compute_id: The compute worker ID from the most recent heartbeat response, or None if not yet known.
  • domain: The domain from the most recent heartbeat response, or None if not yet known.