Skip to content

Commit 1d1aef7

Browse files
authored
Merge pull request #14765 from prodaft/prodaft-usta-atp
usta account takeover prevention playbook fix
2 parents b649b47 + a04f54c commit 1d1aef7

6 files changed

Lines changed: 307 additions & 94 deletions

File tree

Solutions/PRODAFT USTA - Account Takeover Prevention/Data/Solution_PRODAFTUstaATP.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"Playbooks": [
2424
"Playbooks/PRODAFTUstaATP-Backfill/azuredeploy.json"
2525
],
26-
"Version": "3.0.0",
26+
"Version": "3.0.1",
2727
"Metadata": "SolutionMetadata.json",
2828
"TemplateSpec": true,
2929
"Is1PConnector": false
Binary file not shown.

Solutions/PRODAFT USTA - Account Takeover Prevention/Package/mainTemplate.json

Lines changed: 153 additions & 36 deletions
Large diffs are not rendered by default.

Solutions/PRODAFT USTA - Account Takeover Prevention/Playbooks/PRODAFTUstaATP-Backfill/azuredeploy.json

Lines changed: 117 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
"contentVersion": "1.0.0.0",
44
"metadata": {
55
"title": "PRODAFTUstaATP-Backfill",
6-
"description": "On-demand backfill playbook for PRODAFT USTA - Account Takeover Prevention. Pages through the USTA compromised-credentials API and pushes historical tickets to the connector's Data Collection Endpoint (Logs Ingestion API), so the same DCR transform (password redaction, field mapping) is applied as for live polling. The codeless connector only polls forward from connection time; run this playbook once after connecting to load history.",
6+
"description": "On-demand backfill playbook for PRODAFT USTA - Account Takeover Prevention. Pages through the USTA compromised-credentials API and pushes historical tickets into the workspace via the Logs Ingestion API, applying the same transform (password redaction, field mapping) as live polling. The deployment is self-contained: it creates its own Data Collection Endpoint and Rule (writing into the existing PRODAFTUstaCompromisedCredentials_CL table) and assigns its identity the required role. The codeless connector only polls forward from connection time; run this playbook once after installing the solution to load history.",
77
"prerequisites": [
8-
"The PRODAFT USTA - Account Takeover Prevention solution installed and its data connector connected (this creates the DCE, DCR, and table).",
9-
"The Data Collection Endpoint logs-ingestion URI and the DCR immutable ID (see readme.md for how to find them).",
10-
"A PRODAFT USTA long-lived API key."
8+
"The PRODAFT USTA - Account Takeover Prevention solution installed (this creates the PRODAFTUstaCompromisedCredentials_CL table the backfill writes into); connecting the data connector starts forward polling.",
9+
"A PRODAFT USTA long-lived API key.",
10+
"Permission to create role assignments in the workspace resource group (Owner or User Access Administrator), since the template grants its identity 'Monitoring Metrics Publisher' automatically."
1111
],
1212
"postDeployment": [
13-
"Grant the playbook's system-assigned managed identity the 'Monitoring Metrics Publisher' role on the connector's Data Collection Rule, then run the trigger once (see readme.md)."
13+
"Run the playbook's trigger once to start the backfill (see readme.md)."
1414
],
1515
"lastUpdateTime": "2026-07-06T00:00:00.000Z",
1616
"entities": [],
@@ -22,6 +22,11 @@
2222
"name": "PRODAFT"
2323
},
2424
"releaseNotes": [
25+
{
26+
"version": "1.1",
27+
"title": "PRODAFTUstaATP-Backfill",
28+
"notes": [ "Playbook now self-provisions its Data Collection Endpoint and Rule and assigns its identity the Monitoring Metrics Publisher role; removes manual ingestion-endpoint and DCR immutable-ID setup." ]
29+
},
2530
{
2631
"version": "1.0",
2732
"title": "PRODAFTUstaATP-Backfill",
@@ -35,13 +40,18 @@
3540
"type": "string",
3641
"metadata": { "description": "Name of the Logic App playbook." }
3742
},
38-
"DataCollectionEndpointUri": {
43+
"WorkspaceName": {
3944
"type": "string",
40-
"metadata": { "description": "Logs-ingestion URI of the connector's Data Collection Endpoint, e.g. https://<dce-name>-a1b2.westeurope-1.ingest.monitor.azure.com" }
45+
"metadata": { "description": "Name of the existing Microsoft Sentinel / Log Analytics workspace that the ATP connector ingests into." }
4146
},
42-
"DataCollectionRuleImmutableId": {
47+
"WorkspaceLocation": {
4348
"type": "string",
44-
"metadata": { "description": "Immutable ID of the connector's Data Collection Rule, e.g. dcr-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }
49+
"metadata": { "description": "Region of the workspace. The backfill Data Collection Endpoint and Rule are created in this region." }
50+
},
51+
"WorkspaceResourceGroup": {
52+
"type": "string",
53+
"defaultValue": "[resourceGroup().name]",
54+
"metadata": { "description": "Resource group of the workspace. Leave as default if it is in the same resource group as this deployment." }
4555
},
4656
"UstaBaseUrl": {
4757
"type": "string",
@@ -58,25 +68,91 @@
5868
"metadata": { "description": "Number of days of history to backfill." }
5969
}
6070
},
71+
"variables": {
72+
"WorkspaceResourceId": "[resourceId(parameters('WorkspaceResourceGroup'), 'Microsoft.OperationalInsights/workspaces', parameters('WorkspaceName'))]",
73+
"DceName": "[concat(parameters('PlaybookName'), '-DCE')]",
74+
"DcrName": "[concat(parameters('PlaybookName'), '-DCR')]",
75+
"DceResourceId": "[resourceId('Microsoft.Insights/dataCollectionEndpoints', variables('DceName'))]",
76+
"DcrResourceId": "[resourceId('Microsoft.Insights/dataCollectionRules', variables('DcrName'))]",
77+
"MonitoringMetricsPublisherRoleId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '3913510d-42f4-4e42-8a64-420c390055eb')]"
78+
},
6179
"resources": [
80+
{
81+
"type": "Microsoft.Insights/dataCollectionEndpoints",
82+
"apiVersion": "2024-03-11",
83+
"name": "[variables('DceName')]",
84+
"location": "[parameters('WorkspaceLocation')]",
85+
"properties": {
86+
"description": "Data Collection Endpoint for the PRODAFT USTA ATP backfill playbook (Logs Ingestion API).",
87+
"networkAcls": {
88+
"publicNetworkAccess": "Enabled"
89+
}
90+
}
91+
},
92+
{
93+
"type": "Microsoft.Insights/dataCollectionRules",
94+
"apiVersion": "2024-03-11",
95+
"name": "[variables('DcrName')]",
96+
"location": "[parameters('WorkspaceLocation')]",
97+
"dependsOn": [
98+
"[variables('DceResourceId')]"
99+
],
100+
"properties": {
101+
"description": "DCR for the PRODAFT USTA ATP backfill playbook. Applies the same transform as the connector DCR and writes to the existing PRODAFTUstaCompromisedCredentials_CL table.",
102+
"dataCollectionEndpointId": "[variables('DceResourceId')]",
103+
"streamDeclarations": {
104+
"Custom-PRODAFTUstaCompromisedCredentials_CL": {
105+
"columns": [
106+
{ "name": "id", "type": "long" },
107+
{ "name": "status", "type": "string" },
108+
{ "name": "status_timestamp", "type": "datetime" },
109+
{ "name": "created", "type": "datetime" },
110+
{ "name": "content_type", "type": "string" },
111+
{ "name": "company", "type": "dynamic" },
112+
{ "name": "content", "type": "dynamic" }
113+
]
114+
}
115+
},
116+
"destinations": {
117+
"logAnalytics": [
118+
{
119+
"workspaceResourceId": "[variables('WorkspaceResourceId')]",
120+
"name": "clv2ws1"
121+
}
122+
]
123+
},
124+
"dataFlows": [
125+
{
126+
"streams": [ "Custom-PRODAFTUstaCompromisedCredentials_CL" ],
127+
"destinations": [ "clv2ws1" ],
128+
"transformKql": "source | extend c = content, co = company | project TimeGenerated = now(), TicketId = tolong(id), Status = tostring(status), StatusTimestamp = todatetime(status_timestamp), Created = todatetime(created), ContentType = tostring(content_type), CompanyId = toint(co.id), CompanyName = tostring(co.name), Username = tostring(c.username), Url = tostring(c.url), Source = tostring(c.source), IsCorporate = tobool(c.is_corporate), PasswordScore = tostring(c.password_complexity.score), PasswordLength = toint(c.password_complexity.length), VictimUid = tostring(c.victim_detail.victim_uid), VictimUsername = tostring(c.victim_detail.username), VictimCountry = tostring(c.victim_detail.country), VictimIp = tostring(c.victim_detail.ip), VictimComputerName = tostring(c.victim_detail.computer_name), VictimOs = tostring(c.victim_detail.victim_os), VictimMalware = tostring(c.victim_detail.malware), InfectionDate = todatetime(c.victim_detail.infection_date)",
129+
"outputStream": "Custom-PRODAFTUstaCompromisedCredentials_CL"
130+
}
131+
]
132+
}
133+
},
62134
{
63135
"type": "Microsoft.Logic/workflows",
64136
"apiVersion": "2019-05-01",
65137
"name": "[parameters('PlaybookName')]",
66138
"location": "[resourceGroup().location]",
67139
"identity": { "type": "SystemAssigned" },
140+
"dependsOn": [
141+
"[variables('DceResourceId')]",
142+
"[variables('DcrResourceId')]"
143+
],
68144
"tags": {
69145
"hidden-SentinelTemplateName": "PRODAFTUstaATP-Backfill",
70-
"hidden-SentinelTemplateVersion": "1.0"
146+
"hidden-SentinelTemplateVersion": "1.1"
71147
},
72148
"properties": {
73149
"state": "Enabled",
74150
"definition": {
75151
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
76152
"contentVersion": "1.0.0.0",
77153
"parameters": {
78-
"DataCollectionEndpointUri": { "type": "String", "defaultValue": "[parameters('DataCollectionEndpointUri')]" },
79-
"DataCollectionRuleImmutableId": { "type": "String", "defaultValue": "[parameters('DataCollectionRuleImmutableId')]" },
154+
"DataCollectionEndpointUri": { "type": "String", "defaultValue": "[reference(variables('DceResourceId'), '2024-03-11').logsIngestion.endpoint]" },
155+
"DataCollectionRuleImmutableId": { "type": "String", "defaultValue": "[reference(variables('DcrResourceId'), '2024-03-11').immutableId]" },
80156
"UstaBaseUrl": { "type": "String", "defaultValue": "[parameters('UstaBaseUrl')]" },
81157
"UstaApiKey": { "type": "SecureString", "defaultValue": "[parameters('UstaApiKey')]" },
82158
"BackfillDays": { "type": "Int", "defaultValue": "[parameters('BackfillDays')]" }
@@ -164,12 +240,39 @@
164240
},
165241
"parameters": {}
166242
}
243+
},
244+
{
245+
"type": "Microsoft.Authorization/roleAssignments",
246+
"apiVersion": "2022-04-01",
247+
"name": "[guid(variables('DcrResourceId'), parameters('PlaybookName'), variables('MonitoringMetricsPublisherRoleId'))]",
248+
"scope": "[variables('DcrResourceId')]",
249+
"dependsOn": [
250+
"[resourceId('Microsoft.Logic/workflows', parameters('PlaybookName'))]",
251+
"[variables('DcrResourceId')]"
252+
],
253+
"properties": {
254+
"roleDefinitionId": "[variables('MonitoringMetricsPublisherRoleId')]",
255+
"principalId": "[reference(resourceId('Microsoft.Logic/workflows', parameters('PlaybookName')), '2019-05-01', 'Full').identity.principalId]",
256+
"principalType": "ServicePrincipal"
257+
}
167258
}
168259
],
169260
"outputs": {
170-
"playbookPrincipalId": {
261+
"playbookName": {
262+
"type": "string",
263+
"value": "[parameters('PlaybookName')]"
264+
},
265+
"dataCollectionEndpoint": {
266+
"type": "string",
267+
"value": "[reference(variables('DceResourceId'), '2024-03-11').logsIngestion.endpoint]"
268+
},
269+
"dataCollectionRuleName": {
270+
"type": "string",
271+
"value": "[variables('DcrName')]"
272+
},
273+
"dataCollectionRuleImmutableId": {
171274
"type": "string",
172-
"value": "[reference(resourceId('Microsoft.Logic/workflows', parameters('PlaybookName')), '2019-05-01', 'full').identity.principalId]"
275+
"value": "[reference(variables('DcrResourceId'), '2024-03-11').immutableId]"
173276
}
174277
}
175278
}

Solutions/PRODAFT USTA - Account Takeover Prevention/Playbooks/PRODAFTUstaATP-Backfill/readme.md

Lines changed: 35 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,75 +4,64 @@ On-demand backfill playbook for the **PRODAFT USTA - Account Takeover Prevention
44

55
The codeless (CCF) data connector only polls **forward** from the moment it is connected.
66
This playbook loads history: it pages through the USTA compromised-credentials API and
7-
pushes the records to the connector's **Data Collection Endpoint** (Logs Ingestion API),
8-
so the same DCR transform — password redaction and field mapping — is applied exactly as
9-
for live polling. Plaintext passwords are never written to the workspace.
7+
pushes the records into the workspace via the **Logs Ingestion API**, applying the same
8+
transform — password redaction and field mapping — as live polling. Plaintext passwords are
9+
never written to the workspace.
10+
11+
The deployment is **self-contained**: it provisions its own Data Collection Endpoint (DCE)
12+
and Data Collection Rule (DCR) — the DCR uses the same schema and transform as the connector
13+
and writes into the existing `PRODAFTUstaCompromisedCredentials_CL` table — and grants its
14+
own managed identity the required role. You do **not** need to look up any endpoint URI or
15+
DCR immutable ID by hand.
1016

1117
## Prerequisites
1218

13-
1. The solution is **installed** (this deploys the `PRODAFTUstaATPDCR` data collection
14-
rule, its data collection endpoint, and the `PRODAFTUstaCompromisedCredentials_CL`
15-
table) and the **PRODAFT USTA - Account Takeover Prevention** data connector is
16-
**connected** (this creates the poller and starts polling forward).
19+
1. The **PRODAFT USTA - Account Takeover Prevention** solution is **installed**, which
20+
creates the `PRODAFTUstaCompromisedCredentials_CL` table the backfill writes into.
21+
(Connecting the data connector is recommended so forward polling is also active.)
1722
2. A PRODAFT USTA long-lived API key.
18-
3. Azure CLI signed in with permission to deploy into the workspace resource group and to
19-
create role assignments on the DCR (`Microsoft.Authorization/roleAssignments/write`,
20-
i.e. Owner or User Access Administrator on that scope).
23+
3. Permission to deploy into the workspace resource group **and to create role assignments**
24+
(`Microsoft.Authorization/roleAssignments/write` — i.e. Owner or User Access
25+
Administrator on that scope), since the template assigns *Monitoring Metrics Publisher*
26+
to the playbook's identity automatically.
2127

2228
## Scripted deployment (run from this folder)
2329

2430
```bash
2531
# ---- configuration ----
2632
SUB="<subscription-id>"
2733
RG="<usta-sentinel-resource-group>" # resource group of the Sentinel workspace
34+
WORKSPACE="<sentinel-workspace-name>"
35+
LOCATION="<workspace-region>" # e.g. westeurope
2836
USTA_API_KEY="<usta-api-key>"
2937
BACKFILL_DAYS=90
3038
PLAYBOOK="PRODAFTUstaATP-Backfill"
3139

3240
az account set --subscription "$SUB"
3341

34-
# 1. Locate the connector's DCR (deployed by the solution as 'PRODAFTUstaATPDCR')
35-
DCR_ID=$(az monitor data-collection rule list --resource-group "$RG" \
36-
--query "[?contains(name, 'PRODAFTUstaATP')].id | [0]" -o tsv)
37-
DCR_IMMUTABLE_ID=$(az monitor data-collection rule show --ids "$DCR_ID" \
38-
--query immutableId -o tsv)
39-
DCE_ID=$(az monitor data-collection rule show --ids "$DCR_ID" \
40-
--query dataCollectionEndpointId -o tsv)
41-
# If DCE_ID comes back empty, list endpoints directly:
42-
# az monitor data-collection endpoint list -g "$RG" -o table
43-
44-
# 2. Resolve the DCE logs-ingestion URI
45-
DCE_URI=$(az monitor data-collection endpoint show --ids "$DCE_ID" \
46-
--query logsIngestion.endpoint -o tsv)
47-
echo "DCR immutable ID: $DCR_IMMUTABLE_ID"
48-
echo "DCE ingestion URI: $DCE_URI"
49-
50-
# 3. Deploy the playbook (captures its managed identity from the deployment output)
51-
PRINCIPAL_ID=$(az deployment group create \
42+
# Deploy the playbook. It creates its own DCE + DCR, derives the ingestion
43+
# endpoint and DCR immutable ID automatically, and assigns its identity the
44+
# 'Monitoring Metrics Publisher' role on that DCR.
45+
az deployment group create \
5246
--resource-group "$RG" \
5347
--template-file azuredeploy.json \
5448
--parameters PlaybookName="$PLAYBOOK" \
55-
DataCollectionEndpointUri="$DCE_URI" \
56-
DataCollectionRuleImmutableId="$DCR_IMMUTABLE_ID" \
49+
WorkspaceName="$WORKSPACE" \
50+
WorkspaceLocation="$LOCATION" \
5751
UstaApiKey="$USTA_API_KEY" \
58-
BackfillDays=$BACKFILL_DAYS \
59-
--query properties.outputs.playbookPrincipalId.value -o tsv)
60-
61-
# 4. Grant the playbook's identity 'Monitoring Metrics Publisher' on the DCR
62-
az role assignment create \
63-
--assignee-object-id "$PRINCIPAL_ID" \
64-
--assignee-principal-type ServicePrincipal \
65-
--role "Monitoring Metrics Publisher" \
66-
--scope "$DCR_ID"
52+
BackfillDays=$BACKFILL_DAYS
6753

68-
# 5. Run the backfill once (or use 'Run Trigger' on the Logic App in the portal)
54+
# Run the backfill once (or use 'Run Trigger' on the Logic App in the portal)
6955
az rest --method POST \
7056
--url "https://management.azure.com/subscriptions/$SUB/resourceGroups/$RG/providers/Microsoft.Logic/workflows/$PLAYBOOK/triggers/manual/run?api-version=2016-10-01"
7157
```
7258

73-
> **RBAC propagation:** the role assignment in step 4 can take a minute to become
74-
> effective. If the first run shows 403 responses from the ingestion API in the run
75-
> history, simply run the trigger again (step 5).
59+
> If the workspace is in a **different resource group** than this deployment, add
60+
> `WorkspaceResourceGroup="<workspace-rg>"` to the parameters.
61+
62+
> **RBAC propagation:** the role assignment created by the deployment can take a minute to
63+
> become effective. If the first run shows 403 responses from the ingestion API in the run
64+
> history, simply run the trigger again.
7665
7766
Monitor progress under **Logic App → Runs history**, then verify data:
7867

@@ -84,6 +73,9 @@ PRODAFTUstaCompromisedCredentials
8473

8574
## Behavior and notes
8675

76+
* The backfill writes through its own DCR into the same `PRODAFTUstaCompromisedCredentials_CL`
77+
table as the connector, using an identical transform, so backfilled and live rows are
78+
indistinguishable to the solution's content.
8779
* Pages of 100 records are posted per request — well under the Logs Ingestion API's 1 MB
8880
request limit. The loop follows the API's `next` URL until exhausted (up to 1000 pages / 4 hours).
8981
* The API key and the fetched credential data are hidden from the Logic App run history:

Solutions/PRODAFT USTA - Account Takeover Prevention/ReleaseNotes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22

33
| **Version** | **Date Modified (DD-MM-YYYY)** | **Change History** |
44
|-------------|--------------------------------|--------------------|
5+
| 3.0.1 | 27-07-2026 | Updated the backfill **Playbook** to self-provision its Data Collection Endpoint (DCE) and Data Collection Rule (DCR) and to grant its managed identity the *Monitoring Metrics Publisher* role on the DCR. Removes the manual ingestion-endpoint URI and DCR immutable-ID setup previously required to run the backfill. |
56
| 3.0.0 | 06-07-2026 | Initial Solution Release. Codeless (CCF) data connector for compromised-credential tickets with ingestion-time password redaction (only strength signals are stored). Two **Analytic Rules** (corporate credential compromised; compromised credential used in a successful Entra ID sign-in), one **Hunting Query**, an overview **Workbook**, a query-time dedup **Parser**, and an on-demand backfill **Playbook** (Logs Ingestion API via managed identity) for loading historical data. |

0 commit comments

Comments
 (0)