mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
Certificate Imports/Exports (#687)
* allows for root/int certificate imports, certain invariants are not checked at this time * refactor ssl module to only import root certificate * implements certificate export, propagates import functionality all the way to NetController::init
This commit is contained in:
committed by
Aiden McClelland
parent
0f9c20f5f8
commit
f7465da797
@@ -156,6 +156,7 @@ impl RpcContext {
|
||||
base.tor_control
|
||||
.unwrap_or(SocketAddr::from(([127, 0, 0, 1], 9051))),
|
||||
secret_store.clone(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let managers = ManagerMap::default();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use openssl::x509::X509;
|
||||
use rpc_toolkit::command;
|
||||
use sqlx::SqlitePool;
|
||||
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
|
||||
@@ -10,6 +12,7 @@ use self::interface::{Interface, InterfaceId};
|
||||
#[cfg(feature = "avahi")]
|
||||
use self::mdns::MdnsController;
|
||||
use self::nginx::NginxController;
|
||||
use self::ssl::SslManager;
|
||||
use self::tor::TorController;
|
||||
use crate::net::interface::TorConfig;
|
||||
use crate::net::nginx::InterfaceMetadata;
|
||||
@@ -42,12 +45,17 @@ impl NetController {
|
||||
embassyd_tor_key: TorSecretKeyV3,
|
||||
tor_control: SocketAddr,
|
||||
db: SqlitePool,
|
||||
import_root_ca: Option<(PKey<Private>, X509)>,
|
||||
) -> Result<Self, Error> {
|
||||
let ssl_manager = match import_root_ca {
|
||||
None => SslManager::init(db).await,
|
||||
Some(a) => SslManager::import_root_ca(db, a.0, a.1).await,
|
||||
}?;
|
||||
Ok(Self {
|
||||
tor: TorController::init(embassyd_addr, embassyd_tor_key, tor_control).await?,
|
||||
#[cfg(feature = "avahi")]
|
||||
mdns: MdnsController::init(),
|
||||
nginx: NginxController::init(PathBuf::from("/etc/nginx"), db).await?,
|
||||
nginx: NginxController::init(PathBuf::from("/etc/nginx"), ssl_manager).await?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -129,4 +137,8 @@ impl NetController {
|
||||
nginx_res?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn export_root_ca(&self) -> Result<(PKey<Private>, X509), Error> {
|
||||
self.nginx.ssl_manager.export_root_ca().await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,14 @@ use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
pub struct NginxController {
|
||||
nginx_root: PathBuf,
|
||||
pub ssl_manager: SslManager,
|
||||
inner: Mutex<NginxControllerInner>,
|
||||
}
|
||||
impl NginxController {
|
||||
pub async fn init(nginx_root: PathBuf, db: SqlitePool) -> Result<Self, Error> {
|
||||
pub async fn init(nginx_root: PathBuf, ssl_manager: SslManager) -> Result<Self, Error> {
|
||||
Ok(NginxController {
|
||||
inner: Mutex::new(NginxControllerInner::init(&nginx_root, db).await?),
|
||||
inner: Mutex::new(NginxControllerInner::init(&nginx_root, &ssl_manager).await?),
|
||||
ssl_manager,
|
||||
nginx_root,
|
||||
})
|
||||
}
|
||||
@@ -39,7 +41,13 @@ impl NginxController {
|
||||
self.inner
|
||||
.lock()
|
||||
.await
|
||||
.add(&self.nginx_root, package, ipv4, interfaces)
|
||||
.add(
|
||||
&self.nginx_root,
|
||||
&self.ssl_manager,
|
||||
package,
|
||||
ipv4,
|
||||
interfaces,
|
||||
)
|
||||
.await
|
||||
}
|
||||
pub async fn remove(&self, package: &PackageId) -> Result<(), Error> {
|
||||
@@ -53,22 +61,17 @@ impl NginxController {
|
||||
|
||||
pub struct NginxControllerInner {
|
||||
interfaces: BTreeMap<PackageId, PackageNetInfo>,
|
||||
ssl_manager: SslManager,
|
||||
}
|
||||
impl NginxControllerInner {
|
||||
#[instrument(skip(db))]
|
||||
async fn init(nginx_root: &Path, db: SqlitePool) -> Result<Self, Error> {
|
||||
#[instrument]
|
||||
async fn init(nginx_root: &Path, ssl_manager: &SslManager) -> Result<Self, Error> {
|
||||
let inner = NginxControllerInner {
|
||||
interfaces: BTreeMap::new(),
|
||||
ssl_manager: SslManager::init(db).await?,
|
||||
};
|
||||
let (key, cert) = inner
|
||||
.ssl_manager
|
||||
.certificate_for(&get_hostname().await?)
|
||||
.await?;
|
||||
let (key, cert) = ssl_manager.certificate_for(&get_hostname().await?).await?;
|
||||
let ssl_path_key = nginx_root.join(format!("ssl/embassy_main.key.pem"));
|
||||
let ssl_path_cert = nginx_root.join(format!("ssl/embassy_main.cert.pem"));
|
||||
futures::try_join!(
|
||||
tokio::try_join!(
|
||||
tokio::fs::write(&ssl_path_key, key.private_key_to_pem_pkcs8()?),
|
||||
tokio::fs::write(
|
||||
&ssl_path_cert,
|
||||
@@ -83,6 +86,7 @@ impl NginxControllerInner {
|
||||
async fn add<I: IntoIterator<Item = (InterfaceId, InterfaceMetadata)>>(
|
||||
&mut self,
|
||||
nginx_root: &Path,
|
||||
ssl_manager: &SslManager,
|
||||
package: PackageId,
|
||||
ipv4: Ipv4Addr,
|
||||
interfaces: I,
|
||||
@@ -108,9 +112,9 @@ impl NginxControllerInner {
|
||||
nginx_root.join(format!("ssl/{}/{}.key.pem", package, id));
|
||||
let ssl_path_cert =
|
||||
nginx_root.join(format!("ssl/{}/{}.cert.pem", package, id));
|
||||
let (key, chain) = self.ssl_manager.certificate_for(&meta.dns_base).await?;
|
||||
let (key, chain) = ssl_manager.certificate_for(&meta.dns_base).await?;
|
||||
// write nginx ssl certs
|
||||
futures::try_join!(
|
||||
tokio::try_join!(
|
||||
tokio::fs::write(&ssl_path_key, key.private_key_to_pem_pkcs8()?).map(
|
||||
|res| res.with_ctx(|_| (
|
||||
ErrorKind::Filesystem,
|
||||
@@ -197,7 +201,7 @@ impl NginxControllerInner {
|
||||
nginx_root.join(format!("sites-enabled/{}_{}.conf", package, id));
|
||||
let available_path =
|
||||
nginx_root.join(format!("sites-available/{}_{}.conf", package, id));
|
||||
let _ = futures::try_join!(
|
||||
let _ = tokio::try_join!(
|
||||
async {
|
||||
if tokio::fs::metadata(&package_path).await.is_ok() {
|
||||
tokio::fs::remove_dir_all(&package_path)
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::{Error, ErrorKind};
|
||||
|
||||
static CERTIFICATE_VERSION: i32 = 2; // X509 version 3 is actually encoded as '2' in the cert because fuck you.
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SslManager {
|
||||
store: SslStore,
|
||||
root_cert: X509,
|
||||
@@ -24,6 +25,7 @@ pub struct SslManager {
|
||||
int_cert: X509,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SslStore {
|
||||
secret_store: SqlitePool,
|
||||
}
|
||||
@@ -79,6 +81,19 @@ impl SslStore {
|
||||
}
|
||||
}
|
||||
#[instrument(skip(self))]
|
||||
async fn import_root_certificate(
|
||||
&self,
|
||||
root_key: &PKey<Private>,
|
||||
root_cert: &X509,
|
||||
) -> Result<(), Error> {
|
||||
// remove records for both root and intermediate CA
|
||||
sqlx::query!("DELETE FROM certificates WHERE id = 0 OR id = 1;")
|
||||
.execute(&self.secret_store)
|
||||
.await?;
|
||||
self.save_root_certificate(root_key, root_cert).await?;
|
||||
Ok(())
|
||||
}
|
||||
#[instrument(skip(self))]
|
||||
async fn save_certificate(
|
||||
&self,
|
||||
key: &PKey<Private>,
|
||||
@@ -170,6 +185,45 @@ impl SslManager {
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: currently the burden of proof is on the caller to ensure that all of the arguments to this function are
|
||||
// consistent. The following properties are assumed and not verified:
|
||||
// 1. `root_cert` is self-signed and contains the public key that matches the private key `root_key`
|
||||
// 2. certificate is not past its expiration date
|
||||
// Warning: If this function ever fails, you must either call it again or regenerate your certificates from scratch
|
||||
// since it is possible for it to fail after successfully saving the root certificate but before successfully saving
|
||||
// the intermediate certificate
|
||||
#[instrument(skip(db))]
|
||||
pub async fn import_root_ca(
|
||||
db: SqlitePool,
|
||||
root_key: PKey<Private>,
|
||||
root_cert: X509,
|
||||
) -> Result<Self, Error> {
|
||||
let store = SslStore::new(db)?;
|
||||
store.import_root_certificate(&root_key, &root_cert).await?;
|
||||
let int_key = generate_key()?;
|
||||
let int_cert = make_int_cert((&root_key, &root_cert), &int_key)?;
|
||||
store
|
||||
.save_intermediate_certificate(&int_key, &int_cert)
|
||||
.await?;
|
||||
Ok(SslManager {
|
||||
store,
|
||||
root_cert,
|
||||
int_key,
|
||||
int_cert,
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
pub async fn export_root_ca(&self) -> Result<(PKey<Private>, X509), Error> {
|
||||
match self.store.load_root_certificate().await? {
|
||||
None => Err(Error::new(
|
||||
eyre!("Failed to export root certificate: root certificate has not been generated"),
|
||||
ErrorKind::OpenSsl,
|
||||
)),
|
||||
Some(a) => Ok(a),
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
pub async fn certificate_for(
|
||||
&self,
|
||||
|
||||
Reference in New Issue
Block a user