mirror of
https://github.com/warp-tech/warpgate.git
synced 2024-09-20 06:46:17 +08:00
cleaned up TLS code, added certificate and key checks to warpgate check
This commit is contained in:
parent
be98a00bb2
commit
03db7b55fa
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -4391,7 +4391,6 @@ dependencies = [
|
|||
"warpgate-common",
|
||||
"warpgate-db-entities",
|
||||
"warpgate-protocol-ssh",
|
||||
"warpgate-web",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4414,6 +4413,8 @@ dependencies = [
|
|||
"rand",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"sea-orm",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -4427,6 +4428,7 @@ dependencies = [
|
|||
"uuid",
|
||||
"warpgate-db-entities",
|
||||
"warpgate-db-migrations",
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4501,7 +4503,6 @@ dependencies = [
|
|||
"anyhow",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"delegate",
|
||||
"mysql_common",
|
||||
"once_cell",
|
||||
"password-hash 0.2.3",
|
||||
|
@ -4515,7 +4516,6 @@ dependencies = [
|
|||
"tokio-rustls",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"warpgate-admin",
|
||||
"warpgate-common",
|
||||
"warpgate-database-protocols",
|
||||
"warpgate-db-entities",
|
||||
|
|
3
justfile
3
justfile
|
@ -31,3 +31,6 @@ openapi:
|
|||
cd warpgate-web && yarn openapi:client:admin && yarn openapi:client:gateway
|
||||
|
||||
cleanup: (fix "--allow-dirty") (clippy "--fix" "--allow-dirty") fmt svelte-check lint
|
||||
|
||||
udeps:
|
||||
cargo udeps --all-targets
|
||||
|
|
|
@ -40,5 +40,4 @@ uuid = { version = "1.0", features = ["v4", "serde"] }
|
|||
warpgate-common = { version = "*", path = "../warpgate-common" }
|
||||
warpgate-db-entities = { version = "*", path = "../warpgate-db-entities" }
|
||||
warpgate-protocol-ssh = { version = "*", path = "../warpgate-protocol-ssh" }
|
||||
warpgate-web = { version = "*", path = "../warpgate-web" }
|
||||
regex = "1.6"
|
||||
|
|
|
@ -9,7 +9,7 @@ anyhow = "1.0"
|
|||
argon2 = "0.4"
|
||||
async-trait = "0.1"
|
||||
bytes = "1.2"
|
||||
chrono = {version = "0.4", features = ["serde"]}
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
data-encoding = "2.3"
|
||||
humantime-serde = "1.1"
|
||||
lazy_static = "1.4"
|
||||
|
@ -17,20 +17,32 @@ once_cell = "1.10"
|
|||
packet = "0.1"
|
||||
password-hash = "0.4"
|
||||
poem = "^1.3.30"
|
||||
poem-openapi = {version = "2.0.4", features = ["swagger-ui", "chrono", "uuid", "static-files"]}
|
||||
poem-openapi = { version = "2.0.4", features = [
|
||||
"swagger-ui",
|
||||
"chrono",
|
||||
"uuid",
|
||||
"static-files",
|
||||
] }
|
||||
rand = "0.8"
|
||||
rand_chacha = "0.3"
|
||||
rand_core = {version = "0.6", features = ["std"]}
|
||||
sea-orm = {version = "^0.9", features = ["sqlx-sqlite", "runtime-tokio-native-tls", "macros"], default-features = false}
|
||||
rand_core = { version = "0.6", features = ["std"] }
|
||||
sea-orm = { version = "^0.9", features = [
|
||||
"sqlx-sqlite",
|
||||
"runtime-tokio-native-tls",
|
||||
"macros",
|
||||
], default-features = false }
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
thiserror = "1.0"
|
||||
tokio = {version = "1.20", features = ["tracing"]}
|
||||
totp-rs = {version = "2.0", features = ["otpauth"]}
|
||||
tokio = { version = "1.20", features = ["tracing"] }
|
||||
totp-rs = { version = "2.0", features = ["otpauth"] }
|
||||
tracing = "0.1"
|
||||
tracing-core = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
url = "2.2"
|
||||
uuid = {version = "1.0", features = ["v4", "serde"]}
|
||||
warpgate-db-entities = {version = "*", path = "../warpgate-db-entities"}
|
||||
warpgate-db-migrations = {version = "*", path = "../warpgate-db-migrations"}
|
||||
uuid = { version = "1.0", features = ["v4", "serde"] }
|
||||
warpgate-db-entities = { version = "*", path = "../warpgate-db-entities" }
|
||||
warpgate-db-migrations = { version = "*", path = "../warpgate-db-migrations" }
|
||||
rustls = { version = "0.20", features = ["dangerous_configuration"] }
|
||||
rustls-pemfile = "1.0"
|
||||
webpki = "0.22"
|
||||
|
|
|
@ -230,7 +230,6 @@ fn _default_ssh_keys_path() -> String {
|
|||
"./data/keys".to_owned()
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq, Eq, Copy)]
|
||||
pub enum SshHostKeyVerificationMode {
|
||||
#[serde(rename = "prompt")]
|
||||
|
|
|
@ -13,6 +13,7 @@ mod protocols;
|
|||
pub mod recordings;
|
||||
mod services;
|
||||
mod state;
|
||||
mod tls;
|
||||
mod try_macro;
|
||||
mod types;
|
||||
|
||||
|
@ -23,5 +24,6 @@ pub use error::WarpgateError;
|
|||
pub use protocols::*;
|
||||
pub use services::*;
|
||||
pub use state::{SessionState, SessionStateInit, State};
|
||||
pub use tls::*;
|
||||
pub use try_macro::*;
|
||||
pub use types::*;
|
||||
|
|
|
@ -13,11 +13,11 @@ pub enum TargetTestError {
|
|||
Unreachable,
|
||||
#[error("authentication failed")]
|
||||
AuthenticationError,
|
||||
#[error("connection error")]
|
||||
#[error("connection error: {0}")]
|
||||
ConnectionError(String),
|
||||
#[error("misconfigured")]
|
||||
#[error("misconfigured: {0}")]
|
||||
Misconfigured(String),
|
||||
#[error("I/O")]
|
||||
#[error("I/O: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
|
|
|
@ -19,13 +19,13 @@ use writer::RecordingWriter;
|
|||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("I/O")]
|
||||
#[error("I/O: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("Database")]
|
||||
#[error("Database: {0}")]
|
||||
Database(#[from] sea_orm::DbErr),
|
||||
|
||||
#[error("Failed to serialize a recording item")]
|
||||
#[error("Failed to serialize a recording item: {0}")]
|
||||
Serialization(#[from] serde_json::Error),
|
||||
|
||||
#[error("Writer is closed")]
|
||||
|
|
|
@ -4,8 +4,7 @@ use tokio::time::Instant;
|
|||
use warpgate_db_entities::Recording::RecordingKind;
|
||||
|
||||
use super::writer::RecordingWriter;
|
||||
use super::Recorder;
|
||||
use super::{Result, Error};
|
||||
use super::{Error, Recorder, Result};
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(untagged)]
|
||||
|
|
111
warpgate-common/src/tls/cert.rs
Normal file
111
warpgate-common/src/tls/cert.rs
Normal file
|
@ -0,0 +1,111 @@
|
|||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use poem::listener::RustlsCertificate;
|
||||
use rustls::sign::{CertifiedKey, SigningKey};
|
||||
use rustls::{Certificate, PrivateKey};
|
||||
use tokio::fs::File;
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
use crate::RustlsSetupError;
|
||||
|
||||
pub struct TlsCertificateBundle {
|
||||
bytes: Vec<u8>,
|
||||
certificates: Vec<Certificate>,
|
||||
}
|
||||
|
||||
pub struct TlsPrivateKey {
|
||||
bytes: Vec<u8>,
|
||||
key: Arc<dyn SigningKey>,
|
||||
}
|
||||
|
||||
pub struct TlsCertificateAndPrivateKey {
|
||||
pub certificate: TlsCertificateBundle,
|
||||
pub private_key: TlsPrivateKey,
|
||||
}
|
||||
|
||||
impl TlsCertificateBundle {
|
||||
pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, RustlsSetupError> {
|
||||
let mut file = File::open(path).await?;
|
||||
let mut bytes = Vec::new();
|
||||
file.read_to_end(&mut bytes).await?;
|
||||
Self::from_bytes(bytes)
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, RustlsSetupError> {
|
||||
let certificates = rustls_pemfile::certs(&mut &bytes[..]).map(|mut certs| {
|
||||
certs
|
||||
.drain(..)
|
||||
.map(Certificate)
|
||||
.collect::<Vec<Certificate>>()
|
||||
})?;
|
||||
if certificates.is_empty() {
|
||||
return Err(RustlsSetupError::NoCertificates)
|
||||
}
|
||||
Ok(Self {
|
||||
bytes,
|
||||
certificates,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TlsPrivateKey {
|
||||
pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, RustlsSetupError> {
|
||||
let mut file = File::open(path).await?;
|
||||
let mut bytes = Vec::new();
|
||||
file.read_to_end(&mut bytes).await?;
|
||||
Self::from_bytes(bytes)
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, RustlsSetupError> {
|
||||
let mut key = rustls_pemfile::pkcs8_private_keys(&mut bytes.as_slice())?
|
||||
.drain(..)
|
||||
.next()
|
||||
.map(PrivateKey);
|
||||
|
||||
if key.is_none() {
|
||||
key = rustls_pemfile::rsa_private_keys(&mut bytes.as_slice())?
|
||||
.drain(..)
|
||||
.next()
|
||||
.map(PrivateKey);
|
||||
}
|
||||
|
||||
let key = key.ok_or(RustlsSetupError::NoKeys)?;
|
||||
let key = rustls::sign::any_supported_type(&key)?;
|
||||
|
||||
Ok(Self { bytes, key })
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for TlsCertificateBundle {
|
||||
fn into(self) -> Vec<u8> {
|
||||
self.bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for TlsPrivateKey {
|
||||
fn into(self) -> Vec<u8> {
|
||||
self.bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<RustlsCertificate> for TlsCertificateAndPrivateKey {
|
||||
fn into(self) -> RustlsCertificate {
|
||||
RustlsCertificate::new()
|
||||
.cert(self.certificate)
|
||||
.key(self.private_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<CertifiedKey> for TlsCertificateAndPrivateKey {
|
||||
fn into(self) -> CertifiedKey {
|
||||
let cert = self.certificate;
|
||||
let key = self.private_key;
|
||||
CertifiedKey {
|
||||
cert: cert.certificates,
|
||||
key: key.key,
|
||||
ocsp: None,
|
||||
sct_list: None,
|
||||
}
|
||||
}
|
||||
}
|
15
warpgate-common/src/tls/error.rs
Normal file
15
warpgate-common/src/tls/error.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum RustlsSetupError {
|
||||
#[error("rustls: {0}")]
|
||||
Rustls(#[from] rustls::Error),
|
||||
#[error("sign: {0}")]
|
||||
Sign(#[from] rustls::sign::SignError),
|
||||
#[error("no certificates found in certificate file")]
|
||||
NoCertificates,
|
||||
#[error("no private keys found in key file")]
|
||||
NoKeys,
|
||||
#[error("I/O: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("PKI: {0}")]
|
||||
Pki(#[from] webpki::Error),
|
||||
}
|
5
warpgate-common/src/tls/mod.rs
Normal file
5
warpgate-common/src/tls/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod cert;
|
||||
mod error;
|
||||
|
||||
pub use cert::*;
|
||||
pub use error::*;
|
4
warpgate-common/src/types/aliases.rs
Normal file
4
warpgate-common/src/types/aliases.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
use uuid::Uuid;
|
||||
|
||||
pub type SessionId = Uuid;
|
||||
pub type ProtocolName = &'static str;
|
|
@ -2,72 +2,7 @@ use std::fmt::Debug;
|
|||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
use std::ops::Deref;
|
||||
|
||||
use bytes::Bytes;
|
||||
use data_encoding::HEXLOWER;
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::helpers::rng::get_crypto_rng;
|
||||
|
||||
pub type SessionId = Uuid;
|
||||
pub type ProtocolName = &'static str;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
pub struct Secret<T>(T);
|
||||
|
||||
impl Secret<String> {
|
||||
pub fn random() -> Self {
|
||||
Secret::new(HEXLOWER.encode(&Bytes::from_iter(get_crypto_rng().gen::<[u8; 32]>())))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Secret<T> {
|
||||
pub const fn new(v: T) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
|
||||
pub fn expose_secret(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Secret<T> {
|
||||
fn from(v: T) -> Self {
|
||||
Self::new(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> Deserialize<'de> for Secret<T>
|
||||
where
|
||||
T: Deserialize<'de>,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let v = Deserialize::deserialize::<D>(deserializer)?;
|
||||
Ok(Self::new(v))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Serialize for Secret<T>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.0.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for Secret<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "<secret>")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ListenEndpoint(pub SocketAddr);
|
7
warpgate-common/src/types/mod.rs
Normal file
7
warpgate-common/src/types/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
mod aliases;
|
||||
mod listen_endpoint;
|
||||
mod secret;
|
||||
|
||||
pub use aliases::*;
|
||||
pub use listen_endpoint::*;
|
||||
pub use secret::*;
|
64
warpgate-common/src/types/secret.rs
Normal file
64
warpgate-common/src/types/secret.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use bytes::Bytes;
|
||||
use data_encoding::HEXLOWER;
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::helpers::rng::get_crypto_rng;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
pub struct Secret<T>(T);
|
||||
|
||||
impl Secret<String> {
|
||||
pub fn random() -> Self {
|
||||
Secret::new(HEXLOWER.encode(&Bytes::from_iter(get_crypto_rng().gen::<[u8; 32]>())))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Secret<T> {
|
||||
pub const fn new(v: T) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
|
||||
pub fn expose_secret(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Secret<T> {
|
||||
fn from(v: T) -> Self {
|
||||
Self::new(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> Deserialize<'de> for Secret<T>
|
||||
where
|
||||
T: Deserialize<'de>,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let v = Deserialize::deserialize::<D>(deserializer)?;
|
||||
Ok(Self::new(v))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Serialize for Secret<T>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.0.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for Secret<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "<secret>")
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ use common::page_admin_auth;
|
|||
pub use common::PROTOCOL_NAME;
|
||||
use logging::{log_request_result, span_for_request};
|
||||
use poem::endpoint::{EmbeddedFileEndpoint, EmbeddedFilesEndpoint};
|
||||
use poem::listener::{Listener, RustlsCertificate, RustlsConfig, TcpListener};
|
||||
use poem::listener::{Listener, RustlsConfig, TcpListener};
|
||||
use poem::middleware::SetHeader;
|
||||
use poem::session::{CookieConfig, MemoryStorage, ServerSession};
|
||||
use poem::web::Data;
|
||||
|
@ -28,7 +28,7 @@ use poem_openapi::OpenApiService;
|
|||
use tokio::sync::Mutex;
|
||||
use tracing::*;
|
||||
use warpgate_admin::admin_api_app;
|
||||
use warpgate_common::{ProtocolServer, Services, Target, TargetTestError};
|
||||
use warpgate_common::{ProtocolServer, Services, Target, TargetTestError, TlsCertificateAndPrivateKey, TlsCertificateBundle, TlsPrivateKey};
|
||||
use warpgate_web::Assets;
|
||||
|
||||
use crate::common::{endpoint_admin_auth, endpoint_auth, page_auth, COOKIE_MAX_AGE};
|
||||
|
@ -136,29 +136,29 @@ impl ProtocolServer for HTTPProtocolServer {
|
|||
}
|
||||
});
|
||||
|
||||
let (certificate, key) = {
|
||||
let certificate_and_key = {
|
||||
let config = self.services.config.lock().await;
|
||||
let certificate_path = config
|
||||
.paths_relative_to
|
||||
.join(&config.store.http.certificate);
|
||||
let key_path = config.paths_relative_to.join(&config.store.http.key);
|
||||
|
||||
(
|
||||
std::fs::read(&certificate_path).with_context(|| {
|
||||
TlsCertificateAndPrivateKey {
|
||||
certificate: TlsCertificateBundle::from_file(&certificate_path).await.with_context(|| {
|
||||
format!("reading TLS private key from '{}'", key_path.display())
|
||||
})?,
|
||||
private_key: TlsPrivateKey::from_file(&key_path).await.with_context(|| {
|
||||
format!(
|
||||
"reading SSL certificate from '{}'",
|
||||
"reading TLS certificate from '{}'",
|
||||
certificate_path.display()
|
||||
)
|
||||
})?,
|
||||
std::fs::read(&key_path).with_context(|| {
|
||||
format!("reading SSL private key from '{}'", key_path.display())
|
||||
})?,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
info!(?address, "Listening");
|
||||
Server::new(TcpListener::bind(address).rustls(
|
||||
RustlsConfig::new().fallback(RustlsCertificate::new().cert(certificate).key(key)),
|
||||
RustlsConfig::new().fallback(certificate_and_key.into()),
|
||||
))
|
||||
.run(app)
|
||||
.await?;
|
||||
|
|
|
@ -5,7 +5,6 @@ name = "warpgate-protocol-mysql"
|
|||
version = "0.3.0"
|
||||
|
||||
[dependencies]
|
||||
warpgate-admin = { version = "*", path = "../warpgate-admin" }
|
||||
warpgate-common = { version = "*", path = "../warpgate-common" }
|
||||
warpgate-db-entities = { version = "*", path = "../warpgate-db-entities" }
|
||||
warpgate-database-protocols = { version = "*", path = "../warpgate-database-protocols" }
|
||||
|
@ -19,7 +18,6 @@ mysql_common = "0.29"
|
|||
rand = "0.8"
|
||||
sha1 = "0.10.1"
|
||||
password-hash = { version = "0.2", features = ["std"] }
|
||||
delegate = "0.6"
|
||||
rustls = { version = "0.20", features = ["dangerous_configuration"] }
|
||||
rustls-pemfile = "1.0"
|
||||
tokio-rustls = "0.23"
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use std::error::Error;
|
||||
|
||||
use warpgate_common::WarpgateError;
|
||||
use warpgate_common::{RustlsSetupError, WarpgateError};
|
||||
use warpgate_database_protocols::error::Error as SqlxError;
|
||||
|
||||
use crate::stream::MySqlStreamError;
|
||||
use crate::tls::{MaybeTlsStreamError, RustlsSetupError};
|
||||
use crate::tls::MaybeTlsStreamError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum MySqlError {
|
||||
|
|
|
@ -8,17 +8,22 @@ mod stream;
|
|||
mod tls;
|
||||
use std::fmt::Debug;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use rustls::server::NoClientAuth;
|
||||
use rustls::ServerConfig;
|
||||
use tokio::net::TcpListener;
|
||||
use tracing::*;
|
||||
use warpgate_common::{ProtocolServer, Services, SessionStateInit, Target, TargetTestError};
|
||||
use warpgate_common::{
|
||||
ProtocolServer, Services, SessionStateInit, Target, TargetTestError,
|
||||
TlsCertificateAndPrivateKey, TlsCertificateBundle, TlsPrivateKey,
|
||||
};
|
||||
|
||||
use crate::session::MySqlSession;
|
||||
use crate::session_handle::MySqlSessionHandle;
|
||||
use crate::tls::FromCertificateAndKey;
|
||||
use crate::tls::ResolveServerCert;
|
||||
|
||||
pub struct MySQLProtocolServer {
|
||||
services: Services,
|
||||
|
@ -35,27 +40,34 @@ impl MySQLProtocolServer {
|
|||
#[async_trait]
|
||||
impl ProtocolServer for MySQLProtocolServer {
|
||||
async fn run(self, address: SocketAddr) -> Result<()> {
|
||||
let (certificate, key) = {
|
||||
let certificate_and_key = {
|
||||
let config = self.services.config.lock().await;
|
||||
let certificate_path = config
|
||||
.paths_relative_to
|
||||
.join(&config.store.mysql.certificate);
|
||||
let key_path = config.paths_relative_to.join(&config.store.mysql.key);
|
||||
|
||||
(
|
||||
std::fs::read(&certificate_path).with_context(|| {
|
||||
TlsCertificateAndPrivateKey {
|
||||
certificate: TlsCertificateBundle::from_file(&certificate_path)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("reading SSL private key from '{}'", key_path.display())
|
||||
})?,
|
||||
private_key: TlsPrivateKey::from_file(&key_path).await.with_context(|| {
|
||||
format!(
|
||||
"reading SSL certificate from '{}'",
|
||||
certificate_path.display()
|
||||
)
|
||||
})?,
|
||||
std::fs::read(&key_path).with_context(|| {
|
||||
format!("reading SSL private key from '{}'", key_path.display())
|
||||
})?,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let tls_config = ServerConfig::try_from_certificate_and_key(certificate, key)?;
|
||||
let tls_config = ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_client_cert_verifier(NoClientAuth::new())
|
||||
.with_cert_resolver(Arc::new(ResolveServerCert(Arc::new(
|
||||
certificate_and_key.into(),
|
||||
))));
|
||||
|
||||
info!(?address, "Listening");
|
||||
let listener = TcpListener::bind(address).await?;
|
||||
|
|
|
@ -3,5 +3,5 @@ mod rustls_helpers;
|
|||
mod rustls_root_certs;
|
||||
|
||||
pub use maybe_tls_stream::{MaybeTlsStream, MaybeTlsStreamError, UpgradableStream};
|
||||
pub use rustls_helpers::{configure_tls_connector, FromCertificateAndKey, RustlsSetupError};
|
||||
pub use rustls_helpers::{configure_tls_connector, ResolveServerCert};
|
||||
pub use rustls_root_certs::ROOT_CERT_STORE;
|
||||
|
|
|
@ -3,75 +3,14 @@ use std::sync::Arc;
|
|||
use std::time::SystemTime;
|
||||
|
||||
use rustls::client::{ServerCertVerified, ServerCertVerifier, WebPkiVerifier};
|
||||
use rustls::server::{ClientHello, NoClientAuth, ResolvesServerCert};
|
||||
use rustls::server::{ClientHello, ResolvesServerCert};
|
||||
use rustls::sign::CertifiedKey;
|
||||
use rustls::{Certificate, ClientConfig, Error as TlsError, PrivateKey, ServerConfig, ServerName};
|
||||
use rustls::{ClientConfig, Error as TlsError, ServerName};
|
||||
use warpgate_common::RustlsSetupError;
|
||||
|
||||
use super::ROOT_CERT_STORE;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum RustlsSetupError {
|
||||
#[error("rustls")]
|
||||
Rustls(#[from] rustls::Error),
|
||||
#[error("sign")]
|
||||
Sign(#[from] rustls::sign::SignError),
|
||||
#[error("no private keys in key file")]
|
||||
NoKeys,
|
||||
#[error("I/O")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("PKI")]
|
||||
Pki(#[from] webpki::Error),
|
||||
}
|
||||
|
||||
pub trait FromCertificateAndKey<E>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn try_from_certificate_and_key(cert: Vec<u8>, key: Vec<u8>) -> Result<Self, E>;
|
||||
}
|
||||
|
||||
impl FromCertificateAndKey<RustlsSetupError> for rustls::ServerConfig {
|
||||
fn try_from_certificate_and_key(
|
||||
cert: Vec<u8>,
|
||||
key_bytes: Vec<u8>,
|
||||
) -> Result<Self, RustlsSetupError> {
|
||||
let certificates = rustls_pemfile::certs(&mut &cert[..]).map(|mut certs| {
|
||||
certs
|
||||
.drain(..)
|
||||
.map(Certificate)
|
||||
.collect::<Vec<Certificate>>()
|
||||
})?;
|
||||
|
||||
let mut key = rustls_pemfile::pkcs8_private_keys(&mut key_bytes.as_slice())?
|
||||
.drain(..)
|
||||
.next()
|
||||
.map(PrivateKey);
|
||||
|
||||
if key.is_none() {
|
||||
key = rustls_pemfile::rsa_private_keys(&mut key_bytes.as_slice())?
|
||||
.drain(..)
|
||||
.next()
|
||||
.map(PrivateKey);
|
||||
}
|
||||
|
||||
let key = key.ok_or(RustlsSetupError::NoKeys)?;
|
||||
let key = rustls::sign::any_supported_type(&key)?;
|
||||
|
||||
let cert_key = Arc::new(CertifiedKey {
|
||||
cert: certificates,
|
||||
key,
|
||||
ocsp: None,
|
||||
sct_list: None,
|
||||
});
|
||||
|
||||
Ok(ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_client_cert_verifier(NoClientAuth::new())
|
||||
.with_cert_resolver(Arc::new(ResolveServerCert(cert_key))))
|
||||
}
|
||||
}
|
||||
|
||||
struct ResolveServerCert(Arc<CertifiedKey>);
|
||||
pub struct ResolveServerCert(pub Arc<CertifiedKey>);
|
||||
|
||||
impl ResolvesServerCert for ResolveServerCert {
|
||||
fn resolve(&self, _: ClientHello) -> Option<Arc<CertifiedKey>> {
|
||||
|
@ -117,7 +56,7 @@ pub async fn configure_tls_connector(
|
|||
Ok(config)
|
||||
}
|
||||
|
||||
struct DummyTlsVerifier;
|
||||
pub struct DummyTlsVerifier;
|
||||
|
||||
impl ServerCertVerifier for DummyTlsVerifier {
|
||||
fn verify_server_cert(
|
||||
|
|
|
@ -7,7 +7,9 @@ pub static ROOT_CERT_STORE: Lazy<RootCertStore> = Lazy::new(|| {
|
|||
for cert in
|
||||
rustls_native_certs::load_native_certs().expect("could not load root TLS certificates")
|
||||
{
|
||||
roots.add(&rustls::Certificate(cert.0)).expect("could not add root TLS certificate");
|
||||
roots
|
||||
.add(&rustls::Certificate(cert.0))
|
||||
.expect("could not add root TLS certificate");
|
||||
}
|
||||
roots
|
||||
});
|
||||
|
|
|
@ -1,10 +1,35 @@
|
|||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use tracing::*;
|
||||
use warpgate_common::{TlsCertificateBundle, TlsPrivateKey};
|
||||
|
||||
use crate::config::load_config;
|
||||
|
||||
pub(crate) async fn command(cli: &crate::Cli) -> Result<()> {
|
||||
load_config(&cli.config, true)?;
|
||||
let config = load_config(&cli.config, true)?;
|
||||
if config.store.http.enable {
|
||||
TlsCertificateBundle::from_file(
|
||||
config
|
||||
.paths_relative_to
|
||||
.join(&config.store.http.certificate),
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("Checking HTTPS certificate"))?;
|
||||
TlsPrivateKey::from_file(config.paths_relative_to.join(&config.store.http.key))
|
||||
.await
|
||||
.with_context(|| format!("Checking HTTPS key"))?;
|
||||
}
|
||||
if config.store.mysql.enable {
|
||||
TlsCertificateBundle::from_file(
|
||||
config
|
||||
.paths_relative_to
|
||||
.join(&config.store.mysql.certificate),
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("Checking MySQL certificate"))?;
|
||||
TlsPrivateKey::from_file(config.paths_relative_to.join(&config.store.mysql.key))
|
||||
.await
|
||||
.with_context(|| format!("Checking MySQL key"))?;
|
||||
}
|
||||
info!("No problems found");
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue