Skip to content

Latest commit

 

History

History
120 lines (97 loc) · 7.35 KB

File metadata and controls

120 lines (97 loc) · 7.35 KB

SCOPE — terraform-azurerm-mssql-managed-instance-security-alert-policy

Design intent

Configures Advanced Threat Protection for an Azure SQL Managed Instance: which detections run, who is told when one fires, and whether a copy of the threat-detection records is written to storage you own.

The module's enabled default diverges from the provider's, deliberately. The provider declares enabled with no default, so its zero value is false and a bare resource block creates a disabled alert policy — a threat-detection record that detects nothing while appearing in state and in the portal. A module named for a security alert policy should not ship one that is switched off, so true is the default here and the caller types enabled = false to opt out.

That default has a bill attached, and the module says so rather than leaving it to an invoice: Advanced Threat Protection is part of Microsoft Defender for SQL, priced per node — the whole managed instance, not per database.

In scope

  • azurerm_mssql_managed_instance_security_alert_policy — a single resource named this.

Out of scope / consumed by id

  • The managed instance itself — consumed as a name plus resource group, not an ID.
  • The threat-detection storage account — consumed as a blob endpoint; the storage module owns it.
  • The vulnerability assessment, which is the other half of Microsoft Defender for SQL and is owned by the sibling module. Both capabilities sit inside the same per-node purchase.
  • The managed instance's identity and any role assignment on the storage account — not visible from this resource and not created here.

Consumes

Input Type Source module
managed_instance_name string (name) terraform-azurerm-mssql-managed-instance → name
resource_group_name string (name) the caller
storage_endpoint string (https URL) terraform-azurerm-storage-account → primary_blob_endpoint
storage_account_access_key string (sensitive) out of band

🔑 Required Azure RBAC Roles / Permissions

Permission Scope Why
Microsoft.Sql/managedInstances/securityAlertPolicies/read the managed instance plan and refresh
Microsoft.Sql/managedInstances/securityAlertPolicies/write the managed instance create, update and disable the policy
SQL Security Manager the managed instance Microsoft names this as the role for enabling and managing Defender for SQL settings
  • The Terraform principal needs no storage permission. It supplies an endpoint and optionally a key; the service does the writing.
  • Reading this policy does not read a credential — the provider's read never queries Azure for the access key, so plan access here is not credential access.

Azure Prerequisites

  • The Microsoft.Sql resource provider registered on the subscription.
  • An existing Azure SQL Managed Instance.
  • A Microsoft Defender for SQL entitlement, billed per node. Enabling this policy is a purchase.
  • For storage-backed records: an existing storage account whose blob endpoint is passed here, reachable from the instance, plus a credential or an identity grant.
  • The caller configures the provider "azurerm" { features {} } block, auth and subscription.

Emits

Output Description Consumed by
id Resource ID of the alert policy (first) downstream references
managed_instance_id the parent ID, derived by trimming this policy's own ID terraform-azurerm-mssql-managed-instance-vulnerability-assessment → managed_instance_id
enabled_alert_types the deny list stated positively security review
all_alert_types the provider's closed set of six building a deny list against a checked reference
this_policy_detects_nothing true when disabled or when every type is denied security review
alerts_reach_nobody_by_email true when no recipient is configured operational review
an_access_key_is_configured presence flag only — never the key security review
module_default_diverges_from_provider_default constant true reading the module honestly
advanced_threat_protection_is_billed_per_node constant true cost review
force_new_fields the parent reference lifecycle review

No plaintext secret is emitted.

Provider gotchas

Verified against the pinned provider schema and the provider's source at the pinned tag:

  • 🔴 The provider's enabled has no default, so its zero value is false. This module defaults it to true; the divergence is emitted as an output.
  • 🔴 disabled_alerts is a DENY list. An entry switches that detection off. The legal values are a closed, case-sensitive set of six enforced by the provider.
  • 🔴 terraform destroy does not delete this policy — it disables it. The provider's delete sends a policy carrying only the disabled state, so the endpoint, recipients and retention it had are not preserved by that call.
  • 🔴 There is no import-already-exists guard on create. Applying over an existing policy silently overwrites it; a clean apply is not evidence that nothing was configured before.
  • 🔴 The access key is never read back from Azure. The provider's read re-sets the field from state rather than querying the service, so drift on it is undetectable by construction.
  • ⚠️ storage_endpoint carries only a non-empty check in the provider — no URL validation at all. The https and not-a-Resource-ID checks are this module's.
  • ⚠️ The parent is a name and a resource group here, while the sibling vulnerability assessment takes a single managed_instance_id. Both come from the same provider release.
  • ⚠️ managed_instance_name is validated with the logical SERVER name rule, borrowed from the mssql package: lowercase letters, digits and hyphens, no leading or trailing hyphen, at most 63 characters.
  • ℹ️ retention_days governs storage only, and governs nothing when no endpoint is set. The provider's only check is that it is not negative; there is no documented upper bound.
  • ℹ️ resource_group_name comes from the provider's shared schema, which carries force-new and normalisation.
  • ℹ️ No tags, no location. Tag the managed instance instead.
  • All four timeouts are honoured here (30/5/30/30) — which is not true of the sibling.

Design decisions

  • Per this module suite's single-primary-resource convention, the resource is named this.
  • enabled defaults to true, diverging from the provider, because the empty call must produce the safe resource and a disabled alert policy is not it. The divergence and its cost are both emitted.
  • email_account_admins_enabled keeps the provider's false, because who should receive a security alert is an organisational decision rather than a safe-versus-unsafe one.
  • The deny list's argument name is mirrored from the provider — a wrong value fails loudly against a closed enum — and its complement is emitted positively as enabled_alert_types, so a reviewer never has to invert a list.
  • email_addresses refuses only the obvious mistake (no @); no email regex is imposed, because one would reject legal addresses.
  • The parent ID is derived by trimming this policy's own Resource ID rather than assembled from a subscription looked up at plan time.