diff --git a/documentation/docs/getting-started/configuration.mdx b/documentation/docs/getting-started/configuration.mdx index 5cd2ed4c..69d40d72 100644 --- a/documentation/docs/getting-started/configuration.mdx +++ b/documentation/docs/getting-started/configuration.mdx @@ -39,7 +39,8 @@ Please use this table as a reference. | AUTH_PUBLIC_KEY | | | | AUTH_JWT_ALGORITHM | JWT algorithm. See possible values [here](https://pyjwt.readthedocs.io/en/stable/algorithms.html). | | | AUTH_JWT_AUDIENCE | | | -| AUTH_JWT_ISSUER | +| AUTH_JWT_ISSUER | | | +| INLINE_OPA_EXECUTABLE_PATH | Path to the OPA executable. If not specified, defaults to "opa", assuming the OPA executable is in the system PATH. | /usr/local/bin/opa | ## OPAL Server Configuration Variables diff --git a/packages/opal-client/opal_client/config.py b/packages/opal-client/opal_client/config.py index 58d7ae2c..80ef2340 100644 --- a/packages/opal-client/opal_client/config.py +++ b/packages/opal-client/opal_client/config.py @@ -147,6 +147,13 @@ def load_policy_store(): "INLINE_OPA_LOG_FORMAT", EngineLogFormat, EngineLogFormat.NONE ) + + INLINE_OPA_EXECUTABLE_PATH = confi.str( + "INLINE_OPA_EXECUTABLE_PATH", + "opa", + description="Path to the OPA executable. Defaults to 'opa' if not specified." + ) + # Cedar runner configuration (Cedar-engine can optionally be run by OPAL) ---------------- # whether or not OPAL should run the Cedar agent by itself in the same container diff --git a/packages/opal-client/opal_client/engine/options.py b/packages/opal-client/opal_client/engine/options.py index 370424e6..abae6d6a 100644 --- a/packages/opal-client/opal_client/engine/options.py +++ b/packages/opal-client/opal_client/engine/options.py @@ -63,6 +63,8 @@ class OpaServerOptions(BaseModel): description="list of built-in rego policies and data.json files that must be loaded into OPA on startup. e.g: system.authz policy when using --authorization=basic, see: https://www.openpolicyagent.org/docs/latest/security/#authentication-and-authorization", ) + opa_executable_path: str = Field(default="opa", description="Path to the OPA executable") + class Config: use_enum_values = True allow_population_by_field_name = True diff --git a/packages/opal-client/opal_client/engine/runner.py b/packages/opal-client/opal_client/engine/runner.py index 9cca62c2..eb34ab42 100644 --- a/packages/opal-client/opal_client/engine/runner.py +++ b/packages/opal-client/opal_client/engine/runner.py @@ -5,10 +5,11 @@ from typing import Callable, Coroutine, List, Optional import psutil -from opal_client.config import EngineLogFormat +from opal_client.config import EngineLogFormat, opal_client_config from opal_client.engine.logger import log_engine_output_opa, log_engine_output_simple from opal_client.engine.options import CedarServerOptions, OpaServerOptions from opal_client.logger import logger + from tenacity import retry, wait_random_exponential AsyncCallback = Callable[[], Coroutine] @@ -252,7 +253,18 @@ def command(self) -> str: opts = self._options.get_cli_options_dict() opts_string = " ".join([f"{k}={v}" for k, v in opts.items()]) startup_files = self._options.get_opa_startup_files() - return f"opa run --server {opts_string} {startup_files}".strip() + opa_path = self._options.opa_executable_path + + # Check if the OPA executable exists and is a file + if not os.path.isfile(opa_path): + raise FileNotFoundError(f"OPA executable not found at path: {opa_path}") + + opts = self._options.get_cli_options_dict() + opts_string = " ".join([f"{k}={v}" for k, v in opts.items()]) + startup_files = self._options.get_opa_startup_files() + + return f"{opa_path} run --server {opts_string} {startup_files}".strip() + @staticmethod def setup_opa_runner( @@ -273,6 +285,19 @@ def setup_opa_runner( to handle authorization queries. therefore it is necessary that we rehydrate the cache with fresh state fetched from the server. """ + + if options is None: + options = OpaServerOptions( + opa_executable_path=opal_client_config.INLINE_OPA_EXECUTABLE_PATH + ) + elif options.opa_executable_path == "opa": + options.opa_executable_path = opal_client_config.INLINE_OPA_EXECUTABLE_PATH + + # Check if the OPA executable exists and is a file + if not os.path.isfile(options.opa_executable_path): + raise FileNotFoundError(f"OPA executable not found at path: {options.opa_executable_path}") + + opa_runner = OpaRunner(options=options, piped_logs_format=piped_logs_format) if initial_start_callbacks: opa_runner.register_process_initial_start_callbacks(initial_start_callbacks)