This blog is authored and technically implemented by Hesham_Saad with hearty thanks to our collaborator and use-cases executive mind brain yazanouf
Before we dig deep on monitoring TEAMS CallRecords Activity Logs, please have a look at "Protecting your Teams with Azure Sentinel" blog post by Pete Bryan on how to ingest TEAMS management logs into Azure Sentinel via the O365 Management Activity API
Collecting TEAMS CallRecords Activity Data
This section we will go into details on how to ingest TEAMS CallRecords activity logs into Azure Sentinel via the Microsoft Graph API and mainly leveraging CallRecords API which is a Graph webhook API that will give access to the Calls activity logs. SOC team can subscribe to changes to CallRecords via Azure Sentinel and using the Microsoft Graph webhook subscriptions capability, allowing them to build near-real-time reports from the data or to alert on specific scenarios , use cases which mentioned above.
Technically you can use the call records APIs to subscribe to call records and look up call records by IDs, the call records API is defined in the OData sub-namespace, microsoft.graph.callRecords
.
So, what are the key resources types returned by the API ?
Resource | Methods | Description |
CallRecord | Get callRecord | Represents a single peer-to-peer call or a group call between multiple participants |
session | Get callRecord List sessions |
A peer-to-peer call contains a single session between the two participants in the call. Group calls contain one or more session entities. In a group call, each session is between the participant and a service endpoint. |
segment | Get callRecord List sessions |
A segment represents a media link between two endpoints. |
The callRecord entity represents a single peer-to-peer call or a group call between multiple participants, sometimes referred to as an online meeting. A peer-to-peer call contains a single session between the two participants in the call. Group calls contain one or more session entities. In a group call, each session is between the participant and a service endpoint. Each session contains one or more segment entities. A segment represents a media link between two endpoints. For most calls, only one segment will be present for each session, however sometimes there may be one or more intermediate endpoints. For more details click here
Below is the main architecture diagram including the components to deploy Teams CallRecords Activity Logs Connector:
Deployment steps:
Register an App
Create and register Azure AD APP to handle the authentication and authorization to collect data from the Graph API. Here are the steps - navigate to the Azure Active Directory blade of your Azure portal and follow the steps below:
- Click on ‘App Registrations’
- Select ‘New Registration’
- Give it a name and click Register.
- Click ‘API Permissions’ blade.
- Click ‘Add a Permission’.
- Click ‘Microsoft Graph’.
- Click ‘Application Permissions’.
- Search for 'CallRecords', Check CallRecords.Read.All. Also, Search for 'Directory' and Check Directory.ReadWrite.All and 'Click ‘Add permissions’.
- Click ‘grant admin consent’.
- Click ‘Certificates and Secrets’.
- Click ‘New Client Secret’
- Enter a description, select ‘never’. Click ‘Add’.
- Note- Click copy next to the new secret and store it somewhere temporarily. You cannot come back to get the secret once you leave the blade.
- Copy the client Id from the application properties and store it.
- Copy the tenant Id from the main Azure Active Directory blade and store it.
Deploy a Logic App
Last step is to collect the CallRecords activity data and ingest it into Azure Sentinel via a Logic App.
Navigate to Azure Sentinel workspace, click at Playbooks blade and follow the steps below:
- Click on ‘Add Playbook'
- Select 'Resource Group', type a name to your logic app for example 'TeamsCalls-SecurityGraphAPI' and toggle on 'Log Analytics'
- Click 'Review + Create' then 'Create'
- Open your new logic app 'TeamsCalls-SecurityGraphAPI'
- Under 'Logic app designer', add the following steps:
- Add 'Recurrence' step and set the value to 10 minute for example
- Add 'HTTP' step to create CallRecords subscriptions, creating a subscriptions will subscribe a listener application to receive change notifications when the requested type of changes occur to the specified resource in Microsoft Graph, for more details on Create Subscriptions via Microsoft Graph API
- Method: POST
- URI: https://graph.microsoft.com/beta/subscriptions
- Body, note that you can edit 'changeType' value with 'created,updated' for example, 'notificationUrl' is the subscription notification endpoint, please refer to the note section at the end of the blog post to know how you can get the notificationURL hosted in Azure Function, for more details on notificationUrl,
{ "changeType": "created", "clientState": "secretClientValue", "expirationDateTime": "2022-11-20T18:23:45.9356913Z", "latestSupportedTlsVersion": "v1_2", "notificationUrl": "https://outlook.office.com/webhook/3ec886e9-86ef-4c86-bfff-2d0321f3313e@2006d214-5f91-4166-8d92-95f5e3ad9ec6/IncomingWebhook/9c6e121ed--x-x-x-x99939f71721fcbcc7/03c99422-50b0-x-x-x-ea-a00e-2b0b-x-x-x-12d5", "resource": "/communications/callRecords" }
- Authentication Type: Active Directory OAuth
- Tenant: with Tenant ID copied above
- Audience: https://graph.microsoft.com
- Client ID: with Client ID copied above
- Credential Type: Secret
- Secret: with Secret value copied above
- Add 'HTTP' step to list all subscriptions:
- Method: GET
- URI: https://graph.microsoft.com/v1.0/subscriptions
- Authentication Type: Active Directory OAuth
- Tenant: with Tenant ID copied above
- Audience: https://graph.microsoft.com
- Client ID: with Client ID copied above
- Credential Type: Secret
- Secret: with Secret value copied above
- If you want to get all sessions details per specific call record session ID follow the below steps, noting that the below example is for a single CallRecord Session ID for the sake of demonstration and hence we added a variable item, you can add a loop step to get all sessions IDs from the created CallRecords subscription step, noting that there is a change documented here by Oct 2020 so in order to get the callRecords IDs list you need to use the callChainId property of a call. The call record is available only after the associated call is completed, for more details click here:
- Method: GET
- URI: https://graph.microsoft.com/beta/communications/callRecords/@{variables('TEAMSCallRecordsID')}/sessions
- Authentication Type: Active Directory OAuth
- Tenant: with Tenant ID copied above
- Audience: https://graph.microsoft.com
- Client ID: with Client ID copied above
- Credential Type: Secret
- Secret: with Secret value copied above
- Add 'Send TEAMS CallRecords Data to Azure Sentinel LA-Workspace' step, after doing the connection successfully via your Azure Sentinel Workspace ID & Primary key:
- JSON Request Body: Body
- Custom Log Name: TEAMSGraphCallRecords
The complete Playbook code view have been uploaded to github repo as well, please click here for more details and check out the readme section.
NotificationURL code (Hosted in Azure Function) sample:
using System.Net;
using System.IO;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using System;
public static async Task<IActionResult> Run(HttpRequest req, ILogger log, TextWriter outputBlob)
{
log.LogInformation($"Webhook was triggered!");
// Grab the validationToken URL parameter
If(reg.QueryString.HasValue)
{
string Token = req.Query["validationtoken"];
if(!string.isNullorEmpty(Token))
{
log.LogInformation($"Validation token: {Token}");
return (ActionResult)new OkObjectResult(validaTokentionToken);
}
}
JObject jsonContent;
string notifContent;
using (var reader = new StreamReader(reg.Body))
{
notifContent = reader.ReadToEnd();
Log.LogInformation(notifContent);
jsonContent =JObject.Parse(notifContent);
}
}
To get a list of call records without the need to hard-code / set CallRecord ID you need to do the following:
- Set up the subscription for the CallRecord resource.
- Receive notification (POST) when a new CallRecord is created or updated (based on the subscription). This notification contains the CallRecordId
- For each notification, use the GET API to retrieve that CallRecord.
Monitoring TEAMS CallRecords Activity
When the Playbook run successfully, it will create a new custom log table 'TEAMSGraphCallRecords_CL' that will have the CallRecords activity logs, you might wait for a few minutes till the new CL table been created and the CallRecords activity logs been ingested.
Navigate to Azure Sentinel workspace, click at Logs blade and follow the steps below:
- Tables > Group by: Solution > Custom Logs: TEAMSGraphCallRecords_CL
- Below are the list of main attributes that have been ingested:
- TimeGenerated
- Type_s: groupCall
- modalities_s: Audio, Video, ScreenSharing, VideoBasedScreenSharing
- LastModifiedDateTime
- StartDateTime, endDateTime
- joinWebUrl_s
- organizer_user_displayname_s
- participants_s
- sessions_odata_context_s
- As you can see from the results below we get the complete TEAMS CallRecords activity logs.
Parsing the Data
Before building any detections or hunting queries on the ingested TEAMS CallRecords Activity data we can parse and normalize the data via a KQL Function to make it easier to use:
The parsing function have been uploaded as well to the github repo.
Part (2): we will share a couple of hunting queries and upload them to github, it's worth to explore Microsoft Graph API as there are other TEAMS related APIs logs that can be ingested based on the requirements and use cases:
- TeamsActivity:
- Read all users' teamwork activity feed
- TeamsAppInstallation:
- Read installed Teams apps for all chats
- Read installed Teams apps for all teams
- Read installed Teams apps for all users
- TeamsApp
- Read all users' installed Teams apps
...etc
We will be continuing to develop detections and hunting queries for Microsoft 365 solutions data over time so make sure you keep an eye on GitHub. As always if you have your own ideas for queries or detections please feel free to contribute to the Azure Sentinel community.