From dcb466936a2fb77a9b256ae3f52006d2308d524b Mon Sep 17 00:00:00 2001 From: Anton Borodavchenko Date: Tue, 17 Jan 2023 13:09:20 +0100 Subject: [PATCH 1/3] feat(secrets): allow overriding secret config in get_secret helper --- k8t/filters.py | 4 ++-- k8t/secret_providers.py | 8 +++++--- tests/filters.py | 12 ++++++++---- tests/secret_providers.py | 5 +++++ 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/k8t/filters.py b/k8t/filters.py index 536f8ff..72b1c3e 100644 --- a/k8t/filters.py +++ b/k8t/filters.py @@ -80,7 +80,7 @@ def hashf(value, method="sha256"): return hash_method.hexdigest() -def get_secret(key: str, length: Optional[int] = None) -> str: +def get_secret(key: str, length: Optional[int] = None, config_override: Optional[dict] = {}) -> str: provider_name = config.CONFIG.get("secrets", {}).get("provider") if not provider_name: @@ -92,7 +92,7 @@ def get_secret(key: str, length: Optional[int] = None) -> str: except AttributeError as no_secret_provider: raise NotImplementedError(f"secret provider {provider_name} does not exist.") from no_secret_provider - return provider(key, length) + return provider(key, length, config_override) def to_bool(value: Any) -> Optional[bool]: diff --git a/k8t/secret_providers.py b/k8t/secret_providers.py index db961f7..094b5de 100644 --- a/k8t/secret_providers.py +++ b/k8t/secret_providers.py @@ -28,8 +28,10 @@ DEFAULT_SSM_REGION = "eu-central-1" -def ssm(key: str, length: Optional[int] = None) -> str: +def ssm(key: str, length: Optional[int] = None, config_override: Optional[dict] = {}) -> str: + # Merge the config given as an argument with default config. secrets_config = config.CONFIG.get("secrets", {}) + secrets_config.update(config_override) prefix = str(secrets_config.get("prefix", DEFAULT_SSM_PREFIX)) region = str(secrets_config.get("region", DEFAULT_SSM_REGION)) @@ -89,7 +91,7 @@ def _assume_role(role_arn: str, region: str) -> dict: raise RuntimeError(f"Failed to assume role {role_arn}: {exc}") from exc -def random(key: str, length: Optional[int] = None) -> str: +def random(key: str, length: Optional[int] = None, config_override: Optional[dict] = {}) -> str: LOGGER.debug("Requesting secret from %s", key) if key not in RANDOM_STORE: @@ -106,7 +108,7 @@ def random(key: str, length: Optional[int] = None) -> str: return RANDOM_STORE[key] -def hash(key: str, length: Optional[int] = None) -> str: +def hash(key: str, length: Optional[int] = None, config_override: Optional[dict] = {}) -> str: LOGGER.debug("Requesting secret from %s", key) if key not in RANDOM_STORE: diff --git a/tests/filters.py b/tests/filters.py index cd4fdff..d4bb51c 100644 --- a/tests/filters.py +++ b/tests/filters.py @@ -59,16 +59,20 @@ def test_get_secret(): config.CONFIG = {"secrets": {"provider": "random"}} with patch.object(secret_providers, "random") as mock: get_secret("any") - mock.assert_called_with("any", None) + mock.assert_called_with("any", None, {}) get_secret("any", 99) - mock.assert_called_with("any", 99) + mock.assert_called_with("any", 99, {}) + get_secret("any", 99, {"foo": "bar"}) + mock.assert_called_with("any", 99, {"foo": "bar"}) config.CONFIG = {"secrets": {"provider": "ssm"}} with patch.object(secret_providers, "ssm") as mock: get_secret("any") - mock.assert_called_with("any", None) + mock.assert_called_with("any", None, {}) get_secret("any", 99) - mock.assert_called_with("any", 99) + mock.assert_called_with("any", 99, {}) + get_secret("any", 99, {"foo": "bar"}) + mock.assert_called_with("any", 99, {"foo": "bar"}) config.CONFIG = {"secrets": {"provider": "nothing"}} with pytest.raises(NotImplementedError, match=r"secret provider nothing does not exist."): diff --git a/tests/secret_providers.py b/tests/secret_providers.py index 95f9a5c..da796b4 100644 --- a/tests/secret_providers.py +++ b/tests/secret_providers.py @@ -78,6 +78,11 @@ def test_ssm(): } assert ssm("/password"), "my_secret_value" + config.CONFIG = { + "secrets": {"provider": "ssm", "region": region, "prefix": "/app/dev"} + } + assert ssm("/test1", config_override={"prefix": "/dev"}), "string_value" + config.CONFIG = {"secrets": {"provider": "ssm", "region": "eu-central-1"}} with pytest.raises(RuntimeError, match=r"Failed to retrieve secret foo: ..."): ssm("foo") From 7ade8af09ba34c25dd74e5d751f8499caf6943d4 Mon Sep 17 00:00:00 2001 From: Anton Borodavchenko Date: Tue, 17 Jan 2023 13:15:30 +0100 Subject: [PATCH 2/3] feat(secrets): update README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 8a4f815..5206793 100644 --- a/README.md +++ b/README.md @@ -296,6 +296,12 @@ secrets: > > Be careful to follow this format when setting up the provider `prefix` and `get_secret(key)`. +Secrets config can be overridden in `get_secret` helper call by specifying `config_override` argument. + +```yaml +foobar: "{{ get_secret('/my-key', config_override={'prefix': '/dev'}) }}" +``` + ###### role assumption You can optionally assume an IAM role to retrieve secrets by specyfing `role_arn` in the config: From cae7bb04a864be5d11c487bf302bae26c80ae50d Mon Sep 17 00:00:00 2001 From: Anton Borodavchenko Date: Tue, 7 Feb 2023 10:48:31 +0100 Subject: [PATCH 3/3] update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5206793..9d260b6 100644 --- a/README.md +++ b/README.md @@ -296,7 +296,7 @@ secrets: > > Be careful to follow this format when setting up the provider `prefix` and `get_secret(key)`. -Secrets config can be overridden in `get_secret` helper call by specifying `config_override` argument. +Global secrets config can be overridden in `get_secret` helper function call by specifying `config_override` argument. ```yaml foobar: "{{ get_secret('/my-key', config_override={'prefix': '/dev'}) }}"