Skip to content

Commit

Permalink
[Chore] Housekeeping activities (canonical#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
gruyaume authored Aug 21, 2023
1 parent 83617e9 commit b364af5
Show file tree
Hide file tree
Showing 6 changed files with 434 additions and 177 deletions.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion metadata.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# Copyright 2021 Canonical Ltd.
# Copyright 2023 Canonical Ltd.
# See LICENSE file for licensing details.

name: vault-k8s
Expand Down
14 changes: 7 additions & 7 deletions src/charm.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# Copyright 2021 Canonical Ltd.
# Copyright 2023 Canonical Ltd.
# See LICENSE file for licensing details.

"""Charm for Vault running on Kubernetes.
Expand All @@ -14,9 +14,9 @@
KubernetesServicePatch,
ServicePort,
)
from charms.tls_certificates_interface.v1.tls_certificates import (
from charms.tls_certificates_interface.v2.tls_certificates import (
CertificateCreationRequestEvent,
TLSCertificatesProvidesV1,
TLSCertificatesProvidesV2,
)
from ops.charm import ActionEvent, CharmBase, ConfigChangedEvent
from ops.framework import StoredState
Expand All @@ -42,7 +42,7 @@ class VaultCharm(CharmBase):
def __init__(self, *args):
super().__init__(*args)
self._stored.set_default(role_id="", secret_id="")
self.tls_certificates = TLSCertificatesProvidesV1(self, "certificates")
self.tls_certificates = TLSCertificatesProvidesV2(self, "certificates")
self.vault = Vault(
url=f"http://localhost:{self.VAULT_PORT}",
role_id=self._stored.role_id, # type: ignore[arg-type]
Expand Down Expand Up @@ -84,7 +84,7 @@ def _on_certificate_creation_request(self, event: CertificateCreationRequestEven
)

def _on_config_changed(self, event: ConfigChangedEvent) -> None:
"""Handler triggerred whenever there is a config-changed event.
"""Handler triggered whenever there is a config-changed event.
Args:
event: Juju event
Expand Down Expand Up @@ -193,7 +193,7 @@ def _on_authorise_charm_action(self, event: ActionEvent) -> None:
if self.unit.is_leader():
self.vault.set_token(token=event.params["token"])
if not self.vault.is_ready:
self.vault.enable_secrets_engine()
self.vault.enable_pki_secrets_engine()
self.vault.generate_root_certificate()
self.vault.write_charm_pki_role()
self.vault.enable_approle_auth()
Expand All @@ -205,5 +205,5 @@ def _on_authorise_charm_action(self, event: ActionEvent) -> None:
self.unit.status = ActiveStatus()


if __name__ == "__main__":
if __name__ == "__main__": # pragma: no cover
main(VaultCharm)
98 changes: 23 additions & 75 deletions src/vault.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
#!/usr/bin/env python3
# Copyright 2021 Canonical Ltd.
# Copyright 2023 Canonical Ltd.
# See LICENSE file for licensing details.

"""Contains all the specificities to communicate with Vault through its API."""

import ipaddress
import logging
from typing import List, Optional, Tuple
from typing import Optional, Tuple

import hvac # type: ignore[import]
import requests

from certificate_signing_request import CertificateSigningRequest

CHARM_POLICY_FILE = "charm_policy.hcl"
CHARM_POLICY_PATH = f"src/{CHARM_POLICY_FILE}"
CHARM_POLICY_NAME = "local-charm-policy"
CHARM_ACCESS_ROLE = "local-charm-access"

Expand Down Expand Up @@ -47,20 +48,12 @@ def __init__(self, url: str, role_id: Optional[str] = None, secret_id: Optional[

@property
def token(self) -> Optional[str]:
"""Returns Vault's token.
Returns:
str: Vault token.
"""
"""Returns Vault's token."""
return self._client.token

@property
def is_ready(self) -> bool:
"""Returns whether Vault is ready.
Returns:
bool: Whether Vault is ready.
"""
"""Returns whether Vault is ready for interaction."""
if not self._is_backend_mounted:
logger.info("Vault is not ready - Backend not mounted")
return False
Expand All @@ -71,25 +64,11 @@ def is_ready(self) -> bool:
return True

def set_token(self, token: str) -> None:
"""Sets the Vault token.
Args:
token (str): Vault token
Returns:
None
"""
"""Sets the Vault token for authentication."""
self._client.token = token

def approle_login(self, role_id: str, secret_id: str) -> None:
"""Logs in to Vault via API.
Args:
role_id: Role ID.
Returns:
None
"""
"""Authenticate with Vault using the AppRole authentication method."""
try:
login_response = self._client.auth.approle.login(
role_id=role_id, secret_id=secret_id, use_token=False
Expand All @@ -99,32 +78,20 @@ def approle_login(self, role_id: str, secret_id: str) -> None:
logger.error("Login Failed - Can't connect to Vault")

def enable_approle_auth(self) -> None:
"""Enables AppRole auth method.
Returns:
None
"""
"""Enable the AppRole authentication method in Vault, if not already enabled."""
if "approle/" not in self._client.sys.list_auth_methods():
self._client.sys.enable_auth_method("approle")
logger.info("Enabled approle auth method")

def create_local_charm_policy(self) -> None:
"""Add a new policy for the charm.
Returns:
None
"""
with open("src/charm_policy.hcl", "r") as f:
"""Adds a new charm policy to Vault using the predefined charm policy definition."""
with open(CHARM_POLICY_PATH, "r") as f:
charm_policy = f.read()
self._client.sys.create_or_update_policy(name=CHARM_POLICY_NAME, policy=charm_policy)
logger.info(f"Created charm policy: {CHARM_POLICY_NAME}")

def create_local_charm_access_approle(self) -> None:
"""Creates approle for charm.
Returns:
None
"""
"""Create or update an AppRole in Vault with specific permissions for charm access."""
self._client.auth.approle.create_or_update_approle(
role_name=CHARM_ACCESS_ROLE,
token_ttl="60s",
Expand All @@ -134,7 +101,7 @@ def create_local_charm_access_approle(self) -> None:
logger.info(f"Created approle {CHARM_ACCESS_ROLE}")

def get_approle_auth_data(self) -> Tuple[str, str]:
"""Returns Approle authentication data (role_id and secret_id).
"""Retrieve the role ID and secret ID for the AppRole authentication method.
Returns:
str: Role ID
Expand All @@ -155,8 +122,8 @@ def write_charm_pki_role(
allow_glob_domains=True,
enforce_hostnames=False,
max_ttl="87598h",
):
"""Writes role in Vault for the charm to be capable of issuing certificates.
) -> None:
"""Write a role in Vault for the charm to be capable of issuing certificates.
Args:
allow_any_name (bool): Specifies if clients can request certs for any CN.
Expand Down Expand Up @@ -187,7 +154,7 @@ def write_charm_pki_role(
)

def generate_root_certificate(self, ttl: str = "87599h") -> str:
"""Generating root CA certificate and private key and returning certificate.
"""Generate an internal root CA certificate and private key, and return the certificate.
Args:
ttl: Time to live
Expand All @@ -196,9 +163,7 @@ def generate_root_certificate(self, ttl: str = "87599h") -> str:
str: Public key of the root certificate.
"""
config = {
"common_name": (
"Vault Root Certificate Authority " "({})".format(CHARM_PKI_MOUNT_POINT)
),
"common_name": f"Vault Root Certificate Authority ({CHARM_PKI_MOUNT_POINT})",
"ttl": ttl,
}
root_certificate = self._client.write(
Expand All @@ -209,10 +174,10 @@ def generate_root_certificate(self, ttl: str = "87599h") -> str:
logger.info("Generated root CA")
return root_certificate["data"]["certificate"]

def enable_secrets_engine(
def enable_pki_secrets_engine(
self, ttl: Optional[str] = None, max_ttl: Optional[str] = None
) -> None:
"""Enables Vault's secret engine if the backend is mounted.
"""Enable Vault's PKI secrets engine on the specified mount point.
Args:
ttl (str): Time to live.
Expand Down Expand Up @@ -254,7 +219,7 @@ def _write_role(self, role: str, **kwargs) -> None:
logger.info(f"Wrote role for PKI access: {role}")

def issue_certificate(self, certificate_signing_request: str) -> dict:
"""Issues a certificate based on a provided CSR.
"""Issue a certificate based on a provided Certificate Signing Request (CSR).
Args:
certificate_signing_request: Certificate Signing Request
Expand All @@ -275,6 +240,9 @@ def _issue_certificate(self, **config) -> dict:
Args:
role (str): Vault role
Returns:
dict: certificate data
"""
try:
response = self._client.write(
Expand All @@ -286,23 +254,3 @@ def _issue_certificate(self, **config) -> dict:
raise RuntimeError(response.get("warnings", "unknown error"))
logger.info(f"Issued certificate with role {CHARM_PKI_ROLE} for config: {config}")
return response["data"]

@staticmethod
def _sort_sans(sans: list) -> Tuple[List, List]:
"""Split SANs into IP SANs and name SANs.
Args:
sans (list): List of SANs
Returns:
A tuple containing, a list of IP SAN's and a list of name SAN's.
"""
ip_sans = set()
for san in sans:
try:
ipaddress.ip_address(san)
ip_sans.add(san)
except ValueError:
pass
alt_names = set(sans).difference(ip_sans)
return sorted(list(ip_sans)), sorted(list(alt_names))
6 changes: 3 additions & 3 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def test_given_unsecure_config_not_set_when_pebble_ready_then_unit_is_in_waiting
)

@patch(
"charms.tls_certificates_interface.v1.tls_certificates.TLSCertificatesProvidesV1.set_relation_certificate" # noqa: E501,W505
"charms.tls_certificates_interface.v2.tls_certificates.TLSCertificatesProvidesV2.set_relation_certificate" # noqa: E501,W505
)
@patch("vault.Vault.issue_certificate")
def test_given_certificate_request_contains_correct_information_when_certificate_request_then_vault_is_called( # noqa: E501
Expand All @@ -85,7 +85,7 @@ def test_given_certificate_request_contains_correct_information_when_certificate
patch_issue_certs.assert_has_calls(calls=calls)

@patch(
"charms.tls_certificates_interface.v1.tls_certificates.TLSCertificatesProvidesV1.set_relation_certificate" # noqa: E501, W505
"charms.tls_certificates_interface.v2.tls_certificates.TLSCertificatesProvidesV2.set_relation_certificate" # noqa: E501, W505
)
@patch("vault.Vault.issue_certificate")
def test_given_vault_answers_with_certificate_when_certificate_request_then_certificates_are_added_to_relation_data( # noqa: E501
Expand Down Expand Up @@ -124,7 +124,7 @@ def test_given_vault_answers_with_certificate_when_certificate_request_then_cert
@patch("vault.Vault.generate_root_certificate")
@patch("vault.Vault.create_local_charm_access_approle")
@patch("vault.Vault.write_charm_pki_role")
@patch("vault.Vault.enable_secrets_engine")
@patch("vault.Vault.enable_pki_secrets_engine")
@patch("vault.Vault.approle_login")
@patch("vault.Vault.create_local_charm_policy")
@patch("vault.Vault.enable_approle_auth")
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/test_vault.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,15 @@ def test_given_certificate_returned_by_vault_when_generate_root_certificate_then

@patch("hvac.api.system_backend.mount.Mount.list_mounted_secrets_engines")
@patch("hvac.api.system_backend.mount.Mount.enable_secrets_engine")
def test_given_backend_not_mounted_when_enable_secrets_engine_then_secrets_engine_is_enabled(
def test_given_backend_not_mounted_when_enable_pki_secrets_engine_then_secrets_engine_is_enabled( # noqa: E501
self,
patch_enable_secrets_engine,
patch_list_mounted_secrets_engine,
):
patch_list_mounted_secrets_engine.return_value = dict()
vault = Vault(url="http://whatever-url")

vault.enable_secrets_engine()
vault.enable_pki_secrets_engine()

patch_enable_secrets_engine.assert_called_with(
backend_type="pki",
Expand Down

0 comments on commit b364af5

Please sign in to comment.