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