Skip to content

Commit

Permalink
Add cli/json parsing to fleet provisioning collect system info flag (#…
Browse files Browse the repository at this point in the history
…456)

* publish system information at fleet provisioning

* integrate fleet provisioning info with CLI/config parser

---------

Co-authored-by: Jarry Shaw <[email protected]>
  • Loading branch information
RogerZhongAWS and JarryShaw authored May 23, 2024
1 parent cf76107 commit bfae937
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 1 deletion.
14 changes: 13 additions & 1 deletion source/config/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) &&
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
3 changes: 3 additions & 0 deletions source/config/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> templateName;
Aws::Crt::Optional<std::string> templateParameters;
Aws::Crt::Optional<std::string> csrFile;
Aws::Crt::Optional<std::string> deviceKey;
bool collectSystemInformation{false};
};
FleetProvisioning fleetProvisioning;

Expand Down
227 changes: 227 additions & 0 deletions source/fleetprovisioning/FleetProvisioning.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,21 @@
#include <aws/iotidentity/RegisterThingResponse.h>
#include <aws/iotidentity/RegisterThingSubscriptionRequest.h>

#include <arpa/inet.h>
#include <chrono>
#include <ifaddrs.h>
#include <iomanip>
#include <net/if.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <string>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
#include <wordexp.h>

using namespace std;
Expand All @@ -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");
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -492,6 +517,7 @@ bool FleetProvisioning::ProvisionDevice(shared_ptr<SharedCrtResourceManager> 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(
Expand Down Expand Up @@ -745,3 +771,204 @@ bool FleetProvisioning::MapParameters(Aws::Crt::Optional<std::string> 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<struct sockaddr_in *>(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<unsigned char *>(ifr.ifr_hwaddr.sa_data);

Aws::Crt::Optional<std::string> 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<std::string> 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<std::string> params(
FormatMessage(R"({"ProvisioningCertSerialNumber": "%s"})", serialNumber.c_str()));
MapParameters(params);
LOGM_DEBUG(TAG, "Provisioning certificate serial number: %s", serialNumber.c_str());

return true;
}
43 changes: 43 additions & 0 deletions source/fleetprovisioning/FleetProvisioning.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ namespace Aws
class FleetProvisioning
{
public:
/**
* \brief Constructor
*/
FleetProvisioning();

/**
* \brief Provisions device by creating and storing required resources
*
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit bfae937

Please sign in to comment.