From d52e57612942cbe0c6a0dd774fcc2caac8f439d5 Mon Sep 17 00:00:00 2001 From: Eric Hodel Date: Wed, 27 Dec 2023 06:15:48 -0800 Subject: feat: Add TLS to atuin-server (#1457) * Add TLS to atuin-server atuin as a project already includes most of the dependencies necessary for server-side TLS. This allows `atuin server start` to use a TLS certificate when self-hosting in order to avoid the complication of wrapping it in a TLS-aware proxy server. Configuration is handled similar to the metrics server with its own struct and currently accepts only the private key and certificate file paths. Starting a TLS server and a TCP server are divergent because the tests need to bind to an arbitrary port to avoid collisions across tests. The API to accomplish this for a TLS server is much more verbose. * Fix clippy, fmt * Add TLS section to self-hosting --- atuin-server/src/settings.rs | 54 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) (limited to 'atuin-server/src/settings.rs') diff --git a/atuin-server/src/settings.rs b/atuin-server/src/settings.rs index d6f1867c..70008fbc 100644 --- a/atuin-server/src/settings.rs +++ b/atuin-server/src/settings.rs @@ -1,7 +1,7 @@ use std::{io::prelude::*, path::PathBuf}; use config::{Config, Environment, File as ConfigFile, FileFormat}; -use eyre::{eyre, Result}; +use eyre::{bail, eyre, Context, Result}; use fs_err::{create_dir_all, File}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -36,6 +36,7 @@ pub struct Settings { pub register_webhook_url: Option, pub register_webhook_username: String, pub metrics: Metrics, + pub tls: Tls, #[serde(flatten)] pub db_settings: DbSettings, @@ -67,6 +68,9 @@ impl Settings { .set_default("metrics.enable", false)? .set_default("metrics.host", "127.0.0.1")? .set_default("metrics.port", 9001)? + .set_default("tls.enable", false)? + .set_default("tls.cert_path", "")? + .set_default("tls.key_path", "")? .add_source( Environment::with_prefix("atuin") .prefix_separator("_") @@ -97,3 +101,51 @@ impl Settings { pub fn example_config() -> &'static str { EXAMPLE_CONFIG } + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct Tls { + pub enable: bool, + pub cert_path: PathBuf, + pub pkey_path: PathBuf, +} + +impl Tls { + pub fn certificates(&self) -> Result> { + let cert_file = std::fs::File::open(&self.cert_path) + .with_context(|| format!("tls.cert_path {:?} is missing", self.cert_path))?; + let mut reader = std::io::BufReader::new(cert_file); + let certs: Vec<_> = rustls_pemfile::certs(&mut reader) + .with_context(|| format!("tls.cert_path {:?} is invalid", self.cert_path))? + .into_iter() + .map(rustls::Certificate) + .collect(); + + if certs.is_empty() { + bail!( + "tls.cert_path {:?} must have at least one certificate", + self.cert_path + ); + } + + Ok(certs) + } + + pub fn private_key(&self) -> Result { + let pkey_file = std::fs::File::open(&self.pkey_path) + .with_context(|| format!("tls.pkey_path {:?} is missing", self.pkey_path))?; + let mut reader = std::io::BufReader::new(pkey_file); + let keys = rustls_pemfile::pkcs8_private_keys(&mut reader) + .with_context(|| format!("tls.pkey_path {:?} is not PKCS8-encoded", self.pkey_path))?; + + if keys.is_empty() { + bail!( + "tls.pkey_path {:?} must have at least one private key", + self.pkey_path + ); + } + + let key = rustls::PrivateKey(keys[0].clone()); + + Ok(key) + } +} -- cgit v1.3.1