Deploy the Connector
Overview
Beta. The connector is distributed directly by Britive rather than through the Sentinel Content Hub, and is versioned 0.9.x. Request the package from Britive Customer Success — see step 1. The artifacts are the same ones that will publish to Content Hub at general availability.
Connect from the command line. The connector page’s Connect button does not work. It fails with “Deployment failed - content template $BritiveAuditLogsDefinition not found”, and on earlier package versions with “Cannot connect the connector. Missing dependencies”. This is an open defect with Microsoft and is not a problem with your deployment, your token or your workspace. The Azure CLI method in step 4 is verified end to end and creates the identical resource. The Azure PowerShell tab issues the same ARM request through Invoke-AzRestMethod.
This guide takes you from an empty Log Analytics workspace to Britive audit events arriving in Sentinel and confirmed queryable. Every command is given for both Azure CLI and Azure PowerShell — pick one and stay in it, since each step reuses variables set by the last. The Azure CLI path is shown first because it is the one Britive verified end to end; the portal path is included for the deployment step, where it works fine.
For how the integration works and what it puts in your workspace, read Microsoft Sentinel first.
Deployment is two distinct actions, in this order:
- Deploy the solution template — creates the table, ingestion path, connector page, parsers, rules and workbook.
- Connect the connector — supplies your Britive tenant URL and API token, which starts the polling.
Your API token is entered in the Connect step, not in the template, so it is never written to a deployment file.
Before You Begin
- A Log Analytics workspace with Microsoft Sentinel enabled. If the Sentinel breadcrumb still reads Add Microsoft Sentinel to a workspace, finish that first.
- Contributor on the resource group that holds the workspace.
- A Britive tenant on Audit Log API v2.
- The Britive Sentinel connector package, provided by Britive Customer Success — see step 1.
- One of the two command-line paths, signed in and pointed at the right subscription:
- Azure CLI —
az login, thenaz account set --subscription <id>. - Azure PowerShell (the
Azmodule, 11.0 or later) —Connect-AzAccount, thenSet-AzContext -Subscription <id>.
- Azure CLI —
Create a dedicated service identity for the connector and use its token. Do not reuse an administrator token — you cannot scope it, and rotating it breaks unrelated things.
1. Get the Solution Package
The connector is not in the Sentinel Content Hub yet. Contact Britive Customer Success or Support to request the Britive Sentinel connector package. You will receive a versioned zip:
- britiveSentinelConnector.json
- INSTALL.md
- WHATS-INCLUDED.md
- validate-filter.py
- VERSION
britiveSentinelConnector.json is the ARM template you deploy. Unzip it somewhere you can reach from the portal file picker or your shell.
Keep the version number. It is the first thing Support will ask for, and it is recoverable later from the deployment itself — see Confirm which version you are running.
2. Create the Britive Service Identity Token
The connector authenticates to Britive with a service identity token.
Create a service identity
In the Britive console, go to Identity Management → Service Identities → Add Service Identity. Name it for this purpose, for example sentinel-connector. Once saved this would generate a Token, keep this safe. Britive platform will not show this Token again.
Grant it audit log read access
Assign the service identity a policy that permits reading audit logs. Go to Role & Policy Management → Policies → Add Service Identity to the TenantAuditorPolicy
Full walkthrough: Getting Started with Service Identities.
Note your tenant URL as well. It looks like https://<tenant>.britive-app.com, with no path and no trailing slash.
3. Deploy the Solution Template
The ingestion resources must be created in the workspace’s region, which is often not the resource group’s default. Both paths below make you supply it explicitly — getting it wrong is the most common deployment mistake.
Work from inside the unzipped package, so the template filename resolves.
cd <where-you-unzipped-the-package>
ls britiveSentinelConnector.json # should print the filename, not an errorSet these four values once. Every command in step 3 and step 4 reuses them, so keep this shell open. If you close it, run this block again.
RG=<your-resource-group>
WS=<your-workspace-name>
SUB=$(az account show --query id -o tsv)
WS_REGION=$(az monitor log-analytics workspace show -g "$RG" -n "$WS" --query location -o tsv)
echo "RG=$RG WS=$WS region=$WS_REGION sub=$SUB"WS_REGION must print a short form such as eastus2, not a display name like East US 2.
Validate first — this checks the template without creating anything:
az deployment group validate \
--resource-group "$RG" \
--template-file ./britiveSentinelConnector.json \
--parameters workspaceName="$WS" location="$WS_REGION"Then deploy:
az deployment group create \
--resource-group "$RG" \
--name britive-sentinel-connector \
--template-file ./britiveSentinelConnector.json \
--parameters workspaceName="$WS" location="$WS_REGION"Optional parameters, with their defaults:
--parameters workspaceName="$WS" location="$WS_REGION" \
retentionInDays=90 \
deployContent=true \
deployAnalyticRules=trueConfirm both regions agree — a mismatch means the ingestion path is in the wrong region and no data will ever arrive:
az deployment group show -g "$RG" -n britive-sentinel-connector \
--query "properties.outputs.{deployed:deployedRegion.value, workspace:workspaceRegion.value}"A failed deployment can still leave resources behind. Azure does not roll everything back on error, so check what exists before retrying.
Azure CLI:
az resource list -g "$RG" --query "[?starts_with(name,'ASI-') || contains(name,'britiveDCR1')].{name:name, type:type, location:location}" -o tableAzure PowerShell:
Get-AzResource -ResourceGroupName $RG |
Where-Object { $_.Name -like 'ASI-*' -or $_.Name -like '*britiveDCR1*' } |
Format-Table Name, ResourceType, Location4. Connect the Connector
This is the step that starts ingestion.
Use the Azure CLI or Azure PowerShell tab. The portal’s Connect button fails with “content template $BritiveAuditLogsDefinition not found” and creates nothing. Nothing is wrong with your deployment when this happens — the defect is in the button, and either command-line path produces the same connector. The portal tab below documents the intended flow for when it is fixed.
The request body
Both command-line paths send the same JSON. Save it as connector.json in the directory you are working in, replace the four <...> placeholders, and leave the rest alone — it is the connector’s fixed configuration rather than settings to tune. The dce and dcr values come from the deployment outputs that the first step of each tab reads.
The one field you may want to change is queryParameters.filter. Empty means collect every audit event, which is the right starting point. Step 5 covers what a filter can say and how to check one before it starts dropping events.
{
"kind": "RestApiPoller",
"properties": {
"connectorDefinitionName": "BritiveAuditLogsDefinition",
"dataType": "BritiveAuditLogs_CL",
"dcrConfig": {
"streamName": "Custom-BritiveAuditLogs",
"dataCollectionEndpoint": "<dce-from-deployment-outputs>",
"dataCollectionRuleImmutableId": "<dcr-from-deployment-outputs>"
},
"auth": {
"type": "APIKey",
"ApiKey": "<your-britive-api-token>",
"ApiKeyName": "Authorization",
"ApiKeyIdentifier": "TOKEN"
},
"request": {
"apiEndpoint": "https://<tenant>.britive-app.com/api/logs/v2",
"httpMethod": "GET",
"queryWindowInMin": 5,
"queryTimeFormat": "yyyy-MM-ddTHH:mm:ssZ",
"startTimeAttributeName": "from",
"endTimeAttributeName": "to",
"retryCount": 3,
"timeoutInSeconds": 60,
"rateLimitQPS": 5,
"paginatedCallsPerSecond": 5,
"queryParameters": { "size": 100, "filter": "" },
"headers": {
"Accept": "application/json",
"User-Agent": "Britive-Sentinel-Connector/1.0"
}
},
"paging": {
"pagingType": "NextPageToken",
"nextPageParaName": "pageToken",
"nextPageTokenJsonPath": "$.nextPageToken",
"nextPageTokenResponseHeader": "next-page",
"pagingInfoPlacement": "QueryString"
},
"response": {
"eventsJsonPaths": ["$.records"],
"format": "json"
}
}
}The file holds your API token in plain text. Both tabs restrict it before you write the token into it and delete it as soon as the connector exists.
This continues in the same shell as step 3, reusing $RG, $WS and $SUB. If you opened a new shell, re-run the set these four values once block first.
Read the two values the deployment produced
# If you deployed from the portal, the name was generated - list them and pick yours
az deployment group list -g "$RG" --query "[].name" -o tsv
DEP=britive-sentinel-connector
az deployment group show -g "$RG" -n "$DEP" \
--query "properties.outputs.{dce:dataCollectionEndpoint.value, dcr:dataCollectionRuleImmutableId.value}"Copy both values. A redeployment creates a new rule, so read these fresh rather than reusing older ones.
Save the request body
Save the JSON above as connector.json in the directory you are already in, with the placeholders filled in.
pwd # confirm where the file will go
umask 077 # so only you can read itCreate the connection
Two things produce confusing errors if you get them wrong:
--body @connector.jsonneeds the@. It means “read this file”. Without it,azsends the filename as the body and Azure repliesError parsing comment. Expected: *, got U— the JSON parser reading the/Uof a path like/Users/...as a comment. An absolute path is fine with the@:--body @/full/path/connector.json.- Keep the URL quoted. The
?in?api-version=is a wildcard in most shells and the command fails before reaching Azure.
az rest --method put \
--url "https://management.azure.com/subscriptions/$SUB/resourceGroups/$RG/providers/Microsoft.OperationalInsights/workspaces/$WS/providers/Microsoft.SecurityInsights/dataConnectors/BritiveAuditLogsConnection?api-version=2024-01-01-preview" \
--body @connector.json \
--headers "Content-Type=application/json"A successful call echoes the created resource back with "kind": "RestApiPoller", and the connector page then reads Connected.
Delete the body file
It holds your API token in plain text.
rm connector.jsonAzure stores the token as a protected value and will not return it if you read the connector back, so a GET on the same URL is a safe way to confirm the configuration landed. To disconnect later, call the same URL with az rest --method delete or Invoke-AzRestMethod -Method DELETE.
Connected does not yet mean data. The first poll covers a five-minute window and Log Analytics adds its own ingestion delay. Allow up to 15 minutes for the first rows. After that, measured latency from a Britive event to it being queryable is about 4 minutes.
5. Filter What Is Collected (Optional)
Supported from package version 0.9.5 onwards. Earlier packages do not document the filter field, do not include validate-filter.py, and their connector page has no filter input. Check what you are running with Confirm which version you are running, and request a current package from Britive Customer Success if you are behind. Anything below assumes 0.9.5 or later.
Skip this if you want every audit event — that is the default and the recommended starting point.
Set queryParameters.filter in the connection body and Britive applies it server-side, before the events are sent. Two reasons to use it: cutting high-volume automation noise, and keeping a category of event out of the workspace entirely because policy says it must not leave Britive.
"queryParameters": { "size": 100, "filter": "event.eventType neq environment.scan" }A filter drops events permanently. There is no backfill in this connector — collection starts when you connect and covers only what arrives afterwards. Anything a filter excludes is absent from your workspace for good.
If your concern is cost rather than what leaves Britive, filter in the data collection rule’s transform instead (| where EventType !in ('environment.scan', 'application.scan')). Events dropped there are dropped before they are billed, and the change is reversible.
Syntax
A clause is <field> <operator> <value>. Clauses combine with and.
| Operators | eq, neq, co (contains), sw (starts with) |
| Combining | and only — every clause must hold |
| Not supported | or, comma-separated lists, parentheses, in, != |
| Values | unquoted, case-sensitive, spaces allowed — actor.type eq System User |
Filterable fields. Only these; anything else is rejected with HTTP 400:
| Group | Fields |
|---|---|
| Actor | actor.userId, actor.username, actor.displayName, actor.type, actor.role |
| Client | client.ipAddress, client.userAgent, client.device, client.displayName |
| Event | event.eventType, event.displayName |
| Target | target.targetId, target.displayName, target.applicationName, target.environmentName, target.environmentGroupName, target.applicationSessionId |
| Result | result.success |
event.consumer, client.country and everything under additionalInfo appear on records but are not filterable.
Examples
Every filter below was run against a live Britive tenant and accepted. Replace obviously site-specific values (AWS Production, @contoso.com) with your own.
Reduce volume — keep everything except the noise:
| Filter | Keeps |
|---|---|
event.eventType neq environment.scan | everything except environment scans |
event.eventType neq application.scan | everything except application scans |
event.eventType neq access.checkout.request | everything except checkout requests, which accompany every checkout |
actor.type neq System User | everything not performed by Britive’s own automation |
Collect one category only — name what you want instead:
| Filter | Keeps |
|---|---|
event.eventType sw access. | privileged access events — access.checkout, access.checkin, and that family |
event.eventType sw resource. | resource (server) access events |
event.eventType sw authentication. | logins and authentication events |
event.eventType sw secret. | secret reads, writes and deletions |
event.eventType co checkout | anything with checkout in the type, across all families |
event.eventType eq secret.read | exactly one event type |
Scope by who acted:
| Filter | Keeps |
|---|---|
actor.type eq ServiceIdentity | service identity activity only |
actor.type eq AIIdentity | AI identity activity only |
actor.type eq User | human activity only |
actor.username co @contoso.com | actors whose username contains that domain |
actor.displayName sw svc- | actors whose display name starts with svc- |
Scope by where it came from:
| Filter | Keeps |
|---|---|
client.ipAddress sw 10. | events from a 10.x address — prefix matching, not CIDR |
client.device eq CLI | events originating from the Britive CLI |
client.device neq Desktop | everything except desktop browser activity |
client.userAgent co pybritive | events from the pybritive tool |
Scope by what was touched:
| Filter | Keeps |
|---|---|
target.applicationName eq AWS Production | one application — spaces are fine, do not quote |
target.environmentName co prod | environments whose name contains prod |
target.displayName co admin | targets whose display name contains admin |
result.success eq false | failed actions only — true or false, not Success or 0 |
Combinations. Every clause must hold, and the same field may appear more than once:
| Filter | Keeps |
|---|---|
event.eventType sw access. and result.success eq false | failed privileged access attempts |
event.eventType eq secret.read and actor.type eq ServiceIdentity | secrets read by service identities |
target.applicationName eq AWS Production and event.eventType sw access. | access events against one application |
actor.type neq System User and event.eventType neq environment.scan | two exclusions at once |
event.eventType neq environment.scan and event.eventType neq application.scan and actor.type neq System User | three clauses |
neq is exact, and there is no “does not contain”. client.userAgent neq pybritive excludes nothing, because no user agent is exactly that string — they look like pybritive/2.4.1 britive-python-sdk/4.6.1 python-requests/2.34.2. To exclude a family of values, exclude each with its own and clause, or invert the filter and name what you want to keep.
event.eventType and event.displayName are different fields. The type is lowercase dotted (access.checkout); the display name is title case with spaces (Profile Check Out). Both are case-sensitive: event.eventType co Checkout matches nothing, and neither does event.displayName co check out.
Change or Remove a Filter Later
A filter takes effect on the next poll. Editing it does not restart collection or affect events already in the workspace, and events dropped while the old filter was in place stay dropped.
Edit queryParameters.filter in connector.json — set it to "" to go back to collecting everything — then re-send the same PUT from step 4:
az rest --method put \
--url "https://management.azure.com/subscriptions/$SUB/resourceGroups/$RG/providers/Microsoft.OperationalInsights/workspaces/$WS/providers/Microsoft.SecurityInsights/dataConnectors/BritiveAuditLogsConnection?api-version=2024-01-01-preview" \
--body @connector.json \
--headers "Content-Type=application/json"To read back what a connection is filtering on — the filter is returned in plain text, the token is not:
az rest --method get \
--url "https://management.azure.com/subscriptions/$SUB/resourceGroups/$RG/providers/Microsoft.OperationalInsights/workspaces/$WS/providers/Microsoft.SecurityInsights/dataConnectors/BritiveAuditLogsConnection?api-version=2024-01-01-preview" \
--query "properties.request.queryParameters"Delete connector.json again afterwards — it holds your token.
A filter is not a security boundary. It decides what is collected, not who may see it. Excluding an event type does not restrict access to those events in Britive, and anyone with access to the workspace still sees everything the filter let through.
Verify
Allow up to 15 minutes after connecting, then run these in Logs. Run all four — the second one is the check that catches the failure mode that looks like success.
Confirm events are arriving
BritiveAuditLogs_CL
| summarize Events = count(), Earliest = min(TimeGenerated), Latest = max(TimeGenerated)You want a non-zero count and a Latest within the last 15 minutes.
Confirm the columns are populated
A schema mismatch produces rows full of blanks while the connector still reports Connected. This is the check that catches it.
BritiveAuditLogs_CL
| summarize Total = count(),
NullActor = countif(isempty(ActorName)),
NullType = countif(isempty(EventType)),
NullKey = countif(isempty(EventKey)),
NullTenant = countif(isempty(BritiveTenantId))Every null count should be 0. EventId being empty is expected and correct — Britive returns no server-side event identifier.
Confirm TimeGenerated is event time
BritiveAuditLogs_CL
| extend Lag = ingestion_time() - TimeGenerated
| summarize avg(Lag), max(Lag), percentile(Lag, 95)Expect a lag of roughly 4 minutes. A lag near zero means TimeGenerated is holding ingestion time rather than the time the event happened in Britive — redeploy the template.
Confirm the parser works
BritiveAuditLogs
| project TimeGenerated, EventType, ActorName, ProfileName, IsNonHumanIdentity, Success
| sort by TimeGenerated desc
| take 20If this fails with an unknown-function error, the parsers did not deploy — check that you left Deploy Content set to true.
Enable the detections
Analytic rules deploy disabled so nothing starts paging your on-call before you have reviewed it. In Sentinel, go to Analytics → Rule templates, find the Britive rules, and create and enable the ones you want.
Enable BritiveNoAuditDataReceived first. It is the only thing that will tell you when an expired token silently stops ingestion.
Confirm which version you are running
az deployment group show -g "$RG" -n britive-sentinel-connector \
--query properties.outputs.solutionVersion.value -o tsvTroubleshoot
| Symptom | Likely cause | Fix |
|---|---|---|
| Connector not in the gallery | The connector definition failed to deploy, or you are in a different workspace | Confirm the deployment succeeded and that you opened Sentinel on the workspace you targeted |
| Connect fails immediately | Tenant URL or token wrong | Tenant URL must be https://<tenant>.britive-app.com with no path. Regenerate the token if unsure |
| Status Connected, no data after 20 minutes | Wrong tenant URL, the tenant is genuinely quiet, or a filter that matches nothing | Generate activity in Britive — log in, check out a profile — and re-check. Confirm the token’s identity has audit log read access. If you set queryParameters.filter, run validate-filter.py against it: a miscased value, an or, or a quoted value returns success and zero events |
| Fewer events arriving than the tenant produces | A filter is narrower than intended, or the connection was created from a body older than package 0.9.5 | Read the filter back with a GET on the connector URL and check it against step 5. Confirm paging.nextPageTokenResponseHeader reads next-page — bodies written before 0.9.5 used a header name Britive has since renamed, which caps collection at 100 events per five-minute poll |
| Data arrives, columns are blank | The connector version predates a change in the Britive API | Contact Britive Support with your package version — the connector needs an update |
TimeGenerated looks like ingestion time | The transform did not deploy intact | Redeploy the template |
| Deployment fails on the table | BritiveAuditLogs_CL already exists with a different schema | Delete the table under Log Analytics workspace → Tables and redeploy |
InvalidResourceLocation on the endpoint | A previous attempt created it in another region | Delete the stray ASI-* endpoint and redeploy with the workspace’s region |
| Parser queries fail with unknown function | Content was skipped at deployment | Redeploy with deployContent=true |
| Ingestion stopped with no error anywhere | Token expired or was revoked — the connector still reports Connected | Generate a new token and reconnect. Enable BritiveNoAuditDataReceived so this surfaces automatically |
Clean Up
Disconnect the connector first, then remove the resources. Deleting the ARM deployment record does not delete what it created.
Disconnect
On the Britive Audit Logs connector page, select Disconnect. Or from the command line, run az rest --method delete or Invoke-AzRestMethod -Method DELETE against the connector URL from step 4.
Remove the deployment record
Azure CLI:
az deployment group delete --resource-group "$RG" --name britive-sentinel-connectorAzure PowerShell:
Remove-AzResourceGroupDeployment -ResourceGroupName $RG -Name britive-sentinel-connectorRemove the resources
Delete the data collection endpoint and rule, the Britive analytic rules and saved searches, and the BritiveAuditLogs_CL table under Log Analytics workspace → Tables.
Sentinel requires specific names for the endpoint and rule, so neither is prefixed britive: the endpoint is ASI-<your workspace id> and the rule is Microsoft-Sentinel-britiveDCR1-<first 12 characters of that id>.
Azure CLI:
az resource list -g "$RG" --query "[?starts_with(name,'ASI-') || contains(name,'britiveDCR1')].id" -o tsv \
| xargs -r -n1 az resource delete --idsAzure PowerShell:
Get-AzResource -ResourceGroupName $RG |
Where-Object { $_.Name -like 'ASI-*' -or $_.Name -like '*britiveDCR1*' } |
ForEach-Object { Remove-AzResource -ResourceId $_.ResourceId -Force }