Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,11 @@ tokio = { version = "^1.52.3", default-features = false, features = [
"macros",
"fs",
"rt-multi-thread",
"net"
] }
tokio-stream = { version = "0.1.18", features = ["fs"] }
tokio-util = { version = "0.7.18" }
ipnet = "2.12.0"

# perf
hotpath = { version = "0.16.0", optional = true, features = [
Expand Down
2 changes: 1 addition & 1 deletion src/alerts/alert_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,5 @@ pub trait AlertManagerTrait: Send + Sync {

#[async_trait]
pub trait CallableTarget {
async fn call(&self, payload: &Context);
async fn call(&self, tenant_id: &Option<String>, payload: &Context);
}
55 changes: 14 additions & 41 deletions src/alerts/alerts_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,10 +491,7 @@ fn split_array_elements(value: &str) -> Result<Vec<&str>, String> {
.char_indices()
.nth(start)
.map_or(value.len(), |(b, _)| b);
let byte_end = value
.char_indices()
.nth(i)
.map_or(value.len(), |(b, _)| b);
let byte_end = value.char_indices().nth(i).map_or(value.len(), |(b, _)| b);
elements.push(&value[byte_start..byte_end]);
start = i + 1;
}
Expand Down Expand Up @@ -715,10 +712,7 @@ mod tests {
#[test]
fn test_sanitize_numeric_elements() {
assert_eq!(sanitize_array_elements("1, 2, 3").unwrap(), "1, 2, 3");
assert_eq!(
sanitize_array_elements("3.14, 2.71").unwrap(),
"3.14, 2.71"
);
assert_eq!(sanitize_array_elements("3.14, 2.71").unwrap(), "3.14, 2.71");
}

#[test]
Expand All @@ -732,10 +726,7 @@ mod tests {
#[test]
fn test_sanitize_bare_string_elements() {
assert_eq!(sanitize_array_elements("foo").unwrap(), "'foo'");
assert_eq!(
sanitize_array_elements("foo, bar").unwrap(),
"'foo', 'bar'"
);
assert_eq!(sanitize_array_elements("foo, bar").unwrap(), "'foo', 'bar'");
}

#[test]
Expand Down Expand Up @@ -775,8 +766,7 @@ mod tests {

#[test]
fn test_sanitize_multiple_quoted_with_commas() {
let result =
sanitize_array_elements("'hello, world', 'foo, bar'").unwrap();
let result = sanitize_array_elements("'hello, world', 'foo, bar'").unwrap();
assert_eq!(result, "'hello, world', 'foo, bar'");
}

Expand Down Expand Up @@ -852,50 +842,34 @@ mod tests {

#[test]
fn test_list_condition_expr_strips_outer_brackets() {
let result =
list_condition_expr("tags", &WhereConfigOperator::Equal, "[1, 2]").unwrap();
let result = list_condition_expr("tags", &WhereConfigOperator::Equal, "[1, 2]").unwrap();
assert_eq!(result, "\"tags\" = ARRAY[1, 2]");
}

#[test]
fn test_list_condition_expr_does_not_contain() {
let result = list_condition_expr(
"tags",
&WhereConfigOperator::DoesNotContain,
"foo",
)
.unwrap();
let result =
list_condition_expr("tags", &WhereConfigOperator::DoesNotContain, "foo").unwrap();
assert_eq!(result, "NOT array_has_all(\"tags\", ARRAY['foo'])");
}

#[test]
fn test_list_condition_expr_not_equal() {
let result =
list_condition_expr("tags", &WhereConfigOperator::NotEqual, "42").unwrap();
let result = list_condition_expr("tags", &WhereConfigOperator::NotEqual, "42").unwrap();
assert_eq!(result, "\"tags\" != ARRAY[42]");
}

#[test]
fn test_list_condition_expr_quoted_element_with_comma() {
let result = list_condition_expr(
"cities",
&WhereConfigOperator::Contains,
"'New York, NY'",
)
.unwrap();
assert_eq!(
result,
"array_has_all(\"cities\", ARRAY['New York, NY'])"
);
let result =
list_condition_expr("cities", &WhereConfigOperator::Contains, "'New York, NY'")
.unwrap();
assert_eq!(result, "array_has_all(\"cities\", ARRAY['New York, NY'])");
}

#[test]
fn test_list_condition_expr_rejects_injection() {
let result = list_condition_expr(
"tags",
&WhereConfigOperator::Contains,
"1] OR 1=1 --",
);
let result = list_condition_expr("tags", &WhereConfigOperator::Contains, "1] OR 1=1 --");
assert!(
result.is_err(),
"Injection via bracket characters must be rejected"
Expand All @@ -904,8 +878,7 @@ mod tests {

#[test]
fn test_list_condition_expr_unsupported_operator() {
let result =
list_condition_expr("tags", &WhereConfigOperator::GreaterThan, "1");
let result = list_condition_expr("tags", &WhereConfigOperator::GreaterThan, "1");
assert!(result.is_err());
assert!(result.unwrap_err().contains("not supported"));
}
Expand Down
24 changes: 20 additions & 4 deletions src/alerts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub mod alert_structs;
pub mod alert_traits;
pub mod alert_types;
pub mod alerts_utils;
pub mod outbound_http_policy;
pub mod target;

pub use crate::alerts::alert_enums::{
Expand Down Expand Up @@ -619,7 +620,10 @@ impl AlertConfig {

for target_id in &self.targets {
let target = TARGETS.get_target_by_id(target_id, &self.tenant_id).await?;
trace!("Target (trigger_notifications)-\n{target:?}");
if let Err(e) = target.validate_outbound_policy().await {
tracing::error!("Target {} failed alert policy check- {e}", target.id);
continue;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
target.call(context.clone());
}

Expand Down Expand Up @@ -976,6 +980,8 @@ pub enum AlertError {
Metadata(&'static str),
#[error("User is not authorized to run this query")]
Unauthorized,
#[error("Alert target outbound policy rejected request:{0}")]
OutboundPolicy(#[from] outbound_http_policy::OutboundPolicyError),
#[error("ActixError: {0}")]
Error(#[from] actix_web::Error),
#[error("DataFusion Error: {0}")]
Expand Down Expand Up @@ -1042,13 +1048,23 @@ impl actix_web::ResponseError for AlertError {
Self::Unimplemented(_) => StatusCode::INTERNAL_SERVER_ERROR,
Self::NotPresentInOSS(_) => StatusCode::BAD_REQUEST,
Self::MetastoreError(_) => StatusCode::INTERNAL_SERVER_ERROR,
Self::OutboundPolicy(_) => StatusCode::BAD_REQUEST,
}
}

fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
actix_web::HttpResponse::build(self.status_code())
.insert_header(ContentType::plaintext())
.body(self.to_string())
match self {
Self::OutboundPolicy(_) => actix_web::HttpResponse::build(self.status_code())
.insert_header(ContentType::json())
.json(serde_json::json!({
"error": "Alert target blocked by outbound security policy",
"message": self.to_string(),
"hint": "Ask admin to allow this destination using the alert target policy."
})),
_ => actix_web::HttpResponse::build(self.status_code())
.insert_header(ContentType::plaintext())
.body(self.to_string()),
}
}
}

Expand Down
Loading
Loading