Proxy: Allow non-Conduit-bound TLS and non-TLS through. (#1203)

On the server (accept) side of TLS, if the traffic isn't targetting the
proxy (as determined by the TLS ClientHello SNI), or if the traffic
isn't TLS, then pass it through.

Signed-off-by: Brian Smith <brian@briansmith.org>
This commit is contained in:
Brian Smith 2018-06-26 10:16:00 -10:00 committed by GitHub
parent ff8ec8abe0
commit f44a59da33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 139 additions and 45 deletions

View File

@ -30,6 +30,18 @@ pub fn connect(addr: &SocketAddr,
}
}
/// A server socket that is in the process of conditionally upgrading to TLS.
enum ConditionallyUpgradeServerToTls {
Plaintext(Option<ConditionallyUpgradeServerToTlsInner>),
UpgradeToTls(tls::UpgradeServerToTls),
}
struct ConditionallyUpgradeServerToTlsInner {
socket: TcpStream,
tls: tls::ConnectionConfig<tls::ServerConfig>,
peek_buf: BytesMut,
}
/// A socket that is in the process of connecting.
pub enum Connecting {
Plaintext {
@ -154,19 +166,10 @@ impl BoundPort {
Conditional::None(r) => Conditional::None(*r),
};
let conn = match tls {
Conditional::Some(config) => {
// TODO: use `config.identity` to differentiate
// between TLS that the proxy should terminate vs.
// TLS that should be passed through.
let f = tls::Connection::accept(socket, config.config)
.map(move |tls| Connection::tls(tls));
Either::A(f)
},
Conditional::None(why_no_tls) => {
let f = future::ok(socket)
.map(move |plain| Connection::plain(plain, why_no_tls));
Either::B(f)
},
Conditional::Some(tls) =>
Either::A(ConditionallyUpgradeServerToTls::new(socket, tls)),
Conditional::None(why_no_tls) =>
Either::B(future::ok(Connection::plain(socket, why_no_tls))),
};
conn.map(move |conn| (conn, remote_addr))
})
@ -186,6 +189,61 @@ impl BoundPort {
}
}
// ===== impl ConditionallyUpgradeServerToTls =====
impl ConditionallyUpgradeServerToTls {
fn new(socket: TcpStream, tls: tls::ConnectionConfig<tls::ServerConfig>) -> Self {
ConditionallyUpgradeServerToTls::Plaintext(Some(ConditionallyUpgradeServerToTlsInner {
socket,
tls,
peek_buf: BytesMut::with_capacity(8192),
}))
}
}
impl Future for ConditionallyUpgradeServerToTls {
type Item = Connection;
type Error = io::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
*self = match self {
ConditionallyUpgradeServerToTls::Plaintext(ref mut inner) => {
let r = {
let inner = inner.as_mut().unwrap();
try_ready!(inner.socket.read_buf(&mut inner.peek_buf));
tls::conditional_accept::match_client_hello(
inner.peek_buf.as_ref(), &inner.tls.identity)
};
match r {
tls::conditional_accept::Match::Matched => {
trace!("upgrading accepted connection to TLS");
let inner = inner.take().expect("Polled after ready");
let upgrade_to_tls = tls::Connection::accept(
inner.socket, inner.peek_buf.freeze(), inner.tls.config);
ConditionallyUpgradeServerToTls::UpgradeToTls(upgrade_to_tls)
},
tls::conditional_accept::Match::NotMatched => {
trace!("passing through accepted connection without TLS");
let inner = inner.take().expect("Polled after ready");
let conn = Connection::plain_with_peek_buf(
inner.socket, inner.peek_buf, tls::ReasonForNoTls::NotProxyTls);
return Ok(Async::Ready(conn));
},
tls::conditional_accept::Match::Incomplete => {
return Ok(Async::NotReady);
},
}
},
ConditionallyUpgradeServerToTls::UpgradeToTls(upgrading) => {
let tls_stream = try_ready!(upgrading.poll());
return Ok(Async::Ready(Connection::tls(BoxedIo::new(tls_stream))));
}
}
}
}
}
// ===== impl Connecting =====
impl Future for Connecting {
@ -211,7 +269,7 @@ impl Future for Connecting {
},
Connecting::UpgradeToTls(upgrading) => {
let tls_stream = try_ready!(upgrading.poll());
return Ok(Async::Ready(Connection::tls(tls_stream)));
return Ok(Async::Ready(Connection::tls(BoxedIo::new(tls_stream))));
},
};
}
@ -222,16 +280,22 @@ impl Future for Connecting {
impl Connection {
fn plain(io: TcpStream, why_no_tls: tls::ReasonForNoTls) -> Self {
Self::plain_with_peek_buf(io, BytesMut::new(), why_no_tls)
}
fn plain_with_peek_buf(io: TcpStream, peek_buf: BytesMut, why_no_tls: tls::ReasonForNoTls)
-> Self
{
Connection {
io: BoxedIo::new(io),
peek_buf: BytesMut::new(),
peek_buf,
tls_status: Conditional::None(why_no_tls),
}
}
fn tls<S: tls::Session + std::fmt::Debug + 'static>(tls: tls::Connection<S>) -> Self {
fn tls(io: BoxedIo) -> Self {
Connection {
io: BoxedIo::new(tls),
io: io,
peek_buf: BytesMut::new(),
tls_status: Conditional::Some(()),
}

View File

@ -383,7 +383,8 @@ impl fmt::Display for ctx::transport::TlsStatus {
Conditional::None(tls::ReasonForNoTls::NoConfig) => f.pad(",tls=\"no_config\""),
Conditional::None(tls::ReasonForNoTls::Disabled) |
Conditional::None(tls::ReasonForNoTls::InternalTraffic) |
Conditional::None(tls::ReasonForNoTls::NoIdentity(_)) => Ok(()),
Conditional::None(tls::ReasonForNoTls::NoIdentity(_)) |
Conditional::None(tls::ReasonForNoTls::NotProxyTls) => Ok(()),
}
}
}

View File

@ -1,5 +1,3 @@
#![allow(dead_code)] // TODO: Actually use this.
use std::{cmp, fmt::Debug, io, net::SocketAddr};
use super::io::internal::Io;

View File

@ -1,5 +1,3 @@
#![allow(dead_code)] // TODO: Use this.
use super::{Identity, untrusted};
#[derive(Debug, Eq, PartialEq)]

View File

@ -96,6 +96,10 @@ pub enum ReasonForNoTls {
/// The connection is between the proxy and the service
InternalTraffic,
/// The connection isn't TLS or it is TLS but not intended to be handled
/// by the proxy.
NotProxyTls,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]

View File

@ -6,7 +6,7 @@ use futures::Future;
use tokio::prelude::*;
use tokio::net::TcpStream;
use transport::{AddrInfo, io::internal::Io};
use transport::{AddrInfo, io::internal::Io, prefixed::Prefixed};
use super::{
identity::Identity,
@ -19,22 +19,24 @@ use super::{
use std::fmt::Debug;
pub use self::rustls::Session;
use bytes::Bytes;
// In theory we could replace `TcpStream` with `Io`. However, it is likely that
// in the future we'll need to do things specific to `TcpStream`, so optimize
// for that unless/until there is some benefit to doing otherwise.
#[derive(Debug)]
pub struct Connection<S: Session>(TlsStream<TcpStream, S>);
pub struct Connection<S, C>(TlsStream<S, C>) where S: Debug, C: Debug;
pub struct UpgradeToTls<S, F>(F)
where S: Session,
F: Future<Item = TlsStream<TcpStream, S>, Error = io::Error>;
pub struct UpgradeToTls<S, C, F>(F)
where C: Session,
F: Future<Item = TlsStream<S, C>, Error = io::Error>;
impl<S, F> Future for UpgradeToTls<S, F>
where S: Session,
F: Future<Item = TlsStream<TcpStream, S>, Error = io::Error>
impl<C, S, F> Future for UpgradeToTls<S, C, F>
where S: Debug,
C: Session + Debug,
F: Future<Item = TlsStream<S, C>, Error = io::Error>
{
type Item = Connection<S>;
type Item = Connection<S, C>;
type Error = io::Error;
fn poll(&mut self) -> Result<Async<Self::Item>, Self::Error> {
@ -44,12 +46,15 @@ impl<S, F> Future for UpgradeToTls<S, F>
}
pub type UpgradeClientToTls =
UpgradeToTls<rustls::ClientSession, tokio_rustls::ConnectAsync<TcpStream>>;
UpgradeToTls<TcpStream, rustls::ClientSession, tokio_rustls::ConnectAsync<TcpStream>>;
pub type UpgradeServerToTls =
UpgradeToTls<rustls::ServerSession, tokio_rustls::AcceptAsync<TcpStream>>;
UpgradeToTls<
Prefixed<TcpStream>,
rustls::ServerSession,
tokio_rustls::AcceptAsync<Prefixed<TcpStream>>>;
impl Connection<rustls::ClientSession> {
impl Connection<TcpStream, rustls::ClientSession> {
pub fn connect(socket: TcpStream, identity: &Identity, ClientConfig(config): ClientConfig)
-> UpgradeClientToTls
{
@ -57,26 +62,36 @@ impl Connection<rustls::ClientSession> {
}
}
impl Connection<rustls::ServerSession> {
pub fn accept(socket: TcpStream, ServerConfig(config): ServerConfig) -> UpgradeServerToTls
impl Connection<Prefixed<TcpStream>, rustls::ServerSession> {
pub fn accept(socket: TcpStream, prefix: Bytes, ServerConfig(config): ServerConfig)
-> UpgradeServerToTls
{
UpgradeToTls(config.accept_async(socket))
UpgradeToTls(config.accept_async(Prefixed::new(prefix, socket)))
}
}
impl<S: Session> io::Read for Connection<S> {
impl<S, C> io::Read for Connection<S, C>
where S: Debug + io::Read + io::Write,
C: Session + Debug
{
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
}
impl<S: Session> AsyncRead for Connection<S> {
impl<S, C> AsyncRead for Connection<S, C>
where S: AsyncRead + AsyncWrite + Debug + io::Read + io::Write,
C: Session + Debug
{
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
self.0.prepare_uninitialized_buffer(buf)
}
}
impl<S: Session> io::Write for Connection<S> {
impl<S, C> io::Write for Connection<S, C>
where S: Debug + io::Read + io::Write,
C: Session + Debug
{
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
@ -86,7 +101,10 @@ impl<S: Session> io::Write for Connection<S> {
}
}
impl<S: Session> AsyncWrite for Connection<S> {
impl<S, C> AsyncWrite for Connection<S, C>
where S: AsyncRead + AsyncWrite + Debug + io::Read + io::Write,
C: Session + Debug
{
fn shutdown(&mut self) -> Poll<(), io::Error> {
self.0.shutdown()
}
@ -96,7 +114,10 @@ impl<S: Session> AsyncWrite for Connection<S> {
}
}
impl<S: Session + Debug> AddrInfo for Connection<S> {
impl<S, C> AddrInfo for Connection<S, C>
where S: AddrInfo + Debug,
C: Session + Debug
{
fn local_addr(&self) -> Result<SocketAddr, io::Error> {
self.0.get_ref().0.local_addr()
}
@ -106,7 +127,10 @@ impl<S: Session + Debug> AddrInfo for Connection<S> {
}
}
impl<S: Session + Debug> Io for Connection<S> {
impl<S, C> Io for Connection<S, C>
where S: Io + Debug,
C: Session + Debug
{
fn shutdown_write(&mut self) -> Result<(), io::Error> {
self.0.get_mut().0.shutdown_write()
}

View File

@ -4,7 +4,7 @@ extern crate tokio_rustls;
extern crate untrusted;
extern crate webpki;
mod conditional_accept;
pub mod conditional_accept;
mod config;
mod cert_resolver;
mod connection;
@ -25,7 +25,12 @@ pub use self::{
ServerConfigWatch,
watch_for_config_changes,
},
connection::{Connection, Session, UpgradeClientToTls},
connection::{
Connection,
Session,
UpgradeClientToTls,
UpgradeServerToTls
},
dns_name::{DnsName, InvalidDnsName},
identity::Identity,
};