Skip to content

Commit c86f320

Browse files
authored
Merge pull request #12264 from prajval-um/ZTCDXINDIA-540-MS-Sentinel-Add-activity-log-endpoint-to-connector
Changes made to add telephony and activity v2 log endpoint to the Cisco Duo Security Connector.
2 parents 3cfb807 + c0b56c8 commit c86f320

File tree

4 files changed

+145
-84
lines changed

4 files changed

+145
-84
lines changed

Solutions/CiscoDuoSecurity/Data Connectors/AzureFunctionCiscoDuo/main.py

Lines changed: 142 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ def main(mytimer: func.TimerRequest) -> None:
6666
logging.info('Script is running too long. Saving progress and exit.')
6767
return
6868

69+
if 'activity' in log_types:
70+
state_manager = StateManager(FILE_SHARE_CONN_STRING, file_path='cisco_duo_activity_logs_last_ts.txt')
71+
process_activity_logs(admin_api, start_ts, state_manager=state_manager, sentinel=sentinel)
72+
if check_if_script_runs_too_long(start_ts):
73+
logging.info('Script is running too long. Saving progress and exit.')
74+
return
75+
6976
if 'administrator' in log_types:
7077
state_manager = StateManager(FILE_SHARE_CONN_STRING, file_path='cisco_duo_admin_logs_last_ts.txt')
7178
process_admin_logs(admin_api, start_ts, state_manager=state_manager, sentinel=sentinel)
@@ -90,7 +97,7 @@ def main(mytimer: func.TimerRequest) -> None:
9097
def get_log_types():
9198
res = str(os.environ.get('CISCO_DUO_LOG_TYPES', ''))
9299
if not res:
93-
res = 'trust_monitor,authentication,administrator,telephony,offline_enrollment'
100+
res = 'trust_monitor,authentication,administrator,telephony,offline_enrollment,activity'
94101
return [x.lower().strip() for x in res.split(',')]
95102

96103

@@ -290,86 +297,6 @@ def get_admin_logs(admin_api: duo_client.Admin, mintime: int) -> Iterable[dict]:
290297
events = None
291298
return events
292299

293-
def process_tele_logs(admin_api: duo_client.Admin, start_ts, state_manager: StateManager, sentinel: AzureSentinelConnector) -> None:
294-
limit = 1000
295-
logging.info('Start processing telephony logs')
296-
297-
logging.info('Getting last timestamp')
298-
mintime = state_manager.get()
299-
if mintime:
300-
logging.info('Last timestamp is {}'.format(mintime))
301-
mintime = int(mintime) + 1
302-
else:
303-
logging.info('Last timestamp is not known. Getting data for last 24h')
304-
mintime = int(time.time() - 86400)
305-
306-
last_ts = None
307-
308-
events = get_tele_logs(admin_api, mintime)
309-
310-
for event in events:
311-
last_ts = event['timestamp']
312-
sentinel.send(event)
313-
314-
sentinel.flush()
315-
316-
if last_ts:
317-
logging.info('Saving telephony logs last timestamp {}'.format(last_ts))
318-
state_manager.post(str(last_ts))
319-
320-
while len(events) == limit:
321-
mintime = last_ts
322-
mintime += 1
323-
logging.info('Making telephony logs request: mintime={}'.format(mintime))
324-
try:
325-
events = admin_api.get_telephony_log(mintime)
326-
except Exception as ex:
327-
logging.info('Error while getting telephony logs - {}'.format(ex))
328-
if ex.status == 429:
329-
logging.info('429 exception occurred, trying retry after 60 seconds')
330-
time.sleep(60)
331-
events = admin_api.get_telephony_log(mintime)
332-
333-
if(events is not None):
334-
logging.info('Obtained {} tele events'.format(len(events)))
335-
336-
else:
337-
logging.info('Events returned as null in telephony logs')
338-
339-
for event in events:
340-
last_ts = event['timestamp']
341-
sentinel.send(event)
342-
343-
sentinel.flush()
344-
345-
if last_ts:
346-
logging.info('Saving telephony logs last timestamp {}'.format(last_ts))
347-
state_manager.post(str(last_ts))
348-
349-
if check_if_script_runs_too_long(start_ts):
350-
logging.info('Script is running too long. Saving progress and exit.')
351-
return
352-
353-
354-
def get_tele_logs(admin_api: duo_client.Admin, mintime: int) -> Iterable[dict]:
355-
limit = 1000
356-
logging.info('Making telephony logs request: mintime={}'.format(mintime))
357-
try:
358-
events = admin_api.get_telephony_log(mintime)
359-
except Exception as err:
360-
logging.info('Error while getting telephony logs - {}'.format(err))
361-
if err.status == 429:
362-
logging.info('429 exception occurred, trying retry after 60 seconds')
363-
time.sleep(60)
364-
events = admin_api.get_telephony_log(mintime)
365-
366-
if(events is not None):
367-
logging.info('Obtained {} tele events'.format(len(events)))
368-
else:
369-
logging.info('Error while getting telephony logs')
370-
events = None
371-
return events
372-
373300

374301
def process_offline_enrollment_logs(admin_api: duo_client.Admin, start_ts, state_manager: StateManager, sentinel: AzureSentinelConnector) -> None:
375302
limit = 1000
@@ -470,3 +397,137 @@ def check_if_script_runs_too_long(start_ts):
470397
max_duration = int(MAX_SCRIPT_EXEC_TIME_MINUTES * 60 * 0.85)
471398
return duration > max_duration
472399

400+
401+
def process_activity_logs(admin_api: duo_client.Admin, start_ts, state_manager: StateManager, sentinel: AzureSentinelConnector) -> None:
402+
limit = 1000
403+
logging.info('Start processing activity logs')
404+
405+
logging.info('Getting last timestamp')
406+
mintime = state_manager.get()
407+
if mintime:
408+
logging.info('Last timestamp is {}'.format(mintime))
409+
mintime = int(mintime) + 1
410+
else:
411+
logging.info('Last timestamp is not known. Getting data for last 24h')
412+
mintime = int(time.time() - 86400) * 1000
413+
414+
maxtime = int(time.time() - 120) * 1000
415+
diff = maxtime - mintime
416+
maxwindow = int(MAX_SYNC_WINDOW_PER_RUN_MINUTES) * 60000
417+
if diff > maxwindow:
418+
maxtime = mintime + maxwindow
419+
logging.warn('Ingestion is lagging for activity logs, limiting synchronization window to {}'.format(maxwindow))
420+
421+
next_offset = None
422+
while True:
423+
events, next_offset = get_activity_logs(admin_api, mintime, maxtime, limit, next_offset)
424+
for event in events:
425+
sentinel.send(event)
426+
sentinel.flush()
427+
logging.info('Saving activity logs last timestamp {}'.format(maxtime))
428+
state_manager.post(str(maxtime))
429+
if not events:
430+
break
431+
if len(events) < limit or check_if_script_runs_too_long(start_ts):
432+
if check_if_script_runs_too_long(start_ts):
433+
logging.info('Script is running too long. Saving progress and exit.')
434+
break
435+
436+
def get_activity_logs(admin_api: duo_client.Admin, mintime: int, maxtime: int, limit=1000, next_offset=None):
437+
logging.info('Making activity logs request: mintime={}, maxtime={}, next_offset={}'.format(mintime, maxtime, next_offset))
438+
try:
439+
params = {
440+
'api_version': 2,
441+
'mintime': mintime,
442+
'maxtime': maxtime,
443+
'limit': str(limit),
444+
'sort': 'ts:asc'
445+
}
446+
if next_offset:
447+
params['next_offset'] = next_offset
448+
res = admin_api.get_activity_logs(**params)
449+
except Exception as err:
450+
logging.info('Error while getting activity logs- {}'.format(err))
451+
if hasattr(err, 'status') and err.status == 429:
452+
logging.info('429 exception occurred, trying retry after 60 seconds')
453+
time.sleep(60)
454+
res = admin_api.get_activity_logs(**params)
455+
else:
456+
return [], None
457+
458+
if res is not None:
459+
events = res.get('items', [])
460+
next_offset = res.get('metadata', {}).get('next_offset')
461+
logging.info('Obtained {} activity events'.format(len(events)))
462+
else:
463+
logging.info('Error while getting activity logs')
464+
events = []
465+
next_offset = None
466+
return events, next_offset
467+
468+
469+
def process_tele_logs(admin_api: duo_client.Admin, start_ts, state_manager: StateManager, sentinel: AzureSentinelConnector) -> None:
470+
limit = 1000
471+
logging.info('Start processing telephony v2 logs')
472+
logging.info('Getting last timestamp')
473+
mintime = state_manager.get()
474+
if mintime:
475+
logging.info('Last timestamp is {}'.format(mintime))
476+
mintime = int(mintime) + 1
477+
else:
478+
logging.info('Last timestamp is not known. Getting data for last 24h')
479+
mintime = int(time.time() - 86400) * 1000
480+
481+
maxtime = int(time.time() - 120) * 1000
482+
diff = maxtime - mintime
483+
maxwindow = int(MAX_SYNC_WINDOW_PER_RUN_MINUTES) * 60000
484+
if diff > maxwindow:
485+
maxtime = mintime + maxwindow
486+
logging.warn('Ingestion is lagging for telephony logs v2, limiting synchronization window to {}'.format(maxwindow))
487+
488+
next_offset = None
489+
while True:
490+
events, next_offset = get_tele_logs(admin_api, mintime, maxtime, limit, next_offset)
491+
for event in events:
492+
sentinel.send(event)
493+
sentinel.flush()
494+
logging.info('Saving telephony logs v2 last timestamp {}'.format(maxtime))
495+
state_manager.post(str(maxtime))
496+
if not events:
497+
break
498+
if len(events) < limit or check_if_script_runs_too_long(start_ts):
499+
if check_if_script_runs_too_long(start_ts):
500+
logging.info('Script is running too long. Saving progress and exit.')
501+
break
502+
503+
def get_tele_logs(admin_api: duo_client.Admin, mintime: int, maxtime: int, limit=1000, next_offset=None):
504+
logging.info('Making telephony logs v2 request: mintime={}, maxtime={}, next_offset={}'.format(mintime, maxtime, next_offset))
505+
try:
506+
params = {
507+
'api_version': 2,
508+
'mintime': mintime,
509+
'maxtime': maxtime,
510+
'limit': str(limit),
511+
'sort': 'ts:asc'
512+
}
513+
if next_offset:
514+
params['next_offset'] = next_offset
515+
res = admin_api.get_telephony_log(**params)
516+
except Exception as err:
517+
logging.info('Error while getting telephony logs v2 {}'.format(err))
518+
if hasattr(err, 'status') and err.status == 429:
519+
logging.info('429 exception occurred, trying retry after 60 seconds')
520+
time.sleep(60)
521+
res = admin_api.get_telephony_log(**params)
522+
else:
523+
return [], None
524+
525+
if res is not None:
526+
events = res.get('items', [])
527+
next_offset = res.get('metadata', {}).get('next_offset')
528+
logging.info('Obtained {} tele events v2'.format(len(events)))
529+
else:
530+
logging.info('Error while getting telephony logs v2')
531+
events = []
532+
next_offset = None
533+
return events, next_offset
Binary file not shown.

Solutions/CiscoDuoSecurity/Data Connectors/azuredeploy_CiscoDuo_API_FunctionApp.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
},
2323
"Cisco Duo Log Types": {
2424
"type": "string",
25-
"defaultValue": "trust_monitor,authentication,administrator,telephony,offline_enrollment",
25+
"defaultValue": "trust_monitor,authentication,administrator,telephony,offline_enrollment,activity",
2626
"metadata": {
2727
"description": "Comma separated list of log types to get from Cisco Duo. Possible values: trust_monitor, authentication, administrator, telephony, offline_enrollment."
2828
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
azure-functions==1.6.0
2-
duo-client==4.3.2
3-
azure-storage-file-share==12.5.0
2+
duo-client==5.5.0
3+
azure-storage-file-share==12.5.0

0 commit comments

Comments
 (0)