Allow client certificate CN to be used for upgrade path ()

This change causes the wstunnel client to use the common name (CN) of the client's certificate for the upgrade path when mTLS is enabled.
This commit is contained in:
Jasper Siepkes 2024-05-06 09:00:08 +01:00 committed by GitHub
parent a0ccd2622e
commit 88e42d3b9f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 42 additions and 2 deletions

View file

@ -39,6 +39,7 @@ notify = { version = "6.1.1", features = [] }
rustls-native-certs = { version = "0.7.0", features = [] } rustls-native-certs = { version = "0.7.0", features = [] }
rustls-pemfile = { version = "2.1.1", features = [] } rustls-pemfile = { version = "2.1.1", features = [] }
x509-parser = "0.16.0"
scopeguard = "1.2.0" scopeguard = "1.2.0"
serde = { version = "1.0.197", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }
socket2 = { version = "0.5.6", features = [] } socket2 = { version = "0.5.6", features = [] }

View file

@ -48,6 +48,10 @@ use crate::udp::MyUdpSocket;
use tracing_subscriber::filter::Directive; use tracing_subscriber::filter::Directive;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use url::{Host, Url}; use url::{Host, Url};
use x509_parser::{parse_x509_certificate};
use x509_parser::prelude::{X509Certificate};
const DEFAULT_CLIENT_UPGRADE_PATH_PREFIX: &str = "v1";
/// Use Websocket or HTTP2 protocol to tunnel {TCP,UDP} traffic /// Use Websocket or HTTP2 protocol to tunnel {TCP,UDP} traffic
/// wsTunnelClient <---> wsTunnelServer <---> RemoteHost /// wsTunnelClient <---> wsTunnelServer <---> RemoteHost
@ -179,7 +183,7 @@ struct Client {
#[arg( #[arg(
short = 'P', short = 'P',
long, long,
default_value = "v1", default_value = DEFAULT_CLIENT_UPGRADE_PATH_PREFIX,
verbatim_doc_comment, verbatim_doc_comment,
env = "WSTUNNEL_HTTP_UPGRADE_PATH_PREFIX" env = "WSTUNNEL_HTTP_UPGRADE_PATH_PREFIX"
)] )]
@ -226,6 +230,7 @@ struct Client {
/// [Optional] Certificate (pem) to present to the server when connecting over TLS (HTTPS). /// [Optional] Certificate (pem) to present to the server when connecting over TLS (HTTPS).
/// Used when the server requires clients to authenticate themselves with a certificate (i.e. mTLS). /// Used when the server requires clients to authenticate themselves with a certificate (i.e. mTLS).
/// Unless overridden, the HTTP upgrade path will be configured to be the common name (CN) of the certificate.
/// The certificate will be automatically reloaded if it changes /// The certificate will be automatically reloaded if it changes
#[arg(long, value_name = "FILE_PATH", verbatim_doc_comment)] #[arg(long, value_name = "FILE_PATH", verbatim_doc_comment)]
tls_certificate: Option<PathBuf>, tls_certificate: Option<PathBuf>,
@ -594,6 +599,28 @@ fn parse_server_url(arg: &str) -> Result<Url, io::Error> {
Ok(url) Ok(url)
} }
/// Find a leaf certificate in a vector of certificates. It is assumed only a single leaf certificate
/// is present in the vector. The other certificates should be (intermediate) CA certificates.
fn find_leaf_certificate(tls_certificates: &Vec<Certificate>) -> Option<X509Certificate> {
for tls_certificate in tls_certificates {
if let Ok((_, tls_certificate_x509)) = parse_x509_certificate(&tls_certificate.0) {
if !tls_certificate_x509.is_ca() {
return Some(tls_certificate_x509);
}
}
}
None
}
/// Returns the common name (CN) as specified in the supplied certificate.
fn cn_from_certificate(tls_certificate_x509: &X509Certificate) -> Option<String> {
tls_certificate_x509.tbs_certificate.subject
.iter_common_name()
.flat_map(|cn| cn.as_str().ok())
.map(|cn| cn.to_string())
.next()
}
#[derive(Clone)] #[derive(Clone)]
pub struct TlsClientConfig { pub struct TlsClientConfig {
pub tls_sni_disabled: bool, pub tls_sni_disabled: bool,
@ -744,6 +771,18 @@ async fn main() {
(None, None) (None, None)
}; };
let http_upgrade_path_prefix = if args.http_upgrade_path_prefix.eq(DEFAULT_CLIENT_UPGRADE_PATH_PREFIX) {
// When using mTLS and no manual http upgrade path is specified configure the HTTP upgrade path
// to be the common name (CN) of the client's certificate.
tls_certificate.as_ref()
.and_then(|tls_certs| find_leaf_certificate(tls_certs))
.and_then(|leaf_cert| cn_from_certificate(&leaf_cert))
.unwrap_or(args.http_upgrade_path_prefix)
} else {
args.http_upgrade_path_prefix
};
println!("{}", http_upgrade_path_prefix);
let transport_scheme = let transport_scheme =
TransportScheme::from_str(args.remote_addr.scheme()).expect("invalid scheme in server url"); TransportScheme::from_str(args.remote_addr.scheme()).expect("invalid scheme in server url");
let tls = match transport_scheme { let tls = match transport_scheme {
@ -808,7 +847,7 @@ async fn main() {
) )
.unwrap(), .unwrap(),
socket_so_mark: args.socket_so_mark, socket_so_mark: args.socket_so_mark,
http_upgrade_path_prefix: args.http_upgrade_path_prefix, http_upgrade_path_prefix,
http_upgrade_credentials: args.http_upgrade_credentials, http_upgrade_credentials: args.http_upgrade_credentials,
http_headers: args.http_headers.into_iter().filter(|(k, _)| k != HOST).collect(), http_headers: args.http_headers.into_iter().filter(|(k, _)| k != HOST).collect(),
http_headers_file: args.http_headers_file, http_headers_file: args.http_headers_file,