Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
220a7cd
update protos
wojcik91 Apr 15, 2026
0b6b1df
update core dependencies
wojcik91 Apr 15, 2026
3f52dfc
store new certs during setup
wojcik91 Apr 15, 2026
e6acffa
load new certs on startup
wojcik91 Apr 15, 2026
ad52145
add helper for loading tls certs
wojcik91 Apr 15, 2026
36403c9
update purge handler
wojcik91 Apr 15, 2026
4c0be09
add helper for removing files
wojcik91 Apr 15, 2026
c6a95f7
validate received certs during setup
wojcik91 Apr 15, 2026
0edc247
add interceptor to verify core cert serial
wojcik91 Apr 16, 2026
1e72589
don't allow gateway gRPC server to start in non-TLS mode
wojcik91 Apr 16, 2026
932eef1
update interceptor signature
wojcik91 Apr 16, 2026
0ac4fef
add basic mTLS tests
wojcik91 Apr 16, 2026
a39b0fb
align crypto libs
wojcik91 Apr 16, 2026
f86f735
limit tokio features
wojcik91 Apr 16, 2026
dcd6aa3
review cleanup
wojcik91 Apr 16, 2026
155a9b9
use shared setup function
wojcik91 Apr 17, 2026
227a826
cleanup
wojcik91 Apr 17, 2026
fc010ba
expand crypto provider comment
wojcik91 Apr 17, 2026
1617304
review fixes
wojcik91 Apr 20, 2026
0a66935
Merge branch 'release/2.0' into mtls
wojcik91 Apr 20, 2026
8c7ae2d
update dependencies
wojcik91 Apr 20, 2026
2ddec13
fix missing crypto provider init
wojcik91 Apr 20, 2026
14b77cd
remove unused config options
wojcik91 Apr 20, 2026
dccfe16
update cargo deny config
wojcik91 Apr 21, 2026
11930bb
update protos
wojcik91 Apr 21, 2026
4b892ed
review fixes
wojcik91 Apr 21, 2026
c67d240
update core dependencies
wojcik91 Apr 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,073 changes: 1,981 additions & 92 deletions Cargo.lock

Large diffs are not rendered by default.

13 changes: 9 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ axum = "0.8"
base64 = "0.22"
chrono = "0.4"
clap = { version = "4.6", features = ["derive", "env"] }
defguard_certs = { git = "https://github.com/DefGuard/defguard.git", rev = "01957186101fc105803d56f1190efbdb5102df2f" }
defguard_version = { git = "https://github.com/DefGuard/defguard.git", rev = "01957186101fc105803d56f1190efbdb5102df2f" }
defguard_certs = { git = "https://github.com/DefGuard/defguard.git", rev = "8ac70d3157d8a47b038bd1022ab9806bda642da4" }
defguard_grpc_tls = { git = "https://github.com/DefGuard/defguard.git", rev = "8ac70d3157d8a47b038bd1022ab9806bda642da4" }
defguard_version = { git = "https://github.com/DefGuard/defguard.git", rev = "8ac70d3157d8a47b038bd1022ab9806bda642da4" }
rustls = { version = "0.23", default-features = false, features = ["ring"] }
rustls-pki-types = "1"
rustls-webpki = { version = "0.103", features = ["ring", "std"] }
defguard_wireguard_rs = "0.9"
env_logger = "0.11"
ipnetwork = "0.21"
Expand All @@ -20,7 +24,7 @@ prost-types = "0.14"
serde = { version = "1.0", features = ["derive"] }
syslog = "7.0"
thiserror = "2.0"
tokio = { version = "1", features = ["fs", "macros", "rt-multi-thread", "signal"] }
tokio = { version = "1", features = ["fs", "macros", "rt-multi-thread", "signal", "sync", "time"] }
tokio-stream = { version = "0.1", features = [] }
toml = { version = "1.0", default-features = false, features = [
"parse",
Expand All @@ -46,7 +50,8 @@ mnl = "0.3"
nix = { version = "0.31", default-features = false, features = ["ioctl"] }

[dev-dependencies]
tokio = { version = "1", features = ["io-std", "io-util"] }
defguard_certs = { git = "https://github.com/DefGuard/defguard.git", rev = "8ac70d3157d8a47b038bd1022ab9806bda642da4" }
tokio = { version = "1", features = ["net"] }
tonic = { version = "0.14", default-features = false, features = [
"codegen",
"router",
Expand Down
17 changes: 16 additions & 1 deletion deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ feature-depth = 1
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
{ id = "RUSTSEC-2023-0071", reason = "https://github.com/RustCrypto/RSA/issues/19" },
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained" },
{ id = "RUSTSEC-2025-0142", reason = "Awaiting upstream patch" },
]
# If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library.
Expand Down Expand Up @@ -112,14 +112,29 @@ confidence-threshold = 0.8
# aren't accepted for every possible crate as with the normal allow list
exceptions = [
{ allow = [
"AGPL-3.0-only",
"AGPL-3.0-or-later",
], crate = "defguard-gateway" },
{ allow = [
"AGPL-3.0-only",
"AGPL-3.0-or-later",
], crate = "defguard_version" },
{ allow = [
"AGPL-3.0-only",
"AGPL-3.0-or-later",
], crate = "defguard_certs" },
{ allow = [
"AGPL-3.0-only",
"AGPL-3.0-or-later",
], crate = "defguard_grpc_tls" },
{ allow = [
"AGPL-3.0-only",
"AGPL-3.0-or-later",
], crate = "defguard_common" },
{ allow = [
"AGPL-3.0-only",
"AGPL-3.0-or-later",
], crate = "model_derive" },
]

# Some crates don't have (easily) machine readable licensing information,
Expand Down
12 changes: 6 additions & 6 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion proto
10 changes: 0 additions & 10 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,6 @@ pub struct Config {
#[arg(long, env = "DEFGUARD_GRPC_PORT", default_value = "50066")]
pub(crate) grpc_port: u16,

/// Gateway gRPC server certificate.
#[arg(long, env = "DEFGUARD_GATEWAY_GRPC_CERT")]
pub(crate) grpc_cert: Option<String>,

/// Gateway gRPC server private key.
#[arg(long, env = "DEFGUARD_GATEWAY_GRPC_KEY")]
pub(crate) grpc_key: Option<String>,

/// Use userspace WireGuard implementation e.g. wireguard-go
#[arg(long, short = 'u', env = "DEFGUARD_USERSPACE")]
pub userspace: bool,
Expand Down Expand Up @@ -159,8 +151,6 @@ impl Default for Config {
log_level: "info".into(),
grpc_port: 50066,
userspace: false,
grpc_cert: None,
grpc_key: None,
stats_period: 15,
ifname: "wg0".into(),
pidfile: None,
Expand Down
103 changes: 60 additions & 43 deletions src/gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,24 @@ use std::{
time::{Duration, SystemTime},
};

use defguard_certs::{CertificateError, CertificateInfo};
use defguard_grpc_tls::{certs::server_tls_config, server::certificate_serial_interceptor};
use defguard_version::{
ComponentInfo, DefguardComponent, Version, get_tracing_variables, server::DefguardVersionLayer,
};
use defguard_wireguard_rs::{WireguardInterfaceApi, net::IpAddrMask};
use tokio::{
fs::remove_file,
sync::{mpsc, oneshot},
time::interval,
};
use tokio_stream::wrappers::UnboundedReceiverStream;
use tonic::{
Request, Response, Status, Streaming,
transport::{Identity, Server, ServerTlsConfig},
};
use tonic::{Request, Response, Status, Streaming, service::InterceptorLayer, transport::Server};
use tower::ServiceBuilder;
use tracing::instrument;

use crate::{
GRPC_CERT_NAME, GRPC_KEY_NAME, VERSION,
CORE_CLIENT_CERT_NAME, GRPC_CA_CERT_NAME, GRPC_CERT_NAME, GRPC_KEY_NAME, VERSION,
config::Config,
enterprise::firewall::{
FirewallConfig, FirewallError, FirewallRule, SnatBinding,
Expand Down Expand Up @@ -144,6 +144,10 @@ type PubKey = String;
pub struct TlsConfig {
pub grpc_cert_pem: String,
pub grpc_key_pem: String,
/// PEM-encoded CA certificate used to verify Core's mTLS client certificate chain.
pub grpc_ca_cert_pem: String,
/// DER-encoded Core client certificate; used to extract and pin the expected serial.
pub core_client_cert_der: Vec<u8>,
}

pub struct Gateway {
Expand Down Expand Up @@ -558,9 +562,11 @@ impl GatewayServer {
}

/// Starts the gateway process.
/// * Retrieves configuration and configuration updates from Defguard gRPC server
/// * Manages the interface according to configuration and updates
/// * Sends interface statistics to Defguard server periodically
/// * Requires a valid mTLS configuration to be set (via `set_tls_config`) before starting;
/// returns an error if TLS configuration is absent - the gRPC server never starts in plain-text mode
/// * Retrieves configuration and configuration updates from Defguard core via a mTLS-secured gRPC server
/// * Manages the WireGuard interface according to configuration and updates
/// * Sends interface statistics to Defguard core periodically
pub async fn start(self, config: Config) -> Result<(), GatewayError> {
info!("Starting Defguard Gateway version {VERSION} with configuration: {config:?}");

Expand Down Expand Up @@ -593,36 +599,42 @@ impl GatewayServer {
execute_command(post_up)?;
}

let grpc_cert = self
.gateway
.lock()
.unwrap()
.tls_config
.as_ref()
.map(|c| c.grpc_cert_pem.clone());
let grpc_key = self
let tls_config = self
.gateway
.lock()
.unwrap()
.expect("gateway mutex poison")
.tls_config
.as_ref()
.map(|c| c.grpc_key_pem.clone());
.clone();

// Build gRPC server.
let addr = config.grpc_socket();
info!("gRPC server is listening on {addr}");
let mut builder = if let (Some(cert), Some(key)) = (grpc_cert, grpc_key) {
let identity = Identity::from_pem(cert, key);
Server::builder().tls_config(ServerTlsConfig::new().identity(identity))?
} else {
Server::builder()
};

let tls = tls_config.ok_or_else(|| {
GatewayError::SetupError(
"TLS configuration is required; gateway gRPC server cannot start without mTLS"
.into(),
)
})?;

let tls_config =
server_tls_config(&tls.grpc_cert_pem, &tls.grpc_key_pem, &tls.grpc_ca_cert_pem)
.map_err(|e| GatewayError::SetupError(e.to_string()))?;
let mut builder = Server::builder().tls_config(tls_config)?;

// Extract Core client cert serial for pinning.
let expected_serial = CertificateInfo::from_der(&tls.core_client_cert_der)
.map_err(|e: CertificateError| GatewayError::SetupError(e.to_string()))?
.serial;

// Start gRPC server. This should run indefinitely.
debug!("Serving gRPC");
builder
.add_service(
ServiceBuilder::new()
.layer(InterceptorLayer::new(certificate_serial_interceptor(
expected_serial,
)))
.layer(DefguardVersionLayer::new(Version::parse(VERSION)?))
.service(gateway_server::GatewayServer::new(self)),
)
Expand Down Expand Up @@ -760,25 +772,30 @@ impl gateway_server::Gateway for GatewayServer {
debug!("Received purge request, removing gRPC certificate files");
let cert_path = self.cert_dir.join(GRPC_CERT_NAME);
let key_path = self.cert_dir.join(GRPC_KEY_NAME);
let ca_cert_path = self.cert_dir.join(GRPC_CA_CERT_NAME);
let core_client_cert_path = self.cert_dir.join(CORE_CLIENT_CERT_NAME);

let remove_cert_file = async |path: &std::path::Path, label: &str| -> Result<(), Status> {
match remove_file(path).await {
Ok(()) => {
info!("Removed {label} at {}", path.display());
Ok(())
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
debug!("{label} not found at {}, skipping removal", path.display());
Ok(())
}
Err(err) => {
error!("Failed to remove {label} at {}: {err}", path.display());
Err(Status::internal(format!("Failed to remove {label}")))
}
}
};

if let Err(err) = tokio::fs::remove_file(&cert_path).await
&& err.kind() != std::io::ErrorKind::NotFound
{
error!(
"Failed to remove gRPC certificate at {}: {err}",
cert_path.display()
);
return Err(Status::internal("Failed to remove gRPC certificate"));
}
info!("Removed gRPC certificate at {}", cert_path.display());

if let Err(err) = tokio::fs::remove_file(&key_path).await
&& err.kind() != std::io::ErrorKind::NotFound
{
error!("Failed to remove gRPC key at {}: {err}", key_path.display());
return Err(Status::internal("Failed to remove gRPC key"));
}
info!("Removed gRPC key at {}", cert_path.display());
remove_cert_file(&cert_path, "gRPC certificate").await?;
remove_cert_file(&key_path, "gRPC key").await?;
remove_cert_file(&ca_cert_path, "CA certificate").await?;
remove_cert_file(&core_client_cert_path, "Core client certificate").await?;

// Prepare underlying `Gateway` to enter setup mode.
self.gateway
Expand Down
17 changes: 17 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,27 @@ pub mod enterprise;
pub mod logging;
pub mod setup;

#[cfg(test)]
mod tests;

pub const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "+", env!("VERGEN_GIT_SHA"));

/// Install the `ring` CryptoProvider as the process-wide default for rustls.
///
/// Must be called once near process startup, before any TLS code runs. Both
/// `ring` and `aws-lc-rs` may be present as transitive dependencies; without
/// an explicit selection rustls panics at runtime. Subsequent calls are
/// silently ignored (`.ok()` swallows the `AlreadySet` error).
pub fn init_crypto_provider() {
rustls::crypto::ring::default_provider()
.install_default()
.ok();
}

pub const GRPC_CERT_NAME: &str = "gateway_grpc_cert.pem";
pub const GRPC_KEY_NAME: &str = "gateway_grpc_key.pem";
pub const GRPC_CA_CERT_NAME: &str = "grpc_ca_cert.pem";
pub const CORE_CLIENT_CERT_NAME: &str = "core_client_cert.pem";

/// Masks object's field with "***" string.
/// Used to log sensitive/secret objects.
Expand Down
Loading
Loading