Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unauthenticated handlers #962

Merged
merged 21 commits into from
Mar 19, 2020
Merged
19 changes: 19 additions & 0 deletions sphinx/source/schemas/LOG_record_anonymous_params.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"id": {
"maximum": 18446744073709551615,
"minimum": 0,
"type": "number"
},
"msg": {
"type": "string"
}
},
"required": [
"id",
"msg"
],
"title": "LOG_record_anonymous/params",
"type": "object"
}
5 changes: 5 additions & 0 deletions sphinx/source/schemas/LOG_record_anonymous_result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "LOG_record_anonymous/result",
"type": "boolean"
}
1 change: 1 addition & 0 deletions sphinx/source/users/rpc_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The API can also be retrieved from a running service using the `listMethods`_ an
"LOG_get",
"LOG_get_pub",
"LOG_record",
"LOG_record_anonymous",
"LOG_record_prefix_cert",
"LOG_record_pub",
"getCommit",
Expand Down
23 changes: 23 additions & 0 deletions src/apps/logging/logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ namespace ccfapp
static constexpr auto LOG_GET_PUBLIC = "LOG_get_pub";

static constexpr auto LOG_RECORD_PREFIX_CERT = "LOG_record_prefix_cert";
static constexpr auto LOG_RECORD_ANONYMOUS_CALLER = "LOG_record_anonymous";
};

// SNIPPET: table_definition
Expand Down Expand Up @@ -195,6 +196,22 @@ namespace ccfapp
};
// SNIPPET_END: log_record_prefix_cert

auto log_record_anonymous =
[this](Store::Tx& tx, nlohmann::json&& params) {
const auto in = params.get<LoggingRecord::In>();

if (in.msg.empty())
{
return make_error(
HTTP_STATUS_BAD_REQUEST, "Cannot record an empty log message");
}

const auto log_line = fmt::format("Anonymous: {}", in.msg);
auto view = tx.get_view(records);
view->put(in.id, log_line);
return make_success(true);
};

install(Procs::LOG_RECORD, json_adapter(record), Write)
.set_auto_schema<LoggingRecord::In, bool>();
// SNIPPET_START: install_get
Expand All @@ -211,6 +228,12 @@ namespace ccfapp
.set_result_schema(get_public_result_schema);

install(Procs::LOG_RECORD_PREFIX_CERT, log_record_prefix_cert, Write);
install(
Procs::LOG_RECORD_ANONYMOUS_CALLER,
json_adapter(log_record_anonymous),
Write)
.set_auto_schema<LoggingRecord::In, bool>()
.set_require_client_identity(false);

nwt.signatures.set_global_hook([this, &notifier](
kv::Version version,
Expand Down
7 changes: 4 additions & 3 deletions src/enclave/rpccontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace enclave
{
size_t client_session_id = InvalidSessionId;
std::vector<uint8_t> caller_cert = {};
bool is_forwarded = false;
bool is_forwarding = false;

//
// Only set in the case of a forwarded RPC
Expand All @@ -32,7 +32,7 @@ namespace enclave
caller_id(caller_id_)
{}
};
std::optional<Forwarded> fwd = std::nullopt;
std::optional<Forwarded> original_caller = std::nullopt;

// Constructor used for non-forwarded RPC
SessionContext(
Expand All @@ -46,7 +46,8 @@ namespace enclave
size_t fwd_session_id_,
ccf::CallerId caller_id_,
const std::vector<uint8_t>& caller_cert_ = {}) :
fwd(std::make_optional<Forwarded>(fwd_session_id_, caller_id_)),
original_caller(
std::make_optional<Forwarded>(fwd_session_id_, caller_id_)),
caller_cert(caller_cert_)
{}
};
Expand Down
2 changes: 1 addition & 1 deletion src/node/rpc/forwarder.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ namespace ccf
}

if (!send_forwarded_response(
ctx->session->fwd->client_session_id,
ctx->session->original_caller->client_session_id,
from_node,
fwd_handler->process_forwarded(ctx)))
{
Expand Down
150 changes: 68 additions & 82 deletions src/node/rpc/frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ namespace ccf
handlers.set_history(history);
}

std::optional<nlohmann::json> forward_or_redirect_json(
std::optional<std::vector<uint8_t>> forward_or_redirect_json(
std::shared_ptr<enclave::RpcContext> ctx)
{
if (cmd_forwarder && !ctx->session->fwd.has_value())
if (cmd_forwarder && !ctx->session->original_caller.has_value())
{
return std::nullopt;
}
Expand Down Expand Up @@ -218,66 +218,25 @@ namespace ccf

Store::Tx tx;

// Retrieve id of caller
std::optional<CallerId> caller_id;
if (ctx->is_create_request)
jumaffre marked this conversation as resolved.
Show resolved Hide resolved
{
caller_id = INVALID_ID;
}
else
{
caller_id = handlers.valid_caller(tx, ctx->session->caller_cert);
}

if (!caller_id.has_value())
{
ctx->set_response_status(HTTP_STATUS_FORBIDDEN);
ctx->set_response_body(invalid_caller_error_message());
return ctx->serialise_response();
}

const auto signed_request = ctx->get_signed_request();
if (signed_request.has_value())
{
if (
!ctx->is_create_request &&
!verify_client_signature(
ctx->session->caller_cert,
caller_id.value(),
signed_request.value()))
{
set_response_unauthorized(ctx);
return ctx->serialise_response();
}

// Client signature is only recorded on the primary
if (
consensus == nullptr || consensus->is_primary() ||
ctx->is_create_request)
{
record_client_signature(
tx, caller_id.value(), signed_request.value());
}
}
auto caller_id = handlers.get_caller_id(tx, ctx->session->caller_cert);

if (consensus != nullptr && consensus->type() == ConsensusType::PBFT)
{
auto rep = process_if_local_node_rpc(ctx, tx, caller_id.value());
auto rep = process_if_local_node_rpc(ctx, tx, caller_id);
if (rep.has_value())
{
return rep.value();
return rep;
}
kv::TxHistory::RequestID reqid;

update_history();
reqid = {caller_id.value(),
ctx->session->client_session_id,
ctx->get_request_index()};
reqid = {
caller_id, ctx->session->client_session_id, ctx->get_request_index()};
if (history)
{
if (!history->add_request(
reqid,
caller_id.value(),
caller_id,
ctx->session->caller_cert,
ctx->get_serialised_request()))
{
Expand All @@ -302,7 +261,7 @@ namespace ccf
}
else
{
auto rep = process_command(ctx, tx, caller_id.value());
auto rep = process_command(ctx, tx, caller_id);

// If necessary, forward the RPC to the current primary
if (!rep.has_value())
Expand All @@ -314,7 +273,7 @@ namespace ccf
if (
primary_id != NoNode && cmd_forwarder &&
cmd_forwarder->forward_command(
ctx, primary_id, caller_id.value(), get_cert_to_forward(ctx)))
ctx, primary_id, caller_id, get_cert_to_forward(ctx)))
{
// Indicate that the RPC has been forwarded to primary
LOG_DEBUG_FMT("RPC forwarded to primary {}", primary_id);
Expand Down Expand Up @@ -360,13 +319,14 @@ namespace ccf
auto req_view = tx.get_view(*pbft_requests_map);
req_view->put(
0,
{ctx->session->fwd.value().caller_id,
{ctx->session->original_caller.value().caller_id,
ctx->session->caller_cert,
ctx->get_serialised_request(),
ctx->pbft_raw});
}

auto rep = process_command(ctx, tx, ctx->session->fwd->caller_id);
auto rep =
process_command(ctx, tx, ctx->session->original_caller->caller_id);

version = tx.get_version();

Expand Down Expand Up @@ -395,31 +355,18 @@ namespace ccf
std::vector<uint8_t> process_forwarded(
std::shared_ptr<enclave::RpcContext> ctx) override
{
if (!ctx->session->fwd.has_value())
if (!ctx->session->original_caller.has_value())
{
throw std::logic_error(
"Processing forwarded command with unitialised forwarded context");
}

Store::Tx tx;

if (!lookup_forwarded_caller_cert(ctx, tx))
{
ctx->set_response_status(HTTP_STATUS_FORBIDDEN);
ctx->set_response_body(invalid_caller_error_message());
return ctx->serialise_response();
}
update_consensus();

// Store client signature. It is assumed that the forwarder node has
// already verified the client signature.
const auto signed_request = ctx->get_signed_request();
if (signed_request.has_value())
{
record_client_signature(
tx, ctx->session->fwd->caller_id, signed_request.value());
}
Store::Tx tx;

auto rep = process_command(ctx, tx, ctx->session->fwd->caller_id);
auto rep =
process_command(ctx, tx, ctx->session->original_caller->caller_id);
if (!rep.has_value())
{
// This should never be called when process_command is called with a
Expand All @@ -430,7 +377,7 @@ namespace ccf
return rep.value();
}

std::optional<nlohmann::json> process_if_local_node_rpc(
std::optional<std::vector<uint8_t>> process_if_local_node_rpc(
std::shared_ptr<enclave::RpcContext> ctx,
Store::Tx& tx,
CallerId caller_id)
Expand Down Expand Up @@ -459,27 +406,66 @@ namespace ccf
return ctx->serialise_response();
}

if (
handler->require_client_signature &&
!ctx->get_signed_request().has_value())
if (handler->require_client_identity && handlers.has_certs())
{
// Only if handler requires client identity.
// If a request is forwarded, check that the caller is known. Otherwise,
// only check that the caller id is valid.
if (
(ctx->session->original_caller.has_value() &&
!lookup_forwarded_caller_cert(ctx, tx)) ||
caller_id == INVALID_ID)
{
ctx->set_response_status(HTTP_STATUS_FORBIDDEN);
ctx->set_response_body(invalid_caller_error_message());
return ctx->serialise_response();
}
}

bool is_primary = (consensus == nullptr) || consensus->is_primary() ||
ctx->is_create_request;

const auto signed_request = ctx->get_signed_request();
if (handler->require_client_signature && !signed_request.has_value())
{
set_response_unauthorized(
ctx, fmt::format("'{}' RPC must be signed", method));
return ctx->serialise_response();
}

update_history();
// By default, signed requests are verified and recorded, even on
// handlers that do not require client signatures
if (signed_request.has_value())
{
// For forwarded requests (raft only), skip verification as it is
// assumed that the verification was done by the forwarder node.
if (
(!ctx->is_create_request &&
(!(consensus != nullptr &&
consensus->type() == ConsensusType::RAFT) ||
!ctx->session->original_caller.has_value())) &&
!verify_client_signature(
ctx->session->caller_cert, caller_id, signed_request.value()))
{
set_response_unauthorized(ctx);
return ctx->serialise_response();
}

bool is_primary = (consensus == nullptr) || consensus->is_primary() ||
ctx->is_create_request;
if (is_primary)
{
record_client_signature(tx, caller_id, signed_request.value());
}
}

update_history();

if (!is_primary && consensus->type() == ConsensusType::RAFT)
{
switch (handler->read_write)
{
case HandlerRegistry::Read:
{
if (ctx->session->is_forwarded)
if (ctx->session->is_forwarding)
{
return forward_or_redirect_json(ctx);
}
Expand All @@ -488,7 +474,7 @@ namespace ccf

case HandlerRegistry::Write:
{
ctx->session->is_forwarded = true;
ctx->session->is_forwarding = true;
return forward_or_redirect_json(ctx);
}

Expand All @@ -498,10 +484,10 @@ namespace ccf
ctx->get_request_header(http::headers::CCF_READ_ONLY);
if (!read_only_it.has_value() || (read_only_it.value() != "true"))
{
ctx->session->is_forwarded = true;
ctx->session->is_forwarding = true;
return forward_or_redirect_json(ctx);
}
else if (ctx->session->is_forwarded)
else if (ctx->session->is_forwarding)
{
return forward_or_redirect_json(ctx);
}
Expand Down
Loading