aboutsummaryrefslogtreecommitdiffstats
path: root/vespalib/src
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@yahooinc.com>2023-03-22 12:58:30 +0000
committerTor Brede Vekterli <vekterli@yahooinc.com>2023-03-22 16:34:52 +0000
commit0b77cfcea89d0b8033c6c4614172fb78260082a8 (patch)
treeb37494c6f3c20e700b98d8596d4c411b18723e05 /vespalib/src
parentf8970d47a9814e46fdd962d4ac3510106d27ca42 (diff)
Add capability checking to state API handlers
This covers both the entry points from the `storagenode` and `searchnode` HTTP servers, though the former is mostly in the name of legacy support. Ideally, capability checking would exist as a property of the HTTP server (Portal) bindings, but the abstractions for the JSON request handling are sufficiently leaky that it ended up making more sense to push things further down the hierarchy. It's always a good thing to move away from using strings with implicit semantics as return types anyway. The `searchnode` state API handler mapping supports fine grained capabilities. The legacy `storagenode` state API forwarding does not; it uses a sledgehammer that expects the union of all possible API capability requirements.
Diffstat (limited to 'vespalib/src')
-rw-r--r--vespalib/src/tests/state_server/state_server_test.cpp138
-rw-r--r--vespalib/src/vespa/vespalib/net/http/CMakeLists.txt1
-rw-r--r--vespalib/src/vespa/vespalib/net/http/generic_state_handler.cpp15
-rw-r--r--vespalib/src/vespa/vespalib/net/http/generic_state_handler.h7
-rw-r--r--vespalib/src/vespa/vespalib/net/http/http_server.cpp9
-rw-r--r--vespalib/src/vespa/vespalib/net/http/json_get_handler.cpp42
-rw-r--r--vespalib/src/vespa/vespalib/net/http/json_get_handler.h47
-rw-r--r--vespalib/src/vespa/vespalib/net/http/json_handler_repo.cpp9
-rw-r--r--vespalib/src/vespa/vespalib/net/http/json_handler_repo.h7
-rw-r--r--vespalib/src/vespa/vespalib/net/http/state_api.cpp54
-rw-r--r--vespalib/src/vespa/vespalib/net/http/state_api.h7
-rw-r--r--vespalib/src/vespa/vespalib/portal/portal.h2
12 files changed, 243 insertions, 95 deletions
diff --git a/vespalib/src/tests/state_server/state_server_test.cpp b/vespalib/src/tests/state_server/state_server_test.cpp
index 2369e0dac66..b6bfcbcc203 100644
--- a/vespalib/src/tests/state_server/state_server_test.cpp
+++ b/vespalib/src/tests/state_server/state_server_test.cpp
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/vespalib/net/connection_auth_context.h>
#include <vespa/vespalib/net/http/state_server.h>
#include <vespa/vespalib/net/http/simple_health_producer.h>
#include <vespa/vespalib/net/http/simple_metrics_producer.h>
@@ -46,15 +47,33 @@ vespalib::string getPage(int port, const vespalib::string &path, const vespalib:
vespalib::string getFull(int port, const vespalib::string &path) { return getPage(port, path, "-D -"); }
+vespalib::string get_json(const JsonGetHandler &handler,
+ const vespalib::string &host,
+ const vespalib::string &path,
+ const std::map<vespalib::string,vespalib::string> &params)
+{
+ net::ConnectionAuthContext dummy_ctx(net::tls::PeerCredentials(), net::tls::CapabilitySet::all());
+ auto res = handler.get(host, path, params, dummy_ctx);
+ if (res.ok()) {
+ return res.payload();
+ }
+ return {};
+}
+
//-----------------------------------------------------------------------------
struct DummyHandler : JsonGetHandler {
vespalib::string result;
DummyHandler(const vespalib::string &result_in) : result(result_in) {}
- vespalib::string get(const vespalib::string &, const vespalib::string &,
- const std::map<vespalib::string,vespalib::string> &) const override
+ Response get(const vespalib::string &, const vespalib::string &,
+ const std::map<vespalib::string,vespalib::string> &,
+ const net::ConnectionAuthContext &) const override
{
- return result;
+ if (!result.empty()) {
+ return Response::make_ok_with_json(result);
+ } else {
+ return Response::make_not_found();
+ }
}
};
@@ -68,7 +87,7 @@ TEST_F("require that unknown url returns 404 response", HttpServer(0)) {
EXPECT_EQUAL(expect, actual);
}
-TEST_FF("require that empty known url returns 404 response", DummyHandler(""), HttpServer(0)) {
+TEST_FF("require that handler can return a 404 response", DummyHandler(""), HttpServer(0)) {
auto token = f2.repo().bind(my_path, f1);
std::string expect("HTTP/1.1 404 Not Found\r\n"
"Connection: close\r\n"
@@ -114,10 +133,11 @@ TEST_FFFF("require that handler is selected based on longest matching url prefix
struct EchoHost : JsonGetHandler {
~EchoHost() override;
- vespalib::string get(const vespalib::string &host, const vespalib::string &,
- const std::map<vespalib::string,vespalib::string> &) const override
+ Response get(const vespalib::string &host, const vespalib::string &,
+ const std::map<vespalib::string,vespalib::string> &,
+ const net::ConnectionAuthContext &) const override
{
- return "[\"" + host + "\"]";
+ return Response::make_ok_with_json("[\"" + host + "\"]");
}
};
@@ -140,8 +160,9 @@ struct SamplingHandler : JsonGetHandler {
mutable vespalib::string my_path;
mutable std::map<vespalib::string,vespalib::string> my_params;
~SamplingHandler() override;
- vespalib::string get(const vespalib::string &host, const vespalib::string &path,
- const std::map<vespalib::string,vespalib::string> &params) const override
+ Response get(const vespalib::string &host, const vespalib::string &path,
+ const std::map<vespalib::string,vespalib::string> &params,
+ const net::ConnectionAuthContext &) const override
{
{
auto guard = std::lock_guard(my_lock);
@@ -149,7 +170,7 @@ struct SamplingHandler : JsonGetHandler {
my_path = path;
my_params = params;
}
- return "[]";
+ return Response::make_ok_with_json("[]");
}
};
@@ -218,13 +239,13 @@ TEST_FFFF("require that json handlers can be removed from repo",
auto token2 = f4.bind("/foo/bar", f2);
auto token3 = f4.bind("/foo/bar/baz", f3);
std::map<vespalib::string,vespalib::string> params;
- EXPECT_EQUAL("[1]", f4.get("", "/foo", params));
- EXPECT_EQUAL("[2]", f4.get("", "/foo/bar", params));
- EXPECT_EQUAL("[3]", f4.get("", "/foo/bar/baz", params));
+ EXPECT_EQUAL("[1]", get_json(f4, "", "/foo", params));
+ EXPECT_EQUAL("[2]", get_json(f4, "", "/foo/bar", params));
+ EXPECT_EQUAL("[3]", get_json(f4, "", "/foo/bar/baz", params));
token2.reset();
- EXPECT_EQUAL("[1]", f4.get("", "/foo", params));
- EXPECT_EQUAL("[1]", f4.get("", "/foo/bar", params));
- EXPECT_EQUAL("[3]", f4.get("", "/foo/bar/baz", params));
+ EXPECT_EQUAL("[1]", get_json(f4, "", "/foo", params));
+ EXPECT_EQUAL("[1]", get_json(f4, "", "/foo/bar", params));
+ EXPECT_EQUAL("[3]", get_json(f4, "", "/foo/bar/baz", params));
}
TEST_FFFF("require that json handlers can be shadowed",
@@ -234,12 +255,12 @@ TEST_FFFF("require that json handlers can be shadowed",
auto token1 = f4.bind("/foo", f1);
auto token2 = f4.bind("/foo/bar", f2);
std::map<vespalib::string,vespalib::string> params;
- EXPECT_EQUAL("[1]", f4.get("", "/foo", params));
- EXPECT_EQUAL("[2]", f4.get("", "/foo/bar", params));
+ EXPECT_EQUAL("[1]", get_json(f4, "", "/foo", params));
+ EXPECT_EQUAL("[2]", get_json(f4, "", "/foo/bar", params));
auto token3 = f4.bind("/foo/bar", f3);
- EXPECT_EQUAL("[3]", f4.get("", "/foo/bar", params));
+ EXPECT_EQUAL("[3]", get_json(f4, "", "/foo/bar", params));
token3.reset();
- EXPECT_EQUAL("[2]", f4.get("", "/foo/bar", params));
+ EXPECT_EQUAL("[2]", get_json(f4, "", "/foo/bar", params));
}
TEST_F("require that root resources can be tracked", JsonHandlerRepo())
@@ -262,14 +283,14 @@ TEST_FFFF("require that state api responds to the expected paths",
StateApi(f1, f2, f3))
{
f2.setTotalMetrics("{}"); // avoid empty result
- EXPECT_TRUE(!f4.get(host_tag, short_root_path, empty_params).empty());
- EXPECT_TRUE(!f4.get(host_tag, root_path, empty_params).empty());
- EXPECT_TRUE(!f4.get(host_tag, health_path, empty_params).empty());
- EXPECT_TRUE(!f4.get(host_tag, metrics_path, empty_params).empty());
- EXPECT_TRUE(!f4.get(host_tag, config_path, empty_params).empty());
- EXPECT_TRUE(!f4.get(host_tag, total_metrics_path, empty_params).empty());
- EXPECT_TRUE(f4.get(host_tag, unknown_path, empty_params).empty());
- EXPECT_TRUE(f4.get(host_tag, unknown_state_path, empty_params).empty());
+ EXPECT_TRUE(!get_json(f4, host_tag, short_root_path, empty_params).empty());
+ EXPECT_TRUE(!get_json(f4, host_tag, root_path, empty_params).empty());
+ EXPECT_TRUE(!get_json(f4, host_tag, health_path, empty_params).empty());
+ EXPECT_TRUE(!get_json(f4, host_tag, metrics_path, empty_params).empty());
+ EXPECT_TRUE(!get_json(f4, host_tag, config_path, empty_params).empty());
+ EXPECT_TRUE(!get_json(f4, host_tag, total_metrics_path, empty_params).empty());
+ EXPECT_TRUE(get_json(f4, host_tag, unknown_path, empty_params).empty());
+ EXPECT_TRUE(get_json(f4, host_tag, unknown_state_path, empty_params).empty());
}
TEST_FFFF("require that top-level urls are generated correctly",
@@ -280,9 +301,9 @@ TEST_FFFF("require that top-level urls are generated correctly",
"{\"url\":\"http://HOST/state/v1/health\"},"
"{\"url\":\"http://HOST/state/v1/metrics\"},"
"{\"url\":\"http://HOST/state/v1/config\"}]}",
- f4.get(host_tag, root_path, empty_params));
- EXPECT_EQUAL(f4.get(host_tag, root_path, empty_params),
- f4.get(host_tag, short_root_path, empty_params));
+ get_json(f4, host_tag, root_path, empty_params));
+ EXPECT_EQUAL(get_json(f4, host_tag, root_path, empty_params),
+ get_json(f4, host_tag, short_root_path, empty_params));
}
TEST_FFFF("require that top-level resource list can be extended",
@@ -295,7 +316,7 @@ TEST_FFFF("require that top-level resource list can be extended",
"{\"url\":\"http://HOST/state/v1/metrics\"},"
"{\"url\":\"http://HOST/state/v1/config\"},"
"{\"url\":\"http://HOST/state/v1/custom\"}]}",
- f4.get(host_tag, root_path, empty_params));
+ get_json(f4, host_tag, root_path, empty_params));
}
TEST_FFFF("require that health resource works as expected",
@@ -303,10 +324,10 @@ TEST_FFFF("require that health resource works as expected",
StateApi(f1, f2, f3))
{
EXPECT_EQUAL("{\"status\":{\"code\":\"up\"}}",
- f4.get(host_tag, health_path, empty_params));
+ get_json(f4, host_tag, health_path, empty_params));
f1.setFailed("FAIL MSG");
EXPECT_EQUAL("{\"status\":{\"code\":\"down\",\"message\":\"FAIL MSG\"}}",
- f4.get(host_tag, health_path, empty_params));
+ get_json(f4, host_tag, health_path, empty_params));
}
TEST_FFFF("require that metrics resource works as expected",
@@ -314,14 +335,14 @@ TEST_FFFF("require that metrics resource works as expected",
StateApi(f1, f2, f3))
{
EXPECT_EQUAL("{\"status\":{\"code\":\"up\"}}",
- f4.get(host_tag, metrics_path, empty_params));
+ get_json(f4, host_tag, metrics_path, empty_params));
f1.setFailed("FAIL MSG");
EXPECT_EQUAL("{\"status\":{\"code\":\"down\",\"message\":\"FAIL MSG\"}}",
- f4.get(host_tag, metrics_path, empty_params));
+ get_json(f4, host_tag, metrics_path, empty_params));
f1.setOk();
f2.setMetrics("{\"foo\":\"bar\"}");
EXPECT_EQUAL("{\"status\":{\"code\":\"up\"},\"metrics\":{\"foo\":\"bar\"}}",
- f4.get(host_tag, metrics_path, empty_params));
+ get_json(f4, host_tag, metrics_path, empty_params));
}
TEST_FFFF("require that config resource works as expected",
@@ -329,17 +350,17 @@ TEST_FFFF("require that config resource works as expected",
StateApi(f1, f2, f3))
{
EXPECT_EQUAL("{\"config\":{}}",
- f4.get(host_tag, config_path, empty_params));
+ get_json(f4, host_tag, config_path, empty_params));
f3.addConfig(SimpleComponentConfigProducer::Config("foo", 3));
EXPECT_EQUAL("{\"config\":{\"generation\":3,\"foo\":{\"generation\":3}}}",
- f4.get(host_tag, config_path, empty_params));
+ get_json(f4, host_tag, config_path, empty_params));
f3.addConfig(SimpleComponentConfigProducer::Config("foo", 4));
f3.addConfig(SimpleComponentConfigProducer::Config("bar", 4, "error"));
EXPECT_EQUAL("{\"config\":{\"generation\":4,\"bar\":{\"generation\":4,\"message\":\"error\"},\"foo\":{\"generation\":4}}}",
- f4.get(host_tag, config_path, empty_params));
+ get_json(f4, host_tag, config_path, empty_params));
f3.removeConfig("bar");
EXPECT_EQUAL("{\"config\":{\"generation\":4,\"foo\":{\"generation\":4}}}",
- f4.get(host_tag, config_path, empty_params));
+ get_json(f4, host_tag, config_path, empty_params));
}
TEST_FFFF("require that state api also can return total metric",
@@ -348,18 +369,18 @@ TEST_FFFF("require that state api also can return total metric",
{
f2.setTotalMetrics("{\"foo\":\"bar\"}");
EXPECT_EQUAL("{\"foo\":\"bar\"}",
- f4.get(host_tag, total_metrics_path, empty_params));
+ get_json(f4, host_tag, total_metrics_path, empty_params));
}
TEST_FFFFF("require that custom handlers can be added to the state server",
SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(),
StateApi(f1, f2, f3), DummyHandler("[123]"))
{
- EXPECT_EQUAL("", f4.get(host_tag, my_path, empty_params));
+ EXPECT_EQUAL("", get_json(f4, host_tag, my_path, empty_params));
auto token = f4.repo().bind(my_path, f5);
- EXPECT_EQUAL("[123]", f4.get(host_tag, my_path, empty_params));
+ EXPECT_EQUAL("[123]", get_json(f4, host_tag, my_path, empty_params));
token.reset();
- EXPECT_EQUAL("", f4.get(host_tag, my_path, empty_params));
+ EXPECT_EQUAL("", get_json(f4, host_tag, my_path, empty_params));
}
struct EchoConsumer : MetricsProducer {
@@ -379,7 +400,8 @@ TEST_FFFF("require that empty v1 metrics consumer defaults to 'statereporter'",
StateApi(f1, f2, f3))
{
std::map<vespalib::string,vespalib::string> my_params;
- EXPECT_EQUAL("{\"status\":{\"code\":\"up\"},\"metrics\":[\"statereporter\"]}", f4.get(host_tag, metrics_path, empty_params));
+ EXPECT_EQUAL("{\"status\":{\"code\":\"up\"},\"metrics\":[\"statereporter\"]}",
+ get_json(f4, host_tag, metrics_path, empty_params));
}
TEST_FFFF("require that empty total metrics consumer defaults to the empty string",
@@ -387,7 +409,7 @@ TEST_FFFF("require that empty total metrics consumer defaults to the empty strin
StateApi(f1, f2, f3))
{
std::map<vespalib::string,vespalib::string> my_params;
- EXPECT_EQUAL("[\"\"]", f4.get(host_tag, total_metrics_path, empty_params));
+ EXPECT_EQUAL("[\"\"]", get_json(f4, host_tag, total_metrics_path, empty_params));
}
TEST_FFFF("require that metrics consumer is passed correctly",
@@ -396,8 +418,8 @@ TEST_FFFF("require that metrics consumer is passed correctly",
{
std::map<vespalib::string,vespalib::string> my_params;
my_params["consumer"] = "ME";
- EXPECT_EQUAL("{\"status\":{\"code\":\"up\"},\"metrics\":[\"ME\"]}", f4.get(host_tag, metrics_path, my_params));
- EXPECT_EQUAL("[\"ME\"]", f4.get(host_tag, total_metrics_path, my_params));
+ EXPECT_EQUAL("{\"status\":{\"code\":\"up\"},\"metrics\":[\"ME\"]}", get_json(f4, host_tag, metrics_path, my_params));
+ EXPECT_EQUAL("[\"ME\"]", get_json(f4, host_tag, total_metrics_path, my_params));
}
void check_json(const vespalib::string &expect_json, const vespalib::string &actual_json) {
@@ -496,15 +518,15 @@ TEST("require that generic state can be explored") {
EXPECT_TRUE(slime::JsonFormat::decode(json_model, slime_state) > 0);
SlimeExplorer slime_explorer(slime_state.get());
GenericStateHandler state_handler(short_root_path, slime_explorer);
- EXPECT_EQUAL("", state_handler.get(host_tag, unknown_path, empty_params));
- EXPECT_EQUAL("", state_handler.get(host_tag, unknown_state_path, empty_params));
- check_json(json_root, state_handler.get(host_tag, root_path, empty_params));
- check_json(json_engine, state_handler.get(host_tag, root_path + "engine", empty_params));
- check_json(json_engine_stats, state_handler.get(host_tag, root_path + "engine/stats", empty_params));
- check_json(json_list, state_handler.get(host_tag, root_path + "list", empty_params));
- check_json(json_list_one, state_handler.get(host_tag, root_path + "list/one", empty_params));
- check_json(json_list_one_size, state_handler.get(host_tag, root_path + "list/one/size", empty_params));
- check_json(json_list_two, state_handler.get(host_tag, root_path + "list/two", empty_params));
+ EXPECT_EQUAL("", get_json(state_handler, host_tag, unknown_path, empty_params));
+ EXPECT_EQUAL("", get_json(state_handler, host_tag, unknown_state_path, empty_params));
+ check_json(json_root, get_json(state_handler, host_tag, root_path, empty_params));
+ check_json(json_engine, get_json(state_handler, host_tag, root_path + "engine", empty_params));
+ check_json(json_engine_stats, get_json(state_handler, host_tag, root_path + "engine/stats", empty_params));
+ check_json(json_list, get_json(state_handler, host_tag, root_path + "list", empty_params));
+ check_json(json_list_one, get_json(state_handler, host_tag, root_path + "list/one", empty_params));
+ check_json(json_list_one_size, get_json(state_handler, host_tag, root_path + "list/one/size", empty_params));
+ check_json(json_list_two, get_json(state_handler, host_tag, root_path + "list/two", empty_params));
}
TEST_MAIN() {
diff --git a/vespalib/src/vespa/vespalib/net/http/CMakeLists.txt b/vespalib/src/vespa/vespalib/net/http/CMakeLists.txt
index b3e8d8c7a14..2c46c0682f4 100644
--- a/vespalib/src/vespa/vespalib/net/http/CMakeLists.txt
+++ b/vespalib/src/vespa/vespalib/net/http/CMakeLists.txt
@@ -4,6 +4,7 @@ vespa_add_library(vespalib_vespalib_net_http OBJECT
component_config_producer.cpp
generic_state_handler.cpp
http_server.cpp
+ json_get_handler.cpp
json_handler_repo.cpp
simple_component_config_producer.cpp
simple_health_producer.cpp
diff --git a/vespalib/src/vespa/vespalib/net/http/generic_state_handler.cpp b/vespalib/src/vespa/vespalib/net/http/generic_state_handler.cpp
index 91fc6d7e617..5cd78d6bc67 100644
--- a/vespalib/src/vespa/vespalib/net/http/generic_state_handler.cpp
+++ b/vespalib/src/vespa/vespalib/net/http/generic_state_handler.cpp
@@ -126,14 +126,14 @@ vespalib::string render(const StateExplorer &state, const Url &url) {
return buf.get().make_string();
}
-vespalib::string explore(const StateExplorer &state, const vespalib::string &host,
- const std::vector<vespalib::string> &items, size_t pos) {
+JsonGetHandler::Response explore(const StateExplorer &state, const vespalib::string &host,
+ const std::vector<vespalib::string> &items, size_t pos) {
if (pos == items.size()) {
- return render(state, Url(host, items));
+ return JsonGetHandler::Response::make_ok_with_json(render(state, Url(host, items)));
}
std::unique_ptr<StateExplorer> child = state.get_child(items[pos]);
if (!child) {
- return "";
+ return JsonGetHandler::Response::make_not_found();
}
return explore(*child, host, items, pos + 1);
}
@@ -146,14 +146,15 @@ GenericStateHandler::GenericStateHandler(const vespalib::string &root_path, cons
{
}
-vespalib::string
+JsonGetHandler::Response
GenericStateHandler::get(const vespalib::string &host,
const vespalib::string &path,
- const std::map<vespalib::string,vespalib::string> &) const
+ const std::map<vespalib::string,vespalib::string> &,
+ const net::ConnectionAuthContext &) const
{
std::vector<vespalib::string> items = split_path(path);
if (!is_prefix(_root, items)) {
- return "";
+ return Response::make_not_found();
}
return explore(_state, host, items, _root.size());
}
diff --git a/vespalib/src/vespa/vespalib/net/http/generic_state_handler.h b/vespalib/src/vespa/vespalib/net/http/generic_state_handler.h
index 6065aa165ec..37f5e0d4d22 100644
--- a/vespalib/src/vespa/vespalib/net/http/generic_state_handler.h
+++ b/vespalib/src/vespa/vespalib/net/http/generic_state_handler.h
@@ -23,9 +23,10 @@ private:
public:
GenericStateHandler(const vespalib::string &root_path, const StateExplorer &state);
- virtual vespalib::string get(const vespalib::string &host,
- const vespalib::string &path,
- const std::map<vespalib::string,vespalib::string> &params) const override;
+ Response get(const vespalib::string &host,
+ const vespalib::string &path,
+ const std::map<vespalib::string,vespalib::string> &params,
+ const net::ConnectionAuthContext &auth_ctx) const override;
};
} // namespace vespalib
diff --git a/vespalib/src/vespa/vespalib/net/http/http_server.cpp b/vespalib/src/vespa/vespalib/net/http/http_server.cpp
index f2c041cb648..1b707b9dad1 100644
--- a/vespalib/src/vespa/vespalib/net/http/http_server.cpp
+++ b/vespalib/src/vespa/vespalib/net/http/http_server.cpp
@@ -2,17 +2,18 @@
#include "http_server.h"
#include <vespa/vespalib/net/crypto_engine.h>
+#include <vespa/vespalib/net/connection_auth_context.h>
namespace vespalib {
void
HttpServer::get(Portal::GetRequest req)
{
- vespalib::string json_result = _handler_repo.get(req.get_host(), req.get_path(), req.export_params());
- if (json_result.empty()) {
- req.respond_with_error(404, "Not Found");
+ auto response = _handler_repo.get(req.get_host(), req.get_path(), req.export_params(), req.auth_context());
+ if (response.failed()) {
+ req.respond_with_error(response.status_code(), response.status_message());
} else {
- req.respond_with_content("application/json", json_result);
+ req.respond_with_content("application/json", response.payload());
}
}
diff --git a/vespalib/src/vespa/vespalib/net/http/json_get_handler.cpp b/vespalib/src/vespa/vespalib/net/http/json_get_handler.cpp
new file mode 100644
index 00000000000..110924785ad
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/net/http/json_get_handler.cpp
@@ -0,0 +1,42 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "json_get_handler.h"
+
+namespace vespalib {
+
+JsonGetHandler::Response::Response(int status_code, vespalib::string status_or_payload)
+ : _status_code(status_code),
+ _status_or_payload(std::move(status_or_payload))
+{}
+
+JsonGetHandler::Response::Response()
+ : _status_code(500),
+ _status_or_payload("Internal Server Error")
+{}
+
+JsonGetHandler::Response::~Response() = default;
+
+JsonGetHandler::Response::Response(const Response&) = default;
+JsonGetHandler::Response& JsonGetHandler::Response::operator=(const Response&) = default;
+JsonGetHandler::Response::Response(Response&&) noexcept = default;
+JsonGetHandler::Response& JsonGetHandler::Response::operator=(Response&&) noexcept = default;
+
+JsonGetHandler::Response
+JsonGetHandler::Response::make_ok_with_json(vespalib::string json)
+{
+ return {200, std::move(json)};
+}
+
+JsonGetHandler::Response
+JsonGetHandler::Response::make_failure(int status_code, vespalib::string status_message)
+{
+ return {status_code, std::move(status_message)};
+}
+
+JsonGetHandler::Response
+JsonGetHandler::Response::make_not_found()
+{
+ return {404, "Not Found"};
+}
+
+}
diff --git a/vespalib/src/vespa/vespalib/net/http/json_get_handler.h b/vespalib/src/vespa/vespalib/net/http/json_get_handler.h
index d257bd0285f..6a49618ed37 100644
--- a/vespalib/src/vespa/vespalib/net/http/json_get_handler.h
+++ b/vespalib/src/vespa/vespalib/net/http/json_get_handler.h
@@ -5,13 +5,52 @@
#include <vespa/vespalib/stllike/string.h>
#include <map>
+namespace vespalib::net { class ConnectionAuthContext; }
+
namespace vespalib {
struct JsonGetHandler {
- virtual vespalib::string get(const vespalib::string &host,
- const vespalib::string &path,
- const std::map<vespalib::string,vespalib::string> &params) const = 0;
- virtual ~JsonGetHandler() {}
+ class Response {
+ int _status_code;
+ vespalib::string _status_or_payload;
+
+ Response(int status_code, vespalib::string status_or_payload);
+ public:
+ Response(); // By default, 500 Internal Server Error
+ ~Response();
+ Response(const Response&);
+ Response& operator=(const Response&);
+ Response(Response&&) noexcept;
+ Response& operator=(Response&&) noexcept;
+
+ [[nodiscard]] int status_code() const noexcept { return _status_code; }
+ [[nodiscard]] bool ok() const noexcept { return _status_code == 200; }
+ [[nodiscard]] bool failed() const noexcept { return _status_code != 200; }
+ [[nodiscard]] vespalib::stringref status_message() const noexcept {
+ if (_status_code == 200) {
+ return "OK";
+ } else {
+ return _status_or_payload;
+ }
+ }
+ [[nodiscard]] vespalib::stringref payload() const noexcept {
+ if (_status_code == 200) {
+ return _status_or_payload;
+ } else {
+ return {};
+ }
+ }
+
+ [[nodiscard]] static Response make_ok_with_json(vespalib::string json);
+ [[nodiscard]] static Response make_failure(int status_code, vespalib::string status_message);
+ [[nodiscard]] static Response make_not_found();
+ };
+
+ virtual Response get(const vespalib::string &host,
+ const vespalib::string &path,
+ const std::map<vespalib::string,vespalib::string> &params,
+ const net::ConnectionAuthContext &auth_ctx) const = 0;
+ virtual ~JsonGetHandler() = default;
};
} // namespace vespalib
diff --git a/vespalib/src/vespa/vespalib/net/http/json_handler_repo.cpp b/vespalib/src/vespa/vespalib/net/http/json_handler_repo.cpp
index 3f92cffb4af..db43d79cf45 100644
--- a/vespalib/src/vespa/vespalib/net/http/json_handler_repo.cpp
+++ b/vespalib/src/vespa/vespalib/net/http/json_handler_repo.cpp
@@ -74,18 +74,19 @@ JsonHandlerRepo::get_root_resources() const
return result;
}
-vespalib::string
+JsonGetHandler::Response
JsonHandlerRepo::get(const vespalib::string &host,
const vespalib::string &path,
- const std::map<vespalib::string,vespalib::string> &params) const
+ const std::map<vespalib::string,vespalib::string> &params,
+ const net::ConnectionAuthContext &auth_ctx) const
{
std::lock_guard<std::mutex> guard(_state->lock);
for (const auto &hook: _state->hooks) {
if (path.find(hook.path_prefix) == 0) {
- return hook.handler->get(host, path, params);
+ return hook.handler->get(host, path, params, auth_ctx);
}
}
- return "";
+ return Response::make_not_found();
}
} // namespace vespalib
diff --git a/vespalib/src/vespa/vespalib/net/http/json_handler_repo.h b/vespalib/src/vespa/vespalib/net/http/json_handler_repo.h
index 46ef3bd762a..aa12e77160d 100644
--- a/vespalib/src/vespa/vespalib/net/http/json_handler_repo.h
+++ b/vespalib/src/vespa/vespalib/net/http/json_handler_repo.h
@@ -79,12 +79,13 @@ private:
public:
JsonHandlerRepo();
- ~JsonHandlerRepo();
+ ~JsonHandlerRepo() override;
Token::UP bind(vespalib::stringref path_prefix, const JsonGetHandler &get_handler);
Token::UP add_root_resource(vespalib::stringref path);
[[nodiscard]] std::vector<vespalib::string> get_root_resources() const;
- [[nodiscard]] vespalib::string get(const vespalib::string &host, const vespalib::string &path,
- const std::map<vespalib::string,vespalib::string> &params) const override;
+ [[nodiscard]] Response get(const vespalib::string &host, const vespalib::string &path,
+ const std::map<vespalib::string,vespalib::string> &params,
+ const net::ConnectionAuthContext &auth_ctx) const override;
};
} // namespace vespalib
diff --git a/vespalib/src/vespa/vespalib/net/http/state_api.cpp b/vespalib/src/vespa/vespalib/net/http/state_api.cpp
index ca00713352f..e41e12435b9 100644
--- a/vespalib/src/vespa/vespalib/net/http/state_api.cpp
+++ b/vespalib/src/vespa/vespalib/net/http/state_api.cpp
@@ -1,7 +1,13 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "state_api.h"
+#include <vespa/vespalib/net/connection_auth_context.h>
+#include <vespa/vespalib/net/tls/capability.h>
#include <vespa/vespalib/util/jsonwriter.h>
+#include <functional>
+
+using vespalib::net::tls::Capability;
+using vespalib::net::tls::CapabilitySet;
namespace vespalib {
@@ -131,27 +137,59 @@ vespalib::string respond_config(ComponentConfigProducer &componentConfigProducer
return json.toString();
}
+JsonGetHandler::Response cap_checked(const net::ConnectionAuthContext &auth_ctx,
+ CapabilitySet required_caps,
+ std::function<vespalib::string()> fn)
+{
+ if (!auth_ctx.capabilities().contains_all(required_caps)) {
+ return JsonGetHandler::Response::make_failure(403, "Forbidden");
+ }
+ return JsonGetHandler::Response::make_ok_with_json(fn());
+}
+
+JsonGetHandler::Response cap_checked(const net::ConnectionAuthContext &auth_ctx,
+ Capability required_cap,
+ std::function<vespalib::string()> fn)
+{
+ return cap_checked(auth_ctx, CapabilitySet::of({required_cap}), std::move(fn));
+}
+
} // namespace vespalib::<unnamed>
-vespalib::string
+JsonGetHandler::Response
StateApi::get(const vespalib::string &host,
const vespalib::string &path,
- const std::map<vespalib::string,vespalib::string> &params) const
+ const std::map<vespalib::string,vespalib::string> &params,
+ const net::ConnectionAuthContext &auth_ctx) const
{
if (path == "/state/v1/" || path == "/state/v1") {
- return respond_root(_handler_repo, host);
+ return cap_checked(auth_ctx, CapabilitySet::make_empty(), [&] { // TODO consider http_unclassified
+ return respond_root(_handler_repo, host);
+ });
} else if (path == "/state/v1/health") {
- return respond_health(_healthProducer);
+ return cap_checked(auth_ctx, CapabilitySet::make_empty(), [&] { // TODO consider http_unclassified
+ return respond_health(_healthProducer);
+ });
} else if (path == "/state/v1/metrics") {
// Using a 'statereporter' consumer by default removes many uninteresting per-thread
// metrics but retains their aggregates.
- return respond_metrics(get_consumer(params, "statereporter"), _healthProducer, _metricsProducer);
+ return cap_checked(auth_ctx, Capability::content_metrics_api(), [&] {
+ return respond_metrics(get_consumer(params, "statereporter"), _healthProducer, _metricsProducer);
+ });
} else if (path == "/state/v1/config") {
- return respond_config(_componentConfigProducer);
+ return cap_checked(auth_ctx, Capability::content_state_api(), [&] {
+ return respond_config(_componentConfigProducer);
+ });
} else if (path == "/metrics/total") {
- return _metricsProducer.getTotalMetrics(get_consumer(params, ""));
+ return cap_checked(auth_ctx, Capability::content_metrics_api(), [&] {
+ return _metricsProducer.getTotalMetrics(get_consumer(params, ""));
+ });
} else {
- return _handler_repo.get(host, path, params);
+ // Assume this is for the nested state v1 stuff; may delegate capability check to handler later if desired.
+ if (!auth_ctx.capabilities().contains(Capability::content_state_api())) {
+ return Response::make_failure(403, "Forbidden");
+ }
+ return _handler_repo.get(host, path, params, auth_ctx);
}
}
diff --git a/vespalib/src/vespa/vespalib/net/http/state_api.h b/vespalib/src/vespa/vespalib/net/http/state_api.h
index 2bdf320d091..ad86c2cf7c7 100644
--- a/vespalib/src/vespa/vespalib/net/http/state_api.h
+++ b/vespalib/src/vespa/vespalib/net/http/state_api.h
@@ -30,9 +30,10 @@ public:
MetricsProducer &mp,
ComponentConfigProducer &ccp);
~StateApi() override;
- vespalib::string get(const vespalib::string &host,
- const vespalib::string &path,
- const std::map<vespalib::string,vespalib::string> &params) const override;
+ Response get(const vespalib::string &host,
+ const vespalib::string &path,
+ const std::map<vespalib::string,vespalib::string> &params,
+ const net::ConnectionAuthContext &auth_ctx) const override;
JsonHandlerRepo &repo() { return _handler_repo; }
};
diff --git a/vespalib/src/vespa/vespalib/portal/portal.h b/vespalib/src/vespa/vespalib/portal/portal.h
index 6954d800c91..a25c295c083 100644
--- a/vespalib/src/vespa/vespalib/portal/portal.h
+++ b/vespalib/src/vespa/vespalib/portal/portal.h
@@ -83,7 +83,7 @@ private:
vespalib::string prefix;
GetHandler *handler;
BindState(uint64_t handle_in, vespalib::string prefix_in, GetHandler &handler_in) noexcept
- : handle(handle_in), prefix(prefix_in), handler(&handler_in) {}
+ : handle(handle_in), prefix(std::move(prefix_in)), handler(&handler_in) {}
bool operator<(const BindState &rhs) const {
if (prefix.size() == rhs.prefix.size()) {
return (handle > rhs.handle);