Skip to content

Commit 33ed89c

Browse files
authored
fix: fix up issues in target separation (#1365)
- Removal of duplication check - Proper loading of targets on server init - Masking of sensitive information (slack URL, alert manager auth credentials) - Error on modifying name - Renaming repeat to notificationConfig and setting least count of interval to 1 minute
1 parent 8bbdd22 commit 33ed89c

File tree

4 files changed

+128
-51
lines changed

4 files changed

+128
-51
lines changed

src/alerts/mod.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use serde_json::Error as SerdeError;
2828
use std::collections::{HashMap, HashSet};
2929
use std::fmt::{self, Display};
3030
use std::thread;
31+
use std::time::Duration;
3132
use tokio::sync::oneshot::{Receiver, Sender};
3233
use tokio::sync::{mpsc, RwLock};
3334
use tokio::task::JoinHandle;
@@ -610,10 +611,12 @@ impl AlertConfig {
610611
// validate that target repeat notifs !> eval_frequency
611612
for target_id in &self.targets {
612613
let target = TARGETS.get_target_by_id(target_id).await?;
613-
match &target.timeout.times {
614+
match &target.notification_config.times {
614615
target::Retry::Infinite => {}
615616
target::Retry::Finite(repeat) => {
616-
let notif_duration = target.timeout.interval * *repeat as u32;
617+
let notif_duration =
618+
Duration::from_secs(60 * target.notification_config.interval)
619+
* *repeat as u32;
617620
if (notif_duration.as_secs_f64()).gt(&((eval_frequency * 60) as f64)) {
618621
return Err(AlertError::Metadata(
619622
"evalFrequency should be greater than target repetition interval",
@@ -853,8 +856,8 @@ pub enum AlertError {
853856
FromStrError(#[from] FromStrError),
854857
#[error("Invalid Target ID- {0}")]
855858
InvalidTargetID(String),
856-
#[error("Target already exists")]
857-
DuplicateTargetConfig,
859+
#[error("Invalid target modification request: {0}")]
860+
InvalidTargetModification(String),
858861
#[error("Can't delete a Target which is being used")]
859862
TargetInUse,
860863
}
@@ -875,7 +878,7 @@ impl actix_web::ResponseError for AlertError {
875878
Self::InvalidAlertModifyRequest => StatusCode::BAD_REQUEST,
876879
Self::FromStrError(_) => StatusCode::BAD_REQUEST,
877880
Self::InvalidTargetID(_) => StatusCode::BAD_REQUEST,
878-
Self::DuplicateTargetConfig => StatusCode::BAD_REQUEST,
881+
Self::InvalidTargetModification(_) => StatusCode::BAD_REQUEST,
879882
Self::TargetInUse => StatusCode::CONFLICT,
880883
}
881884
}

src/alerts/target.rs

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ use base64::Engine;
2727
use bytes::Bytes;
2828
use chrono::Utc;
2929
use http::{header::AUTHORIZATION, HeaderMap, HeaderValue};
30-
use humantime_serde::re::humantime;
3130
use itertools::Itertools;
3231
use once_cell::sync::Lazy;
3332
use reqwest::ClientBuilder;
33+
use serde_json::{json, Value};
3434
use tokio::sync::RwLock;
3535
use tracing::{error, trace, warn};
3636
use ulid::Ulid;
@@ -66,14 +66,6 @@ impl TargetConfigs {
6666

6767
pub async fn update(&self, target: Target) -> Result<(), AlertError> {
6868
let mut map = self.target_configs.write().await;
69-
if map.values().any(|t| {
70-
t.target == target.target
71-
&& t.timeout.interval == target.timeout.interval
72-
&& t.timeout.times == target.timeout.times
73-
&& t.id != target.id
74-
}) {
75-
return Err(AlertError::DuplicateTargetConfig);
76-
}
7769
map.insert(target.id, target.clone());
7870

7971
let path = target_json_path(&target.id);
@@ -148,16 +140,84 @@ pub struct Target {
148140
pub name: String,
149141
#[serde(flatten)]
150142
pub target: TargetType,
151-
#[serde(default, rename = "repeat")]
152-
pub timeout: Timeout,
143+
pub notification_config: Timeout,
153144
#[serde(default = "Ulid::new")]
154145
pub id: Ulid,
155146
}
156147

157148
impl Target {
149+
pub fn mask(self) -> Value {
150+
match self.target {
151+
TargetType::Slack(slack_web_hook) => {
152+
let endpoint = slack_web_hook.endpoint.to_string();
153+
let masked_endpoint = if endpoint.len() > 20 {
154+
format!("{}********", &endpoint[..20])
155+
} else {
156+
"********".to_string()
157+
};
158+
json!({
159+
"name":self.name,
160+
"type":"slack",
161+
"endpoint":masked_endpoint,
162+
"notificationConfig":self.notification_config,
163+
"id":self.id
164+
})
165+
}
166+
TargetType::Other(other_web_hook) => {
167+
let endpoint = other_web_hook.endpoint.to_string();
168+
let masked_endpoint = if endpoint.len() > 20 {
169+
format!("{}********", &endpoint[..20])
170+
} else {
171+
"********".to_string()
172+
};
173+
json!({
174+
"name":self.name,
175+
"type":"webhook",
176+
"endpoint":masked_endpoint,
177+
"headers":other_web_hook.headers,
178+
"skipTlsCheck":other_web_hook.skip_tls_check,
179+
"notificationConfig":self.notification_config,
180+
"id":self.id
181+
})
182+
}
183+
TargetType::AlertManager(alert_manager) => {
184+
let endpoint = alert_manager.endpoint.to_string();
185+
let masked_endpoint = if endpoint.len() > 20 {
186+
format!("{}********", &endpoint[..20])
187+
} else {
188+
"********".to_string()
189+
};
190+
if let Some(auth) = alert_manager.auth {
191+
let password = "********";
192+
json!({
193+
"name":self.name,
194+
"type":"webhook",
195+
"endpoint":masked_endpoint,
196+
"username":auth.username,
197+
"password":password,
198+
"skipTlsCheck":alert_manager.skip_tls_check,
199+
"notificationConfig":self.notification_config,
200+
"id":self.id
201+
})
202+
} else {
203+
json!({
204+
"name":self.name,
205+
"type":"webhook",
206+
"endpoint":masked_endpoint,
207+
"username":Value::Null,
208+
"password":Value::Null,
209+
"skipTlsCheck":alert_manager.skip_tls_check,
210+
"notificationConfig":self.notification_config,
211+
"id":self.id
212+
})
213+
}
214+
}
215+
}
216+
}
217+
158218
pub fn call(&self, context: Context) {
159219
trace!("target.call context- {context:?}");
160-
let timeout = &self.timeout;
220+
let timeout = &self.notification_config;
161221
let resolves = context.alert_info.alert_state;
162222
let mut state = timeout.state.lock().unwrap();
163223
trace!("target.call state- {state:?}");
@@ -205,7 +265,7 @@ impl Target {
205265
let sleep_and_check_if_call =
206266
move |timeout_state: Arc<Mutex<TimeoutState>>, current_state: AlertState| {
207267
async move {
208-
tokio::time::sleep(timeout).await;
268+
tokio::time::sleep(Duration::from_secs(timeout * 60)).await;
209269

210270
let mut state = timeout_state.lock().unwrap();
211271

@@ -276,8 +336,8 @@ fn call_target(target: TargetType, context: Context) {
276336
}
277337

278338
#[derive(Debug, serde::Deserialize)]
279-
pub struct RepeatVerifier {
280-
interval: Option<String>,
339+
pub struct NotificationConfigVerifier {
340+
interval: Option<u64>,
281341
times: Option<usize>,
282342
}
283343

@@ -288,7 +348,7 @@ pub struct TargetVerifier {
288348
#[serde(flatten)]
289349
pub target: TargetType,
290350
#[serde(default)]
291-
pub repeat: Option<RepeatVerifier>,
351+
pub notification_config: Option<NotificationConfigVerifier>,
292352
#[serde(default = "Ulid::new")]
293353
pub id: Ulid,
294354
}
@@ -304,26 +364,22 @@ impl TryFrom<TargetVerifier> for Target {
304364
timeout.times = Retry::Infinite
305365
}
306366

307-
if let Some(repeat_config) = value.repeat {
308-
let interval = repeat_config
309-
.interval
310-
.map(|ref interval| humantime::parse_duration(interval))
311-
.transpose()
312-
.map_err(|err| err.to_string())?;
367+
if let Some(notification_config) = value.notification_config {
368+
let interval = notification_config.interval.map(|ref interval| *interval);
313369

314370
if let Some(interval) = interval {
315371
timeout.interval = interval
316372
}
317373

318-
if let Some(times) = repeat_config.times {
374+
if let Some(times) = notification_config.times {
319375
timeout.times = Retry::Finite(times)
320376
}
321377
}
322378

323379
Ok(Target {
324380
name: value.name,
325381
target: value.target,
326-
timeout,
382+
notification_config: timeout,
327383
id: value.id,
328384
})
329385
}
@@ -518,8 +574,7 @@ impl CallableTarget for AlertManager {
518574

519575
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
520576
pub struct Timeout {
521-
#[serde(with = "humantime_serde")]
522-
pub interval: Duration,
577+
pub interval: u64,
523578
#[serde(default = "Retry::default")]
524579
pub times: Retry,
525580
#[serde(skip)]
@@ -529,7 +584,7 @@ pub struct Timeout {
529584
impl Default for Timeout {
530585
fn default() -> Self {
531586
Self {
532-
interval: Duration::from_secs(60),
587+
interval: 1,
533588
times: Retry::default(),
534589
state: Arc::<Mutex<TimeoutState>>::default(),
535590
}

src/handlers/http/modal/mod.rs

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use tokio::sync::oneshot;
3434
use tracing::{error, info, warn};
3535

3636
use crate::{
37-
alerts::ALERTS,
37+
alerts::{target::TARGETS, ALERTS},
3838
cli::Options,
3939
correlation::CORRELATIONS,
4040
oidc::Claims,
@@ -173,18 +173,20 @@ pub trait ParseableServer {
173173

174174
pub async fn load_on_init() -> anyhow::Result<()> {
175175
// Run all loading operations concurrently
176-
let (correlations_result, filters_result, dashboards_result, alerts_result) = future::join4(
177-
async {
178-
CORRELATIONS
179-
.load()
180-
.await
181-
.context("Failed to load correlations")
182-
},
183-
async { FILTERS.load().await.context("Failed to load filters") },
184-
async { DASHBOARDS.load().await.context("Failed to load dashboards") },
185-
async { ALERTS.load().await.context("Failed to load alerts") },
186-
)
187-
.await;
176+
let (correlations_result, filters_result, dashboards_result, alerts_result, targets_result) =
177+
future::join5(
178+
async {
179+
CORRELATIONS
180+
.load()
181+
.await
182+
.context("Failed to load correlations")
183+
},
184+
async { FILTERS.load().await.context("Failed to load filters") },
185+
async { DASHBOARDS.load().await.context("Failed to load dashboards") },
186+
async { ALERTS.load().await.context("Failed to load alerts") },
187+
async { TARGETS.load().await.context("Failed to load targets") },
188+
)
189+
.await;
188190

189191
// Handle errors from each operation
190192
if let Err(e) = correlations_result {
@@ -203,6 +205,10 @@ pub async fn load_on_init() -> anyhow::Result<()> {
203205
error!("{err}");
204206
}
205207

208+
if let Err(err) = targets_result {
209+
error!("{err}");
210+
}
211+
206212
Ok(())
207213
}
208214

src/handlers/http/targets.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use actix_web::{
22
web::{self, Json, Path},
33
HttpRequest, Responder,
44
};
5+
use itertools::Itertools;
56
use ulid::Ulid;
67

78
use crate::alerts::{
@@ -18,13 +19,18 @@ pub async fn post(
1819
// add to the map
1920
TARGETS.update(target.clone()).await?;
2021

21-
Ok(web::Json(target))
22+
Ok(web::Json(target.mask()))
2223
}
2324

2425
// GET /targets
2526
pub async fn list(_req: HttpRequest) -> Result<impl Responder, AlertError> {
2627
// add to the map
27-
let list = TARGETS.list().await?;
28+
let list = TARGETS
29+
.list()
30+
.await?
31+
.into_iter()
32+
.map(|t| t.mask())
33+
.collect_vec();
2834

2935
Ok(web::Json(list))
3036
}
@@ -35,7 +41,7 @@ pub async fn get(_req: HttpRequest, target_id: Path<Ulid>) -> Result<impl Respon
3541

3642
let target = TARGETS.get_target_by_id(&target_id).await?;
3743

38-
Ok(web::Json(target))
44+
Ok(web::Json(target.mask()))
3945
}
4046

4147
// PUT /targets/{target_id}
@@ -47,7 +53,14 @@ pub async fn update(
4753
let target_id = target_id.into_inner();
4854

4955
// if target_id does not exist, error
50-
TARGETS.get_target_by_id(&target_id).await?;
56+
let old_target = TARGETS.get_target_by_id(&target_id).await?;
57+
58+
// do not allow modifying name
59+
if old_target.name != target.name {
60+
return Err(AlertError::InvalidTargetModification(
61+
"Can't modify target name".to_string(),
62+
));
63+
}
5164

5265
// esnure that the supplied target id is assigned to the target config
5366
target.id = target_id;
@@ -56,7 +69,7 @@ pub async fn update(
5669
// add to the map
5770
TARGETS.update(target.clone()).await?;
5871

59-
Ok(web::Json(target))
72+
Ok(web::Json(target.mask()))
6073
}
6174

6275
// DELETE /targets/{target_id}
@@ -68,5 +81,5 @@ pub async fn delete(
6881

6982
let target = TARGETS.delete(&target_id).await?;
7083

71-
Ok(web::Json(target))
84+
Ok(web::Json(target.mask()))
7285
}

0 commit comments

Comments
 (0)