CCF
Loading...
Searching...
No Matches
internal_tables_access.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
13#include "ccf/tx.h"
14#include "node/ledger_secrets.h"
18
19#include <algorithm>
20#include <ostream>
21
22namespace ccf
23{
24 // This class provides functions for interacting with various internal
25 // service-governance tables. Specifically, it aims to maintain some
26 // invariants amongst these tables (eg - keys being present in multiple
27 // tables) despite access by distinct callers. These tables may be accessed
28 // directly with a Tx object, but it is recommended to use these methods where
29 // available.
31 {
32 public:
33 // This class is purely a container for static methods, should not be
34 // instantiated
36
38 {
39 auto nodes = tx.rw<ccf::Nodes>(Tables::NODES);
40
41 std::map<NodeId, NodeInfo> nodes_to_delete;
42 nodes->foreach([&nodes_to_delete](const NodeId& nid, const NodeInfo& ni) {
43 // Only retire nodes that have not already been retired
45 nodes_to_delete[nid] = ni;
46 return true;
47 });
48
49 for (auto [nid, ni] : nodes_to_delete)
50 {
52 nodes->put(nid, ni);
53 }
54 }
55
56 static bool is_recovery_member(
57 ccf::kv::ReadOnlyTx& tx, const MemberId& member_id)
58 {
59 auto member_encryption_public_keys =
61 Tables::MEMBER_ENCRYPTION_PUBLIC_KEYS);
62
63 return member_encryption_public_keys->get(member_id).has_value();
64 }
65
66 static bool is_active_member(
67 ccf::kv::ReadOnlyTx& tx, const MemberId& member_id)
68 {
69 auto member_info = tx.ro<ccf::MemberInfo>(Tables::MEMBER_INFO);
70 auto mi = member_info->get(member_id);
71 if (!mi.has_value())
72 {
73 return false;
74 }
75
76 return mi->status == MemberStatus::ACTIVE;
77 }
78
79 static std::map<MemberId, ccf::crypto::Pem> get_active_recovery_members(
81 {
82 auto member_info = tx.ro<ccf::MemberInfo>(Tables::MEMBER_INFO);
83 auto member_encryption_public_keys =
85 Tables::MEMBER_ENCRYPTION_PUBLIC_KEYS);
86
87 std::map<MemberId, ccf::crypto::Pem> active_recovery_members;
88
89 member_encryption_public_keys->foreach(
90 [&active_recovery_members,
91 &member_info](const auto& mid, const auto& pem) {
92 auto info = member_info->get(mid);
93 if (!info.has_value())
94 {
95 throw std::logic_error(
96 fmt::format("Recovery member {} has no member info", mid));
97 }
98
99 if (info->status == MemberStatus::ACTIVE)
100 {
101 active_recovery_members[mid] = pem;
102 }
103 return true;
104 });
105 return active_recovery_members;
106 }
107
109 ccf::kv::Tx& tx, const NewMember& member_pub_info)
110 {
111 auto member_certs = tx.rw<ccf::MemberCerts>(Tables::MEMBER_CERTS);
112 auto member_info = tx.rw<ccf::MemberInfo>(Tables::MEMBER_INFO);
113 auto member_acks = tx.rw<ccf::MemberAcks>(Tables::MEMBER_ACKS);
114 auto signatures = tx.ro<ccf::Signatures>(Tables::SIGNATURES);
115
116 auto member_cert_der =
117 ccf::crypto::make_verifier(member_pub_info.cert)->cert_der();
118 auto id = ccf::crypto::Sha256Hash(member_cert_der).hex_str();
119
120 auto member = member_certs->get(id);
121 if (member.has_value())
122 {
123 // No effect if member already exists
124 return id;
125 }
126
127 member_certs->put(id, member_pub_info.cert);
128 member_info->put(
129 id, {MemberStatus::ACCEPTED, member_pub_info.member_data});
130
131 if (member_pub_info.encryption_pub_key.has_value())
132 {
133 auto member_encryption_public_keys =
135 Tables::MEMBER_ENCRYPTION_PUBLIC_KEYS);
136 member_encryption_public_keys->put(
137 id, member_pub_info.encryption_pub_key.value());
138 }
139
140 auto s = signatures->get();
141 if (!s)
142 {
143 member_acks->put(id, MemberAck());
144 }
145 else
146 {
147 member_acks->put(id, MemberAck(s->root));
148 }
149 return id;
150 }
151
152 static bool activate_member(ccf::kv::Tx& tx, const MemberId& member_id)
153 {
154 auto member_info = tx.rw<ccf::MemberInfo>(Tables::MEMBER_INFO);
155
156 auto member = member_info->get(member_id);
157 if (!member.has_value())
158 {
159 throw std::logic_error(fmt::format(
160 "Member {} cannot be activated as they do not exist", member_id));
161 }
162
163 const auto newly_active = member->status != MemberStatus::ACTIVE;
164
165 member->status = MemberStatus::ACTIVE;
166 if (
167 is_recovery_member(tx, member_id) &&
168 (get_active_recovery_members(tx).size() >= max_active_recovery_members))
169 {
170 throw std::logic_error(fmt::format(
171 "Cannot activate new recovery member {}: no more than {} active "
172 "recovery members are allowed",
173 member_id,
174 max_active_recovery_members));
175 }
176 member_info->put(member_id, member.value());
177
178 return newly_active;
179 }
180
181 static bool remove_member(ccf::kv::Tx& tx, const MemberId& member_id)
182 {
183 auto member_certs = tx.rw<ccf::MemberCerts>(Tables::MEMBER_CERTS);
184 auto member_encryption_public_keys =
186 Tables::MEMBER_ENCRYPTION_PUBLIC_KEYS);
187 auto member_info = tx.rw<ccf::MemberInfo>(Tables::MEMBER_INFO);
188 auto member_acks = tx.rw<ccf::MemberAcks>(Tables::MEMBER_ACKS);
189 auto member_gov_history =
190 tx.rw<ccf::GovernanceHistory>(Tables::GOV_HISTORY);
191
192 auto member_to_remove = member_info->get(member_id);
193 if (!member_to_remove.has_value())
194 {
195 // The remove member proposal is idempotent so if the member does not
196 // exist, the proposal should succeed with no effect
198 "Could not remove member {}: member does not exist", member_id);
199 return true;
200 }
201
202 // If the member was active and had a recovery share, check that
203 // the new number of active members is still sufficient for
204 // recovery
205 if (
206 member_to_remove->status == MemberStatus::ACTIVE &&
207 is_recovery_member(tx, member_id))
208 {
209 // Because the member to remove is active, there is at least one active
210 // member (i.e. get_active_recovery_members_count_after >= 0)
211 size_t get_active_recovery_members_count_after =
212 get_active_recovery_members(tx).size() - 1;
213 auto recovery_threshold = get_recovery_threshold(tx);
214 if (get_active_recovery_members_count_after < recovery_threshold)
215 {
217 "Failed to remove recovery member {}: number of active recovery "
218 "members ({}) would be less than recovery threshold ({})",
219 member_id,
220 get_active_recovery_members_count_after,
221 recovery_threshold);
222 return false;
223 }
224 }
225
226 member_info->remove(member_id);
227 member_encryption_public_keys->remove(member_id);
228 member_certs->remove(member_id);
229 member_acks->remove(member_id);
230 member_gov_history->remove(member_id);
231
232 return true;
233 }
234
235 static UserId add_user(ccf::kv::Tx& tx, const NewUser& new_user)
236 {
237 auto user_certs = tx.rw<ccf::UserCerts>(Tables::USER_CERTS);
238
239 auto user_cert_der =
240 ccf::crypto::make_verifier(new_user.cert)->cert_der();
241 auto id = ccf::crypto::Sha256Hash(user_cert_der).hex_str();
242
243 auto user_cert = user_certs->get(id);
244 if (user_cert.has_value())
245 {
246 throw std::logic_error(
247 fmt::format("Certificate already exists for user {}", id));
248 }
249
250 user_certs->put(id, new_user.cert);
251
252 if (new_user.user_data != nullptr)
253 {
254 auto user_info = tx.rw<ccf::UserInfo>(Tables::USER_INFO);
255 auto ui = user_info->get(id);
256 if (ui.has_value())
257 {
258 throw std::logic_error(
259 fmt::format("User data already exists for user {}", id));
260 }
261
262 user_info->put(id, {new_user.user_data});
263 }
264
265 return id;
266 }
267
268 static void remove_user(ccf::kv::Tx& tx, const UserId& user_id)
269 {
270 // Has no effect if the user does not exist
271 auto user_certs = tx.rw<ccf::UserCerts>(Tables::USER_CERTS);
272 auto user_info = tx.rw<ccf::UserInfo>(Tables::USER_INFO);
273
274 user_certs->remove(user_id);
275 user_info->remove(user_id);
276 }
277
278 static void add_node(
279 ccf::kv::Tx& tx, const NodeId& id, const NodeInfo& node_info)
280 {
281 auto node = tx.rw<ccf::Nodes>(Tables::NODES);
282 node->put(id, node_info);
283 }
284
285 static std::map<NodeId, NodeInfo> get_trusted_nodes(ccf::kv::ReadOnlyTx& tx)
286 {
287 std::map<NodeId, NodeInfo> active_nodes;
288
289 auto nodes = tx.ro<ccf::Nodes>(Tables::NODES);
290
291 nodes->foreach(
292 [&active_nodes, &nodes](const NodeId& nid, const NodeInfo& ni) {
294 {
295 active_nodes[nid] = ni;
296 }
297 else if (ni.status == ccf::NodeStatus::RETIRED)
298 {
299 // If a node is retired, but knowledge of their retirement has not
300 // yet been globally committed, they are still considered active.
301 auto cni = nodes->get_globally_committed(nid);
302 if (cni.has_value() && !cni->retired_committed)
303 {
304 active_nodes[nid] = ni;
305 }
306 }
307 return true;
308 });
309
310 return active_nodes;
311 }
312
313 // Service status should use a state machine, very much like NodeState.
314 static void create_service(
315 ccf::kv::Tx& tx,
316 const ccf::crypto::Pem& service_cert,
317 ccf::TxID create_txid,
318 nlohmann::json service_data = nullptr,
319 bool recovering = false)
320 {
321 auto service = tx.rw<ccf::Service>(Tables::SERVICE);
322
323 size_t recovery_count = 0;
324
325 if (service->has())
326 {
327 const auto prev_service_info = service->get();
328 auto previous_service_identity = tx.wo<ccf::PreviousServiceIdentity>(
329 ccf::Tables::PREVIOUS_SERVICE_IDENTITY);
330 previous_service_identity->put(prev_service_info->cert);
331
332 // Record number of recoveries for service. If the value does
333 // not exist in the table (i.e. pre 2.x ledger), assume it is the first
334 // recovery.
335 recovery_count = prev_service_info->recovery_count.value_or(0) + 1;
336 }
337
338 service->put(
339 {service_cert,
341 recovering ? service->get_version_of_previous_write() : std::nullopt,
342 recovery_count,
343 service_data,
344 create_txid});
345 }
346
348 ccf::kv::ReadOnlyTx& tx, const ccf::crypto::Pem& expected_service_cert)
349 {
350 auto service = tx.ro<ccf::Service>(Tables::SERVICE)->get();
351 return service.has_value() && service->cert == expected_service_cert;
352 }
353
354 static bool open_service(ccf::kv::Tx& tx)
355 {
356 auto service = tx.rw<ccf::Service>(Tables::SERVICE);
357
358 auto active_recovery_members_count =
360 if (active_recovery_members_count < get_recovery_threshold(tx))
361 {
363 "Cannot open network as number of active recovery members ({}) is "
364 "less than recovery threshold ({})",
365 active_recovery_members_count,
367 return false;
368 }
369
370 auto active_service = service->get();
371 if (!active_service.has_value())
372 {
373 LOG_FAIL_FMT("Failed to get active service");
374 return false;
375 }
376
377 if (active_service->status == ServiceStatus::OPEN)
378 {
379 // If the service is already open, return with no effect
380 return true;
381 }
382
383 if (
384 active_service->status != ServiceStatus::OPENING &&
385 active_service->status != ServiceStatus::WAITING_FOR_RECOVERY_SHARES)
386 {
388 "Could not open current service: status is not OPENING or "
389 "WAITING_FOR_RECOVERY_SHARES");
390 return false;
391 }
392
393 active_service->status = ServiceStatus::OPEN;
394 active_service->previous_service_identity_version =
395 service->get_version_of_previous_write();
396 service->put(active_service.value());
397
398 return true;
399 }
400
401 static std::optional<ServiceStatus> get_service_status(
403 {
404 auto service = tx.ro<ccf::Service>(Tables::SERVICE);
405 auto active_service = service->get();
406 if (!active_service.has_value())
407 {
408 LOG_FAIL_FMT("Failed to get active service");
409 return {};
410 }
411
412 return active_service->status;
413 }
414
415 static void trust_node(
416 ccf::kv::Tx& tx,
417 const NodeId& node_id,
418 ccf::kv::Version latest_ledger_secret_seqno)
419 {
420 auto nodes = tx.rw<ccf::Nodes>(Tables::NODES);
421 auto node_info = nodes->get(node_id);
422
423 if (!node_info.has_value())
424 {
425 throw std::logic_error(fmt::format("Node {} does not exist", node_id));
426 }
427
428 if (node_info->status == NodeStatus::RETIRED)
429 {
430 throw std::logic_error(fmt::format("Node {} is retired", node_id));
431 }
432
433 node_info->status = NodeStatus::TRUSTED;
434 node_info->ledger_secret_seqno = latest_ledger_secret_seqno;
435 nodes->put(node_id, node_info.value());
436
437 LOG_INFO_FMT("Node {} is now {}", node_id, node_info->status);
438 }
439
440 static void set_constitution(
441 ccf::kv::Tx& tx, const std::string& constitution)
442 {
443 tx.rw<ccf::Constitution>(Tables::CONSTITUTION)->put(constitution);
444 }
445
447 ccf::kv::Tx& tx,
448 const pal::PlatformAttestationMeasurement& node_measurement,
449 const QuoteFormat& platform)
450 {
451 switch (platform)
452 {
453 // For now, record null code id for virtual platform in SGX code id
454 // table
457 {
458 tx.rw<CodeIDs>(Tables::NODE_CODE_IDS)
459 ->put(
460 pal::SgxAttestationMeasurement(node_measurement),
462 break;
463 }
465 {
466 tx.rw<SnpMeasurements>(Tables::NODE_SNP_MEASUREMENTS)
467 ->put(
468 pal::SnpAttestationMeasurement(node_measurement),
470 break;
471 }
472 default:
473 {
474 throw std::logic_error(fmt::format(
475 "Unexpected quote format {} when trusting node code id", platform));
476 }
477 }
478 }
479
481 ccf::kv::Tx& tx,
482 const HostData& host_data,
483 const std::optional<HostDataMetadata>& security_policy = std::nullopt)
484 {
485 auto host_data_table = tx.rw<ccf::SnpHostDataMap>(Tables::HOST_DATA);
486 if (security_policy.has_value())
487 {
488 auto raw_security_policy =
489 ccf::crypto::raw_from_b64(security_policy.value());
490 host_data_table->put(
491 host_data, {raw_security_policy.begin(), raw_security_policy.end()});
492 }
493 else
494 {
495 LOG_TRACE_FMT("Trusting node with unset policy");
496 host_data_table->put(host_data, pal::snp::NO_SECURITY_POLICY);
497 }
498 }
499
501 ccf::kv::Tx& tx, const std::optional<UVMEndorsements>& uvm_endorsements)
502 {
503 if (!uvm_endorsements.has_value())
504 {
505 // UVM endorsements are optional
506 return;
507 }
508
509 auto uvme =
510 tx.rw<ccf::SNPUVMEndorsements>(Tables::NODE_SNP_UVM_ENDORSEMENTS);
511 uvme->put(
512 uvm_endorsements->did,
513 {{uvm_endorsements->feed, {uvm_endorsements->svn}}});
514 }
515
517 ccf::kv::Tx& tx, const ServiceConfiguration& configuration)
518 {
519 auto config = tx.rw<ccf::Configuration>(Tables::CONFIGURATION);
520 if (config->has())
521 {
522 throw std::logic_error(
523 "Cannot initialise service configuration: configuration already "
524 "exists");
525 }
526
527 config->put(configuration);
528 }
529
530 static bool set_recovery_threshold(ccf::kv::Tx& tx, size_t threshold)
531 {
532 auto config = tx.rw<ccf::Configuration>(Tables::CONFIGURATION);
533
534 if (threshold == 0)
535 {
536 LOG_FAIL_FMT("Cannot set recovery threshold to 0");
537 return false;
538 }
539
540 auto service_status = get_service_status(tx);
541 if (!service_status.has_value())
542 {
543 LOG_FAIL_FMT("Failed to get active service");
544 return false;
545 }
546
547 if (service_status.value() == ServiceStatus::WAITING_FOR_RECOVERY_SHARES)
548 {
549 // While waiting for recovery shares, the recovery threshold cannot be
550 // modified. Otherwise, the threshold could be passed without triggering
551 // the end of recovery procedure
553 "Cannot set recovery threshold: service is currently waiting for "
554 "recovery shares");
555 return false;
556 }
557 else if (service_status.value() == ServiceStatus::OPEN)
558 {
559 auto get_active_recovery_members_count =
560 get_active_recovery_members(tx).size();
561 if (threshold > get_active_recovery_members_count)
562 {
564 "Cannot set recovery threshold to {} as it is greater than the "
565 "number of active recovery members ({})",
566 threshold,
567 get_active_recovery_members_count);
568 return false;
569 }
570 }
571
572 auto current_config = config->get();
573 if (!current_config.has_value())
574 {
575 throw std::logic_error("Configuration should already be set");
576 }
577
578 current_config->recovery_threshold = threshold;
579 config->put(current_config.value());
580 return true;
581 }
582
584 {
585 auto config = tx.ro<ccf::Configuration>(Tables::CONFIGURATION);
586 auto current_config = config->get();
587 if (!current_config.has_value())
588 {
589 throw std::logic_error(
590 "Failed to get recovery threshold: No active configuration found");
591 }
592 return current_config->recovery_threshold;
593 }
594 };
595}
Definition internal_tables_access.h:31
static bool is_service_created(ccf::kv::ReadOnlyTx &tx, const ccf::crypto::Pem &expected_service_cert)
Definition internal_tables_access.h:347
static bool set_recovery_threshold(ccf::kv::Tx &tx, size_t threshold)
Definition internal_tables_access.h:530
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 std::optional< ServiceStatus > get_service_status(ccf::kv::ReadOnlyTx &tx)
Definition internal_tables_access.h:401
static bool open_service(ccf::kv::Tx &tx)
Definition internal_tables_access.h:354
static void init_configuration(ccf::kv::Tx &tx, const ServiceConfiguration &configuration)
Definition internal_tables_access.h:516
static std::map< NodeId, NodeInfo > get_trusted_nodes(ccf::kv::ReadOnlyTx &tx)
Definition internal_tables_access.h:285
static UserId add_user(ccf::kv::Tx &tx, const NewUser &new_user)
Definition internal_tables_access.h:235
static bool is_recovery_member(ccf::kv::ReadOnlyTx &tx, const MemberId &member_id)
Definition internal_tables_access.h:56
static bool remove_member(ccf::kv::Tx &tx, const MemberId &member_id)
Definition internal_tables_access.h:181
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 std::map< MemberId, ccf::crypto::Pem > get_active_recovery_members(ccf::kv::ReadOnlyTx &tx)
Definition internal_tables_access.h:79
static bool is_active_member(ccf::kv::ReadOnlyTx &tx, const MemberId &member_id)
Definition internal_tables_access.h:66
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
static bool activate_member(ccf::kv::Tx &tx, const MemberId &member_id)
Definition internal_tables_access.h:152
static void trust_node(ccf::kv::Tx &tx, const NodeId &node_id, ccf::kv::Version latest_ledger_secret_seqno)
Definition internal_tables_access.h:415
static size_t get_recovery_threshold(ccf::kv::ReadOnlyTx &tx)
Definition internal_tables_access.h:583
static void remove_user(ccf::kv::Tx &tx, const UserId &user_id)
Definition internal_tables_access.h:268
Definition pem.h:18
Definition sha256_hash.h:16
std::string hex_str() const
Definition sha256_hash.cpp:61
Definition tx.h:161
M::ReadOnlyHandle * ro(M &m)
Definition tx.h:170
Definition tx.h:202
M::Handle * rw(M &m)
Definition tx.h:213
M::WriteOnlyHandle * wo(M &m)
Definition tx.h:234
#define LOG_INFO_FMT
Definition logger.h:395
#define LOG_TRACE_FMT
Definition logger.h:378
#define LOG_FAIL_FMT
Definition logger.h:396
std::vector< uint8_t > raw_from_b64(const std::string_view &b64_string)
Definition base64.cpp:12
VerifierPtr make_verifier(const std::vector< uint8_t > &cert)
Definition verifier.cpp:18
uint64_t Version
Definition version.h:8
AttestationMeasurement< snp_attestation_measurement_size > SnpAttestationMeasurement
Definition measurement.h:107
AttestationMeasurement< sgx_attestation_measurement_size > SgxAttestationMeasurement
Definition measurement.h:97
Definition app_interface.h:15
ServiceMap< NodeId, NodeInfo > Nodes
Definition nodes.h:19
ServiceMap< UserId, UserDetails > UserInfo
Definition users.h:32
ccf::kv::RawCopySerialisedMap< MemberId, ccf::crypto::Pem > MemberCerts
Definition members.h:79
ServiceMap< MemberId, MemberDetails > MemberInfo
Definition members.h:77
ccf::kv::RawCopySerialisedMap< UserId, ccf::crypto::Pem > UserCerts
Definition users.h:31
ServiceValue< std::string > Constitution
Definition constitution.h:9
ServiceMap< DID, FeedToEndorsementsDataMap > SNPUVMEndorsements
Definition uvm_endorsements.h:24
ServiceValue< ServiceInfo > Service
Definition service.h:55
ServiceValue< PrimarySignature > Signatures
Definition signatures.h:58
ServiceMap< MemberId, SignedReq > GovernanceHistory
Definition governance_history.h:12
ServiceMap< MemberId, MemberAck > MemberAcks
Definition members.h:140
ServiceMap< pal::SgxAttestationMeasurement, CodeStatus > CodeIDs
Definition code_id.h:11
ServiceMap< pal::SnpAttestationMeasurement, CodeStatus > SnpMeasurements
Definition snp_measurements.h:12
ServiceMap< HostData, HostDataMetadata > SnpHostDataMap
Definition host_data.h:14
ccf::kv::RawCopySerialisedMap< MemberId, ccf::crypto::Pem > MemberPublicEncryptionKeys
Definition members.h:81
QuoteFormat
Definition quote_info.h:12
ServiceValue< ccf::crypto::Pem > PreviousServiceIdentity
Definition previous_service_identity.h:13
Value & value()
Definition entity_id.h:60
Definition members.h:109
Definition members.h:32
std::optional< ccf::crypto::Pem > encryption_pub_key
Definition members.h:36
ccf::crypto::Pem cert
Definition members.h:33
nlohmann::json member_data
Definition members.h:37
Definition users.h:14
nlohmann::json user_data
Definition users.h:16
ccf::crypto::Pem cert
Definition users.h:15
Definition node_info.h:30
NodeStatus status
Node status.
Definition node_info.h:36
Definition service_config.h:14
Definition tx_id.h:44
Definition kv_types.h:82
Definition measurement.h:116