Skip to content

Commit

Permalink
feat: Implement CA certificate renewal (#242)
Browse files Browse the repository at this point in the history
  • Loading branch information
saltiyazan authored Sep 24, 2024
1 parent d7103f7 commit bc49fab
Show file tree
Hide file tree
Showing 9 changed files with 505 additions and 106 deletions.
63 changes: 50 additions & 13 deletions charmcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,33 +76,70 @@ config:
ca-common-name:
type: string
default: self-signed-certificates-operator
description: Common name to be used by the Certificate Authority.
description: >
Common name to be used by the Certificate Authority.
Changing this value will trigger generation of a new CA certificate,
revoking all previously issued certificates.
ca-organization:
type: string
description: Organization name to be used by the Certificate Authority.
description: >
Organization name to be used by the Certificate Authority.
Changing this value will trigger generation of a new CA certificate,
revoking all previously issued certificates.
ca-organizational-unit:
type: string
description: Organizational unit to be used by the Certificate Authority.
description: >
Organizational unit to be used by the Certificate Authority.
Changing this value will trigger generation of a new CA certificate,
revoking all previously issued certificates.
ca-email-address:
type: string
description: Email address to be used by the Certificate Authority.
description: >
Email address to be used by the Certificate Authority.
Changing this value will trigger generation of a new CA certificate,
revoking all previously issued certificates.
ca-country-name:
type: string
description: Country name to be used by the Certificate Authority.
description: >
Country name to be used by the Certificate Authority.
Changing this value will trigger generation of a new CA certificate,
revoking all previously issued certificates.
ca-state-or-province-name:
type: string
description: State or province name to be used by the Certificate Authority.
description: >
State or province name to be used by the Certificate Authority.
Changing this value will trigger generation of a new CA certificate,
revoking all previously issued certificates.
ca-locality-name:
type: string
description: Locality name to be used by the Certificate Authority.
description: >
Locality name to be used by the Certificate Authority.
Changing this value will trigger generation of a new CA certificate,
revoking all previously issued certificates.
root-ca-validity:
type: int
default: 365
description: RootCA certificate validity (in days).
type: string
default: 365d
description: >
RootCA certificate validity.
The given value must be followed by one of: "m" for minutes, "h" for hours, "d" for days and "w" for weeks.
For example, "1m" for 1 minute, "10w" for 10 weeks.
If no units are given, the unit will be assumed as days.
Defaults to 365 days.
This value should be equal to or longer than twice the certificate-validity.
Changing this value will trigger generation of a new CA certificate,
revoking all previously issued certificates.
certificate-validity:
type: int
default: 365
description: Certificate validity (in days).
type: string
default: 90d
description: >
Signed certificate validity.
The given value must be followed by one of: "m" for minutes, "h" for hours, "d" for days and "w" for weeks.
For example, "1m" for 1 minute, "10w" for 10 weeks.
If no units are given, the unit will be assumed as days.
Defaults to 90 days.
This value should be equal to or shorter than half the root-ca-validity.
Changing this value will trigger generation of a new CA certificate,
revoking all previously issued certificates.
actions:
get-ca-certificate:
Expand Down
30 changes: 23 additions & 7 deletions lib/charms/tls_certificates_interface/v4/tls_certificates.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def _get_config_locality_name(self) -> Optional[str]:

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

PYDEPS = ["cryptography", "pydantic"]

Expand Down Expand Up @@ -862,7 +862,7 @@ def generate_csr( # noqa: C901

def generate_ca(
private_key: PrivateKey,
validity: int,
validity: timedelta,
common_name: str,
sans_dns: Optional[FrozenSet[str]] = frozenset(),
sans_ip: Optional[FrozenSet[str]] = frozenset(),
Expand All @@ -878,7 +878,7 @@ def generate_ca(
Args:
private_key (PrivateKey): Private key
validity (int): Certificate validity time (in days)
validity (timedelta): Certificate validity time
common_name (str): Common Name that can be an IP or a Full Qualified Domain Name (FQDN).
sans_dns (FrozenSet[str]): DNS Subject Alternative Names
sans_ip (FrozenSet[str]): IP Subject Alternative Names
Expand Down Expand Up @@ -945,7 +945,7 @@ def generate_ca(
.public_key(private_key_object.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.now(timezone.utc))
.not_valid_after(datetime.now(timezone.utc) + timedelta(days=validity))
.not_valid_after(datetime.now(timezone.utc) + validity)
.add_extension(x509.SubjectAlternativeName(set(_sans)), critical=False)
.add_extension(x509.SubjectKeyIdentifier(digest=subject_identifier), critical=False)
.add_extension(
Expand All @@ -971,7 +971,7 @@ def generate_certificate(
csr: CertificateSigningRequest,
ca: Certificate,
ca_private_key: PrivateKey,
validity: int,
validity: timedelta,
is_ca: bool = False,
) -> Certificate:
"""Generate a TLS certificate based on a CSR.
Expand All @@ -980,7 +980,7 @@ def generate_certificate(
csr (CertificateSigningRequest): CSR
ca (Certificate): CA Certificate
ca_private_key (PrivateKey): CA private key
validity (int): Certificate validity (in days)
validity (timedelta): Certificate validity time
is_ca (bool): Whether the certificate is a CA certificate
Returns:
Expand All @@ -999,7 +999,7 @@ def generate_certificate(
.public_key(csr_object.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.now(timezone.utc))
.not_valid_after(datetime.now(timezone.utc) + timedelta(days=validity))
.not_valid_after(datetime.now(timezone.utc) + validity)
)
extensions = _get_certificate_request_extensions(
authority_key_identifier=ca_pem.extensions.get_extension_for_class(
Expand Down Expand Up @@ -1793,6 +1793,22 @@ def get_provider_certificates(
certificates.append(certificate.to_provider_certificate(relation_id=relation.id))
return certificates

def get_unsolicited_certificates(
self, relation_id: Optional[int] = None
) -> List[ProviderCertificate]:
"""Return provider certificates for which no certificate requests exists.
Those certificates should be revoked.
"""
unsolicited_certificates: List[ProviderCertificate] = []
provider_certificates = self.get_provider_certificates(relation_id=relation_id)
requirer_csrs = self.get_certificate_requests(relation_id=relation_id)
list_of_csrs = [csr.certificate_signing_request for csr in requirer_csrs]
for certificate in provider_certificates:
if certificate.certificate_signing_request not in list_of_csrs:
unsolicited_certificates.append(certificate)
return unsolicited_certificates

def get_outstanding_certificate_requests(
self, relation_id: Optional[int] = None
) -> List[RequirerCSR]:
Expand Down
Loading

0 comments on commit bc49fab

Please sign in to comment.