CCF
Loading...
Searching...
No Matches
self_healing_open_handlers.h
Go to the documentation of this file.
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3#pragma once
4
8#include "ccf/json_handler.h"
9#include "ccf/node_context.h"
10#include "ccf/odata_error.h"
17
18namespace ccf::node
19{
20 template <typename Input>
22 std::function<std::optional<ErrorDetails>(
23 endpoints::EndpointContext& args, Input& in)>;
24
25 template <typename Input>
26 static HandlerJsonParamsAndForward wrap_recovery_decision_protocol(
28 ccf::AbstractNodeContext& node_context)
29 {
30 return [cb = std::move(cb), &node_context](
31 endpoints::EndpointContext& args, const nlohmann::json& params) {
32 auto config = node_context.get_subsystem<NodeConfigurationSubsystem>();
33 auto node_operation = node_context.get_subsystem<AbstractNodeOperation>();
34 if (config == nullptr || node_operation == nullptr)
35 {
36 return make_error(
37 HTTP_STATUS_BAD_REQUEST,
38 ccf::errors::InvalidNodeState,
39 "Unable to open recovery-decision-protocol subsystems");
40 }
41
42 if (
43 !config->get().node_config.sealing_recovery.has_value() ||
44 !config->get()
45 .node_config.sealing_recovery->recovery_decision_protocol
46 .has_value())
47 {
48 return make_error(
49 HTTP_STATUS_BAD_REQUEST,
50 ccf::errors::InvalidNodeState,
51 "This node cannot do recovery-decision-protocol");
52 }
53
54 auto in = params.get<Input>();
56
57 // ---- Validate the quote against our store and store the node info ----
58
60 args.rpc_ctx->get_session_context()->caller_cert);
61
63 QuoteVerificationResult verify_result = node_operation->verify_quote(
64 args.tx, info.quote_info, cert_der, measurement, std::nullopt, nullptr);
65 if (verify_result != QuoteVerificationResult::Verified)
66 {
67 const auto [code, message] = quote_verification_error(verify_result);
69 "Recovery-decision-protocol message from {} has an invalid quote: {} "
70 "({})",
71 info.location.name,
72 code,
73 message);
74 return make_error(code, ccf::errors::InvalidQuote, message);
75 }
76
78 "Recovery-decision-protocol message from location name {} has a valid "
79 "quote",
80 info.location.name);
81
82 // ---- The sender now has trusted code ----
83
84 // Validating that we haven't heard from this node before, of if we have
85 // that the cert hasn't changed
86 auto* node_info_handle =
88 Tables::RECOVERY_DECISION_PROTOCOL_NODES);
89 auto existing_node_info = node_info_handle->get(info.location.name);
90
91 if (existing_node_info.has_value())
92 {
93 // If we have seen this node before, check that the cert is the same
94 if (existing_node_info->node_cert_der != cert_der)
95 {
96 auto message = fmt::format(
97 "Recovery-decision-protocol message from location {} is "
98 "invalid: "
99 "certificate public key has changed",
100 info.location.name);
101 LOG_FAIL_FMT("{}", message);
102 return make_error(
103 HTTP_STATUS_BAD_REQUEST, ccf::errors::NodeAlreadyExists, message);
104 }
105 }
106 else
107 {
109 info,
110 cert_der,
111 };
112 node_info_handle->put(info.location.name, src_info);
113 }
114
115 // ---- Run callback ----
116
117 auto ret = cb(args, in);
118 if (ret.has_value())
119 {
120 jsonhandler::JsonAdapterResponse res = ret.value();
121 return res;
122 }
123
124 // ---- Advance state machine ----
125
126 try
127 {
128 node_operation->recovery_decision_protocol().advance(args.tx, false);
129 }
130 catch (const std::logic_error& e)
131 {
133 "Recovery-decision-protocol failed to advance state: {}", e.what());
134 return make_error(
135 HTTP_STATUS_INTERNAL_SERVER_ERROR,
136 ccf::errors::InternalError,
137 fmt::format(
138 "Failed to advance recovery-decision-protocol state: {}",
139 e.what()));
140 }
141
142 return make_success();
143 };
144 }
145
146 static void init_recovery_decision_protocol_handlers(
148 ccf::AbstractNodeContext& node_context)
149 {
150 auto recovery_decision_protocol_gossip =
152 -> std::optional<ErrorDetails> {
154 "Recovery-decision-protocol: receive gossip from {}",
155 in.info.location.name);
156
157 // Stop accepting gossips once a node has voted
158 auto chosen_replica =
159 args.tx.template ro<recovery_decision_protocol::ChosenNode>(
160 Tables::RECOVERY_DECISION_PROTOCOL_CHOSEN_NODE);
161 if (chosen_replica->get().has_value())
162 {
163 return ErrorDetails{
164 .status = HTTP_STATUS_INTERNAL_SERVER_ERROR,
165 .code = ccf::errors::InternalError,
166 .msg = fmt::format(
167 "This node has already voted for {}",
168 chosen_replica->get().value())};
169 }
170
171 auto gossip_handle =
172 args.tx.template rw<recovery_decision_protocol::Gossips>(
173 Tables::RECOVERY_DECISION_PROTOCOL_GOSSIPS);
174 if (gossip_handle->get(in.info.location.name).has_value())
175 {
177 "Node {} already gossiped, skipping", in.info.location.name);
178 return std::nullopt;
179 }
180 gossip_handle->put(in.info.location.name, in.txid);
181 return std::nullopt;
182 };
183 registry
185 "/recovery_decision_protocol/gossip",
186 HTTP_PUT,
187 json_adapter(wrap_recovery_decision_protocol<
188 recovery_decision_protocol::GossipRequest>(
189 recovery_decision_protocol_gossip, node_context)),
190 no_auth_required)
192 .set_openapi_hidden(true)
193 .install();
194
195 auto recovery_decision_protocol_vote =
196 [](auto& args, recovery_decision_protocol::TaggedWithNodeInfo in)
197 -> std::optional<ErrorDetails> {
199 "Recovery-decision-protocol: receive vote from {}",
200 in.info.location.name);
201
202 args.tx
203 .template rw<recovery_decision_protocol::Votes>(
204 Tables::RECOVERY_DECISION_PROTOCOL_VOTES)
205 ->insert(in.info.location.name);
206
207 return std::nullopt;
208 };
209 registry
211 "/recovery_decision_protocol/vote",
212 HTTP_PUT,
213 json_adapter(wrap_recovery_decision_protocol<
214 recovery_decision_protocol::TaggedWithNodeInfo>(
215 recovery_decision_protocol_vote, node_context)),
216 no_auth_required)
218 .set_openapi_hidden(true)
219 .install();
220
221 auto recovery_decision_protocol_iamopen =
222 [&node_context](auto& args, recovery_decision_protocol::IAmOpenRequest in)
223 -> std::optional<ErrorDetails> {
224 auto sm_state = args.tx
225 .template ro<recovery_decision_protocol::SMState>(
226 Tables::RECOVERY_DECISION_PROTOCOL_SM_STATE)
227 ->get();
228 if (!sm_state.has_value())
229 {
230 throw std::logic_error(
231 "Recovery-decision-protocol state machine state is not set");
232 }
233
234 if (
237 {
238 auto node_operation =
239 node_context.get_subsystem<AbstractNodeOperation>();
240 auto& self_iamopen_request =
241 node_operation->recovery_decision_protocol().get_iamopen_request(
242 args.tx);
243
244 auto myid = fmt::format(
245 "{}:{} previously {}@{}",
246 self_iamopen_request.info.location.name,
249 self_iamopen_request.info.service_cert_der)),
250 self_iamopen_request.prev_service_fingerprint,
251 self_iamopen_request.txid.to_str());
252 auto inid = fmt::format(
253 "{}:{} previously {}@{}",
254 in.info.location.name,
256 crypto::cert_der_to_pem(in.info.service_cert_der)),
257 in.prev_service_fingerprint,
258 in.txid.to_str());
260 "{} is already open, ignoring IAmOpen from {}", myid, inid);
261
262 return ErrorDetails{
263 .status = HTTP_STATUS_BAD_REQUEST,
264 .code = ccf::errors::InvalidNodeState,
265 .msg = "Node is already open, ignoring iamopen request"};
266 }
267
269 "Recovery-decision-protocol: receive IAmOpen from {}",
270 in.info.location.name);
271 args.tx
272 .template rw<recovery_decision_protocol::SMState>(
273 Tables::RECOVERY_DECISION_PROTOCOL_SM_STATE)
275 args.tx
276 .template rw<recovery_decision_protocol::ChosenNode>(
277 Tables::RECOVERY_DECISION_PROTOCOL_CHOSEN_NODE)
278 ->put(in.info.location.name);
279 return std::nullopt;
280 };
281 registry
283 "/recovery_decision_protocol/iamopen",
284 HTTP_PUT,
285 json_adapter(wrap_recovery_decision_protocol<
286 recovery_decision_protocol::IAmOpenRequest>(
287 recovery_decision_protocol_iamopen, node_context)),
288 no_auth_required)
290 .set_openapi_hidden(true)
291 .install();
292
293 auto recovery_decision_protocol_timeout = [&](
294 auto& args,
295 const nlohmann::json& params) {
296 (void)params;
297 auto config = node_context.get_subsystem<NodeConfigurationSubsystem>();
298 auto node_operation = node_context.get_subsystem<AbstractNodeOperation>();
299 if (config == nullptr || node_operation == nullptr)
300 {
301 return make_error(
302 HTTP_STATUS_BAD_REQUEST,
303 ccf::errors::InvalidNodeState,
304 "Unable to open recovery-decision-protocol subsystems");
305 }
306
307 auto sealing_recovery_config = config->get().node_config.sealing_recovery;
308
309 if (
310 !sealing_recovery_config.has_value() ||
311 !sealing_recovery_config->recovery_decision_protocol.has_value())
312 {
313 return make_error(
314 HTTP_STATUS_BAD_REQUEST,
315 ccf::errors::InvalidNodeState,
316 "This node cannot do recovery-decision-protocol");
317 }
318
319 LOG_TRACE_FMT("Recovery-decision-protocol timeout received");
320
321 // Must ensure that the request originates from the primary
322 auto primary_id = node_operation->get_primary();
323 if (!primary_id.has_value())
324 {
325 LOG_FAIL_FMT("recovery-decision-protocol timeout: primary unknown");
326 return make_error(
327 HTTP_STATUS_INTERNAL_SERVER_ERROR,
328 ccf::errors::InternalError,
329 "Primary is unknown");
330 }
331 const auto& sig_auth_ident =
332 args.template get_caller<ccf::NodeCertAuthnIdentity>();
333 if (primary_id.value() != sig_auth_ident.node_id)
334 {
336 "recovery-decision-protocol timeout: request does not originate from "
337 "primary");
338 return make_error(
339 HTTP_STATUS_INTERNAL_SERVER_ERROR,
340 ccf::errors::InternalError,
341 "Request does not originate from primary.");
342 }
343
344 try
345 {
346 node_operation->recovery_decision_protocol().advance(args.tx, true);
347 }
348 catch (const std::logic_error& e)
349 {
351 "Recovery-decision-protocol failed to advance state: {}", e.what());
352 return make_error(
353 HTTP_STATUS_INTERNAL_SERVER_ERROR,
354 ccf::errors::InternalError,
355 fmt::format(
356 "Failed to advance recovery-decision-protocol state: {}",
357 e.what()));
358 }
359 return make_success(
360 "Recovery-decision-protocol timeout processed successfully");
361 };
362 registry
364 "/recovery_decision_protocol/timeout",
365 HTTP_PUT,
366 json_adapter(recovery_decision_protocol_timeout),
367 {std::make_shared<NodeCertAuthnPolicy>()})
368 .set_forwarding_required(endpoints::ForwardingRequired::Never)
369 .set_openapi_hidden(true)
370 .install();
371 }
372}
Definition node_operation_interface.h:24
Definition node_configuration_subsystem.h:13
Definition endpoint_registry.h:128
virtual Endpoint make_endpoint(const std::string &method, RESTVerb verb, const EndpointFunction &f, const AuthnPolicies &ap)
Definition endpoint_registry.cpp:282
M::Handle * rw(M &m)
Definition tx.h:211
Definition map.h:30
#define LOG_INFO_FMT
Definition internal_logger.h:15
#define LOG_TRACE_FMT
Definition internal_logger.h:13
#define LOG_FAIL_FMT
Definition internal_logger.h:16
ccf::crypto::Pem cert_der_to_pem(const std::vector< uint8_t > &der)
Definition verifier.cpp:33
std::vector< uint8_t > public_key_der_from_cert(const std::vector< uint8_t > &der)
Definition verifier.cpp:43
std::variant< ErrorDetails, RedirectDetails, AlreadyPopulatedResponse, nlohmann::json > JsonAdapterResponse
Definition json_handler.h:62
Definition file_serving_handlers.h:15
std::function< std::optional< ErrorDetails >(endpoints::EndpointContext &args, Input &in)> RecoveryDecisionProtocolHandler
Definition self_healing_open_handlers.h:23
std::string service_fingerprint_from_pem(const ccf::crypto::Pem &pem)
Definition self_healing_open.h:36
jsonhandler::JsonAdapterResponse make_success()
Definition json_handler.cpp:108
std::function< jsonhandler::JsonAdapterResponse(endpoints::EndpointContext &ctx, nlohmann::json &&params)> HandlerJsonParamsAndForward
Definition json_handler.h:85
QuoteVerificationResult
Definition quote.h:19
endpoints::EndpointFunction json_adapter(const HandlerJsonParamsAndForward &f)
Definition json_handler.cpp:142
jsonhandler::JsonAdapterResponse make_error(ccf::http_status status, const std::string &code, const std::string &msg)
Definition json_handler.cpp:124
Definition node_context.h:12
std::shared_ptr< T > get_subsystem() const
Definition node_context.h:60
Definition odata_error.h:58
http_status status
Definition odata_error.h:59
std::shared_ptr< ccf::RpcContext > rpc_ctx
Definition endpoint_context.h:34
Definition endpoint_context.h:58
ccf::kv::Tx & tx
Definition endpoint_context.h:64
Endpoint & set_openapi_hidden(bool hidden)
Definition endpoint.cpp:10
void install()
Definition endpoint.cpp:135
Endpoint & set_forwarding_required(ForwardingRequired fr)
Definition endpoint.cpp:74
Definition measurement.h:123
Definition recovery_decision_protocol.h:24
Definition self_healing_open.h:54
Definition self_healing_open.h:44