diff --git a/lib/charms/mongodb/v1/helpers.py b/lib/charms/mongodb/v1/helpers.py index ea2dd7363..806c0ed2f 100644 --- a/lib/charms/mongodb/v1/helpers.py +++ b/lib/charms/mongodb/v1/helpers.py @@ -143,6 +143,9 @@ def get_mongod_args( # for simplicity we run the mongod daemon on shards, configsvrs, and replicas on the same # port f"--port={Config.MONGODB_PORT}", + "--auditDestination=file", + f"--auditFormat={Config.AuditLog.FORMAT}", + f"--auditPath={full_data_dir}/{Config.AuditLog.FILE_NAME}", logging_options, ] if auth: diff --git a/src/config.py b/src/config.py index 95263189a..1b0a22989 100644 --- a/src/config.py +++ b/src/config.py @@ -18,19 +18,19 @@ class Config: MONGOD_CONF_FILE_PATH = f"{MONGOD_CONF_DIR}/mongod.conf" SNAP_PACKAGES = [("charmed-mongodb", "6/edge", 87)] - class Role: - """Role config names for MongoDB Charm.""" - - CONFIG_SERVER = "config-server" - REPLICATION = "replication" - SHARD = "shard" - + # Keep these alphabetically sorted class Actions: """Actions related config for MongoDB Charm.""" PASSWORD_PARAM_NAME = "password" USERNAME_PARAM_NAME = "username" + class AuditLog: + """Audit log related configuration.""" + + FORMAT = "JSON" + FILE_NAME = "audit.json" + class Backup: """Backup related config for MongoDB Charm.""" @@ -80,6 +80,13 @@ class Relations: DB_RELATIONS = [OBSOLETE_RELATIONS_NAME, NAME] Scopes = Literal[APP_SCOPE, UNIT_SCOPE] + class Role: + """Role config names for MongoDB Charm.""" + + CONFIG_SERVER = "config-server" + REPLICATION = "replication" + SHARD = "shard" + class Secrets: """Secrets related constants.""" diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 3ce39568f..efe1f38e5 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -2,6 +2,7 @@ # See LICENSE file for licensing details. import json +import logging from pathlib import Path from typing import Dict, Optional @@ -18,6 +19,9 @@ SERIES = "jammy" +logger = logging.getLogger(__name__) + + def unit_uri(ip_address: str, password, app=APP_NAME) -> str: """Generates URI that is used by MongoDB to connect to a single replica. @@ -189,3 +193,12 @@ async def get_secret_content(ops_test, secret_id) -> Dict[str, str]: _, stdout, _ = await ops_test.juju(*complete_command.split()) data = json.loads(stdout) return data[secret_id]["content"]["Data"] + + +def audit_log_line_sanity_check(entry) -> bool: + fields = ["atype", "ts", "local", "remote", "users", "roles", "param", "result"] + for field in fields: + if entry.get(field) is None: + logger.error("Field '%s' not found in audit log entry \"%s\"", field, entry) + return False + return True diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py index a3c6cd250..b52a78751 100644 --- a/tests/integration/test_charm.py +++ b/tests/integration/test_charm.py @@ -5,7 +5,9 @@ import json import logging import os +import subprocess import time +from subprocess import check_output from uuid import uuid4 import pytest @@ -20,6 +22,7 @@ APP_NAME, PORT, UNIT_IDS, + audit_log_line_sanity_check, count_primaries, find_unit, get_leader_id, @@ -314,3 +317,22 @@ def juju_reports_one_primary(unit_messages): # cleanup, remove killed unit await ops_test.model.destroy_unit(target_unit) + + +async def test_audit_log(ops_test: OpsTest) -> None: + """Test that audit log was created and contains actual audit data.""" + leader_unit = await find_unit(ops_test, leader=True) + audit_log_snap_path = "/var/snap/charmed-mongodb/common/var/lib/mongodb/audit.json" + audit_log = check_output( + f"JUJU_MODEL={ops_test.model_full_name} juju ssh {leader_unit.name} 'sudo cat {audit_log_snap_path}'", + stderr=subprocess.PIPE, + shell=True, + universal_newlines=True, + ) + + for line in audit_log.splitlines(): + if not len(line): + continue + item = json.loads(line) + # basic sanity check + assert audit_log_line_sanity_check(item), "Audit sanity log check failed for first line" diff --git a/tests/unit/test_mongodb_helpers.py b/tests/unit/test_mongodb_helpers.py index d64557a12..4c5bd675c 100644 --- a/tests/unit/test_mongodb_helpers.py +++ b/tests/unit/test_mongodb_helpers.py @@ -14,6 +14,9 @@ def test_get_mongod_args(self): "--replSet=my_repl_set", "--dbpath=/var/snap/charmed-mongodb/common/var/lib/mongodb", "--port=27017", + "--auditDestination=file", + "--auditFormat=JSON", + "--auditPath=/var/snap/charmed-mongodb/common/var/lib/mongodb/audit.json", "--auth", "--clusterAuthMode=keyFile", "--keyFile=/var/snap/charmed-mongodb/current/etc/mongod/keyFile", @@ -36,6 +39,9 @@ def test_get_mongod_args(self): "--replSet=my_repl_set", "--dbpath=/var/snap/charmed-mongodb/common/var/lib/mongodb", "--port=27017", + "--auditDestination=file", + "--auditFormat=JSON", + "--auditPath=/var/snap/charmed-mongodb/common/var/lib/mongodb/audit.json", ] self.assertEqual( @@ -50,6 +56,9 @@ def test_get_mongod_args(self): "--replSet=my_repl_set", "--dbpath=/var/lib/mongodb", "--port=27017", + "--auditDestination=file", + "--auditFormat=JSON", + "--auditPath=/var/lib/mongodb/audit.json", "--logpath=/var/lib/mongodb/mongodb.log", ]