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

Extend MS-Graph integration - MDM Intune integration #86

Merged
merged 13 commits into from
Aug 19, 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
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
Loading