diff options
author | Tor Brede Vekterli <vekterli@oath.com> | 2018-12-03 15:02:00 +0000 |
---|---|---|
committer | Tor Brede Vekterli <vekterli@oath.com> | 2018-12-03 15:24:12 +0000 |
commit | 3c86000931f45a0f0aa82a6cdf59ea82ea53b47e (patch) | |
tree | cbfb0867dfd696cddc6adf5782fc405432f0aa07 /vespalib/src/tests | |
parent | 5bf52410ce593878770d945f2a7941dc2082b466 (diff) |
Support auto-reloading of TLS config in C++ implementation
By default reloads every 60 minutes. This also reloads all peer
authorization rules. Files referenced by the TLS config are reloaded
transitively.
If reloading fails a warning will be logged and the existing config
will continue to be in effect until the next reload time.
Diffstat (limited to 'vespalib/src/tests')
6 files changed, 129 insertions, 0 deletions
diff --git a/vespalib/src/tests/net/tls/auto_reloading_tls_crypto_engine/CMakeLists.txt b/vespalib/src/tests/net/tls/auto_reloading_tls_crypto_engine/CMakeLists.txt new file mode 100644 index 00000000000..c3361ee1fa3 --- /dev/null +++ b/vespalib/src/tests/net/tls/auto_reloading_tls_crypto_engine/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(vespalib_net_tls_auto_reloading_tls_crypto_engine_test_app TEST + SOURCES + auto_reloading_tls_crypto_engine_test.cpp + DEPENDS + vespalib +) +vespa_add_test(NAME vespalib_net_tls_auto_reloading_tls_crypto_engine_test_app + COMMAND vespalib_net_tls_auto_reloading_tls_crypto_engine_test_app) + diff --git a/vespalib/src/tests/net/tls/auto_reloading_tls_crypto_engine/auto_reloading_tls_crypto_engine_test.cpp b/vespalib/src/tests/net/tls/auto_reloading_tls_crypto_engine/auto_reloading_tls_crypto_engine_test.cpp new file mode 100644 index 00000000000..7926f71e812 --- /dev/null +++ b/vespalib/src/tests/net/tls/auto_reloading_tls_crypto_engine/auto_reloading_tls_crypto_engine_test.cpp @@ -0,0 +1,87 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/vespalib/io/fileutil.h> +#include <vespa/vespalib/net/tls/auto_reloading_tls_crypto_engine.h> +#include <vespa/vespalib/net/tls/transport_security_options.h> +#include <vespa/vespalib/net/tls/transport_security_options_reading.h> +#include <vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h> +#include <vespa/vespalib/testkit/test_kit.h> + +#include <chrono> + +#include <openssl/ssl.h> + +using namespace vespalib; +using namespace vespalib::net::tls; +using namespace std::chrono_literals; + +constexpr const char* cert1_pem = R"(-----BEGIN CERTIFICATE----- +MIIBszCCAVgCCQCXsYrXQWS0bzAKBggqhkjOPQQDAjBkMQswCQYDVQQGEwJVUzEU +MBIGA1UEBwwLTG9vbmV5VmlsbGUxDTALBgNVBAoMBEFDTUUxFTATBgNVBAsMDEFD +TUUgdGVzdCBDQTEZMBcGA1UEAwwQYWNtZS5leGFtcGxlLmNvbTAeFw0xODExMzAx +NDA0MzdaFw00NjA0MTcxNDA0MzdaMF4xCzAJBgNVBAYTAlVTMRQwEgYDVQQHDAtM +b29uZXlWaWxsZTEeMBwGA1UECgwVV2lsZS4gRS4gQ295b3RlLCBMdGQuMRkwFwYD +VQQDDBB3aWxlLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +cQN3UOKg30+h1EYgAxQukAYgzbx7VmcrOBheD7AaJoTUnaRn9xQ6j0t4eKNa6x/1 +K7luNL+AfaJiCQLrbalVoDAKBggqhkjOPQQDAgNJADBGAiEAyzvCt9qJCtY/7Qi1 +2Jzb1BTvAPOszeBFRzovMatQSUICIQDuT6cyV3yigoxLZbn5In3Sx+qUPFPCMI8O +X5yKMXNkmQ== +-----END CERTIFICATE-----)"; + +constexpr const char* cert2_pem = R"(-----BEGIN CERTIFICATE----- +MIIBsjCCAVgCCQCXsYrXQWS0cDAKBggqhkjOPQQDAjBkMQswCQYDVQQGEwJVUzEU +MBIGA1UEBwwLTG9vbmV5VmlsbGUxDTALBgNVBAoMBEFDTUUxFTATBgNVBAsMDEFD +TUUgdGVzdCBDQTEZMBcGA1UEAwwQYWNtZS5leGFtcGxlLmNvbTAeFw0xODExMzAx +NDA0MzdaFw00NjA0MTcxNDA0MzdaMF4xCzAJBgNVBAYTAlVTMRQwEgYDVQQHDAtM +b29uZXlWaWxsZTEeMBwGA1UECgwVV2lsZS4gRS4gQ295b3RlLCBMdGQuMRkwFwYD +VQQDDBB3aWxlLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +cQN3UOKg30+h1EYgAxQukAYgzbx7VmcrOBheD7AaJoTUnaRn9xQ6j0t4eKNa6x/1 +K7luNL+AfaJiCQLrbalVoDAKBggqhkjOPQQDAgNIADBFAiEAluT52NkVdGBRZJxo +PhL9XBnJJfzvG5GKXIK/iZgFuYkCIFLp+SIQ5Nc1+NzrU2ii/mkzCgC4N/nOWu9H +88OP2wnm +-----END CERTIFICATE-----)"; + +void write_file(vespalib::stringref path, vespalib::stringref data) { + File f(path); + f.open(File::CREATE | File::TRUNC); + f.write(data.data(), data.size(), 0); +} + +struct Fixture { + std::unique_ptr<AutoReloadingTlsCryptoEngine> engine; + explicit Fixture(AutoReloadingTlsCryptoEngine::TimeInterval reload_interval) { + write_file("test_cert.pem", cert1_pem); + // Must be done after file has been written + engine = std::make_unique<AutoReloadingTlsCryptoEngine>("test_config.json", reload_interval); + } + + ~Fixture() { + engine.reset(); + if (fileExists("test_cert.pem")) { + unlink("test_cert.pem"); // just crash the test if this throws + } + } + + vespalib::string current_cert_chain() const { + auto impl = engine->acquire_current_engine(); + auto& ctx_impl = dynamic_cast<impl::OpenSslTlsContextImpl&>(*impl->tls_context()); + return ctx_impl.transport_security_options().cert_chain_pem(); + } +}; + +TEST_FF("Config reloading transitively loads updated files", Fixture(50ms), TimeBomb(60)) { + auto current_certs = f1.current_cert_chain(); + ASSERT_EQUAL(cert1_pem, current_certs); + + write_file("test_cert.pem.tmp", cert2_pem); + rename("test_cert.pem.tmp", "test_cert.pem", false, false); // We expect this to be an atomic rename under the hood + + current_certs = f1.current_cert_chain(); + while (current_certs != cert2_pem) { + std::this_thread::sleep_for(10ms); + current_certs = f1.current_cert_chain(); + } + // If the config is never reloaded, test will go boom. +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/vespalib/src/tests/net/tls/auto_reloading_tls_crypto_engine/test_ca.pem b/vespalib/src/tests/net/tls/auto_reloading_tls_crypto_engine/test_ca.pem new file mode 100644 index 00000000000..e6f03fd52e3 --- /dev/null +++ b/vespalib/src/tests/net/tls/auto_reloading_tls_crypto_engine/test_ca.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBuDCCAV4CCQDDjrv2sJswHTAKBggqhkjOPQQDAjBkMQswCQYDVQQGEwJVUzEU +MBIGA1UEBwwLTG9vbmV5VmlsbGUxDTALBgNVBAoMBEFDTUUxFTATBgNVBAsMDEFD +TUUgdGVzdCBDQTEZMBcGA1UEAwwQYWNtZS5leGFtcGxlLmNvbTAeFw0xODExMzAx +NDA0MzdaFw00NjA0MTcxNDA0MzdaMGQxCzAJBgNVBAYTAlVTMRQwEgYDVQQHDAtM +b29uZXlWaWxsZTENMAsGA1UECgwEQUNNRTEVMBMGA1UECwwMQUNNRSB0ZXN0IENB +MRkwFwYDVQQDDBBhY21lLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAEtCn9+LpDQIHZwS2UQq0oh3Q3zetb/9fjgSjZH3aRA3Cpy9I6j5ydTSIA +ox1VX9OjvM4RRjUtjBDgA/+TwJcD2DAKBggqhkjOPQQDAgNIADBFAiA0xOB7h7FH +rrPVNbyWdPyCB4y6P+BsOywyPXzE+p6TvQIhAJfuj53MUlBkE0Hc5bvWI5VK4Qmb +8Q4SAXR9bhPcnpz6 +-----END CERTIFICATE----- diff --git a/vespalib/src/tests/net/tls/auto_reloading_tls_crypto_engine/test_config.json b/vespalib/src/tests/net/tls/auto_reloading_tls_crypto_engine/test_config.json new file mode 100644 index 00000000000..2b2322d928f --- /dev/null +++ b/vespalib/src/tests/net/tls/auto_reloading_tls_crypto_engine/test_config.json @@ -0,0 +1,7 @@ +{ + "files":{ + "private-key": "test_key.pem", + "ca-certificates": "test_ca.pem", + "certificates": "test_cert.pem" + } +} diff --git a/vespalib/src/tests/net/tls/auto_reloading_tls_crypto_engine/test_key.pem b/vespalib/src/tests/net/tls/auto_reloading_tls_crypto_engine/test_key.pem new file mode 100644 index 00000000000..a90d7834216 --- /dev/null +++ b/vespalib/src/tests/net/tls/auto_reloading_tls_crypto_engine/test_key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIHSvTpHIslB+IHRZHMMBuu5Tj2iMVsH9eFNk3RwCp1r3oAoGCCqGSM49 +AwEHoUQDQgAEcQN3UOKg30+h1EYgAxQukAYgzbx7VmcrOBheD7AaJoTUnaRn9xQ6 +j0t4eKNa6x/1K7luNL+AfaJiCQLrbalVoA== +-----END EC PRIVATE KEY----- diff --git a/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp b/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp index 1a442dc54f0..c1e2c0b5f49 100644 --- a/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp +++ b/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp @@ -19,6 +19,14 @@ TEST("can load TLS credentials via config file") { EXPECT_EQUAL("My certificate chain\n", opts->cert_chain_pem()); } +TEST("copying options without private key does, in fact, not include private key") { + auto opts = read_options_from_json_file("ok_config.json"); + auto cloned = opts->copy_without_private_key(); + EXPECT_EQUAL("", cloned.private_key_pem()); + EXPECT_EQUAL("My CA certificates\n", cloned.ca_certs_pem()); + EXPECT_EQUAL("My certificate chain\n", cloned.cert_chain_pem()); +} + TEST("missing JSON file throws exception") { EXPECT_EXCEPTION(read_options_from_json_file("missing_config.json"), IllegalArgumentException, "TLS config file 'missing_config.json' could not be read"); |