Skip to content

Commit eeff92e

Browse files
authored
Implement proxy wizard (#233)
* send logs from proxy * proxy wizard * update proto * fix audit * fix linter errors
1 parent 7490067 commit eeff92e

File tree

13 files changed

+728
-583
lines changed

13 files changed

+728
-583
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ base64 = "0.22"
5353
tower = "0.5"
5454
futures-util = "0.3"
5555
ammonia = "4.1.1"
56+
chrono = "0.4"
5657

5758
[build-dependencies]
5859
tonic-prost-build = "0.14"

proto

src/config.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ fn default_url() -> Url {
1111

1212
#[derive(Parser, Debug, Deserialize)]
1313
#[command(version)]
14-
pub struct Config {
14+
pub struct EnvConfig {
1515
// port the API server will listen on
1616
#[arg(
1717
long,
@@ -77,15 +77,15 @@ pub enum ConfigError {
7777
ParseError(#[from] toml::de::Error),
7878
}
7979

80-
pub fn get_config() -> Result<Config, ConfigError> {
80+
pub fn get_env_config() -> Result<EnvConfig, ConfigError> {
8181
// parse CLI arguments to get config file path
82-
let cli_config = Config::parse();
82+
let cli_config = EnvConfig::parse();
8383

8484
// load config from file if one was specified
8585
if let Some(config_path) = cli_config.config_path {
8686
info!("Reading configuration from file: {config_path:?}");
8787
let config_toml = read_to_string(config_path)?;
88-
let file_config: Config = toml::from_str(&config_toml)?;
88+
let file_config: EnvConfig = toml::from_str(&config_toml)?;
8989
Ok(file_config)
9090
} else {
9191
Ok(cli_config)

src/grpc.rs

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ use tracing::Instrument;
2525

2626
use crate::{
2727
error::ApiError,
28-
http::GRPC_SERVER_RESTART_CHANNEL,
2928
proto::{core_request, core_response, proxy_server, CoreRequest, CoreResponse, DeviceInfo},
3029
MIN_CORE_VERSION, VERSION,
3130
};
@@ -34,9 +33,9 @@ use crate::{
3433
type ClientMap = HashMap<SocketAddr, mpsc::UnboundedSender<Result<CoreRequest, Status>>>;
3534

3635
#[derive(Debug, Clone, Default)]
37-
pub(crate) struct Configuration {
38-
pub(crate) grpc_key_pem: String,
39-
pub(crate) grpc_cert_pem: String,
36+
pub struct Configuration {
37+
pub grpc_key_pem: String,
38+
pub grpc_cert_pem: String,
4039
}
4140

4241
pub(crate) struct ProxyServer {
@@ -47,7 +46,6 @@ pub(crate) struct ProxyServer {
4746
pub(crate) core_version: Arc<Mutex<Option<Version>>>,
4847
config: Arc<Mutex<Option<Configuration>>>,
4948
cookie_key: Arc<RwLock<Option<Key>>>,
50-
setup_in_progress: Arc<AtomicBool>,
5149
}
5250

5351
impl ProxyServer {
@@ -62,21 +60,9 @@ impl ProxyServer {
6260
connected: Arc::new(AtomicBool::new(false)),
6361
core_version: Arc::new(Mutex::new(None)),
6462
config: Arc::new(Mutex::new(None)),
65-
setup_in_progress: Arc::new(AtomicBool::new(false)),
6663
}
6764
}
6865

69-
pub(crate) fn set_tls_config(&self, cert_pem: String, key_pem: String) -> Result<(), ApiError> {
70-
let mut lock = self
71-
.config
72-
.lock()
73-
.expect("Failed to acquire lock on config mutex when updating TLS configuration");
74-
let config = lock.get_or_insert_with(Configuration::default);
75-
config.grpc_cert_pem = cert_pem;
76-
config.grpc_key_pem = key_pem;
77-
Ok(())
78-
}
79-
8066
pub(crate) fn configure(&self, config: Configuration) {
8167
let mut lock = self
8268
.config
@@ -121,11 +107,7 @@ impl ProxyServer {
121107

122108
builder
123109
.add_service(versioned_service)
124-
.serve_with_shutdown(addr, async move {
125-
let mut rx_lock = GRPC_SERVER_RESTART_CHANNEL.1.lock().await;
126-
rx_lock.recv().await;
127-
info!("Shutting down gRPC server for restart...");
128-
})
110+
.serve(addr)
129111
.await
130112
.map_err(|err| {
131113
error!("gRPC server error: {err}");
@@ -194,7 +176,6 @@ impl Clone for ProxyServer {
194176
core_version: Arc::clone(&self.core_version),
195177
cookie_key: Arc::clone(&self.cookie_key),
196178
config: Arc::clone(&self.config),
197-
setup_in_progress: Arc::clone(&self.setup_in_progress),
198179
}
199180
}
200181
}

src/http.rs

Lines changed: 58 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
use std::{
22
collections::HashMap,
3-
fs::read_to_string,
43
net::{IpAddr, Ipv4Addr, SocketAddr},
54
path::Path,
6-
sync::{atomic::Ordering, Arc, LazyLock, RwLock},
5+
sync::{atomic::Ordering, Arc, RwLock},
76
time::Duration,
87
};
98

@@ -21,11 +20,7 @@ use axum_extra::extract::cookie::Key;
2120
use clap::crate_version;
2221
use defguard_version::{server::DefguardVersionLayer, Version};
2322
use serde::Serialize;
24-
use tokio::{
25-
net::TcpListener,
26-
sync::{oneshot, Mutex},
27-
task::JoinSet,
28-
};
23+
use tokio::{net::TcpListener, sync::oneshot, task::JoinSet};
2924
use tower_governor::{
3025
governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor, GovernorLayer,
3126
};
@@ -35,13 +30,13 @@ use url::Url;
3530

3631
use crate::{
3732
assets::{index, web_asset},
38-
config::Config,
33+
config::EnvConfig,
3934
enterprise::handlers::openid_login::{self, FlowType},
4035
error::ApiError,
4136
grpc::{Configuration, ProxyServer},
4237
handlers::{desktop_client_mfa, enrollment, password_reset, polling},
4338
setup::ProxySetupServer,
44-
CommsChannel, VERSION,
39+
LogsReceiver, VERSION,
4540
};
4641

4742
pub(crate) static ENROLLMENT_COOKIE_NAME: &str = "defguard_proxy";
@@ -51,13 +46,8 @@ const DEFGUARD_CORE_VERSION_HEADER: &str = "defguard-core-version";
5146
const RATE_LIMITER_CLEANUP_PERIOD: Duration = Duration::from_secs(60);
5247
const X_FORWARDED_FOR: &str = "x-forwarded-for";
5348
const X_POWERED_BY: &str = "x-powered-by";
54-
const GRPC_CERT_NAME: &str = "proxy_grpc_cert.pem";
55-
const GRPC_KEY_NAME: &str = "proxy_grpc_key.pem";
56-
57-
pub static GRPC_SERVER_RESTART_CHANNEL: LazyLock<CommsChannel<()>> = LazyLock::new(|| {
58-
let (tx, rx) = tokio::sync::mpsc::channel(100);
59-
(Arc::new(Mutex::new(tx)), Arc::new(Mutex::new(rx)))
60-
});
49+
pub const GRPC_CERT_NAME: &str = "proxy_grpc_cert.pem";
50+
pub const GRPC_KEY_NAME: &str = "proxy_grpc_key.pem";
6151

6252
#[derive(Clone)]
6353
pub(crate) struct AppState {
@@ -177,6 +167,45 @@ async fn powered_by_header<B>(mut response: Response<B>) -> Response<B> {
177167
response
178168
}
179169

170+
pub async fn run_setup(
171+
env_config: &EnvConfig,
172+
logs_rx: LogsReceiver,
173+
) -> anyhow::Result<Configuration> {
174+
let setup_server = ProxySetupServer::new(logs_rx);
175+
let cert_dir = Path::new(&env_config.cert_dir);
176+
if !cert_dir.exists() {
177+
tokio::fs::create_dir_all(cert_dir).await?;
178+
}
179+
180+
// Only attempt setup if not already configured
181+
info!(
182+
"No gRPC TLS certificates found at {}, new certificates will be obtained during setup",
183+
cert_dir.display()
184+
);
185+
let configuration = setup_server
186+
.await_initial_setup(SocketAddr::new(
187+
env_config
188+
.grpc_bind_address
189+
.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)),
190+
env_config.grpc_port,
191+
))
192+
.await?;
193+
info!("Generated new gRPC TLS certificates and signed by Defguard Core");
194+
195+
let Configuration {
196+
grpc_cert_pem,
197+
grpc_key_pem,
198+
..
199+
} = &configuration;
200+
201+
let cert_path = cert_dir.join(GRPC_CERT_NAME);
202+
let key_path = cert_dir.join(GRPC_KEY_NAME);
203+
tokio::fs::write(&cert_path, grpc_cert_pem).await?;
204+
tokio::fs::write(&key_path, grpc_key_pem).await?;
205+
206+
Ok(configuration)
207+
}
208+
180209
/// Middleware that gates all HTTP endpoints except health checks until the proxy
181210
/// is fully configured.
182211
///
@@ -206,9 +235,9 @@ async fn ensure_configured(
206235
next.run(request).await
207236
}
208237

209-
pub async fn run_server(config: Config) -> anyhow::Result<()> {
238+
pub async fn run_server(env_config: EnvConfig, config: Configuration) -> anyhow::Result<()> {
210239
info!("Starting Defguard Proxy server");
211-
debug!("Using config: {config:?}");
240+
debug!("Using config: {env_config:?}");
212241

213242
let mut tasks = JoinSet::new();
214243
let cookie_key = Default::default();
@@ -217,68 +246,20 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> {
217246
let grpc_server = ProxyServer::new(Arc::clone(&cookie_key));
218247

219248
let server_clone = grpc_server.clone();
220-
221-
let setup_server = ProxySetupServer::new();
249+
grpc_server.configure(config);
222250

223251
// Start gRPC server.
224252
// TODO: Wait with spawning the HTTP server until gRPC server is ready.
225253
debug!("Spawning gRPC server");
226254
tasks.spawn(async move {
227-
let cert_dir = Path::new(&config.cert_dir);
228-
if !cert_dir.exists() {
229-
debug!("Creating certs directory");
230-
tokio::fs::create_dir_all(cert_dir).await?;
231-
}
232-
233255
loop {
256+
info!("Starting gRPC server...");
234257
let server_to_run = server_clone.clone();
235-
236-
if let (Some(cert), Some(key)) = (
237-
read_to_string(cert_dir.join(GRPC_CERT_NAME)).ok(),
238-
read_to_string(cert_dir.join(GRPC_KEY_NAME)).ok(),
239-
) {
240-
info!(
241-
"Using existing gRPC TLS certificates from {}",
242-
cert_dir.display()
243-
);
244-
server_clone.set_tls_config(cert, key)?;
245-
} else if !server_clone.setup_completed() {
246-
// Only attempt setup if not already configured
247-
info!(
248-
"No gRPC TLS certificates found at {}, new certificates will be generated",
249-
cert_dir.display()
250-
);
251-
let configuration = setup_server
252-
.await_setup(SocketAddr::new(
253-
config
254-
.grpc_bind_address
255-
.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)),
256-
config.grpc_port,
257-
))
258-
.await?;
259-
info!("Generated new gRPC TLS certificates and signed by Defguard Core");
260-
261-
let Configuration {
262-
grpc_cert_pem,
263-
grpc_key_pem,
264-
..
265-
} = &configuration;
266-
267-
let cert_path = cert_dir.join(GRPC_CERT_NAME);
268-
let key_path = cert_dir.join(GRPC_KEY_NAME);
269-
tokio::fs::write(&cert_path, grpc_cert_pem).await?;
270-
tokio::fs::write(&key_path, grpc_key_pem).await?;
271-
272-
server_to_run.configure(configuration);
273-
} else {
274-
info!("Proxy already configured, skipping setup phase");
275-
}
276-
277258
let addr = SocketAddr::new(
278-
config
259+
env_config
279260
.grpc_bind_address
280261
.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)),
281-
config.grpc_port,
262+
env_config.grpc_port,
282263
);
283264

284265
if let Err(e) = server_to_run.run(addr).await {
@@ -293,18 +274,18 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> {
293274
cookie_key,
294275
grpc_server,
295276
remote_mfa_sessions: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
296-
url: config.url.clone(),
277+
url: env_config.url.clone(),
297278
};
298279

299280
// Setup tower_governor rate-limiter
300281
debug!(
301282
"Configuring rate limiter, per_second: {}, burst: {}",
302-
config.rate_limit_per_second, config.rate_limit_burst
283+
env_config.rate_limit_per_second, env_config.rate_limit_burst
303284
);
304285
let governor_conf = GovernorConfigBuilder::default()
305286
.key_extractor(SmartIpKeyExtractor)
306-
.per_second(config.rate_limit_per_second)
307-
.burst_size(config.rate_limit_burst)
287+
.per_second(env_config.rate_limit_per_second)
288+
.burst_size(env_config.rate_limit_burst)
308289
.finish();
309290

310291
let governor_conf = if let Some(conf) = governor_conf {
@@ -323,7 +304,7 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> {
323304
});
324305
info!(
325306
"Configured rate limiter, per_second: {}, burst: {}",
326-
config.rate_limit_per_second, config.rate_limit_burst
307+
env_config.rate_limit_per_second, env_config.rate_limit_burst
327308
);
328309
Some(conf)
329310
} else {
@@ -385,10 +366,10 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> {
385366
debug!("Spawning API web server");
386367
tasks.spawn(async move {
387368
let addr = SocketAddr::new(
388-
config
369+
env_config
389370
.http_bind_address
390371
.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)),
391-
config.http_port,
372+
env_config.http_port,
392373
);
393374
let listener = TcpListener::bind(&addr).await?;
394375
info!("API web server is listening on {addr}");

src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ use std::sync::Arc;
33
use defguard_version::Version;
44
use tokio::sync::mpsc;
55

6+
use crate::proto::LogEntry;
7+
68
pub mod assets;
79
pub mod config;
810
mod enterprise;
911
mod error;
10-
mod grpc;
12+
pub mod grpc;
1113
mod handlers;
1214
pub mod http;
1315
pub mod logging;
@@ -27,3 +29,5 @@ type CommsChannel<T> = (
2729
Arc<tokio::sync::Mutex<mpsc::Sender<T>>>,
2830
Arc<tokio::sync::Mutex<mpsc::Receiver<T>>>,
2931
);
32+
33+
type LogsReceiver = Arc<tokio::sync::Mutex<mpsc::Receiver<LogEntry>>>;

0 commit comments

Comments
 (0)