Skip to content

Commit

Permalink
feat(charm): add juju secret to share the protected nats connection url
Browse files Browse the repository at this point in the history
This commit adds support for juju secrets to share the protcted nats url
containing the auth_token. The older way of sharing the url through
relation data still remains and is available to maintaine backwards
compatibility. However we should depracate this when juju 3.0 becomes
the defacto standard for all the charms.
  • Loading branch information
jat-canonical committed Oct 13, 2023
1 parent 0342a32 commit 2b44f86
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 10 deletions.
10 changes: 8 additions & 2 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@
from cryptography.hazmat.primitives import serialization
from nats_config import NATS
from nrpe.client import NRPEClient # noqa: E402
from ops import ConfigChangedEvent, EventBase, EventSource, InstallEvent, RelationJoinedEvent, StoredState
from ops import (
ConfigChangedEvent,
EventBase,
EventSource,
InstallEvent,
RelationJoinedEvent,
StoredState,
)
from ops.charm import CharmBase, CharmEvents
from ops.main import main
from ops.model import ActiveStatus, BlockedStatus, ModelError
Expand Down Expand Up @@ -173,7 +180,6 @@ def _reconfigure_nats(self, config):

self.framework.breakpoint()
if ctxt["tls_ca_cert"]:

self.nats_client.set_tls_ca(ctxt["tls_ca_cert"])

changed = self._snap.configure(ctxt)
Expand Down
2 changes: 1 addition & 1 deletion src/nats_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ def configure(self, config: dict, restart: bool = True) -> bool:
# Restart the snap service only if it was running already
if restart:
self._snap.restart()
return (config_changed or use_tls)
return config_changed or use_tls

def _generate_config(self, config: NATSConfig) -> str:
tenv = Environment(loader=FileSystemLoader("templates"))
Expand Down
4 changes: 3 additions & 1 deletion src/relations/cluster_peers.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,7 @@ def listen_address(self):
def ingress_address(self):
"""Property to get the ingress address."""
if not self._ingress_address:
self._ingress_address = self.model.get_binding(self._relation_name).network.ingress_address
self._ingress_address = self.model.get_binding(
self._relation_name
).network.ingress_address
return self._ingress_address
20 changes: 18 additions & 2 deletions src/relations/natsclient_provider.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"""NATS Client Provider for `nats` interface."""
import ipaddress

import logging
from ops import Object, StoredState

from ops import JujuVersion, Object, SecretNotFoundError, StoredState

logger = logging.getLogger(__name__)


class NATSClientProvider(Object):
"""`nats` interface for the client to NATS."""

Expand All @@ -23,6 +24,8 @@ def __init__(self, charm, relation_name, listen_on_all_addresses, client_port):
self._listen_address = None
self._ingress_addresses = None
self.state.set_default(tls_ca=None)
if JujuVersion.from_environ().has_secrets:
self._secret_label = "nats-protected-url"

@property
def listen_address(self):
Expand Down Expand Up @@ -66,6 +69,19 @@ def expose_nats(self, auth_token=None):
if self.model.unit.is_leader() and self.state.tls_ca is not None:
rel.data[self.model.app]["ca_cert"] = self.state.tls_ca

# Use secrets only if juju > 3.0
# TODO: make this the only way to share url once all charms use the
# charm-lib and juju > 3.0
if JujuVersion.from_environ().has_secrets:
try:
secret = self.model.get_secret(label=self._secret_label)
except SecretNotFoundError:
logger.debug("Secret not found, creating a new one")
secret = self.model.unit.add_secret(
content={"url": url}, label=self._secret_label
)
secret.grant(rel)

@property
def ingress_addresses(self):
"""Property to get the ingress address."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,19 @@ def _on_client_relation_changed(self, event):
unit_data = event.relation.data.get(event.unit)
if not unit_data:
logger.error("data not found in relation")
self.unit.status = ops.BlockedStatus('waiting for relation data')
self.unit.status = ops.BlockedStatus("waiting for relation data")
return
url = unit_data.get("url")
if not url:
logger.error("url not found")
self.unit.status = ops.BlockedStatus('waiting for relation data')
self.unit.status = ops.BlockedStatus("waiting for relation data")
return
connect_opts = {}
if url.startswith("tls"):
cert = event.relation.data.get(event.app).get("ca_cert")
if not cert:
logger.error("ca_cert not found")
self.unit.status = ops.BlockedStatus('waiting for relation data')
self.unit.status = ops.BlockedStatus("waiting for relation data")
return
tls = ssl.create_default_context(cadata=cert)
connect_opts.update({"tls": tls})
Expand Down
15 changes: 14 additions & 1 deletion tests/integration/relation_tests/test_tls.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import asyncio

import pytest
from helpers import APP_NAMES, APPLICATION_APP_NAME, TEST_APP_CHARM_PATH
from nrpe.client import logging
from pytest_operator.plugin import OpsTest

from tests.integration.relation_tests.helpers import CHARM_NAME

TLS_CA_CHARM_NAME = "easyrsa"


@pytest.mark.skip_if_deployed
async def test_deploy_tls(ops_test: OpsTest):
charms = await ops_test.build_charms(".", TEST_APP_CHARM_PATH)
async with ops_test.fast_forward():
Expand Down Expand Up @@ -41,7 +44,17 @@ async def test_tls_enabled(ops_test: OpsTest):
await ops_test.model.wait_for_idle(apps=[*APP_NAMES, TLS_CA_CHARM_NAME], status="active")


async def test_adding_unit_to_nats_cluster_works(ops_test: OpsTest):
async def test_secrets(ops_test: OpsTest):
# Check that on juju 3 we have secrets
if hasattr(ops_test.model, "list_secrets"):
logging.info("checking for secrets")
secrets = await ops_test.model.list_secrets()
assert len(secrets.results) > 1, "secrets not found"
else:
pytest.skip("secrets not supported for juju < 3.0")


async def test_adding_unit_works(ops_test: OpsTest):
async with ops_test.fast_forward():
nats_app = ops_test.model.applications[CHARM_NAME]
await nats_app.add_units(count=2)
Expand Down

0 comments on commit 2b44f86

Please sign in to comment.