βοΈ Azure Sentinel Microsoft Threat Intelligence Data Connector Terraform Module β β οΈ DEPRECATED
Brings Microsoft's emerging-threat-feed indicators into a Microsoft Sentinel workspace (
azurerm_sentinel_data_connector_microsoft_threat_intelligence). Microsoft Defender Threat Intelligence reached end of life on 1 August 2026 and the standalone MDTI SKU is retired; its capabilities now arrive at no extra cost through the Microsoft Defender portal. SeeDEPRECATED.md. Targetshashicorp/azurerm ~> 4.0.
Milestone Date Microsoft Defender Threat Intelligence reaches end of life; the standalone MDTI SKU is retired 1 August 2026 β elapsed Sentinel's Azure portal experience retires 31 March 2027 Microsoft: "All MDTI capabilities are now available at no extra cost through the Microsoft Defender portal to any customer with Microsoft Defender or Microsoft Sentinel."
The purchasable product in front of this connector no longer exists, so any procurement step in an older runbook β an MDTI subscription, an API-access SKU, a sales conversation to unlock the feed β describes something retired. The capability was folded in rather than withdrawn: it is included for Defender and Sentinel customers through the Defender portal. This does NOT mean the connector has stopped collecting β that is established only for the sibling Threat Intelligence Platforms connector, so verify ingestion in the workspace rather than inferring it. The provider schema flags nothing. See
DEPRECATED.md, and note that this module deliberately adds no new validation, because avalidation {}failure blocksterraform destroyand destroy is now the point.
- π Brings Microsoft's own emerging-threat-feed indicators into the Sentinel workspace.
- β The lookback date is REQUIRED here β and that required-ness is doing real work: it makes the epoch-default trap its pair sibling carries impossible on this resource.
β οΈ And it is force-new, unlike the TAXII connector's equivalent field β so changing it costs a gap in collection and a fresh import.β οΈ tenant_idlooks like a cross-tenant switch and is not. Omit it.- π΄ Nothing is updatable. Every argument is force-new.
- π It is one of the threat-intelligence alert rule's possible upstreams β that rule supplies no indicators of its own.
- π No credential is accepted and none is emitted.
π‘ Why it matters: Four arguments, and the one that matters is required rather than optional β which is the whole difference between this module and its sibling. A required argument is doing the job a warning would otherwise have to do, and it is worth noticing when two resources look otherwise identical.
Microsoft states that after March 31, 2027, Microsoft Sentinel will no longer be supported in the Azure portal and will be available only in the Microsoft Defender portal, and that customers using Microsoft Sentinel in the Azure portal will be redirected to the Defender portal. Since July 2025 many new customers are onboarded and redirected to the Defender portal automatically.
This does not change what this module manages.
azurerm_sentinel_*are ARM resources and Terraform talks to ARM, not to a portal, so these resources continue to exist and stay manageable from code across that date. What moves is the management experience - runbooks, screenshots, analyst training, and any procedure that ends in a human clicking through Sentinel in the Azure portal. Plan that transition on its own schedule; this module emitsmanagement_moves_to_the_defender_portalso the date reaches plan output and inventory reports rather than living only in documentation.
If this module saves you time, please consider supporting its continued development:
- β Star the repository on GitHub.
- π€ Connect on LinkedIn: linkedin.com/in/microsoftexpert
- β Buy me a coffee: buymeacoffee.com/microsoftexpert
flowchart LR
law["terraform-azurerm-log-analytics-workspace: every connector writes into this workspace, and its RETENTION bounds how far back any detection built on the data can look"]
onboard["terraform-azurerm-sentinel-log-analytics-workspace-onboarding: THE GATE. No connector works until Sentinel is onboarded, and DESTROYING IT OFFBOARDS SENTINEL."]
wsid["and every connector takes log_analytics_workspace_id FROM THAT MODULE'S workspace_id OUTPUT, not from the workspace module's id - same string, but reading it through the onboarding module is what makes Terraform ORDER ONBOARDING FIRST"]
clones["THE NINE TENANT-SCOPED CONNECTORS, all with an IDENTICAL three-argument shape: name, workspace, and an optional tenant_id. NOTHING is updatable, so every argument is force-new and the schema omits the update timeout."]
list["azure-active-directory. azure-advanced-threat-protection. dynamics-365. microsoft-defender-advanced-threat-protection. microsoft-threat-protection. office-365-project. office-atp. office-irm. office-power-bi."]
tenant["AND tenant_id IS A TRAP: it is optional and computed, so it defaults to the running account's tenant - and the provider states that ONLY the same tenant is allowed, because cross-tenant collection IS NOT SUPPORTED. An argument that looks like it enables multi-tenant collection and does not."]
preview["all nine sit on a PREVIEW API version, which is worth knowing before depending on their argument surface"]
others["THE OTHER NINE CONNECTORS ARE NOT CLONES: azure-security-center and iot take a SUBSCRIPTION id instead. microsoft-cloud-app-security and office-365 carry per-stream TOGGLES. microsoft-threat-intelligence and threat-intelligence take a LOOKBACK DATE. aws-cloud-trail and aws-s3 take an AWS ROLE ARN. threat-intelligence-taxii takes a URL, a collection and CREDENTIALS."]
rules["AND CONNECTORS ARE THE UPSTREAM THAT DECIDES WHETHER A DETECTION CAN FIRE AT ALL"]
ms["terraform-azurerm-sentinel-alert-rule-ms-security-incident: its product_filter takes LEGACY product names, and three of them map exactly onto connectors in this batch - Azure Advanced Threat Protection, Microsoft Defender Advanced Threat Protection, and Office 365 Advanced Threat Protection."]
sched["terraform-azurerm-sentinel-alert-rule-scheduled and -nrt: a query against a table no connector is filling DEPLOYS CLEANLY AND NEVER FIRES"]
anom["terraform-azurerm-sentinel-alert-rule-anomaly-built-in: emits required_data_connector, which is Microsoft's own statement of which connectors its detection needs. Cross-check it against what is actually deployed."]
silent["SO THE FAMILY RISK IS THE SAME ONE AS THE ALERT RULES, ARRIVING FROM UPSTREAM: a perfectly configured detection with no connector behind it is SILENT, and silence is indistinguishable from the good outcome."]
law -->|"id"| onboard
onboard -->|"workspace_id"| wsid
wsid -->|"log_analytics_workspace_id"| clones
wsid -->|"log_analytics_workspace_id"| others
clones -->|"which nine"| list
tenant -->|"read this first"| clones
preview -->|"stability"| clones
clones -->|"fills tables"| rules
others -->|"fills tables"| rules
rules -->|"product_filter upstream"| ms
rules -->|"query tables"| sched
rules -->|"required_data_connector"| anom
silent -->|"the shared failure mode"| rules
classDef me fill:#0078D4,stroke:#004578,color:#fff;
classDef keystone fill:#004578,stroke:#001f3f,color:#fff;
classDef sib fill:#eef2f7,stroke:#b8c4d0,color:#1b1b1b;
class onboard keystone;
class clones me;
class law,wsid,list,tenant,preview,others,rules,ms,sched,anom,silent sib;
flowchart TB
pair["TWO CONNECTOR MODULES SHARE THIS EXACT SHAPE: microsoft-threat-intelligence and threat-intelligence. name, log_analytics_workspace_id, an optional tenant_id, and a LOOKBACK DATE. So this diagram is the same in both READMEs."]
diff["BUT ONE FIELD DIFFERS IN A WAY THAT MATTERS MORE THAN ANY OTHER DIFFERENCE IN THIS FAMILY: on microsoft-threat-intelligence the lookback is REQUIRED, and on threat-intelligence it is OPTIONAL and DEFAULTS TO THE UNIX EPOCH."]
trap["so threat-intelligence carries the same trap as the TAXII connector: leave the lookback unset and the first poll imports EVERYTHING the feed has ever published - a real one-off ingestion bill that arrives without warning"]
saved["AND ITS SIBLING CANNOT MAKE THAT MISTAKE, because the provider makes the field REQUIRED there. A required argument is doing the job a warning would otherwise have to do, which is worth noticing when the two resources look otherwise identical."]
rfc["both are validated for RFC 3339 shape with can(timeadd), because a PROVIDER schema check only fires against a literal in a resource block and does not reach through a module boundary. NOTE what is NOT true: an unparseable value does not fall back to the epoch. The provider discards the parse error and would send Go zero time, 0001-01-01, and the schema validator makes that path unreachable anyway. The epoch matters only as the OPTIONAL side default."]
fnew["AND UNLIKE THE TAXII CONNECTOR, THE LOOKBACK HERE IS FORCE-NEW. Changing it destroys and recreates the connector, so you get a GAP in collection AND a fresh import from the new date - two costs from one edit."]
nothing["in fact NOTHING is updatable on either resource: every argument is force-new and the schema OMITS the update timeout, so writing update into the resource's timeouts block fails at plan"]
tenant["tenant_id is optional, computed and force-new, and the provider states that ONLY THE SAME TENANT IS ALLOWED because cross-tenant collection is not supported - so the computed default is the only workable value. Omit it."]
ws["log_analytics_workspace_id wants the workspace's ARM RESOURCE ID, wired from the ONBOARDING module's workspace_id output so Terraform orders onboarding first. A bare GUID is rejected at plan time."]
ti["AND THE INDICATORS THESE BRING IN ARE WHAT THE THREAT-INTELLIGENCE ALERT RULE MATCHES AGAINST. That rule supplies none of its own, so these connectors are exactly the upstream whose absence makes it a detection that CANNOT FIRE while looking perfectly healthy."]
this["one of the two lookback-scoped threat-intelligence connector modules"]
keystone["the azurerm_sentinel_data_connector resource for that one feed, named this"]
pair -->|"but"| diff
diff -->|"so"| trap
trap -->|"whereas"| saved
saved -->|"and"| rfc
rfc -->|"validated"| this
fnew -->|"in fact"| nothing
nothing -->|"lifecycle"| this
tenant -->|"posture"| this
ws -->|"wiring"| this
ti -->|"why it matters"| this
this -->|"brings threat-intelligence indicators in"| keystone
classDef me fill:#0078D4,stroke:#004578,color:#fff;
classDef keystone fill:#004578,stroke:#001f3f,color:#fff;
classDef sib fill:#eef2f7,stroke:#b8c4d0,color:#1b1b1b;
class this me;
class keystone keystone;
class pair,diff,trap,saved,rfc,fnew,nothing,tenant,ws,ti sib;
βΉοΈ That diagram is shared with
terraform-azurerm-sentinel-data-connector-threat-intelligence, deliberately: within this pair the schema, the lifecycle and the failure modes are identical. What differs is which data arrives β and, for the lookback pair, one field's required-ness, which example 3 is about.
Resource inventory
| Resource | Count | Notes |
|---|---|---|
azurerm_sentinel_data_connector_microsoft_threat_intelligence.this |
1 | The keystone. Four arguments, none updatable. |
timeouts block |
0..1 | Create, read and delete only β there is no update operation. |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/azurerm |
~> 4.0 |
| Azure resource provider | Microsoft.SecurityInsights on a Log Analytics workspace β a preview API version |
| Provider block | None in this module. The caller configures provider "azurerm", including the mandatory features {} block, and supplies authentication. |
Schema notes that bite β confirmed against the live provider schema and its documentation:
- β
microsoft_emerging_threat_feed_lookback_dateis REQUIRED β the provider gives it no default, so the epoch trap its pair sibling carries cannot happen here. β οΈ And it is force-new, unlike the TAXII connector'slookback_date, which updates in place.tenant_idis optional, computed and force-new, and restricted to the running account's tenant.- The argument name is long and unlike anything else in the family β
microsoft_emerging_threat_feed_lookback_date, notlookback_date. - π΄ Every argument is force-new, and the schema omits the
updatetimeout β so every change is a destroy-and-recreate, which for a connector is a gap in collection. β οΈ Anupdatekey passed to this module'stimeoutsvariable is silently discarded, not rejected.- The Resource ID ends
/dataConnectors/<name>under the workspace. - No
tags. Tag the workspace. lifecycleis not valid inside amoduleblock, so a caller cannot addprevent_destroy.
| Operation | Role | Scope |
|---|---|---|
| Creating or deleting a data connector | Microsoft Sentinel Contributor | the resource group containing the Log Analytics workspace, or the workspace |
| Reading connectors | Microsoft Sentinel Reader | the same scope |
Contributor or Owner at the same scope also work, and are broader than needed.
βΉοΈ The entitlement question this connector used to raise has closed. Microsoft Defender Threat Intelligence reached end of life on 1 August 2026: the standalone MDTI SKU is retired, and MDTI's capabilities are now included at no extra cost through the Microsoft Defender portal for any customer with Microsoft Defender or Microsoft Sentinel. So there is nothing to purchase in front of this connector, and any older instruction to buy an MDTI subscription or an API-access SKU describes a product that no longer exists.
βΉοΈ No product-side licence or tenant setting is documented for this connector, which makes it one of the more straightforward in the family from a prerequisites point of view.
- The Log Analytics workspace already onboarded to Microsoft Sentinel β wire
log_analytics_workspace_idfrom that module'sworkspace_idoutput so Terraform orders the two correctly. - A decision about the lookback date, since the provider requires one. Something recent is the usual choice.
- Retention long enough to be useful. Sentinel has no storage of its own, so the workspace's retention bounds what any detection on this data can look back at.
terraform-azurerm-sentinel-data-connector-microsoft-threat-intelligence/
βββ providers.tf # required_version + the pinned azurerm provider. No provider block.
βββ variables.tf # name, log_analytics_workspace_id,
# # microsoft_emerging_threat_feed_lookback_date (REQUIRED),
# # tenant_id, timeouts
βββ main.tf # the keystone, one resource
βββ outputs.tf # id first, then the distinctive facts, then the constant posture flags
βββ README.md # this document
βββ SCOPE.md # the cross-module contract
βββ DEPRECATED.md # the retirement dates, what still works, and the migration sequence
βββ LICENSE # MIT
βββ .gitignore
provider "azurerm" {
features {}
}
module "dc_ms_ti" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-sentinel-data-connector-microsoft-threat-intelligence.git?ref=v1.0.0"
name = "microsoft-threat-intelligence"
# β οΈ From the ONBOARDING module, not the workspace module. See example 1.
log_analytics_workspace_id = module.sentinel.workspace_id
# β
REQUIRED β the provider gives no default, which is what prevents the
# epoch trap this module's sibling carries. See example 3.
microsoft_emerging_threat_feed_lookback_date = "2026-06-01T00:00:00Z"
# tenant_id deliberately omitted β only the running account's tenant is supported.
}βΉοΈ The caller configures the provider, its authentication, and the mandatory
features {}block. This module declares none of them.
β οΈ Read example 3 β the required lookback here is the interesting difference from this module's sibling.
Consumes
| Input | Type | Source module |
|---|---|---|
name |
string |
caller β an ARM name, force-new |
log_analytics_workspace_id |
string |
the Sentinel onboarding module β workspace_id |
microsoft_emerging_threat_feed_lookback_date |
string |
caller β required, RFC 3339 |
tenant_id |
string |
omit it |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
The connector's Resource ID. | review, imports |
name |
The ARM name. Force-new. | review |
log_analytics_workspace_id |
The workspace it writes into. Force-new. | review |
microsoft_emerging_threat_feed_lookback_date |
How far back the feed reaches. Force-new. | cost review |
tenant_id |
Computed when omitted. | review |
collects |
A constant naming what arrives. | coverage review |
is_upstream_of_detections |
Always true. |
dependency review |
No credential is accepted and none is emitted.
1 Β· β οΈ Wire the workspace from the onboarding module
# β
This creates the dependency edge.
log_analytics_workspace_id = module.sentinel.workspace_id# β οΈ Identical string. No dependency on onboarding.
log_analytics_workspace_id = module.law.idπ‘ Both carry the same value, so both work once Sentinel is onboarded. The difference is ordering: reading it through the onboarding module makes Terraform onboard Sentinel before creating this connector, and a connector created against a workspace that is not yet a Sentinel workspace fails at apply.
β οΈ And note what the field is not:
# β Rejected at plan time.
log_analytics_workspace_id = azurerm_log_analytics_workspace.example.workspace_idπ΄
azurerm_log_analytics_workspaceexports both anidand an attribute literally namedworkspace_id. This argument wants the ARM Resource ID; the similarly-named attribute is the customer GUID.
2 Β· π What this connector actually brings in
Brings in: Microsoft's EMERGING THREAT FEED indicators
(Microsoft's own threat-intelligence, curated from its global signal)
βΉοΈ These are Microsoft's indicators, not yours and not a third party's. That makes this connector complementary to the TAXII one rather than an alternative: TAXII brings whatever feeds you subscribe to, this brings what Microsoft sees.
π‘ And it needs no credential and no feed subscription, which makes it the cheapest indicator source to turn on β four arguments, one of which is a date.
β οΈ It is not the same as thethreat-intelligenceconnector, this module's pair sibling, despite the near-identical shape. That one is the platform's general threat-intelligence upload path; this one is specifically Microsoft's emerging-threat feed β see example 3 for the difference that shows up in the schema.
3 Β· β The lookback is REQUIRED here β and that prevents its sibling's trap
THIS module: REQUIRED, no default
microsoft_emerging_threat_feed_lookback_date
Its pair sibling (threat-intelligence): OPTIONAL, defaults to 1970-01-01T00:00:00Z
lookback_date
The TAXII connector: OPTIONAL, defaults to 1970-01-01T00:00:00Z
lookback_date
β A required argument is doing the job a warning would otherwise have to do. On the other two, an unset lookback means the first poll imports everything the feed has ever published β a substantial one-off ingestion bill that arrives without warning. Here that cannot happen, because the provider will not let you omit the field.
π‘ That is worth noticing precisely because the two resources look otherwise identical: same three other arguments, same force-new lifecycle, same tenant restriction. The one difference is the one that protects you.
β οΈ So set it to something considered rather than something arbitrary. Recent β the last 30 or 90 days β gets you a useful indicator set without importing years of expired ones:
microsoft_emerging_threat_feed_lookback_date = "2026-06-01T00:00:00Z"βΉοΈ And note the argument's name, which is unlike anything else in the family: not
lookback_datebutmicrosoft_emerging_threat_feed_lookback_date. Copying the shorter name across from the sibling module produces an "unsupported argument" error.
4 Β· β οΈ The lookback is FORCE-NEW here β unlike on the TAXII connector
This module: microsoft_emerging_threat_feed_lookback_date FORCE-NEW
The TAXII connector: lookback_date UPDATES IN PLACE
β οΈ So changing the lookback costs twice. It destroys and recreates the connector, which is a gap in collection (example 8) β and then the fresh connector imports from the new date, which is a second ingestion event.
π‘ Which makes the lookback a decision to get right before the first apply rather than one to tune afterwards. Compare the TAXII connector, where the same field is an ordinary in-place edit.
βΉοΈ Validated for RFC 3339 shape using the
can(timeadd(..., "0s"))technique this suite uses for timestamp inputs:
Error: Invalid value for variable
microsoft_emerging_threat_feed_lookback_date must be a valid RFC 3339 timestamp, for
example 2026-06-01T00:00:00Z.
β οΈ 01/06/2026is the mistake this catches. A date in almost any other format is rejected at plan rather than producing whatever the service makes of it.
5 Β· β οΈ `tenant_id` looks like a cross-tenant switch and is not
# β
Recommended: omit it.
# tenant_id = ...
β οΈ The provider states the restriction plainly: only the same tenant as the running account is allowed, because cross-tenant collection is not supported yet. The field is optional and computed, so omitting it resolves to that same tenant β the only value that can work.
π‘ So omitting it is strictly better than setting it. An explicit value adds a way to be wrong without adding a capability, and because the field is force-new, correcting a wrong one is a gap in collection.
βΉοΈ The restriction is documented, not enforced. This module cannot see which tenant the running account belongs to, so a validation would either be wrong or reject legal input. The GUID shape is checked and the restriction is stated.
π‘ The same field, with the same non-capability, appears on nine other connectors in this family β it is the family's most consistent trap.
6 Β· π This is the threat-intelligence alert rule's missing upstream
THIS CONNECTOR the threat-intelligence ALERT RULE
brings indicators in -> matches log data against them
and supplies NONE of its own
π΄ The
sentinel-alert-rule-threat-intelligencemodule cannot fire without a feed like this one. With no indicator source it is a detection that cannot produce anything, and nothing about it looks wrong β every field set,is_activetrue, and nothing to match against.
β οΈ And the silence is indistinguishable from the good outcome. "No indicator matches" is what you hope to see, so a rule with an empty indicator set looks exactly like a healthy quiet period.
π‘ So the check after the first apply is upstream: are indicators arriving? The Sentinel threat-intelligence blade answers it; Terraform cannot.
βΉοΈ Three modules in this suite can feed that rule β this one, its pair sibling, and the TAXII connector for third-party feeds. They are complementary rather than alternatives: this one and its sibling bring Microsoft's and the platform's own indicators, and TAXII brings whatever third-party feeds you subscribe to. Running all three is normal.
7 Β· β οΈ A preview API version
This resource's ARM API version is a PREVIEW version.
β οΈ Worth knowing before depending on the argument surface. Preview API versions can change behaviour between provider releases in ways a stable version would not, and this family has already seen that churn: the Fusion alert rule'snameargument is deprecated and removed in provider v5.0.
π‘ Note that the TAXII connector is documented against a stable API version as well, making it the exception among the eighteen. If you are choosing where to invest first, that is a small point in its favour.
βΉοΈ This library pins
azurerm ~> 4.0precisely so that a provider upgrade is a deliberate, per-module decision rather than something that happens on aterraform init.
8 Β· π΄ Nothing is updatable β and that means gaps
Force-new: name log_analytics_workspace_id microsoft_emerging_threat_feed_lookback_date tenant_id
Updatable: nothing
timeouts = { create = "30m", read = "5m", delete = "30m" } # β
π΄ Every change to this connector is a destroy-and-recreate, and for a connector that is not a metadata edit β it is a gap in collection. Whatever would have been ingested between the destroy and the create is simply absent from the workspace, and no detection built on the table will report the hole.
β οΈ So a rename β the most innocuous-looking change imaginable β costs data. Settlenamebefore the first apply.
βΉοΈ Writing
updateinto the resource's own timeouts block fails at plan, because the provider's schema omits it. But note the asymmetry:
timeouts = { create = "30m", update = "30m" } # β οΈ the `update` key is SILENTLY DISCARDED
β οΈ Terraform's object-type conversion drops attributes this variable's type does not declare, so a four-field timeouts block copied from a sibling module applies three fields and warns about nothing. It is the one place in this module where a wrong input produces no error at all.
π‘ A caller cannot add
prevent_destroy(lifecycleis not valid inside amoduleblock). ACanNotDeletelock at the workspace scope is the available control.
9 Β· What a review should assert
output "dc_ms_ti_review" {
value = {
id = module.dc_ms_ti.id
collects = module.dc_ms_ti.collects # what arrives
upstream = module.dc_ms_ti.is_upstream_of_detections # always true
lookback = module.dc_ms_ti.microsoft_emerging_threat_feed_lookback_date
tenant = module.dc_ms_ti.tenant_id
}
}π
collectsis the line that makes a coverage review possible. A Resource ID and an ARM name say nothing about the data; this output names it, so a list of connectors reads as a list of what you can detect on.
β
lookbackis always populated here, because the provider requires it β so unlike this module's sibling, there is no epoch-default flag to check. The required argument removed the need for one.
βΉοΈ
upstreamis a constant. It is in the outputs so that "what goes quiet if we remove this?" has an answer in the state review rather than only in this document.
10 Β· Importing a connector that already exists
terraform import 'module.dc_ms_ti.azurerm_sentinel_data_connector_microsoft_threat_intelligence.this' \
"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sentinel/providers/Microsoft.OperationalInsights/workspaces/law-sentinel-prod/providers/Microsoft.SecurityInsights/dataConnectors/microsoft-threat-intelligence"π‘ Importing is usually the right move. Connectors are normally enabled from the Sentinel data-connectors gallery, and since every argument is force-new, creating a duplicate in Terraform rather than adopting the existing one means deleting and recreating β a gap in collection.
π΄ And on THIS resource the import does not check the connector kind, unlike every sibling. Confirmed against the provider source: it declares no importer of its own, only an ID-validation function, so the wrapper falls through to a plain passthrough import that checks the ID shape and nothing else β and the family's shared kind assertion has no case for this connector's kind at all. Every connector in the family shares the ID shape
.../dataConnectors/<name>, so importing an Azure AD or Office 365 connector into this module succeeds. The mismatch then surfaces as a read failure or a permanent diff rather than a refused import. Confirm the kind in the portal or via the API first.import_validates_the_id_shape_onlyis emitted for this reason.
β οΈ Matchnameexactly. It is the last segment of the Resource ID, it is force-new, and a mismatch proposes a replacement rather than an error.
β οΈ Restate the lookback date to match before the first plan. It is required and force-new, so a mismatch proposes a replacement β a gap in collection plus a fresh import (example 4).
βΉοΈ Leave
tenant_idunset. It is computed, so an omitted argument matches the imported value.
π‘ Import, confirm an empty plan, and only then change anything.
11 Β· What a destroy actually removes
terraform destroy on this module:
removes the connector -> collection STOPS
-> the workspace, its tables and existing data SURVIVE
-> every rule built on this data stays valid and goes SILENT
βΉοΈ Existing data is not deleted. The workspace keeps whatever was already ingested, subject to retention β so a destroy is not data loss, it is the end of new data.
π΄ But the detections do not fail; they go quiet. Nothing in Azure raises an alert for "a table stopped filling", and a rule with no results looks exactly like a rule with nothing to report.
β οΈ And re-creating the connector does not backfill. The gap between destroy and create is permanent.
π‘ So the order for a deliberate removal is: decide what detections depend on this data, say so out loud, then remove the connector. If the intent is only "stop managing this with Terraform", the operation you want is
terraform state rm, which leaves collection running.
12 Β· ποΈ End-to-end composition
provider "azurerm" {
features {}
}
module "rg" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = "rg-sentinel-prod"
location = "eastus"
}
module "law" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-log-analytics-workspace.git?ref=v1.0.0"
name = "law-sentinel-prod"
resource_group_name = module.rg.name
location = module.rg.location
sku = "PerGB2018"
retention_in_days = 90
}
# ββ The gate βββββββββββββββββββββββββββββββββββββββββββββββββββ
module "sentinel" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-sentinel-log-analytics-workspace-onboarding.git?ref=v1.0.0"
workspace_id = module.law.id
}
# ββ THE CONNECTOR: Microsoft's own indicators, no credential needed ββββββββ
module "dc_ms_ti" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-sentinel-data-connector-microsoft-threat-intelligence.git?ref=v1.0.0"
name = "microsoft-threat-intelligence"
log_analytics_workspace_id = module.sentinel.workspace_id
# REQUIRED β which is what prevents the epoch trap (example 3). Force-new (example 4).
microsoft_emerging_threat_feed_lookback_date = "2026-06-01T00:00:00Z"
# tenant_id omitted (example 5).
}
# ββ The rule that matches against those indicators, and supplies none itself ββ
module "rule_ti_matching" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-sentinel-alert-rule-threat-intelligence.git?ref=v1.0.0"
name = "ti-map-ip-to-signin"
log_analytics_workspace_id = module.sentinel.workspace_id
alert_rule_template_guid = var.ti_template_guid
# Orders creation only β it cannot make indicators exist (example 6).
depends_on = [module.dc_ms_ti]
}
resource "azurerm_management_lock" "sentinel" {
name = "sentinel-no-delete"
scope = module.law.id
lock_level = "CanNotDelete"
notes = "Deleting connectors leaves gaps in collection that nothing reports"
}
output "collection_posture" {
value = {
collects = module.dc_ms_ti.collects
lookback = module.dc_ms_ti.microsoft_emerging_threat_feed_lookback_date
# Active, which is necessary and NOT sufficient β it needs the connector above.
rule_active = module.rule_ti_matching.is_active
}
}π What the composition gets right: a considered lookback date rather than an arbitrary one,
tenant_idomitted, and the alert rule declared alongside the connector it depends on with adepends_onwhose limits are documented.
β οΈ What no plan will tell you: whether any indicators have actually arrived.depends_onorders creation; it cannot make a first sync complete, and a rule matching against an empty indicator set is silent and looks healthy.
π‘ This is the cheapest indicator source in the family to turn on β no credential, no feed subscription, four arguments. Worth having even alongside a TAXII feed.
| Input | Type | Default | Notes |
|---|---|---|---|
name |
string |
β | Required. Force-new. |
log_analytics_workspace_id |
string |
β | Required. Force-new. Shape-validated; from the onboarding module. |
microsoft_emerging_threat_feed_lookback_date |
string |
β | Required. RFC 3339, validated. Force-new. |
tenant_id |
string |
null |
Force-new, computed. |
timeouts |
object(...) |
null |
Create, read, delete only β there is no update operation. |
There is no tags variable β the provider exposes none on Sentinel data connectors.
Full schemas
# See variables.tf β the distinctive checks are quoted in the examples above.| Output | Description | Kind |
|---|---|---|
id |
The Resource ID of the data connector | Passthrough |
name |
The connector's ARM name | Passthrough |
log_analytics_workspace_id |
The workspace this connector writes into | Passthrough |
microsoft_emerging_threat_feed_lookback_date |
How far back the feed reached on first sync | Passthrough |
tenant_id |
The tenant being collected from | Passthrough |
collects |
A constant naming what this connector brings into the workspace | Derived |
is_upstream_of_detections |
Always true, and emitted because it is the fact most easily forgotten: detections built on this connector's tables cannot fire without it | Constant |
every_argument_is_force_new |
Always true | Constant |
import_validates_the_id_shape_only |
Always true, and the EXCEPTION in this family: this connector's import checks the ID shape and not the connector kind, unlike every sibling | Constant |
requires_the_workspace_to_be_sentinel_onboarded |
Always true, and enforced by nothing offline | Constant |
first_sync_volume_is_set_at_creation_and_immutable |
Always true, and the fact most likely to cost money on this connector | Constant |
management_moves_to_the_defender_portal |
Always true - Sentinel's Azure portal experience retires 2027-03-31 and moves to the Microsoft Defender portal; the ARM resources are unaffected | Constant |
retirement_notice |
A fixed string carrying the deprecation: MDTI reached end of life 2026-08-01 and the standalone SKU is retired; collection by this connector is NOT confirmed stopped | Constant |
π No credential is accepted and none is emitted.
-
The required lookback is treated as the module's most interesting property, because it is the one thing that distinguishes this resource from a near-identical sibling β and what it prevents is a real, expensive trap. A required argument doing the job a warning would otherwise have to do is worth naming.
-
No epoch-default flag is emitted here, deliberately. Its sibling and the TAXII connector both emit
pulled_entire_historybecause their lookback can silently be the epoch; here the provider makes that impossible, so a flag would always readfalseand imply a risk that does not exist. -
The unusual argument name is called out.
microsoft_emerging_threat_feed_lookback_dateis unlike anything else in the family, and copying the sibling's shorterlookback_dateacross produces an "unsupported argument" error that reads as a module defect. -
The force-new lookback is contrasted with the TAXII connector's updatable one. The same conceptual field has different lifecycles across two resources in one family, and only one of them makes a lookback change cheap.
-
collectsis emitted as a constant string, which is unusual and earns its place. A connector's Resource ID and ARM name say nothing about the data, and "what can we detect on?" is the question a coverage review actually asks. -
is_upstream_of_detectionsis a constanttruefor the fact it makes visible, not for the value. Destroying a connector breaks no rule's configuration and makes every rule built on its data permanently silent β an asymmetry the provider models nowhere. -
depends_onis explicitly not recommended for the connector-to-rule relationship. Creation order is not the problem: a rule created before its data simply finds nothing until data arrives. -
The pair shares one shape diagram and one generated skeleton deliberately. Within the pair the schema, lifecycle and failure modes are identical; inventing distinctions a reader would then have to check is worse than saying plainly that they are the same.
| Concern | Secure default (empty call) | Opt-out (caller must type it) |
|---|---|---|
| Unbounded first import | prevented by the provider β the lookback is required, so the epoch trap cannot occur | β |
| Unparseable timestamps | RFC 3339 validated at plan | β |
| Illusory capability | tenant_id recommended omitted |
state it, knowingly |
| Silent collection gaps | the no-update-path consequence documented in four sections | β |
| Silently-dropped inputs | the timeouts update-key discard documented |
β |
| Invisible coverage | collects emitted so a connector list reads as a capability list |
β |
| Invisible dependencies | is_upstream_of_detections emitted |
β |
| Wrong-value pastes | the workspace's GUID attribute rejected at plan | β |
| Destroy protection | a workspace-scope CanNotDelete lock recommended |
destroy anyway, knowingly |
| Secrets | none accepted, none emitted | β |
- Before the first apply: choose a considered lookback. It is required, and force-new.
- Before setting
tenant_id: it cannot vary, so omitting it is strictly better. - Before changing the lookback later: it costs a gap in collection plus a fresh import.
- After the first apply: check indicators are arriving β the alert rule cannot tell you.
terraform init -backend=false
terraform validate
terraform fmt -check- Pin the source to a tag β
?ref=v1.0.0β never a branch. - Plan-only from here. A human applies from CI.
- π΄ Any change is a destroy-and-recreate, and therefore a gap in collection. Read plans carefully.
β οΈ A destroy stops collection and leaves every dependent rule valid and silent. Existing data survives.β οΈ The lookback is force-new here (unlike on the TAXII connector), so changing it costs a gap plus a fresh import.- βΉοΈ Prefer
terraform importover creating a connector that already exists in the portal. - βΉοΈ To stop managing without stopping collection, use
terraform state rm. - βΉοΈ After the first apply, confirm data is arriving in the Sentinel data-connectors blade.
terraform validate and terraform fmt -check are the offline gate. They confirm:
log_analytics_workspace_idis aMicrosoft.OperationalInsights/workspacesARM Resource ID β a bare GUID is rejected;nameis non-empty;microsoft_emerging_threat_feed_lookback_dateis present β it has no default, so Terraform requires it β and is a valid RFC 3339 timestamp;tenant_id, when set, is a GUID;- the
timeoutsvariable's object type declares onlycreate,readanddelete; - no output is sensitive, because nothing sensitive is accepted;
- the module declares no
providerblock.
π‘ These were proved by evaluating the conditions in
terraform consoleinside the module β which does fire root-module variable validations, unliketerraform validateon a calling configuration. A bare GUID in the workspace field and a01/06/2026lookback both fail. The lookback's required-ness is a property of its declaration: a variable with nodefaultis required by Terraform's own semantics, so it needs no separate test.
What only plan and apply exercise:
- whether the workspace exists and is onboarded to Sentinel;
- whether the identity holds Microsoft Sentinel Contributor.
What no Terraform command checks at any stage:
- π΄ whether any indicators have arrived, and therefore whether the threat-intelligence alert rule can fire at all;
- how much data the first sync will import for a given lookback date;
- whether the feed is enabled for this tenant on Microsoft's side.
Outputs:
collects = "Microsoft emerging-threat-feed indicators"
id = "/subscriptions/00000000-.../dataConnectors/microsoft-threat-intelligence"
is_upstream_of_detections = true
log_analytics_workspace_id = "/subscriptions/00000000-.../workspaces/law-sentinel-prod"
microsoft_emerging_threat_feed_lookback_date = "2026-06-01T00:00:00Z"
name = "microsoft-threat-intelligence"
tenant_id = "00000000-0000-0000-0000-000000000000"
β The lookback is always populated, because the provider requires it β which is why there is no
pulled_entire_historyflag here and there is one on this module's sibling (example 3).
βΉοΈ
tenant_idis populated despite being omitted β it is computed from the running account's tenant.
| Symptom | Cause | Fix |
|---|---|---|
Plan rejects log_analytics_workspace_id |
The workspace's workspace_id GUID attribute was passed. |
Use the onboarding module's workspace_id (example 1). |
| A first apply fails saying the workspace is not onboarded | Nothing ordered onboarding first. | Wire from the onboarding module (example 1). |
Unsupported argument: lookback_date |
The sibling module's shorter field name was used. | It is microsoft_emerging_threat_feed_lookback_date (example 3). |
No value for required variable on the lookback |
It is required here, unlike on the sibling. | Supply it (example 3). |
| Plan rejects the lookback | Not RFC 3339 β 01/06/2026 is the usual mistake. |
Use 2026-06-01T00:00:00Z (example 4). |
| A lookback change proposed a replacement | It is force-new here, unlike on TAXII. | Expected β gap plus fresh import (example 4). |
| The threat-intelligence alert rule never fires | It has no indicators to match. | This connector is one of its upstreams (example 6). |
| Indicators are not arriving | The first sync may not have completed. | Check the threat-intelligence blade (example 6). |
| A small edit shows a destroy and recreate | Every argument is force-new. | Expected β and it costs data (example 8). |
An update timeout was ignored without error |
The variable's object type silently discards it. | Use create/read/delete only (example 8). |
| The connector exists and no data arrives | See the prerequisites β most causes are outside this module. | Check the Sentinel data-connectors blade. |
| Detections built on this data never fire | The connector may be absent or not yet collecting. | Check here first, not the rule (example 9). |
| A duplicate connector was created instead of adopted | name is force-new, so a mismatch replaces. |
Import the existing one (example 10). |
Wanted prevent_destroy |
lifecycle is not valid inside a module block. |
Use a workspace-scope lock (example 8). |
| Wanted to tag this resource | The provider exposes no tags. | Tag the workspace. |
azurerm_sentinel_data_connector_microsoft_threat_intelligenceβ provider documentation.- Microsoft Sentinel data connectors β what connectors do, and the gallery this one appears in.
- Microsoft Defender Threat Intelligence data connector β what Microsoft's feed contains.
- Microsoft Sentinel roles and permissions β the RBAC table above.
- Sibling modules:
terraform-azurerm-sentinel-log-analytics-workspace-onboarding, its pair siblingterraform-azurerm-sentinel-data-connector-threat-intelligence, the nine tenant-scoped connector clones, and the five substantive connectors (...-aws-cloud-trail,...-aws-s3,...-threat-intelligence-taxii,...-microsoft-cloud-app-security,...-office-365). - MDTI reaches end of life, August 1, 2026 β the retirement of the standalone SKU.
- This module's
DEPRECATED.mdβ the retirement dates and the migration sequence. - This module's
SCOPE.md.
π "Infrastructure as Code should be standardized, consistent, and secure."