Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

☁️ 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. See DEPRECATED.md. Targets hashicorp/azurerm ~> 4.0.

Terraform Provider Module Type Resources Caveat Status

🧩 Overview

⚠️ Read this before anything else

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 a validation {} failure blocks terraform destroy and 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_id looks 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.

πŸ“… Azure portal retirement - March 31, 2027

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 emits management_moves_to_the_defender_portal so the date reaches plan output and inventory reports rather than living only in documentation.

Reference: https://learn.microsoft.com/azure/sentinel/overview#microsoft-sentinel-in-the-azure-portal-retirement-timeline

❀️ Support this project

If this module saves you time, please consider supporting its continued development:


πŸ—ΊοΈ Where this fits in the family

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;
Loading

🧬 What this module builds

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;
Loading

ℹ️ 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.

βœ… Provider / Versions

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_date is 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's lookback_date, which updates in place.
  • tenant_id is 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, not lookback_date.
  • πŸ”΄ Every argument is force-new, and the schema omits the update timeout β€” so every change is a destroy-and-recreate, which for a connector is a gap in collection.
  • ⚠️ An update key passed to this module's timeouts variable is silently discarded, not rejected.
  • The Resource ID ends /dataConnectors/<name> under the workspace.
  • No tags. Tag the workspace.
  • lifecycle is not valid inside a module block, so a caller cannot add prevent_destroy.

πŸ”‘ Required Azure RBAC Roles / Permissions

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.

Azure Prerequisites

  • The Log Analytics workspace already onboarded to Microsoft Sentinel β€” wire log_analytics_workspace_id from that module's workspace_id output 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.

πŸ“ Module Structure

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

βš™οΈ Quick Start

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.

πŸ”Œ Cross-Module Contract

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.

πŸ“š Example Library

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_workspace exports both an id and an attribute literally named workspace_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 the threat-intelligence connector, 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_date but microsoft_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/2026 is 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-intelligence module 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_active true, 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's name argument 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.0 precisely so that a provider upgrade is a deliberate, per-module decision rather than something that happens on a terraform 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. Settle name before the first apply.

ℹ️ Writing update into 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 (lifecycle is not valid inside a module block). A CanNotDelete lock 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
  }
}

πŸ”’ collects is 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.

βœ… lookback is 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.

ℹ️ upstream is 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_only is emitted for this reason.

⚠️ Match name exactly. 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_id unset. 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_id omitted, and the alert rule declared alongside the connector it depends on with a depends_on whose limits are documented.

⚠️ What no plan will tell you: whether any indicators have actually arrived. depends_on orders 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.

πŸ“₯ Inputs

Input Type Default Notes
name string β€” Required. Force-new. ⚠️ A rename costs data.
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. ⚠️ Omit it.
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.

🧾 Outputs

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.

🧠 Architecture Notes

  • 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_history because their lookback can silently be the epoch; here the provider makes that impossible, so a flag would always read false and imply a risk that does not exist.

  • The unusual argument name is called out. microsoft_emerging_threat_feed_lookback_date is unlike anything else in the family, and copying the sibling's shorter lookback_date across 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.

  • collects is 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_detections is a constant true for 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_on is 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.

🧱 Design Principles

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.

πŸš€ Runbook

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 import over 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.

πŸ§ͺ Testing

terraform validate and terraform fmt -check are the offline gate. They confirm:

  • log_analytics_workspace_id is a Microsoft.OperationalInsights/workspaces ARM Resource ID β€” a bare GUID is rejected;
  • name is non-empty;
  • microsoft_emerging_threat_feed_lookback_date is present β€” it has no default, so Terraform requires it β€” and is a valid RFC 3339 timestamp;
  • tenant_id, when set, is a GUID;
  • the timeouts variable's object type declares only create, read and delete;
  • no output is sensitive, because nothing sensitive is accepted;
  • the module declares no provider block.

πŸ’‘ These were proved by evaluating the conditions in terraform console inside the module β€” which does fire root-module variable validations, unlike terraform validate on a calling configuration. A bare GUID in the workspace field and a 01/06/2026 lookback both fail. The lookback's required-ness is a property of its declaration: a variable with no default is 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.

πŸ’¬ Example Output

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_history flag here and there is one on this module's sibling (example 3).

ℹ️ tenant_id is populated despite being omitted β€” it is computed from the running account's tenant.

πŸ” Troubleshooting

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.

πŸ”— Related Docs

πŸ’™ "Infrastructure as Code should be standardized, consistent, and secure."