Aktualizr
C++ SOTA Client
config.cc
1 #include "config.h"
2 
3 #include <fcntl.h>
4 #include <unistd.h>
5 #include <iomanip>
6 #include <sstream>
7 
8 #include <boost/algorithm/string/classification.hpp>
9 #include <boost/algorithm/string/split.hpp>
10 #include <boost/algorithm/string/trim.hpp>
11 #include <boost/filesystem.hpp>
12 
13 #include "bootstrap/bootstrap.h"
14 #include "config.h"
15 #include "utilities/exceptions.h"
16 #include "utilities/utils.h"
17 
18 void NetworkConfig::updateFromPropertyTree(const boost::property_tree::ptree& pt) {
19  CopyFromConfig(ipdiscovery_host, "ipdiscovery_host", pt);
20  CopyFromConfig(ipdiscovery_port, "ipdiscovery_port", pt);
21  CopyFromConfig(ipdiscovery_wait_seconds, "ipdiscovery_wait_seconds", pt);
22  CopyFromConfig(ipuptane_port, "ipuptane_port", pt);
23 }
24 
25 void NetworkConfig::writeToStream(std::ostream& out_stream) const {
26  writeOption(out_stream, ipdiscovery_host, "ipdiscovery_host");
27  writeOption(out_stream, ipdiscovery_port, "ipdiscovery_port");
28  writeOption(out_stream, ipdiscovery_wait_seconds, "ipdiscovery_wait_seconds");
29  writeOption(out_stream, ipuptane_port, "ipuptane_port");
30 }
31 
32 void TlsConfig::updateFromPropertyTree(const boost::property_tree::ptree& pt) {
33  CopyFromConfig(server, "server", pt);
34  CopyFromConfig(server_url_path, "server_url_path", pt);
35  CopyFromConfig(ca_source, "ca_source", pt);
36  CopyFromConfig(cert_source, "cert_source", pt);
37  CopyFromConfig(pkey_source, "pkey_source", pt);
38 }
39 
40 void TlsConfig::writeToStream(std::ostream& out_stream) const {
41  writeOption(out_stream, server, "server");
42  writeOption(out_stream, server_url_path, "server_url_path");
43  writeOption(out_stream, ca_source, "ca_source");
44  writeOption(out_stream, pkey_source, "pkey_source");
45  writeOption(out_stream, cert_source, "cert_source");
46 }
47 
48 void ProvisionConfig::updateFromPropertyTree(const boost::property_tree::ptree& pt) {
49  CopyFromConfig(server, "server", pt);
50  CopyFromConfig(p12_password, "p12_password", pt);
51  CopyFromConfig(expiry_days, "expiry_days", pt);
52  CopyFromConfig(provision_path, "provision_path", pt);
53  CopyFromConfig(device_id, "device_id", pt);
54  CopyFromConfig(primary_ecu_serial, "primary_ecu_serial", pt);
55  CopyFromConfig(primary_ecu_hardware_id, "primary_ecu_hardware_id", pt);
56  CopyFromConfig(ecu_registration_endpoint, "ecu_registration_endpoint", pt);
57  // provision.mode is set in postUpdateValues.
58 }
59 
60 void ProvisionConfig::writeToStream(std::ostream& out_stream) const {
61  writeOption(out_stream, server, "server");
62  writeOption(out_stream, p12_password, "p12_password");
63  writeOption(out_stream, expiry_days, "expiry_days");
64  writeOption(out_stream, provision_path, "provision_path");
65  writeOption(out_stream, device_id, "device_id");
66  writeOption(out_stream, primary_ecu_serial, "primary_ecu_serial");
67  writeOption(out_stream, primary_ecu_hardware_id, "primary_ecu_hardware_id");
68  writeOption(out_stream, ecu_registration_endpoint, "ecu_registration_endpoint");
69  // Skip provision.mode since it is dependent on other options.
70 }
71 
72 void UptaneConfig::updateFromPropertyTree(const boost::property_tree::ptree& pt) {
73  CopyFromConfig(running_mode, "running_mode", pt);
74  CopyFromConfig(polling_sec, "polling_sec", pt);
75  CopyFromConfig(director_server, "director_server", pt);
76  CopyFromConfig(repo_server, "repo_server", pt);
77  CopyFromConfig(key_source, "key_source", pt);
78  CopyFromConfig(key_type, "key_type", pt);
79  CopyFromConfig(legacy_interface, "legacy_interface", pt);
80  // uptane.secondary_configs is populated by processing secondary configs from
81  // the commandline and uptane.legacy_interface.
82 }
83 
84 void UptaneConfig::writeToStream(std::ostream& out_stream) const {
85  writeOption(out_stream, StringFromRunningMode(running_mode), "running_mode");
86  writeOption(out_stream, polling_sec, "polling_sec");
87  writeOption(out_stream, director_server, "director_server");
88  writeOption(out_stream, repo_server, "repo_server");
89  writeOption(out_stream, key_source, "key_source");
90  writeOption(out_stream, key_type, "key_type");
91  writeOption(out_stream, legacy_interface, "legacy_interface");
92 }
93 
94 void DiscoveryConfig::updateFromPropertyTree(const boost::property_tree::ptree& pt) {
95  CopyFromConfig(ipuptane, "ipuptane", pt);
96 }
97 
98 void DiscoveryConfig::writeToStream(std::ostream& out_stream) const { writeOption(out_stream, ipuptane, "ipuptane"); }
99 
100 /**
101  * \par Description:
102  * Overload the << operator for the configuration class allowing
103  * us to print its content, i.e. for logging purposes.
104  */
105 std::ostream& operator<<(std::ostream& os, const Config& cfg) {
106  cfg.writeToStream(os);
107  return os;
108 }
109 
110 Config::Config() { postUpdateValues(); }
111 
112 Config::Config(const boost::filesystem::path& filename) {
113  updateFromToml(filename);
114  postUpdateValues();
115 }
116 
117 Config::Config(const std::vector<boost::filesystem::path>& config_dirs) {
118  checkDirs(config_dirs);
119  updateFromDirs(config_dirs);
120  postUpdateValues();
121 }
122 
123 Config::Config(const boost::program_options::variables_map& cmd) {
124  // Redundantly check and set the loglevel from the commandline prematurely so
125  // that it is taken account while processing the config.
126  if (cmd.count("loglevel") != 0) {
127  logger.loglevel = cmd["loglevel"].as<int>();
128  logger_set_threshold(logger);
129  loglevel_from_cmdline = true;
130  }
131 
132  if (cmd.count("config") > 0) {
133  const auto configs = cmd["config"].as<std::vector<boost::filesystem::path>>();
134  checkDirs(configs);
135  updateFromDirs(configs);
136  } else {
137  updateFromDirs(config_dirs_);
138  }
139  updateFromCommandLine(cmd);
140  postUpdateValues();
141 }
142 
143 KeyManagerConfig Config::keymanagerConfig() const {
144  return KeyManagerConfig{p11, tls.ca_source, tls.pkey_source, tls.cert_source, uptane.key_type, uptane.key_source};
145 }
146 
147 void Config::postUpdateValues() {
148  logger_set_threshold(logger);
149 
150  if (provision.provision_path.empty()) {
151  provision.mode = ProvisionMode::kImplicit;
152  }
153 
154  if (tls.server.empty()) {
155  if (!tls.server_url_path.empty()) {
156  try {
157  tls.server = Utils::readFile(tls.server_url_path);
158  } catch (const boost::filesystem::filesystem_error& e) {
159  LOG_ERROR << "Couldn't read gateway URL: " << e.what();
160  tls.server = "";
161  }
162  } else if (!provision.provision_path.empty()) {
163  if (boost::filesystem::exists(provision.provision_path)) {
164  tls.server = Bootstrap::readServerUrl(provision.provision_path);
165  } else {
166  LOG_ERROR << "Provided provision archive " << provision.provision_path << " does not exist!";
167  }
168  }
169  }
170 
171  if (!tls.server.empty()) {
172  if (provision.server.empty()) {
173  provision.server = tls.server;
174  }
175 
176  if (uptane.repo_server.empty()) {
177  uptane.repo_server = tls.server + "/repo";
178  }
179 
180  if (uptane.director_server.empty()) {
181  uptane.director_server = tls.server + "/director";
182  }
183 
184  if (pacman.ostree_server.empty()) {
185  pacman.ostree_server = tls.server + "/treehub";
186  }
187  }
188 
189  if (!uptane.director_server.empty()) {
190  if (provision.ecu_registration_endpoint.empty()) {
191  provision.ecu_registration_endpoint = uptane.director_server + "/ecus";
192  }
193  }
194 
195  checkLegacyVersion();
196  initLegacySecondaries();
197  LOG_TRACE << "Final configuration that will be used: \n" << (*this);
198 }
199 
200 // For testing
201 void Config::updateFromTomlString(const std::string& contents) {
202  boost::property_tree::ptree pt;
203  std::stringstream stream(contents);
204  boost::property_tree::ini_parser::read_ini(stream, pt);
205  updateFromPropertyTree(pt);
206 }
207 
208 void Config::updateFromPropertyTree(const boost::property_tree::ptree& pt) {
209  // Keep this order the same as in config.h and Config::writeToStream().
210  if (!loglevel_from_cmdline) {
211  CopySubtreeFromConfig(logger, "logger", pt);
212  // If not already set from the commandline, set the loglevel now so that it
213  // affects the rest of the config processing.
214  logger_set_threshold(logger);
215  }
216  CopySubtreeFromConfig(network, "network", pt);
217  CopySubtreeFromConfig(p11, "p11", pt);
218  CopySubtreeFromConfig(tls, "tls", pt);
219  CopySubtreeFromConfig(provision, "provision", pt);
220  CopySubtreeFromConfig(uptane, "uptane", pt);
221  CopySubtreeFromConfig(discovery, "discovery", pt);
222  CopySubtreeFromConfig(pacman, "pacman", pt);
223  CopySubtreeFromConfig(storage, "storage", pt);
224  CopySubtreeFromConfig(import, "import", pt);
225  CopySubtreeFromConfig(telemetry, "telemetry", pt);
226  CopySubtreeFromConfig(bootloader, "bootloader", pt);
227 }
228 
229 void Config::updateFromCommandLine(const boost::program_options::variables_map& cmd) {
230  // Try to keep these options in the same order as parse_options() in main.cc.
231  if (cmd.count("loglevel") != 0) {
232  logger.loglevel = cmd["loglevel"].as<int>();
233  }
234  if (cmd.count("running-mode") != 0) {
235  uptane.running_mode = RunningModeFromString(cmd["running-mode"].as<std::string>());
236  }
237  if (cmd.count("tls-server") != 0) {
238  tls.server = cmd["tls-server"].as<std::string>();
239  }
240  if (cmd.count("repo-server") != 0) {
241  uptane.repo_server = cmd["repo-server"].as<std::string>();
242  }
243  if (cmd.count("director-server") != 0) {
244  uptane.director_server = cmd["director-server"].as<std::string>();
245  }
246  if (cmd.count("ostree-server") != 0) {
247  pacman.ostree_server = cmd["ostree-server"].as<std::string>();
248  }
249  if (cmd.count("primary-ecu-serial") != 0) {
250  provision.primary_ecu_serial = cmd["primary-ecu-serial"].as<std::string>();
251  }
252  if (cmd.count("primary-ecu-hardware-id") != 0) {
253  provision.primary_ecu_hardware_id = cmd["primary-ecu-hardware-id"].as<std::string>();
254  }
255  if (cmd.count("secondary-config") != 0) {
256  std::vector<boost::filesystem::path> sconfigs = cmd["secondary-config"].as<std::vector<boost::filesystem::path>>();
257  readSecondaryConfigs(sconfigs);
258  }
259  if (cmd.count("legacy-interface") != 0) {
260  uptane.legacy_interface = cmd["legacy-interface"].as<boost::filesystem::path>();
261  }
262 }
263 
264 void Config::readSecondaryConfigs(const std::vector<boost::filesystem::path>& sconfigs) {
265  for (auto it = sconfigs.cbegin(); it != sconfigs.cend(); ++it) {
266  uptane.secondary_configs.emplace_back(Uptane::SecondaryConfig(*it));
267  }
268 }
269 
270 void Config::checkLegacyVersion() {
271  if (uptane.legacy_interface.empty()) {
272  return;
273  }
274  if (!boost::filesystem::exists(uptane.legacy_interface)) {
275  throw FatalException(std::string("Legacy external flasher not found: ") + uptane.legacy_interface.string());
276  }
277  std::stringstream command;
278  std::string output;
279  command << uptane.legacy_interface << " api-version --loglevel " << loggerGetSeverity();
280  int rs = Utils::shell(command.str(), &output);
281  if (rs != 0) {
282  throw FatalException(std::string("Legacy external flasher api-version command failed: ") + output);
283  }
284  boost::trim_if(output, boost::is_any_of(" \n\r\t"));
285  if (output != "1") {
286  throw FatalException(std::string("Unexpected legacy external flasher API version: ") + output);
287  }
288 }
289 
290 void Config::initLegacySecondaries() {
291  if (uptane.legacy_interface.empty()) {
292  return;
293  }
294  std::stringstream command;
295  std::string output;
296  command << uptane.legacy_interface << " list-ecus --loglevel " << loggerGetSeverity();
297  int rs = Utils::shell(command.str(), &output);
298  if (rs != 0) {
299  LOG_ERROR << "Legacy external flasher list-ecus command failed: " << output;
300  return;
301  }
302 
303  std::stringstream ss(output);
304  std::string buffer;
305  while (std::getline(ss, buffer, '\n')) {
306  Uptane::SecondaryConfig sconfig;
307  sconfig.secondary_type = Uptane::SecondaryType::kLegacy;
308  std::vector<std::string> ecu_info;
309  boost::split(ecu_info, buffer, boost::is_any_of(" \n\r\t"), boost::token_compress_on);
310  if (ecu_info.size() == 0) {
311  // Could print a warning but why bother.
312  continue;
313  }
314  if (ecu_info.size() == 1) {
315  sconfig.ecu_hardware_id = ecu_info[0];
316  // Use getSerial, which will get the public_key_id, initialized in ManagedSecondary constructor.
317  sconfig.ecu_serial = "";
318  sconfig.full_client_dir = storage.path / sconfig.ecu_hardware_id;
319  LOG_INFO << "Legacy ECU configured with hardware ID " << sconfig.ecu_hardware_id;
320  } else if (ecu_info.size() >= 2) {
321  // For now, silently ignore anything after the second token.
322  sconfig.ecu_hardware_id = ecu_info[0];
323  sconfig.ecu_serial = ecu_info[1];
324  sconfig.full_client_dir = storage.path / (sconfig.ecu_hardware_id + "-" + sconfig.ecu_serial);
325  LOG_INFO << "Legacy ECU configured with hardware ID " << sconfig.ecu_hardware_id << " and serial "
326  << sconfig.ecu_serial;
327  }
328 
329  sconfig.partial_verifying = false;
330  sconfig.ecu_private_key = "sec.private";
331  sconfig.ecu_public_key = "sec.public";
332 
333  sconfig.firmware_path = sconfig.full_client_dir / "firmware.bin";
334  sconfig.metadata_path = sconfig.full_client_dir / "metadata";
335  sconfig.target_name_path = sconfig.full_client_dir / "target_name";
336  sconfig.flasher = uptane.legacy_interface;
337 
338  uptane.secondary_configs.push_back(sconfig);
339  }
340 }
341 
342 void Config::writeToStream(std::ostream& sink) const {
343  // Keep this order the same as in config.h and
344  // Config::updateFromPropertyTree().
345  WriteSectionToStream(logger, "logger", sink);
346  WriteSectionToStream(network, "network", sink);
347  WriteSectionToStream(p11, "p11", sink);
348  WriteSectionToStream(tls, "tls", sink);
349  WriteSectionToStream(provision, "provision", sink);
350  WriteSectionToStream(uptane, "uptane", sink);
351  WriteSectionToStream(discovery, "discovery", sink);
352  WriteSectionToStream(pacman, "pacman", sink);
353  WriteSectionToStream(storage, "storage", sink);
354  WriteSectionToStream(import, "import", sink);
355  WriteSectionToStream(telemetry, "telemetry", sink);
356  WriteSectionToStream(bootloader, "bootloader", sink);
357 }
358 
359 asn1::Serializer& operator<<(asn1::Serializer& ser, CryptoSource cs) {
360  ser << asn1::implicit<kAsn1Enum>(static_cast<const int32_t&>(static_cast<int>(cs)));
361 
362  return ser;
363 }
364 
365 asn1::Serializer& operator<<(asn1::Serializer& ser, const TlsConfig& tls_conf) {
366  ser << asn1::seq << asn1::implicit<kAsn1Utf8String>(tls_conf.server)
367  << asn1::implicit<kAsn1Utf8String>(tls_conf.server_url_path.string()) << tls_conf.ca_source
368  << tls_conf.pkey_source << tls_conf.cert_source << asn1::endseq;
369  return ser;
370 }
371 
372 asn1::Deserializer& operator>>(asn1::Deserializer& des, CryptoSource& cs) {
373  int32_t cs_i;
374  des >> asn1::implicit<kAsn1Enum>(cs_i);
375 
376  if (cs_i < static_cast<int>(CryptoSource::kFile) || cs_i > static_cast<int>(CryptoSource::kPkcs11)) {
377  throw deserialization_error();
378  }
379 
380  cs = static_cast<CryptoSource>(cs_i);
381 
382  return des;
383 }
384 
385 asn1::Deserializer& operator>>(asn1::Deserializer& des, boost::filesystem::path& path) {
386  std::string path_string;
387  des >> asn1::implicit<kAsn1Utf8String>(path_string);
388  path = path_string;
389  return des;
390 }
391 
392 asn1::Deserializer& operator>>(asn1::Deserializer& des, TlsConfig& tls_conf) {
393  des >> asn1::seq >> asn1::implicit<kAsn1Utf8String>(tls_conf.server) >> tls_conf.server_url_path >>
394  tls_conf.ca_source >> tls_conf.pkey_source >> tls_conf.cert_source >> asn1::endseq;
395  return des;
396 }
Configuration object for an aktualizr instance running on a primary ECU.
Definition: config.h:100