Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Store authentication keys in a separate database #55

Merged
merged 3 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ juju deploy sdcore-nrf-k8s --channel=edge
juju deploy sdcore-udr-k8s --channel=edge
juju deploy self-signed-certificates --channel=beta
juju integrate mongodb-k8s sdcore-nrf-k8s
juju integrate mongodb-k8s sdcore-udr-k8s:database
juju integrate mongodb-k8s sdcore-udr-k8s:common_database
juju integrate mongodb-k8s sdcore-udr-k8s:auth_database
juju integrate sdcore-nrf-k8s:certificates self-signed-certificates:certificates
juju integrate sdcore-nrf-k8s:fiveg_nrf sdcore-udr-k8s:fiveg_nrf
juju integrate sdcore-udr-k8s:certificates self-signed-certificates:certificates
Expand Down
4 changes: 3 additions & 1 deletion metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ containers:
location: /support/TLS

requires:
database:
common_database:
interface: mongodb_client
auth_database:
interface: mongodb_client
fiveg_nrf:
interface: fiveg_nrf
Expand Down
175 changes: 128 additions & 47 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,20 @@
generate_private_key,
)
from jinja2 import Environment, FileSystemLoader
from ops import ActiveStatus, BlockedStatus, RelationBrokenEvent, WaitingStatus
from ops.charm import CharmBase, EventBase
from ops.main import main
from ops.model import ActiveStatus, BlockedStatus, WaitingStatus
from ops.pebble import Layer, PathError

logger = logging.getLogger(__name__)

BASE_CONFIG_PATH = "/free5gc/config"
DEFAULT_DATABASE_NAME = "free5gc"
COMMON_DATABASE_NAME = "free5gc"
AUTH_DATABASE_NAME = "authentication"
COMMON_DATABASE_RELATION_NAME = "common_database"
AUTH_DATABASE_RELATION_NAME = "auth_database"
NRF_RELATION_NAME = "fiveg_nrf"
TLS_RELATION_NAME = "certificates"
UDR_CONFIG_FILE_NAME = "udrcfg.yaml"
UDR_SBI_PORT = 29504
CERTS_DIR_PATH = "/support/TLS" # Certificate paths are hardcoded in UDR code
Expand All @@ -44,17 +49,27 @@ def __init__(self, *args):
super().__init__(*args)
self._container_name = self._service_name = "udr"
self._container = self.unit.get_container(self._container_name)
self._nrf = NRFRequires(charm=self, relation_name="fiveg_nrf")
self._database = DatabaseRequires(
self, relation_name="database", database_name=DEFAULT_DATABASE_NAME
self._nrf = NRFRequires(charm=self, relation_name=NRF_RELATION_NAME)
self._common_database = DatabaseRequires(
self, relation_name=COMMON_DATABASE_RELATION_NAME, database_name=COMMON_DATABASE_NAME
)
self._auth_database = DatabaseRequires(
self, relation_name=AUTH_DATABASE_RELATION_NAME, database_name=AUTH_DATABASE_NAME
)
self.unit.set_ports(UDR_SBI_PORT)
self._certificates = TLSCertificatesRequiresV3(self, "certificates")
self._certificates = TLSCertificatesRequiresV3(self, TLS_RELATION_NAME)

self.framework.observe(self.on.udr_pebble_ready, self._configure_udr)
self.framework.observe(self.on.database_relation_joined, self._configure_udr)
self.framework.observe(self.on.database_relation_broken, self._on_database_relation_broken)
self.framework.observe(self._database.on.database_created, self._configure_udr)
self.framework.observe(self.on.common_database_relation_joined, self._configure_udr)
self.framework.observe(self.on.auth_database_relation_joined, self._configure_udr)
self.framework.observe(
self.on.common_database_relation_broken, self._on_common_database_relation_broken
)
self.framework.observe(
self.on.auth_database_relation_broken, self._on_auth_database_relation_broken
)
self.framework.observe(self._common_database.on.database_created, self._configure_udr)
self.framework.observe(self._auth_database.on.database_created, self._configure_udr)
self.framework.observe(self.on.fiveg_nrf_relation_joined, self._configure_udr)
self.framework.observe(self._nrf.on.nrf_available, self._configure_udr)
self.framework.observe(self._nrf.on.nrf_broken, self._on_nrf_broken)
Expand All @@ -74,6 +89,38 @@ def __init__(self, *args):
self._certificates.on.certificate_expiring, self._on_certificate_expiring
)

def ready_to_configure(self) -> bool:
"""Returns whether the preconditions are met to proceed with configuration."""
for relation in [
COMMON_DATABASE_RELATION_NAME,
AUTH_DATABASE_RELATION_NAME,
NRF_RELATION_NAME,
TLS_RELATION_NAME,
]:
if not self._relation_created(relation):
self.unit.status = BlockedStatus(
f"Waiting for the {relation} relation to be created"
)
return False
if not self._common_database_is_available():
self.unit.status = WaitingStatus("Waiting for the common database to be available")
return False
if not self._auth_database_is_available():
self.unit.status = WaitingStatus(
"Waiting for the authentication database to be available"
)
return False
if not self._get_common_database_url():
self.unit.status = WaitingStatus("Waiting for the common database url to be available")
return False
if not self._get_auth_database_url():
self.unit.status = WaitingStatus("Waiting for the auth database url to be available")
return False
if not self._nrf_is_available():
self.unit.status = WaitingStatus("Waiting for the NRF to be available")
return False
return True

def _configure_udr(self, event: EventBase) -> None:
"""Main callback function of the UDR operator.

Expand All @@ -83,21 +130,7 @@ def _configure_udr(self, event: EventBase) -> None:
Args:
event: Juju event
"""
for relation in ["database", "fiveg_nrf", "certificates"]:
if not self._relation_created(relation):
self.unit.status = BlockedStatus(
f"Waiting for the `{relation}` relation to be created"
)
return
if not self._database_is_available():
self.unit.status = WaitingStatus("Waiting for the database to be available")
return
if not self._get_database_data():
self.unit.status = WaitingStatus("Waiting for the database data to be available")
event.defer()
return
if not self._nrf_is_available():
self.unit.status = WaitingStatus("Waiting for the NRF to be available")
if not self.ready_to_configure():
return
if not self._container.can_connect():
self.unit.status = WaitingStatus("Waiting for the container to be ready")
Expand All @@ -118,21 +151,33 @@ def _configure_udr(self, event: EventBase) -> None:
self._configure_udr_service()
self.unit.status = ActiveStatus()

def _on_nrf_broken(self, event: EventBase) -> None:
def _on_nrf_broken(self, event: RelationBrokenEvent) -> None:
"""Event handler for NRF relation broken.

Args:
event (NRFBrokenEvent): Juju event
"""
self.unit.status = BlockedStatus("Waiting for fiveg_nrf relation")

def _on_database_relation_broken(self, event: EventBase) -> None:
"""Event handler for database relation broken.
def _on_common_database_relation_broken(self, event: RelationBrokenEvent) -> None:
"""Event handler for common database relation broken.

Args:
event: Juju event
"""
if not self.model.relations[COMMON_DATABASE_RELATION_NAME]:
self.unit.status = BlockedStatus(
f"Waiting for {COMMON_DATABASE_RELATION_NAME} relation"
)

def _on_auth_database_relation_broken(self, event: RelationBrokenEvent) -> None:
"""Event handler for auth database relation broken.

Args:
event: Juju event
"""
self.unit.status = BlockedStatus("Waiting for database relation")
if not self.model.relations[AUTH_DATABASE_RELATION_NAME]:
self.unit.status = BlockedStatus(f"Waiting for {AUTH_DATABASE_RELATION_NAME} relation")

def _on_certificates_relation_created(self, event: EventBase) -> None:
"""Generates Private key."""
Expand All @@ -141,7 +186,7 @@ def _on_certificates_relation_created(self, event: EventBase) -> None:
return
self._generate_private_key()

def _on_certificates_relation_broken(self, event: EventBase) -> None:
def _on_certificates_relation_broken(self, event: RelationBrokenEvent) -> None:
"""Deletes TLS related artifacts and reconfigures workload."""
if not self._container.can_connect():
event.defer()
Expand Down Expand Up @@ -280,8 +325,10 @@ def _generate_udr_config_file(self) -> None:
content = self._render_config_file(
udr_ip_address=_get_pod_ip(), # type: ignore[arg-type]
udr_sbi_port=UDR_SBI_PORT,
default_database_name=DEFAULT_DATABASE_NAME,
default_database_url=self._get_database_data()["uris"].split(",")[0],
common_database_name=COMMON_DATABASE_NAME,
common_database_url=self._get_common_database_url(),
auth_database_name=AUTH_DATABASE_NAME,
auth_database_url=self._get_auth_database_url(),
nrf_url=self._nrf.nrf_url,
scheme="https",
)
Expand Down Expand Up @@ -311,8 +358,10 @@ def _render_config_file(
*,
udr_ip_address: str,
udr_sbi_port: int,
default_database_name: str,
default_database_url: str,
common_database_name: str,
auth_database_name: str,
common_database_url: str,
auth_database_url: str,
nrf_url: str,
scheme: str,
) -> str:
Expand All @@ -321,8 +370,10 @@ def _render_config_file(
Args:
udr_ip_address (str): UDR IP address.
udr_sbi_port (str): UDR SBI port.
default_database_name (str): Database name.
default_database_url (str): Database URL.
common_database_name (str): Commmon Database name.
auth_database_name (str): Database name to store authentication keys.
common_database_url (str): Common Database URL.
auth_database_url (str): Authentication Database URL.
nrf_url (str): NRF URL.
scheme (str): SBI interface scheme ("http" or "https")

Expand All @@ -334,8 +385,10 @@ def _render_config_file(
return template.render(
udr_ip_address=udr_ip_address,
udr_sbi_port=udr_sbi_port,
default_database_name=default_database_name,
default_database_url=default_database_url,
common_database_name=common_database_name,
common_database_url=common_database_url,
auth_database_name=auth_database_name,
auth_database_url=auth_database_url,
nrf_url=nrf_url,
scheme=scheme,
)
Expand Down Expand Up @@ -364,15 +417,35 @@ def _push_udr_config_file_to_workload(self, content: str) -> None:
)
logger.info(f"Config file {UDR_CONFIG_FILE_NAME} pushed to workload.")

def _get_database_data(self) -> dict:
"""Returns the database data.
def _get_common_database_url(self) -> str:
"""Returns the common database URL.

Returns:
dict: The database data.
str: The common database URL.
"""
if not self._database_is_available():
raise RuntimeError(f"Database `{DEFAULT_DATABASE_NAME}` is not available")
return self._database.fetch_relation_data()[self._database.relations[0].id]
if not self._common_database_is_available():
raise RuntimeError(f"Database `{COMMON_DATABASE_NAME}` is not available")
uris = self._common_database.fetch_relation_data()[
self._common_database.relations[0].id
].get("uris")
if uris:
return uris.split(",")[0]
return ""

def _get_auth_database_url(self) -> str:
"""Returns the authentication database URL.

Returns:
str: The authentication database URL.
"""
if not self._auth_database_is_available():
raise RuntimeError(f"Database `{AUTH_DATABASE_NAME}` is not available")
uris = self._auth_database.fetch_relation_data()[self._auth_database.relations[0].id].get(
"uris"
)
if uris:
return uris.split(",")[0]
return ""

def _nrf_is_available(self) -> bool:
"""Returns whether the NRF endpoint is available.
Expand All @@ -382,13 +455,21 @@ def _nrf_is_available(self) -> bool:
"""
return bool(self._nrf.nrf_url)

def _database_is_available(self) -> bool:
"""Returns whether database relation is available.
def _common_database_is_available(self) -> bool:
"""Returns whether common database relation is available.

Returns:
bool: Whether common database relation is available.
"""
return bool(self._common_database.is_resource_created())

def _auth_database_is_available(self) -> bool:
"""Returns whether authentication database relation is available.

Returns:
bool: Whether database relation is available.
bool: Whether authentication database relation is available.
"""
return bool(self._database.is_resource_created())
return bool(self._auth_database.is_resource_created())

@property
def _pebble_layer(self) -> Layer:
Expand Down
6 changes: 4 additions & 2 deletions src/templates/udrcfg.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ configuration:
bindingIPv4: 0.0.0.0
port: {{ udr_sbi_port }}
mongodb:
name: {{ default_database_name }}
url: {{ default_database_url }}
name: {{ common_database_name }}
url: {{ common_database_url }}
authKeysDbName: {{ auth_database_name }}
authUrl: {{ auth_database_url }}
nrfUri: {{ nrf_url }}

# the kind of log output
Expand Down
4 changes: 2 additions & 2 deletions terraform/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ module "sdcore-udr-k8s" {
Create the integrations, for instance:

```text
resource "juju_integration" "udr-db" {
resource "juju_integration" "udr-common-db" {
model = var.model_name

application {
name = module.udr.app_name
endpoint = module.udr.database_endpoint
endpoint = module.udr.commmon_database_endpoint
}

application {
Expand Down
11 changes: 8 additions & 3 deletions terraform/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ output "app_name" {
value = juju_application.sdcore-udr-k8s.name
}

output "database_endpoint" {
description = "Name of the endpoint to integrate with MongoDB using mongodb_client interface."
value = "database"
output "common_database_endpoint" {
description = "Name of the endpoint to integrate with MongoDB for common_database using mongodb_client interface."
value = "common_database"
}

output "auth_database_endpoint" {
description = "Name of the endpoint to integrate with MongoDB for auth_database using mongodb_client interface."
value = "auth_database"
}

output "fiveg_nrf_endpoint" {
Expand Down
Loading
Loading