Skip to content

Commit c711d0f

Browse files
committed
add query to get recent URLs hit by a given IP address
1 parent 486a0d5 commit c711d0f

File tree

3 files changed

+102
-8
lines changed

3 files changed

+102
-8
lines changed

src/db/datastore_wrapper.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::db::installation_code::InstallationCode;
55
use crate::db::tables::DbTable;
66
use crate::firewall::firewall::Firewall;
77
use crate::proto::appguard::{AppGuardIpInfo, Log};
8+
use chrono::Utc;
89
use ipnetwork::IpNetwork;
910
use nullnet_libdatastore::{
1011
AdvanceFilter, BatchCreateBody, BatchCreateRequest, BatchDeleteBody, BatchDeleteRequest,
@@ -19,7 +20,9 @@ use nullnet_liberror::{location, Error, ErrorHandler, Location};
1920
use serde_json::{json, Value};
2021
use std::collections::HashMap;
2122
use std::net::IpAddr;
23+
use std::ops::Sub;
2224
use std::str::FromStr;
25+
use std::time::Duration;
2326

2427
#[derive(Debug, Clone)]
2528
pub struct DatastoreWrapper {
@@ -1231,6 +1234,91 @@ impl DatastoreWrapper {
12311234

12321235
Ok(id)
12331236
}
1237+
1238+
pub async fn get_recent_urls_for_ip(
1239+
&self,
1240+
token: &str,
1241+
ip: IpAddr,
1242+
period: usize,
1243+
) -> Result<Vec<String>, Error> {
1244+
let table = DbTable::HttpRequest.to_str();
1245+
let filter_1 = AdvanceFilter {
1246+
r#type: String::from("criteria"),
1247+
field: String::from("ip"),
1248+
operator: String::from("equal"),
1249+
entity: String::from(table),
1250+
values: format!("[\"{ip}\"]"),
1251+
};
1252+
1253+
let filter_2 = AdvanceFilter {
1254+
r#type: String::from("operator"),
1255+
field: String::new(),
1256+
operator: String::from("and"),
1257+
entity: String::new(),
1258+
values: String::new(),
1259+
};
1260+
1261+
let timestamp = Utc::now()
1262+
.sub(Duration::from_secs(
1263+
u64::try_from(period).handle_err(location!())?,
1264+
))
1265+
.to_rfc3339();
1266+
let filter_3 = AdvanceFilter {
1267+
r#type: String::from("criteria"),
1268+
field: String::from("timestamp"),
1269+
operator: String::from("greater_than_or_equal"),
1270+
entity: String::from(table),
1271+
values: format!("[\"{timestamp}\"]"),
1272+
};
1273+
1274+
let request = GetByFilterRequest {
1275+
body: Some(GetByFilterBody {
1276+
pluck: vec!["id".into(), "original_url".into()],
1277+
advance_filters: vec![filter_1, filter_2, filter_3],
1278+
order_by: String::new(),
1279+
limit: i32::MAX,
1280+
offset: 0,
1281+
order_direction: String::new(),
1282+
joins: vec![],
1283+
multiple_sort: vec![],
1284+
pluck_object: HashMap::new(),
1285+
date_format: String::new(),
1286+
is_case_sensitive_sorting: true,
1287+
}),
1288+
params: Some(Params {
1289+
id: String::new(),
1290+
table: table.to_string(),
1291+
r#type: String::from("root"),
1292+
}),
1293+
};
1294+
1295+
let result = self.inner.clone().get_by_filter(request, token).await?.data;
1296+
1297+
Self::internal_recent_urls_for_ip_parse_response_data(result)
1298+
}
1299+
1300+
fn internal_recent_urls_for_ip_parse_response_data(data: String) -> Result<Vec<String>, Error> {
1301+
let array_val = serde_json::from_str::<serde_json::Value>(&data).handle_err(location!())?;
1302+
let array = array_val
1303+
.as_array()
1304+
.ok_or("Failed to parse response")
1305+
.handle_err(location!())?;
1306+
1307+
let mut ret_val = Vec::new();
1308+
1309+
for i in array {
1310+
let Some(map) = i.as_object() else { continue };
1311+
let Some(original_url_val) = map.get("original_url") else {
1312+
continue;
1313+
};
1314+
let Some(original_url) = original_url_val.as_str() else {
1315+
continue;
1316+
};
1317+
ret_val.push(original_url.to_string());
1318+
}
1319+
1320+
Ok(ret_val)
1321+
}
12341322
}
12351323

12361324
#[cfg(test)]

src/firewall/items/http_request.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ impl HttpRequestField {
4040
// }
4141
// }
4242

43-
fn get_compare_fields<'a>(
43+
async fn get_compare_fields<'a>(
4444
&'a self,
4545
item: &'a AppGuardHttpRequest,
4646
context: &AppContext,
@@ -72,8 +72,11 @@ impl HttpRequestField {
7272
.map(|user_agent| FirewallCompareType::String((user_agent, v))),
7373
HttpRequestField::HttpRequestRateLimit(rate_limit) => {
7474
// TODO: only if matches this item's URL??
75-
let db_urls = rate_limit.get_urls(context, item.get_remote_ip());
76-
Some(FirewallCompareType::RateLimit((db_urls, rate_limit)))
75+
rate_limit
76+
.get_urls(context, item.get_remote_ip())
77+
.await
78+
.ok()
79+
.map(|db_urls| FirewallCompareType::RateLimit((db_urls, rate_limit)))
7780
}
7881
}
7982
}
@@ -97,7 +100,7 @@ impl PredicateEvaluator for AppGuardHttpRequest {
97100
if let FirewallRuleField::HttpRequest(f) = &predicate.field {
98101
predicate
99102
.condition
100-
.compare(f.get_compare_fields(self, context))
103+
.compare(f.get_compare_fields(self, context).await)
101104
} else {
102105
self.tcp_info
103106
.as_ref()

src/firewall/rate_limit.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::app_context::AppContext;
2+
use nullnet_liberror::Error;
23
use serde::{Deserialize, Serialize};
34
use std::net::IpAddr;
45

@@ -10,9 +11,11 @@ pub struct RateLimit {
1011
}
1112

1213
impl RateLimit {
13-
// TODO!
14-
pub fn get_urls(&self, ctx: &AppContext, ip: IpAddr) -> Vec<String> {
15-
// get from datastore all the urls queried by this ip in the last period seconds
16-
vec![]
14+
pub async fn get_urls(&self, ctx: &AppContext, ip: IpAddr) -> Result<Vec<String>, Error> {
15+
let token = ctx.root_token_provider.get().await?;
16+
let jwt = &token.jwt;
17+
ctx.datastore
18+
.get_recent_urls_for_ip(jwt, ip, self.period)
19+
.await
1720
}
1821
}

0 commit comments

Comments
 (0)