Skip to content

Commit 69e5255

Browse files
authored
[release/6.x] Cherry pick: Disable snapshot read endpoints by-default, require a per-interface opt-in to enable new OperatorFeature (#7440) (#7513)
1 parent 24828dd commit 69e5255

37 files changed

+947
-715
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2323
### Changed
2424

2525
- When the `fetch_recent_snapshot` behaviour is enabled by the node config, the Joiner will now prefer the peer's snapshot over _any_ local snapshot, regardless of version (#7314).
26+
- The snapshot-serving endpoints required for `fetch_recent_snapshot` behaviour are now disabled-by-default to avoid public DoS requests. They should be enabled on a per-interface basis by adding `"enabled_operator_features": ["SnapshotRead"]` to the interface's configuration, on an interface with local visibility used for node-to-node join requests (#7440).
2627

2728
## [6.0.16]
2829

doc/host_config_schema/cchost_config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,14 @@
176176
}
177177
},
178178
"additionalProperties": false
179+
},
180+
"enabled_operator_features": {
181+
"type": "array",
182+
"items": {
183+
"enum": ["SnapshotRead"],
184+
"type": "string"
185+
},
186+
"description": "An array of features which should be enabled on this interface, providing access to endpoints with specific security or performance constraints. The only feature currently supported is 'SnapshotRead', which gates access to the /snapshot/* endpoints used to fetch snapshots directly from nodes. Since these require disk IO and produce large responses, this feature should not be enabled on interfaces with public access, and instead restricted to interfaces with local connectivity for node-to-node and operator access."
179187
}
180188
},
181189
"required": ["bind_address"]

doc/schemas/gov_openapi.json

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,15 @@
523523
"bind_address": {
524524
"type": "string"
525525
},
526+
"enabled_operator_features": {
527+
"items": {
528+
"enum": [
529+
"SnapshotRead"
530+
],
531+
"type": "string"
532+
},
533+
"type": "array"
534+
},
526535
"endorsement": {
527536
"properties": {
528537
"acme_configuration": {
@@ -662,6 +671,15 @@
662671
"bind_address": {
663672
"type": "string"
664673
},
674+
"enabled_operator_features": {
675+
"items": {
676+
"enum": [
677+
"SnapshotRead"
678+
],
679+
"type": "string"
680+
},
681+
"type": "array"
682+
},
665683
"endorsement": {
666684
"properties": {
667685
"acme_configuration": {
@@ -1376,7 +1394,7 @@
13761394
"info": {
13771395
"description": "This API is used to submit and query proposals which affect CCF's public governance tables.",
13781396
"title": "CCF Governance API",
1379-
"version": "4.7.4"
1397+
"version": "4.7.5"
13801398
},
13811399
"openapi": "3.0.0",
13821400
"paths": {

doc/schemas/node_openapi.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,9 @@
529529
"bind_address": {
530530
"$ref": "#/components/schemas/string"
531531
},
532+
"enabled_operator_features": {
533+
"$ref": "#/components/schemas/OperatorFeature_set"
534+
},
532535
"endorsement": {
533536
"$ref": "#/components/schemas/Endorsement"
534537
},
@@ -589,6 +592,18 @@
589592
],
590593
"type": "string"
591594
},
595+
"OperatorFeature": {
596+
"enum": [
597+
"SnapshotRead"
598+
],
599+
"type": "string"
600+
},
601+
"OperatorFeature_set": {
602+
"items": {
603+
"$ref": "#/components/schemas/OperatorFeature"
604+
},
605+
"type": "array"
606+
},
592607
"ParserConfiguration": {
593608
"properties": {
594609
"initial_window_size": {

include/ccf/endpoint.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "ccf/http_consts.h"
99
#include "ccf/rest_verb.h"
1010
#include "ccf/service/map.h"
11+
#include "ccf/service/operator_feature.h"
1112

1213
#include <string>
1314
#include <utility>
@@ -229,6 +230,8 @@ namespace ccf::endpoints
229230
* @see ccf::any_cert_auth_policy
230231
*/
231232
AuthnPolicies authn_policies;
233+
234+
std::set<OperatorFeature> required_operator_features;
232235
};
233236

234237
using EndpointDefinitionPtr = std::shared_ptr<const EndpointDefinition>;
@@ -308,6 +311,14 @@ namespace ccf::endpoints
308311
*/
309312
Endpoint& set_openapi_hidden(bool hidden);
310313

314+
/** Add an opt-in feature which this endpoint uses. The endpoint will only
315+
* be available on interfaces which have opted in to enabling all required
316+
* features.
317+
*
318+
* @return This Endpoint for further modification
319+
*/
320+
Endpoint& require_operator_feature(OperatorFeature feature);
321+
311322
/** Sets the JSON schema that the request parameters must comply with.
312323
*
313324
* @param j Request parameters JSON schema

include/ccf/service/node_info_network.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "ccf/ds/nonstd.h"
88
#include "ccf/http_configuration.h"
99
#include "ccf/service/acme_client_config.h"
10+
#include "ccf/service/operator_feature.h"
1011

1112
#include <string>
1213

@@ -115,6 +116,11 @@ namespace ccf
115116
/// Timeout for forwarded RPC calls (in milliseconds)
116117
std::optional<size_t> forwarding_timeout_ms = std::nullopt;
117118

119+
/// Features enabled for this interface. Any endpoint with required
120+
/// features will be inaccessible (on this interface) if this does not
121+
/// contain those features.
122+
std::set<ccf::endpoints::OperatorFeature> enabled_operator_features;
123+
118124
struct Redirections
119125
{
120126
RedirectionResolverConfig to_primary;
@@ -136,6 +142,7 @@ namespace ccf
136142
http_configuration == other.http_configuration &&
137143
accepted_endpoints == other.accepted_endpoints &&
138144
forwarding_timeout_ms == other.forwarding_timeout_ms &&
145+
enabled_operator_features == other.enabled_operator_features &&
139146
redirections == other.redirections;
140147
}
141148
};
@@ -183,6 +190,7 @@ namespace ccf
183190
http_configuration,
184191
accepted_endpoints,
185192
forwarding_timeout_ms,
193+
enabled_operator_features,
186194
redirections);
187195
DECLARE_JSON_TYPE(NodeInfoNetwork_v2::ACME);
188196
DECLARE_JSON_REQUIRED_FIELDS(NodeInfoNetwork_v2::ACME, configurations);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the Apache 2.0 License.
3+
#pragma once
4+
5+
#include "ccf/ds/json.h"
6+
7+
namespace ccf::endpoints
8+
{
9+
enum class OperatorFeature : uint8_t
10+
{
11+
SnapshotRead,
12+
};
13+
14+
DECLARE_JSON_ENUM(
15+
OperatorFeature,
16+
{
17+
{OperatorFeature::SnapshotRead, "SnapshotRead"},
18+
});
19+
}

src/ds/pending_io.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ struct PendingIO
7979
*/
8080
static void clear_empty(std::vector<PendingIO<T>>& list)
8181
{
82-
std::remove_if(
83-
list.begin(), list.end(), [](PendingIO<T>& p) { return p.clear; });
82+
list.erase(
83+
std::remove_if(
84+
list.begin(), list.end(), [](PendingIO<T>& p) { return p.clear; }),
85+
list.end());
8486
}
8587
};

src/endpoints/endpoint.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ namespace ccf::endpoints
1313
return *this;
1414
}
1515

16+
Endpoint& Endpoint::require_operator_feature(OperatorFeature feature)
17+
{
18+
required_operator_features.insert(feature);
19+
return *this;
20+
}
21+
1622
Endpoint& Endpoint::set_params_schema(const nlohmann::json& j)
1723
{
1824
params_schema = j;

src/node/node_state.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1872,6 +1872,11 @@ namespace ccf
18721872
consensus->can_replicate());
18731873
}
18741874

1875+
std::optional<ccf::NodeId> get_primary() override
1876+
{
1877+
return consensus->primary();
1878+
}
1879+
18751880
bool is_in_initialised_state() const override
18761881
{
18771882
return sm.check(NodeStartupState::initialized);

0 commit comments

Comments
 (0)