From 7ca47279f13c294d93ffa69cd6cd25cd4eea4e96 Mon Sep 17 00:00:00 2001 From: Sumit Kumar Date: Thu, 14 Sep 2023 23:00:42 +0530 Subject: [PATCH 1/3] with Private VPN, it is important that SNI are supplied for SSL handshake, hence this change ensure that SNI is set for next_layer() which is client hello call. Test and working fine. --- src/TcpAdapterProxy.cpp | 14 +++++++------- src/WebSocketStream.cpp | 22 +++++++++++++++++++++- src/WebSocketStream.h | 2 +- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/TcpAdapterProxy.cpp b/src/TcpAdapterProxy.cpp index 435dc727..ddd92708 100644 --- a/src/TcpAdapterProxy.cpp +++ b/src/TcpAdapterProxy.cpp @@ -111,7 +111,7 @@ namespace aws { namespace iot { namespace securedtunneling { { std::ostringstream request_stream; request_stream << request; - std::string unfiltered_request_string = request_stream.str(); + std::string unfiltered_request_string = request_stream.str(); std::tuple token_filter_range = get_access_token_range(unfiltered_request_string); return (boost::format("%1%***ACCESS_TOKEN_REMOVED***%2%") % unfiltered_request_string.substr(0, std::get<0>(token_filter_range)) % @@ -803,13 +803,13 @@ namespace aws { namespace iot { namespace securedtunneling { } tac.wss = std::make_shared(tac.adapter_config, &log, tac.io_ctx); tac.wss->control_callback(std::bind(&tcp_adapter_proxy::handle_web_socket_control_message, this, std::ref(tac), std::placeholders::_1, std::placeholders::_2)); - + static std::string user_agent_string = (boost::format("localproxy %1% %2%-bit/boost-%3%.%4%.%5%/openssl-%6%.%7%.%8%/protobuf-%9%") % BOOST_PLATFORM % (sizeof(void*)*8) % (BOOST_VERSION / 100000) % ((BOOST_VERSION / 100) % 1000) % (BOOST_VERSION % 100) % (OPENSSL_VERSION_NUMBER >> 28) % ((OPENSSL_VERSION_NUMBER >> 20) & 0xF) % ((OPENSSL_VERSION_NUMBER >> 12) & 0xF) % google::protobuf::internal::VersionString(GOOGLE_PROTOBUF_VERSION) ).str(); - + //the actual work of this function starts here BOOST_LOG_SEV(log, info) << "Attempting to establish web socket connection with endpoint wss://" << tac.adapter_config.proxy_host << ":" << tac.adapter_config.proxy_port; @@ -885,8 +885,8 @@ namespace aws { namespace iot { namespace securedtunneling { { BOOST_LOG_SEV(log, debug) << "SSL host verification is off"; } - //next ssl handshake - tac.wss->async_ssl_handshake(boost::asio::ssl::stream_base::client, [=, &tac](boost::system::error_code const &ec) + //next ssl handshake and providing host string + tac.wss->async_ssl_handshake(boost::asio::ssl::stream_base::client, tac.adapter_config.proxy_host.c_str(), [=, &tac](boost::system::error_code const &ec) { if (ec) { @@ -1910,7 +1910,7 @@ namespace aws { namespace iot { namespace securedtunneling { std::uint16_t port_to_connect = boost::lexical_cast(src_port); BOOST_LOG_SEV(log, debug) << "Port to connect " << port_to_connect; server->resolver_.async_resolve(tac.bind_address_actual, src_port, - boost::asio::ip::resolver_base::passive, + boost::asio::ip::resolver_base::passive, [=, &tac](boost::system::error_code const &ec, tcp::resolver::results_type results) { if (ec) @@ -2072,7 +2072,7 @@ namespace aws { namespace iot { namespace securedtunneling { void tcp_adapter_proxy::async_setup_dest_tcp_socket(tcp_adapter_context &tac, string const & service_id, uint32_t const & connection_id, bool is_first_connection) { BOOST_LOG_SEV(log, trace) << "Setup destination tcp socket for service id" << service_id; - std::shared_ptr retry_config = + std::shared_ptr retry_config = std::make_shared(tac.io_ctx, GET_SETTING(settings, TCP_CONNECTION_RETRY_COUNT), GET_SETTING(settings, TCP_CONNECTION_RETRY_DELAY_MS), diff --git a/src/WebSocketStream.cpp b/src/WebSocketStream.cpp index fdd11c8b..7790cce5 100644 --- a/src/WebSocketStream.cpp +++ b/src/WebSocketStream.cpp @@ -173,15 +173,35 @@ namespace aws { } } - void WebSocketStream::async_ssl_handshake(const ssl::stream_base::handshake_type &type, + void WebSocketStream::async_ssl_handshake(const ssl::stream_base::handshake_type &type, const std::string &host, const BoostCallbackFunc &handler) { if (localproxyConfig.is_web_proxy_using_tls) { BOOST_LOG_SEV(*log, trace) << "Calling next_layer().async_handshake with type: " << WEB_PROXY_WITH_TLS_TYPE_NAME; + // Set SNI Hostname (many hosts need this to handshake successfully) + if(!SSL_set_tlsext_host_name(boost::get>(wss)->next_layer().native_handle(), host.c_str())) + { + BOOST_LOG_SEV(*log, trace) << "SSL next_layer() failed to set SNI"; + } + else + { + BOOST_LOG_SEV(*log, trace) << "SSL next_layer() SNI is set : " + << host; + } return boost::get>(wss)->next_layer().async_handshake(type, handler); } else { BOOST_LOG_SEV(*log, trace) << "Calling next_layer().async_handshake with type: " << WEB_PROXY_NO_TLS_TYPE_NAME; + // Set SNI Hostname (many hosts need this to handshake successfully) + if(!SSL_set_tlsext_host_name(boost::get>(wss)->next_layer().native_handle(), host.c_str())) + { + BOOST_LOG_SEV(*log, trace) << "SSL next_layer() failed to set SNI"; + } + else + { + BOOST_LOG_SEV(*log, trace) << "SSL next_layer() SNI is set : " + << host; + } return boost::get>(wss)->next_layer().async_handshake(type, handler); } } diff --git a/src/WebSocketStream.h b/src/WebSocketStream.h index 981e0d03..68a0b9eb 100644 --- a/src/WebSocketStream.h +++ b/src/WebSocketStream.h @@ -150,7 +150,7 @@ namespace aws { * @param handler the callback handler when the async operation is complete. */ void - async_ssl_handshake(const ssl::stream_base::handshake_type &type, const BoostCallbackFunc &handler); + async_ssl_handshake(const ssl::stream_base::handshake_type &type, const std::string &host, const BoostCallbackFunc &handler); #endif /** From 6b3912e40b9d3824210e055b98410b99c34b9183 Mon Sep 17 00:00:00 2001 From: Sumit Kumar Date: Fri, 29 Sep 2023 10:45:17 +0530 Subject: [PATCH 2/3] Add host Param in function discription --- src/TcpAdapterProxy.cpp | 12 ++++++------ src/WebSocketStream.h | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/TcpAdapterProxy.cpp b/src/TcpAdapterProxy.cpp index ddd92708..e7a66069 100644 --- a/src/TcpAdapterProxy.cpp +++ b/src/TcpAdapterProxy.cpp @@ -111,7 +111,7 @@ namespace aws { namespace iot { namespace securedtunneling { { std::ostringstream request_stream; request_stream << request; - std::string unfiltered_request_string = request_stream.str(); + std::string unfiltered_request_string = request_stream.str(); std::tuple token_filter_range = get_access_token_range(unfiltered_request_string); return (boost::format("%1%***ACCESS_TOKEN_REMOVED***%2%") % unfiltered_request_string.substr(0, std::get<0>(token_filter_range)) % @@ -803,13 +803,13 @@ namespace aws { namespace iot { namespace securedtunneling { } tac.wss = std::make_shared(tac.adapter_config, &log, tac.io_ctx); tac.wss->control_callback(std::bind(&tcp_adapter_proxy::handle_web_socket_control_message, this, std::ref(tac), std::placeholders::_1, std::placeholders::_2)); - + static std::string user_agent_string = (boost::format("localproxy %1% %2%-bit/boost-%3%.%4%.%5%/openssl-%6%.%7%.%8%/protobuf-%9%") % BOOST_PLATFORM % (sizeof(void*)*8) % (BOOST_VERSION / 100000) % ((BOOST_VERSION / 100) % 1000) % (BOOST_VERSION % 100) % (OPENSSL_VERSION_NUMBER >> 28) % ((OPENSSL_VERSION_NUMBER >> 20) & 0xF) % ((OPENSSL_VERSION_NUMBER >> 12) & 0xF) % google::protobuf::internal::VersionString(GOOGLE_PROTOBUF_VERSION) ).str(); - + //the actual work of this function starts here BOOST_LOG_SEV(log, info) << "Attempting to establish web socket connection with endpoint wss://" << tac.adapter_config.proxy_host << ":" << tac.adapter_config.proxy_port; @@ -1910,7 +1910,7 @@ namespace aws { namespace iot { namespace securedtunneling { std::uint16_t port_to_connect = boost::lexical_cast(src_port); BOOST_LOG_SEV(log, debug) << "Port to connect " << port_to_connect; server->resolver_.async_resolve(tac.bind_address_actual, src_port, - boost::asio::ip::resolver_base::passive, + boost::asio::ip::resolver_base::passive, [=, &tac](boost::system::error_code const &ec, tcp::resolver::results_type results) { if (ec) @@ -2072,7 +2072,7 @@ namespace aws { namespace iot { namespace securedtunneling { void tcp_adapter_proxy::async_setup_dest_tcp_socket(tcp_adapter_context &tac, string const & service_id, uint32_t const & connection_id, bool is_first_connection) { BOOST_LOG_SEV(log, trace) << "Setup destination tcp socket for service id" << service_id; - std::shared_ptr retry_config = + std::shared_ptr retry_config = std::make_shared(tac.io_ctx, GET_SETTING(settings, TCP_CONNECTION_RETRY_COUNT), GET_SETTING(settings, TCP_CONNECTION_RETRY_DELAY_MS), @@ -2266,4 +2266,4 @@ namespace aws { namespace iot { namespace securedtunneling { return false; } } -}}} +}}} \ No newline at end of file diff --git a/src/WebSocketStream.h b/src/WebSocketStream.h index 68a0b9eb..1d661e25 100644 --- a/src/WebSocketStream.h +++ b/src/WebSocketStream.h @@ -147,6 +147,7 @@ namespace aws { /** * Performs the SSL handshake between the localproxy and the proxy server asynchronously. * @param type The handshake type + * @param host the host subdoman and domain * @param handler the callback handler when the async operation is complete. */ void From 7652baf0b56bbe42d3b86a018cac541c9960a974 Mon Sep 17 00:00:00 2001 From: Sumit Kumar Date: Thu, 7 Dec 2023 18:33:43 +0530 Subject: [PATCH 3/3] Support of path in own reverse proxy endpoint. --- src/LocalproxyConfig.h | 4 ++++ src/TcpAdapterProxy.cpp | 14 +++++++------- src/main.cpp | 4 +++- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/LocalproxyConfig.h b/src/LocalproxyConfig.h index f553fbe1..1f98e32d 100644 --- a/src/LocalproxyConfig.h +++ b/src/LocalproxyConfig.h @@ -71,6 +71,10 @@ namespace aws { * The tunnel access token which the user gets when they open the tunnel. */ std::string access_token { }; + /** + * The reverse proxy tunnel endpoint path. + */ + std::string url_path { }; proxy_mode mode{ proxy_mode::UNKNOWN }; /** * A unique client-token to ensure only the agent which generated the token may connect to a tunnel diff --git a/src/TcpAdapterProxy.cpp b/src/TcpAdapterProxy.cpp index e7a66069..a03ad3fb 100644 --- a/src/TcpAdapterProxy.cpp +++ b/src/TcpAdapterProxy.cpp @@ -111,7 +111,7 @@ namespace aws { namespace iot { namespace securedtunneling { { std::ostringstream request_stream; request_stream << request; - std::string unfiltered_request_string = request_stream.str(); + std::string unfiltered_request_string = request_stream.str(); std::tuple token_filter_range = get_access_token_range(unfiltered_request_string); return (boost::format("%1%***ACCESS_TOKEN_REMOVED***%2%") % unfiltered_request_string.substr(0, std::get<0>(token_filter_range)) % @@ -803,13 +803,13 @@ namespace aws { namespace iot { namespace securedtunneling { } tac.wss = std::make_shared(tac.adapter_config, &log, tac.io_ctx); tac.wss->control_callback(std::bind(&tcp_adapter_proxy::handle_web_socket_control_message, this, std::ref(tac), std::placeholders::_1, std::placeholders::_2)); - + static std::string user_agent_string = (boost::format("localproxy %1% %2%-bit/boost-%3%.%4%.%5%/openssl-%6%.%7%.%8%/protobuf-%9%") % BOOST_PLATFORM % (sizeof(void*)*8) % (BOOST_VERSION / 100000) % ((BOOST_VERSION / 100) % 1000) % (BOOST_VERSION % 100) % (OPENSSL_VERSION_NUMBER >> 28) % ((OPENSSL_VERSION_NUMBER >> 20) & 0xF) % ((OPENSSL_VERSION_NUMBER >> 12) & 0xF) % google::protobuf::internal::VersionString(GOOGLE_PROTOBUF_VERSION) ).str(); - + //the actual work of this function starts here BOOST_LOG_SEV(log, info) << "Attempting to establish web socket connection with endpoint wss://" << tac.adapter_config.proxy_host << ":" << tac.adapter_config.proxy_port; @@ -901,7 +901,7 @@ namespace aws { namespace iot { namespace securedtunneling { //next do web socket upgrade - add two custom headers tac.wss->async_handshake_ex(tac.wss_response, tac.adapter_config.proxy_host.c_str(), - (boost::format("/tunnel?%1%=%2%")%PROXY_MODE_QUERY_PARAM % get_proxy_mode_string(tac.adapter_config.mode)).str(), + (boost::format("%1%?%2%=%3%")% tac.adapter_config.url_path.c_str() %PROXY_MODE_QUERY_PARAM % get_proxy_mode_string(tac.adapter_config.mode)).str(), [&](boost::beast::websocket::request_type &request) { request.set(boost::beast::http::field::sec_websocket_protocol, GET_SETTING(settings, WEB_SOCKET_SUBPROTOCOL)); @@ -1910,7 +1910,7 @@ namespace aws { namespace iot { namespace securedtunneling { std::uint16_t port_to_connect = boost::lexical_cast(src_port); BOOST_LOG_SEV(log, debug) << "Port to connect " << port_to_connect; server->resolver_.async_resolve(tac.bind_address_actual, src_port, - boost::asio::ip::resolver_base::passive, + boost::asio::ip::resolver_base::passive, [=, &tac](boost::system::error_code const &ec, tcp::resolver::results_type results) { if (ec) @@ -2072,7 +2072,7 @@ namespace aws { namespace iot { namespace securedtunneling { void tcp_adapter_proxy::async_setup_dest_tcp_socket(tcp_adapter_context &tac, string const & service_id, uint32_t const & connection_id, bool is_first_connection) { BOOST_LOG_SEV(log, trace) << "Setup destination tcp socket for service id" << service_id; - std::shared_ptr retry_config = + std::shared_ptr retry_config = std::make_shared(tac.io_ctx, GET_SETTING(settings, TCP_CONNECTION_RETRY_COUNT), GET_SETTING(settings, TCP_CONNECTION_RETRY_DELAY_MS), @@ -2266,4 +2266,4 @@ namespace aws { namespace iot { namespace securedtunneling { return false; } } -}}} \ No newline at end of file +}}} diff --git a/src/main.cpp b/src/main.cpp index 902e7960..6b74c19d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -161,6 +161,7 @@ bool process_cli(int argc, char ** argv, LocalproxyConfig &cfg, ptree &settings, ("access-token,t", value()->required(), "Client access token") ("client-token,i", value(), "Optional Client Token") ("proxy-endpoint,e", value(), "Endpoint of proxy server with port (if not default 443). Example: data.tunneling.iot.us-east-1.amazonaws.com:443") + ("endpoint-path,p", value()->default_value("/tunnel"), "Endpoint path of proxy server if need extra path. Example: reverse-proxy.domain.com/aws-data-service/tunnel, so you connect with -e reverse-proxy.domain.com -p /aws-data-service/tunnel") ("region,r", value(), "Endpoint region where tunnel exists. Mutually exclusive flag with --proxy-endpoint") ("source-listen-port,s", value(), "Sets the mappings between source listening ports and service identifier. Example: SSH1=5555 or 5555") ("destination-app,d", value(), "Sets the mappings between the endpoint(address:port/port) and service identifier. Example: SSH1=127.0.0.1:22 or 22") @@ -200,7 +201,7 @@ bool process_cli(int argc, char ** argv, LocalproxyConfig &cfg, ptree &settings, store(parse_config_file(vm["config"].as().c_str(), cliargs_desc), vm); } //either way, parse from environment - store(parse_environment(cliargs_desc, + store(parse_environment(cliargs_desc, [](std::string name) -> std::string { if (name == ACCESS_TOKEN_ENV_VARIABLE) @@ -234,6 +235,7 @@ bool process_cli(int argc, char ** argv, LocalproxyConfig &cfg, ptree &settings, BOOST_LOG_TRIVIAL(warning) << "Found access token supplied via CLI arg. Consider using environment variable " << ACCESS_TOKEN_ENV_VARIABLE << " instead"; } cfg.access_token = vm["access-token"].as(); + cfg.url_path = vm["endpoint-path"].as(); if (vm.count("client-token") != 0) {