From 1371de30d0deb9ceac94fd3e666d91b9d1f89f2a Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 30 Jan 2024 02:16:18 +0000 Subject: [PATCH] feat(secrets): helper function gets secret by name --- benefits/secrets.py | 30 ++++++++++++++---------- tests/pytest/test_secrets.py | 45 ++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 tests/pytest/test_secrets.py diff --git a/benefits/secrets.py b/benefits/secrets.py index 5a509704bf..a94d699db4 100644 --- a/benefits/secrets.py +++ b/benefits/secrets.py @@ -9,24 +9,30 @@ KEY_VAULT_URL = "https://kv-cdt-pub-calitp-{env}-001.vault.azure.net/" +def get_secret_by_name(secret_name, client=None): + if client is None: + # construct the KeyVault URL from the runtime environment + # see https://docs.calitp.org/benefits/deployment/infrastructure/#environments + # and https://github.com/cal-itp/benefits/blob/dev/terraform/key_vault.tf + runtime_env = settings.RUNTIME_ENVIRONMENT() + vault_url = KEY_VAULT_URL.format(env=runtime_env[0]) + + credential = DefaultAzureCredential() + client = SecretClient(vault_url=vault_url, credential=credential) + + secret = client.get_secret(secret_name) + return secret.value + + if __name__ == "__main__": args = sys.argv[1:] if len(args) < 1: print("Provide the name of the secret to read") exit(1) - # construct the KeyVault URL from the runtime environment - # see https://docs.calitp.org/benefits/deployment/infrastructure/#environments - # and https://github.com/cal-itp/benefits/blob/dev/terraform/key_vault.tf - runtime_env = settings.RUNTIME_ENVIRONMENT() - vault_url = KEY_VAULT_URL.format(env=runtime_env[0]) - secret_name = args[0] + secret_value = get_secret_by_name(secret_name) - credential = DefaultAzureCredential() - client = SecretClient(vault_url=vault_url, credential=credential) - secret = client.get_secret(secret_name) - - print(f"Reading {secret_name} from {vault_url}") - print(f"Value: {secret.value}") + print(f"Reading {secret_name} from {settings.RUNTIME_ENVIRONMENT()}") + print(f"Value: {secret_value}") exit(0) diff --git a/tests/pytest/test_secrets.py b/tests/pytest/test_secrets.py new file mode 100644 index 0000000000..6c42c6a1ea --- /dev/null +++ b/tests/pytest/test_secrets.py @@ -0,0 +1,45 @@ +import pytest + +from benefits.secrets import KEY_VAULT_URL, get_secret_by_name + + +@pytest.fixture(autouse=True) +def mock_DefaultAzureCredential(mocker): + # patching the class to ensure new instances always return the same mock + credential_cls = mocker.patch("benefits.secrets.DefaultAzureCredential") + credential_cls.return_value = mocker.Mock() + return credential_cls + + +def test_get_secret_by_name__mock_client__returns_value(mocker): + secret_name = "the secret name" + secret_value = "the secret value" + client = mocker.patch("benefits.secrets.SecretClient") + client.get_secret.return_value = mocker.Mock(value=secret_value) + + actual_value = get_secret_by_name(secret_name, client) + + client.get_secret.assert_called_once_with(secret_name) + assert actual_value == secret_value + + +def test_get_secret_by_name__None_client__returns_value(mocker, settings, mock_DefaultAzureCredential): + secret_name = "the secret name" + secret_value = "the secret value" + + # override runtime to dev + settings.RUNTIME_ENVIRONMENT = lambda: "dev" + expected_keyvault_url = KEY_VAULT_URL.format(env="d") + + # set up the mock client class and expected return values + # this test does not pass in a known client, instead checking that a client is constructed as expected + mock_credential = mock_DefaultAzureCredential.return_value + client_cls = mocker.patch("benefits.secrets.SecretClient") + client = client_cls.return_value + client.get_secret.return_value = mocker.Mock(value=secret_value) + + actual_value = get_secret_by_name(secret_name) + + client_cls.assert_called_once_with(vault_url=expected_keyvault_url, credential=mock_credential) + client.get_secret.assert_called_once_with(secret_name) + assert actual_value == secret_value