Allow client certificate CN to be used for upgrade path (#264)
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:
parent
a0ccd2622e
commit
88e42d3b9f
2 changed files with 42 additions and 2 deletions
|
@ -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 = [] }
|
||||||
|
|
43
src/main.rs
43
src/main.rs
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue