mirror of
https://github.com/neondatabase/neon.git
synced 2026-05-14 11:40:38 +00:00
Properly extract cert names
This commit is contained in:
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -1659,6 +1659,19 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.15"
|
||||
@@ -3129,6 +3142,7 @@ dependencies = [
|
||||
"consumption_metrics",
|
||||
"futures",
|
||||
"git-version",
|
||||
"globset",
|
||||
"hashbrown 0.13.2",
|
||||
"hashlink",
|
||||
"hex",
|
||||
|
||||
@@ -47,6 +47,7 @@ futures = "0.3"
|
||||
futures-core = "0.3"
|
||||
futures-util = "0.3"
|
||||
git-version = "0.3"
|
||||
globset = "0.4.10"
|
||||
hashbrown = "0.13"
|
||||
hashlink = "0.8.1"
|
||||
hex = "0.4"
|
||||
|
||||
@@ -16,6 +16,7 @@ clap.workspace = true
|
||||
consumption_metrics.workspace = true
|
||||
futures.workspace = true
|
||||
git-version.workspace = true
|
||||
globset.workspace = true
|
||||
hashbrown.workspace = true
|
||||
hashlink.workspace = true
|
||||
hex.workspace = true
|
||||
|
||||
@@ -4,15 +4,16 @@ use rustls::{
|
||||
};
|
||||
use std::{io, sync::Arc};
|
||||
|
||||
/// App-level configuration structs for TLS certificates.
|
||||
pub mod config {
|
||||
use super::*;
|
||||
use serde::Deserialize;
|
||||
use serde::{de, Deserialize};
|
||||
use std::path::Path;
|
||||
|
||||
/// Collection of TLS-related configurations of virtual proxy servers.
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct TlsServers(Vec<TlsServer>);
|
||||
pub struct TlsServers(pub Vec<TlsServer>);
|
||||
|
||||
impl TlsServers {
|
||||
/// Load [`Self`] config from a file.
|
||||
@@ -29,6 +30,7 @@ pub mod config {
|
||||
}
|
||||
}
|
||||
|
||||
/// Helps deserialize certificate chain from a string.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct TlsCert(
|
||||
@@ -42,9 +44,10 @@ pub mod config {
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let text = String::deserialize(des)?;
|
||||
parse_certs(&mut text.as_bytes()).map_err(serde::de::Error::custom)
|
||||
parse_certs(&mut text.as_bytes()).map_err(de::Error::custom)
|
||||
}
|
||||
|
||||
/// Helps deserialize private key from a string.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct TlsKey(
|
||||
@@ -58,17 +61,20 @@ pub mod config {
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let text = String::deserialize(des)?;
|
||||
parse_key(&mut text.as_bytes()).map_err(serde::de::Error::custom)
|
||||
parse_key(&mut text.as_bytes()).map_err(de::Error::custom)
|
||||
}
|
||||
|
||||
/// TODO: explain.
|
||||
/// Represents TLS config of a single virtual proxy server.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct TlsServer {
|
||||
/// Proxy server's certificate chain.
|
||||
pub certificate: TlsCert,
|
||||
/// Proxy server's private key.
|
||||
pub private_key: TlsKey,
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse TLS certificate chain from a byte buffer.
|
||||
fn parse_certs(buf: &mut impl io::BufRead) -> io::Result<Vec<rustls::Certificate>> {
|
||||
let chain = rustls_pemfile::certs(buf)?
|
||||
.into_iter()
|
||||
@@ -78,6 +84,7 @@ fn parse_certs(buf: &mut impl io::BufRead) -> io::Result<Vec<rustls::Certificate
|
||||
Ok(chain)
|
||||
}
|
||||
|
||||
/// Parse exactly one TLS private key from a byte buffer.
|
||||
fn parse_key(buf: &mut impl io::BufRead) -> io::Result<rustls::PrivateKey> {
|
||||
let mut keys = rustls_pemfile::pkcs8_private_keys(buf)?;
|
||||
|
||||
@@ -92,18 +99,68 @@ fn parse_key(buf: &mut impl io::BufRead) -> io::Result<rustls::PrivateKey> {
|
||||
Ok(rustls::PrivateKey(keys.pop().unwrap()))
|
||||
}
|
||||
|
||||
/// Extract domain names from a certificate: first CN, then SANs.
|
||||
/// Further reading: https://www.rfc-editor.org/rfc/rfc4985
|
||||
fn certificate_names(cert: &rustls::Certificate) -> anyhow::Result<Vec<String>> {
|
||||
use x509_parser::{extensions::GeneralName, x509::AttributeTypeAndValue};
|
||||
|
||||
let get_dns_name = |gn: &GeneralName| match gn {
|
||||
GeneralName::DNSName(name) => Some(name.to_string()),
|
||||
_other => None,
|
||||
};
|
||||
|
||||
let get_common_name = |attr: &AttributeTypeAndValue| {
|
||||
// There really shouldn't be anything but string here.
|
||||
attr.attr_value().as_string().expect("bad CN attribute")
|
||||
};
|
||||
|
||||
let (rest, cert) = x509_parser::parse_x509_certificate(cert.0.as_ref())?;
|
||||
anyhow::ensure!(rest.is_empty(), "excessive bytes in DER certificate");
|
||||
|
||||
// Extract CN, Common Name.
|
||||
let mut names: Vec<String> = cert
|
||||
.subject()
|
||||
.iter_common_name()
|
||||
.map(get_common_name)
|
||||
.collect();
|
||||
|
||||
// Now append SANs, Subject Alternative Names, if any.
|
||||
if let Some(extension) = cert.subject_alternative_name()? {
|
||||
let alt_names = &extension.value.general_names;
|
||||
names.extend(alt_names.iter().filter_map(get_dns_name));
|
||||
}
|
||||
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
struct CertResolverEntry {
|
||||
raw: Arc<rustls::sign::CertifiedKey>,
|
||||
names: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct CertResolver {
|
||||
resolver: rustls::server::ResolvesServerCertUsingSni,
|
||||
certs: Vec<CertResolverEntry>,
|
||||
}
|
||||
|
||||
impl CertResolver {
|
||||
pub fn new() -> Self {
|
||||
todo!("CertResolver ctor")
|
||||
pub fn new(config: config::TlsServers) -> anyhow::Result<Self> {
|
||||
let mut builder = globset::GlobSetBuilder::new();
|
||||
for server in config.0 {
|
||||
let Some(cert) = server.certificate.0.first() else {
|
||||
tracing::warn!("found empty certificate, skipping");
|
||||
continue;
|
||||
};
|
||||
|
||||
let names = certificate_names(cert)?;
|
||||
dbg!(names);
|
||||
}
|
||||
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolvesServerCert for CertResolver {
|
||||
fn resolve(&self, message: ClientHello) -> Option<Arc<CertifiedKey>> {
|
||||
self.resolver.resolve(message)
|
||||
todo!("implement")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ fn build_tls_config(args: &clap::ArgMatches) -> anyhow::Result<Option<TlsConfig>
|
||||
|
||||
let tls_config = args.get_one::<PathBuf>("tls-config");
|
||||
let main = tls_config.map(TlsServers::from_config_file).transpose()?;
|
||||
tracing::info!(?main, "config");
|
||||
certs::CertResolver::new(main.unwrap());
|
||||
|
||||
let tls_cert = args.get_one::<PathBuf>("tls-cert");
|
||||
let tls_key = args.get_one::<PathBuf>("tls-key");
|
||||
@@ -144,7 +144,7 @@ fn build_tls_config(args: &clap::ArgMatches) -> anyhow::Result<Option<TlsConfig>
|
||||
_ => bail!("either both or neither tls-key and tls-cert must be specified"),
|
||||
};
|
||||
|
||||
todo!()
|
||||
todo!("TlsConfig")
|
||||
}
|
||||
|
||||
fn build_metrics_config(args: &clap::ArgMatches) -> anyhow::Result<Option<MetricCollectionConfig>> {
|
||||
|
||||
Reference in New Issue
Block a user