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

Restrict maximum size of client HTTP request #3941

Merged
merged 40 commits into from
Jun 23, 2022
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
66d7d2d
Hardcoded limit
Jun 13, 2022
05cef4a
Configuration
Jun 13, 2022
dc8b976
Move unit strings
Jun 13, 2022
e7903d2
Plumbing
Jun 13, 2022
314880d
WIP
Jun 13, 2022
0f3d68b
Fix
Jun 14, 2022
cc514a7
.
Jun 14, 2022
0b5389b
Configuration and fixes
Jun 14, 2022
d606d6c
Metrics and odata
Jun 14, 2022
540e49b
Introduce `closing` state
Jun 14, 2022
e17138c
WIP
Jun 14, 2022
edffa78
Introduce `closing` state
Jun 14, 2022
0590aaf
.
Jun 14, 2022
9a25fdc
Fix
Jun 14, 2022
35b0755
Hardcoded max values
Jun 14, 2022
f450d28
Merge branch 'main' into tls_endpoint_closing_state
jumaffre Jun 15, 2022
fa71f6c
Merge remote-tracking branch 'origin/tls_endpoint_closing_state' into…
Jun 15, 2022
dbd988e
Test cleanup
Jun 15, 2022
d81bc25
Test
Jun 15, 2022
49ba79c
Max HTTP headers count
Jun 15, 2022
589040e
Cleanup
Jun 15, 2022
8121e3c
Cleanup test
Jun 15, 2022
d570ce1
.
Jun 15, 2022
c46da8d
.
Jun 15, 2022
71d39b1
Fix
Jun 15, 2022
04692ae
Merge branch 'main' into max_msg_size_http
jumaffre Jun 21, 2022
99fb5ab
Merge branch 'main' of github.com:microsoft/ccf into HEAD
Jun 21, 2022
6092a50
Fix build
Jun 21, 2022
26694ca
Merge branch 'main' of github.com:microsoft/ccf into max_msg_size_http
Jun 21, 2022
04724e3
Fix schema test
Jun 22, 2022
64aff96
Fix test issue
Jun 22, 2022
f97d619
Remove log
Jun 22, 2022
8ef0180
Test cleanup
Jun 22, 2022
b7ed5f7
const
Jun 22, 2022
18ba299
fmt
Jun 22, 2022
327a97b
Merge branch 'main' into max_msg_size_http
achamayou Jun 22, 2022
08f2189
Merge branch 'main' of github.com:microsoft/ccf into max_msg_size_http
Jun 23, 2022
6d10f4d
Merge branch 'max_msg_size_http' of github.com:jumaffre/CCF into max_…
Jun 23, 2022
b544abc
New section of docs for network configuration
Jun 23, 2022
1303198
Merge branch 'main' into max_msg_size_http
jumaffre Jun 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .daily_canary
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Splicer
......
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## Unreleased

### Added

- New per-interface configuration entries (`network.rpc_interfaces.http_configuration`) are added to let operators cap the maximum size of body, header value size and number of headers in client HTTP requests. The client session is automatically closed if the HTTP request exceeds one of these limits (#3941).

## [3.0.0-dev0]

### Added
Expand Down
21 changes: 21 additions & 0 deletions doc/host_config_schema/cchost_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,27 @@
"default": 1010,
"description": "The maximum number of active client sessions on that interface after which clients sessions will be terminated, before the TLS handshake is complete. Note that its value must be greater than the value of ``max_open_sessions_soft``"
},
"http_configuration": {
"type": "object",
"properties": {
"max_body_size": {
"type": "string",
"default": "1MB",
"description": "Maximum size (size string) of a single HTTP request body. Submitting a request with a payload larger than this value will result in the client session being automatically closed."
},
"max_header_size": {
"type": "string",
"default": "16KB",
"description": "Maximum size (size string) of a single HTTP request header (key or value). Submitting a request with a header larger than this value will result in the client session being automatically closed."
},
"max_headers_count": {
"type": "integer",
"default": 256,
"description": "Maximum number of headers in a single HTTP request. Submitting a request with more headers than this value will result in the session being automatically closed."
}
},
"additionalProperties": false
},
"endorsement": {
"type": "object",
"properties": {
Expand Down
22 changes: 22 additions & 0 deletions src/ds/unit_strings.h → include/ccf/ds/unit_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ namespace ds
s = j.get<std::string_view>();
}

inline std::string schema_name(const SizeString*)
{
return "TimeString";
}

inline void fill_json_schema(nlohmann::json& schema, const SizeString*)
{
schema["type"] = "string";
schema["pattern"] = "^[0-9]+(B|KB|MB|GB|TB|PB)?$";
}

struct TimeString : UnitString
{
std::chrono::microseconds value;
Expand Down Expand Up @@ -181,4 +192,15 @@ namespace ds
{
s = j.get<std::string_view>();
}

inline std::string schema_name(const TimeString*)
{
return "TimeString";
}

inline void fill_json_schema(nlohmann::json& schema, const TimeString*)
{
schema["type"] = "string";
schema["pattern"] = "^[0-9]+(us|ms|s|min|h)?$";
}
}
29 changes: 29 additions & 0 deletions include/ccf/http_configuration.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once

#include "ccf/ds/json.h"
#include "ccf/ds/unit_strings.h"

#include <optional>

namespace http
{
struct ParserConfiguration
{
ds::SizeString max_body_size = {"1MB"};
ds::SizeString max_header_size = {"16KB"};
size_t max_headers_count = 256;
jumaffre marked this conversation as resolved.
Show resolved Hide resolved

bool operator==(const ParserConfiguration& other) const
{
return max_body_size == other.max_body_size &&
max_header_size == other.max_header_size &&
max_headers_count == other.max_headers_count;
}
};
DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(ParserConfiguration);
DECLARE_JSON_REQUIRED_FIELDS(ParserConfiguration);
DECLARE_JSON_OPTIONAL_FIELDS(
ParserConfiguration, max_body_size, max_header_size, max_headers_count);
}
2 changes: 2 additions & 0 deletions include/ccf/odata_error.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ namespace ccf
ERROR(RequestNotSigned)
ERROR(UnsupportedHttpVerb)
ERROR(UnsupportedContentType)
ERROR(RequestBodyTooLarge)
ERROR(RequestHeaderTooLarge)

// CCF-specific errors
// client-facing:
Expand Down
14 changes: 10 additions & 4 deletions include/ccf/service/node_info_network.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "ccf/ds/json.h"
#include "ccf/ds/nonstd.h"
#include "ccf/http_configuration.h"

#include <string>

Expand Down Expand Up @@ -60,6 +61,9 @@ namespace ccf
std::optional<size_t> max_open_sessions_soft = std::nullopt;
std::optional<size_t> max_open_sessions_hard = std::nullopt;

std::optional<http::ParserConfiguration> http_configuration =
std::nullopt;

std::optional<Endorsement> endorsement = std::nullopt;

bool operator==(const NetInterface& other) const
Expand All @@ -69,7 +73,8 @@ namespace ccf
protocol == other.protocol &&
max_open_sessions_soft == other.max_open_sessions_soft &&
max_open_sessions_hard == other.max_open_sessions_hard &&
endorsement == other.endorsement;
endorsement == other.endorsement &&
http_configuration == other.http_configuration;
}
};

Expand All @@ -78,6 +83,7 @@ namespace ccf
NetInterface node_to_node_interface;
RpcInterfaces rpc_interfaces;
};

DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(NodeInfoNetwork_v2::NetInterface);
DECLARE_JSON_REQUIRED_FIELDS(NodeInfoNetwork_v2::NetInterface, bind_address);
DECLARE_JSON_OPTIONAL_FIELDS(
Expand All @@ -86,7 +92,8 @@ namespace ccf
max_open_sessions_soft,
max_open_sessions_hard,
published_address,
protocol);
protocol,
http_configuration);
DECLARE_JSON_TYPE(NodeInfoNetwork_v2);
DECLARE_JSON_REQUIRED_FIELDS(
NodeInfoNetwork_v2, node_to_node_interface, rpc_interfaces);
Expand Down Expand Up @@ -168,8 +175,7 @@ namespace ccf
}
}

FMT_BEGIN_NAMESPACE
template <>
FMT_BEGIN_NAMESPACE template <>
struct formatter<ccf::Authority>
{
template <typename ParseContext>
Expand Down
2 changes: 1 addition & 1 deletion src/common/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
#include "ccf/crypto/curve.h"
#include "ccf/crypto/pem.h"
#include "ccf/ds/logger.h"
#include "ccf/ds/unit_strings.h"
#include "ccf/service/node_info_network.h"
#include "ccf/service/tables/members.h"
#include "common/enclave_interface_types.h"
#include "consensus/consensus_types.h"
#include "ds/oversized.h"
#include "ds/unit_strings.h"
#include "enclave/consensus_type.h"
#include "enclave/reconfiguration_type.h"
#include "service/tables/config.h"
Expand Down
2 changes: 1 addition & 1 deletion src/consensus/consensus_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
// Licensed under the Apache 2.0 License.
#pragma once

#include "ccf/ds/unit_strings.h"
#include "ccf/service/tables/nodes.h"
#include "ccf/tx_id.h"
#include "ds/unit_strings.h"
#include "enclave/consensus_type.h"

#include <stdint.h>
Expand Down
2 changes: 1 addition & 1 deletion src/ds/test/unit_strings.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.

#include "ds/unit_strings.h"
#include "ccf/ds/unit_strings.h"

#include <cmath>
#include <doctest/doctest.h>
Expand Down
48 changes: 39 additions & 9 deletions src/enclave/rpc_sessions.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ namespace ccf
size_t max_open_sessions_soft;
size_t max_open_sessions_hard;
ccf::Endorsement endorsement;
http::ParserConfiguration http_configuration;
ccf::SessionMetrics::Errors errors;
};
std::map<ListenInterfaceID, ListenInterface> listening_interfaces;
Expand Down Expand Up @@ -146,6 +147,23 @@ namespace ccf
return id;
}

ListenInterface& get_interface_from_session_id(tls::ConnID id)
{
// Lock must be first acquired and held while accessing returned interface
auto search = sessions.find(id);
if (search != sessions.end())
{
auto it = listening_interfaces.find(search->second.first);
if (it != listening_interfaces.end())
{
return it->second;
}
}

throw std::logic_error(
fmt::format("No RPC interface for session ID {}", id));
}

public:
RPCSessions(
ringbuffer::AbstractWriterFactory& writer_factory,
Expand All @@ -159,16 +177,19 @@ namespace ccf
void report_parsing_error(tls::ConnID id) override
{
std::lock_guard<std::mutex> guard(lock);
get_interface_from_session_id(id).errors.parsing++;
}

auto search = sessions.find(id);
if (search != sessions.end())
{
auto it = listening_interfaces.find(search->second.first);
if (it != listening_interfaces.end())
{
it->second.errors.parsing++;
}
}
void report_request_payload_too_large_error(tls::ConnID id) override
{
std::lock_guard<std::mutex> guard(lock);
get_interface_from_session_id(id).errors.request_payload_too_large++;
}

void report_request_header_too_large_error(tls::ConnID id) override
{
std::lock_guard<std::mutex> guard(lock);
get_interface_from_session_id(id).errors.request_header_too_large++;
}

void update_listening_interface_options(
Expand All @@ -188,6 +209,14 @@ namespace ccf

li.endorsement = interface.endorsement.value_or(endorsement_default);

if (!interface.http_configuration.has_value())
{
throw std::logic_error(
fmt::format("RPC Interface {} has no HTTP configuration", name));
}

li.http_configuration = interface.http_configuration.value();

LOG_INFO_FMT(
"Setting max open sessions on interface \"{}\" ({}) to [{}, "
"{}] and endorsement authority to {}",
Expand Down Expand Up @@ -353,6 +382,7 @@ namespace ccf
listen_interface_id,
writer_factory,
std::move(ctx),
per_listen_interface.http_configuration,
shared_from_this());
sessions.insert(std::make_pair(
id, std::make_pair(listen_interface_id, std::move(session))));
Expand Down
24 changes: 20 additions & 4 deletions src/enclave/tls_endpoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ namespace ccf
{
handshake,
ready,
closing,
closed,
authfail,
error
Expand Down Expand Up @@ -57,6 +58,13 @@ namespace ccf
std::unique_ptr<tls::Context> ctx;
Status status;

bool can_send()
{
// Closing endpoint should still be able to respond to clients (e.g. to
// report errors)
return status == ready || status == closing;
}

public:
TLSEndpoint(
int64_t session_id_,
Expand Down Expand Up @@ -104,17 +112,18 @@ namespace ccf
// used by caller.
size_t read(uint8_t* data, size_t size, bool exact = false)
{
LOG_TRACE_FMT("Requesting up to {} bytes", size);

// This will return empty if the connection isn't
// ready, but it will not block on the handshake.
do_handshake();

if (status != ready)
{
LOG_TRACE_FMT("Not ready to read {} bytes", size);
return 0;
}

LOG_TRACE_FMT("Requesting up to {} bytes", size);

// Send pending writes.
flush();

Expand Down Expand Up @@ -255,8 +264,10 @@ namespace ccf
return;
}

if (status != ready)
if (!can_send())
{
return;
}

pending_write.insert(pending_write.end(), data.begin(), data.end());

Expand All @@ -282,8 +293,10 @@ namespace ccf

do_handshake();

if (status != ready)
if (!can_send())
{
return;
}

while (pending_write.size() > 0)
{
Expand Down Expand Up @@ -318,6 +331,7 @@ namespace ccf

void close()
{
status = closing;
auto msg = std::make_unique<threading::Tmsg<EmptyMsg>>(&close_cb);
msg->data.self = this->shared_from_this();

Expand All @@ -342,6 +356,7 @@ namespace ccf
}

case ready:
case closing:
{
int r = ctx->close();

Expand Down Expand Up @@ -478,6 +493,7 @@ namespace ccf

switch (status)
{
case closing:
case closed:
{
RINGBUFFER_WRITE_MESSAGE(
Expand Down
2 changes: 1 addition & 1 deletion src/host/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

#pragma once

#include "ccf/ds/unit_strings.h"
#include "common/configuration.h"
#include "ds/unit_strings.h"

#include <optional>
#include <string>
Expand Down
Loading