From bfae93764466e9629aa064fe6473a32b531e18a3 Mon Sep 17 00:00:00 2001 From: RogerZhongAWS <100961047+RogerZhongAWS@users.noreply.github.com> Date: Thu, 23 May 2024 18:14:23 +0000 Subject: [PATCH] Add cli/json parsing to fleet provisioning collect system info flag (#456) * publish system information at fleet provisioning * integrate fleet provisioning info with CLI/config parser --------- Co-authored-by: Jarry Shaw --- source/config/Config.cpp | 14 +- source/config/Config.h | 3 + .../fleetprovisioning/FleetProvisioning.cpp | 227 ++++++++++++++++++ source/fleetprovisioning/FleetProvisioning.h | 43 ++++ 4 files changed, 286 insertions(+), 1 deletion(-) diff --git a/source/config/Config.cpp b/source/config/Config.cpp index bd9bb5214..a053176ae 100644 --- a/source/config/Config.cpp +++ b/source/config/Config.cpp @@ -277,7 +277,8 @@ bool PlainConfig::LoadFromCliArgs(const CliArgs &cliArgs) thingName = cliArgs.at(PlainConfig::CLI_THING_NAME).c_str(); } - bool loadFeatureCliArgs = tunneling.LoadFromCliArgs(cliArgs) && logConfig.LoadFromCliArgs(cliArgs) && httpProxyConfig.LoadFromCliArgs(cliArgs); + bool loadFeatureCliArgs = tunneling.LoadFromCliArgs(cliArgs) && logConfig.LoadFromCliArgs(cliArgs) && + httpProxyConfig.LoadFromCliArgs(cliArgs); #if !defined(DISABLE_MQTT) loadFeatureCliArgs = loadFeatureCliArgs && jobs.LoadFromCliArgs(cliArgs) && deviceDefender.LoadFromCliArgs(cliArgs) && fleetProvisioning.LoadFromCliArgs(cliArgs) && @@ -1024,12 +1025,14 @@ constexpr char PlainConfig::FleetProvisioning::CLI_FLEET_PROVISIONING_TEMPLATE_N constexpr char PlainConfig::FleetProvisioning::CLI_FLEET_PROVISIONING_TEMPLATE_PARAMETERS[]; constexpr char PlainConfig::FleetProvisioning::CLI_FLEET_PROVISIONING_CSR_FILE[]; constexpr char PlainConfig::FleetProvisioning::CLI_FLEET_PROVISIONING_DEVICE_KEY[]; +constexpr char PlainConfig::FleetProvisioning::CLI_FLEET_PROVISIONING_PUBLISH_SYS_INFO[]; constexpr char PlainConfig::FleetProvisioning::JSON_KEY_ENABLED[]; constexpr char PlainConfig::FleetProvisioning::JSON_KEY_TEMPLATE_NAME[]; constexpr char PlainConfig::FleetProvisioning::JSON_KEY_TEMPLATE_PARAMETERS[]; constexpr char PlainConfig::FleetProvisioning::JSON_KEY_CSR_FILE[]; constexpr char PlainConfig::FleetProvisioning::JSON_KEY_DEVICE_KEY[]; +constexpr char PlainConfig::FleetProvisioning::JSON_KEY_PUBLISH_SYS_INFO[]; bool PlainConfig::FleetProvisioning::LoadFromJson(const Crt::JsonView &json) { @@ -1087,6 +1090,11 @@ bool PlainConfig::FleetProvisioning::LoadFromJson(const Crt::JsonView &json) Config::TAG, "Key {%s} was provided in the JSON configuration file with an empty value", jsonKey); } } + jsonKey = JSON_KEY_PUBLISH_SYS_INFO; + if (json.ValueExists(jsonKey)) + { + collectSystemInformation = json.GetBool(jsonKey); + } } return true; @@ -1117,6 +1125,10 @@ bool PlainConfig::FleetProvisioning::LoadFromCliArgs(const CliArgs &cliArgs) deviceKey = FileUtils::ExtractExpandedPath( cliArgs.at(PlainConfig::FleetProvisioning::CLI_FLEET_PROVISIONING_DEVICE_KEY).c_str()); } + if (cliArgs.count(PlainConfig::FleetProvisioning::CLI_FLEET_PROVISIONING_PUBLISH_SYS_INFO)) + { + enabled = cliArgs.at(CLI_FLEET_PROVISIONING_PUBLISH_SYS_INFO).compare("true") == 0; + } return true; } diff --git a/source/config/Config.h b/source/config/Config.h index 5d13b468e..965f2b3e3 100644 --- a/source/config/Config.h +++ b/source/config/Config.h @@ -234,18 +234,21 @@ namespace Aws "--fleet-provisioning-template-parameters"; static constexpr char CLI_FLEET_PROVISIONING_CSR_FILE[] = "--csr-file"; static constexpr char CLI_FLEET_PROVISIONING_DEVICE_KEY[] = "--device-key"; + static constexpr char CLI_FLEET_PROVISIONING_PUBLISH_SYS_INFO[] = "--collect-system-information"; static constexpr char JSON_KEY_ENABLED[] = "enabled"; static constexpr char JSON_KEY_TEMPLATE_NAME[] = "template-name"; static constexpr char JSON_KEY_TEMPLATE_PARAMETERS[] = "template-parameters"; static constexpr char JSON_KEY_CSR_FILE[] = "csr-file"; static constexpr char JSON_KEY_DEVICE_KEY[] = "device-key"; + static constexpr char JSON_KEY_PUBLISH_SYS_INFO[] = "collect-system-information"; bool enabled{false}; Aws::Crt::Optional templateName; Aws::Crt::Optional templateParameters; Aws::Crt::Optional csrFile; Aws::Crt::Optional deviceKey; + bool collectSystemInformation{false}; }; FleetProvisioning fleetProvisioning; diff --git a/source/fleetprovisioning/FleetProvisioning.cpp b/source/fleetprovisioning/FleetProvisioning.cpp index 68dd374a4..d7846db64 100644 --- a/source/fleetprovisioning/FleetProvisioning.cpp +++ b/source/fleetprovisioning/FleetProvisioning.cpp @@ -17,9 +17,21 @@ #include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include +#include #include using namespace std; @@ -34,6 +46,8 @@ using namespace Aws::Iot::DeviceClient::Util; constexpr char FleetProvisioning::TAG[]; constexpr int FleetProvisioning::DEFAULT_WAIT_TIME_SECONDS; +FleetProvisioning::FleetProvisioning() : collectSystemInformation(false) {} + bool FleetProvisioning::CreateCertificateAndKey(Iotidentity::IotIdentityClient identityClient) { LOG_INFO(TAG, "Provisioning new device certificate and private key using CreateKeysAndCertificate API"); @@ -460,6 +474,17 @@ bool FleetProvisioning::RegisterThing(Iotidentity::IotIdentityClient identityCli return false; } + if (collectSystemInformation) + { + LOG_INFO(TAG, "Collecting system information"); + if (!PopulateSystemInformation()) + { + LOGM_ERROR(TAG, "*** %s: Failed to collect system information. ***", DeviceClient::DC_FATAL_ERROR); + return false; + } + LOGM_INFO(TAG, "System information: \n\t%s", MapToString(templateParameters).c_str()); + } + LOG_INFO(TAG, "Publishing to RegisterThing topic"); RegisterThingRequest registerThingRequest; registerThingRequest.TemplateName = templateName; @@ -492,6 +517,7 @@ bool FleetProvisioning::ProvisionDevice(shared_ptr fpC try { LOG_INFO(TAG, "Fleet Provisioning Feature has been started."); + collectSystemInformation = config.fleetProvisioning.collectSystemInformation; bool didSetup = FileUtils::CreateDirectoryWithPermissions(keyDir.c_str(), S_IRWXU) && FileUtils::CreateDirectoryWithPermissions( @@ -745,3 +771,204 @@ bool FleetProvisioning::MapParameters(Aws::Crt::Optional params) } return true; } + +bool FleetProvisioning::PopulateSystemInformation() +{ + // Step 1: Get MAC and IP address of the device. + if (!CollectNetworkInformation()) + { + LOG_ERROR(TAG, "*** %s: Failed to collect network information ***"); + return false; + } + + // Step 2: Get hash values of related files. + char exec_path[PATH_MAX]; + ssize_t count = readlink("/proc/self/exe", exec_path, PATH_MAX); + if (count == -1) + { + LOG_ERROR(TAG, "*** %s: Failed to get executable path ***"); + return false; + } + std::string exec_path_str(exec_path, count); + + if (!CalculateFileSHA256Value("IoTDeviceClient", exec_path_str)) + { + LOG_ERROR(TAG, "*** %s: Failed to calculate IoT device client hash value ***"); + return false; + } + + // Step 3: Get provisioning certificate IDs. + if (!ObtainCertificateSerialID(certPath.c_str())) + { + LOG_ERROR(TAG, "*** %s: Failed to obtain provisioning certificate IDs ***"); + return false; + } + + return true; +} + +bool FleetProvisioning::CollectNetworkInformation() +{ + struct ifaddrs *ifap = nullptr; + char ip[INET6_ADDRSTRLEN]; + + if (getifaddrs(&ifap) == -1) + { + LOG_ERROR(TAG, "*** %s: Failed to get network interfaces ***"); + return false; + } + + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd == -1) + { + freeifaddrs(ifap); + + LOG_ERROR(TAG, "*** %s: Failed to create socket ***"); + return false; + } + + for (struct ifaddrs *ifa = ifap; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr == nullptr) + continue; + + int family = ifa->ifa_addr->sa_family; + char *name = ifa->ifa_name; + + // We only search for addresses on eth0 interface. + if (family == AF_INET && strncmp(name, "eth0", 3) == 0) + { + struct in_addr addr = (reinterpret_cast(ifa->ifa_addr))->sin_addr; + inet_ntop(AF_INET, &addr, ip, INET_ADDRSTRLEN); + + struct ifreq ifr; + unsigned char *mac; + + strncpy(ifr.ifr_name, name, IFNAMSIZ - 1); + if (ioctl(fd, SIOCGIFHWADDR, &ifr) == -1) + { + close(fd); + freeifaddrs(ifap); + + LOG_ERROR(TAG, "*** %s: Failed to get MAC address for interface ***"); + return false; + } + mac = reinterpret_cast(ifr.ifr_hwaddr.sa_data); + + Aws::Crt::Optional params(FormatMessage( + R"({"DeviceIPAddress": "%s", "DeviceMACAddress": "%02x:%02x:%02x:%02x:%02x:%02x"})", + ip, + mac[0], + mac[1], + mac[2], + mac[3], + mac[4], + mac[5])); + MapParameters(params); + LOGM_DEBUG(TAG, "Successfully collected network information: %s", params.value().c_str()); + + break; + } + } + + close(fd); + freeifaddrs(ifap); + return true; +} + +bool FleetProvisioning::CalculateFileSHA256Value(const char *fileName, const std::string &filePath) +{ + std::ifstream file(filePath, std::ios::binary); + if (!file.is_open()) + { + LOG_ERROR(TAG, "*** %s: Failed to open file"); + return false; + } + + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + if (!mdctx) + { + LOG_ERROR(TAG, "*** %s: Failed to create EVP_MD_CTX"); + return false; + } + + if (EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1) + { + LOG_ERROR(TAG, "*** %s: Failed to initialize EVP_DigestInit_ex"); + return false; + } + + const int bufferSize = 8192; + char buffer[bufferSize]; + while (file.good()) + { + file.read(buffer, bufferSize); + + if (!EVP_DigestUpdate(mdctx, buffer, file.gcount())) + { + LOG_ERROR(TAG, "*** %s: Failed to update EVP_DigestUpdate"); + return false; + } + } + + unsigned char hashBuffer[SHA256_DIGEST_LENGTH]; + if (EVP_DigestFinal_ex(mdctx, hashBuffer, NULL) != 1) + { + LOG_ERROR(TAG, "*** %s: Failed to finalize EVP_DigestFinal_ex"); + return false; + } + EVP_MD_CTX_free(mdctx); + + std::stringstream ss; + for (unsigned int i = 0; i < SHA256_DIGEST_LENGTH; i++) + { + ss << std::hex << std::setw(2) << std::setfill('0') << (int)hashBuffer[i]; + } + std::string hash = ss.str(); + + Aws::Crt::Optional params(FormatMessage(R"({"%s-SHA256Hash": "%s"})", fileName, hash.c_str())); + MapParameters(params); + LOGM_DEBUG(TAG, "File '%s' SHA256 hash: %s", fileName, hash.c_str()); + + return true; +} + +bool FleetProvisioning::ObtainCertificateSerialID(const char *certPath) +{ + BIO *certBIO = BIO_new(BIO_s_file()); + if (BIO_read_filename(certBIO, certPath) <= 0) + { + BIO_free(certBIO); + + LOG_ERROR(TAG, "*** %s: Failed to open certificate file ***"); + return false; + } + + X509 *cert = PEM_read_bio_X509(certBIO, nullptr, nullptr, nullptr); + if (cert == nullptr) + { + BIO_free(certBIO); + + LOG_ERROR(TAG, "*** %s: Failed to load certificate ***"); + return false; + } + + // Convert ASN1_INTEGER to a readable string + ASN1_INTEGER *serial = X509_get_serialNumber(cert); + BIGNUM *bn = ASN1_INTEGER_to_BN(serial, nullptr); + char *hex = BN_bn2hex(bn); + std::string serialNumber(hex); + + // Clean up + BN_free(bn); + OPENSSL_free(hex); + X509_free(cert); + BIO_free(certBIO); + + Aws::Crt::Optional params( + FormatMessage(R"({"ProvisioningCertSerialNumber": "%s"})", serialNumber.c_str())); + MapParameters(params); + LOGM_DEBUG(TAG, "Provisioning certificate serial number: %s", serialNumber.c_str()); + + return true; +} diff --git a/source/fleetprovisioning/FleetProvisioning.h b/source/fleetprovisioning/FleetProvisioning.h index 232c57857..6895bc86c 100644 --- a/source/fleetprovisioning/FleetProvisioning.h +++ b/source/fleetprovisioning/FleetProvisioning.h @@ -20,6 +20,11 @@ namespace Aws class FleetProvisioning { public: + /** + * \brief Constructor + */ + FleetProvisioning(); + /** * \brief Provisions device by creating and storing required resources * @@ -156,6 +161,11 @@ namespace Aws */ std::string csrFile; + /** + * \brief Flag whether to collect system information. + */ + bool collectSystemInformation; + /** * \brief creates a new certificate and private key using the AWS certificate authority * @@ -214,6 +224,39 @@ namespace Aws * @return returns false if client is not able to find the file or if valid permissions are not set */ bool LocateDeviceKey(const std::string &filePath) const; + + /** + * \brief Collect system information for fleet provisioning. + * + * @return returns false if client is not able to collect required information + */ + bool PopulateSystemInformation(); + + /** + * \brief Collect network information for fleet provisioning. + * + * @return returns false if client is not able to collect required information + */ + bool CollectNetworkInformation(); + + /** + * \brief Calculate SHA-256 hash value of the given file. + * + * @param fileName friendly display name of the file + * @param filePath path to the file + * + * @return returns false if client is not able to collect required information + */ + bool CalculateFileSHA256Value(const char *fileName, const std::string &filePath); + + /** + * \brief Obtain serial number of the given X.509 certificate. + * + * @param certPath path to the certificate + * + * @return returns false if client is not able to collect required information + */ + bool ObtainCertificateSerialID(const char *certPath); }; } // namespace FleetProvisioningNS } // namespace DeviceClient