Skip to content

Commit 6fe732b

Browse files
authored
Auth fix mix req (ideal-world#381)
* add anti_replay test * auth fix mix req * update * update * update * update * update * update * update
1 parent b780c68 commit 6fe732b

File tree

12 files changed

+370
-91
lines changed

12 files changed

+370
-91
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ node_modules
5555

5656
#macos
5757
.DS_Store
58+
#gateway dev
5859
gateway/example/log
60+
gateway/spacegate/devsh/save-o.sh
5961

6062
# pre-commit
6163
.pre-commit-config.yaml

clippy.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
too-many-arguments-threshold = 12
22
# fixed: variant name ends with the enum's name
33
# like basic/src/rbum/domain/rbum_kind_attr.rs #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
4-
enum-variant-name-threshold = 6
4+
enum-variant-name-threshold = 6
5+
allow-unwrap-in-tests = true

gateway/spacegate/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ spacegate-kernel = { git = "https://github.com/ideal-world/spacegate.git", featu
3535

3636
jsonpath-rust = "0.3.1"
3737
bios-auth = { path = "../../support/auth" }
38-
tardis = { workspace = true, features = ["web-server", "web-client"] }
38+
tardis = { workspace = true, features = ["web-client"] }
3939
ipnet = "2.8.0"
4040
[dev-dependencies]
4141
tardis = { workspace = true, features = ["test", "web-client"] }

gateway/spacegate/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ mod plugin;
88
async fn main() -> TardisResult<()> {
99
TardisFuns::init_log()?;
1010
let namespaces = std::env::args().nth(1).map(Some).unwrap_or(None);
11-
spacegate_kernel::register_filter_def(auth::CODE, Box::new(auth::SgFilterAuthDef));
11+
spacegate_kernel::register_filter_def(audit_log::CODE, Box::new(audit_log::SgFilterAuditLogDef));
1212
spacegate_kernel::register_filter_def(ip_time::CODE, Box::new(ip_time::SgFilterIpTimeDef));
1313
spacegate_kernel::register_filter_def(anti_replay::CODE, Box::new(anti_replay::SgFilterAntiReplayDef));
14-
spacegate_kernel::register_filter_def(audit_log::CODE, Box::new(audit_log::SgFilterAuditLogDef));
1514
spacegate_kernel::register_filter_def(anti_xss::CODE, Box::new(anti_xss::SgFilterAntiXSSDef));
15+
spacegate_kernel::register_filter_def(auth::CODE, Box::new(auth::SgFilterAuthDef));
1616
spacegate_kernel::startup(true, namespaces, None).await?;
1717
spacegate_kernel::wait_graceful_shutdown().await
1818
}

gateway/spacegate/src/plugin.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ pub mod anti_xss;
33
pub mod audit_log;
44
pub mod auth;
55
pub mod ip_time;
6+
mod plugin_constants {
7+
pub const BEFORE_ENCRYPT_BODY: &str = "beforeEncryptBody";
8+
}

gateway/spacegate/src/plugin/audit_log.rs

Lines changed: 188 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use spacegate_kernel::plugins::{
1616
filters::{BoxSgPluginFilter, SgPluginFilter, SgPluginFilterAccept, SgPluginFilterDef, SgPluginFilterInitDto},
1717
};
1818
use tardis::basic::dto::TardisContext;
19+
use tardis::basic::error::TardisError;
1920
use tardis::serde_json::{json, Value};
2021

2122
use tardis::{
@@ -27,6 +28,8 @@ use tardis::{
2728
TardisFuns, TardisFunsInst,
2829
};
2930

31+
use super::plugin_constants;
32+
3033
pub const CODE: &str = "audit_log";
3134
pub struct SgFilterAuditLogDef;
3235

@@ -46,9 +49,88 @@ pub struct SgFilterAuditLog {
4649
header_token_name: String,
4750
success_json_path: String,
4851
success_json_path_values: Vec<String>,
52+
/// Exclude log path exact match.
53+
exclude_log_path: Vec<String>,
4954
enabled: bool,
5055
}
5156

57+
impl SgFilterAuditLog {
58+
async fn get_log_content(&self, end_time: i64, ctx: &mut SgRoutePluginContext) -> TardisResult<LogParamContent> {
59+
let start_time = ctx.get_ext(&get_start_time_ext_code()).and_then(|time| time.parse::<i64>().ok());
60+
let body_string = if let Some(raw_body) = ctx.get_ext(plugin_constants::BEFORE_ENCRYPT_BODY) {
61+
Some(raw_body)
62+
} else {
63+
ctx.response
64+
.pop_resp_body()
65+
.await?
66+
.map(|body| {
67+
let body_string = String::from_utf8_lossy(&body).to_string();
68+
ctx.response.set_resp_body(body)?;
69+
Ok::<_, TardisError>(body_string)
70+
})
71+
.transpose()?
72+
};
73+
let success = match serde_json::from_str::<Value>(&body_string.unwrap_or_default()) {
74+
Ok(json) => {
75+
if let Ok(matching_value) = json.path(&self.success_json_path) {
76+
if let Some(matching_value) = matching_value.as_array() {
77+
let matching_value = &matching_value[0];
78+
if matching_value.is_string() {
79+
let mut is_match = false;
80+
for value in self.success_json_path_values.clone() {
81+
if Some(value.as_str()) == matching_value.as_str() {
82+
is_match = true;
83+
break;
84+
}
85+
}
86+
is_match
87+
} else if matching_value.is_number() {
88+
let mut is_match = false;
89+
for value in self.success_json_path_values.clone() {
90+
let value = value.parse::<i64>();
91+
if value.is_ok() && value.ok() == matching_value.as_i64() {
92+
is_match = true;
93+
break;
94+
}
95+
}
96+
is_match
97+
} else {
98+
false
99+
}
100+
} else {
101+
false
102+
}
103+
} else {
104+
false
105+
}
106+
}
107+
Err(_) => false,
108+
};
109+
Ok(LogParamContent {
110+
op: ctx.request.get_req_method().to_string(),
111+
key: None,
112+
name: ctx.get_cert_info().and_then(|info| info.account_name.clone()).unwrap_or_default(),
113+
user_id: ctx.get_cert_info().map(|info| info.account_id.clone()),
114+
role: ctx.get_cert_info().map(|info| info.roles.clone()).unwrap_or_default(),
115+
ip: if let Some(real_ips) = ctx.request.get_req_headers().get("X-Forwarded-For") {
116+
real_ips
117+
.to_str()
118+
.ok()
119+
.and_then(|ips| ips.split(',').collect::<Vec<_>>().first().map(|ip| ip.to_string()))
120+
.unwrap_or(ctx.request.get_req_remote_addr().ip().to_string())
121+
} else {
122+
ctx.request.get_req_remote_addr().ip().to_string()
123+
},
124+
path: ctx.request.get_req_uri_raw().path().to_string(),
125+
scheme: ctx.request.get_req_uri_raw().scheme_str().unwrap_or("http").to_string(),
126+
token: ctx.request.get_req_headers().get(&self.header_token_name).and_then(|v| v.to_str().ok().map(|v| v.to_string())),
127+
server_timing: start_time.map(|st| end_time - st),
128+
resp_status: ctx.response.get_resp_status_code().as_u16().to_string(),
129+
success,
130+
})
131+
}
132+
}
133+
52134
impl Default for SgFilterAuditLog {
53135
fn default() -> Self {
54136
Self {
@@ -59,6 +141,7 @@ impl Default for SgFilterAuditLog {
59141
success_json_path: "$.code".to_string(),
60142
enabled: false,
61143
success_json_path_values: vec!["200".to_string(), "201".to_string()],
144+
exclude_log_path: vec!["/starsysApi/apis".to_string()],
62145
}
63146
}
64147
}
@@ -74,7 +157,7 @@ impl SgPluginFilter for SgFilterAuditLog {
74157

75158
async fn init(&mut self, _: &SgPluginFilterInitDto) -> TardisResult<()> {
76159
if !self.log_url.is_empty() && !self.spi_app_id.is_empty() {
77-
if JsonPathInst::from_str(&self.success_json_path).map_err(|e| log::error!("[[Plugin.AuditLog]] invalid json path:{e}")).is_err() {
160+
if JsonPathInst::from_str(&self.success_json_path).map_err(|e| log::error!("[Plugin.AuditLog] invalid json path:{e}")).is_err() {
78161
self.enabled = false;
79162
return Ok(());
80163
};
@@ -87,6 +170,7 @@ impl SgPluginFilter for SgFilterAuditLog {
87170
},
88171
)?;
89172
} else {
173+
log::warn!("[Plugin.AuditLog] plugin is not active, miss log_url or spi_app_id.");
90174
self.enabled = false;
91175
}
92176
Ok(())
@@ -103,66 +187,36 @@ impl SgPluginFilter for SgFilterAuditLog {
103187

104188
async fn resp_filter(&self, _: &str, mut ctx: SgRoutePluginContext) -> TardisResult<(bool, SgRoutePluginContext)> {
105189
if self.enabled {
190+
let path = ctx.request.get_req_uri_raw().path().to_string();
191+
for exclude_path in self.exclude_log_path.clone() {
192+
if exclude_path == path {
193+
return Ok((true, ctx));
194+
}
195+
}
106196
let funs = get_tardis_inst();
107-
let start_time = ctx.get_ext(&get_start_time_ext_code()).and_then(|time| time.parse::<i64>().ok());
108197
let end_time = tardis::chrono::Utc::now().timestamp_millis();
109198
let spi_ctx = TardisContext {
110199
owner: ctx.get_cert_info().map(|info| info.account_id.clone()).unwrap_or_default(),
111200
roles: ctx.get_cert_info().map(|info| info.roles.clone().into_iter().map(|r| r.id).collect()).unwrap_or_default(),
112201
..Default::default()
113202
};
114203
let op = ctx.request.get_req_method().to_string();
115-
let resp_body = ctx.response.pop_resp_body().await?;
116-
let success = match resp_body {
117-
Some(body) => {
118-
let body_string = String::from_utf8_lossy(&body).to_string();
119-
let result = match serde_json::from_str::<Value>(&body_string) {
120-
Ok(json) => {
121-
if let Ok(matching_value) = json.path(&self.success_json_path) {
122-
if matching_value.is_number() && matching_value.is_string() {
123-
let mut is_match = false;
124-
for value in self.success_json_path_values.clone() {
125-
if value == matching_value {
126-
is_match = true;
127-
break;
128-
}
129-
}
130-
is_match
131-
} else {
132-
false
133-
}
134-
} else {
135-
false
136-
}
137-
}
138-
Err(_) => false,
139-
};
140-
ctx.response.set_resp_body(body)?;
141-
result
142-
}
143-
None => false,
144-
};
145-
let content = LogParamContent {
146-
op: op.clone(),
147-
key: None,
148-
name: ctx.get_cert_info().and_then(|info| info.account_name.clone()).unwrap_or_default(),
149-
user_id: ctx.get_cert_info().map(|info| info.account_id.clone()),
150-
role: ctx.get_cert_info().map(|info| info.roles.clone()).unwrap_or_default(),
151-
ip: ctx.request.get_req_remote_addr().ip().to_string(),
152-
token: ctx.request.get_req_headers().get(&self.header_token_name).and_then(|v| v.to_str().ok().map(|v| v.to_string())),
153-
server_timing: start_time.map(|st| end_time - st),
154-
success,
155-
};
204+
205+
let content = self.get_log_content(end_time, &mut ctx).await?;
206+
156207
let log_ext = json!({
157208
"name":content.name,
158209
"id":content.user_id,
159210
"ip":content.ip,
160211
"op":op.clone(),
212+
"path":content.path,
213+
"resp_status": content.resp_status,
161214
"success":content.success,
162215
});
216+
let tag = self.tag.clone();
163217
tokio::spawn(async move {
164218
match spi_log_client::SpiLogClient::add(
165-
"tag",
219+
&tag,
166220
&TardisFuns::json.obj_to_string(&content).unwrap_or_default(),
167221
Some(log_ext),
168222
None,
@@ -181,7 +235,7 @@ impl SgPluginFilter for SgFilterAuditLog {
181235
log::trace!("[Plugin.AuditLog] add log success")
182236
}
183237
Err(e) => {
184-
log::trace!("[Plugin.AuditLog] failed to add log:{e}")
238+
log::warn!("[Plugin.AuditLog] failed to add log:{e}")
185239
}
186240
};
187241
});
@@ -209,8 +263,97 @@ pub struct LogParamContent {
209263
pub user_id: Option<String>,
210264
pub role: Vec<SGRoleInfo>,
211265
pub ip: String,
266+
pub path: String,
267+
pub scheme: String,
212268
pub token: Option<String>,
213269
pub server_timing: Option<i64>,
270+
pub resp_status: String,
214271
//Indicates whether the business operation was successful.
215272
pub success: bool,
216273
}
274+
275+
#[cfg(test)]
276+
mod test {
277+
use spacegate_kernel::{
278+
http::{HeaderName, Uri},
279+
hyper::{Body, HeaderMap, Method, StatusCode, Version},
280+
plugins::context::SgRoutePluginContext,
281+
};
282+
use tardis::tokio;
283+
284+
use crate::plugin::audit_log::get_start_time_ext_code;
285+
286+
use super::SgFilterAuditLog;
287+
288+
#[tokio::test]
289+
async fn test_log_content() {
290+
let sg_filter_audit_log = SgFilterAuditLog { ..Default::default() };
291+
let end_time = 20100;
292+
let mut header = HeaderMap::new();
293+
header.insert(sg_filter_audit_log.header_token_name.parse::<HeaderName>().unwrap(), "aaa".parse().unwrap());
294+
let mut ctx = SgRoutePluginContext::new_http(
295+
Method::POST,
296+
Uri::from_static("http://sg.idealworld.group/test1"),
297+
Version::HTTP_11,
298+
header,
299+
Body::from(""),
300+
"127.0.0.1:8080".parse().unwrap(),
301+
"".to_string(),
302+
None,
303+
);
304+
ctx.set_ext(&get_start_time_ext_code(), &20000.to_string());
305+
let mut ctx = ctx.resp(StatusCode::OK, HeaderMap::new(), Body::from(r##"{"code":"200","msg":"success"}"##));
306+
let log_content = sg_filter_audit_log.get_log_content(end_time, &mut ctx).await.unwrap();
307+
assert_eq!(log_content.token, Some("aaa".to_string()));
308+
assert_eq!(log_content.server_timing, Some(100));
309+
assert!(log_content.success);
310+
311+
let mut header = HeaderMap::new();
312+
header.insert(sg_filter_audit_log.header_token_name.parse::<HeaderName>().unwrap(), "aaa".parse().unwrap());
313+
let ctx = SgRoutePluginContext::new_http(
314+
Method::POST,
315+
Uri::from_static("http://sg.idealworld.group/test1"),
316+
Version::HTTP_11,
317+
header,
318+
Body::from(""),
319+
"127.0.0.1:8080".parse().unwrap(),
320+
"".to_string(),
321+
None,
322+
);
323+
let mut ctx = ctx.resp(StatusCode::OK, HeaderMap::new(), Body::from(r##"{"code":200,"msg":"success"}"##));
324+
let log_content = sg_filter_audit_log.get_log_content(end_time, &mut ctx).await.unwrap();
325+
assert!(log_content.success);
326+
327+
let mut header = HeaderMap::new();
328+
header.insert(sg_filter_audit_log.header_token_name.parse::<HeaderName>().unwrap(), "aaa".parse().unwrap());
329+
let ctx = SgRoutePluginContext::new_http(
330+
Method::POST,
331+
Uri::from_static("http://sg.idealworld.group/test1"),
332+
Version::HTTP_11,
333+
header,
334+
Body::from(""),
335+
"127.0.0.1:8080".parse().unwrap(),
336+
"".to_string(),
337+
None,
338+
);
339+
let mut ctx = ctx.resp(StatusCode::OK, HeaderMap::new(), Body::from(r##"{"code":"500","msg":"not success"}"##));
340+
let log_content = sg_filter_audit_log.get_log_content(end_time, &mut ctx).await.unwrap();
341+
assert!(!log_content.success);
342+
343+
let mut header = HeaderMap::new();
344+
header.insert(sg_filter_audit_log.header_token_name.parse::<HeaderName>().unwrap(), "aaa".parse().unwrap());
345+
let ctx = SgRoutePluginContext::new_http(
346+
Method::POST,
347+
Uri::from_static("http://sg.idealworld.group/test1"),
348+
Version::HTTP_11,
349+
header,
350+
Body::from(""),
351+
"127.0.0.1:8080".parse().unwrap(),
352+
"".to_string(),
353+
None,
354+
);
355+
let mut ctx = ctx.resp(StatusCode::OK, HeaderMap::new(), Body::from(r##"{"code":500,"msg":"not success"}"##));
356+
let log_content = sg_filter_audit_log.get_log_content(end_time, &mut ctx).await.unwrap();
357+
assert!(!log_content.success);
358+
}
359+
}

0 commit comments

Comments
 (0)