Skip to content

Commit 09879af

Browse files
authored
Save certificates before completing setup, test write access (#280)
* confirm write permissions, save cert before completing wizard * bump
1 parent 447db8b commit 09879af

2 files changed

Lines changed: 138 additions & 79 deletions

File tree

src/http.rs

Lines changed: 23 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ use clap::crate_version;
2424
use defguard_version::{Version, server::DefguardVersionLayer};
2525
use serde::Serialize;
2626
use tokio::{
27-
fs::OpenOptions,
28-
io::AsyncWriteExt,
2927
sync::{broadcast, mpsc, oneshot},
3028
task::JoinSet,
3129
};
@@ -53,10 +51,7 @@ const DEFGUARD_CORE_VERSION_HEADER: &str = "defguard-core-version";
5351
const RATE_LIMITER_CLEANUP_PERIOD: Duration = Duration::from_secs(60);
5452
const X_FORWARDED_FOR: &str = "x-forwarded-for";
5553
const X_POWERED_BY: &str = "x-powered-by";
56-
pub const GRPC_CERT_NAME: &str = "proxy_grpc_cert.pem";
57-
pub const GRPC_KEY_NAME: &str = "proxy_grpc_key.pem";
58-
pub const GRPC_CA_CERT_NAME: &str = "grpc_ca_cert.pem";
59-
pub const CORE_CLIENT_CERT_NAME: &str = "core_client_cert.pem";
54+
pub use crate::setup::{CORE_CLIENT_CERT_NAME, GRPC_CA_CERT_NAME, GRPC_CERT_NAME, GRPC_KEY_NAME};
6055

6156
#[derive(Clone)]
6257
pub(crate) struct AppState {
@@ -181,7 +176,6 @@ async fn powered_by_header<B>(mut response: Response<B>) -> Response<B> {
181176
}
182177

183178
pub async fn run_setup(env_config: &EnvConfig, logs_rx: LogsReceiver) -> anyhow::Result<TlsConfig> {
184-
let setup_server = ProxySetupServer::new(logs_rx);
185179
let cert_dir = Path::new(&env_config.cert_dir);
186180
if !cert_dir.exists() {
187181
tokio::fs::create_dir_all(cert_dir).await.map_err(|err| {
@@ -196,13 +190,34 @@ pub async fn run_setup(env_config: &EnvConfig, logs_rx: LogsReceiver) -> anyhow:
196190
})?;
197191
#[cfg(unix)]
198192
tokio::fs::set_permissions(cert_dir, Permissions::from_mode(0o700)).await?;
193+
} else {
194+
// verify write access before starting the setup server
195+
let test_path = cert_dir.join(".write_test");
196+
match tokio::fs::OpenOptions::new()
197+
.write(true)
198+
.create(true)
199+
.truncate(true)
200+
.open(&test_path)
201+
.await
202+
{
203+
Ok(_) => {
204+
let _ = tokio::fs::remove_file(&test_path).await;
205+
}
206+
Err(err) if err.kind() == ErrorKind::PermissionDenied => {
207+
anyhow::bail!(
208+
"Certificate directory {} is not writable. Permission denied.",
209+
cert_dir.display()
210+
);
211+
}
212+
Err(err) => return Err(err.into()),
213+
}
199214
}
200215

201-
// Only attempt setup if not already configured
202216
info!(
203217
"No gRPC TLS certificates found at {}, new certificates will be obtained during setup",
204218
cert_dir.display()
205219
);
220+
let setup_server = ProxySetupServer::new(logs_rx, cert_dir.to_path_buf());
206221
let tls_config = setup_server
207222
.await_initial_setup(
208223
SocketAddr::new(
@@ -216,75 +231,6 @@ pub async fn run_setup(env_config: &EnvConfig, logs_rx: LogsReceiver) -> anyhow:
216231
.await?;
217232
info!("Generated new gRPC TLS certificates and signed by Defguard Core");
218233

219-
let TlsConfig {
220-
grpc_cert_pem,
221-
grpc_key_pem,
222-
grpc_ca_cert_pem,
223-
core_client_cert_der,
224-
} = &tls_config;
225-
226-
let cert_path = cert_dir.join(GRPC_CERT_NAME);
227-
let key_path = cert_dir.join(GRPC_KEY_NAME);
228-
// Certificate and its key will be accessed only to this process's user.
229-
let mut options = OpenOptions::new();
230-
options.write(true).create(true).truncate(true);
231-
#[cfg(unix)]
232-
options.mode(0o600); // rw-------
233-
234-
// Write certificate to a file.
235-
options
236-
.clone()
237-
.open(&cert_path)
238-
.await?
239-
.write_all(grpc_cert_pem.as_bytes())
240-
.await.map_err(|err| {
241-
if err.kind() == ErrorKind::PermissionDenied {
242-
anyhow::anyhow!(
243-
"Cannot write certificate file {}. Permission denied for certificate directory {}.",
244-
cert_path.display(),
245-
cert_dir.display()
246-
)
247-
} else {
248-
err.into()
249-
}
250-
})?;
251-
// Write key to a file.
252-
options
253-
.clone()
254-
.open(&key_path)
255-
.await?
256-
.write_all(grpc_key_pem.as_bytes())
257-
.await
258-
.map_err(|err| {
259-
if err.kind() == ErrorKind::PermissionDenied {
260-
anyhow::anyhow!(
261-
"Cannot write key file {}. Permission denied for certificate directory {}.",
262-
key_path.display(),
263-
cert_dir.display()
264-
)
265-
} else {
266-
err.into()
267-
}
268-
})?;
269-
// Write CA certificate to a file.
270-
options
271-
.clone()
272-
.open(cert_dir.join(GRPC_CA_CERT_NAME))
273-
.await?
274-
.write_all(grpc_ca_cert_pem.as_bytes())
275-
.await?;
276-
// Write Core client certificate (PEM-encoded) to a file for serial pinning on restart.
277-
let core_client_cert_pem =
278-
defguard_certs::der_to_pem(core_client_cert_der, defguard_certs::PemLabel::Certificate)
279-
.map_err(|err| {
280-
anyhow::anyhow!("Failed to PEM-encode Core client certificate: {err}")
281-
})?;
282-
options
283-
.open(cert_dir.join(CORE_CLIENT_CERT_NAME))
284-
.await?
285-
.write_all(core_client_cert_pem.as_bytes())
286-
.await?;
287-
288234
Ok(tls_config)
289235
}
290236

src/setup.rs

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{
22
net::SocketAddr,
3+
path::{Path, PathBuf},
34
sync::{
45
Arc, Mutex,
56
atomic::{AtomicBool, Ordering},
@@ -12,7 +13,11 @@ use defguard_version::{
1213
server::{DefguardVersionLayer, grpc::DefguardVersionInterceptor},
1314
};
1415
use rustls_pki_types::{CertificateDer, UnixTime};
15-
use tokio::sync::{mpsc, oneshot};
16+
use tokio::{
17+
fs::OpenOptions,
18+
io::AsyncWriteExt,
19+
sync::{mpsc, oneshot},
20+
};
1621
use tokio_stream::wrappers::UnboundedReceiverStream;
1722
use tonic::{Request, Response, Status, service::InterceptorLayer, transport::Server};
1823
use webpki::{KeyUsage, anchor_from_trusted_cert};
@@ -27,6 +32,11 @@ use crate::{
2732

2833
const AUTH_HEADER: &str = "authorization";
2934

35+
pub const GRPC_CERT_NAME: &str = "proxy_grpc_cert.pem";
36+
pub const GRPC_KEY_NAME: &str = "proxy_grpc_key.pem";
37+
pub const GRPC_CA_CERT_NAME: &str = "grpc_ca_cert.pem";
38+
pub const CORE_CLIENT_CERT_NAME: &str = "core_client_cert.pem";
39+
3040
/// Verify that both `component_der` and `core_client_der` are signed by `ca_der`.
3141
///
3242
/// Uses ECDSA P-256 via `aws-lc-rs` (Linux-only deployment; FIPS-capable).
@@ -88,13 +98,104 @@ fn validate_cert_bundle(
8898
Ok(())
8999
}
90100

101+
async fn save_tls_certs(tls_config: &TlsConfig, cert_dir: &Path) -> Result<(), String> {
102+
let cert_path = cert_dir.join(GRPC_CERT_NAME);
103+
let key_path = cert_dir.join(GRPC_KEY_NAME);
104+
let ca_cert_path = cert_dir.join(GRPC_CA_CERT_NAME);
105+
let client_cert_path = cert_dir.join(CORE_CLIENT_CERT_NAME);
106+
107+
let mut options = OpenOptions::new();
108+
options.write(true).create(true).truncate(true);
109+
#[cfg(unix)]
110+
options.mode(0o600); // rw-------
111+
112+
// PEM-encode the Core client certificate DER for serial pinning on restart.
113+
let core_client_cert_pem = defguard_certs::der_to_pem(
114+
&tls_config.core_client_cert_der,
115+
defguard_certs::PemLabel::Certificate,
116+
)
117+
.map_err(|err| format!("Failed to PEM-encode Core client certificate: {err}"))?;
118+
119+
// Write component (server) certificate.
120+
options
121+
.clone()
122+
.open(&cert_path)
123+
.await
124+
.map_err(|err| {
125+
format!(
126+
"Cannot open certificate file {}: {err}",
127+
cert_path.display()
128+
)
129+
})?
130+
.write_all(tls_config.grpc_cert_pem.as_bytes())
131+
.await
132+
.map_err(|err| {
133+
format!(
134+
"Cannot write certificate file {}: {err}",
135+
cert_path.display()
136+
)
137+
})?;
138+
139+
// Write private key.
140+
options
141+
.clone()
142+
.open(&key_path)
143+
.await
144+
.map_err(|err| format!("Cannot open key file {}: {err}", key_path.display()))?
145+
.write_all(tls_config.grpc_key_pem.as_bytes())
146+
.await
147+
.map_err(|err| format!("Cannot write key file {}: {err}", key_path.display()))?;
148+
149+
// Write CA certificate.
150+
options
151+
.clone()
152+
.open(&ca_cert_path)
153+
.await
154+
.map_err(|err| {
155+
format!(
156+
"Cannot open CA certificate file {}: {err}",
157+
ca_cert_path.display()
158+
)
159+
})?
160+
.write_all(tls_config.grpc_ca_cert_pem.as_bytes())
161+
.await
162+
.map_err(|err| {
163+
format!(
164+
"Cannot write CA certificate file {}: {err}",
165+
ca_cert_path.display()
166+
)
167+
})?;
168+
169+
// Write Core client certificate (PEM) for serial pinning on restart.
170+
options
171+
.open(&client_cert_path)
172+
.await
173+
.map_err(|err| {
174+
format!(
175+
"Cannot open Core client certificate file {}: {err}",
176+
client_cert_path.display()
177+
)
178+
})?
179+
.write_all(core_client_cert_pem.as_bytes())
180+
.await
181+
.map_err(|err| {
182+
format!(
183+
"Cannot write Core client certificate file {}: {err}",
184+
client_cert_path.display()
185+
)
186+
})?;
187+
188+
Ok(())
189+
}
190+
91191
pub(crate) struct ProxySetupServer {
92192
key_pair: Arc<Mutex<Option<defguard_certs::RcGenKeyPair>>>,
93193
logs_rx: LogsReceiver,
94194
current_session_token: Arc<Mutex<Option<String>>>,
95195
setup_tx: Arc<tokio::sync::Mutex<Option<oneshot::Sender<TlsConfig>>>>,
96196
setup_rx: Arc<tokio::sync::Mutex<oneshot::Receiver<TlsConfig>>>,
97197
adoption_expired: Arc<AtomicBool>,
198+
cert_dir: Arc<PathBuf>,
98199
}
99200

100201
impl Clone for ProxySetupServer {
@@ -106,12 +207,13 @@ impl Clone for ProxySetupServer {
106207
setup_tx: Arc::clone(&self.setup_tx),
107208
setup_rx: Arc::clone(&self.setup_rx),
108209
adoption_expired: Arc::clone(&self.adoption_expired),
210+
cert_dir: Arc::clone(&self.cert_dir),
109211
}
110212
}
111213
}
112214

113215
impl ProxySetupServer {
114-
pub fn new(logs_rx: LogsReceiver) -> Self {
216+
pub fn new(logs_rx: LogsReceiver, cert_dir: PathBuf) -> Self {
115217
let (setup_tx, setup_rx) = oneshot::channel();
116218
Self {
117219
key_pair: Arc::new(Mutex::new(None)),
@@ -120,6 +222,7 @@ impl ProxySetupServer {
120222
setup_tx: Arc::new(tokio::sync::Mutex::new(Some(setup_tx))),
121223
setup_rx: Arc::new(tokio::sync::Mutex::new(setup_rx)),
122224
adoption_expired: Arc::new(AtomicBool::new(false)),
225+
cert_dir: Arc::new(cert_dir),
123226
}
124227
}
125228

@@ -490,6 +593,16 @@ impl proxy_setup_server::ProxySetup for ProxySetupServer {
490593
core_client_cert_der: bundle.core_client_cert_der,
491594
};
492595

596+
debug!("Saving TLS certificate files to disk");
597+
if let Err(err) = save_tls_certs(&configuration, &self.cert_dir).await {
598+
error!("Failed to save TLS certificates: {err}");
599+
self.clear_setup_session();
600+
return Err(Status::internal(format!(
601+
"Failed to save TLS certificates: {err}"
602+
)));
603+
}
604+
debug!("TLS certificate files saved successfully");
605+
493606
debug!("Passing configuration to gRPC server for finalization");
494607
let Some(sender) = self.setup_tx.lock().await.take() else {
495608
error!("Setup channel sender already consumed");

0 commit comments

Comments
 (0)