CCF
Loading...
Searching...
No Matches
node_frontend.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
7#include "ccf/http_query.h"
9#include "ccf/json_handler.h"
10#include "ccf/node/quote.h"
11#include "ccf/odata_error.h"
12#include "ccf/pal/attestation.h"
13#include "ccf/pal/mem.h"
14#include "ccf/version.h"
15#include "crypto/certs.h"
16#include "crypto/csr.h"
17#include "ds/std_formatters.h"
19#include "frontend.h"
20#include "node/network_state.h"
25#include "node_interface.h"
28
29namespace ccf
30{
31 struct Quote
32 {
34 std::vector<uint8_t> raw;
35 std::vector<uint8_t> endorsements;
37
38 std::string mrenclave = {}; // < Hex-encoded
39
40 std::optional<std::vector<uint8_t>> uvm_endorsements =
41 std::nullopt; // SNP only
42 };
43
45 DECLARE_JSON_REQUIRED_FIELDS(Quote, node_id, raw, endorsements, format);
46 DECLARE_JSON_OPTIONAL_FIELDS(Quote, mrenclave, uvm_endorsements);
47
48 struct GetQuotes
49 {
50 using In = void;
51
52 struct Out
53 {
54 std::vector<Quote> quotes;
55 };
56 };
57
60
65
68
78
82 bytecode_size,
83 bytecode_used,
84 max_heap_size,
85 max_stack_size,
86 max_execution_time,
87 max_cached_interpreters);
88
90 {
91 size_t attempts = 0;
92 size_t successes = 0;
93 size_t failures = 0;
94 };
95
97 DECLARE_JSON_REQUIRED_FIELDS(JWTRefreshMetrics, attempts, successes, failures)
98
100 {
101 std::string issuer;
103 };
104
107
109 {
110 std::string address;
111 };
112
115
116 using ConsensusConfig = std::map<std::string, ConsensusNodeConfig>;
117
122
125
130
133 SelfSignedNodeCertificateInfo, self_signed_certificate);
134
142
145 GetServicePreviousIdentity::Out, previous_service_identity);
146
148 {
149 public:
150 // The node frontend is exempt from backpressure rules to enable an operator
151 // to access a node that is not making progress.
153 {
154 return false;
155 }
156
157 private:
158 NetworkState& network;
159 ccf::AbstractNodeOperation& node_operation;
160
161 static std::pair<http_status, std::string> quote_verification_error(
163 {
164 switch (result)
165 {
167 return std::make_pair(
168 HTTP_STATUS_UNAUTHORIZED, "Quote could not be verified");
170 return std::make_pair(
171 HTTP_STATUS_UNAUTHORIZED,
172 "Quote does not contain known enclave measurement");
174 return std::make_pair(
175 HTTP_STATUS_UNAUTHORIZED,
176 "Quote report data does not contain node's public key hash");
178 return std::make_pair(
179 HTTP_STATUS_UNAUTHORIZED,
180 "Quote does not contain trusted host data");
182 return std::make_pair(
183 HTTP_STATUS_UNAUTHORIZED, "Quote host data is not authorised");
185 return std::make_pair(
186 HTTP_STATUS_UNAUTHORIZED, "UVM endorsements are not authorised");
187 default:
188 return std::make_pair(
189 HTTP_STATUS_INTERNAL_SERVER_ERROR,
190 "Unknown quote verification error");
191 }
192 }
193
194 struct ExistingNodeInfo
195 {
196 NodeId node_id;
197 std::optional<ccf::kv::Version> ledger_secret_seqno = std::nullopt;
198 std::optional<ccf::crypto::Pem> endorsed_certificate = std::nullopt;
199 };
200
201 std::optional<ExistingNodeInfo> check_node_exists(
202 ccf::kv::Tx& tx,
203 const std::vector<uint8_t>& self_signed_node_der,
204 std::optional<NodeStatus> node_status = std::nullopt)
205 {
206 // Check that a node exists by looking up its public key in the nodes
207 // table.
208 auto nodes = tx.ro(network.nodes);
209 auto endorsed_node_certificates =
210 tx.ro(network.node_endorsed_certificates);
211
213 "Check node exists with certificate [{}]", self_signed_node_der);
214 auto pk_pem = ccf::crypto::public_key_pem_from_cert(self_signed_node_der);
215
216 std::optional<ExistingNodeInfo> existing_node_info = std::nullopt;
217 nodes->foreach([&existing_node_info,
218 &pk_pem,
219 &node_status,
220 &endorsed_node_certificates](
221 const NodeId& nid, const NodeInfo& ni) {
222 if (
223 ni.public_key == pk_pem &&
224 (!node_status.has_value() || ni.status == node_status.value()))
225 {
226 existing_node_info = {
227 nid, ni.ledger_secret_seqno, endorsed_node_certificates->get(nid)};
228 return false;
229 }
230 return true;
231 });
232
233 return existing_node_info;
234 }
235
236 std::optional<NodeId> check_conflicting_node_network(
237 ccf::kv::Tx& tx, const NodeInfoNetwork& node_info_network)
238 {
239 auto nodes = tx.rw(network.nodes);
240
241 std::optional<NodeId> duplicate_node_id = std::nullopt;
242 nodes->foreach([&node_info_network, &duplicate_node_id](
243 const NodeId& nid, const NodeInfo& ni) {
244 if (
245 node_info_network.node_to_node_interface.published_address ==
246 ni.node_to_node_interface.published_address &&
247 ni.status != NodeStatus::RETIRED)
248 {
249 duplicate_node_id = nid;
250 return false;
251 }
252 return true;
253 });
254
255 return duplicate_node_id;
256 }
257
258 bool is_taking_part_in_acking(NodeStatus node_status)
259 {
260 return node_status == NodeStatus::TRUSTED;
261 }
262
263 auto add_node(
264 ccf::kv::Tx& tx,
265 const std::vector<uint8_t>& node_der,
266 const JoinNetworkNodeToNode::In& in,
267 NodeStatus node_status,
268 ServiceStatus service_status,
269 ReconfigurationType reconfiguration_type)
270 {
271 auto nodes = tx.rw(network.nodes);
272 auto node_endorsed_certificates =
273 tx.rw(network.node_endorsed_certificates);
274
275 auto conflicting_node_id =
276 check_conflicting_node_network(tx, in.node_info_network);
277 if (conflicting_node_id.has_value())
278 {
279 return make_error(
280 HTTP_STATUS_BAD_REQUEST,
281 ccf::errors::NodeAlreadyExists,
282 fmt::format(
283 "A node with the same published node address {} already exists "
284 "(node id: {}).",
285 in.node_info_network.node_to_node_interface.published_address,
286 conflicting_node_id.value()));
287 }
288
289 auto pubk_der = ccf::crypto::public_key_der_from_cert(node_der);
290 NodeId joining_node_id = compute_node_id_from_pubk_der(pubk_der);
291
292 pal::PlatformAttestationMeasurement measurement;
293
294 QuoteVerificationResult verify_result = this->node_operation.verify_quote(
295 tx, in.quote_info, pubk_der, measurement);
296 if (verify_result != QuoteVerificationResult::Verified)
297 {
298 const auto [code, message] = quote_verification_error(verify_result);
299 return make_error(code, ccf::errors::InvalidQuote, message);
300 }
301
302 std::optional<ccf::kv::Version> ledger_secret_seqno = std::nullopt;
303 if (node_status == NodeStatus::TRUSTED)
304 {
305 ledger_secret_seqno =
306 this->network.ledger_secrets->get_latest(tx).first;
307 }
308
309 // Note: All new nodes should specify a CSR from 2.x
310 auto client_public_key_pem =
312 if (in.certificate_signing_request.has_value())
313 {
314 // Verify that client's public key matches the one specified in the CSR
315 auto csr_public_key_pem = ccf::crypto::public_key_pem_from_csr(
316 in.certificate_signing_request.value());
317 if (client_public_key_pem != csr_public_key_pem)
318 {
319 return make_error(
320 HTTP_STATUS_BAD_REQUEST,
321 ccf::errors::CSRPublicKeyInvalid,
322 "Public key in CSR does not match TLS client identity.");
323 }
324 }
325
326 NodeInfo node_info = {
327 in.node_info_network,
328 in.quote_info,
329 in.public_encryption_key,
330 node_status,
331 ledger_secret_seqno,
332 measurement.hex_str(),
333 in.certificate_signing_request,
334 client_public_key_pem,
335 in.node_data};
336
337 nodes->put(joining_node_id, node_info);
338
339 LOG_INFO_FMT("Node {} added as {}", joining_node_id, node_status);
340
341 JoinNetworkNodeToNode::Out rep;
342 rep.node_status = node_status;
343 rep.node_id = joining_node_id;
344
345 if (node_status == NodeStatus::TRUSTED)
346 {
347 // Joining node only submit a CSR from 2.x
348 std::optional<ccf::crypto::Pem> endorsed_certificate = std::nullopt;
349 if (in.certificate_signing_request.has_value())
350 {
351 // For a pre-open service, extract the validity period of self-signed
352 // node certificate and use it verbatim in endorsed certificate
353 auto [valid_from, valid_to] =
354 ccf::crypto::make_verifier(node_der)->validity_period();
355 endorsed_certificate = ccf::crypto::create_endorsed_cert(
356 in.certificate_signing_request.value(),
357 valid_from,
358 valid_to,
359 this->network.identity->priv_key,
360 this->network.identity->cert);
361
362 node_endorsed_certificates->put(
363 joining_node_id, {endorsed_certificate.value()});
364 }
365
366 rep.network_info = JoinNetworkNodeToNode::Out::NetworkInfo{
367 node_operation.is_part_of_public_network(),
368 node_operation.get_last_recovered_signed_idx(),
370 this->network.ledger_secrets->get(tx),
371 *this->network.identity.get(),
372 service_status,
373 endorsed_certificate,
374 std::nullopt /* cose_signatures_config */};
375 }
376 return make_success(rep);
377 }
378
379 JWTRefreshMetrics jwt_refresh_metrics;
380 void handle_event_request_completed(
381 const ccf::endpoints::RequestCompletedEvent& event) override
382 {
383 if (event.method == "POST" && event.dispatch_path == "/jwt_keys/refresh")
384 {
385 jwt_refresh_metrics.attempts += 1;
386 int status_category = event.status / 100;
387 if (status_category >= 4)
388 {
389 jwt_refresh_metrics.failures += 1;
390 }
391 else if (status_category == 2)
392 {
393 jwt_refresh_metrics.successes += 1;
394 }
395 }
396 }
397
398 public:
401 network(network_),
402 node_operation(*context_.get_subsystem<ccf::AbstractNodeOperation>())
403 {
404 openapi_info.title = "CCF Public Node API";
405 openapi_info.description =
406 "This API provides public, uncredentialed access to service and node "
407 "state.";
408 openapi_info.document_version = "4.10.3";
409 }
410
411 void init_handlers() override
412 {
414
415 auto accept = [this](auto& args, const nlohmann::json& params) {
416 const auto in = params.get<JoinNetworkNodeToNode::In>();
417
418 if (
419 !this->node_operation.is_part_of_network() &&
420 !this->node_operation.is_part_of_public_network() &&
421 !this->node_operation.is_reading_private_ledger())
422 {
423 return make_error(
424 HTTP_STATUS_INTERNAL_SERVER_ERROR,
425 ccf::errors::InternalError,
426 "Target node should be part of network to accept new nodes.");
427 }
428
429 // Make sure that the joiner's snapshot is more recent than this node's
430 // snapshot. Otherwise, the joiner may not be given all the ledger
431 // secrets required to replay historical transactions.
432 auto this_startup_seqno =
433 this->node_operation.get_startup_snapshot_seqno();
434 if (
435 in.startup_seqno.has_value() &&
436 this_startup_seqno > in.startup_seqno.value())
437 {
438 return make_error(
439 HTTP_STATUS_BAD_REQUEST,
440 ccf::errors::StartupSeqnoIsOld,
441 fmt::format(
442 "Node requested to join from seqno {} which is older than this "
443 "node startup seqno {}. A snapshot at least as recent as {} must "
444 "be used instead.",
445 in.startup_seqno.value(),
446 this_startup_seqno,
447 this_startup_seqno));
448 }
449
450 auto nodes = args.tx.rw(this->network.nodes);
451 auto service = args.tx.rw(this->network.service);
452
453 auto active_service = service->get();
454 if (!active_service.has_value())
455 {
456 return make_error(
457 HTTP_STATUS_INTERNAL_SERVER_ERROR,
458 ccf::errors::InternalError,
459 "No service is available to accept new node.");
460 }
461
462 auto config = args.tx.ro(network.config);
463 auto service_config = config->get();
464
465 if (
466 active_service->status == ServiceStatus::OPENING ||
467 active_service->status == ServiceStatus::RECOVERING)
468 {
469 // If the service is opening, new nodes are trusted straight away
470 NodeStatus joining_node_status = NodeStatus::TRUSTED;
471
472 // If the node is already trusted, return network secrets
473 auto existing_node_info = check_node_exists(
474 args.tx,
475 args.rpc_ctx->get_session_context()->caller_cert,
476 joining_node_status);
477 if (existing_node_info.has_value())
478 {
480 rep.node_status = joining_node_status;
482 node_operation.is_part_of_public_network(),
483 node_operation.get_last_recovered_signed_idx(),
485 this->network.ledger_secrets->get(
486 args.tx, existing_node_info->ledger_secret_seqno),
487 *this->network.identity.get(),
488 active_service->status,
489 existing_node_info->endorsed_certificate,
490 std::nullopt /* cose_signatures_config */);
491
492 return make_success(rep);
493 }
494
495 if (consensus != nullptr && !this->node_operation.can_replicate())
496 {
497 auto primary_id = consensus->primary();
498 if (primary_id.has_value())
499 {
500 auto info = nodes->get(primary_id.value());
501 if (info)
502 {
503 auto& interface_id =
504 args.rpc_ctx->get_session_context()->interface_id;
505 if (!interface_id.has_value())
506 {
507 return make_error(
508 HTTP_STATUS_INTERNAL_SERVER_ERROR,
509 ccf::errors::InternalError,
510 "Cannot redirect non-RPC request.");
511 }
512 const auto& address =
513 info->rpc_interfaces[interface_id.value()].published_address;
514 args.rpc_ctx->set_response_header(
515 http::headers::LOCATION,
516 fmt::format("https://{}/node/join", address));
517
518 return make_error(
519 HTTP_STATUS_PERMANENT_REDIRECT,
520 ccf::errors::NodeCannotHandleRequest,
521 "Node is not primary; cannot handle write");
522 }
523 }
524
525 return make_error(
526 HTTP_STATUS_INTERNAL_SERVER_ERROR,
527 ccf::errors::InternalError,
528 "Primary unknown");
529 }
530
531 return add_node(
532 args.tx,
533 args.rpc_ctx->get_session_context()->caller_cert,
534 in,
535 joining_node_status,
536 active_service->status,
538 }
539
540 // If the service is open, new nodes are first added as pending and
541 // then only trusted via member governance. It is expected that a new
542 // node polls the network to retrieve the network secrets until it is
543 // trusted
544
545 auto existing_node_info = check_node_exists(
546 args.tx, args.rpc_ctx->get_session_context()->caller_cert);
547 if (existing_node_info.has_value())
548 {
550
551 // If the node already exists, return network secrets if is already
552 // trusted. Otherwise, only return its status
553 auto node_info = nodes->get(existing_node_info->node_id);
554 auto node_status = node_info->status;
555 rep.node_status = node_status;
556 if (is_taking_part_in_acking(node_status))
557 {
559 node_operation.is_part_of_public_network(),
560 node_operation.get_last_recovered_signed_idx(),
562 this->network.ledger_secrets->get(
563 args.tx, existing_node_info->ledger_secret_seqno),
564 *this->network.identity.get(),
565 active_service->status,
566 existing_node_info->endorsed_certificate,
567 std::nullopt /* cose_signatures_config */);
568
569 return make_success(rep);
570 }
571 else if (node_status == NodeStatus::PENDING)
572 {
573 // Only return node status and ID
574 return make_success(rep);
575 }
576 else
577 {
578 return make_error(
579 HTTP_STATUS_BAD_REQUEST,
580 ccf::errors::InvalidNodeState,
581 fmt::format(
582 "Joining node is not in expected state ({}).", node_status));
583 }
584 }
585 else
586 {
587 if (consensus != nullptr && !this->node_operation.can_replicate())
588 {
589 auto primary_id = consensus->primary();
590 if (primary_id.has_value())
591 {
592 auto info = nodes->get(primary_id.value());
593 if (info)
594 {
595 auto& interface_id =
596 args.rpc_ctx->get_session_context()->interface_id;
597 if (!interface_id.has_value())
598 {
599 return make_error(
600 HTTP_STATUS_INTERNAL_SERVER_ERROR,
601 ccf::errors::InternalError,
602 "Cannot redirect non-RPC request.");
603 }
604 const auto& address =
605 info->rpc_interfaces[interface_id.value()].published_address;
606 args.rpc_ctx->set_response_header(
607 http::headers::LOCATION,
608 fmt::format("https://{}/node/join", address));
609
610 return make_error(
611 HTTP_STATUS_PERMANENT_REDIRECT,
612 ccf::errors::NodeCannotHandleRequest,
613 "Node is not primary; cannot handle write");
614 }
615 }
616
617 return make_error(
618 HTTP_STATUS_INTERNAL_SERVER_ERROR,
619 ccf::errors::InternalError,
620 "Primary unknown");
621 }
622
623 // If the node does not exist, add it to the KV in state pending
624 return add_node(
625 args.tx,
626 args.rpc_ctx->get_session_context()->caller_cert,
627 in,
629 active_service->status,
631 }
632 };
633 make_endpoint("/join", HTTP_POST, json_adapter(accept), no_auth_required)
634 .set_forwarding_required(endpoints::ForwardingRequired::Never)
635 .set_openapi_hidden(true)
636 .install();
637
638 auto set_retired_committed = [this](auto& ctx, nlohmann::json&&) {
639 auto nodes = ctx.tx.rw(network.nodes);
640 nodes->foreach([this, &nodes](const auto& node_id, auto node_info) {
641 auto gc_node = nodes->get_globally_committed(node_id);
642 if (
643 gc_node.has_value() &&
644 gc_node->status == ccf::NodeStatus::RETIRED &&
645 !node_info.retired_committed)
646 {
647 // Set retired_committed on nodes for which RETIRED status
648 // has been committed.
649 node_info.retired_committed = true;
650 nodes->put(node_id, node_info);
651
652 LOG_DEBUG_FMT("Setting retired_committed on node {}", node_id);
653 }
654 return true;
655 });
656
657 return make_success();
658 };
659 make_endpoint(
660 "network/nodes/set_retired_committed",
661 HTTP_POST,
662 json_adapter(set_retired_committed),
663 {std::make_shared<NodeCertAuthnPolicy>()})
664 .set_openapi_hidden(true)
665 .install();
666
667 auto get_state = [this](auto& args, nlohmann::json&&) {
668 GetState::Out result;
669 auto [s, rts, lrs] = this->node_operation.state();
670 result.node_id = this->context.get_node_id();
671 result.state = s;
672 result.recovery_target_seqno = rts;
673 result.last_recovered_seqno = lrs;
674 result.startup_seqno =
675 this->node_operation.get_startup_snapshot_seqno();
676
677 auto signatures = args.tx.template ro<Signatures>(Tables::SIGNATURES);
678 auto sig = signatures->get();
679 if (!sig.has_value())
680 {
681 result.last_signed_seqno = 0;
682 }
683 else
684 {
685 result.last_signed_seqno = sig.value().seqno;
686 }
687
688 auto node_configuration_subsystem =
689 this->context.get_subsystem<NodeConfigurationSubsystem>();
690 if (!node_configuration_subsystem)
691 {
692 return make_error(
693 HTTP_STATUS_INTERNAL_SERVER_ERROR,
694 ccf::errors::InternalError,
695 "NodeConfigurationSubsystem is not available");
696 }
697 result.stop_notice =
698 node_configuration_subsystem->has_received_stop_notice();
699
700 return make_success(result);
701 };
702 make_read_only_endpoint(
703 "/state", HTTP_GET, json_read_only_adapter(get_state), no_auth_required)
704 .set_auto_schema<GetState>()
705 .set_forwarding_required(endpoints::ForwardingRequired::Never)
706 .install();
707
708 auto get_quote = [this](auto& args, nlohmann::json&&) {
709 QuoteInfo node_quote_info;
710 const auto result =
711 get_quote_for_this_node_v1(args.tx, node_quote_info);
712 if (result == ApiResult::OK)
713 {
714 Quote q;
715 q.node_id = context.get_node_id();
716 q.raw = node_quote_info.quote;
717 q.endorsements = node_quote_info.endorsements;
718 q.format = node_quote_info.format;
719 q.uvm_endorsements = node_quote_info.uvm_endorsements;
720
721 // get_measurement attempts to re-validate the quote to extract
722 // mrenclave and the Open Enclave is insufficiently flexible to allow
723 // quotes with expired collateral to be parsed at all. Recent nodes
724 // therefore cache their code digest on startup, and this code
725 // attempts to fetch that value when possible and only call the
726 // unreliable get_measurement otherwise.
727 auto nodes = args.tx.ro(network.nodes);
728 auto node_info = nodes->get(context.get_node_id());
729 if (node_info.has_value() && node_info->code_digest.has_value())
730 {
731 q.mrenclave = node_info->code_digest.value();
732 }
733 else
734 {
735 auto measurement =
737 if (measurement.has_value())
738 {
739 q.mrenclave = measurement.value().hex_str();
740 }
741 else
742 {
743 return make_error(
744 HTTP_STATUS_INTERNAL_SERVER_ERROR,
745 ccf::errors::InvalidQuote,
746 "Failed to extract code id from node quote.");
747 }
748 }
749
750 return make_success(q);
751 }
752 else if (result == ApiResult::NotFound)
753 {
754 return make_error(
755 HTTP_STATUS_NOT_FOUND,
756 ccf::errors::ResourceNotFound,
757 "Could not find node quote.");
758 }
759 else
760 {
761 return make_error(
762 HTTP_STATUS_INTERNAL_SERVER_ERROR,
763 ccf::errors::InternalError,
764 fmt::format("Error code: {}", ccf::api_result_to_str(result)));
765 }
766 };
767 make_read_only_endpoint(
768 "/quotes/self",
769 HTTP_GET,
770 json_read_only_adapter(get_quote),
771 no_auth_required)
772 .set_auto_schema<void, Quote>()
773 .set_forwarding_required(endpoints::ForwardingRequired::Never)
774 .install();
775
776 auto get_quotes = [this](auto& args, nlohmann::json&&) {
777 GetQuotes::Out result;
778
779 auto nodes = args.tx.ro(network.nodes);
780 nodes->foreach([&quotes = result.quotes](
781 const auto& node_id, const auto& node_info) {
782 if (node_info.status == ccf::NodeStatus::TRUSTED)
783 {
784 Quote q;
785 q.node_id = node_id;
786 q.raw = node_info.quote_info.quote;
787 q.endorsements = node_info.quote_info.endorsements;
788 q.format = node_info.quote_info.format;
789
790 // get_measurement attempts to re-validate the quote to extract
791 // mrenclave and the Open Enclave is insufficiently flexible to
792 // allow quotes with expired collateral to be parsed at all. Recent
793 // nodes therefore cache their code digest on startup, and this code
794 // attempts to fetch that value when possible and only call the
795 // unreliable get_measurement otherwise.
796 if (node_info.code_digest.has_value())
797 {
798 q.mrenclave = node_info.code_digest.value();
799 }
800 else
801 {
802 auto measurement =
803 AttestationProvider::get_measurement(node_info.quote_info);
804 if (measurement.has_value())
805 {
806 q.mrenclave = measurement.value().hex_str();
807 }
808 }
809 quotes.emplace_back(q);
810 }
811 return true;
812 });
813
814 return make_success(result);
815 };
816 make_read_only_endpoint(
817 "/quotes",
818 HTTP_GET,
819 json_read_only_adapter(get_quotes),
820 no_auth_required)
821 .set_auto_schema<GetQuotes>()
822 .install();
823
824 auto network_status = [this](auto& args, nlohmann::json&&) {
826 auto service = args.tx.ro(network.service);
827 auto service_state = service->get();
828 if (service_state.has_value())
829 {
830 const auto& service_value = service_state.value();
831 out.service_status = service_value.status;
832 out.service_certificate = service_value.cert;
833 out.recovery_count = service_value.recovery_count.value_or(0);
834 out.service_data = service_value.service_data;
836 service_value.current_service_create_txid;
837 if (consensus != nullptr)
838 {
839 out.current_view = consensus->get_view();
840 auto primary_id = consensus->primary();
841 if (primary_id.has_value())
842 {
843 out.primary_id = primary_id.value();
844 }
845 }
846 return make_success(out);
847 }
848 return make_error(
849 HTTP_STATUS_NOT_FOUND,
850 ccf::errors::ResourceNotFound,
851 "Service state not available.");
852 };
853 make_read_only_endpoint(
854 "/network",
855 HTTP_GET,
856 json_read_only_adapter(network_status),
857 no_auth_required)
858 .set_auto_schema<void, GetNetworkInfo::Out>()
859 .install();
860
861 auto service_previous_identity = [this](auto& args, nlohmann::json&&) {
862 auto psi_handle = args.tx.template ro<ccf::PreviousServiceIdentity>(
863 ccf::Tables::PREVIOUS_SERVICE_IDENTITY);
864 const auto psi = psi_handle->get();
865 if (psi.has_value())
866 {
868 out.previous_service_identity = psi.value();
869 return make_success(out);
870 }
871 else
872 {
873 return make_error(
874 HTTP_STATUS_NOT_FOUND,
875 ccf::errors::ResourceNotFound,
876 "This service is not a recovery of a previous service.");
877 }
878 };
879 make_read_only_endpoint(
880 "/service/previous_identity",
881 HTTP_GET,
882 json_read_only_adapter(service_previous_identity),
883 no_auth_required)
884 .set_auto_schema<void, GetServicePreviousIdentity::Out>()
885 .install();
886
887 auto get_nodes = [this](auto& args, nlohmann::json&&) {
888 const auto parsed_query =
889 http::parse_query(args.rpc_ctx->get_request_query());
890
891 std::string error_string; // Ignored - all params are optional
892 const auto host = http::get_query_value_opt<std::string>(
893 parsed_query, "host", error_string);
894 const auto port = http::get_query_value_opt<std::string>(
895 parsed_query, "port", error_string);
896 const auto status_str = http::get_query_value_opt<std::string>(
897 parsed_query, "status", error_string);
898
899 std::optional<NodeStatus> status;
900 if (status_str.has_value())
901 {
902 // Convert the query argument to a JSON string, try to parse it as
903 // a NodeStatus, return an error if this doesn't work
904 try
905 {
906 status = nlohmann::json(status_str.value()).get<NodeStatus>();
907 }
908 catch (const ccf::JsonParseError& e)
909 {
910 return ccf::make_error(
911 HTTP_STATUS_BAD_REQUEST,
912 ccf::errors::InvalidQueryParameterValue,
913 fmt::format(
914 "Query parameter '{}' is not a valid node status",
915 status_str.value()));
916 }
917 }
918
919 GetNodes::Out out;
920
921 auto nodes = args.tx.ro(this->network.nodes);
922 nodes->foreach([this, host, port, status, &out, nodes](
923 const NodeId& nid, const NodeInfo& ni) {
924 if (status.has_value() && status.value() != ni.status)
925 {
926 return true;
927 }
928
929 // Match on any interface
930 bool is_matched = false;
931 for (auto const& interface : ni.rpc_interfaces)
932 {
933 const auto& [pub_host, pub_port] =
934 split_net_address(interface.second.published_address);
935
936 if (
937 (!host.has_value() || host.value() == pub_host) &&
938 (!port.has_value() || port.value() == pub_port))
939 {
940 is_matched = true;
941 break;
942 }
943 }
944
945 if (!is_matched)
946 {
947 return true;
948 }
949
950 bool is_primary = false;
951 if (consensus != nullptr)
952 {
953 is_primary = consensus->primary() == nid;
954 }
955
956 out.nodes.push_back(
957 {nid,
958 ni.status,
959 is_primary,
961 ni.node_data,
962 nodes->get_version_of_previous_write(nid).value_or(0)});
963 return true;
964 });
965
966 return make_success(out);
967 };
968 make_read_only_endpoint(
969 "/network/nodes",
970 HTTP_GET,
971 json_read_only_adapter(get_nodes),
972 no_auth_required)
973 .set_auto_schema<void, GetNodes::Out>()
974 .add_query_parameter<std::string>(
976 .add_query_parameter<std::string>(
978 .add_query_parameter<std::string>(
980 .install();
981
982 auto get_removable_nodes = [this](auto& args, nlohmann::json&&) {
983 GetNodes::Out out;
984
985 auto nodes = args.tx.ro(this->network.nodes);
986 nodes->foreach(
987 [this, &out, nodes](const NodeId& node_id, const NodeInfo& ni) {
988 // Only nodes whose retire_committed status is committed can be
989 // safely removed, because any primary elected from here on would
990 // consider them retired, and would consequently not need their
991 // input in any quorum. We must therefore read the KV at its
992 // globally committed watermark, for the purpose of this RPC. Since
993 // this transaction does not perform a write, it is safe to do this.
994 auto node = nodes->get_globally_committed(node_id);
995 if (
996 node.has_value() && node->status == ccf::NodeStatus::RETIRED &&
997 node->retired_committed)
998 {
999 out.nodes.push_back(
1000 {node_id,
1001 node->status,
1002 false /* is_primary */,
1003 node->rpc_interfaces,
1004 node->node_data,
1005 nodes->get_version_of_previous_write(node_id).value_or(0)});
1006 }
1007 return true;
1008 });
1009
1010 return make_success(out);
1011 };
1012
1013 make_read_only_endpoint(
1014 "/network/removable_nodes",
1015 HTTP_GET,
1016 json_read_only_adapter(get_removable_nodes),
1017 no_auth_required)
1018 .set_auto_schema<void, GetNodes::Out>()
1019 .install();
1020
1021 auto delete_retired_committed_node =
1022 [this](auto& args, nlohmann::json&&) {
1023 GetNodes::Out out;
1024
1025 std::string node_id;
1026 std::string error;
1027 if (!get_path_param(
1028 args.rpc_ctx->get_request_path_params(),
1029 "node_id",
1030 node_id,
1031 error))
1032 {
1033 return make_error(
1034 HTTP_STATUS_BAD_REQUEST, ccf::errors::InvalidResourceName, error);
1035 }
1036
1037 auto nodes = args.tx.rw(this->network.nodes);
1038 if (!nodes->has(node_id))
1039 {
1040 return make_error(
1041 HTTP_STATUS_NOT_FOUND,
1042 ccf::errors::ResourceNotFound,
1043 "No such node");
1044 }
1045
1046 auto node_endorsed_certificates =
1047 args.tx.rw(network.node_endorsed_certificates);
1048
1049 // A node's retirement is only complete when the
1050 // transition of retired_committed is itself committed,
1051 // i.e. when the next eligible primary is guaranteed to
1052 // be aware the retirement is committed.
1053 // As a result, the handler must check node info at the
1054 // current committed level, rather than at the end of the
1055 // local suffix.
1056 // While this transaction does execute a write, it specifically
1057 // deletes the value it reads from. It is therefore safe to
1058 // execute on the basis of a potentially stale read-set,
1059 // which get_globally_committed() typically produces.
1060 auto node = nodes->get_globally_committed(node_id);
1061 if (
1062 node.has_value() && node->status == ccf::NodeStatus::RETIRED &&
1063 node->retired_committed)
1064 {
1065 nodes->remove(node_id);
1066 node_endorsed_certificates->remove(node_id);
1067 }
1068 else
1069 {
1070 return make_error(
1071 HTTP_STATUS_BAD_REQUEST,
1072 ccf::errors::NodeNotRetiredCommitted,
1073 "Node is not completely retired");
1074 }
1075
1076 return make_success(true);
1077 };
1078
1079 make_endpoint(
1080 "/network/nodes/{node_id}",
1081 HTTP_DELETE,
1082 json_adapter(delete_retired_committed_node),
1083 no_auth_required)
1084 .set_auto_schema<void, bool>()
1085 .install();
1086
1087 auto get_self_signed_certificate = [this](auto& args, nlohmann::json&&) {
1089 this->node_operation.get_self_signed_node_certificate()};
1090 };
1091 make_command_endpoint(
1092 "/self_signed_certificate",
1093 HTTP_GET,
1094 json_command_adapter(get_self_signed_certificate),
1095 no_auth_required)
1096 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1097 .set_auto_schema<void, SelfSignedNodeCertificateInfo>()
1098 .install();
1099
1100 auto get_node_info = [this](auto& args, nlohmann::json&&) {
1101 std::string node_id;
1102 std::string error;
1103 if (!get_path_param(
1104 args.rpc_ctx->get_request_path_params(),
1105 "node_id",
1106 node_id,
1107 error))
1108 {
1109 return make_error(
1110 HTTP_STATUS_BAD_REQUEST, ccf::errors::InvalidResourceName, error);
1111 }
1112
1113 auto nodes = args.tx.ro(this->network.nodes);
1114 auto info = nodes->get(node_id);
1115
1116 if (!info)
1117 {
1118 return make_error(
1119 HTTP_STATUS_NOT_FOUND,
1120 ccf::errors::ResourceNotFound,
1121 "Node not found");
1122 }
1123
1124 bool is_primary = false;
1125 if (consensus != nullptr)
1126 {
1127 auto primary = consensus->primary();
1128 if (primary.has_value() && primary.value() == node_id)
1129 {
1130 is_primary = true;
1131 }
1132 }
1133 auto& ni = info.value();
1135 node_id,
1136 ni.status,
1137 is_primary,
1138 ni.rpc_interfaces,
1139 ni.node_data,
1140 nodes->get_version_of_previous_write(node_id).value_or(0)});
1141 };
1142 make_read_only_endpoint(
1143 "/network/nodes/{node_id}",
1144 HTTP_GET,
1145 json_read_only_adapter(get_node_info),
1146 no_auth_required)
1147 .set_auto_schema<void, GetNode::Out>()
1148 .install();
1149
1150 auto get_self_node = [this](auto& args, nlohmann::json&&) {
1151 auto node_id = this->context.get_node_id();
1152 auto nodes = args.tx.ro(this->network.nodes);
1153 auto info = nodes->get(node_id);
1154
1155 bool is_primary = false;
1156 if (consensus != nullptr)
1157 {
1158 auto primary = consensus->primary();
1159 if (primary.has_value() && primary.value() == node_id)
1160 {
1161 is_primary = true;
1162 }
1163 }
1164
1165 if (info.has_value())
1166 {
1167 // Answers from the KV are preferred, as they are more up-to-date,
1168 // especially status and node_data.
1169 auto& ni = info.value();
1171 node_id,
1172 ni.status,
1173 is_primary,
1174 ni.rpc_interfaces,
1175 ni.node_data,
1176 nodes->get_version_of_previous_write(node_id).value_or(0)});
1177 }
1178 else
1179 {
1180 // If the node isn't in its KV yet, fall back to configuration
1181 auto node_configuration_subsystem =
1182 this->context.get_subsystem<NodeConfigurationSubsystem>();
1183 if (!node_configuration_subsystem)
1184 {
1185 return make_error(
1186 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1187 ccf::errors::InternalError,
1188 "NodeConfigurationSubsystem is not available");
1189 }
1191 node_id,
1193 is_primary,
1194 node_configuration_subsystem->get()
1195 .node_config.network.rpc_interfaces,
1196 node_configuration_subsystem->get().node_config.node_data,
1197 0});
1198 }
1199 };
1200 make_read_only_endpoint(
1201 "/network/nodes/self",
1202 HTTP_GET,
1203 json_read_only_adapter(get_self_node),
1204 no_auth_required)
1205 .set_auto_schema<void, GetNode::Out>()
1206 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1207 .install();
1208
1209 auto get_primary_node = [this](auto& args, nlohmann::json&&) {
1210 if (consensus != nullptr)
1211 {
1212 auto primary_id = consensus->primary();
1213 if (!primary_id.has_value())
1214 {
1215 return make_error(
1216 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1217 ccf::errors::InternalError,
1218 "Primary unknown");
1219 }
1220
1221 auto nodes = args.tx.ro(this->network.nodes);
1222 auto info = nodes->get(primary_id.value());
1223 if (!info)
1224 {
1225 return make_error(
1226 HTTP_STATUS_NOT_FOUND,
1227 ccf::errors::ResourceNotFound,
1228 "Node not found");
1229 }
1230
1231 auto& ni = info.value();
1233 primary_id.value(),
1234 ni.status,
1235 true,
1236 ni.rpc_interfaces,
1237 ni.node_data,
1238 nodes->get_version_of_previous_write(primary_id.value())
1239 .value_or(0)});
1240 }
1241 else
1242 {
1243 return make_error(
1244 HTTP_STATUS_NOT_FOUND,
1245 ccf::errors::ResourceNotFound,
1246 "No configured consensus");
1247 }
1248 };
1249 make_read_only_endpoint(
1250 "/network/nodes/primary",
1251 HTTP_GET,
1252 json_read_only_adapter(get_primary_node),
1253 no_auth_required)
1254 .set_auto_schema<void, GetNode::Out>()
1255 .install();
1256
1257 auto head_primary = [this](auto& args) {
1258 if (this->node_operation.can_replicate())
1259 {
1260 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1261 }
1262 else
1263 {
1264 args.rpc_ctx->set_response_status(HTTP_STATUS_PERMANENT_REDIRECT);
1265 if (consensus != nullptr)
1266 {
1267 auto primary_id = consensus->primary();
1268 if (!primary_id.has_value())
1269 {
1270 args.rpc_ctx->set_error(
1271 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1272 ccf::errors::InternalError,
1273 "Primary unknown");
1274 return;
1275 }
1276
1277 auto nodes = args.tx.ro(this->network.nodes);
1278 auto info = nodes->get(primary_id.value());
1279 if (info)
1280 {
1281 auto& interface_id =
1282 args.rpc_ctx->get_session_context()->interface_id;
1283 if (!interface_id.has_value())
1284 {
1285 args.rpc_ctx->set_error(
1286 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1287 ccf::errors::InternalError,
1288 "Cannot redirect non-RPC request.");
1289 return;
1290 }
1291 const auto& address =
1292 info->rpc_interfaces[interface_id.value()].published_address;
1293 args.rpc_ctx->set_response_header(
1294 http::headers::LOCATION,
1295 fmt::format("https://{}/node/primary", address));
1296 }
1297 }
1298 }
1299 };
1300 make_read_only_endpoint(
1301 "/primary", HTTP_HEAD, head_primary, no_auth_required)
1302 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1303 .install();
1304
1305 auto get_primary = [this](auto& args) {
1306 if (this->node_operation.can_replicate())
1307 {
1308 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1309 return;
1310 }
1311 else
1312 {
1313 args.rpc_ctx->set_error(
1314 HTTP_STATUS_NOT_FOUND,
1315 ccf::errors::ResourceNotFound,
1316 "Node is not primary");
1317 return;
1318 }
1319 };
1320 make_read_only_endpoint(
1321 "/primary", HTTP_GET, get_primary, no_auth_required)
1322 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1323 .install();
1324
1325 auto get_backup = [this](auto& args) {
1326 if (!this->node_operation.can_replicate())
1327 {
1328 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1329 return;
1330 }
1331 else
1332 {
1333 args.rpc_ctx->set_error(
1334 HTTP_STATUS_NOT_FOUND,
1335 ccf::errors::ResourceNotFound,
1336 "Node is not backup");
1337 return;
1338 }
1339 };
1340 make_read_only_endpoint("/backup", HTTP_GET, get_backup, no_auth_required)
1341 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1342 .install();
1343
1344 auto consensus_config = [this](auto& args, nlohmann::json&&) {
1345 // Query node for configurations, separate current from pending
1346 if (consensus != nullptr)
1347 {
1348 auto cfg = consensus->get_latest_configuration();
1349 ConsensusConfig cc;
1350 for (auto& [nid, ninfo] : cfg)
1351 {
1352 cc.emplace(
1353 nid.value(),
1355 fmt::format("{}:{}", ninfo.hostname, ninfo.port)});
1356 }
1357 return make_success(cc);
1358 }
1359 else
1360 {
1361 return make_error(
1362 HTTP_STATUS_NOT_FOUND,
1363 ccf::errors::ResourceNotFound,
1364 "No configured consensus");
1365 }
1366 };
1367
1368 make_command_endpoint(
1369 "/config",
1370 HTTP_GET,
1371 json_command_adapter(consensus_config),
1372 no_auth_required)
1373 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1374 .set_auto_schema<void, ConsensusConfig>()
1375 .install();
1376
1377 auto consensus_state = [this](auto& args, nlohmann::json&&) {
1378 if (consensus != nullptr)
1379 {
1380 return make_success(ConsensusConfigDetails{consensus->get_details()});
1381 }
1382 else
1383 {
1384 return make_error(
1385 HTTP_STATUS_NOT_FOUND,
1386 ccf::errors::ResourceNotFound,
1387 "No configured consensus");
1388 }
1389 };
1390
1391 make_command_endpoint(
1392 "/consensus",
1393 HTTP_GET,
1394 json_command_adapter(consensus_state),
1395 no_auth_required)
1396 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1397 .set_auto_schema<void, ConsensusConfigDetails>()
1398 .install();
1399
1400 auto memory_usage = [](auto& args) {
1401
1402// Do not attempt to call get_mallinfo when used from
1403// unit tests such as the frontend_test
1404#ifdef INSIDE_ENCLAVE
1406 if (ccf::pal::get_mallinfo(info))
1407 {
1408 MemoryUsage::Out mu(info);
1409 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1410 args.rpc_ctx->set_response_header(
1411 http::headers::CONTENT_TYPE, http::headervalues::contenttype::JSON);
1412 args.rpc_ctx->set_response_body(nlohmann::json(mu).dump());
1413 return;
1414 }
1415#endif
1416
1417 args.rpc_ctx->set_response_status(HTTP_STATUS_INTERNAL_SERVER_ERROR);
1418 args.rpc_ctx->set_response_body("Failed to read memory usage");
1419 };
1420
1421 make_command_endpoint("/memory", HTTP_GET, memory_usage, no_auth_required)
1422 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1423 .set_auto_schema<MemoryUsage>()
1424 .install();
1425
1426 auto node_metrics = [this](auto& args) {
1427 NodeMetrics nm;
1428 nm.sessions = node_operation.get_session_metrics();
1429
1430 args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
1431 args.rpc_ctx->set_response_header(
1432 http::headers::CONTENT_TYPE, http::headervalues::contenttype::JSON);
1433 args.rpc_ctx->set_response_body(nlohmann::json(nm).dump());
1434 };
1435
1436 make_command_endpoint(
1437 "/metrics", HTTP_GET, node_metrics, no_auth_required)
1438 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1439 .set_auto_schema<void, NodeMetrics>()
1440 .install();
1441
1442 auto js_metrics = [this](auto& args, nlohmann::json&&) {
1443 auto bytecode_map = args.tx.ro(this->network.modules_quickjs_bytecode);
1444 auto version_val = args.tx.ro(this->network.modules_quickjs_version);
1445 uint64_t bytecode_size = 0;
1446 bytecode_map->foreach(
1447 [&bytecode_size](const auto&, const auto& bytecode) {
1448 bytecode_size += bytecode.size();
1449 return true;
1450 });
1451 auto js_engine_map = args.tx.ro(this->network.js_engine);
1453 m.bytecode_size = bytecode_size;
1454 m.bytecode_used =
1455 version_val->get() == std::string(ccf::quickjs_version);
1456
1457 auto options = js_engine_map->get().value_or(ccf::JSRuntimeOptions{});
1458 m.max_stack_size = options.max_stack_bytes;
1459 m.max_heap_size = options.max_heap_bytes;
1460 m.max_execution_time = options.max_execution_time_ms;
1461 m.max_cached_interpreters = options.max_cached_interpreters;
1462
1463 return m;
1464 };
1465
1466 make_read_only_endpoint(
1467 "/js_metrics",
1468 HTTP_GET,
1469 json_read_only_adapter(js_metrics),
1470 no_auth_required)
1471 .set_auto_schema<void, JavaScriptMetrics>()
1472 .install();
1473
1474 auto version = [this](auto&, nlohmann::json&&) {
1475 GetVersion::Out result;
1476 result.ccf_version = ccf::ccf_version;
1477 result.quickjs_version = ccf::quickjs_version;
1478#ifdef UNSAFE_VERSION
1479 result.unsafe = true;
1480#else
1481 result.unsafe = false;
1482#endif
1483
1484 return make_success(result);
1485 };
1486
1487 make_command_endpoint(
1488 "/version", HTTP_GET, json_command_adapter(version), no_auth_required)
1489 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1490 .set_auto_schema<GetVersion>()
1491 .install();
1492
1493 auto create = [this](auto& ctx, nlohmann::json&& params) {
1494 LOG_DEBUG_FMT("Processing create RPC");
1495
1496 bool recovering = node_operation.is_reading_public_ledger();
1497
1498 // This endpoint can only be called once, directly from the starting
1499 // node for the genesis or end of public recovery transaction to
1500 // initialise the service
1501 if (!node_operation.is_in_initialised_state() && !recovering)
1502 {
1503 return make_error(
1504 HTTP_STATUS_FORBIDDEN,
1505 ccf::errors::InternalError,
1506 "Node is not in initial state.");
1507 }
1508
1509 const auto in = params.get<CreateNetworkNodeToNode::In>();
1510
1511 if (InternalTablesAccess::is_service_created(ctx.tx, in.service_cert))
1512 {
1513 return make_error(
1514 HTTP_STATUS_FORBIDDEN,
1515 ccf::errors::InternalError,
1516 "Service is already created.");
1517 }
1518
1520 ctx.tx, in.service_cert, in.create_txid, in.service_data, recovering);
1521
1522 // Retire all nodes, in case there are any (i.e. post recovery)
1524
1525 // Genesis transaction (i.e. not after recovery)
1526 if (in.genesis_info.has_value())
1527 {
1528 // Note that it is acceptable to start a network without any member
1529 // having a recovery share. The service will check that at least one
1530 // recovery member is added before the service is opened.
1531 for (const auto& info : in.genesis_info->members)
1532 {
1534 }
1535
1537 ctx.tx, in.genesis_info->service_configuration);
1539 ctx.tx, in.genesis_info->constitution);
1540 }
1541 else
1542 {
1543 if (in.recovery_constitution.has_value())
1544 {
1546 ctx.tx, in.recovery_constitution.value());
1547 }
1548 // On recovery, force a new ledger chunk
1549 auto tx_ = static_cast<ccf::kv::CommittableTx*>(&ctx.tx);
1550 if (tx_ == nullptr)
1551 {
1552 throw std::logic_error("Could not cast tx to CommittableTx");
1553 }
1554 tx_->set_flag(
1556 }
1557
1558 auto endorsed_certificates =
1559 ctx.tx.rw(network.node_endorsed_certificates);
1560 endorsed_certificates->put(in.node_id, in.node_endorsed_certificate);
1561
1562 NodeInfo node_info = {
1563 in.node_info_network,
1564 {in.quote_info},
1565 in.public_encryption_key,
1567 std::nullopt,
1568 in.measurement.hex_str(),
1569 in.certificate_signing_request,
1570 in.public_key,
1571 in.node_data};
1572 InternalTablesAccess::add_node(ctx.tx, in.node_id, node_info);
1573 if (
1574 in.quote_info.format != QuoteFormat::amd_sev_snp_v1 ||
1575 !in.snp_uvm_endorsements.has_value())
1576 {
1577 // For improved serviceability on SNP, do not record trusted
1578 // measurements if UVM endorsements are available
1580 ctx.tx, in.measurement, in.quote_info.format);
1581 }
1582 if (in.quote_info.format == QuoteFormat::amd_sev_snp_v1)
1583 {
1584 auto host_data =
1585 AttestationProvider::get_host_data(in.quote_info).value();
1587 ctx.tx, host_data, in.snp_security_policy);
1589 ctx.tx, in.snp_uvm_endorsements);
1590 }
1591
1592 std::optional<ccf::ClaimsDigest::Digest> digest =
1594 if (digest.has_value())
1595 {
1596 auto digest_value = digest.value();
1597 ctx.rpc_ctx->set_claims_digest(std::move(digest_value));
1598 }
1599
1600 LOG_INFO_FMT("Created service");
1601 return make_success(true);
1602 };
1603 make_endpoint(
1604 "/create", HTTP_POST, json_adapter(create), no_auth_required)
1605 .set_openapi_hidden(true)
1606 .install();
1607
1608 // Only called from node. See node_state.h.
1609 auto refresh_jwt_keys = [this](auto& ctx, nlohmann::json&& body) {
1610 // All errors are server errors since the client is the server.
1611
1612 auto primary_id = consensus->primary();
1613 if (!primary_id.has_value())
1614 {
1615 LOG_FAIL_FMT("JWT key auto-refresh: primary unknown");
1616 return make_error(
1617 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1618 ccf::errors::InternalError,
1619 "Primary is unknown");
1620 }
1621
1622 const auto& sig_auth_ident =
1623 ctx.template get_caller<ccf::NodeCertAuthnIdentity>();
1624 if (primary_id.value() != sig_auth_ident.node_id)
1625 {
1627 "JWT key auto-refresh: request does not originate from primary");
1628 return make_error(
1629 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1630 ccf::errors::InternalError,
1631 "Request does not originate from primary.");
1632 }
1633
1635 try
1636 {
1637 parsed = body.get<SetJwtPublicSigningKeys>();
1638 }
1639 catch (const ccf::JsonParseError& e)
1640 {
1641 return make_error(
1642 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1643 ccf::errors::InternalError,
1644 "Unable to parse body.");
1645 }
1646
1647 auto issuers = ctx.tx.ro(this->network.jwt_issuers);
1648 auto issuer_metadata_ = issuers->get(parsed.issuer);
1649 if (!issuer_metadata_.has_value())
1650 {
1652 "JWT key auto-refresh: {} is not a valid issuer", parsed.issuer);
1653 return make_error(
1654 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1655 ccf::errors::InternalError,
1656 fmt::format("{} is not a valid issuer.", parsed.issuer));
1657 }
1658 auto& issuer_metadata = issuer_metadata_.value();
1659
1660 if (!issuer_metadata.auto_refresh)
1661 {
1663 "JWT key auto-refresh: {} does not have auto_refresh enabled",
1664 parsed.issuer);
1665 return make_error(
1666 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1667 ccf::errors::InternalError,
1668 fmt::format(
1669 "{} does not have auto_refresh enabled.", parsed.issuer));
1670 }
1671
1672 if (!set_jwt_public_signing_keys(
1673 ctx.tx,
1674 "<auto-refresh>",
1675 parsed.issuer,
1676 issuer_metadata,
1677 parsed.jwks))
1678 {
1680 "JWT key auto-refresh: error while storing signing keys for issuer "
1681 "{}",
1682 parsed.issuer);
1683 return make_error(
1684 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1685 ccf::errors::InternalError,
1686 fmt::format(
1687 "Error while storing signing keys for issuer {}.",
1688 parsed.issuer));
1689 }
1690
1691 return make_success(true);
1692 };
1693 make_endpoint(
1694 "/jwt_keys/refresh",
1695 HTTP_POST,
1696 json_adapter(refresh_jwt_keys),
1697 {std::make_shared<NodeCertAuthnPolicy>()})
1698 .set_openapi_hidden(true)
1699 .install();
1700
1701 auto get_jwt_metrics = [this](auto& args, const nlohmann::json& params) {
1702 return make_success(jwt_refresh_metrics);
1703 };
1704 make_read_only_endpoint(
1705 "/jwt_keys/refresh/metrics",
1706 HTTP_GET,
1707 json_read_only_adapter(get_jwt_metrics),
1708 no_auth_required)
1709 .set_auto_schema<void, JWTRefreshMetrics>()
1710 .install();
1711
1712 auto service_config_handler =
1713 [this](auto& args, const nlohmann::json& params) {
1714 return make_success(args.tx.ro(network.config)->get());
1715 };
1716 make_endpoint(
1717 "/service/configuration",
1718 HTTP_GET,
1719 json_adapter(service_config_handler),
1720 no_auth_required)
1721 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1722 .set_auto_schema<void, ServiceConfiguration>()
1723 .install();
1724
1725 auto list_indexing_strategies = [this](
1726 auto& args,
1727 const nlohmann::json& params) {
1728 return make_success(this->context.get_indexing_strategies().describe());
1729 };
1730
1731 make_endpoint(
1732 "/index/strategies",
1733 HTTP_GET,
1734 json_adapter(list_indexing_strategies),
1735 no_auth_required)
1736 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1737 .set_auto_schema<void, nlohmann::json>()
1738 .install();
1739
1740 auto get_ready_app =
1741 [this](const ccf::endpoints::ReadOnlyEndpointContext& ctx) {
1742 auto node_configuration_subsystem =
1743 this->context.get_subsystem<NodeConfigurationSubsystem>();
1744 if (!node_configuration_subsystem)
1745 {
1746 ctx.rpc_ctx->set_error(
1747 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1748 ccf::errors::InternalError,
1749 "NodeConfigurationSubsystem is not available");
1750 return;
1751 }
1752 if (
1753 !node_configuration_subsystem->has_received_stop_notice() &&
1754 this->node_operation.is_part_of_network() &&
1755 this->node_operation.is_user_frontend_open())
1756 {
1757 ctx.rpc_ctx->set_response_status(HTTP_STATUS_NO_CONTENT);
1758 }
1759 else
1760 {
1761 ctx.rpc_ctx->set_response_status(HTTP_STATUS_SERVICE_UNAVAILABLE);
1762 }
1763 return;
1764 };
1765 make_read_only_endpoint(
1766 "/ready/app", HTTP_GET, get_ready_app, no_auth_required)
1767 .set_auto_schema<void, void>()
1768 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1769 .install();
1770
1771 auto get_ready_gov =
1772 [this](const ccf::endpoints::ReadOnlyEndpointContext& ctx) {
1773 auto node_configuration_subsystem =
1774 this->context.get_subsystem<NodeConfigurationSubsystem>();
1775 if (!node_configuration_subsystem)
1776 {
1777 ctx.rpc_ctx->set_error(
1778 HTTP_STATUS_INTERNAL_SERVER_ERROR,
1779 ccf::errors::InternalError,
1780 "NodeConfigurationSubsystem is not available");
1781 return;
1782 }
1783 if (
1784 !node_configuration_subsystem->has_received_stop_notice() &&
1785 this->node_operation.is_accessible_to_members() &&
1786 this->node_operation.is_member_frontend_open())
1787 {
1788 ctx.rpc_ctx->set_response_status(HTTP_STATUS_NO_CONTENT);
1789 }
1790 else
1791 {
1792 ctx.rpc_ctx->set_response_status(HTTP_STATUS_SERVICE_UNAVAILABLE);
1793 }
1794 return;
1795 };
1796 make_read_only_endpoint(
1797 "/ready/gov", HTTP_GET, get_ready_gov, no_auth_required)
1798 .set_auto_schema<void, void>()
1799 .set_forwarding_required(endpoints::ForwardingRequired::Never)
1800 .install();
1801 }
1802 };
1803
1805 {
1806 protected:
1808
1809 public:
1811 RpcFrontend(*network.tables, node_endpoints, context),
1812 node_endpoints(network, context)
1813 {}
1814 };
1815}
Definition node_operation_interface.h:22
virtual bool is_accessible_to_members() const =0
virtual bool is_user_frontend_open()=0
virtual ccf::kv::Version get_startup_snapshot_seqno()=0
virtual QuoteVerificationResult verify_quote(ccf::kv::ReadOnlyTx &tx, const QuoteInfo &quote_info, const std::vector< uint8_t > &expected_node_public_key_der, pal::PlatformAttestationMeasurement &measurement)=0
virtual bool is_in_initialised_state() const =0
virtual ccf::crypto::Pem get_self_signed_node_certificate()=0
virtual bool is_reading_private_ledger() const =0
virtual ExtendedState state()=0
virtual SessionMetrics get_session_metrics()=0
virtual bool is_member_frontend_open()=0
virtual bool can_replicate()=0
virtual bool is_reading_public_ledger() const =0
virtual bool is_part_of_network() const =0
virtual bool is_part_of_public_network() const =0
virtual ccf::kv::Version get_last_recovered_signed_idx()=0
static std::optional< HostData > get_host_data(const QuoteInfo &quote_info)
Definition quote.cpp:128
static std::optional< pal::PlatformAttestationMeasurement > get_measurement(const QuoteInfo &quote_info)
Definition quote.cpp:111
Definition common_endpoint_registry.h:16
void init_handlers() override
Definition common_endpoint_registry.cpp:69
static bool is_service_created(ccf::kv::ReadOnlyTx &tx, const ccf::crypto::Pem &expected_service_cert)
Definition internal_tables_access.h:347
static void trust_node_host_data(ccf::kv::Tx &tx, const HostData &host_data, const std::optional< HostDataMetadata > &security_policy=std::nullopt)
Definition internal_tables_access.h:480
static void init_configuration(ccf::kv::Tx &tx, const ServiceConfiguration &configuration)
Definition internal_tables_access.h:516
static MemberId add_member(ccf::kv::Tx &tx, const NewMember &member_pub_info)
Definition internal_tables_access.h:108
static void set_constitution(ccf::kv::Tx &tx, const std::string &constitution)
Definition internal_tables_access.h:440
static void create_service(ccf::kv::Tx &tx, const ccf::crypto::Pem &service_cert, ccf::TxID create_txid, nlohmann::json service_data=nullptr, bool recovering=false)
Definition internal_tables_access.h:314
static void trust_node_uvm_endorsements(ccf::kv::Tx &tx, const std::optional< UVMEndorsements > &uvm_endorsements)
Definition internal_tables_access.h:500
static void trust_node_measurement(ccf::kv::Tx &tx, const pal::PlatformAttestationMeasurement &node_measurement, const QuoteFormat &platform)
Definition internal_tables_access.h:446
static void retire_active_nodes(ccf::kv::Tx &tx)
Definition internal_tables_access.h:37
static void add_node(ccf::kv::Tx &tx, const NodeId &id, const NodeInfo &node_info)
Definition internal_tables_access.h:278
Definition json.h:24
Definition node_configuration_subsystem.h:13
Definition node_frontend.h:148
void init_handlers() override
Definition node_frontend.h:411
bool apply_uncommitted_tx_backpressure() const override
Definition node_frontend.h:152
NodeEndpoints(NetworkState &network_, ccf::AbstractNodeContext &context_)
Definition node_frontend.h:399
Definition node_frontend.h:1805
NodeEndpoints node_endpoints
Definition node_frontend.h:1807
NodeRpcFrontend(NetworkState &network, ccf::AbstractNodeContext &context)
Definition node_frontend.h:1810
Definition frontend.h:36
Definition pem.h:18
Definition committable_tx.h:18
M::ReadOnlyHandle * ro(M &m)
Definition tx.h:170
Definition tx.h:202
M::Handle * rw(M &m)
Definition tx.h:213
ReconfigurationType
Definition reconfiguration_type.h:6
@ ONE_TRANSACTION
Definition reconfiguration_type.h:7
#define DECLARE_JSON_REQUIRED_FIELDS(TYPE,...)
Definition json.h:712
#define DECLARE_JSON_TYPE(TYPE)
Definition json.h:661
#define DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(TYPE)
Definition json.h:688
#define DECLARE_JSON_OPTIONAL_FIELDS(TYPE,...)
Definition json.h:784
#define LOG_INFO_FMT
Definition logger.h:395
#define LOG_DEBUG_FMT
Definition logger.h:380
#define LOG_FAIL_FMT
Definition logger.h:396
std::string error_string(int ec)
Returns the error string from an error code.
Definition openssl_wrappers.h:38
ccf::crypto::Pem public_key_pem_from_cert(const std::vector< uint8_t > &der)
Definition verifier.cpp:48
VerifierPtr make_verifier(const std::vector< uint8_t > &cert)
Definition verifier.cpp:18
Pem public_key_pem_from_csr(const Pem &signing_request)
Definition csr.h:16
std::vector< uint8_t > public_key_der_from_cert(const std::vector< uint8_t > &der)
Definition verifier.cpp:43
@ OptionalParameter
Definition endpoint.h:122
Definition app_interface.h:15
EntityId< NodeIdFormatter > NodeId
Definition entity_id.h:155
ServiceStatus
Definition service.h:13
constexpr auto get_actor_prefix(ActorsType at)
Definition actors.h:31
constexpr char const * api_result_to_str(ApiResult result)
Definition base_endpoint_registry.h:35
@ error
Definition tls_session.h:25
QuoteVerificationResult
Definition quote.h:17
std::optional< ccf::ClaimsDigest::Digest > get_create_tx_claims_digest(ccf::kv::ReadOnlyTx &tx)
Definition create_tx_claims_digest.cpp:10
NodeId compute_node_id_from_pubk_der(const std::vector< uint8_t > &node_pubk_der)
Definition nodes.h:30
jsonhandler::JsonAdapterResponse make_success()
Definition json_handler.cpp:102
endpoints::CommandEndpointFunction json_command_adapter(const CommandHandlerWithJson &f)
Definition json_handler.cpp:148
std::map< std::string, ConsensusNodeConfig > ConsensusConfig
Definition node_frontend.h:116
QuoteFormat
Definition quote_info.h:12
NodeStatus
Definition node_info.h:18
jsonhandler::JsonAdapterResponse make_error(http_status status, const std::string &code, const std::string &msg)
Definition json_handler.cpp:118
endpoints::ReadOnlyEndpointFunction json_read_only_adapter(const ReadOnlyHandlerWithJson &f)
Definition json_handler.cpp:139
endpoints::EndpointFunction json_adapter(const HandlerJsonParamsAndForward &f)
Definition json_handler.cpp:131
ActorsType
Definition actors.h:11
Definition consensus_types.h:23
Definition configuration.h:13
Definition node_context.h:12
Definition node_frontend.h:119
ccf::kv::ConsensusDetails details
Definition node_frontend.h:120
Definition node_frontend.h:109
std::string address
Definition node_frontend.h:110
Definition node_call_types.h:58
Definition call_types.h:71
nlohmann::json service_data
Definition call_types.h:77
std::optional< NodeId > primary_id
Definition call_types.h:75
std::optional< ccf::TxID > current_service_create_txid
Definition call_types.h:78
size_t recovery_count
Definition call_types.h:76
ServiceStatus service_status
Definition call_types.h:72
std::optional< ccf::View > current_view
Definition call_types.h:74
ccf::crypto::Pem service_certificate
Definition call_types.h:73
Definition call_types.h:85
NodeStatus status
Definition call_types.h:87
ccf::NodeInfoNetwork::RpcInterfaces rpc_interfaces
Definition call_types.h:89
Definition call_types.h:100
std::vector< GetNode::NodeInfo > nodes
Definition call_types.h:101
Definition node_frontend.h:53
std::vector< Quote > quotes
Definition node_frontend.h:54
Definition node_frontend.h:49
void In
Definition node_frontend.h:50
Definition node_frontend.h:138
ccf::crypto::Pem previous_service_identity
Definition node_frontend.h:139
Definition node_frontend.h:136
Definition node_call_types.h:29
std::optional< ccf::kv::Version > last_recovered_seqno
Definition node_call_types.h:37
std::optional< ccf::kv::Version > recovery_target_seqno
Definition node_call_types.h:36
bool stop_notice
Definition node_call_types.h:39
ccf::NodeId node_id
Definition node_call_types.h:30
ccf::kv::Version startup_seqno
Definition node_call_types.h:33
ccf::NodeStartupState state
Definition node_call_types.h:31
ccf::kv::Version last_signed_seqno
Definition node_call_types.h:32
Definition node_call_types.h:25
Definition node_call_types.h:48
bool unsafe
Definition node_call_types.h:51
std::string quickjs_version
Definition node_call_types.h:50
std::string ccf_version
Definition node_call_types.h:49
Definition node_call_types.h:44
Definition jsengine.h:12
Definition node_frontend.h:90
size_t failures
Definition node_frontend.h:93
size_t attempts
Definition node_frontend.h:91
size_t successes
Definition node_frontend.h:92
Definition node_frontend.h:70
bool bytecode_used
Definition node_frontend.h:72
uint64_t max_execution_time
Definition node_frontend.h:75
uint64_t max_heap_size
Definition node_frontend.h:73
uint64_t max_stack_size
Definition node_frontend.h:74
uint64_t max_cached_interpreters
Definition node_frontend.h:76
uint64_t bytecode_size
Definition node_frontend.h:71
Definition node_call_types.h:85
Definition node_call_types.h:106
Definition node_call_types.h:99
std::optional< NetworkInfo > network_info
Definition node_call_types.h:162
NodeStatus node_status
Definition node_call_types.h:100
Definition jwt.h:97
Definition node_call_types.h:171
Definition node_call_types.h:167
Definition network_state.h:12
std::shared_ptr< LedgerSecrets > ledger_secrets
Definition network_state.h:14
std::unique_ptr< NetworkIdentity > identity
Definition network_state.h:13
const JwtIssuers jwt_issuers
Definition network_tables.h:157
const NodeEndorsedCertificates node_endorsed_certificates
Definition network_tables.h:87
const Service service
Definition network_tables.h:179
const Configuration config
Definition network_tables.h:182
const JSEngine js_engine
Definition network_tables.h:139
const ModulesQuickJsBytecode modules_quickjs_bytecode
Definition network_tables.h:134
const ModulesQuickJsVersion modules_quickjs_version
Definition network_tables.h:136
const Nodes nodes
Definition network_tables.h:86
RpcInterfaces rpc_interfaces
RPC interfaces.
Definition node_info_network.h:150
Definition node_info.h:30
QuoteInfo quote_info
Node enclave quote.
Definition node_info.h:32
NodeStatus status
Node status.
Definition node_info.h:36
nlohmann::json node_data
Definition node_info.h:57
Definition node_frontend.h:62
ccf::SessionMetrics sessions
Definition node_frontend.h:63
Describes a quote (attestation) from trusted hardware.
Definition quote_info.h:26
QuoteFormat format
Quote format.
Definition quote_info.h:28
std::optional< std::vector< uint8_t > > uvm_endorsements
UVM endorsements (SNP-only)
Definition quote_info.h:34
std::vector< uint8_t > quote
Enclave quote.
Definition quote_info.h:30
std::vector< uint8_t > endorsements
Quote endorsements.
Definition quote_info.h:32
Definition node_frontend.h:32
std::string mrenclave
Definition node_frontend.h:38
std::vector< uint8_t > raw
Definition node_frontend.h:34
std::optional< std::vector< uint8_t > > uvm_endorsements
Definition node_frontend.h:40
NodeId node_id
Definition node_frontend.h:33
QuoteFormat format
Definition node_frontend.h:36
std::vector< uint8_t > endorsements
Definition node_frontend.h:35
Definition node_frontend.h:127
ccf::crypto::Pem self_signed_certificate
Definition node_frontend.h:128
Definition service_config.h:14
Definition session_metrics.h:13
Definition node_frontend.h:100
JsonWebKeySet jwks
Definition node_frontend.h:102
std::string issuer
Definition node_frontend.h:101
Definition endpoint_context.h:70
Definition endpoint_registry.h:42
std::string dispatch_path
Definition endpoint_registry.h:48
std::string method
Definition endpoint_registry.h:43
Definition kv_types.h:182
Definition mem.h:25