Provisions a hardened Azure AI Search service (
azurerm_search_service) as a single, self-contained unit, targetinghashicorp/azurerm ~> 4.0. The empty call yields a private, Entra-ID-only service; every relaxation is an explicit caller opt-in.
- 🔎 Creates one Azure AI Search service (
azurerm_search_service) — the managed retrieval engine behind keyword, semantic, and vector search. - 🔐 Ships private and Entra-ID-only by default:
public_network_access_enabled = falseandlocal_authentication_enabled = false. - 🧮 Exposes tier and capacity —
sku,replica_count,partition_count,hosting_mode— with validation on the closed value sets. - 🧠 Supports semantic ranking (
semantic_search_sku) for relevance re-ranking on top of full-text search. - 🪪 Provisions a system-assigned managed identity by default, so the service can reach data sources and Key Vault without embedded secrets.
- 🧱 Offers a public-endpoint IP allow-list (
allowed_ips), a trusted-services bypass (network_rule_bypass_option), and customer-managed-key enforcement (customer_managed_key_enforcement_enabled).
💡 Why it matters: Azure AI Search is the retrieval layer for most Retrieval-Augmented Generation (RAG) workloads. Getting its exposure and authentication posture right at provisioning time keeps index data — often derived from sensitive source documents — off the public internet and behind Entra ID role-based access, rather than behind long-lived API keys.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
flowchart LR
rg["terraform-azurerm-resource-group"]:::sib
st["terraform-azurerm-storage-account"]:::sib
cos["terraform-azurerm-cosmosdb-account"]:::sib
cog["terraform-azurerm-cognitive-account"]:::sib
ra["terraform-azurerm-role-assignments"]:::sib
pe["terraform-azurerm-private-endpoint"]:::sib
app["Application / RAG workload"]:::sib
mod["terraform-azurerm-search-service"]:::me
svc["azurerm_search_service"]:::keystone
rg -->|"resource_group_name + location"| mod
mod -->|"creates"| svc
st -->|"indexer data source"| svc
cos -->|"indexer data source"| svc
svc -->|"vector search for RAG"| cog
ra -->|"Search Index Data Reader / Contributor"| svc
app -->|"query via Entra ID"| svc
pe -->|"private endpoint access"| svc
classDef me fill:#0078D4,color:#fff,stroke:#004578,stroke-width:1px;
classDef keystone fill:#004578,color:#fff,stroke:#002a4a,stroke-width:1px;
classDef sib fill:#eef3f8,color:#0a2540,stroke:#b9c9da,stroke-width:1px;
This is a standalone primitive. It consumes a resource group (name + location) and, at run time, reaches sibling data stores (Storage, Cosmos DB) as indexer sources. It pairs with terraform-azurerm-cognitive-account to serve vector search for RAG, is granted data-plane access through terraform-azurerm-role-assignments, and is fronted by terraform-azurerm-private-endpoint for private access.
flowchart LR
in1["name / resource_group_name / location"]:::sib
in2["sku + replica_count + partition_count"]:::sib
in3["identity: SystemAssigned"]:::sib
in4["secure defaults: private + Entra-only"]:::sib
svc["azurerm_search_service.this"]:::keystone
o1["id"]:::sib
o2["name"]:::sib
o3["endpoint"]:::sib
o4["identity_principal_id"]:::sib
in1 -->|"placement"| svc
in2 -->|"tier and capacity"| svc
in3 -->|"managed identity"| svc
in4 -->|"hardened posture"| svc
svc -->|"resource id"| o1
svc -->|"service name"| o2
svc -->|"query endpoint"| o3
svc -->|"principal id"| o4
classDef keystone fill:#004578,color:#fff,stroke:#002a4a,stroke-width:1px;
classDef sib fill:#eef3f8,color:#0a2540,stroke:#b9c9da,stroke-width:1px;
Resource inventory
| Resource | Count | Role |
|---|---|---|
azurerm_search_service.this |
1 | The keystone Search Service. |
identity {} (dynamic) |
0–1 | Optional managed identity (system-assigned by default). |
timeouts {} (dynamic) |
0–1 | Optional operation timeouts. |
| Requirement | Value |
|---|---|
| Terraform floor | >= 1.12.0 |
| Provider | hashicorp/azurerm, pinned ~> 4.0 |
| Provider block | None in this module — the caller configures provider "azurerm" { features {} }, auth, and subscription. |
Schema notes that bite (verified against the live provider schema):
name,resource_group_name, andlocationare force-new — changing any of them replaces the service.hosting_modeis force-new, andHighDensityis only accepted whensku = "standard3".skuchanges are partly force-new: tier upgrades within Basic and Standard apply in place, but downgrades, any change to or fromfree, and any change to or from the storage-optimized tiers replace the service.- 🔴
replica_countandpartition_countCAN be set on thefreetier. An earlier revision of this document said they could not. The provider refuses only values greater than 1 there — and its own schema default of1is sent on every apply, including onfree, so the arguments are never absent. The real per-tier ceilings, none of which were documented and all of which the module now checks atterraform validate: replicas 1 onfree, 3 onbasic, 12 elsewhere; partitions 1 onfree, 3 onbasic, 3 onstandard3withHighDensity, 12 elsewhere. - 🔴 The module's
hosting_modecheck used to be case-SENSITIVE while the provider's is not.hosting_modeis the only enum on this resource whose provider check ignores case, so"highdensity"was accepted by the provider and refused by this module — and because a failedvalidation {}blocksterraform destroyas well as apply, anyone who had used that spelling could not manage the service through the module at all. The check now compares case-insensitively, matching the provider. ⚠️ **authentication_failure_modeis REFUSED, not ignored, whenlocal_authentication_enabled = false.** The provider'sCustomizeDifferrors outright. Since this module defaults that flag tofalse, setting the failure mode alone is the *default* path into that error, not an unusual one — so the module now catches it atterraform validate`.⚠️ The IP firewall is not supported on thefreetier at all, soallowed_ipsentries there configure nothing.allowed_ipsare only honored whenpublic_network_access_enabled = true; with public access disabled, only private endpoint connections are accepted.authentication_failure_modeis only honored whenlocal_authentication_enabled = true.semantic_search_skucannot be set whensku = "free", and semantic ranking is region-limited.primary_key,secondary_key, andquery_keysare computed and sensitive — this module deliberately does not emit them.
Least-privilege, at the smallest scope that works:
- Search Service Contributor on the target resource group (or a custom role granting
Microsoft.Search/searchServices/*) to create and manage the service. - Granting data-plane access to callers (index read/write) is a separate concern — assign Search Index Data Reader or Search Index Data Contributor to consuming identities via
terraform-azurerm-role-assignments, scoped to this service'sid.
- An existing resource group in a supported US Azure region (this module does not create it).
- The
Microsoft.Searchresource provider registered on the target subscription. - A submitted quota-increase request for the
standard2,standard3,storage_optimized_l1, andstorage_optimized_l2tiers. - The caller configures the
provider "azurerm" { features {} }block, authentication, and subscription; the module declares none of these.
terraform-azurerm-search-service/
├── providers.tf # required_version + azurerm ~> 4.0; no provider block
├── variables.tf # deeply-typed object() schemas + tags/timeouts tail
├── main.tf # keystone azurerm_search_service.this; dynamic identity + timeouts, try()
├── outputs.tf # id first, then name, endpoint, identity principal/tenant, CMK status
├── README.md # this document
├── SCOPE.md # the cross-module contract
├── LICENSE # MIT
└── .gitignore # canonical library ignore set
The smallest real call — a private, Entra-ID-only Standard service:
module "search" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-search-service.git?ref=v1.0.0"
name = "srch-platform-eastus"
resource_group_name = "rg-platform-eastus"
location = "eastus"
sku = "standard"
tags = {
environment = "production"
workload = "search"
}
}ℹ️ The caller configures the provider, authentication, and the mandatory
features {}block:provider "azurerm" { features {} }Always pin the module with
?ref=v1.0.0; never track a branch.
Consumes
| Input | Type | Source module |
|---|---|---|
resource_group_name |
string |
terraform-azurerm-resource-group (name) |
location |
string |
caller / terraform-azurerm-resource-group (location) |
identity.identity_ids |
list(string) |
terraform-azurerm-user-assigned-identity (id) |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
Search Service Resource ID (first) | search-shared-private-link-service, role-assignments, private-endpoint, diagnostics |
name |
Search Service name | diagnostics / tagging conventions |
endpoint |
HTTPS query endpoint | application / RAG workload |
identity_principal_id |
Managed identity principal ID | role-assignments on Storage / Cosmos DB / Key Vault |
identity_tenant_id |
Managed identity tenant ID | cross-tenant references |
customer_managed_key_encryption_compliance_status |
CMK compliance status | governance reporting |
The examples below reference existing resources by ID or name rather than creating them; this module owns only its own resource. Those references are declared inputs:
variable "search_identity_id" {
description = "id of an existing search identity that these examples reference but do not create."
type = string
}1 · Minimal secure call
The empty-posture call: private, Entra-ID-only, system-assigned identity, no API keys.
module "search" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-search-service.git?ref=v1.0.0"
name = "srch-min-eastus"
resource_group_name = "rg-platform-eastus"
location = "eastus"
sku = "standard"
}🔒 With no overrides,
public_network_access_enabled = falseandlocal_authentication_enabled = false— the service is reachable only over private endpoints and only via Microsoft Entra ID.
2 · Basic tier for small workloads
module "search" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-search-service.git?ref=v1.0.0"
name = "srch-basic-eastus"
resource_group_name = "rg-team-eastus"
location = "eastus"
sku = "basic"
}💡
basicruns on a dedicated cluster and supports up to 3 replicas / 1 partition — a good floor for small production indexes.
3 · Replicas and partitions for scale
module "search" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-search-service.git?ref=v1.0.0"
name = "srch-scale-eastus2"
resource_group_name = "rg-platform-eastus2"
location = "eastus2"
sku = "standard"
replica_count = 3
partition_count = 3
}💡 Query throughput scales with
replica_count; storage and indexing throughput scale withpartition_count. Search Units billed = replicas x partitions.
4 · High-density hosting
module "search" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-search-service.git?ref=v1.0.0"
name = "srch-multitenant-eastus2"
resource_group_name = "rg-saas-eastus2"
location = "eastus2"
sku = "standard3"
hosting_mode = "HighDensity"
partition_count = 3
}
⚠️ hosting_mode = "HighDensity"is only valid withsku = "standard3", caps partitions at 3, and is force-new — changing it later replaces the service. It targets multi-tenant designs needing up to 1000 indexes.
5 · Semantic ranking
module "search" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-search-service.git?ref=v1.0.0"
name = "srch-semantic-eastus"
resource_group_name = "rg-ai-eastus"
location = "eastus"
sku = "standard"
semantic_search_sku = "standard"
}ℹ️ Semantic ranking re-scores results with a language-understanding model. It cannot be enabled on the
freetier and is available only in certain regions.
6 · Mixed Entra ID + API key authentication
Entra-ID-only is the default. Enable local authentication only when a workload cannot use Entra ID.
module "search" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-search-service.git?ref=v1.0.0"
name = "srch-mixedauth-eastus"
resource_group_name = "rg-legacy-eastus"
location = "eastus"
sku = "standard"
local_authentication_enabled = true
authentication_failure_mode = "http401WithBearerChallenge"
}
⚠️ Turning onlocal_authentication_enabledkeeps the computed admin and query API keys live.authentication_failure_modeis only honored in this mixed mode. Prefer Entra ID and leave both off where you can.
7 · User-assigned managed identity
module "search" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-search-service.git?ref=v1.0.0"
name = "srch-uami-eastus"
resource_group_name = "rg-platform-eastus"
location = "eastus"
sku = "standard"
identity = {
type = "UserAssigned"
identity_ids = [var.search_identity_id]
}
}💡 A user-assigned identity survives service re-creation and can be granted access to data sources ahead of time, decoupling RBAC setup from the service lifecycle.
8 · System- and user-assigned identity together
module "search" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-search-service.git?ref=v1.0.0"
name = "srch-dualid-eastus"
resource_group_name = "rg-platform-eastus"
location = "eastus"
sku = "standard"
identity = {
type = "SystemAssigned, UserAssigned"
identity_ids = [var.search_identity_id]
}
}ℹ️ Use the system-assigned identity for CMK and the user-assigned identity for shared data-source access, or vice versa.
9 · Public endpoint with an IP allow-list
module "search" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-search-service.git?ref=v1.0.0"
name = "srch-public-eastus"
resource_group_name = "rg-edge-eastus"
location = "eastus"
sku = "standard"
public_network_access_enabled = true
allowed_ips = ["203.0.113.10", "198.51.100.0/24"]
}🔴 An earlier revision of this document had this backwards, in the dangerous direction. It said an empty
allowed_ipswith public access on "blocks all public inbound traffic". It does the opposite. Microsoft's API reference is explicit: the rules "define the inbound network(s) with allowing access to the search service endpoint. At the meantime, all other public IP networks are blocked by the firewall" — the blocking is a consequence of listing rules. The three real states are:
configuration reachable from public_network_access_enabled = false(the module default)private endpoints only = true,allowed_ips = []every public network = true,allowed_ips = […]only the listed sources Requests still authenticate in every state — this is a network control, not an authorization one. The module emits
public_ip_firewall_is_openso the middle row is visible in an output rather than inferred.
10 · Trusted Azure services bypass
module "search" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-search-service.git?ref=v1.0.0"
name = "srch-bypass-eastus"
resource_group_name = "rg-ai-eastus"
location = "eastus"
sku = "standard"
public_network_access_enabled = true
network_rule_bypass_option = "AzureServices"
}🔒
network_rule_bypass_optiondefaults toNone. SetAzureServicesonly when a paired Microsoft service (for example an Azure AI service or a portal import) must reach a network-restricted service.
11 · Customer-managed-key enforcement
module "search" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-search-service.git?ref=v1.0.0"
name = "srch-cmk-eastus"
resource_group_name = "rg-regulated-eastus"
location = "eastus"
sku = "standard"
identity = {
type = "SystemAssigned"
}
customer_managed_key_enforcement_enabled = true
}
⚠️ Data is always encrypted at rest with Microsoft-managed keys. Enablecustomer_managed_key_enforcement_enabledonly once every downstream object (indexes, synonym maps) is configured with a customer-managed key, otherwise object creation is rejected.
12 · Private access only (default posture, made explicit)
module "search" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-search-service.git?ref=v1.0.0"
name = "srch-private-eastus"
resource_group_name = "rg-platform-eastus"
location = "eastus"
sku = "standard"
public_network_access_enabled = false
}🔒 With public access off, front the service with
terraform-azurerm-private-endpoint(sub-resourcesearchService) and a private DNS zone so clients resolve the endpoint privately.
13 · Many services with for_each
locals {
search_services = {
eastus = { location = "eastus", sku = "standard" }
westus2 = { location = "westus2", sku = "standard" }
}
}
module "search" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-search-service.git?ref=v1.0.0"
for_each = local.search_services
name = "srch-platform-${each.key}"
resource_group_name = "rg-platform-${each.key}"
location = each.value.location
sku = each.value.sku
}💡 A stable map key per region keeps
for_eachfrom re-indexing when you add or remove a region.
14 · Tags and operation timeouts
module "search" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-search-service.git?ref=v1.0.0"
name = "srch-tagged-eastus"
resource_group_name = "rg-platform-eastus"
location = "eastus"
sku = "standard"
tags = {
environment = "production"
cost_center = "1234"
owner = "data-platform"
}
timeouts = {
create = "30m"
delete = "30m"
}
}ℹ️ The
tags+timeoutstail is present on every module in this suite for consistent governance and lifecycle control.
15 · 🏗️ End-to-end composition
A resource group, a hardened Search Service, and data-plane role assignments wired from real sibling outputs. Pair it with terraform-azurerm-cognitive-account to complete a vector RAG stack.
provider "azurerm" {
features {}
}
module "rg" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = "rg-rag-eastus"
location = "eastus"
}
module "search" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-search-service.git?ref=v1.0.0"
name = "srch-rag-eastus"
resource_group_name = module.rg.name
location = module.rg.location
sku = "standard"
semantic_search_sku = "standard"
identity = {
type = "SystemAssigned"
}
}
# Grant the application identity data-plane access to the search indexes,
# and let a platform team manage the service itself.
module "search_access" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-role-assignments.git?ref=v1.0.0"
scope = module.search.id
role_assignments = {
app_reader = {
role_definition_name = "Search Index Data Reader"
principal_id = var.application_principal_id
}
platform_admin = {
role_definition_name = "Search Service Contributor"
principal_id = var.platform_group_object_id
}
}
}💡 The Search Service's own
identity_principal_idoutput feeds role assignments on the pairedterraform-azurerm-cognitive-accountand any Storage / Cosmos DB indexer data sources, so the service reaches its data — and the embedding model — without a single API key.
Required: name, resource_group_name, location, sku.
Capacity: replica_count, partition_count, hosting_mode, semantic_search_sku.
Authentication: local_authentication_enabled, authentication_failure_mode.
Network: public_network_access_enabled, allowed_ips, network_rule_bypass_option.
Encryption & identity: customer_managed_key_enforcement_enabled, identity.
Universal tail: tags, timeouts.
Full schemas
| Name | Type | Default | Description |
|---|---|---|---|
name |
string |
— | Globally unique service name; force-new. |
resource_group_name |
string |
— | Existing resource group; force-new. |
location |
string |
— | Azure region; force-new. |
sku |
string |
— | free | basic | standard | standard2 | standard3 | storage_optimized_l1 | storage_optimized_l2. |
replica_count |
number |
null |
Replicas; not valid on free. |
partition_count |
number |
null |
Partitions (1,2,3,4,6,12); not valid on free. |
hosting_mode |
string |
"Default" |
Default | HighDensity; force-new. Compared case-insensitively, matching the provider — the only enum here that does. |
semantic_search_sku |
string |
null |
free | standard; not valid on free tier. |
local_authentication_enabled |
bool |
false |
API key auth alongside Entra ID. |
authentication_failure_mode |
string |
null |
http401WithBearerChallenge | http403; mixed-mode only. |
public_network_access_enabled |
bool |
false |
Public endpoint exposure. |
allowed_ips |
list(string) |
[] |
Public-endpoint IP allow-list. |
network_rule_bypass_option |
string |
"None" |
None | AzureServices. |
customer_managed_key_enforcement_enabled |
bool |
false |
Reject non-CMK objects. |
variable "identity" {
type = object({
type = string # "SystemAssigned" | "UserAssigned" | "SystemAssigned, UserAssigned"
identity_ids = optional(list(string)) # required when type includes "UserAssigned"
})
default = { type = "SystemAssigned" }
}
variable "timeouts" {
type = object({
create = optional(string)
read = optional(string)
update = optional(string)
delete = optional(string)
})
default = null
}
variable "tags" {
type = map(string)
default = {}
}| Output | Description | Notes |
|---|---|---|
id |
Search Service Resource ID | Emitted first. |
name |
Search Service name | — |
location |
Azure region, in the canonical form Azure uses. | Read from the resource, not var.location. |
endpoint |
HTTPS query endpoint | — |
identity_principal_id |
Managed identity principal ID | null when no identity is configured. |
identity_tenant_id |
Managed identity tenant ID | null when no identity is configured. |
customer_managed_key_encryption_compliance_status |
CMK compliance status | Computed. |
🔒 The admin keys (
primary_key,secondary_key) andquery_keysare computed and sensitive; this module deliberately does not emit them. Prefer Entra ID role-based data-plane access.
- Force-new fields.
name,resource_group_name,location, andhosting_modeall force replacement.skuis partly force-new: upgrades within Basic/Standard apply in place, while downgrades and any move to or from thefreeor storage-optimized tiers replace the service. Treat a tier change as a potentially destructive operation and review the plan. - Free-tier constraints. The
freetier rejectsreplica_count,partition_count, andsemantic_search_sku. The module defaultsreplica_countandpartition_counttonullso the empty call works on any tier. - Interdependent fields, and the two behave differently.
allowed_ipshave no effect whilepublic_network_access_enabled = false— with public access off, private endpoints are the exclusive path and no IP rule changes that.authentication_failure_mode, by contrast, is refused outright whenlocal_authentication_enabled = false: the provider'sCustomizeDiffreturns an error, so the plan fails rather than the setting being dropped. The module now catches both atterraform validate, along with the per-tier capacity ceilings and the free-tier firewall restriction, so none of them waits for an apply. - Secret handling. API keys are never accepted as input and never emitted as output. Data-plane access is expected to run through Entra ID role assignments against the service
id. - Identity lifecycle. The default system-assigned identity is created with the service and destroyed with it; a user-assigned identity outlives the service and is the better choice when data-source RBAC must be prepared in advance.
features {}dependence. The provider will not initialize without a caller-sideprovider "azurerm" { features {} }block. That is expected — library modules never carry one.
The empty call produces the hardened service; each relaxation is a value the caller must type:
| Concern | Secure default (empty call) | Opt-out (caller types it) |
|---|---|---|
| Public network access | public_network_access_enabled = false |
set to true |
| Local (API key) authentication | local_authentication_enabled = false |
set to true |
| Trusted-service bypass | network_rule_bypass_option = "None" |
set to "AzureServices" |
| Public IP allow-list | allowed_ips = [] |
add addresses / CIDRs (with public access on) |
| Managed identity | system-assigned identity provisioned | pass identity = null |
| Encryption at rest | Microsoft-managed keys, always on | enable customer_managed_key_enforcement_enabled for CMK |
| Secret exposure | keys never accepted or emitted | — |
terraform init -backend=false
terraform validate
terraform fmt -check- Pin the module with
?ref=v1.0.0— never a branch. - This library is plan-only during authoring; a human runs
terraform planandterraform applyfrom CI against real credentials.
The offline proof gate is what this module is validated against:
terraform init -backend=false— resolves the pinned provider without a backend.terraform validate— proves the configuration is internally consistent and type-correct against the pinned provider schema. This catches every typing mistake theobject()schemas andvalidation {}blocks are designed to surface (a badsku, an out-of-rangehosting_mode, a malformedidentity).terraform fmt -check— enforces canonical formatting.
Neither validate nor fmt calls Azure. Only terraform plan (run by a human, against real credentials) exercises the ARM API and confirms tier availability, quota, and region support.
$ terraform output
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-rag-eastus/providers/Microsoft.Search/searchServices/srch-rag-eastus"
name = "srch-rag-eastus"
endpoint = "https://srch-rag-eastus.search.windows.net"
identity_principal_id = "11111111-2222-3333-4444-555555555555"
identity_tenant_id = "66666666-7777-8888-9999-aaaaaaaaaaaa"
customer_managed_key_encryption_compliance_status = "Compliant"| Symptom | Cause | Fix |
|---|---|---|
partition_count cannot be set when sku is free |
Capacity fields supplied on the free tier |
Leave replica_count / partition_count null on free, or move to basic/standard. |
hosting_mode HighDensity requires sku standard3 |
HighDensity set on a lower tier |
Set sku = "standard3", or use hosting_mode = "Default". |
| Plan shows the service being replaced after a tier change | sku change is force-new (downgrade, free, or storage-optimized) |
Confirm the change is intended; upgrades within Basic/Standard apply in place. |
allowed_ips appear to have no effect |
public_network_access_enabled = false |
IP rules only apply with public access on; otherwise use a private endpoint. |
| Clients get 403 with a valid API key | local_authentication_enabled = false |
Use Entra ID role-based access, or enable local auth if the client cannot use Entra ID. |
| Provider fails to initialize in isolation | No caller-side features {} block |
Add provider "azurerm" { features {} } to the root module. |
| Object creation rejected as non-compliant | customer_managed_key_enforcement_enabled = true without CMK on objects |
Configure CMK on every object, or disable enforcement. |
- Provider resource:
azurerm_search_service - Azure AI Search documentation
- Azure AI Search security and Entra ID role-based access
- Sibling modules:
terraform-azurerm-resource-group,terraform-azurerm-role-assignments,terraform-azurerm-private-endpoint,terraform-azurerm-cognitive-account,terraform-azurerm-storage-account,terraform-azurerm-cosmosdb-account,terraform-azurerm-user-assigned-identity. - This module's
SCOPE.md.
💙 "Infrastructure as Code should be standardized, consistent, and secure."