summaryrefslogtreecommitdiffstats
path: root/vespalib
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@yahooinc.com>2023-03-23 12:41:07 +0100
committerGitHub <noreply@github.com>2023-03-23 12:41:07 +0100
commitd2533769f355b0475c4ca39ad77f5c70140d070b (patch)
tree43890eb32751371f8802ec5367d8d77bf4857c6d /vespalib
parent40e45bb57c0d6abd9b96ff35ff21115b1d66f9be (diff)
parent0b77cfcea89d0b8033c6c4614172fb78260082a8 (diff)
Merge pull request #26538 from vespa-engine/vekterli/add-capability-checks-to-state-api-handlers
Add capability checking to state API handlers
Diffstat (limited to 'vespalib')
-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);