diff --git a/Dockerfile b/Dockerfile index 43a84de0..c88b9c27 100644 --- a/Dockerfile +++ b/Dockerfile @@ -167,6 +167,7 @@ ENV PDP_HORIZON_HOST=0.0.0.0 ENV PDP_HORIZON_PORT=7001 ENV PDP_PORT=7000 ENV PDP_PYTHON_PATH=python3 +ENV NO_PROXY=localhost,127.0.0.1,::1 # 7000 pdp port # 7001 horizon port diff --git a/horizon/enforcer/api.py b/horizon/enforcer/api.py index dd601524..53a0e8f6 100644 --- a/horizon/enforcer/api.py +++ b/horizon/enforcer/api.py @@ -206,7 +206,7 @@ async def post_to_opa(request: Request, path: str, data: dict | None): _set_use_debugger(data) try: logger.debug(f"calling OPA at '{url}' with input: {data}") - async with aiohttp.ClientSession() as session: # noqa: SIM117 + async with aiohttp.ClientSession(trust_env=True) as session: # noqa: SIM117 async with session.post( url, data=json.dumps(data) if data is not None else None, diff --git a/horizon/facts/client.py b/horizon/facts/client.py index 743a9d66..1a256d0f 100644 --- a/horizon/facts/client.py +++ b/horizon/facts/client.py @@ -27,6 +27,7 @@ def client(self) -> AsyncClient: self._client = AsyncClient( base_url=sidecar_config.CONTROL_PLANE, headers={"Authorization": f"Bearer {env_api_key}"}, + trust_env=True, ) return self._client diff --git a/horizon/opal_relay_api.py b/horizon/opal_relay_api.py index e1375f27..df6c5975 100644 --- a/horizon/opal_relay_api.py +++ b/horizon/opal_relay_api.py @@ -95,7 +95,7 @@ def _apply_context(self, context: dict[str, str]): def api_session(self) -> ClientSession: if self._api_session is None: env_api_key = get_env_api_key() - self._api_session = ClientSession(headers={"Authorization": f"Bearer {env_api_key}"}) + self._api_session = ClientSession(headers={"Authorization": f"Bearer {env_api_key}"}, trust_env=True) return self._api_session async def relay_session(self) -> ClientSession: @@ -133,7 +133,9 @@ async def relay_session(self) -> ClientSession: f"Server responded to token request with an invalid result: {text}", ) from e self._relay_token = obj.token - self._relay_session = ClientSession(headers={"Authorization": f"Bearer {self._relay_token}"}) + self._relay_session = ClientSession( + headers={"Authorization": f"Bearer {self._relay_token}"}, trust_env=True + ) return self._relay_session async def send_ping(self): diff --git a/horizon/proxy/api.py b/horizon/proxy/api.py index 37729bba..cdfdc3f2 100644 --- a/horizon/proxy/api.py +++ b/horizon/proxy/api.py @@ -200,7 +200,9 @@ async def proxy_request_to_cloud_service( logger.info(f"Proxying request: {request.method} {path}") - async with aiohttp.ClientSession() as session: + async with aiohttp.ClientSession( + trust_env=True, + ) as session: if request.method == HTTP_GET: async with session.get(path, headers=headers, params=params) as backend_response: return await proxy_response(backend_response) diff --git a/horizon/state.py b/horizon/state.py index 9553aefa..eff90d90 100644 --- a/horizon/state.py +++ b/horizon/state.py @@ -198,7 +198,9 @@ async def _report(self, state: PersistentState | None = None): if state is not None: self._state = state.copy() config_url = f"{sidecar_config.CONTROL_PLANE}{sidecar_config.REMOTE_STATE_ENDPOINT}" - async with aiohttp.ClientSession() as session: + async with aiohttp.ClientSession( + trust_env=True, + ) as session: logger.info("Reporting status update to server...") response = await session.post( url=config_url, diff --git a/pdp-server/src/opa_client/allowed.rs b/pdp-server/src/opa_client/allowed.rs index 7a6e973d..945f7dc7 100644 --- a/pdp-server/src/opa_client/allowed.rs +++ b/pdp-server/src/opa_client/allowed.rs @@ -1,7 +1,9 @@ use crate::opa_client::{send_request_to_opa, ForwardingError}; use crate::state::AppState; +use log::{debug, info}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use std::fmt::Display; use utoipa::ToSchema; /// Send an allowed query to OPA and get the result @@ -9,7 +11,25 @@ pub async fn query_allowed( state: &AppState, query: &AllowedQuery, ) -> Result { - send_request_to_opa::(state, "/v1/data/permit/root", query).await + let result = + send_request_to_opa::(state, "/v1/data/permit/root", query).await; + if let Ok(response) = &result { + if state.config.debug.unwrap_or(false) { + info!( + "permit.check(\"{user}\", \"{action}\", \"{resource}\") -> {result}", + user = query.user, + action = query.action, + resource = query.resource, + result = response.allow, + ); + debug!( + "Query: {}\nResult: {}", + serde_json::to_string_pretty(query).unwrap_or("Serialization error".to_string()), + serde_json::to_string_pretty(response).unwrap_or("Serialization error".to_string()), + ); + } + } + result } #[derive(Debug, Serialize, Deserialize, ToSchema, Clone, PartialEq)] @@ -30,6 +50,12 @@ pub struct User { pub attributes: HashMap, } +impl Display for User { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.key) + } +} + #[derive(Debug, Serialize, Deserialize, ToSchema, Clone, PartialEq)] pub struct Resource { /// Type of the resource @@ -48,6 +74,18 @@ pub struct Resource { pub context: HashMap, } +impl Display for Resource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(key) = &self.key { + write!(f, "{}:{}", self.r#type, key) + } else if let Some(tenant) = &self.tenant { + write!(f, "{}@{}", self.r#type, tenant) + } else { + write!(f, "{}", self.r#type) + } + } +} + /// Authorization query parameters for the allowed endpoint #[derive(Debug, Serialize, Deserialize, ToSchema, Clone, PartialEq)] pub struct AllowedQuery { diff --git a/pdp-server/src/opa_client/allowed_bulk.rs b/pdp-server/src/opa_client/allowed_bulk.rs index 9fa72572..c59a2eb7 100644 --- a/pdp-server/src/opa_client/allowed_bulk.rs +++ b/pdp-server/src/opa_client/allowed_bulk.rs @@ -27,6 +27,31 @@ pub async fn query_allowed_bulk( let bulk_query = BulkAuthorizationQuery { checks: queries.to_vec(), }; - send_request_to_opa::(state, "/v1/data/permit/bulk", &bulk_query) - .await + let result = send_request_to_opa::( + state, + "/v1/data/permit/bulk", + &bulk_query, + ) + .await; + + // Add debug logging if enabled + if let Ok(response) = &result { + if state.config.debug.unwrap_or(false) { + let allowed_count = response.allow.iter().filter(|r| r.allow).count(); + log::info!( + "permit.bulk_check({} queries) -> {} allowed, {} denied", + queries.len(), + allowed_count, + response.allow.len() - allowed_count + ); + log::debug!( + "Query: {}\nResult: {}", + serde_json::to_string_pretty(&bulk_query) + .unwrap_or("Serialization error".to_string()), + serde_json::to_string_pretty(response).unwrap_or("Serialization error".to_string()), + ); + } + } + + result } diff --git a/pdp-server/src/opa_client/authorized_users.rs b/pdp-server/src/opa_client/authorized_users.rs index 54332460..d2fa661f 100644 --- a/pdp-server/src/opa_client/authorized_users.rs +++ b/pdp-server/src/opa_client/authorized_users.rs @@ -24,7 +24,27 @@ pub async fn query_authorized_users( // Process the result to extract the nested 'result' field if it exists if let serde_json::Value::Object(map) = &result { if let Some(inner_result) = map.get("result") { - return Ok(serde_json::from_value(inner_result.clone())?); + let authorized_result: AuthorizedUsersResult = + serde_json::from_value(inner_result.clone())?; + + // Add debug logging if enabled + if state.config.debug.unwrap_or(false) { + log::info!( + "permit.authorized_users(\"{}\", \"{}\") -> {} users", + query.action, + query.resource, + authorized_result.users.len() + ); + log::debug!( + "Query: {}\nResult: {}", + serde_json::to_string_pretty(query) + .unwrap_or("Serialization error".to_string()), + serde_json::to_string_pretty(&authorized_result) + .unwrap_or("Serialization error".to_string()), + ); + } + + return Ok(authorized_result); } } @@ -40,11 +60,26 @@ pub async fn query_authorized_users( .clone() .unwrap_or_else(|| "default".to_string()); - Ok(AuthorizedUsersResult { + let empty_result = AuthorizedUsersResult { resource: format!("{}:{}", query.resource.r#type, resource_key), tenant, users: HashMap::new(), - }) + }; + + // Add debug logging if enabled + if state.config.debug.unwrap_or(false) { + log::info!( + "permit.authorized_users(\"{}\", \"{}\") -> 0 users", + query.action, + query.resource + ); + log::debug!( + "Query: {}\nResult: empty", + serde_json::to_string_pretty(query)? + ); + } + + Ok(empty_result) } /// Query parameters for the authorized users endpoint diff --git a/pdp-server/src/opa_client/user_permissions.rs b/pdp-server/src/opa_client/user_permissions.rs index 6ddb0f01..af2e4cc1 100644 --- a/pdp-server/src/opa_client/user_permissions.rs +++ b/pdp-server/src/opa_client/user_permissions.rs @@ -22,7 +22,29 @@ pub async fn query_user_permissions( if let serde_json::Value::Object(result_map) = response { if let Some(permissions) = result_map.get("permissions") { - return Ok(serde_json::from_value(permissions.clone())?); + let result: HashMap = + serde_json::from_value(permissions.clone())?; + + // Add debug logging if enabled + if state.config.debug.unwrap_or(false) { + log::info!( + "permit.user_permissions(\"{}\", tenants={:?}, resources={:?}, resource_types={:?}) -> {} permissions", + query.user, + query.tenants, + query.resources, + query.resource_types, + result.len() + ); + log::debug!( + "Query: {}\nResult: {}", + serde_json::to_string_pretty(query) + .unwrap_or("Serialization error".to_string()), + serde_json::to_string_pretty(&result) + .unwrap_or("Serialization error".to_string()), + ); + } + + return Ok(result); } else { debug!("No 'permissions' field found in result"); } @@ -31,7 +53,24 @@ pub async fn query_user_permissions( } // If we couldn't find the permissions, return an empty map - Ok(HashMap::new()) + let empty_result = HashMap::new(); + + // Add debug logging if enabled + if state.config.debug.unwrap_or(false) { + log::info!( + "permit.user_permissions(\"{}\", tenants={:?}, resources={:?}, resource_types={:?}) -> 0 permissions", + query.user, + query.tenants, + query.resources, + query.resource_types + ); + log::debug!( + "Query: {}\nResult: empty", + serde_json::to_string_pretty(query)? + ); + } + + Ok(empty_result) } #[derive(Debug, Serialize, Deserialize, ToSchema, Clone, PartialEq)] diff --git a/requirements.txt b/requirements.txt index 6d47d20d..5f6059e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,5 +14,5 @@ httpx>=0.27.0,<1 # TODO: change to use re2 in the future, currently not supported in alpine due to c++ library issues # google-re2 # use re2 instead of re for regex matching because it's simiplier and safer for user inputted regexes protobuf>=3.20.2 # not directly required, pinned by Snyk to avoid a vulnerability -opal-common==0.8.1 -opal-client==0.8.1 +opal-common==0.8.2 +opal-client==0.8.2 diff --git a/watchdog/src/service.rs b/watchdog/src/service.rs index 0bd073fb..00b43694 100644 --- a/watchdog/src/service.rs +++ b/watchdog/src/service.rs @@ -1,7 +1,7 @@ use crate::health::HealthCheck; use crate::stats::ServiceWatchdogStats; use crate::{CommandWatchdog, CommandWatchdogOptions}; -use log::{debug, error, info, warn}; +use log::{debug, error, info, log, warn}; use std::sync::Arc; use std::time::Duration; use tokio::process::Command; @@ -130,7 +130,12 @@ impl ServiceWatchdog { } if consecutive_failures > 0 { - info!( + log!( + if consecutive_failures < opt.health_check_failure_threshold / 2 { + log::Level::Debug + } else { + log::Level::Info + }, "Service '{}' health restored after {} failures", command_watchdog.program_name, consecutive_failures ); @@ -146,7 +151,12 @@ impl ServiceWatchdog { stats.increment_failed_health_checks(); consecutive_failures += 1; - warn!( + log!( + if consecutive_failures < opt.health_check_failure_threshold / 2 { + log::Level::Debug + } else { + log::Level::Warn + }, "Service '{}' health check failed: {} (consecutive failures: {}/{})", command_watchdog.program_name, e,