Skip to content

Commit

Permalink
Merge pull request #253 from canonical/IAM-882
Browse files Browse the repository at this point in the history
Move charm constants to separate file
  • Loading branch information
nsklikas authored Sep 3, 2024
2 parents 05f07f5 + 3936553 commit 4e1f8ca
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 130 deletions.
11 changes: 4 additions & 7 deletions src/certificate_transfer_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@
)
from ops import CharmBase, Object

from constants import CERTIFICATE_TRANSFER_NAME

LOCAL_CA_CERTS_PATH = Path("/usr/local/share/ca-certificates")
BUNDLE_PATH = "/etc/ssl/certs/ca-certificates.crt"
from constants import CA_BUNDLE_PATH, CERTIFICATE_TRANSFER_RELATION_NAME, LOCAL_CA_CERTS_PATH


class CertTransfer(Object):
Expand All @@ -26,7 +23,7 @@ def __init__(
charm: CharmBase,
container_name: str,
callback_fn: Callable,
cert_transfer_relation_name: str = CERTIFICATE_TRANSFER_NAME,
cert_transfer_relation_name: str = CERTIFICATE_TRANSFER_RELATION_NAME,
bundle_name: str = "ca-certificates.crt",
):
super().__init__(charm, cert_transfer_relation_name)
Expand Down Expand Up @@ -61,8 +58,8 @@ def push_ca_certs(self) -> None:

subprocess.run(["update-ca-certificates", "--fresh"], capture_output=True)

with open(BUNDLE_PATH) as f:
self.container.push(BUNDLE_PATH, f, make_dirs=True)
with open(CA_BUNDLE_PATH) as f:
self.container.push(CA_BUNDLE_PATH, f, make_dirs=True)

def clean_ca_certs(self) -> None:
"""Remove the cert bundle from the container."""
Expand Down
150 changes: 76 additions & 74 deletions src/charm.py

Large diffs are not rendered by default.

43 changes: 41 additions & 2 deletions src/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,45 @@

"""File containing all constants."""

INTERNAL_INGRESS_RELATION_NAME = "internal-ingress"
from pathlib import Path

# Charm constants
WORKLOAD_CONTAINER_NAME = "kratos"
CERTIFICATE_TRANSFER_NAME = "receive-ca-cert"
EMAIL_TEMPLATE_FILE_PATH = Path("/etc/config/templates") / "recovery-body.html.gotmpl"
MAPPERS_LOCAL_DIR_PATH = Path("claim_mappers")

# Application constants
KRATOS_ADMIN_PORT = 4434
KRATOS_PUBLIC_PORT = 4433
KRATOS_SERVICE_COMMAND = "kratos serve all"
DEFAULT_SCHEMA_ID_FILE_NAME = "default.schema"
KRATOS_CONFIG_MAP_NAME = "kratos-config"
LOG_LEVELS = ["panic", "fatal", "error", "warn", "info", "debug", "trace"]
CONFIG_DIR_PATH = Path("/etc/config/kratos")
LOG_DIR = Path("/var/log")
LOG_PATH = LOG_DIR / "kratos.log"
CONFIG_FILE_PATH = CONFIG_DIR_PATH / "kratos.yaml"
IDENTITY_SCHEMAS_LOCAL_DIR_PATH = Path("identity_schemas")

# Integration constants
PEER_RELATION_NAME = "kratos-peers"
INTERNAL_INGRESS_RELATION_NAME = "internal-ingress"
CERTIFICATE_TRANSFER_RELATION_NAME = "receive-ca-cert"
DB_RELATION_NAME = "pg-database"
HYDRA_RELATION_NAME = "hydra-endpoint-info"
LOGIN_UI_RELATION_NAME = "ui-endpoint-info"
PROMETHEUS_SCRAPE_RELATION_NAME = "metrics-endpoint"
LOKI_PUSH_API_RELATION_NAME = "logging"
GRAFANA_DASHBOARD_RELATION_NAME = "grafana-dashboard"
TRACING_RELATION_NAME = "tracing"

# Peer data keys
PEER_KEY_DB_MIGRATE_VERSION = "db_migrate_version"

# Secret constants
SECRET_LABEL = "cookie_secret"
COOKIE_SECRET_KEY = "cookiesecret"

# Cert transfer constants
LOCAL_CA_CERTS_PATH = Path("/usr/local/share/ca-certificates")
CA_BUNDLE_PATH = "/etc/ssl/certs/ca-certificates.crt"
18 changes: 13 additions & 5 deletions src/kratos.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@
import requests
from ops.model import Container

from constants import CONFIG_FILE_PATH

logger = logging.getLogger(__name__)


class KratosAPI:
"""A helper object for interacting with the kratos API."""

def __init__(self, kratos_admin_url: str, container: Container, config_file_path: str) -> None:
def __init__(self, kratos_admin_url: str, container: Container) -> None:
self.kratos_admin_url = kratos_admin_url
self.container = container
self.config_file_path = config_file_path

def create_identity(
self, traits: Dict, schema_id: str, password: Optional[str] = None
Expand Down Expand Up @@ -128,15 +129,22 @@ def reset_password(self, identity_id: str, password: str) -> Dict:
credentials = {
"password": {
"config": {
"hashed_password": bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
"hashed_password": bcrypt.hashpw(
password.encode("utf-8"), bcrypt.gensalt()
).decode("utf-8")
}
}
}

# Update the identity with new password.
# Note that passwords can't be updated with Kratos CLI
url = join(self.kratos_admin_url, f"admin/identities/{identity_id}")
data = {"state": state, "traits": traits, "schema_id": schema_id, "credentials": credentials}
data = {
"state": state,
"traits": traits,
"schema_id": schema_id,
"credentials": credentials,
}

r = requests.put(url, json=data)
r.raise_for_status()
Expand Down Expand Up @@ -189,7 +197,7 @@ def run_migration(self, dsn=None, timeout: float = 120) -> str:
env = {"DSN": dsn}
else:
cmd.append("--config")
cmd.append(self.config_file_path)
cmd.append(CONFIG_FILE_PATH)
env = None

return self._run_cmd(cmd, timeout=timeout, environment=env)
Expand Down
4 changes: 3 additions & 1 deletion tests/integration/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ async def test_get_identity(ops_test: OpsTest) -> None:

async def test_reset_password(ops_test: OpsTest) -> None:
secret_name = "password-secret"
secret_id = await ops_test.model.add_secret(name=secret_name, data_args=["password=some-password"])
secret_id = await ops_test.model.add_secret(
name=secret_name, data_args=["password=some-password"]
)
await ops_test.model.grant_secret(secret_name=secret_name, application=KRATOS_APP)

action = (
Expand Down
14 changes: 4 additions & 10 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def mocked_kratos_process() -> MagicMock:
def kratos_api(mocked_kratos_process: MagicMock) -> KratosAPI:
container = MagicMock()
container.exec = MagicMock(return_value=mocked_kratos_process)
return KratosAPI("http://localhost:4434", container, "/etc/config/kratos.yaml")
return KratosAPI("http://localhost:4434", container)


@pytest.fixture()
Expand Down Expand Up @@ -218,25 +218,19 @@ def mocked_recover_password_with_code(

@pytest.fixture()
def mocked_reset_password(mocker: MockerFixture, kratos_identity_json: Dict) -> MagicMock:
mock = mocker.patch(
"charm.KratosAPI.reset_password", return_value=kratos_identity_json
)
mock = mocker.patch("charm.KratosAPI.reset_password", return_value=kratos_identity_json)
return mock


@pytest.fixture()
def mocked_invalidate_sessions(mocker: MockerFixture) -> MagicMock:
mock = mocker.patch(
"charm.KratosAPI.invalidate_sessions", return_value=True
)
mock = mocker.patch("charm.KratosAPI.invalidate_sessions", return_value=True)
return mock


@pytest.fixture()
def mocked_delete_mfa_credential(mocker: MockerFixture) -> MagicMock:
mock = mocker.patch(
"charm.KratosAPI.delete_mfa_credential", return_value=True
)
mock = mocker.patch("charm.KratosAPI.delete_mfa_credential", return_value=True)
return mock


Expand Down
49 changes: 25 additions & 24 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def setup_postgres_relation(harness: Harness) -> None:
"postgresql-k8s",
{
"data": '{"database": "database", "extra-user-roles": "SUPERUSER"}',
"database": "kratos-model_kratos",
"endpoints": DB_ENDPOINTS,
"password": DB_PASSWORD,
"username": DB_USERNAME,
Expand Down Expand Up @@ -193,7 +194,7 @@ def setup_smtp_relation(harness: Harness, transport_security: str, skip_ssl_veri
"transport_security": transport_security,
"domain": "domain",
"skip_ssl_verify": skip_ssl_verify,
}
},
)
return smtp_relation_id

Expand Down Expand Up @@ -509,7 +510,7 @@ def test_on_pebble_ready_lk_called(
("starttls", "smtp", "False", ""),
("tls", "smtps", "True", "?skip_ssl_verify=true"),
("starttls", "smtp", "True", "?skip_ssl_verify=true"),
]
],
)
def test_config_file_with_smtp_integration(
harness: Harness,
Expand Down Expand Up @@ -555,7 +556,9 @@ def test_config_file_with_smtp_integration(
"default_browser_return_url": DEFAULT_BROWSER_RETURN_URL,
},
"courier": {
"smtp": {"connection_uri": f"{method}://example_user:some-password@example.smtp:25/{additional_param}"},
"smtp": {
"connection_uri": f"{method}://example_user:some-password@example.smtp:25/{additional_param}"
},
},
"serve": {
"public": {
Expand Down Expand Up @@ -1024,10 +1027,10 @@ def test_on_client_config_changed_with_ingress(
"after": {
"default_browser_return_url": login_databag["login_url"],
"hooks": [
{
"hook": "revoke_active_sessions",
},
],
{
"hook": "revoke_active_sessions",
},
],
},
},
"registration": {
Expand All @@ -1048,15 +1051,15 @@ def test_on_client_config_changed_with_ingress(
},
"password": {
"enabled": True,
"config": {
"haveibeenpwned_enabled": False,
},
"config": {
"haveibeenpwned_enabled": False,
},
},
"totp": {
"enabled": True,
"config": {
"issuer": "Identity Platform",
},
"totp": {
"enabled": True,
"config": {
"issuer": "Identity Platform",
},
},
"oidc": {
"config": {
Expand Down Expand Up @@ -1661,7 +1664,7 @@ def test_on_config_changed_when_recovery_email_template_set(
harness: Harness,
mocked_kratos_configmap: MagicMock,
mocked_migration_is_needed: MagicMock,
mocked_recovery_email_template: MagicMock
mocked_recovery_email_template: MagicMock,
) -> None:
setup_peer_relation(harness)
setup_postgres_relation(harness)
Expand All @@ -1676,9 +1679,7 @@ def test_on_config_changed_when_recovery_email_template_set(


def test_on_config_changed_when_local_idp_disabled(
harness: Harness,
mocked_kratos_configmap: MagicMock,
mocked_migration_is_needed: MagicMock
harness: Harness, mocked_kratos_configmap: MagicMock, mocked_migration_is_needed: MagicMock
) -> None:
setup_peer_relation(harness)
setup_postgres_relation(harness)
Expand Down Expand Up @@ -1765,9 +1766,7 @@ def test_on_config_changed_when_local_idp_disabled(


def test_on_config_changed_when_webauthn_enabled(
harness: Harness,
mocked_kratos_configmap: MagicMock,
mocked_migration_is_needed: MagicMock
harness: Harness, mocked_kratos_configmap: MagicMock, mocked_migration_is_needed: MagicMock
) -> None:
setup_peer_relation(harness)
setup_postgres_relation(harness)
Expand Down Expand Up @@ -1855,7 +1854,7 @@ def test_on_config_changed_when_webauthn_enabled(
"id": "public",
"origins": ["https://public"],
"display_name": "Identity Platform",
}
},
},
},
},
Expand Down Expand Up @@ -2186,7 +2185,9 @@ def test_error_on_reset_mfa_action_with_identity_id_when_mfa_type_uncorrect(

harness.charm._on_reset_identity_mfa_action(event)

event.fail.assert_called_with(f"Unsupported MFA credential type {unsupported_type}, allowed methods are: `totp` and `lookup_secret`")
event.fail.assert_called_with(
f"Unsupported MFA credential type {unsupported_type}, allowed methods are: `totp` and `lookup_secret`"
)


@pytest.mark.parametrize("mfa_type", ["totp", "lookup_secret"])
Expand Down
11 changes: 4 additions & 7 deletions tests/unit/test_kratos.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ops.pebble import ExecError
from pytest_mock import MockerFixture

from constants import CONFIG_FILE_PATH
from kratos import KratosAPI


Expand Down Expand Up @@ -207,19 +208,15 @@ def test_reset_password(
assert ret == kratos_identity_json


def test_invalidate_sessions(
kratos_api: KratosAPI, mocker: MockerFixture
) -> None:
def test_invalidate_sessions(kratos_api: KratosAPI, mocker: MockerFixture) -> None:
mocker.patch("requests.delete")

ret = kratos_api.invalidate_sessions("identity_id")

assert ret


def test_delete_mfa_credential(
kratos_api: KratosAPI, mocker: MockerFixture
) -> None:
def test_delete_mfa_credential(kratos_api: KratosAPI, mocker: MockerFixture) -> None:
mocker.patch("requests.delete")

ret = kratos_api.delete_mfa_credential("identity_id", "totp")
Expand All @@ -240,7 +237,7 @@ def test_run_migration(kratos_api: KratosAPI, mocked_kratos_process: MagicMock)
"-e",
"--yes",
"--config",
kratos_api.config_file_path,
CONFIG_FILE_PATH,
]

assert expected_output == cmd_output

0 comments on commit 4e1f8ca

Please sign in to comment.