v0.26.1
This commit is contained in:
1 parent
ddc5c6593f
commit
b9d137eee1
63 files changed
+1232
-445
No files matched your search
+74
-2
@@ -2,9 +2,14 @@
|
||||
pub struct LogEntry {
|
||||
pub event: &'static str,
|
||||
pub protocol_error: Option<&'static str>,
|
||||
pub reason: Option<&'static str>,
|
||||
pub error_detail: Option<String>,
|
||||
pub error: Option<String>,
|
||||
pub local_addr: Option<String>,
|
||||
pub tls_sni: Option<String>,
|
||||
pub sni: Option<String>,
|
||||
pub authority: Option<String>,
|
||||
pub matched_by: Option<&'static str>,
|
||||
pub vhost: Option<String>,
|
||||
pub route: Option<String>,
|
||||
pub cache_effective_enabled: Option<bool>,
|
||||
@@ -85,6 +90,21 @@ fn protocol_error_detail(detail: impl std::fmt::Display) -> String {
|
||||
detail
|
||||
}
|
||||
|
||||
fn protocol_identity_field(value: Option<String>) -> Option<String> {
|
||||
value.map(|value| {
|
||||
let mut value: String = value.chars().filter(|ch| !ch.is_control()).collect();
|
||||
if value.len() > 512 {
|
||||
let mut end = 512;
|
||||
while !value.is_char_boundary(end) {
|
||||
end -= 1;
|
||||
}
|
||||
value.truncate(end);
|
||||
value.push_str("...");
|
||||
}
|
||||
value
|
||||
})
|
||||
}
|
||||
|
||||
impl Logs {
|
||||
pub fn start(settings: Settings, shutdown: CancellationToken) -> Arc<Self> {
|
||||
let (tx, rx) = mpsc::channel(SINK_QUEUE);
|
||||
@@ -143,18 +163,47 @@ impl Logs {
|
||||
http_version: &'static str,
|
||||
status: u16,
|
||||
tls_sni: Option<String>,
|
||||
) {
|
||||
self.push_protocol_error_attributed(
|
||||
kind, detail, client, local, transport, http_version, status, tls_sni, None, None,
|
||||
"none",
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn push_protocol_error_attributed(
|
||||
&self,
|
||||
kind: &'static str,
|
||||
detail: impl std::fmt::Display,
|
||||
client: std::net::SocketAddr,
|
||||
local: std::net::SocketAddr,
|
||||
transport: &'static str,
|
||||
http_version: &'static str,
|
||||
status: u16,
|
||||
tls_sni: Option<String>,
|
||||
authority: Option<String>,
|
||||
vhost: Option<String>,
|
||||
matched_by: &'static str,
|
||||
) {
|
||||
if !self.settings.load().enabled() {
|
||||
return;
|
||||
}
|
||||
let detail = protocol_error_detail(detail);
|
||||
let tls_sni = protocol_identity_field(tls_sni);
|
||||
let authority = protocol_identity_field(authority);
|
||||
let sni = tls_sni.clone();
|
||||
self.push_sink(LogEntry {
|
||||
event: "protocol_error",
|
||||
protocol_error: Some(kind),
|
||||
error_detail: Some(detail),
|
||||
reason: Some(kind),
|
||||
error_detail: Some(detail.clone()),
|
||||
error: Some(detail),
|
||||
local_addr: Some(local.to_string()),
|
||||
tls_sni,
|
||||
vhost: None,
|
||||
sni,
|
||||
authority,
|
||||
matched_by: Some(matched_by),
|
||||
vhost,
|
||||
route: None,
|
||||
cache_effective_enabled: None,
|
||||
transform_effective_enabled: None,
|
||||
@@ -221,11 +270,20 @@ fn value_for(entry: &LogEntry, name: &str) -> Value {
|
||||
.protocol_error
|
||||
.map(|v| Value::String(v.into()))
|
||||
.unwrap_or(Value::Null),
|
||||
"reason" => entry
|
||||
.reason
|
||||
.map(|v| Value::String(v.into()))
|
||||
.unwrap_or(Value::Null),
|
||||
"error_detail" => entry
|
||||
.error_detail
|
||||
.clone()
|
||||
.map(Value::String)
|
||||
.unwrap_or(Value::Null),
|
||||
"error" => entry
|
||||
.error
|
||||
.clone()
|
||||
.map(Value::String)
|
||||
.unwrap_or(Value::Null),
|
||||
"local_addr" => entry
|
||||
.local_addr
|
||||
.clone()
|
||||
@@ -236,6 +294,20 @@ fn value_for(entry: &LogEntry, name: &str) -> Value {
|
||||
.clone()
|
||||
.map(Value::String)
|
||||
.unwrap_or(Value::Null),
|
||||
"sni" => entry
|
||||
.sni
|
||||
.clone()
|
||||
.map(Value::String)
|
||||
.unwrap_or(Value::Null),
|
||||
"authority" => entry
|
||||
.authority
|
||||
.clone()
|
||||
.map(Value::String)
|
||||
.unwrap_or(Value::Null),
|
||||
"matched_by" => entry
|
||||
.matched_by
|
||||
.map(|v| Value::String(v.into()))
|
||||
.unwrap_or(Value::Null),
|
||||
"vhost" => entry
|
||||
.vhost
|
||||
.clone()
|
||||
|
||||
@@ -231,9 +231,14 @@ fn source_value_name(name: &str) -> bool {
|
||||
name,
|
||||
"event"
|
||||
| "protocol_error"
|
||||
| "reason"
|
||||
| "error_detail"
|
||||
| "error"
|
||||
| "local_addr"
|
||||
| "tls_sni"
|
||||
| "sni"
|
||||
| "authority"
|
||||
| "matched_by"
|
||||
| "vhost"
|
||||
| "route"
|
||||
| "cache_effective_enabled"
|
||||
|
||||
+10
-18
@@ -19,11 +19,17 @@ fn format_entry(entry: &LogEntry, format: &JsonFormat) -> Value {
|
||||
for name in [
|
||||
"event",
|
||||
"protocol_error",
|
||||
"reason",
|
||||
"error_detail",
|
||||
"error",
|
||||
"time_ms",
|
||||
"client_ip",
|
||||
"local_addr",
|
||||
"tls_sni",
|
||||
"sni",
|
||||
"authority",
|
||||
"matched_by",
|
||||
"vhost",
|
||||
"transport",
|
||||
"http_version",
|
||||
"status",
|
||||
@@ -63,24 +69,10 @@ struct FileState {
|
||||
const PER_VHOST_OPEN_FILES: usize = 64;
|
||||
|
||||
fn vhost_log_name(vhost: Option<&str>) -> String {
|
||||
let raw = vhost
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or("_unmatched");
|
||||
let mut safe = String::with_capacity(raw.len().min(96));
|
||||
for ch in raw.chars().take(96) {
|
||||
safe.push(
|
||||
if ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.') {
|
||||
ch
|
||||
} else {
|
||||
'_'
|
||||
},
|
||||
);
|
||||
}
|
||||
if safe.is_empty() {
|
||||
"_unmatched".into()
|
||||
} else {
|
||||
safe
|
||||
}
|
||||
vhost
|
||||
.filter(|value| crate::config::valid_id(value))
|
||||
.unwrap_or("_unmatched")
|
||||
.to_owned()
|
||||
}
|
||||
struct LogstashState {
|
||||
address: String,
|
||||
|
||||
+25
-1
@@ -5,9 +5,14 @@ mod tests {
|
||||
LogEntry {
|
||||
event: "request",
|
||||
protocol_error: None,
|
||||
reason: None,
|
||||
error_detail: None,
|
||||
error: None,
|
||||
local_addr: Some("192.0.2.1:443".into()),
|
||||
tls_sni: Some("app.example".into()),
|
||||
sni: Some("app.example".into()),
|
||||
authority: None,
|
||||
matched_by: None,
|
||||
vhost: Some("app".into()),
|
||||
route: Some("root".into()),
|
||||
cache_effective_enabled: Some(true),
|
||||
@@ -95,7 +100,7 @@ mod tests {
|
||||
fn per_vhost_file_mode_has_safe_names_and_requires_directory() {
|
||||
assert_eq!(vhost_log_name(Some("app-1")), "app-1");
|
||||
assert_eq!(vhost_log_name(None), "_unmatched");
|
||||
assert_eq!(vhost_log_name(Some("bad/name")), "bad_name");
|
||||
assert_eq!(vhost_log_name(Some("bad/name")), "_unmatched");
|
||||
let mut settings = Settings::default();
|
||||
settings.file.enabled = true;
|
||||
settings.file.per_vhost = true;
|
||||
@@ -124,7 +129,12 @@ mod tests {
|
||||
let mut item = entry();
|
||||
item.event = "protocol_error";
|
||||
item.protocol_error = Some("tls_handshake_error");
|
||||
item.reason = Some("tls_handshake_error");
|
||||
item.error_detail = Some("invalid handshake".into());
|
||||
item.error = Some("invalid handshake".into());
|
||||
item.sni = Some("app.example".into());
|
||||
item.authority = Some("app.example:443".into());
|
||||
item.matched_by = Some("sni");
|
||||
item.outcome = "protocol_error";
|
||||
item.status = 0;
|
||||
item.transport = "tls";
|
||||
@@ -135,7 +145,12 @@ mod tests {
|
||||
assert_eq!(value["site"], "app");
|
||||
assert_eq!(value["event"], "protocol_error");
|
||||
assert_eq!(value["protocol_error"], "tls_handshake_error");
|
||||
assert_eq!(value["reason"], "tls_handshake_error");
|
||||
assert_eq!(value["error_detail"], "invalid handshake");
|
||||
assert_eq!(value["error"], "invalid handshake");
|
||||
assert_eq!(value["sni"], "app.example");
|
||||
assert_eq!(value["authority"], "app.example:443");
|
||||
assert_eq!(value["matched_by"], "sni");
|
||||
assert_eq!(value["client_ip"], "192.0.2.10");
|
||||
assert_eq!(value["transport"], "tls");
|
||||
assert_eq!(value["outcome"], "protocol_error");
|
||||
@@ -170,6 +185,15 @@ mod tests {
|
||||
assert!(detail.ends_with("..."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn protocol_identity_fields_are_bounded_and_strip_controls() {
|
||||
let value = protocol_identity_field(Some(format!("host\n{}", "€".repeat(300)))).unwrap();
|
||||
assert!(!value.contains('\n'));
|
||||
assert!(value.is_char_boundary(value.len()));
|
||||
assert!(value.len() <= 515);
|
||||
assert!(value.ends_with("..."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_log_defaults_off_and_redacts_common_secret_shapes() {
|
||||
let settings = ErrorLogSettings::default();
|
||||
|
||||
Reference in new issue
Block a user