Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cli/json parsing to fleet provisioning collect system info flag #456

Merged
merged 4 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading