diff --git a/.changesets/feat_geal_tls_server.md b/.changesets/feat_geal_tls_server.md new file mode 100644 index 0000000000..beeb40a93f --- /dev/null +++ b/.changesets/feat_geal_tls_server.md @@ -0,0 +1,5 @@ +### TLS server support ([Issue #2615](https://github.com/apollographql/router/issues/2615)) + +The Router has to provide a TLS server to support HTTP/2 on the client side. This uses the rustls implementation (no TLS versions below 1.2), limited to one server certificate and safe default ciphers. + +By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/2614 \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index b069d0d7a9..59fa827e3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -271,6 +271,7 @@ dependencies = [ "test-span", "thiserror", "tokio", + "tokio-rustls", "tokio-stream", "tokio-util", "tonic", diff --git a/apollo-router/Cargo.toml b/apollo-router/Cargo.toml index 147b128656..d1cad0735b 100644 --- a/apollo-router/Cargo.toml +++ b/apollo-router/Cargo.toml @@ -203,6 +203,7 @@ urlencoding = "2.1.2" uuid = { version = "1.2.2", features = ["serde", "v4"] } yaml-rust = "0.4.5" wsl = "0.1.0" +tokio-rustls = "0.23.4" [target.'cfg(macos)'.dependencies] uname = "0.1.1" diff --git a/apollo-router/src/axum_factory/axum_http_server_factory.rs b/apollo-router/src/axum_factory/axum_http_server_factory.rs index 2bdcbf43f3..8d2b5f3603 100644 --- a/apollo-router/src/axum_factory/axum_http_server_factory.rs +++ b/apollo-router/src/axum_factory/axum_http_server_factory.rs @@ -25,9 +25,9 @@ use hyper::Body; use itertools::Itertools; use multimap::MultiMap; use serde::Serialize; -use tokio::net::TcpListener; #[cfg(unix)] use tokio::net::UnixListener; +use tokio_rustls::TlsAcceptor; use tower::service_fn; use tower::BoxError; use tower::ServiceExt; @@ -176,21 +176,30 @@ impl HttpServerFactory for AxumHttpServerFactory { // if we received a TCP listener, reuse it, otherwise create a new one let main_listener = match all_routers.main.0.clone() { ListenAddr::SocketAddr(addr) => { - match main_listener.take().and_then(|listener| { - listener.local_addr().ok().and_then(|l| { - if l == ListenAddr::SocketAddr(addr) { - Some(listener) + let tls_config = configuration + .tls + .supergraph + .as_ref() + .map(|tls| tls.tls_config()) + .transpose()?; + let tls_acceptor = tls_config.clone().map(TlsAcceptor::from); + + match main_listener.take() { + Some(Listener::Tcp(listener)) => { + if listener.local_addr().ok() == Some(addr) { + Listener::new_from_listener(listener, tls_acceptor) } else { - None + Listener::new_from_socket_addr(addr, tls_acceptor).await? } - }) - }) { - Some(listener) => listener, - None => Listener::Tcp( - TcpListener::bind(addr) - .await - .map_err(ApolloRouterError::ServerCreationError)?, - ), + } + Some(Listener::Tls { listener, .. }) => { + if listener.local_addr().ok() == Some(addr) { + Listener::new_from_listener(listener, tls_acceptor) + } else { + Listener::new_from_socket_addr(addr, tls_acceptor).await? + } + } + _ => Listener::new_from_socket_addr(addr, tls_acceptor).await?, } } #[cfg(unix)] diff --git a/apollo-router/src/axum_factory/listeners.rs b/apollo-router/src/axum_factory/listeners.rs index 1dbcf23b69..b970a3f142 100644 --- a/apollo-router/src/axum_factory/listeners.rs +++ b/apollo-router/src/axum_factory/listeners.rs @@ -12,17 +12,16 @@ use futures::channel::oneshot; use futures::prelude::*; use hyper::server::conn::Http; use multimap::MultiMap; -use tokio::net::TcpListener; #[cfg(unix)] use tokio::net::UnixListener; use tokio::sync::Notify; use crate::configuration::Configuration; -use crate::configuration::ListenAddr; use crate::http_server_factory::Listener; use crate::http_server_factory::NetworkStream; use crate::router::ApolloRouterError; use crate::router_factory::Endpoint; +use crate::ListenAddr; #[derive(Clone, Debug)] pub(crate) struct ListenAddrAndRouter(pub(crate) ListenAddr, pub(crate) Router); @@ -163,11 +162,7 @@ pub(super) async fn get_extra_listeners( // if we received a TCP listener, reuse it, otherwise create a new one #[cfg_attr(not(unix), allow(unused_mut))] let listener = match listen_addr.clone() { - ListenAddr::SocketAddr(addr) => Listener::Tcp( - TcpListener::bind(addr) - .await - .map_err(ApolloRouterError::ServerCreationError)?, - ), + ListenAddr::SocketAddr(addr) => Listener::new_from_socket_addr(addr, None).await?, #[cfg(unix)] ListenAddr::UnixSocket(path) => Listener::Unix( UnixListener::bind(path).map_err(ApolloRouterError::ServerCreationError)?, @@ -260,6 +255,38 @@ pub(super) fn serve_router_on_listen_addr( .http1_keep_alive(true) .serve_connection(stream, app); + tokio::pin!(connection); + tokio::select! { + // the connection finished first + _res = &mut connection => { + } + // the shutdown receiver was triggered first, + // so we tell the connection to do a graceful shutdown + // on the next request, then we wait for it to finish + _ = connection_shutdown.notified() => { + let c = connection.as_mut(); + c.graceful_shutdown(); + + let _= connection.await; + } + } + }, + NetworkStream::Tls(stream) => { + stream.get_ref().0 + .set_nodelay(true) + .expect( + "this should not fail unless the socket is invalid", + ); + + let protocol = stream.get_ref().1.alpn_protocol(); + let http2 = protocol == Some(&b"h2"[..]); + + let connection = Http::new() + .http1_keep_alive(true) + .http1_header_read_timeout(Duration::from_secs(10)) + .http2_only(http2) + .serve_connection(stream, app); + tokio::pin!(connection); tokio::select! { // the connection finished first diff --git a/apollo-router/src/configuration/mod.rs b/apollo-router/src/configuration/mod.rs index 94a7a3560d..616f0e8b79 100644 --- a/apollo-router/src/configuration/mod.rs +++ b/apollo-router/src/configuration/mod.rs @@ -12,22 +12,33 @@ mod yaml; use std::collections::HashMap; use std::fmt; +use std::io; +use std::io::BufReader; +use std::iter; use std::net::IpAddr; use std::net::SocketAddr; use std::num::NonZeroUsize; use std::str::FromStr; +use std::sync::Arc; use derivative::Derivative; use displaydoc::Display; use itertools::Itertools; use once_cell::sync::Lazy; use regex::Regex; +use rustls::Certificate; +use rustls::PrivateKey; +use rustls::ServerConfig; +use rustls_pemfile::certs; +use rustls_pemfile::read_one; +use rustls_pemfile::Item; use schemars::gen::SchemaGenerator; use schemars::schema::ObjectValidation; use schemars::schema::Schema; use schemars::schema::SchemaObject; use schemars::JsonSchema; use serde::Deserialize; +use serde::Deserializer; use serde::Serialize; use serde_json::Map; use serde_json::Value; @@ -41,6 +52,7 @@ pub(crate) use self::schema::generate_upgrade; use crate::cache::DEFAULT_CACHE_CAPACITY; use crate::configuration::schema::Mode; use crate::plugin::plugins; +use crate::ApolloRouterError; static SUPERGRAPH_ENDPOINT_REGEX: Lazy = Lazy::new(|| { Regex::new(r"(?P.*/)(?P.+)\*$") @@ -623,9 +635,129 @@ pub(crate) struct RedisCache { #[serde(deny_unknown_fields)] #[serde(default)] pub(crate) struct Tls { + /// TLS server configuration + /// + /// this will affect the GraphQL endpoint and any other endpoint targeting the same listen address + pub(crate) supergraph: Option, pub(crate) subgraph: TlsSubgraphWrapper, } +/// Configuration options pertaining to the supergraph server component. +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +#[serde(deny_unknown_fields)] +pub(crate) struct TlsSupergraph { + /// server certificate in PEM format + #[serde(deserialize_with = "deserialize_certificate", skip_serializing)] + #[schemars(with = "String")] + pub(crate) certificate: Certificate, + /// server key in PEM format + #[serde(deserialize_with = "deserialize_key", skip_serializing)] + #[schemars(with = "String")] + pub(crate) key: PrivateKey, + /// list of certificate authorities in PEM format + #[serde(deserialize_with = "deserialize_certificate_chain", skip_serializing)] + #[schemars(with = "String")] + pub(crate) certificate_chain: Vec, +} + +impl TlsSupergraph { + pub(crate) fn tls_config(&self) -> Result, ApolloRouterError> { + let mut certificates = vec![self.certificate.clone()]; + certificates.extend(self.certificate_chain.iter().cloned()); + + let mut config = ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(certificates, self.key.clone()) + .map_err(ApolloRouterError::Rustls)?; + config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + + Ok(Arc::new(config)) + } +} + +fn deserialize_certificate<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let data = String::deserialize(deserializer)?; + + load_certs(&data) + .map_err(serde::de::Error::custom) + .and_then(|mut certs| { + if certs.len() > 1 { + Err(serde::de::Error::custom( + "expected exactly one server certificate", + )) + } else { + certs.pop().ok_or(serde::de::Error::custom( + "expected exactly one server certificate", + )) + } + }) +} + +fn deserialize_certificate_chain<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let data = String::deserialize(deserializer)?; + + load_certs(&data).map_err(serde::de::Error::custom) +} + +fn deserialize_key<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let data = String::deserialize(deserializer)?; + + load_keys(&data).map_err(serde::de::Error::custom) +} + +fn load_certs(data: &str) -> io::Result> { + certs(&mut BufReader::new(data.as_bytes())) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid cert")) + .map(|mut certs| certs.drain(..).map(Certificate).collect()) +} + +fn load_keys(data: &str) -> io::Result { + let mut reader = BufReader::new(data.as_bytes()); + let mut key_iterator = iter::from_fn(|| read_one(&mut reader).transpose()); + + let private_key = match key_iterator.next() { + Some(Ok(Item::RSAKey(key))) => PrivateKey(key), + Some(Ok(Item::PKCS8Key(key))) => PrivateKey(key), + Some(Ok(Item::ECKey(key))) => PrivateKey(key), + Some(Err(e)) => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("could not parse the key: {e}"), + )) + } + Some(_) => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "expected a private key", + )) + } + None => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "could not find a private key", + )) + } + }; + + if key_iterator.next().is_some() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "expected exactly one private key", + )); + } + Ok(private_key) +} + /// Configuration options pertaining to the subgraph server component. #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index 33cc6e1208..c688a8d550 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -4296,6 +4296,7 @@ expression: "&schema" "tls": { "description": "TLS related configuration options.", "default": { + "supergraph": null, "subgraph": { "all": { "certificate_authorities": null @@ -4351,6 +4352,35 @@ expression: "&schema" } }, "additionalProperties": false + }, + "supergraph": { + "description": "TLS server configuration\n\nthis will affect the GraphQL endpoint and any other endpoint targeting the same listen address", + "default": null, + "type": "object", + "required": [ + "certificate", + "certificate_chain", + "key" + ], + "properties": { + "certificate": { + "description": "server certificate in PEM format", + "writeOnly": true, + "type": "string" + }, + "certificate_chain": { + "description": "list of certificate authorities in PEM format", + "writeOnly": true, + "type": "string" + }, + "key": { + "description": "server key in PEM format", + "writeOnly": true, + "type": "string" + } + }, + "additionalProperties": false, + "nullable": true } }, "additionalProperties": false diff --git a/apollo-router/src/configuration/testdata/server.crt b/apollo-router/src/configuration/testdata/server.crt new file mode 100644 index 0000000000..f04bb5c10f --- /dev/null +++ b/apollo-router/src/configuration/testdata/server.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF9TCCA92gAwIBAgIUM+6LSYfTRzSalYzqdFfuPbcznyswDQYJKoZIhvcNAQEL +BQAwQDELMAkGA1UEBhMCRlIxCjAIBgNVBAgMASAxCjAIBgNVBAoMASAxGTAXBgNV +BAMMEGxvY2FsLmFwb2xsby5kZXYwHhcNMjIxMjE2MTQ1OTMzWhcNMjMwMTE1MTQ1 +OTMzWjBAMQswCQYDVQQGEwJGUjEKMAgGA1UECAwBIDEKMAgGA1UECgwBIDEZMBcG +A1UEAwwQbG9jYWwuYXBvbGxvLmRldjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC +AgoCggIBALK52xtnhD1MJEuuXbLlEU3tcPO3MIWYFY2i+rTyYQYKxa5a4QG9vBjB +bQb18b2xXVxmCs57MYt9v5GQrU4Dc55qWXVzSFK3wLD8PvS+NaTkjh+TH1MbW8Rr +BVxTq1XD0HJAJfXdbTlg62VoKk6UXFk+YH/In+u1UAq0T1amC39B8hiTFNd2Yawg +SKn4i+6NmZluzIb88ZLzRb2xrnEd2FG4JAucPHpTjmNtwFzl3nmbgMNKntLA3Ac+ +CdaIWuPqkbDEDzR5mP8tx2IzUSz3C08Z1Oo+8uS5aOyWg8l4MPBhyWONFA8ilvd3 ++yjzPKwa/zFEozoUp5GWSWLl53Ff6anw54yUIND0qhD5X4ICtOk2F41Gwv/GKTSO +AnfwpxZiUji2OOZwXQ/Zs+lUXTgQvshvb6PXbJT6T3wxou+WpVJFDctELBNdMNbe +WldtYvPry7rngLWOUsLq6c/oQibvL19Pc98532LKsWFsYEMRVA7WNsyj040Y9FoO +zBgvZ/AyxgT/23/P9xJxg0RjqOkO8jPx5kpDOL9c8qkKds7CQ5z4d3DZuzLjfKUw +pT3u10an1nh5xmcSH9vLnZuDoJL9OzJdmQ8pEdKdQGTTP4KXM/OUh8+sSxFyLoYV +850SeydMTTm72GkWzwq2npp2KNo41F0mT2eyvQSNy0PIN6eSRgpnAgMBAAGjgeYw +geMwHQYDVR0OBBYEFDV0smlfWnSnE/PtxF65lwn5ewgrMHsGA1UdIwR0MHKAFDV0 +smlfWnSnE/PtxF65lwn5ewgroUSkQjBAMQswCQYDVQQGEwJGUjEKMAgGA1UECAwB +IDEKMAgGA1UECgwBIDEZMBcGA1UEAwwQbG9jYWwuYXBvbGxvLmRldoIUM+6LSYfT +RzSalYzqdFfuPbcznyswCwYDVR0PBAQDAgL8MBsGA1UdEQQUMBKCEGxvY2FsLmFw +b2xsby5kZXYwGwYDVR0SBBQwEoIQbG9jYWwuYXBvbGxvLmRldjANBgkqhkiG9w0B +AQsFAAOCAgEAZtNEFzXO0phLUQm2yRZzOA5OPwsW97iMBUc2o5tP8jkkmWTNMWHe +1COAkPVBpPS+lbCAMdoMd+yrQ/tZdMmVvqXYMc087ZkIGeIG8NOHWJkBoAlV3mYP +feb8nbbZBHLzZUgj8p77sQeCR3gbodeUHoB3YEgb/btz39r6zYBdBcbrhU1D4Qki ++xpd1iYdo/qI9TwgnEavcIZ4Zpv7T6IvxPXQ6WjWofXlb3G8atm5lL88TxMszHv4 +d2A3giMd4wv66usme9CN2kFKV568eQqnqAzY+bNGdAVlLX2ieWCKT9NmUhHc8b1M +oaS6E/qlcOT4c+F8/kDcW35TasPuzLEH8YBrn+e+rl0etv6DJL3gBqMciJNJ0DSj +YW0inRx6VaQCH0iqzeKjy7bas6Mj/emfkmMIuzL2UVFE2khfMqbpaR9Uat4jbIzH +Pfh5zF40bklOqA5axztJurKWv5deEof5PZ5jLx47VIU3VrwYmIUEUpOdEi426LwX +0TSEG0P8d82UqU+mh7Ibcd1KWTmmwA7pJ9hsN6n2VYhogojh1n1lwDH0g6ND6+mh +LOGdw2a3DeyWSZNl/HRezyq983gbK/1U2DeuoxzAC8axEJa4iRRBWMKX7XdBuuHD +wj3nI/0PXcNFsiaB7qPpIFCv7F9fw44tdh58mCdQSWC+JeJp43E9Wfg= +-----END CERTIFICATE----- diff --git a/apollo-router/src/configuration/testdata/server.key b/apollo-router/src/configuration/testdata/server.key new file mode 100644 index 0000000000..133183abb3 --- /dev/null +++ b/apollo-router/src/configuration/testdata/server.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +wJ/KXhcyqjcyToe5KOxF5cOo0BLFpNwQ +-----END PRIVATE KEY----- diff --git a/apollo-router/src/configuration/tests.rs b/apollo-router/src/configuration/tests.rs index c2a6a221d0..9b031e55e9 100644 --- a/apollo-router/src/configuration/tests.rs +++ b/apollo-router/src/configuration/tests.rs @@ -732,3 +732,36 @@ fn test_configuration_validate_and_sanitize() { .build() .is_err()); } + +#[test] +fn load_tls() { + let mut cert_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + cert_path.push("src"); + cert_path.push("configuration"); + cert_path.push("testdata"); + cert_path.push("server.crt"); + let cert_path = cert_path.to_string_lossy(); + + let mut key_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + key_path.push("src"); + key_path.push("configuration"); + key_path.push("testdata"); + key_path.push("server.key"); + let key_path = key_path.to_string_lossy(); + + let cfg = validate_yaml_configuration( + &format!( + r#" +tls: + supergraph: + certificate: ${{file.{cert_path}}} + certificate_chain: ${{file.{cert_path}}} + key: ${{file.{key_path}}} +"#, + ), + Expansion::builder().supported_mode("file").build(), + Mode::NoUpgrade, + ) + .expect("should not have resulted in an error"); + cfg.tls.supergraph.unwrap().tls_config().unwrap(); +} diff --git a/apollo-router/src/http_server_factory.rs b/apollo-router/src/http_server_factory.rs index 3bd7b2cc13..c4439bc390 100644 --- a/apollo-router/src/http_server_factory.rs +++ b/apollo-router/src/http_server_factory.rs @@ -1,4 +1,5 @@ // With regards to ELv2 licensing, this entire file is license key functionality +use std::net::SocketAddr; use std::pin::Pin; use std::sync::Arc; @@ -65,7 +66,11 @@ impl HttpServerHandle { pub(crate) fn new( shutdown_sender: oneshot::Sender<()>, server_future: Pin< - Box> + Send>, + Box< + dyn Future> + + Send + + 'static, + >, >, graphql_listen_address: Option, listen_addresses: Vec, @@ -153,15 +158,43 @@ pub(crate) enum Listener { Tcp(tokio::net::TcpListener), #[cfg(unix)] Unix(tokio::net::UnixListener), + Tls { + listener: tokio::net::TcpListener, + acceptor: tokio_rustls::TlsAcceptor, + }, } pub(crate) enum NetworkStream { Tcp(tokio::net::TcpStream), #[cfg(unix)] Unix(tokio::net::UnixStream), + Tls(tokio_rustls::server::TlsStream), } impl Listener { + pub(crate) async fn new_from_socket_addr( + address: SocketAddr, + tls_acceptor: Option, + ) -> Result { + let listener = tokio::net::TcpListener::bind(address) + .await + .map_err(ApolloRouterError::ServerCreationError)?; + match tls_acceptor { + None => Ok(Listener::Tcp(listener)), + Some(acceptor) => Ok(Listener::Tls { listener, acceptor }), + } + } + + pub(crate) fn new_from_listener( + listener: tokio::net::TcpListener, + tls_acceptor: Option, + ) -> Self { + match tls_acceptor { + None => Listener::Tcp(listener), + Some(acceptor) => Listener::Tls { listener, acceptor }, + } + } + pub(crate) fn local_addr(&self) -> std::io::Result { match self { Listener::Tcp(listener) => listener.local_addr().map(Into::into), @@ -173,6 +206,7 @@ impl Listener { .unwrap_or_default(), ) }), + Listener::Tls { listener, .. } => listener.local_addr().map(Into::into), } } @@ -187,6 +221,11 @@ impl Listener { .accept() .await .map(|(stream, _)| NetworkStream::Unix(stream)), + Listener::Tls { listener, acceptor } => { + let (stream, _) = listener.accept().await?; + + Ok(NetworkStream::Tls(acceptor.accept(stream).await?)) + } } } } diff --git a/apollo-router/src/router.rs b/apollo-router/src/router.rs index 8c6bb6edd4..a57915dec2 100644 --- a/apollo-router/src/router.rs +++ b/apollo-router/src/router.rs @@ -137,6 +137,9 @@ pub enum ApolloRouterError { /// tried to register two endpoints on `{0}:{1}{2}` SameRouteUsedTwice(IpAddr, u16, String), + + /// TLS configuration error: {0} + Rustls(rustls::Error), } type SchemaStream = Pin + Send>>;