Skip to content

Commit bfae937

Browse files
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 <jarryx@amazon.com>
1 parent cf76107 commit bfae937

4 files changed

Lines changed: 286 additions & 1 deletion

File tree

source/config/Config.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,8 @@ bool PlainConfig::LoadFromCliArgs(const CliArgs &cliArgs)
277277
thingName = cliArgs.at(PlainConfig::CLI_THING_NAME).c_str();
278278
}
279279

280-
bool loadFeatureCliArgs = tunneling.LoadFromCliArgs(cliArgs) && logConfig.LoadFromCliArgs(cliArgs) && httpProxyConfig.LoadFromCliArgs(cliArgs);
280+
bool loadFeatureCliArgs = tunneling.LoadFromCliArgs(cliArgs) && logConfig.LoadFromCliArgs(cliArgs) &&
281+
httpProxyConfig.LoadFromCliArgs(cliArgs);
281282
#if !defined(DISABLE_MQTT)
282283
loadFeatureCliArgs = loadFeatureCliArgs && jobs.LoadFromCliArgs(cliArgs) &&
283284
deviceDefender.LoadFromCliArgs(cliArgs) && fleetProvisioning.LoadFromCliArgs(cliArgs) &&
@@ -1024,12 +1025,14 @@ constexpr char PlainConfig::FleetProvisioning::CLI_FLEET_PROVISIONING_TEMPLATE_N
10241025
constexpr char PlainConfig::FleetProvisioning::CLI_FLEET_PROVISIONING_TEMPLATE_PARAMETERS[];
10251026
constexpr char PlainConfig::FleetProvisioning::CLI_FLEET_PROVISIONING_CSR_FILE[];
10261027
constexpr char PlainConfig::FleetProvisioning::CLI_FLEET_PROVISIONING_DEVICE_KEY[];
1028+
constexpr char PlainConfig::FleetProvisioning::CLI_FLEET_PROVISIONING_PUBLISH_SYS_INFO[];
10271029

10281030
constexpr char PlainConfig::FleetProvisioning::JSON_KEY_ENABLED[];
10291031
constexpr char PlainConfig::FleetProvisioning::JSON_KEY_TEMPLATE_NAME[];
10301032
constexpr char PlainConfig::FleetProvisioning::JSON_KEY_TEMPLATE_PARAMETERS[];
10311033
constexpr char PlainConfig::FleetProvisioning::JSON_KEY_CSR_FILE[];
10321034
constexpr char PlainConfig::FleetProvisioning::JSON_KEY_DEVICE_KEY[];
1035+
constexpr char PlainConfig::FleetProvisioning::JSON_KEY_PUBLISH_SYS_INFO[];
10331036

10341037
bool PlainConfig::FleetProvisioning::LoadFromJson(const Crt::JsonView &json)
10351038
{
@@ -1087,6 +1090,11 @@ bool PlainConfig::FleetProvisioning::LoadFromJson(const Crt::JsonView &json)
10871090
Config::TAG, "Key {%s} was provided in the JSON configuration file with an empty value", jsonKey);
10881091
}
10891092
}
1093+
jsonKey = JSON_KEY_PUBLISH_SYS_INFO;
1094+
if (json.ValueExists(jsonKey))
1095+
{
1096+
collectSystemInformation = json.GetBool(jsonKey);
1097+
}
10901098
}
10911099

10921100
return true;
@@ -1117,6 +1125,10 @@ bool PlainConfig::FleetProvisioning::LoadFromCliArgs(const CliArgs &cliArgs)
11171125
deviceKey = FileUtils::ExtractExpandedPath(
11181126
cliArgs.at(PlainConfig::FleetProvisioning::CLI_FLEET_PROVISIONING_DEVICE_KEY).c_str());
11191127
}
1128+
if (cliArgs.count(PlainConfig::FleetProvisioning::CLI_FLEET_PROVISIONING_PUBLISH_SYS_INFO))
1129+
{
1130+
enabled = cliArgs.at(CLI_FLEET_PROVISIONING_PUBLISH_SYS_INFO).compare("true") == 0;
1131+
}
11201132

11211133
return true;
11221134
}

source/config/Config.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,18 +234,21 @@ namespace Aws
234234
"--fleet-provisioning-template-parameters";
235235
static constexpr char CLI_FLEET_PROVISIONING_CSR_FILE[] = "--csr-file";
236236
static constexpr char CLI_FLEET_PROVISIONING_DEVICE_KEY[] = "--device-key";
237+
static constexpr char CLI_FLEET_PROVISIONING_PUBLISH_SYS_INFO[] = "--collect-system-information";
237238

238239
static constexpr char JSON_KEY_ENABLED[] = "enabled";
239240
static constexpr char JSON_KEY_TEMPLATE_NAME[] = "template-name";
240241
static constexpr char JSON_KEY_TEMPLATE_PARAMETERS[] = "template-parameters";
241242
static constexpr char JSON_KEY_CSR_FILE[] = "csr-file";
242243
static constexpr char JSON_KEY_DEVICE_KEY[] = "device-key";
244+
static constexpr char JSON_KEY_PUBLISH_SYS_INFO[] = "collect-system-information";
243245

244246
bool enabled{false};
245247
Aws::Crt::Optional<std::string> templateName;
246248
Aws::Crt::Optional<std::string> templateParameters;
247249
Aws::Crt::Optional<std::string> csrFile;
248250
Aws::Crt::Optional<std::string> deviceKey;
251+
bool collectSystemInformation{false};
249252
};
250253
FleetProvisioning fleetProvisioning;
251254

source/fleetprovisioning/FleetProvisioning.cpp

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,21 @@
1717
#include <aws/iotidentity/RegisterThingResponse.h>
1818
#include <aws/iotidentity/RegisterThingSubscriptionRequest.h>
1919

20+
#include <arpa/inet.h>
2021
#include <chrono>
22+
#include <ifaddrs.h>
23+
#include <iomanip>
24+
#include <net/if.h>
25+
#include <openssl/bio.h>
26+
#include <openssl/err.h>
27+
#include <openssl/evp.h>
28+
#include <openssl/pem.h>
29+
#include <openssl/x509.h>
2130
#include <string>
31+
#include <sys/ioctl.h>
32+
#include <sys/socket.h>
2233
#include <sys/stat.h>
34+
#include <unistd.h>
2335
#include <wordexp.h>
2436

2537
using namespace std;
@@ -34,6 +46,8 @@ using namespace Aws::Iot::DeviceClient::Util;
3446
constexpr char FleetProvisioning::TAG[];
3547
constexpr int FleetProvisioning::DEFAULT_WAIT_TIME_SECONDS;
3648

49+
FleetProvisioning::FleetProvisioning() : collectSystemInformation(false) {}
50+
3751
bool FleetProvisioning::CreateCertificateAndKey(Iotidentity::IotIdentityClient identityClient)
3852
{
3953
LOG_INFO(TAG, "Provisioning new device certificate and private key using CreateKeysAndCertificate API");
@@ -460,6 +474,17 @@ bool FleetProvisioning::RegisterThing(Iotidentity::IotIdentityClient identityCli
460474
return false;
461475
}
462476

477+
if (collectSystemInformation)
478+
{
479+
LOG_INFO(TAG, "Collecting system information");
480+
if (!PopulateSystemInformation())
481+
{
482+
LOGM_ERROR(TAG, "*** %s: Failed to collect system information. ***", DeviceClient::DC_FATAL_ERROR);
483+
return false;
484+
}
485+
LOGM_INFO(TAG, "System information: \n\t%s", MapToString(templateParameters).c_str());
486+
}
487+
463488
LOG_INFO(TAG, "Publishing to RegisterThing topic");
464489
RegisterThingRequest registerThingRequest;
465490
registerThingRequest.TemplateName = templateName;
@@ -492,6 +517,7 @@ bool FleetProvisioning::ProvisionDevice(shared_ptr<SharedCrtResourceManager> fpC
492517
try
493518
{
494519
LOG_INFO(TAG, "Fleet Provisioning Feature has been started.");
520+
collectSystemInformation = config.fleetProvisioning.collectSystemInformation;
495521

496522
bool didSetup = FileUtils::CreateDirectoryWithPermissions(keyDir.c_str(), S_IRWXU) &&
497523
FileUtils::CreateDirectoryWithPermissions(
@@ -745,3 +771,204 @@ bool FleetProvisioning::MapParameters(Aws::Crt::Optional<std::string> params)
745771
}
746772
return true;
747773
}
774+
775+
bool FleetProvisioning::PopulateSystemInformation()
776+
{
777+
// Step 1: Get MAC and IP address of the device.
778+
if (!CollectNetworkInformation())
779+
{
780+
LOG_ERROR(TAG, "*** %s: Failed to collect network information ***");
781+
return false;
782+
}
783+
784+
// Step 2: Get hash values of related files.
785+
char exec_path[PATH_MAX];
786+
ssize_t count = readlink("/proc/self/exe", exec_path, PATH_MAX);
787+
if (count == -1)
788+
{
789+
LOG_ERROR(TAG, "*** %s: Failed to get executable path ***");
790+
return false;
791+
}
792+
std::string exec_path_str(exec_path, count);
793+
794+
if (!CalculateFileSHA256Value("IoTDeviceClient", exec_path_str))
795+
{
796+
LOG_ERROR(TAG, "*** %s: Failed to calculate IoT device client hash value ***");
797+
return false;
798+
}
799+
800+
// Step 3: Get provisioning certificate IDs.
801+
if (!ObtainCertificateSerialID(certPath.c_str()))
802+
{
803+
LOG_ERROR(TAG, "*** %s: Failed to obtain provisioning certificate IDs ***");
804+
return false;
805+
}
806+
807+
return true;
808+
}
809+
810+
bool FleetProvisioning::CollectNetworkInformation()
811+
{
812+
struct ifaddrs *ifap = nullptr;
813+
char ip[INET6_ADDRSTRLEN];
814+
815+
if (getifaddrs(&ifap) == -1)
816+
{
817+
LOG_ERROR(TAG, "*** %s: Failed to get network interfaces ***");
818+
return false;
819+
}
820+
821+
int fd = socket(AF_INET, SOCK_DGRAM, 0);
822+
if (fd == -1)
823+
{
824+
freeifaddrs(ifap);
825+
826+
LOG_ERROR(TAG, "*** %s: Failed to create socket ***");
827+
return false;
828+
}
829+
830+
for (struct ifaddrs *ifa = ifap; ifa != nullptr; ifa = ifa->ifa_next)
831+
{
832+
if (ifa->ifa_addr == nullptr)
833+
continue;
834+
835+
int family = ifa->ifa_addr->sa_family;
836+
char *name = ifa->ifa_name;
837+
838+
// We only search for addresses on eth0 interface.
839+
if (family == AF_INET && strncmp(name, "eth0", 3) == 0)
840+
{
841+
struct in_addr addr = (reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr))->sin_addr;
842+
inet_ntop(AF_INET, &addr, ip, INET_ADDRSTRLEN);
843+
844+
struct ifreq ifr;
845+
unsigned char *mac;
846+
847+
strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
848+
if (ioctl(fd, SIOCGIFHWADDR, &ifr) == -1)
849+
{
850+
close(fd);
851+
freeifaddrs(ifap);
852+
853+
LOG_ERROR(TAG, "*** %s: Failed to get MAC address for interface ***");
854+
return false;
855+
}
856+
mac = reinterpret_cast<unsigned char *>(ifr.ifr_hwaddr.sa_data);
857+
858+
Aws::Crt::Optional<std::string> params(FormatMessage(
859+
R"({"DeviceIPAddress": "%s", "DeviceMACAddress": "%02x:%02x:%02x:%02x:%02x:%02x"})",
860+
ip,
861+
mac[0],
862+
mac[1],
863+
mac[2],
864+
mac[3],
865+
mac[4],
866+
mac[5]));
867+
MapParameters(params);
868+
LOGM_DEBUG(TAG, "Successfully collected network information: %s", params.value().c_str());
869+
870+
break;
871+
}
872+
}
873+
874+
close(fd);
875+
freeifaddrs(ifap);
876+
return true;
877+
}
878+
879+
bool FleetProvisioning::CalculateFileSHA256Value(const char *fileName, const std::string &filePath)
880+
{
881+
std::ifstream file(filePath, std::ios::binary);
882+
if (!file.is_open())
883+
{
884+
LOG_ERROR(TAG, "*** %s: Failed to open file");
885+
return false;
886+
}
887+
888+
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
889+
if (!mdctx)
890+
{
891+
LOG_ERROR(TAG, "*** %s: Failed to create EVP_MD_CTX");
892+
return false;
893+
}
894+
895+
if (EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1)
896+
{
897+
LOG_ERROR(TAG, "*** %s: Failed to initialize EVP_DigestInit_ex");
898+
return false;
899+
}
900+
901+
const int bufferSize = 8192;
902+
char buffer[bufferSize];
903+
while (file.good())
904+
{
905+
file.read(buffer, bufferSize);
906+
907+
if (!EVP_DigestUpdate(mdctx, buffer, file.gcount()))
908+
{
909+
LOG_ERROR(TAG, "*** %s: Failed to update EVP_DigestUpdate");
910+
return false;
911+
}
912+
}
913+
914+
unsigned char hashBuffer[SHA256_DIGEST_LENGTH];
915+
if (EVP_DigestFinal_ex(mdctx, hashBuffer, NULL) != 1)
916+
{
917+
LOG_ERROR(TAG, "*** %s: Failed to finalize EVP_DigestFinal_ex");
918+
return false;
919+
}
920+
EVP_MD_CTX_free(mdctx);
921+
922+
std::stringstream ss;
923+
for (unsigned int i = 0; i < SHA256_DIGEST_LENGTH; i++)
924+
{
925+
ss << std::hex << std::setw(2) << std::setfill('0') << (int)hashBuffer[i];
926+
}
927+
std::string hash = ss.str();
928+
929+
Aws::Crt::Optional<std::string> params(FormatMessage(R"({"%s-SHA256Hash": "%s"})", fileName, hash.c_str()));
930+
MapParameters(params);
931+
LOGM_DEBUG(TAG, "File '%s' SHA256 hash: %s", fileName, hash.c_str());
932+
933+
return true;
934+
}
935+
936+
bool FleetProvisioning::ObtainCertificateSerialID(const char *certPath)
937+
{
938+
BIO *certBIO = BIO_new(BIO_s_file());
939+
if (BIO_read_filename(certBIO, certPath) <= 0)
940+
{
941+
BIO_free(certBIO);
942+
943+
LOG_ERROR(TAG, "*** %s: Failed to open certificate file ***");
944+
return false;
945+
}
946+
947+
X509 *cert = PEM_read_bio_X509(certBIO, nullptr, nullptr, nullptr);
948+
if (cert == nullptr)
949+
{
950+
BIO_free(certBIO);
951+
952+
LOG_ERROR(TAG, "*** %s: Failed to load certificate ***");
953+
return false;
954+
}
955+
956+
// Convert ASN1_INTEGER to a readable string
957+
ASN1_INTEGER *serial = X509_get_serialNumber(cert);
958+
BIGNUM *bn = ASN1_INTEGER_to_BN(serial, nullptr);
959+
char *hex = BN_bn2hex(bn);
960+
std::string serialNumber(hex);
961+
962+
// Clean up
963+
BN_free(bn);
964+
OPENSSL_free(hex);
965+
X509_free(cert);
966+
BIO_free(certBIO);
967+
968+
Aws::Crt::Optional<std::string> params(
969+
FormatMessage(R"({"ProvisioningCertSerialNumber": "%s"})", serialNumber.c_str()));
970+
MapParameters(params);
971+
LOGM_DEBUG(TAG, "Provisioning certificate serial number: %s", serialNumber.c_str());
972+
973+
return true;
974+
}

source/fleetprovisioning/FleetProvisioning.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ namespace Aws
2020
class FleetProvisioning
2121
{
2222
public:
23+
/**
24+
* \brief Constructor
25+
*/
26+
FleetProvisioning();
27+
2328
/**
2429
* \brief Provisions device by creating and storing required resources
2530
*
@@ -156,6 +161,11 @@ namespace Aws
156161
*/
157162
std::string csrFile;
158163

164+
/**
165+
* \brief Flag whether to collect system information.
166+
*/
167+
bool collectSystemInformation;
168+
159169
/**
160170
* \brief creates a new certificate and private key using the AWS certificate authority
161171
*
@@ -214,6 +224,39 @@ namespace Aws
214224
* @return returns false if client is not able to find the file or if valid permissions are not set
215225
*/
216226
bool LocateDeviceKey(const std::string &filePath) const;
227+
228+
/**
229+
* \brief Collect system information for fleet provisioning.
230+
*
231+
* @return returns false if client is not able to collect required information
232+
*/
233+
bool PopulateSystemInformation();
234+
235+
/**
236+
* \brief Collect network information for fleet provisioning.
237+
*
238+
* @return returns false if client is not able to collect required information
239+
*/
240+
bool CollectNetworkInformation();
241+
242+
/**
243+
* \brief Calculate SHA-256 hash value of the given file.
244+
*
245+
* @param fileName friendly display name of the file
246+
* @param filePath path to the file
247+
*
248+
* @return returns false if client is not able to collect required information
249+
*/
250+
bool CalculateFileSHA256Value(const char *fileName, const std::string &filePath);
251+
252+
/**
253+
* \brief Obtain serial number of the given X.509 certificate.
254+
*
255+
* @param certPath path to the certificate
256+
*
257+
* @return returns false if client is not able to collect required information
258+
*/
259+
bool ObtainCertificateSerialID(const char *certPath);
217260
};
218261
} // namespace FleetProvisioningNS
219262
} // namespace DeviceClient

0 commit comments

Comments
 (0)