Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

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

Repository files navigation

☁️ Azure Redis Firewall Rule Terraform Module

A single IP-range firewall rule on an Azure Cache for Redis, targeting hashicorp/azurerm ~> 4.0. It allows client connections from a contiguous address range whenever the cache permits public network access.

Terraform azurerm Module Version Type Resources

🧩 Overview

This module manages one Redis firewall rule and nothing else:

  • πŸ”₯ A single azurerm_redis_firewall_rule keyed to an existing Redis cache.
  • πŸ“ A contiguous allowed IP range expressed as start_ip / end_ip (set both to the same value for a single address).
  • πŸ”— References the cache by redis_cache_name and resource_group_name; it never creates the cache itself.
  • 🧾 Emits the firewall rule's resource id for downstream references.

πŸ’‘ Why it matters: A Redis cache with public network access on rejects every client not covered by a firewall rule. This module lets a composition declare each allowed range as a small, independently keyed record β€” so adding, removing, or re-scoping one range never disturbs the others.

❀️ Support this project

If this module saves you time, a little support goes a long way:

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

flowchart LR
  rc["terraform-azurerm-redis-cache"]
  me["terraform-azurerm-redis-firewall-rule"]
  v["azurerm_redis_firewall_rule"]
  client["client IP range"]
  rc -->|"redis_cache_name"| me
  me -->|"creates"| v
  client -.->|"allowed by (public access only)"| v
  classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
  classDef target fill:#004578,stroke:#002d4d,color:#ffffff;
  classDef ext fill:#f2f2f2,stroke:#c8c8c8,color:#111111;
  class me me;
  class v target;
  class rc,client ext;
Loading

The Redis cache is owned by terraform-azurerm-redis-cache; this module consumes its name and adds allowed ranges. The dotted edge is a reminder that firewall rules are consulted only when the cache exposes a public endpoint.

🧬 What this module builds

flowchart LR
  in_id["name / redis_cache_name / resource_group_name"]
  in_ip["start_ip / end_ip"]
  res["azurerm_redis_firewall_rule.this"]
  out_id["id"]
  in_id -->|"input"| res
  in_ip -->|"input"| res
  res -->|"output"| out_id
  classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
  class res me;
Loading

Resource inventory

Resource Cardinality Role
azurerm_redis_firewall_rule.this single (keystone) The allowed IP range on the target cache.

βœ… Provider / Versions

Requirement Value
Terraform >= 1.12.0
azurerm provider ~> 4.0
Provider block None in the module β€” the caller configures provider "azurerm", including the mandatory features {} block, plus authentication.

Schema notes that bite

  • name, redis_cache_name, and resource_group_name are immutable β€” changing any of them forces a new resource (the rule is destroyed and recreated).
  • start_ip and end_ip update in place β€” widening or narrowing a range does not replace the rule.
  • Firewall rules apply only when the cache allows public network access. With the secure default of terraform-azurerm-redis-cache (public access off), a rule is created but never consulted β€” reach the cache over a private endpoint instead.
  • For a single address, set start_ip and end_ip to the same value.

πŸ”‘ Required Azure RBAC Roles / Permissions

Least-privilege at the Redis cache scope (or its resource group):

  • Microsoft.Cache/redis/firewallRules/write
  • Microsoft.Cache/redis/firewallRules/read
  • Microsoft.Cache/redis/firewallRules/delete

The built-in Contributor role scoped to the cache (or its resource group) covers these.

🧰 Azure Prerequisites

  • An existing Redis cache.
  • The Microsoft.Cache resource provider registered on the subscription.

πŸ“ Module Structure

terraform-azurerm-redis-firewall-rule/
β”œβ”€β”€ providers.tf   # required_version + azurerm ~> 4.0 pin (no provider block)
β”œβ”€β”€ variables.tf   # name, redis_cache_name, resource_group_name, start_ip, end_ip, timeouts
β”œβ”€β”€ main.tf        # azurerm_redis_firewall_rule.this + dynamic timeouts
β”œβ”€β”€ outputs.tf     # id
β”œβ”€β”€ README.md      # this document
β”œβ”€β”€ SCOPE.md       # cross-module contract
β”œβ”€β”€ LICENSE        # MIT
└── .gitignore     # canonical library ignore set

βš™οΈ Quick Start

provider "azurerm" {
  features {}
}

module "office_range" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-firewall-rule.git?ref=v1.0.0"

  name                = "office"
  redis_cache_name    = "cache-prod-eastus"
  resource_group_name = "rg-data-prod"
  start_ip            = "203.0.113.0"
  end_ip              = "203.0.113.255"
}

ℹ️ The caller configures the provider, authentication, and the features {} block. This module declares neither a provider block nor any auth input.

πŸ”Œ Cross-Module Contract

Consumes

Input Type Source module
resource_group_name string terraform-azurerm-resource-group
redis_cache_name string terraform-azurerm-redis-cache

Emits

Output Description Consumed by
id Resource ID of the firewall rule references / audit

πŸ“š Example Library

1 Β· Single IP address

Set start_ip and end_ip to the same value to allow exactly one client address.

module "single_host" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-firewall-rule.git?ref=v1.0.0"

  name                = "bastion"
  redis_cache_name    = "cache-prod-eastus"
  resource_group_name = "rg-data-prod"
  start_ip            = "203.0.113.10"
  end_ip              = "203.0.113.10"
}
2 Β· A contiguous range
module "app_tier" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-firewall-rule.git?ref=v1.0.0"

  name                = "apptier"
  redis_cache_name    = "cache-prod-eastus"
  resource_group_name = "rg-data-prod"
  start_ip            = "203.0.113.32"
  end_ip              = "203.0.113.63"
}
3 Β· An office CIDR expressed as start/end

A /24 (203.0.113.0/24) becomes the first and last usable host of the block.

module "office" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-firewall-rule.git?ref=v1.0.0"

  name                = "hq_office"
  redis_cache_name    = "cache-prod-eastus"
  resource_group_name = "rg-data-prod"
  start_ip            = "203.0.113.0"
  end_ip              = "203.0.113.255"
}

ℹ️ Rule names accept letters, numbers, and underscores; hyphens are not permitted in a Redis firewall rule name.

4 Β· Naming rules per environment

Keep the rule name stable across environments and vary only the cache it targets.

module "office_dev" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-firewall-rule.git?ref=v1.0.0"

  name                = "office"
  redis_cache_name    = "cache-dev-eastus"
  resource_group_name = "rg-data-dev"
  start_ip            = "203.0.113.0"
  end_ip              = "203.0.113.255"
}

⚠️ Changing name, redis_cache_name, or resource_group_name forces replacement. Renaming for cosmetic reasons briefly removes the allowed range.

5 Β· for_each over multiple ranges

Key each range by a stable label so adding or removing one never re-indexes the rest.

locals {
  redis_ranges = {
    office   = { start = "203.0.113.0", end = "203.0.113.255" }
    datastage = { start = "198.51.100.16", end = "198.51.100.31" }
    monitor  = { start = "198.51.100.200", end = "198.51.100.200" }
  }
}

module "redis_ranges" {
  source   = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-firewall-rule.git?ref=v1.0.0"
  for_each = local.redis_ranges

  name                = each.key
  redis_cache_name    = "cache-prod-eastus"
  resource_group_name = "rg-data-prod"
  start_ip            = each.value.start
  end_ip              = each.value.end
}
6 Β· Explicit per-operation timeouts
module "slow_range" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-firewall-rule.git?ref=v1.0.0"

  name                = "batch"
  redis_cache_name    = "cache-prod-eastus"
  resource_group_name = "rg-data-prod"
  start_ip            = "198.51.100.0"
  end_ip              = "198.51.100.63"

  timeouts = {
    create = "30m"
    delete = "30m"
  }
}

ℹ️ Leave timeouts unset (null) to use provider defaults; supply only the operations you need.

7 Β· A CI runner egress IP
module "ci_runner" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-firewall-rule.git?ref=v1.0.0"

  name                = "ci_runner"
  redis_cache_name    = "cache-build-eastus2"
  resource_group_name = "rg-build"
  start_ip            = "20.42.0.10"
  end_ip              = "20.42.0.10"
}
8 Β· A partner data feed range
module "partner_feed" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-firewall-rule.git?ref=v1.0.0"

  name                = "partner_feed"
  redis_cache_name    = "cache-prod-centralus"
  resource_group_name = "rg-data-prod"
  start_ip            = "192.0.2.128"
  end_ip              = "192.0.2.159"
}

⚠️ Firewall rules broaden reachability. Grant the narrowest range the partner actually egresses from.

9 Β· Reading the emitted id
module "office" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-firewall-rule.git?ref=v1.0.0"

  name                = "office"
  redis_cache_name    = "cache-prod-eastus"
  resource_group_name = "rg-data-prod"
  start_ip            = "203.0.113.0"
  end_ip              = "203.0.113.255"
}

output "office_rule_id" {
  value = module.office.id
}
10 Β· Non-overlapping ranges on one cache

Split allowed space into disjoint, self-documenting rules rather than one broad block.

module "corp_west" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-firewall-rule.git?ref=v1.0.0"

  name                = "corp_west"
  redis_cache_name    = "cache-prod-westus2"
  resource_group_name = "rg-data-prod"
  start_ip            = "203.0.113.0"
  end_ip              = "203.0.113.127"
}

module "corp_east" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-firewall-rule.git?ref=v1.0.0"

  name                = "corp_east"
  redis_cache_name    = "cache-prod-westus2"
  resource_group_name = "rg-data-prod"
  start_ip            = "203.0.113.128"
  end_ip              = "203.0.113.255"
}

⚠️ Keep ranges disjoint. Overlapping rules are accepted but obscure which record grants a given address and complicate later removal.

11 Β· Ranges sourced from a variable
variable "allowed_ranges" {
  type = map(object({
    start_ip = string
    end_ip   = string
  }))
  default = {}
}

module "redis_ranges" {
  source   = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-firewall-rule.git?ref=v1.0.0"
  for_each = var.allowed_ranges

  name                = each.key
  redis_cache_name    = "cache-prod-eastus"
  resource_group_name = "rg-data-prod"
  start_ip            = each.value.start_ip
  end_ip              = each.value.end_ip
}

πŸ’‘ An empty default means the empty call grants no public reachability at all β€” ranges are opt-in.

12 Β· Consuming a cache module output directly
module "cache" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-cache.git?ref=v1.0.0"
  location                     = "eastus2"
  name                         = "cache-example"
  resource_group_name          = "rg-example"
}

module "office" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-firewall-rule.git?ref=v1.0.0"

  name                = "office"
  redis_cache_name    = module.cache.name
  resource_group_name = "rg-data-prod"
  start_ip            = "203.0.113.0"
  end_ip              = "203.0.113.255"
}
13 Β· πŸ—οΈ End-to-end composition

Wire a resource group and a Redis cache into a keyed set of firewall rules.

provider "azurerm" {
  features {}
}

module "rg" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"

  name     = "rg-data-prod"
  location = "eastus"
}

module "cache" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-cache.git?ref=v1.0.0"

  name                = "cache-prod-eastus"
  resource_group_name = module.rg.name
  location            = module.rg.location
  # The cache module keeps public network access off by default; enable it here
  # only if these firewall rules are meant to take effect over the public endpoint.
  public_network_access_enabled = true
}

locals {
  redis_ranges = {
    office  = { start = "203.0.113.0", end = "203.0.113.255" }
    ci      = { start = "20.42.0.10", end = "20.42.0.10" }
    partner = { start = "192.0.2.128", end = "192.0.2.159" }
  }
}

module "redis_ranges" {
  source   = "git::https://github.com/microsoftexpert/terraform-azurerm-redis-firewall-rule.git?ref=v1.0.0"
  for_each = local.redis_ranges

  name                = each.key
  redis_cache_name    = module.cache.name
  resource_group_name = module.rg.name
  start_ip            = each.value.start
  end_ip              = each.value.end
}

output "redis_rule_ids" {
  value = { for k, m in module.redis_ranges : k => m.id }
}

πŸ”’ The firewall rules above only take effect because the cache enables public network access. For a private-endpoint topology, keep public access off and manage reachability through the network layer instead β€” the rules become inert.

πŸ“₯ Inputs

Identity (required, force-new)

  • name β€” name of the firewall rule.
  • redis_cache_name β€” name of the target Redis cache.
  • resource_group_name β€” resource group holding the cache.

Allowed range (required, updatable)

  • start_ip β€” lowest IP in the allowed range.
  • end_ip β€” highest IP in the allowed range (equal to start_ip for a single address).

Universal tail

  • timeouts β€” optional per-operation timeouts. This resource does not support tags, so the tail is timeouts only.
Full input schemas
variable "name" {
  description = "Name of the firewall rule. Immutable β€” changing it forces a new resource."
  type        = string
}

variable "redis_cache_name" {
  description = "Name of the Redis cache. Immutable β€” changing it forces a new resource."
  type        = string
}

variable "resource_group_name" {
  description = "Name of the resource group holding the Redis cache. Immutable β€” changing it forces a new resource."
  type        = string
}

variable "start_ip" {
  description = "Lowest IP address included in the allowed range."
  type        = string
}

variable "end_ip" {
  description = "Highest IP address included in the allowed range. Use the same value as start_ip for a single address."
  type        = string
}

variable "timeouts" {
  type = object({
    create = optional(string)
    read   = optional(string)
    update = optional(string)
    delete = optional(string)
  })
  default     = null
  description = "Optional per-operation timeouts (Go duration strings such as \"30m\"). Leave null for provider defaults."
}

🧾 Outputs

Output Description Kind
id Resource ID of the Redis firewall rule Passthrough
name Name of the firewall rule, as created Passthrough
redis_cache_name Name of the parent Redis cache, re-read from the created resource's ID by the provider rather than echoed from the input Passthrough
resource_group_name Name of the resource group holding the parent Redis cache, re-read from the created resource's ID Passthrough
start_ip Lowest address permitted by this rule, inclusive, as stored by Azure Passthrough
end_ip Highest address permitted by this rule, inclusive, as stored by Azure Passthrough
permits_the_entire_internet True when this rule spans 0.0.0.0 through 255.255.255.255, which permits every host on the internet to reach the cache's public endpoint Passthrough
permits_a_single_address True when both endpoints are the same address, which is the narrowest rule this resource can express and the form to prefer Passthrough
permitted_address_count Number of addresses this rule permits, inclusive of both endpoints Passthrough
range_is_entirely_private_rfc1918 True when the whole permitted range sits inside one of the RFC 1918 private blocks -- 10.0.0.0/8, 172.16.0.0/12 or 192.168.0.0/16 Passthrough
secure_default_cannot_apply_to_this_resource Constant true, and the reason this module reports rather than defaults Constant
parent_cache_public_endpoint_is_open_by_default Constant true Constant
rule_has_no_effect_while_public_access_is_disabled Constant true Constant
widening_the_range_updates_in_place Constant true, and it is a review hazard Constant
renaming_the_rule_replaces_it Constant true Constant
resource_supports_no_tags Constant true Constant
range_overlap_with_sibling_rules_is_unchecked Constant true Constant

🧠 Architecture Notes

  • Force-new identity fields. name, redis_cache_name, and resource_group_name are immutable. Terraform destroys and recreates the rule when any of them changes, which briefly removes the allowed range during the replace.
  • In-place range edits. start_ip and end_ip update without replacement, so tuning a range is a clean, non-disruptive change.
  • Public-access-only applicability. A Redis firewall rule governs the cache's public endpoint. When the cache disables public network access, the rule is created and stored but never consulted β€” do not rely on it as the sole reachability control in a hardened topology.
  • Private-endpoint alternative. For caches without public access, connect over a private endpoint and manage exposure through subnet, NSG, and private-DNS wiring rather than firewall rules.
  • for_each key stability. When managing many ranges, key the map by a meaningful, stable label. Re-keying causes churn; changing only the range values does not.
  • features {} dependence. The azurerm provider will not initialize without a caller-side provider "azurerm" { features {} } block. This module intentionally ships no provider block.

🧱 Design Principles

Principle How this module applies it
Least exposure Firewall rules only matter under public network access; the module adds no reachability on its own and grants nothing unless a caller supplies a range. Prefer private endpoints for hardened caches.
Opt-in breadth Each allowed range is an explicit, independently keyed record β€” the caller types every address that becomes reachable.
No tags azurerm_redis_firewall_rule does not support tags, so the universal tail is timeouts only.
Single primary resource The module owns only the firewall rule and references the cache by name, keeping ownership boundaries clean.

πŸš€ Runbook

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; a human applies from CI after review.

πŸ§ͺ Testing

  • terraform init -backend=false resolves the provider without configuring a backend.
  • terraform validate confirms the type contract: all five required inputs are strings and timeouts matches its object shape.
  • terraform fmt -check enforces canonical formatting.
  • What only plan/apply exercises: whether the target cache exists, whether public access is enabled, and whether the IP range is well-formed against the live API. Validation cannot see live tenant state.

πŸ’¬ Example Output

$ terraform output
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-data-prod/providers/Microsoft.Cache/Redis/cache-prod-eastus/firewallRules/office"

For a for_each deployment:

$ terraform output redis_rule_ids
{
  "ci"      = ".../Redis/cache-prod-eastus/firewallRules/ci"
  "office"  = ".../Redis/cache-prod-eastus/firewallRules/office"
  "partner" = ".../Redis/cache-prod-eastus/firewallRules/partner"
}

πŸ” Troubleshooting

Symptom Cause Fix
Rule applies cleanly but clients still cannot connect Cache has public network access disabled, so the rule is never consulted. Enable public access on the cache if you intend to use firewall rules, or connect over a private endpoint and drop the rule.
Two rules seem to conflict Overlapping IP ranges across separate rules. Redefine the ranges to be disjoint so each address is granted by exactly one rule.
Every plan shows the rule being replaced name, redis_cache_name, or resource_group_name is changing (these are force-new). Keep the identity fields stable; edit only start_ip / end_ip, which update in place.
provider "azurerm": features block must be specified The caller's root module is missing features {}. Add provider "azurerm" { features {} } to the root β€” the module never declares one.
Apply rejects the rule name Name contains hyphens or other unsupported characters. Use letters, numbers, and underscores only in the rule name.

πŸ”— Related Docs

  • Terraform Registry β€” azurerm_redis_firewall_rule
  • Terraform Registry β€” azurerm_redis_cache
  • Sibling module β€” terraform-azurerm-redis-cache
  • Sibling module β€” terraform-azurerm-private-endpoint (private-access alternative)
  • This module's SCOPE.md

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