summaryrefslogtreecommitdiffstats
path: root/vespalib
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@oath.com>2018-09-07 11:08:20 +0000
committerTor Brede Vekterli <vekterli@oath.com>2018-09-07 11:12:02 +0000
commit3bc0bbe2bc406a51b40ac19f42298415eb76938b (patch)
treeb9c8064c4b59ef2ede989d2e39ec56c6471b2ed1 /vespalib
parente8daf30cbd919f98376c85f00cb987330faca2f6 (diff)
Add TLS config file support with proposed JSON structure
Diffstat (limited to 'vespalib')
-rw-r--r--vespalib/CMakeLists.txt1
-rw-r--r--vespalib/src/tests/net/tls/transport_options/CMakeLists.txt10
-rw-r--r--vespalib/src/tests/net/tls/transport_options/dummy_ca_certs.txt1
-rw-r--r--vespalib/src/tests/net/tls/transport_options/dummy_certs.txt1
-rw-r--r--vespalib/src/tests/net/tls/transport_options/dummy_privkey.txt1
-rw-r--r--vespalib/src/tests/net/tls/transport_options/ok_config.json7
-rw-r--r--vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp65
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt1
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp102
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.h20
10 files changed, 209 insertions, 0 deletions
diff --git a/vespalib/CMakeLists.txt b/vespalib/CMakeLists.txt
index a4b3f1e643c..4ae98be29b6 100644
--- a/vespalib/CMakeLists.txt
+++ b/vespalib/CMakeLists.txt
@@ -57,6 +57,7 @@ vespa_define_module(
src/tests/net/socket
src/tests/net/socket_spec
src/tests/net/tls/openssl_impl
+ src/tests/net/tls/transport_options
src/tests/objects/nbostream
src/tests/optimized
src/tests/printable
diff --git a/vespalib/src/tests/net/tls/transport_options/CMakeLists.txt b/vespalib/src/tests/net/tls/transport_options/CMakeLists.txt
new file mode 100644
index 00000000000..ee1e2477708
--- /dev/null
+++ b/vespalib/src/tests/net/tls/transport_options/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_transport_options_test_app TEST
+ SOURCES
+ transport_options_reading_test.cpp
+ DEPENDS
+ vespalib
+)
+vespa_add_test(NAME vespalib_net_tls_transport_options_test_app
+ COMMAND vespalib_net_tls_transport_options_test_app)
+
diff --git a/vespalib/src/tests/net/tls/transport_options/dummy_ca_certs.txt b/vespalib/src/tests/net/tls/transport_options/dummy_ca_certs.txt
new file mode 100644
index 00000000000..b617f6f17e4
--- /dev/null
+++ b/vespalib/src/tests/net/tls/transport_options/dummy_ca_certs.txt
@@ -0,0 +1 @@
+My CA certificates
diff --git a/vespalib/src/tests/net/tls/transport_options/dummy_certs.txt b/vespalib/src/tests/net/tls/transport_options/dummy_certs.txt
new file mode 100644
index 00000000000..088b91ff770
--- /dev/null
+++ b/vespalib/src/tests/net/tls/transport_options/dummy_certs.txt
@@ -0,0 +1 @@
+My certificate chain
diff --git a/vespalib/src/tests/net/tls/transport_options/dummy_privkey.txt b/vespalib/src/tests/net/tls/transport_options/dummy_privkey.txt
new file mode 100644
index 00000000000..f29585fe31f
--- /dev/null
+++ b/vespalib/src/tests/net/tls/transport_options/dummy_privkey.txt
@@ -0,0 +1 @@
+My private key
diff --git a/vespalib/src/tests/net/tls/transport_options/ok_config.json b/vespalib/src/tests/net/tls/transport_options/ok_config.json
new file mode 100644
index 00000000000..dd2591661dc
--- /dev/null
+++ b/vespalib/src/tests/net/tls/transport_options/ok_config.json
@@ -0,0 +1,7 @@
+{
+ "files":{
+ "private-key": "dummy_privkey.txt",
+ "ca-certificates": "dummy_ca_certs.txt",
+ "certificates": "dummy_certs.txt"
+ }
+}
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
new file mode 100644
index 00000000000..859d2cc90f2
--- /dev/null
+++ b/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp
@@ -0,0 +1,65 @@
+// 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/transport_security_options.h>
+#include <vespa/vespalib/net/tls/transport_security_options_reading.h>
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/vespalib/util/exceptions.h>
+
+using namespace vespalib;
+using namespace vespalib::net::tls;
+
+TEST("can load TLS credentials via config file") {
+ auto opts = read_options_from_json_file("ok_config.json");
+ ASSERT_TRUE(opts.get() != nullptr);
+ // Obviously we'd need to change this to actual PEM data if config reading started
+ // actually verifying the _content_ of files, not just reading them.
+ EXPECT_EQUAL("My private key\n", opts->private_key_pem());
+ EXPECT_EQUAL("My CA certificates\n", opts->ca_certs_pem());
+ EXPECT_EQUAL("My certificate chain\n", opts->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' does not exist");
+}
+
+TEST("bad JSON content throws exception") {
+ const char* bad_json = "hello world :D";
+ EXPECT_EXCEPTION(read_options_from_json_string(bad_json), IllegalArgumentException,
+ "Provided TLS config file is not valid JSON");
+}
+
+TEST("missing 'files' field throws exception") {
+ const char* incomplete_json = R"({})";
+ EXPECT_EXCEPTION(read_options_from_json_string(incomplete_json), IllegalArgumentException,
+ "TLS config root field 'files' is missing or empty");
+}
+
+TEST("missing 'private-key' field throws exception") {
+ const char* incomplete_json = R"({"files":{"certificates":"dummy_certs.txt","ca-certificates":"dummy_ca_certs.txt"}})";
+ EXPECT_EXCEPTION(read_options_from_json_string(incomplete_json), IllegalArgumentException,
+ "TLS config field 'private-key' has not been set");
+}
+
+TEST("missing 'certificates' field throws exception") {
+ const char* incomplete_json = R"({"files":{"private-key":"dummy_privkey.txt","ca-certificates":"dummy_ca_certs.txt"}})";
+ EXPECT_EXCEPTION(read_options_from_json_string(incomplete_json), IllegalArgumentException,
+ "TLS config field 'certificates' has not been set");
+}
+
+TEST("missing 'ca-certificates' field throws exception") {
+ const char* incomplete_json = R"({"files":{"private-key":"dummy_privkey.txt","certificates":"dummy_certs.txt"}})";
+ EXPECT_EXCEPTION(read_options_from_json_string(incomplete_json), IllegalArgumentException,
+ "TLS config field 'ca-certificates' has not been set");
+}
+
+TEST("missing file referenced by field throws exception") {
+ const char* incomplete_json = R"({"files":{"private-key":"missing_privkey.txt",
+ "certificates":"dummy_certs.txt",
+ "ca-certificates":"dummy_ca_certs.txt"}})";
+ EXPECT_EXCEPTION(read_options_from_json_string(incomplete_json), IllegalArgumentException,
+ "File 'missing_privkey.txt' referenced by TLS config does not exist");
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
+
diff --git a/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt b/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt
index 938ae0896a2..a501af89cfb 100644
--- a/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt
+++ b/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt
@@ -5,6 +5,7 @@ vespa_add_library(vespalib_vespalib_net_tls OBJECT
crypto_exception.cpp
tls_context.cpp
transport_security_options.cpp
+ transport_security_options_reading.cpp
DEPENDS
)
find_package(OpenSSL)
diff --git a/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp b/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp
new file mode 100644
index 00000000000..3d95e2327da
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp
@@ -0,0 +1,102 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "transport_security_options_reading.h"
+#include <vespa/vespalib/data/slime/slime.h>
+#include <vespa/vespalib/util/exceptions.h>
+#include <vespa/vespalib/io/fileutil.h>
+#include <vespa/vespalib/io/mapped_file_input.h>
+#include <vespa/vespalib/data/memory_input.h>
+
+namespace vespalib::net::tls {
+
+/*
+
+ Proposed JSON format for TLS configuration file:
+
+{
+ "files": {
+ "private-key": "myhost.key",
+ "ca-certificates": "my_cas.pem",
+ "certificates": "certs.pem"
+ },
+ // for later:
+ "peer-taggers": [
+ {
+ "requirements":[
+ {
+ "field": "SAN"
+ "must-match": "DNS:foo.bar.baz.*"
+ }
+ ],
+ "tags": ["cluster-peers", "config-server"] // or "roles"? Avoid ambiguities with Athenz concepts
+ },
+ {
+ "requirements":[
+ { "field":"CN", "must-match": "config.blarg.*"}
+ ],
+ "tags": ["config-server"]
+ }
+ ]
+}
+
+ */
+
+using namespace slime;
+
+namespace {
+
+constexpr const char* files_field = "files";
+constexpr const char* private_key_field = "private-key";
+constexpr const char* ca_certs_field = "ca-certificates";
+constexpr const char* certs_field = "certificates";
+
+void verify_referenced_file_exists(const vespalib::string& file_path) {
+ if (!fileExists(file_path)) {
+ throw IllegalArgumentException(make_string("File '%s' referenced by TLS config does not exist", file_path.c_str()));
+ }
+}
+
+vespalib::string load_file_referenced_by_field(const Cursor& cursor, const char* field) {
+ auto file_path = cursor[field].asString().make_string();
+ if (file_path.empty()) {
+ throw IllegalArgumentException(make_string("TLS config field '%s' has not been set", field));
+ }
+ verify_referenced_file_exists(file_path);
+ return File::readAll(file_path);
+}
+
+std::unique_ptr<TransportSecurityOptions> load_from_input(Input& input) {
+ Slime root;
+ auto parsed = JsonFormat::decode(input, root);
+ if (parsed == 0) {
+ throw IllegalArgumentException("Provided TLS config file is not valid JSON");
+ }
+ auto& files = root[files_field];
+ if (files.children() == 0) {
+ throw IllegalArgumentException("TLS config root field 'files' is missing or empty");
+ }
+ // Note: we do no look at the _contents_ of the files; this is deferred to the
+ // TLS context code which actually tries to extract key and certificate material
+ // from them.
+ auto ca_certs = load_file_referenced_by_field(files, ca_certs_field);
+ auto certs = load_file_referenced_by_field(files, certs_field);
+ auto priv_key = load_file_referenced_by_field(files, private_key_field);
+
+ return std::make_unique<TransportSecurityOptions>(std::move(ca_certs), std::move(certs), std::move(priv_key));
+}
+
+} // anon ns
+
+std::unique_ptr<TransportSecurityOptions> read_options_from_json_string(const vespalib::string& json_data) {
+ MemoryInput file_input(json_data);
+ return load_from_input(file_input);
+}
+
+std::unique_ptr<TransportSecurityOptions> read_options_from_json_file(const vespalib::string& file_path) {
+ if (!fileExists(file_path)) {
+ throw IllegalArgumentException(make_string("TLS config file '%s' does not exist", file_path.c_str()));
+ }
+ MappedFileInput file_input(file_path);
+ return load_from_input(file_input);
+}
+
+}
diff --git a/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.h b/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.h
new file mode 100644
index 00000000000..800b3b5ed0d
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.h
@@ -0,0 +1,20 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "transport_security_options.h"
+#include <memory>
+
+namespace vespalib::net::tls {
+
+// TODO consider renaming TransportSecurityOptions -> TlsConfig
+
+/**
+ * Throws IoException if file_path or any files referenced by it can't be accessed
+ * Throws IllegalArgumentException if file is not parseable as a valid TLS config file or
+ * if mandatory JSON fields are missing or incomplete.
+ */
+std::unique_ptr<TransportSecurityOptions> read_options_from_json_file(const vespalib::string& file_path);
+// Same properties as read_options_from_json_file()
+std::unique_ptr<TransportSecurityOptions> read_options_from_json_string(const vespalib::string& json_data);
+
+}