Skip to content

Commit a4380dc

Browse files
committed
feat(dgw): emit syslogs and Windows events for important events
Issue: DGW-63 Security: yes
1 parent 9ddf10c commit a4380dc

File tree

32 files changed

+1768
-28
lines changed

32 files changed

+1768
-28
lines changed

.cargo/mutants.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
skip_calls = ["emit_to_syslog", "emit_to_event_log"]
2+
exclude_re = ["impl Debug"]

Cargo.lock

Lines changed: 41 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ members = [
66
"devolutions-gateway",
77
"devolutions-session",
88
"jetsocat",
9+
"testsuite",
910
"tools/generate-openapi",
1011
]
1112
default-members = [
@@ -36,6 +37,10 @@ strip = "symbols"
3637
tracing-appender = { git = "https://github.com/CBenoit/tracing.git", rev = "42097daf92e683cf18da7639ddccb056721a796c" }
3738

3839
[workspace.lints.rust]
40+
# Declare the custom cfgs.
41+
unexpected_cfgs = { level = "warn", check-cfg = [
42+
'cfg(build_profile, values("dev","release","production"))',
43+
]}
3944

4045
# == Safer unsafe == #
4146
unsafe_op_in_unsafe_fn = "warn"
@@ -55,6 +60,9 @@ noop_method_call = "warn"
5560
unused_crate_dependencies = "warn"
5661
unused_macro_rules = "warn"
5762

63+
[workspace.dependencies]
64+
proptest = "1.0"
65+
5866
[workspace.lints.clippy]
5967

6068
# == Safer unsafe == #

crates/proxy-socks/src/socks5.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,10 +276,8 @@ impl From<io::ErrorKind> for Socks5FailureCode {
276276
match kind {
277277
io::ErrorKind::ConnectionRefused => Socks5FailureCode::ConnectionRefused,
278278
io::ErrorKind::TimedOut => Socks5FailureCode::TtlExpired,
279-
#[cfg(feature = "nightly")] // https://github.com/rust-lang/rust/issues/86442
280-
std::io::ErrorKind::HostUnreachable => Socks5FailureCode::HostUnreachable,
281-
#[cfg(feature = "nightly")] // https://github.com/rust-lang/rust/issues/86442
282-
std::io::ErrorKind::NetworkUnreachable => Socks5FailureCode::NetworkUnreachable,
279+
io::ErrorKind::HostUnreachable => Socks5FailureCode::HostUnreachable,
280+
io::ErrorKind::NetworkUnreachable => Socks5FailureCode::NetworkUnreachable,
283281
_ => Socks5FailureCode::GeneralSocksServerFailure,
284282
}
285283
}

crates/sysevent-codes/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "sysevent-codes"
3+
version = "0.0.0"
4+
edition = "2024"
5+
authors = ["Devolutions Inc. <infos@devolutions.net>"]
6+
license = "MIT OR Apache-2.0"
7+
publish = false
8+
9+
[lints]
10+
workspace = true
11+
12+
[dependencies]
13+
sysevent.path = "../sysevent"

crates/sysevent-codes/src/lib.rs

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
use sysevent::{Entry, Severity};
2+
3+
// 1000-1099 **Service/Lifecycle**
4+
5+
/// Fired after `GatewayService::start()`
6+
pub const SERVICE_STARTED: u32 = 1000;
7+
/// Graceful stop received
8+
pub const SERVICE_STOPPING: u32 = 1001;
9+
/// Failed to init config
10+
pub const INVALID_CONFIG: u32 = 1010;
11+
/// Top-level start failure
12+
pub const START_FAILED: u32 = 1020;
13+
14+
pub fn service_started(version: impl ToString) -> Entry {
15+
Entry::new("Service started")
16+
.event_code(SERVICE_STARTED)
17+
.severity(Severity::Info)
18+
.field("version", version)
19+
}
20+
21+
pub fn service_stopping() -> Entry {
22+
Entry::new("Service stopping")
23+
.event_code(SERVICE_STOPPING)
24+
.severity(Severity::Info)
25+
}
26+
27+
pub fn invalid_config(error: &dyn std::fmt::Display) -> Entry {
28+
Entry::new(format!("Invalid config: {error:#}"))
29+
.event_code(INVALID_CONFIG)
30+
.severity(Severity::Critical)
31+
}
32+
33+
pub fn start_failed(error: &dyn std::fmt::Display) -> Entry {
34+
Entry::new(format!("Start failure: {error:#}"))
35+
.event_code(START_FAILED)
36+
.severity(Severity::Error)
37+
}
38+
39+
// 2000-2099 **Listeners & Networking**
40+
41+
// Fires with listener start.
42+
pub const LISTENER_STARTED: u32 = 2000;
43+
// Bind failure with OS error.
44+
pub const LISTENER_BIND_FAILED: u32 = 2001;
45+
46+
pub fn listener_started(message: impl Into<String>) -> Entry {
47+
Entry::new(message)
48+
.event_code(LISTENER_STARTED)
49+
.severity(Severity::Info)
50+
}
51+
52+
pub fn listener_bind_failed(message: impl Into<String>) -> Entry {
53+
Entry::new(message)
54+
.event_code(LISTENER_BIND_FAILED)
55+
.severity(Severity::Error)
56+
}
57+
58+
// 3000-3099 **TLS / Certificates**
59+
60+
/// TLS configured (includes which source: file vs system store)
61+
pub const TLS_CONFIGURED: u32 = 3000;
62+
/// Strict verification off (compat mode).
63+
pub const TLS_VERIFY_STRICT_DISABLED: u32 = 3001;
64+
/// Missing SAN or EKU=serverAuth.
65+
pub const TLS_CERTIFICATE_REJECTED: u32 = 3002;
66+
/// Thumbprint/subject; selection criteria.
67+
pub const SYSTEM_CERT_SELECTED: u32 = 3003;
68+
pub const TLS_CERTIFICATE_NAME_MISMATCH: u32 = 3004;
69+
70+
pub fn tls_configured() -> Entry {
71+
Entry::new("TLS configured")
72+
.event_code(TLS_CONFIGURED)
73+
.severity(Severity::Info)
74+
}
75+
76+
pub fn tls_verify_strict_disabled(message: impl Into<String>) -> Entry {
77+
Entry::new(message)
78+
.event_code(TLS_VERIFY_STRICT_DISABLED)
79+
.severity(Severity::Notice)
80+
}
81+
82+
pub fn tls_certificate_rejected(message: impl Into<String>) -> Entry {
83+
Entry::new(message)
84+
.event_code(TLS_CERTIFICATE_REJECTED)
85+
.severity(Severity::Critical)
86+
}
87+
88+
pub fn system_cert_selected(message: impl Into<String>) -> Entry {
89+
Entry::new(message)
90+
.event_code(SYSTEM_CERT_SELECTED)
91+
.severity(Severity::Info)
92+
}
93+
94+
pub fn tls_certificate_name_mismatch(message: impl Into<String>) -> Entry {
95+
Entry::new(message)
96+
.event_code(TLS_CERTIFICATE_NAME_MISMATCH)
97+
.severity(Severity::Notice)
98+
}
99+
100+
// 4000-4099 **Sessions, Tokens & Recording**
101+
102+
pub const TOKEN_REUSE_LIMIT_EXCEEDED: u32 = 4012;
103+
pub const RECORDING_ERROR: u32 = 4032;
104+
105+
pub fn token_reuse_limit_exceeded(message: impl Into<String>) -> Entry {
106+
Entry::new(message)
107+
.event_code(TOKEN_REUSE_LIMIT_EXCEEDED)
108+
.severity(Severity::Warning)
109+
}
110+
111+
pub fn recording_error(message: impl Into<String>) -> Entry {
112+
Entry::new(message)
113+
.event_code(RECORDING_ERROR)
114+
.severity(Severity::Error)
115+
}
116+
117+
// 5000-5099 **Authentication / Authorization**
118+
119+
/// Signature/Expiry/Audience failure.
120+
pub const JWT_REJECTED: u32 = 5001;
121+
/// Rule evaluation result.
122+
pub const AUTHORIZATION_DENIED: u32 = 5010;
123+
124+
pub fn jwt_rejected(message: impl Into<String>) -> Entry {
125+
Entry::new(message).event_code(JWT_REJECTED).severity(Severity::Warning)
126+
}
127+
128+
pub fn authorization_denied(message: impl Into<String>) -> Entry {
129+
Entry::new(message)
130+
.event_code(AUTHORIZATION_DENIED)
131+
.severity(Severity::Warning)
132+
}
133+
134+
// 6000-6099 **Agent Integration**
135+
136+
/// `DevolutionsSession.exe` started in session; include session id & kind (console/remote).
137+
pub const USER_SESSION_PROCESS_STARTED: u32 = 6000;
138+
/// Exit code; who triggered.
139+
pub const USER_SESSION_PROCESS_TERMINATED: u32 = 6001;
140+
pub const UPDATER_TASK_ENABLED: u32 = 6010;
141+
pub const UPDATER_ERROR: u32 = 6011;
142+
pub const PEDM_ENABLED: u32 = 6020;
143+
144+
pub fn user_session_process_started(message: impl Into<String>) -> Entry {
145+
Entry::new(message)
146+
.event_code(USER_SESSION_PROCESS_STARTED)
147+
.severity(Severity::Info)
148+
}
149+
150+
pub fn user_session_process_terminated(message: impl Into<String>) -> Entry {
151+
Entry::new(message)
152+
.event_code(USER_SESSION_PROCESS_TERMINATED)
153+
.severity(Severity::Info)
154+
}
155+
156+
pub fn updater_task_enabled(message: impl Into<String>) -> Entry {
157+
Entry::new(message)
158+
.event_code(UPDATER_TASK_ENABLED)
159+
.severity(Severity::Info)
160+
}
161+
162+
pub fn updater_error(message: impl Into<String>) -> Entry {
163+
Entry::new(message)
164+
.event_code(UPDATER_ERROR)
165+
.severity(Severity::Warning)
166+
}
167+
168+
pub fn pedm_enabled(message: impl Into<String>) -> Entry {
169+
Entry::new(message).event_code(PEDM_ENABLED).severity(Severity::Info)
170+
}
171+
172+
// 7000-7099 **Health**
173+
174+
// 9000-9099 **Diagnostics**
175+
176+
pub const DEBUG_OPTIONS_ENABLED: u32 = 9001;
177+
pub const XMF_NOT_FOUND: u32 = 9002;
178+
179+
pub fn debug_options_enabled(message: impl Into<String>) -> Entry {
180+
Entry::new(message)
181+
.event_code(DEBUG_OPTIONS_ENABLED)
182+
.severity(Severity::Notice)
183+
}
184+
185+
pub fn xmf_not_found(message: impl Into<String>) -> Entry {
186+
Entry::new(message)
187+
.event_code(XMF_NOT_FOUND)
188+
.severity(Severity::Warning)
189+
}

crates/sysevent-syslog/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "sysevent-syslog"
3+
version = "0.0.0"
4+
edition = "2024"
5+
authors = ["Devolutions Inc. <infos@devolutions.net>"]
6+
description = "Syslog backend for system-wide critical event logging"
7+
license = "MIT OR Apache-2.0"
8+
repository = "https://github.com/Devolutions/devolutions-gateway"
9+
publish = false
10+
11+
[dependencies]
12+
sysevent.path = "../sysevent"
13+
libc = "0.2"
14+
15+
[dev-dependencies]
16+
proptest.workspace = true
17+
18+
[lints]
19+
workspace = true

crates/sysevent-syslog/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# sysevent-syslog
2+
3+
Syslog backend for system-wide critical event logging.
4+
5+
This crate provides Unix/Linux syslog implementation using standard libc calls.
6+
7+
## Platform Requirements
8+
9+
- Unix/Linux systems with syslog facilities
10+
- Standard C library with openlog/syslog/closelog functions
11+
- Thread-safe operation across concurrent emission calls
12+
13+
## Examples
14+
15+
```rust,no_run
16+
use sysevent::{Entry, Severity, Facility, SystemEventSink};
17+
use sysevent_syslog::{Syslog, SyslogOptions};
18+
19+
let options = SyslogOptions::default()
20+
.facility(Facility::Daemon)
21+
.log_pid(true);
22+
23+
let syslog = Syslog::new(c"myapp", options)?;
24+
25+
let entry = Entry::new("Database connection failed")
26+
.severity(Severity::Critical);
27+
28+
syslog.emit(entry)?;
29+
# Ok::<(), sysevent::SysEventError>(())
30+
```

0 commit comments

Comments
 (0)