Skip to content

Commit

Permalink
Merge pull request #86 from wazuh/24498/feat-mdm-intune-integration
Browse files Browse the repository at this point in the history
Extend MS-Graph integration - MDM Intune integration
  • Loading branch information
vikman90 authored Aug 19, 2024
2 parents 2c4d3e3 + 0002046 commit a8e7478
Show file tree
Hide file tree
Showing 5 changed files with 515 additions and 121 deletions.
13 changes: 12 additions & 1 deletion src/modules/ms_graph/include/wm_ms_graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,20 @@
#define WM_MS_GRAPH_DOD_API_QUERY_FQDN "dod-graph.microsoft.us"


#define WM_MS_GRAPH_API_URL "https://%s/%s/%s/%s?$filter=createdDateTime+gt+%s"
#define WM_MS_GRAPH_API_URL "https://%s/%s/%s/%s?$top=%d"
#define WM_MS_GRAPH_API_URL_FILTER_CREATED_DATE WM_MS_GRAPH_API_URL "&$filter=createdDateTime+ge+%s+and+createdDateTime+lt+%s"
#define WM_MS_GRAPH_API_URL_FILTER_ACTIVITY_DATE WM_MS_GRAPH_API_URL "&$filter=activityDateTime+ge+%s+and+activityDateTime+lt+%s"
#define WM_MS_GRAPH_ACCESS_TOKEN_URL "https://%s/%s/oauth2/v2.0/token"
#define WM_MS_GRAPH_ACCESS_TOKEN_PAYLOAD "scope=https://%s/.default&grant_type=client_credentials&client_id=%s&client_secret=%s"
#define WM_MS_GRAPH_ITEM_PER_PAGE 100

// MDM Intune
#define WM_MS_GRAPH_API_URL_DEVICES_EXPANDED "https://%s/%s/%s/%s/%s/%s?$top=%d"
#define WM_MS_GRAPH_API_URL_FILTER_DEVICE_FIELDS WM_MS_GRAPH_API_URL_DEVICES_EXPANDED "&$select=id,deviceName"
#define WM_MS_GRAPH_RESOURCE_DEVICE_MANAGEMENT "deviceManagement"
#define WM_MS_GRAPH_RELATIONSHIP_AUDIT_EVENTS "auditEvents"
#define WM_MS_GRAPH_RELATIONSHIP_MANAGED_DEVICES "managedDevices"
#define WM_MS_GRAPH_RELATIONSHIP_DETECTED_APPS "detectedApps"

typedef struct wm_ms_graph_state_t {
time_t next_time;
Expand Down
172 changes: 144 additions & 28 deletions src/modules/ms_graph/src/wm_ms_graph.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ static void* wm_ms_graph_main(wm_ms_graph* ms_graph);
static bool wm_ms_graph_setup(wm_ms_graph* ms_graph);
static bool wm_ms_graph_check();
static void wm_ms_graph_get_access_token(wm_ms_graph_auth* auth_config, const ssize_t curl_max_size);
static void wm_ms_graph_scan_relationships(wm_ms_graph* ms_graph, const bool initial_scan);
static void wm_ms_graph_scan_relationships(wm_ms_graph* ms_graph, wm_ms_graph_auth* auth_config, const bool initial_scan);
static cJSON* wm_ms_graph_scan_apps_devices(const wm_ms_graph* ms_graph, const cJSON* app_id, const char* query_fqdn, char** headers);
static void wm_ms_graph_destroy(wm_ms_graph* ms_graph);
static void wm_ms_graph_cleanup();
cJSON* wm_ms_graph_dump(const wm_ms_graph* ms_graph);
Expand Down Expand Up @@ -80,7 +81,7 @@ void* wm_ms_graph_main(wm_ms_graph* ms_graph) {

if (it->access_token && time(NULL) < it->token_expiration_time) {
mtinfo(WM_MS_GRAPH_LOGTAG, "Scanning tenant '%s'", it->tenant_id);
wm_ms_graph_scan_relationships(ms_graph, initial);
wm_ms_graph_scan_relationships(ms_graph, it, initial);
initial = false;
}
}
Expand Down Expand Up @@ -186,30 +187,50 @@ void wm_ms_graph_get_access_token(wm_ms_graph_auth* auth_config, const ssize_t c
}
}

void wm_ms_graph_scan_relationships(wm_ms_graph* ms_graph, const bool initial_scan) {
void wm_ms_graph_scan_relationships(wm_ms_graph* ms_graph, wm_ms_graph_auth* auth_config, const bool initial_scan) {
char url[OS_SIZE_8192] = { '\0' };
char auth_header[OS_SIZE_2048] = { '\0' };
char auth_header[OS_SIZE_8192] = { '\0' };
char* headers[] = { NULL, NULL };
curl_response* response;
char relationship_state_name[OS_SIZE_1024] = { '\0' };
char start_time_str[WM_MS_GRAPH_TIMESTAMP_SIZE_80] = { '\0' };
char end_time_str[WM_MS_GRAPH_TIMESTAMP_SIZE_80] = { '\0' };
struct tm tm_aux = { .tm_sec = 0 };
wm_ms_graph_state_t relationship_state_struc;
time_t now;
bool fail;
bool next_page;
bool inventory = false;

#ifndef WIN32
int id = os_random();
if (id < 0) {
id = -id;
}
#else
char random_id[RANDOM_LENGTH];
snprintf(random_id, RANDOM_LENGTH - 1, "%u%u", os_random(), os_random());
int id = atoi(random_id);

if (id < 0) {
id = -id;
}
#endif

for (unsigned int resource_num = 0; resource_num < ms_graph->num_resources; resource_num++) {

for (unsigned int relationship_num = 0; relationship_num < ms_graph->resources[resource_num].num_relationships; relationship_num++) {

int e;
wm_ms_graph_auth *it;

for (e = 0; ms_graph->auth_config[e]; e++) {
it = ms_graph->auth_config[e];
if (!strcmp(ms_graph->resources[resource_num].name, WM_MS_GRAPH_RESOURCE_DEVICE_MANAGEMENT)) {
// If not auditEvents, treat as inventory
if (strcmp(ms_graph->resources[resource_num].relationships[relationship_num], WM_MS_GRAPH_RELATIONSHIP_AUDIT_EVENTS)) {
inventory = true;
}
}

if (!inventory) {
snprintf(relationship_state_name, OS_SIZE_1024 -1, "%s-%s-%s-%s", WM_MS_GRAPH_CONTEXT.name,
it->tenant_id, ms_graph->resources[resource_num].name, ms_graph->resources[resource_num].relationships[relationship_num]);
auth_config->tenant_id, ms_graph->resources[resource_num].name, ms_graph->resources[resource_num].relationships[relationship_num]);

memset(&relationship_state_struc, 0, sizeof(relationship_state_struc));

Expand All @@ -229,30 +250,57 @@ void wm_ms_graph_scan_relationships(wm_ms_graph* ms_graph, const bool initial_sc
gmtime_r(&now, &tm_aux);
strftime(start_time_str, sizeof(start_time_str), "%Y-%m-%dT%H:%M:%SZ", &tm_aux);
mtdebug1(WM_MS_GRAPH_LOGTAG, "Bookmark updated to '%s' for tenant '%s' resource '%s' and relationship '%s', waiting '%d' seconds to run first scan.",
start_time_str, it->tenant_id, ms_graph->resources[resource_num].name, ms_graph->resources[resource_num].relationships[relationship_num], ms_graph->scan_config.interval);
start_time_str, auth_config->tenant_id, ms_graph->resources[resource_num].name, ms_graph->resources[resource_num].relationships[relationship_num], ms_graph->scan_config.interval);
}
continue;
}

gmtime_r(&relationship_state_struc.next_time, &tm_aux);
strftime(start_time_str, sizeof(start_time_str), "%Y-%m-%dT%H:%M:%SZ", &tm_aux);

snprintf(auth_header, OS_SIZE_2048 - 1, "Authorization: Bearer %s", it->access_token);
os_strdup(auth_header, headers[0]);
gmtime_r(&now, &tm_aux);
strftime(end_time_str, sizeof(end_time_str), "%Y-%m-%dT%H:%M:%SZ", &tm_aux);
}

snprintf(url, OS_SIZE_8192 - 1, WM_MS_GRAPH_API_URL,
it->query_fqdn,
snprintf(auth_header, OS_SIZE_8192 - 1, "Authorization: Bearer %s", auth_config->access_token);
os_strdup(auth_header, headers[0]);

if (!strcmp(ms_graph->resources[resource_num].name, WM_MS_GRAPH_RESOURCE_DEVICE_MANAGEMENT)) {
if (!strcmp(ms_graph->resources[resource_num].relationships[relationship_num], WM_MS_GRAPH_RELATIONSHIP_AUDIT_EVENTS)) {
snprintf(url, OS_SIZE_8192 - 1, WM_MS_GRAPH_API_URL_FILTER_ACTIVITY_DATE,
auth_config->query_fqdn,
ms_graph->version,
WM_MS_GRAPH_RESOURCE_DEVICE_MANAGEMENT,
WM_MS_GRAPH_RELATIONSHIP_AUDIT_EVENTS,
WM_MS_GRAPH_ITEM_PER_PAGE,
start_time_str,
end_time_str);
} else {
snprintf(url, OS_SIZE_8192 - 1, WM_MS_GRAPH_API_URL,
auth_config->query_fqdn,
ms_graph->version,
WM_MS_GRAPH_RESOURCE_DEVICE_MANAGEMENT,
ms_graph->resources[resource_num].relationships[relationship_num],
WM_MS_GRAPH_ITEM_PER_PAGE);
}
} else {
snprintf(url, OS_SIZE_8192 - 1, WM_MS_GRAPH_API_URL_FILTER_CREATED_DATE,
auth_config->query_fqdn,
ms_graph->version,
ms_graph->resources[resource_num].name,
ms_graph->resources[resource_num].relationships[relationship_num],
start_time_str);
WM_MS_GRAPH_ITEM_PER_PAGE,
start_time_str,
end_time_str);
}

next_page = true;
while (next_page) {
mtdebug1(WM_MS_GRAPH_LOGTAG, "Microsoft Graph API Log URL: '%s'", url);

fail = true;
next_page = false;
response = wurl_http_request(WURL_GET_METHOD, headers, url, "", ms_graph->curl_max_size, WM_MS_GRAPH_DEFAULT_TIMEOUT);
// It takes the time right after the response to be saved for the next scan.
now = time(0);
if (response) {
if (response->status_code != 200) {
char status_code[4];
Expand All @@ -261,6 +309,9 @@ void wm_ms_graph_scan_relationships(wm_ms_graph* ms_graph, const bool initial_sc
ms_graph->resources[resource_num].relationships[relationship_num],
status_code,
response->body);
if (response->status_code == 401) {
auth_config->token_expiration_time = time(NULL);
}
} else if (response->max_size_reached) {
mtwarn(WM_MS_GRAPH_LOGTAG, "Reached maximum CURL size when attempting to get relationship '%s' logs. Consider increasing the value of 'curl_max_size'.",
ms_graph->resources[resource_num].relationships[relationship_num]);
Expand All @@ -276,8 +327,17 @@ void wm_ms_graph_scan_relationships(wm_ms_graph* ms_graph, const bool initial_sc
cJSON* full_log = cJSON_CreateObject();
char* payload;

if (inventory && !strcmp(ms_graph->resources[resource_num].relationships[relationship_num], WM_MS_GRAPH_RELATIONSHIP_DETECTED_APPS)) {
cJSON_AddItemToObject(log, WM_MS_GRAPH_RELATIONSHIP_MANAGED_DEVICES,
wm_ms_graph_scan_apps_devices(ms_graph, cJSON_GetObjectItem(log, "id"), auth_config->query_fqdn, headers));
}

cJSON_AddStringToObject(log, "resource", ms_graph->resources[resource_num].name);
cJSON_AddStringToObject(log, "relationship", ms_graph->resources[resource_num].relationships[relationship_num]);

if (inventory) {
cJSON_AddNumberToObject(full_log, "scan_id", id);
}
cJSON_AddStringToObject(full_log, "integration", WM_MS_GRAPH_CONTEXT.name);
cJSON_AddItemToObject(full_log, WM_MS_GRAPH_CONTEXT.name, cJSON_Duplicate(log, true));

Expand All @@ -298,6 +358,14 @@ void wm_ms_graph_scan_relationships(wm_ms_graph* ms_graph, const bool initial_sc
mtdebug2(WM_MS_GRAPH_LOGTAG, "No new logs received.");
fail = false;
}

cJSON* next_url = cJSON_GetObjectItem(body_parse, "@odata.nextLink");
if (cJSON_IsString(next_url)) {
memset(url, '\0', OS_SIZE_8192);
snprintf(url, OS_SIZE_8192 -1, "%s", next_url->valuestring);
next_page = true;
}

cJSON_Delete(body_parse);
} else {
mtwarn(WM_MS_GRAPH_LOGTAG, "Failed to parse relationship '%s' JSON body.", ms_graph->resources[resource_num].relationships[relationship_num]);
Expand All @@ -310,22 +378,70 @@ void wm_ms_graph_scan_relationships(wm_ms_graph* ms_graph, const bool initial_sc
ms_graph->resources[resource_num].name,
ms_graph->version);
}
}

if (!fail) {
relationship_state_struc.next_time = now;
gmtime_r(&relationship_state_struc.next_time, &tm_aux);
strftime(start_time_str, sizeof(start_time_str), "%Y-%m-%dT%H:%M:%SZ", &tm_aux);
if (wm_state_io(relationship_state_name, WM_IO_WRITE, &relationship_state_struc, sizeof(relationship_state_struc)) < 0) {
mterror(WM_MS_GRAPH_LOGTAG, "Couldn't save running state.");
} else {
mtdebug1(WM_MS_GRAPH_LOGTAG, "Bookmark updated to '%s' for tenant '%s' resource '%s' and relationship '%s', waiting '%d' seconds to run next scan.",
start_time_str, it->tenant_id, ms_graph->resources[resource_num].name, ms_graph->resources[resource_num].relationships[relationship_num], ms_graph->scan_config.interval);
if (!inventory && !fail) {
relationship_state_struc.next_time = now;
if (wm_state_io(relationship_state_name, WM_IO_WRITE, &relationship_state_struc, sizeof(relationship_state_struc)) < 0) {
mterror(WM_MS_GRAPH_LOGTAG, "Couldn't save running state.");
} else {
mtdebug1(WM_MS_GRAPH_LOGTAG, "Bookmark updated to '%s' for tenant '%s' resource '%s' and relationship '%s', waiting '%d' seconds to run next scan.",
end_time_str, auth_config->tenant_id, ms_graph->resources[resource_num].name, ms_graph->resources[resource_num].relationships[relationship_num], ms_graph->scan_config.interval);
}
}
os_free(headers[0]);
}
}
}

cJSON* wm_ms_graph_scan_apps_devices(const wm_ms_graph* ms_graph, const cJSON* app_id, const char* query_fqdn, char** headers) {
char url[OS_SIZE_8192] = { '\0' };
curl_response* response;
bool next_page;

cJSON *array = cJSON_CreateArray();

if (cJSON_IsString(app_id)) {
snprintf(url, OS_SIZE_8192 - 1, WM_MS_GRAPH_API_URL_FILTER_DEVICE_FIELDS, query_fqdn, ms_graph->version, WM_MS_GRAPH_RESOURCE_DEVICE_MANAGEMENT,
WM_MS_GRAPH_RELATIONSHIP_DETECTED_APPS, app_id->valuestring, WM_MS_GRAPH_RELATIONSHIP_MANAGED_DEVICES, WM_MS_GRAPH_ITEM_PER_PAGE);

next_page = true;
while (next_page) {
mtdebug1(WM_MS_GRAPH_LOGTAG, "Microsoft Graph API Log URL: '%s'", url);

next_page = false;
response = wurl_http_request(WURL_GET_METHOD, headers, url, "", ms_graph->curl_max_size, WM_MS_GRAPH_DEFAULT_TIMEOUT);
if (response) {
if (response->status_code == 200 && !response->max_size_reached) {
cJSON* body_parse = NULL;
if (body_parse = cJSON_Parse(response->body), body_parse) {
cJSON* logs = cJSON_GetObjectItem(body_parse, "value");
int num_logs = cJSON_GetArraySize(logs);
if (num_logs > 0) {
for (int log_index = 0; log_index < num_logs; log_index++) {
cJSON* log = NULL;
if (log = cJSON_GetArrayItem(logs, log_index), log) {
cJSON_AddItemToArray(array, cJSON_Duplicate(log, true));
}
}
}

cJSON* next_url = cJSON_GetObjectItem(body_parse, "@odata.nextLink");
if (cJSON_IsString(next_url)) {
memset(url, '\0', OS_SIZE_8192);
snprintf(url, OS_SIZE_8192 -1, "%s", next_url->valuestring);
next_page = true;
}

cJSON_Delete(body_parse);
}
}
os_free(headers[0]);
wurl_free_response(response);
}
}
}

return array;
}

void wm_ms_graph_destroy(wm_ms_graph* ms_graph) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
}
},
{
"url": "http://graph.microsoft.com/v1.0/security/alerts_v2?$filter=createdDateTime+gt+*",
"url": "http://graph.microsoft.com/v1.0/security/alerts_v2?$top=*&$filter=createdDateTime+ge+*+and+createdDateTime+lt+*",
"method": "GET",
"responseCode": 200,
"responseBody": {
Expand All @@ -30,7 +30,7 @@
}
},
{
"url": "http://graph.microsoft.com/v1.0/security/incidents?$filter=createdDateTime+gt+*",
"url": "http://graph.microsoft.com/v1.0/security/incidents?$top=*&$filter=createdDateTime+ge+*+and+createdDateTime+lt+*",
"method": "GET",
"responseCode": 200,
"responseBody": {
Expand All @@ -40,7 +40,7 @@
}
},
{
"url": "http://graph.microsoft.com/v1.0/resource_name/resource_relationship?$filter=createdDateTime+gt+*",
"url": "http://graph.microsoft.com/v1.0/resource_name/resource_relationship?$top=*&$filter=createdDateTime+ge+*+and+createdDateTime+lt+*",
"method": "GET",
"responseCode": 200,
"responseBody": {
Expand All @@ -50,7 +50,7 @@
}
},
{
"url": "http://*/*/*/*$filter=createdDateTime+gt+*",
"url": "http://*/*/*/*$top=*&$filter=createdDateTime+ge+*+and+createdDateTime+lt+*",
"method": "GET",
"responseCode": 403,
"responseBody": {
Expand Down
4 changes: 2 additions & 2 deletions src/modules/ms_graph/tests/unit/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ list(APPEND tests_flags "-Wl,--wrap=_merror -Wl,--wrap=_mtinfo -Wl,--wrap=_mtwar
-Wl,--wrap,atexit -Wl,--wrap,time -Wl,--wrap,wm_state_io -Wl,--wrap,StartMQ -Wl,--wrap,gmtime_r -Wl,--wrap,isDebug \
-Wl,--wrap,sched_scan_get_time_until_next_scan -Wl,--wrap,is_fim_shutdown -Wl,--wrap,fim_db_teardown -Wl,--wrap,Start_win32_Syscheck \
-Wl,--wrap,strftime -Wl,--wrap,_mtdebug2 -Wl,--wrap,wm_sendmsg -Wl,--wrap,strerror -Wl,--wrap,_imp__rsync_initialize \
-Wl,--wrap,syscom_dispatch -Wl,--wrap,_imp__dbsync_initialize -Wl,--wrap,w_get_timestamp -Wl,--wrap,FOREVER")
-Wl,--wrap,syscom_dispatch -Wl,--wrap,_imp__dbsync_initialize -Wl,--wrap,w_get_timestamp -Wl,--wrap,os_random -Wl,--wrap,FOREVER")


list(APPEND use_shared_libs 1)

Expand Down
Loading

0 comments on commit a8e7478

Please sign in to comment.