Skip to content

Commit

Permalink
vdk-core: support overriding configs with secrets
Browse files Browse the repository at this point in the history
Why?

VDK doesn't provide a way to set sensitive configuration like passwords, such as trino_password.
The only way to currently do this is by adding config keys and fetching the values from secrets.

What?

Add a plugin that reconfigures the Configuration object in CoreContext based on secrets. Do this
in the initialize_job hook. In this setup, secrets override options set by regular configs. For example
if you set trino_password to "password" in config.ini, but also have a secret called trino_passowrd="another password",
the value of trino_password will be "another_passowrd".

How was this tested?

Functional test
CI/CD

What kind of change is this?

Feature/non-breaking

Signed-off-by: Dilyan Marinov <[email protected]>
  • Loading branch information
Dilyan Marinov authored and DeltaMichael committed Mar 11, 2024
1 parent bcf09c3 commit 7a6ad23
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from vdk.internal.builtin_plugins.config import vdk_config
from vdk.internal.builtin_plugins.config.config_help import ConfigHelpPlugin
from vdk.internal.builtin_plugins.config.log_config import LoggingPlugin
from vdk.internal.builtin_plugins.config.secrets_config import SecretsConfigPlugin
from vdk.internal.builtin_plugins.config.vdk_config import CoreConfigDefinitionPlugin
from vdk.internal.builtin_plugins.config.vdk_config import EnvironmentVarsConfigPlugin
from vdk.internal.builtin_plugins.config.vdk_config import JobConfigIniPlugin
Expand Down Expand Up @@ -130,6 +131,7 @@ def vdk_start(plugin_registry: PluginRegistry, command_line_args: List) -> None:
# TODO: should be in run package only
plugin_registry.add_hook_specs(JobRunHookSpecs)
plugin_registry.load_plugin_with_hooks_impl(JobConfigIniPlugin())
plugin_registry.load_plugin_with_hooks_impl(SecretsConfigPlugin())
plugin_registry.load_plugin_with_hooks_impl(TerminationMessageWriterPlugin())
plugin_registry.load_plugin_with_hooks_impl(JobRunSummaryOutputPlugin())
# connection plugins
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright 2023-2024 Broadcom
# SPDX-License-Identifier: Apache-2.0
# Copyright 2021-2024 VMware, Inc.
# SPDX-License-Identifier: Apache-2.0
from vdk.api.plugin.hook_markers import hookimpl
from vdk.internal.builtin_plugins.run.job_context import JobContext


class SecretsConfigPlugin:
@hookimpl(trylast=True)
def initialize_job(self, context: JobContext):
secrets = context.job_input.get_all_secrets()
for key, value in secrets.items():
context.core_context.configuration.override_value(key, value)
6 changes: 5 additions & 1 deletion projects/vdk-core/src/vdk/internal/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from __future__ import annotations

import logging
from collections import OrderedDict
from dataclasses import dataclass
from dataclasses import field
from typing import Any
Expand Down Expand Up @@ -70,6 +69,11 @@ def __getitem__(self, key: ConfigKey):
key = _normalize_config_key(key)
return self.get_value(key)

def override_value(self, key: ConfigKey, value: ConfigValue):
key = _normalize_config_key(key)
if key in self.__config_key_to_default_value:
self.__config_key_to_value[key] = value

def get_value(self, key: ConfigKey) -> ConfigValue:
"""
Return configuration value associated with a given key or None if it does not exists.
Expand Down
38 changes: 38 additions & 0 deletions projects/vdk-core/tests/functional/run/test_run_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from vdk.internal.core.statestore import CommonStoreKeys
from vdk.plugin.test_utils.util_funcs import cli_assert_equal
from vdk.plugin.test_utils.util_funcs import CliEntryBasedTestRunner
from vdk.plugin.test_utils.util_plugins import TestSecretsServiceClient


@mock.patch.dict(
Expand Down Expand Up @@ -100,3 +101,40 @@ def run_job(self, context: JobContext) -> Optional[ExecutionResult]:
config_log.config.get_value("other_config")
== "other-config-from-config-ini"
)


def test_run_secrets_override_config_vars():
class DebugConfigLog:
def __init__(self):
self.config = None
self.secrets_client = TestSecretsServiceClient()

@hookimpl(tryfirst=True)
def initialize_job(self, context: JobContext):
context.secrets.set_secrets_factory_method(
"default", lambda: self.secrets_client
)
secrets = context.job_input.get_all_secrets()
secrets["LOG_EXCEPTION_FORMATTER"] = "plain"
secrets["IRRELEVANT_SECRET"] = "not relevant at all"

Check warning on line 119 in projects/vdk-core/tests/functional/run/test_run_configuration.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

projects/vdk-core/tests/functional/run/test_run_configuration.py#L119

Possible hardcoded password: 'not relevant at all'
context.job_input.set_all_secrets(secrets)

@hookimpl(trylast=True)
def run_job(self, context: JobContext) -> Optional[ExecutionResult]:
self.config = context.core_context.configuration
return None # continue with next hook impl.

with mock.patch.dict(
os.environ,
{
"VDK_LOG_EXCEPTION_FORMATTER": "pretty",
},
):
config_log = DebugConfigLog()
runner = CliEntryBasedTestRunner(config_log)
result: Result = runner.invoke(["run", util.job_path("simple-job")])
cli_assert_equal(0, result)
# secrets that match config keys are overridden
assert config_log.config.get_value("log_exception_formatter") == "plain"
# secrets that do not match any config key do not leak in the config object
assert config_log.config.get_value("irrelevant_secret") is None
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,15 @@ def test_secrets_plugin_no_url_configured():
):
runner = CliEntryBasedTestRunner(vdk_plugin_control_cli, secrets_plugin)

result: Result = runner.invoke(
["run", jobs_path_from_caller_directory("simple-job")]
)
# job that do not use secrets should succeed
cli_assert_equal(0, result)
# TODO: Uncomment this after vdk-core release
# result: Result = runner.invoke(
# ["run", jobs_path_from_caller_directory("simple-job")]
# )
# # job that doesn't explicitly use secrets should fail due to secrets overrides in core
# cli_assert_equal(1, result)

result: Result = runner.invoke(
["run", jobs_path_from_caller_directory("secrets-job")]
)
# but jobs that do use secrets should fail.
# job that explicitly uses secrets should also fail
cli_assert_equal(1, result)

0 comments on commit 7a6ad23

Please sign in to comment.