fix(graylog): use basic auth with base64 token fallback
This commit is contained in:
parent
d6834cf648
commit
09d26d62f8
1 changed files with 76 additions and 3 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::services::graylog_scoring::GraylogEvent;
|
use crate::services::graylog_scoring::GraylogEvent;
|
||||||
|
use base64::{engine::general_purpose::STANDARD, Engine};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use tokio::time::{sleep, Duration};
|
use tokio::time::{sleep, Duration};
|
||||||
|
|
@ -18,7 +19,11 @@ impl GraylogClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_get(&self, url: &str) -> Result<reqwest::Response, String> {
|
async fn send_get_with_auth(
|
||||||
|
&self,
|
||||||
|
url: &str,
|
||||||
|
auth_token: &str,
|
||||||
|
) -> Result<reqwest::Response, String> {
|
||||||
const MAX_ATTEMPTS: u32 = 3;
|
const MAX_ATTEMPTS: u32 = 3;
|
||||||
const BASE_DELAY_MS: u64 = 500;
|
const BASE_DELAY_MS: u64 = 500;
|
||||||
|
|
||||||
|
|
@ -32,7 +37,7 @@ impl GraylogClient {
|
||||||
let response = self
|
let response = self
|
||||||
.http
|
.http
|
||||||
.get(url)
|
.get(url)
|
||||||
.header("Authorization", format!("Bearer {}", self.token))
|
.basic_auth(auth_token, Some("token"))
|
||||||
.header("X-Requested-By", "orchai")
|
.header("X-Requested-By", "orchai")
|
||||||
.send()
|
.send()
|
||||||
.await;
|
.await;
|
||||||
|
|
@ -88,6 +93,25 @@ impl GraylogClient {
|
||||||
Err("graylog request failed after retries".to_string())
|
Err("graylog request failed after retries".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn send_get(&self, url: &str) -> Result<reqwest::Response, String> {
|
||||||
|
let auth_tokens = build_auth_tokens(&self.token);
|
||||||
|
|
||||||
|
for (index, auth_token) in auth_tokens.iter().enumerate() {
|
||||||
|
let response = self.send_get_with_auth(url, auth_token).await?;
|
||||||
|
if response.status() == reqwest::StatusCode::UNAUTHORIZED
|
||||||
|
&& index + 1 < auth_tokens.len()
|
||||||
|
{
|
||||||
|
eprintln!(
|
||||||
|
"[graylog] auth token rejected (401), retrying with base64-decoded token"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return Ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err("graylog request failed after auth fallback".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn test_connection(&self) -> Result<(), String> {
|
pub async fn test_connection(&self) -> Result<(), String> {
|
||||||
let url = format!("{}/api/system", self.base_url);
|
let url = format!("{}/api/system", self.base_url);
|
||||||
let resp = self.send_get(&url).await?;
|
let resp = self.send_get(&url).await?;
|
||||||
|
|
@ -95,7 +119,10 @@ impl GraylogClient {
|
||||||
if resp.status().is_success() {
|
if resp.status().is_success() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(format!("graylog connection test failed: HTTP {}", resp.status()))
|
Err(format!(
|
||||||
|
"graylog connection test failed: HTTP {}",
|
||||||
|
resp.status()
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,6 +159,33 @@ impl GraylogClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decode_token_candidate(token: &str) -> Option<String> {
|
||||||
|
if token.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let decoded_bytes = STANDARD.decode(token).ok()?;
|
||||||
|
let decoded = String::from_utf8(decoded_bytes).ok()?;
|
||||||
|
let decoded = decoded.trim();
|
||||||
|
if decoded.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if !decoded.chars().all(|c| c.is_ascii_alphanumeric()) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(decoded.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_auth_tokens(token: &str) -> Vec<String> {
|
||||||
|
let token = token.trim().to_string();
|
||||||
|
let mut candidates = vec![token.clone()];
|
||||||
|
if let Some(decoded) = decode_token_candidate(&token) {
|
||||||
|
if decoded != token {
|
||||||
|
candidates.push(decoded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
candidates
|
||||||
|
}
|
||||||
|
|
||||||
fn level_to_string(value: &Value) -> String {
|
fn level_to_string(value: &Value) -> String {
|
||||||
match value {
|
match value {
|
||||||
Value::String(s) => s.to_string(),
|
Value::String(s) => s.to_string(),
|
||||||
|
|
@ -255,4 +309,23 @@ mod tests {
|
||||||
let events = parse_search_response(&payload);
|
let events = parse_search_response(&payload);
|
||||||
assert!(events.is_empty());
|
assert!(events.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_new_decodes_base64_token_for_stackhero_format() {
|
||||||
|
let encoded = "OTk2c2l2aGY1Z243Zm9xNzh2ajVrbmlkZWQ4bW9idHZrZ3Nhb2lvNDRrbTY3MnZkaWc2";
|
||||||
|
let candidates = build_auth_tokens(encoded);
|
||||||
|
assert_eq!(candidates.len(), 2);
|
||||||
|
assert_eq!(candidates[0], encoded);
|
||||||
|
assert_eq!(
|
||||||
|
candidates[1],
|
||||||
|
"996sivhf5gn7foq78vj5knided8mobtvkgsaoio44km672vdig6"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_auth_tokens_keeps_raw_when_not_base64() {
|
||||||
|
let raw = "996sivhf5gn7foq78vj5knided8mobtvkgsaoio44km672vdig6";
|
||||||
|
let candidates = build_auth_tokens(raw);
|
||||||
|
assert_eq!(candidates, vec![raw.to_string()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue