From 873fcc4215c027ba995f9e6247a74c1d6bc93459 Mon Sep 17 00:00:00 2001 From: Paulo Bressan Date: Wed, 21 Feb 2024 11:30:50 -0300 Subject: [PATCH] ssl proxy suport (#16) * chore: adjusted handlers * feat: added ssl support to http * feat(proxy): added certs from envs * feat(proxy): adjusted example to use cert --- Cargo.lock | 72 ++++++++++++++++++++++++++++++++---- proxy/.gitignore | 3 +- proxy/Cargo.toml | 6 ++- proxy/README.md | 2 + proxy/examples/manifest.yaml | 70 ++++++++++++++++++++++++++++++++++- proxy/src/config.rs | 10 ++++- proxy/src/proxy.rs | 62 +++++++++++++++++++++++++++---- 7 files changed, 206 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 96c58d6..a6e21e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -978,10 +978,10 @@ dependencies = [ "http 0.2.11", "hyper 0.14.28", "log", - "rustls", + "rustls 0.21.10", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", ] [[package]] @@ -1191,8 +1191,8 @@ dependencies = [ "kube-core", "pem", "pin-project", - "rustls", - "rustls-pemfile", + "rustls 0.21.10", + "rustls-pemfile 1.0.4", "secrecy", "serde", "serde_json", @@ -1713,8 +1713,12 @@ dependencies = [ "operator", "prometheus", "regex", + "rustls 0.22.2", + "rustls-pemfile 2.1.0", + "rustls-pki-types", "thiserror", "tokio", + "tokio-rustls 0.25.0", "tokio-tungstenite", "tracing", "tracing-subscriber", @@ -1822,7 +1826,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", @@ -1888,10 +1892,24 @@ checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.2", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -1899,7 +1917,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "schannel", "security-framework", ] @@ -1913,6 +1931,22 @@ dependencies = [ "base64", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c333bb734fcdedcea57de1602543590f545f127dc8b533324318fd492c5c70b" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048a63e5b3ac996d78d402940b5fa47973d2d080c6c6fffa1d0f19c4445310b7" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -1923,6 +1957,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.16" @@ -2370,7 +2415,18 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.10", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.2", + "rustls-pki-types", "tokio", ] diff --git a/proxy/.gitignore b/proxy/.gitignore index 50cd4e8..400a2b8 100644 --- a/proxy/.gitignore +++ b/proxy/.gitignore @@ -1,3 +1,4 @@ /target fake-api -.env* \ No newline at end of file +.env* +cert \ No newline at end of file diff --git a/proxy/Cargo.toml b/proxy/Cargo.toml index 25db4f0..51cdb2e 100644 --- a/proxy/Cargo.toml +++ b/proxy/Cargo.toml @@ -22,4 +22,8 @@ tokio = { version = "1.35.1", features = ["full"] } tokio-tungstenite = "0.21.0" tracing = "0.1.40" tracing-subscriber = "0.3.18" -url = "2.5.0" \ No newline at end of file +url = "2.5.0" +rustls-pemfile = "2.1.0" +rustls = "0.22.2" +rustls-pki-types = "1.3.0" +tokio-rustls = "0.25.0" diff --git a/proxy/README.md b/proxy/README.md index 6fcd57c..7b6f19c 100644 --- a/proxy/README.md +++ b/proxy/README.md @@ -18,6 +18,8 @@ The proxy exposes metrics about HTTP requests and WebSocket frames. | PROXY_ADDR | "0.0.0.0:8100" | | PROMETHEUS_ADDR | "0.0.0.0:5000" | | OGMIOS_PORT | - | +| SSL_CRT_PATH | file.crt | +| SSL_KEY_PATH | file.key | ## Commands diff --git a/proxy/examples/manifest.yaml b/proxy/examples/manifest.yaml index ca17514..b133ad7 100644 --- a/proxy/examples/manifest.yaml +++ b/proxy/examples/manifest.yaml @@ -147,6 +147,62 @@ spec: protocol: TCP --- # Run proxy +apiVersion: v1 +data: + localhost.crt: | + -----BEGIN CERTIFICATE----- + MIIDDzCCAfegAwIBAgIUM+uDlS5+M0PRTrSwcAlAMC1zYkwwDQYJKoZIhvcNAQEL + BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDIyMDE3Mjk1OFoXDTI0MDMy + MTE3Mjk1OFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF + AAOCAQ8AMIIBCgKCAQEAn9O3c1TMOzrYgA2zRoxokV31pMhARUDc9i4h7IFYNubz + AHJI0ThZ8XWFtyWgWsKSjJOM1V3H3wzvpR9TDK7ksUjocP8a385Hy5dNtiWHaM0Z + lCVVqAxps6T3qqXMRU/ttnfzHwToiWTjiSFP9RszCmXJigYVoNW4YPwpH8+EdPrU + C+CNWRf6P3OvhKY9djSNAmrKWl1snc9n/LwGofKoC2ggmyYYZZqtySAcsoHlk58q + n0nik5+rqCBKqUSEnGoRU+iz/xbrXB+2MGAsk/v6CXA6rQvdCDW2Z+e0JdqqszXM + NpN4bVR107mxPia2tvMLJrMaVbFv1ysHNbQWoXQg3wIDAQABo1kwVzAUBgNVHREE + DTALgglsb2NhbGhvc3QwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMB + MB0GA1UdDgQWBBRYCnOgk68CN7DUMTPlrvo7hZxRVDANBgkqhkiG9w0BAQsFAAOC + AQEATBDWZ29zGjB9uzeEy8nsRK3KtTSRXHLF7Haog9Q0BAYD/nGZVQk0PzMBbAAs + T3vtA+RWGt3qXuxCEjqxIVFUZGO1JPOk05T9rrV2iv+cBBrgPLka7yeZDlmdRE9k + /Y6O43nZrGidqDjA5Na+S+vqMA4SRlp2Nd8vtbGaNxUkm+8VYaI2EgeBB6LtbtAj + GjwWDj2sR2bh4Rx9VhNg1DhCBjN52ww5gC7UzDlYSFB3vcA9CX7WIHvfHcXSJ8oV + TkR8NJopLMlwUFazsNBe7kEZeiyv4XPKiCysUKfh6q0VhAUjlVgg6Ljd38N5e6ep + Fzcbv6g2bfI4vKicDA7Bqi/xNA== + -----END CERTIFICATE----- + localhost.key: | + -----BEGIN PRIVATE KEY----- + MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCf07dzVMw7OtiA + DbNGjGiRXfWkyEBFQNz2LiHsgVg25vMAckjROFnxdYW3JaBawpKMk4zVXcffDO+l + H1MMruSxSOhw/xrfzkfLl022JYdozRmUJVWoDGmzpPeqpcxFT+22d/MfBOiJZOOJ + IU/1GzMKZcmKBhWg1bhg/Ckfz4R0+tQL4I1ZF/o/c6+Epj12NI0CaspaXWydz2f8 + vAah8qgLaCCbJhhlmq3JIByygeWTnyqfSeKTn6uoIEqpRIScahFT6LP/FutcH7Yw + YCyT+/oJcDqtC90INbZn57Ql2qqzNcw2k3htVHXTubE+Jra28wsmsxpVsW/XKwc1 + tBahdCDfAgMBAAECggEAGiQ88JuWvrwEnAf292ZmgXBcNKme/t95xLe4S2tp4aur + ZLqh97vp1OP4t64V060uNIQQbaMQhVvOTrRtlw4m0GsxFFpa1kRyhcr8+6VDx/vU + CovYviCETCKCx+H6eVPLeSRBcHoTDCCjqX2jYF8kiAzXXBYGr5natdzzX6/yOwJm + zMfrJdTDll4njIfThk06dZA7dpGxSRxK/689J/TNG/sT6PWduZOOQ4gZDIlBwG9s + mNzbZo+YrS9OreV3cMq8MnPDQKcIpxCogoiXlDIY9iFL8BfKqpjKlYoIvWR5FuNu + XFiKqBZxtK3dFfQKX5btabICiKggeutUZ/upa5gfoQKBgQDXqU20v22ikNm6qj32 + m9bCp4edJ68JJWU6JARCbb9gDXtcP6P+oSteoSihY9RikI5rEM9lvVjDpzjgbJEV + 3aQxBTHT9MKweona+jhqNgqZnea4d3h4uTu24fINWhxedeWWFgbFDbBt/lnmRv4k + Kj/r3O/D/1/l9dCsdOw3Thw/2QKBgQC9uNmyrnX5m3xoiZr0BuFpCl9SrHfYaOKz + jg6IqUV0M+z+Fmefen9EKQpuIA16oPYqr0ogyx/GFpbbI4aU+3H2eTm1UDeSenJ5 + I6DayZhZM8Y3G1PtTYJBy87QB7C0klpa+X0jML3seDp6jQgNnWbqGIgvDyJWHFy2 + B+ZkWcErdwKBgFxWJOsquypLkq2Vjoo0FzOovyvOfecQl9LY8OnwS2w42YSZywGO + yB7wKZFQSPMaqZ+1xtbsx0CeLIAKe+Q8zbwfWUJDHcip7rRPRjBTix5SuSJqJK6r + wKGBBD4rQtI+8FnefG+KeOvfZ2ZtJwsc+9lk81Ob19eB9CKivTDAxN+hAoGAH0SE + 1Hb+SIoAofXzzL4JjldASI7WHZuDqVYDPTCwmqsoJuQoZdc5fFFLP8UWk5xNldFX + 5Tm03d/BMxKSzqD2MkneYex7jC+UCDUAAK7y5dirlU9ysIxyqEdfqVdrHwdzzsSJ + hDA3TO6vrJzrs9q6KGCsqRzUat63xOReazGDrZcCgYAOC+ZXjnvb4sHffmUGZn7R + p1JvAS1eoOgwR284jS3DGdNRYxXsaqpD5SLpLzWDXPDfsPKDg613MjaBrghceA9i + 1+pvxRA1z1SjK0UtkVc280pRwNZjue/GtNnc0KgkqN3guJ5WeDumBxmStJ5KGNYG + T4jj2oxkaijVhOEOzUkq4w== + -----END PRIVATE KEY----- +kind: ConfigMap +metadata: + name: proxy-vol + namespace: prj-mainnet-test +--- apiVersion: apps/v1 kind: Deployment metadata: @@ -176,6 +232,18 @@ spec: value: "0.0.0.0:9187" - name: OGMIOS_PORT value: "80" + - name: SSL_CRT_PATH + value: "/certs/localhost.crt" + - name: SSL_KEY_PATH + value: "/certs/localhost.key" + volumeMounts: + - name: certs + mountPath: /certs + + volumes: + - name: certs + configMap: + name: proxy-vol --- apiVersion: v1 kind: Service @@ -310,4 +378,4 @@ metadata: namespace: prj-mainnet-test spec: network: "mainnet" - version: 1 \ No newline at end of file + version: 1 diff --git a/proxy/src/config.rs b/proxy/src/config.rs index 5e2a8aa..2aed46f 100644 --- a/proxy/src/config.rs +++ b/proxy/src/config.rs @@ -1,4 +1,4 @@ -use std::env; +use std::{env, path::PathBuf}; #[derive(Debug, Clone)] pub struct Config { @@ -6,6 +6,8 @@ pub struct Config { pub proxy_namespace: String, pub prometheus_addr: String, pub ogmios_port: u16, + pub ssl_crt_path: PathBuf, + pub ssl_key_path: PathBuf, } impl Config { @@ -14,6 +16,12 @@ impl Config { proxy_addr: env::var("PROXY_ADDR").expect("PROXY_ADDR must be set"), proxy_namespace: env::var("PROXY_NAMESPACE").unwrap_or("ftr-ogmios-v1".into()), prometheus_addr: env::var("PROMETHEUS_ADDR").expect("PROMETHEUS_ADDR must be set"), + ssl_crt_path: env::var("SSL_CRT_PATH") + .map(|e| e.into()) + .expect("SSL_CRT_PATH must be set"), + ssl_key_path: env::var("SSL_KEY_PATH") + .map(|e| e.into()) + .expect("SSL_KEY_PATH must be set"), ogmios_port: env::var("OGMIOS_PORT") .expect("OGMIOS_PORT must be set") .parse() diff --git a/proxy/src/proxy.rs b/proxy/src/proxy.rs index 8374b37..0d7385c 100644 --- a/proxy/src/proxy.rs +++ b/proxy/src/proxy.rs @@ -5,16 +5,22 @@ use hyper::client::conn::http1 as http1_client; use hyper::header::{ HeaderValue, CONNECTION, HOST, SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_KEY, UPGRADE, }; -use hyper::server::conn::http1 as http1_server; use hyper::service::service_fn; use hyper::{Request, Response, StatusCode}; -use hyper_util::rt::TokioIo; +use hyper_util::rt::{TokioExecutor, TokioIo}; +use hyper_util::server::conn::auto::Builder; +use rustls::ServerConfig; +use rustls_pki_types::{CertificateDer, PrivateKeyDer}; +use std::error::Error; use std::fmt::Display; use std::net::SocketAddr; +use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; +use std::{fs, io}; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::RwLock; +use tokio_rustls::TlsAcceptor; use tokio_tungstenite::tungstenite::handshake::derive_accept_key; use tokio_tungstenite::tungstenite::protocol::Role; use tokio_tungstenite::{connect_async, WebSocketStream}; @@ -41,6 +47,13 @@ pub async fn start(rw_state: Arc>) { } let listener = listener_result.unwrap(); + let tls_acceptor_result = build_tls_acceptor(&state); + if let Err(err) = tls_acceptor_result { + error!(error = err.to_string(), "fail to load tls"); + std::process::exit(1); + } + let tls_acceptor = tls_acceptor_result.unwrap(); + info!(addr = state.config.proxy_addr, "proxy listening"); loop { @@ -52,14 +65,23 @@ pub async fn start(rw_state: Arc>) { } let (stream, _) = accept_result.unwrap(); - tokio::task::spawn(async move { - let io = TokioIo::new(stream); + let tls_acceptor = tls_acceptor.clone(); + + tokio::spawn(async move { + let tls_stream = match tls_acceptor.accept(stream).await { + Ok(tls_stream) => tls_stream, + Err(err) => { + error!(error = err.to_string(), "failed to perform tls handshake"); + return; + } + }; + + let io = TokioIo::new(tls_stream); let service = service_fn(move |req| handle(req, rw_state.clone())); - if let Err(err) = http1_server::Builder::new() - .serve_connection(io, service) - .with_upgrades() + if let Err(err) = Builder::new(TokioExecutor::new()) + .serve_connection_with_upgrades(io, service) .await { error!(error = err.to_string(), "failed proxy server connection"); @@ -246,3 +268,29 @@ impl ProxyRequest { } } } + +fn build_tls_acceptor(state: &State) -> Result> { + let certs = load_certs(&state.config.ssl_crt_path)?; + + let key = load_private_key(&state.config.ssl_key_path)?; + + let server_config = ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(certs, key) + .unwrap(); + + let tls_acceptor = TlsAcceptor::from(Arc::new(server_config)); + Ok(tls_acceptor) +} + +fn load_certs(path: &PathBuf) -> io::Result>> { + let cert_file = fs::File::open(path)?; + let mut reader = io::BufReader::new(cert_file); + rustls_pemfile::certs(&mut reader).collect() +} + +fn load_private_key(path: &PathBuf) -> io::Result> { + let key_file = fs::File::open(path)?; + let mut reader = io::BufReader::new(key_file); + rustls_pemfile::private_key(&mut reader).map(|key| key.unwrap()) +}