diff options
-rw-r--r-- | fbench/CMakeLists.txt | 1 | ||||
-rw-r--r-- | fbench/src/httpclient/CMakeLists.txt | 1 | ||||
-rw-r--r-- | fbench/src/httpclient/httpclient.cpp | 16 | ||||
-rw-r--r-- | fbench/src/httpclient/httpclient.h | 4 | ||||
-rw-r--r-- | fbench/src/test/authority/CMakeLists.txt | 10 | ||||
-rw-r--r-- | fbench/src/test/authority/authority_test.cpp | 89 | ||||
-rw-r--r-- | fbench/src/util/CMakeLists.txt | 3 | ||||
-rw-r--r-- | fbench/src/util/authority.cpp | 42 | ||||
-rw-r--r-- | fbench/src/util/authority.h | 30 | ||||
-rw-r--r-- | fnet/src/tests/connect/connect_test.cpp | 2 | ||||
-rw-r--r-- | vespalib/src/vespa/vespalib/net/crypto_engine.h | 6 | ||||
-rw-r--r-- | vespalib/src/vespa/vespalib/net/tls/auto_reloading_tls_crypto_engine.cpp | 12 | ||||
-rw-r--r-- | vespalib/src/vespa/vespalib/net/tls/auto_reloading_tls_crypto_engine.h | 2 | ||||
-rw-r--r-- | vespalib/src/vespa/vespalib/net/tls/maybe_tls_crypto_engine.h | 2 | ||||
-rw-r--r-- | vespalib/src/vespa/vespalib/net/tls/tls_crypto_engine.h | 2 |
15 files changed, 210 insertions, 12 deletions
diff --git a/fbench/CMakeLists.txt b/fbench/CMakeLists.txt index 5cc56786227..3da632d98a6 100644 --- a/fbench/CMakeLists.txt +++ b/fbench/CMakeLists.txt @@ -15,6 +15,7 @@ vespa_define_module( TESTS src/test + src/test/authority ) vespa_install_script(util/resultfilter.pl vespa-fbench-result-filter.pl bin) diff --git a/fbench/src/httpclient/CMakeLists.txt b/fbench/src/httpclient/CMakeLists.txt index 5f3333128b3..a28f3666383 100644 --- a/fbench/src/httpclient/CMakeLists.txt +++ b/fbench/src/httpclient/CMakeLists.txt @@ -3,5 +3,6 @@ vespa_add_library(fbench_httpclient STATIC SOURCES httpclient.cpp DEPENDS + fbench_util fastos ) diff --git a/fbench/src/httpclient/httpclient.cpp b/fbench/src/httpclient/httpclient.cpp index 99134a6e297..9615a6e6df7 100644 --- a/fbench/src/httpclient/httpclient.cpp +++ b/fbench/src/httpclient/httpclient.cpp @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "httpclient.h" #include <vespa/vespalib/net/socket_spec.h> +#include <util/authority.h> #include <cassert> #include <cstring> @@ -29,7 +30,8 @@ HTTPClient::HTTPClient(vespalib::CryptoEngine::SP engine, const char *hostname, _keepAlive(keepAlive), _headerBenchmarkdataCoverage(headerBenchmarkdataCoverage), _extraHeaders(extraHeaders), - _authority(authority), + _sni_spec(make_sni_spec(authority, hostname, port, _engine->use_tls_when_client())), + _host_header_value(make_host_header_value(_sni_spec, _engine->use_tls_when_client())), _reuseCount(0), _bufsize(10240), _buf(new char[_bufsize]), @@ -51,11 +53,6 @@ HTTPClient::HTTPClient(vespalib::CryptoEngine::SP engine, const char *hostname, _dataDone(false), _reader(NULL) { - if (_authority == "") { - char tmp[1024]; - snprintf(tmp, 1024, "%s:%d", hostname, port); - _authority = tmp; - } } bool @@ -70,8 +67,7 @@ HTTPClient::connect_socket() if (!handle.valid()) { return false; } - _socket = vespalib::SyncCryptoSocket::create_client(*_engine, std::move(handle), - vespalib::SocketSpec::from_host_port(_hostname, _port)); + _socket = vespalib::SyncCryptoSocket::create_client(*_engine, std::move(handle), _sni_spec); return bool(_socket); } @@ -153,14 +149,14 @@ HTTPClient::Connect(const char *url, bool usePost, const char *content, int cLen "Content-Length: %d\r\n" "%s" "\r\n", - url, _authority.c_str(), cLen, headers.c_str()); + url, _host_header_value.c_str(), cLen, headers.c_str()); } else { snprintf(req, req_max, "GET %s HTTP/1.1\r\n" "Host: %s\r\n" "%s" "\r\n", - url, _authority.c_str(), headers.c_str()); + url, _host_header_value.c_str(), headers.c_str()); } // try to reuse connection if keep-alive is enabled diff --git a/fbench/src/httpclient/httpclient.h b/fbench/src/httpclient/httpclient.h index 9c3ccd437d1..cad01826db7 100644 --- a/fbench/src/httpclient/httpclient.h +++ b/fbench/src/httpclient/httpclient.h @@ -6,6 +6,7 @@ #include <vespa/vespalib/net/sync_crypto_socket.h> #include <vespa/vespalib/net/crypto_engine.h> #include <vespa/vespalib/net/socket_address.h> +#include <vespa/vespalib/net/socket_spec.h> /** * This class implements a HTTP client that may be used to fetch @@ -99,7 +100,8 @@ protected: bool _keepAlive; bool _headerBenchmarkdataCoverage; std::string _extraHeaders; - std::string _authority; + vespalib::SocketSpec _sni_spec; + std::string _host_header_value; uint64_t _reuseCount; size_t _bufsize; diff --git a/fbench/src/test/authority/CMakeLists.txt b/fbench/src/test/authority/CMakeLists.txt new file mode 100644 index 00000000000..00f804f43f6 --- /dev/null +++ b/fbench/src/test/authority/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(fbench_authority_test_app TEST + SOURCES + authority_test.cpp + DEPENDS + fbench_util + vespalib + gtest +) +vespa_add_test(NAME fbench_authority_test_app COMMAND fbench_authority_test_app) diff --git a/fbench/src/test/authority/authority_test.cpp b/fbench/src/test/authority/authority_test.cpp new file mode 100644 index 00000000000..f5d0afa32bb --- /dev/null +++ b/fbench/src/test/authority/authority_test.cpp @@ -0,0 +1,89 @@ +// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <util/authority.h> +#include <vespa/vespalib/gtest/gtest.h> + +using vespalib::SocketSpec; + +//----------------------------------------------------------------------------- + +TEST(MakeSNISpecTest, host_port_is_parsed_as_expected) { + EXPECT_EQ(make_sni_spec("my_host:123", "fallback", 456, false).host(), "my_host"); + EXPECT_EQ(make_sni_spec("my_host:123", "fallback", 456, true).host(), "my_host"); + EXPECT_EQ(make_sni_spec("my_host:123", "fallback", 456, false).port(), 123); + EXPECT_EQ(make_sni_spec("my_host:123", "fallback", 456, true).port(), 123); +} + +TEST(MakeSNISpecTest, user_info_is_stripped) { + EXPECT_EQ(make_sni_spec("myuser:deprecated@my_host:123", "fallback", 456, false).host(), "my_host"); + EXPECT_EQ(make_sni_spec("myuser:deprecated@my_host:123", "fallback", 456, true).host(), "my_host"); + EXPECT_EQ(make_sni_spec("myuser:deprecated@my_host:123", "fallback", 456, false).port(), 123); + EXPECT_EQ(make_sni_spec("myuser:deprecated@my_host:123", "fallback", 456, true).port(), 123); +} + +TEST(MakeSNISpecTest, port_can_be_skipped) { + EXPECT_EQ(make_sni_spec("my_host", "fallback", 456, false).host(), "my_host"); + EXPECT_EQ(make_sni_spec("my_host", "fallback", 456, true).host(), "my_host"); + EXPECT_EQ(make_sni_spec("my_host", "fallback", 456, false).port(), 80); + EXPECT_EQ(make_sni_spec("my_host", "fallback", 456, true).port(), 443); +} + +TEST(MakeSNISpecTest, quoted_ip_addresses_work_as_expected) { + EXPECT_EQ(make_sni_spec("[::1]:123", "fallback", 456, false).host(), "::1"); + EXPECT_EQ(make_sni_spec("[::1]:123", "fallback", 456, true).host(), "::1"); + EXPECT_EQ(make_sni_spec("[::1]:123", "fallback", 456, false).port(), 123); + EXPECT_EQ(make_sni_spec("[::1]:123", "fallback", 456, true).port(), 123); + EXPECT_EQ(make_sni_spec("[::1]", "fallback", 456, false).host(), "::1"); + EXPECT_EQ(make_sni_spec("[::1]", "fallback", 456, true).host(), "::1"); + EXPECT_EQ(make_sni_spec("[::1]", "fallback", 456, false).port(), 80); + EXPECT_EQ(make_sni_spec("[::1]", "fallback", 456, true).port(), 443); +} + +TEST(MakeSNISpecTest, supplied_host_port_is_used_as_fallback) { + EXPECT_EQ(make_sni_spec("", "fallback", 456, false).host(), "fallback"); + EXPECT_EQ(make_sni_spec("", "fallback", 456, true).host(), "fallback"); + EXPECT_EQ(make_sni_spec("", "fallback", 456, false).port(), 456); + EXPECT_EQ(make_sni_spec("", "fallback", 456, true).port(), 456); +} + +//----------------------------------------------------------------------------- + +std::string make_host_header_value(const vespalib::SocketSpec &sni_spec, bool use_https); + +TEST(MakeHostHeaderValueTest, host_port_is_formatted_as_expected) { + auto my_spec = SocketSpec::from_host_port("myhost", 123); + EXPECT_EQ(make_host_header_value(my_spec, false), "myhost:123"); + EXPECT_EQ(make_host_header_value(my_spec, true), "myhost:123"); +} + +TEST(MakeHostHeaderValueTest, inappropriate_spec_gives_empty_host_value) { + std::vector<SocketSpec> bad_specs = { + SocketSpec::invalid, + SocketSpec::from_port(123), + SocketSpec::from_name("foo"), + SocketSpec::from_path("bar") + }; + for (const auto &spec: bad_specs) { + EXPECT_EQ(make_host_header_value(spec, false), ""); + EXPECT_EQ(make_host_header_value(spec, true), ""); + } +} + +TEST(MakeHostHeaderValueTest, default_port_is_omitted) { + auto spec1 = SocketSpec::from_host_port("myhost", 80); + auto spec2 = SocketSpec::from_host_port("myhost", 443); + EXPECT_EQ(make_host_header_value(spec1, false), "myhost"); + EXPECT_EQ(make_host_header_value(spec1, true), "myhost:80"); + EXPECT_EQ(make_host_header_value(spec2, false), "myhost:443"); + EXPECT_EQ(make_host_header_value(spec2, true), "myhost"); +} + +TEST(MakeHostHeaderValueTest, ipv6_addresses_are_quoted) { + auto my_spec = SocketSpec::from_host_port("::1", 123); + EXPECT_EQ(make_host_header_value(my_spec, false), "[::1]:123"); + EXPECT_EQ(make_host_header_value(my_spec, true), "[::1]:123"); +} + +//----------------------------------------------------------------------------- + +GTEST_MAIN_RUN_ALL_TESTS() diff --git a/fbench/src/util/CMakeLists.txt b/fbench/src/util/CMakeLists.txt index 47cc46ffc8f..3cdff26ce16 100644 --- a/fbench/src/util/CMakeLists.txt +++ b/fbench/src/util/CMakeLists.txt @@ -1,8 +1,9 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. vespa_add_library(fbench_util STATIC SOURCES + authority.cpp + clientstatus.cpp filereader.cpp timer.cpp - clientstatus.cpp DEPENDS ) diff --git a/fbench/src/util/authority.cpp b/fbench/src/util/authority.cpp new file mode 100644 index 00000000000..6247c72d9b0 --- /dev/null +++ b/fbench/src/util/authority.cpp @@ -0,0 +1,42 @@ +// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "authority.h" +#include <vespa/vespalib/util/stringfmt.h> +#include <cassert> + +namespace { + +int default_port(bool use_https) { return use_https ? 443 : 80; } + +} + +vespalib::SocketSpec make_sni_spec(const std::string &authority, const char *hostname, int port, bool use_https) { + if (authority.empty()) { + return vespalib::SocketSpec::from_host_port(hostname, port); + } + auto split = authority.rfind('@'); + std::string spec_str = (split == std::string::npos) ? authority : authority.substr(split + 1); + auto a = spec_str.rfind(':'); + auto b = spec_str.rfind(']'); + bool has_port = (a != std::string::npos) && ((b == std::string::npos) || (a > b)); + if (has_port) { + spec_str = "tcp/" + spec_str; + } else { + spec_str = vespalib::make_string("tcp/%s:%d", spec_str.c_str(), default_port(use_https)); + } + // use SocketSpec parser to ensure ipv6 addresses are dequoted + return vespalib::SocketSpec(spec_str); +} + +std::string make_host_header_value(const vespalib::SocketSpec &sni_spec, bool use_https) { + if (sni_spec.host().empty()) { + return ""; + } + if (sni_spec.port() == default_port(use_https)) { + return sni_spec.host(); + } + // use SocketSpec formatter to ensure ipv6 addresses are quoted + std::string spec_str = sni_spec.spec(); + assert(spec_str.find("tcp/") == 0); + return spec_str.substr(4); +} diff --git a/fbench/src/util/authority.h b/fbench/src/util/authority.h new file mode 100644 index 00000000000..49dab4a29fd --- /dev/null +++ b/fbench/src/util/authority.h @@ -0,0 +1,30 @@ +// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <vespa/vespalib/net/socket_spec.h> + +/** + * Assemble an SNI (Server Name Indication) spec that will be used + * when handshaking over TLS. The authority will be used if + * non-empty. Hostname/port will be used as fall-back. Note that the + * SNI spec will also be used to generate the Host header used in + * subsequent HTTP requests. + * + * @return sni spec + * @param authority user-provided authority + * @param hostname name of the host we are connecting to + * @param port which port we are connecting to + * @param use_https are we using https? (TLS) + **/ +vespalib::SocketSpec make_sni_spec(const std::string &authority, const char *hostname, int port, bool use_https); + +/** + * Use an SNI spec to generate a matching Host header to be used in + * HTTP requests. Note that default port numbers will be omitted. + * + * @return host header value + * @param sni_spec SNI spec + * @param use_https are we using https? (TLS) + **/ +std::string make_host_header_value(const vespalib::SocketSpec &sni_spec, bool use_https); diff --git a/fnet/src/tests/connect/connect_test.cpp b/fnet/src/tests/connect/connect_test.cpp index d94b6759077..62000efb682 100644 --- a/fnet/src/tests/connect/connect_test.cpp +++ b/fnet/src/tests/connect/connect_test.cpp @@ -65,6 +65,8 @@ struct BlockingCryptoEngine : public CryptoEngine { Gate handshake_work_enter; Gate handshake_work_exit; Gate handshake_socket_deleted; + bool use_tls_when_client() const override { return false; } + bool always_use_tls_when_server() const override { return false; } CryptoSocket::UP create_client_crypto_socket(SocketHandle socket, const SocketSpec &) override { return std::make_unique<BlockingCryptoSocket>(std::move(socket), handshake_work_enter, handshake_work_exit, handshake_socket_deleted); diff --git a/vespalib/src/vespa/vespalib/net/crypto_engine.h b/vespalib/src/vespa/vespalib/net/crypto_engine.h index 4deacf9a6c7..71511b8a552 100644 --- a/vespalib/src/vespa/vespalib/net/crypto_engine.h +++ b/vespalib/src/vespa/vespalib/net/crypto_engine.h @@ -19,6 +19,8 @@ class SocketSpec; **/ struct CryptoEngine { using SP = std::shared_ptr<CryptoEngine>; + virtual bool use_tls_when_client() const = 0; + virtual bool always_use_tls_when_server() const = 0; virtual CryptoSocket::UP create_client_crypto_socket(SocketHandle socket, const SocketSpec &spec) = 0; virtual CryptoSocket::UP create_server_crypto_socket(SocketHandle socket) = 0; virtual ~CryptoEngine(); @@ -29,6 +31,8 @@ struct CryptoEngine { * Crypto engine without encryption. **/ struct NullCryptoEngine : public CryptoEngine { + bool use_tls_when_client() const override { return false; } + bool always_use_tls_when_server() const override { return false; } CryptoSocket::UP create_client_crypto_socket(SocketHandle socket, const SocketSpec &spec) override; CryptoSocket::UP create_server_crypto_socket(SocketHandle socket) override; }; @@ -39,6 +43,8 @@ struct NullCryptoEngine : public CryptoEngine { * from TLS. **/ struct XorCryptoEngine : public CryptoEngine { + bool use_tls_when_client() const override { return false; } + bool always_use_tls_when_server() const override { return false; } CryptoSocket::UP create_client_crypto_socket(SocketHandle socket, const SocketSpec &spec) override; CryptoSocket::UP create_server_crypto_socket(SocketHandle socket) override; }; diff --git a/vespalib/src/vespa/vespalib/net/tls/auto_reloading_tls_crypto_engine.cpp b/vespalib/src/vespa/vespalib/net/tls/auto_reloading_tls_crypto_engine.cpp index c425ab75ce8..bdb2402adbc 100644 --- a/vespalib/src/vespa/vespalib/net/tls/auto_reloading_tls_crypto_engine.cpp +++ b/vespalib/src/vespa/vespalib/net/tls/auto_reloading_tls_crypto_engine.cpp @@ -99,6 +99,18 @@ CryptoSocket::UP AutoReloadingTlsCryptoEngine::create_server_crypto_socket(Socke return acquire_current_engine()->create_server_crypto_socket(std::move(socket)); } +bool +AutoReloadingTlsCryptoEngine::use_tls_when_client() const +{ + return acquire_current_engine()->use_tls_when_client(); +} + +bool +AutoReloadingTlsCryptoEngine::always_use_tls_when_server() const +{ + return acquire_current_engine()->always_use_tls_when_server(); +} + std::unique_ptr<TlsCryptoSocket> AutoReloadingTlsCryptoEngine::create_tls_client_crypto_socket(SocketHandle socket, const SocketSpec &spec) { return acquire_current_engine()->create_tls_client_crypto_socket(std::move(socket), spec); diff --git a/vespalib/src/vespa/vespalib/net/tls/auto_reloading_tls_crypto_engine.h b/vespalib/src/vespa/vespalib/net/tls/auto_reloading_tls_crypto_engine.h index e268cbc8f1a..1b80b782daf 100644 --- a/vespalib/src/vespa/vespalib/net/tls/auto_reloading_tls_crypto_engine.h +++ b/vespalib/src/vespa/vespalib/net/tls/auto_reloading_tls_crypto_engine.h @@ -47,6 +47,8 @@ public: CryptoSocket::UP create_client_crypto_socket(SocketHandle socket, const SocketSpec &spec) override; CryptoSocket::UP create_server_crypto_socket(SocketHandle socket) override; + bool use_tls_when_client() const override; + bool always_use_tls_when_server() const override; std::unique_ptr<TlsCryptoSocket> create_tls_client_crypto_socket(SocketHandle socket, const SocketSpec &spec) override; std::unique_ptr<TlsCryptoSocket> create_tls_server_crypto_socket(SocketHandle socket) override; }; diff --git a/vespalib/src/vespa/vespalib/net/tls/maybe_tls_crypto_engine.h b/vespalib/src/vespa/vespalib/net/tls/maybe_tls_crypto_engine.h index 147a770bc8f..ece7d094c54 100644 --- a/vespalib/src/vespa/vespalib/net/tls/maybe_tls_crypto_engine.h +++ b/vespalib/src/vespa/vespalib/net/tls/maybe_tls_crypto_engine.h @@ -28,6 +28,8 @@ public: : _null_engine(std::make_shared<NullCryptoEngine>()), _tls_engine(std::move(tls_engine)), _use_tls_when_client(use_tls_when_client) {} + bool use_tls_when_client() const override { return _use_tls_when_client; } + bool always_use_tls_when_server() const override { return false; } CryptoSocket::UP create_client_crypto_socket(SocketHandle socket, const SocketSpec &spec) override; CryptoSocket::UP create_server_crypto_socket(SocketHandle socket) override; }; diff --git a/vespalib/src/vespa/vespalib/net/tls/tls_crypto_engine.h b/vespalib/src/vespa/vespalib/net/tls/tls_crypto_engine.h index 5e760cf5585..444a817b357 100644 --- a/vespalib/src/vespa/vespalib/net/tls/tls_crypto_engine.h +++ b/vespalib/src/vespa/vespalib/net/tls/tls_crypto_engine.h @@ -27,6 +27,8 @@ public: net::tls::AuthorizationMode authz_mode = net::tls::AuthorizationMode::Enforce); std::unique_ptr<TlsCryptoSocket> create_tls_client_crypto_socket(SocketHandle socket, const SocketSpec &spec) override; std::unique_ptr<TlsCryptoSocket> create_tls_server_crypto_socket(SocketHandle socket) override; + bool use_tls_when_client() const override { return true; } + bool always_use_tls_when_server() const override { return true; } CryptoSocket::UP create_client_crypto_socket(SocketHandle socket, const SocketSpec &spec) override { return create_tls_client_crypto_socket(std::move(socket), spec); } |