diff --git a/datastore/redis/azure_managed_redis/1.0/README.md b/datastore/redis/azure_managed_redis/1.0/README.md new file mode 100644 index 0000000..5a4a3b2 --- /dev/null +++ b/datastore/redis/azure_managed_redis/1.0/README.md @@ -0,0 +1,100 @@ +# Azure Managed Redis Module v1.0 + +## Overview + +This module provisions Azure Managed Redis, Microsoft's next-generation Redis service built on Redis 7.4+. It provides enhanced performance, enterprise security features, and automatic high availability. All connections are secured via Private Endpoint for VNet-only access. + +## Environment as Dimension + +The module is environment-aware through: +- Resource naming includes environment unique identifier for isolation +- Cloud tags from environment applied to all resources +- Private DNS zone and endpoint are unique per environment + +## Resources Created + +- Azure Managed Redis instance (Balanced tier SKUs) +- Private Endpoint for secure VNet access +- Private DNS Zone (`privatelink.redisenterprise.cache.azure.net`) +- Private DNS Zone VNet Link +- Private DNS A Record for endpoint resolution + +## Network Integration + +This module consumes network resources from an Azure network module: +- Automatically uses database subnet when available for better isolation +- Falls back to private subnet if database subnet is not configured +- Public network access is disabled by default +- All traffic flows through Private Endpoint within the VNet + +## Key Features + +### Size Options + +| Size | SKU | Memory | Use Case | +|------|-----|--------|----------| +| small | Balanced_B1 | ~1GB | Development/Testing | +| medium | Balanced_B5 | ~6GB | Small production workloads | +| large | Balanced_B50 | ~30GB | High-traffic applications | +| xlarge | Balanced_B100 | ~60GB | Enterprise workloads | + +### Clustering Modes + +- **standard** (OSS Cluster): Recommended for new applications, uses native Redis clustering +- **legacy_compatible** (Enterprise Cluster): For migration from older Redis deployments + +### Security Features + +- TLS encryption enforced (Encrypted client protocol) +- Public network access disabled +- Private Endpoint for VNet-only connectivity +- Optional password authentication (enabled by default) +- Private DNS resolution within VNet + +## Differences from Azure Cache for Redis + +Azure Managed Redis is the next-generation offering with key differences: + +| Feature | Azure Managed Redis | Azure Cache for Redis | +|---------|--------------------|-----------------------| +| Redis Version | 7.4+ | Up to 6.x | +| Port | 10000 | 6380 (TLS) | +| SKU Types | Balanced, Memory Optimized, Compute Optimized | Basic, Standard, Premium | +| Network | Private Endpoint only | VNet injection or Public | +| Availability | Built-in HA | Depends on tier | + +## Security Considerations + +- All connections use TLS encryption +- Public network access is disabled +- Access limited to VNet through Private Endpoint +- Access keys marked as sensitive in outputs +- Private DNS ensures hostname resolution stays within VNet + +## Subnet Selection Logic + +The module intelligently selects subnets: +1. **Database Subnet Priority**: Uses general database subnet if available +2. **Private Subnet Fallback**: Uses first private subnet otherwise +3. **Private Endpoint**: Deployed in selected subnet for secure access + +## Connection Details + +Applications connect using: +- **Host**: `{cache-name}.privatelink.redisenterprise.cache.azure.net` +- **Port**: `10000` +- **Protocol**: `rediss://` (TLS-encrypted) + +## Terraform State Import + +To import existing Azure Managed Redis resources into Terraform state: + +```bash +# Import the Redis instance +terraform import azurerm_managed_redis.main /subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.Cache/redisenterprise/{cache-name} + +# Import the Private Endpoint +terraform import azurerm_private_endpoint.redis /subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.Network/privateEndpoints/{endpoint-name} +``` + +Note: This imports resource configuration only, not the cached data. diff --git a/datastore/redis/azure_managed_redis/1.0/facets.yaml b/datastore/redis/azure_managed_redis/1.0/facets.yaml new file mode 100644 index 0000000..b72d9f3 --- /dev/null +++ b/datastore/redis/azure_managed_redis/1.0/facets.yaml @@ -0,0 +1,104 @@ +intent: redis +flavor: azure_managed_redis +version: '1.0' +clouds: + - azure +description: | + Azure Managed Redis - Next-generation Redis on Azure with Redis 7.4+, + enhanced performance, and enterprise security. Uses Private Endpoint + for secure VNet-only access. + +spec: + title: Azure Managed Redis Configuration + description: Configure Azure Managed Redis with developer-friendly settings + type: object + properties: + size: + type: string + title: Cache Size + description: Select the appropriate cache size for your workload + enum: + - small + - medium + - large + - xlarge + default: small + x-ui-help: | + Size guidelines based on workload: + - small: Development/Testing (~1GB, Balanced_B1) + - medium: Small production workloads (~6GB, Balanced_B5) + - large: High-traffic applications (~30GB, Balanced_B50) + - xlarge: Enterprise workloads (~60GB, Balanced_B100) + advanced: + type: object + title: Advanced Settings + x-ui-toggle: true + description: Advanced Redis configuration options + properties: + clustering_mode: + type: string + title: Clustering Mode + description: Redis clustering policy for data distribution + enum: + - standard + - legacy_compatible + default: standard + x-ui-help: | + - standard: OSS Cluster mode (recommended for new applications) + - legacy_compatible: Enterprise Cluster mode (for migration from older Redis) + enable_password_auth: + type: boolean + title: Enable Password Authentication + description: Enable access key authentication for Redis connections + default: true + required: + - size + x-ui-order: + - size + - advanced + +inputs: + azure_provider: + type: '@facets/azure_cloud_account' + optional: false + displayName: Azure Cloud Account + description: Azure cloud provider configuration + providers: + - azurerm + network_details: + type: '@facets/azure-network-details' + optional: false + displayName: Azure Network Details + description: Azure VNet and subnet configuration for Private Endpoint + +outputs: + default: + type: '@facets/redis' + title: Azure Managed Redis + secrets: + - interfaces.cluster.auth_token + - interfaces.cluster.connection_string + +imports: + - name: redis_resource_id + resource_address: azurerm_managed_redis.main + required: false + - name: private_endpoint_resource_id + resource_address: azurerm_private_endpoint.redis + required: false + +iac: + validated_files: + - main.tf + - locals.tf + - variables.tf + - outputs.tf + - versions.tf + +sample: + kind: redis + flavor: azure_managed_redis + version: '1.0' + disabled: true + spec: + size: small diff --git a/datastore/redis/azure_managed_redis/1.0/locals.tf b/datastore/redis/azure_managed_redis/1.0/locals.tf new file mode 100644 index 0000000..0733558 --- /dev/null +++ b/datastore/redis/azure_managed_redis/1.0/locals.tf @@ -0,0 +1,65 @@ +locals { + # Spec configuration + spec = lookup(var.instance, "spec", {}) + + # Tags + metadata = lookup(var.instance, "metadata", {}) + user_defined_tags = lookup(local.metadata, "tags", {}) + tags = merge(local.user_defined_tags, var.environment.cloud_tags) + + # Network configuration from @facets/azure-network-details + resource_group_name = var.inputs.network_details.attributes.resource_group_name + region = var.inputs.network_details.attributes.region + + # Use database subnet if available, otherwise fall back to private subnet + subnet_id = coalesce( + var.inputs.network_details.attributes.database_general_subnet_id, + var.inputs.network_details.attributes.private_subnet_ids[0] + ) + + # VNet ID for Private DNS Zone linking + vnet_id = var.inputs.network_details.attributes.vnet_id + + # Resource naming - inline approach (same as azure_cache_custom) + cache_name = "${var.instance_name}-${var.environment.unique_name}" + + # Size to SKU mapping - Developer-friendly names to Azure Managed Redis SKUs + # Balanced SKUs provide good mix of compute and memory + size_to_sku = { + small = "Balanced_B1" # ~1GB, suitable for dev/test + medium = "Balanced_B5" # ~6GB, small production + large = "Balanced_B50" # ~30GB, high-traffic + xlarge = "Balanced_B100" # ~60GB, enterprise + } + + size = lookup(local.spec, "size", "small") + sku_name = lookup(local.size_to_sku, local.size, "Balanced_B1") + + # Advanced settings + advanced = lookup(local.spec, "advanced", {}) + + # Clustering mode mapping + clustering_mode_to_policy = { + standard = "OSSCluster" + legacy_compatible = "EnterpriseCluster" + } + + clustering_mode = lookup(local.advanced, "clustering_mode", "standard") + clustering_policy = lookup(local.clustering_mode_to_policy, local.clustering_mode, "OSSCluster") + + # Authentication + enable_password_auth = lookup(local.advanced, "enable_password_auth", true) + access_keys_authentication_enabled = local.enable_password_auth + + # Protocol configuration + client_protocol = "Encrypted" # Always use TLS + + # Eviction policy + eviction_policy = "VolatileLRU" # Standard Redis eviction for volatile keys + + # Azure Managed Redis port (different from legacy Redis Cache) + redis_port = 10000 + + # Username for connection strings + username = "default" +} diff --git a/datastore/redis/azure_managed_redis/1.0/main.tf b/datastore/redis/azure_managed_redis/1.0/main.tf new file mode 100644 index 0000000..c55f4e9 --- /dev/null +++ b/datastore/redis/azure_managed_redis/1.0/main.tf @@ -0,0 +1,74 @@ +# Azure Managed Redis (Redis Enterprise) +resource "azurerm_managed_redis" "main" { + name = local.cache_name + location = local.region + resource_group_name = local.resource_group_name + sku_name = local.sku_name + + # VNet-only access - disable public network access + public_network_access = "Disabled" + + tags = local.tags + + # Database configuration + default_database { + client_protocol = local.client_protocol + clustering_policy = local.clustering_policy + eviction_policy = local.eviction_policy + access_keys_authentication_enabled = local.access_keys_authentication_enabled + } + + lifecycle { + prevent_destroy = false + ignore_changes = [ + # Immutable attributes that cannot be changed after creation + name, + location, + sku_name, + ] + } +} + +# Private Endpoint for secure VNet access +resource "azurerm_private_endpoint" "redis" { + name = "${local.cache_name}-pe" + location = local.region + resource_group_name = local.resource_group_name + subnet_id = local.subnet_id + + private_service_connection { + name = "${local.cache_name}-psc" + private_connection_resource_id = azurerm_managed_redis.main.id + is_manual_connection = false + subresource_names = ["redisCache"] + } + + tags = local.tags +} + +# Private DNS Zone for Private Endpoint resolution +resource "azurerm_private_dns_zone" "redis" { + name = "privatelink.redisenterprise.cache.azure.net" + resource_group_name = local.resource_group_name + + tags = local.tags +} + +# Link Private DNS Zone to VNet +resource "azurerm_private_dns_zone_virtual_network_link" "redis" { + name = "${local.cache_name}-dns-link" + resource_group_name = local.resource_group_name + private_dns_zone_name = azurerm_private_dns_zone.redis.name + virtual_network_id = local.vnet_id + + tags = local.tags +} + +# DNS A record for Private Endpoint +resource "azurerm_private_dns_a_record" "redis" { + name = local.cache_name + zone_name = azurerm_private_dns_zone.redis.name + resource_group_name = local.resource_group_name + ttl = 300 + records = [azurerm_private_endpoint.redis.private_service_connection[0].private_ip_address] +} diff --git a/datastore/redis/azure_managed_redis/1.0/outputs.tf b/datastore/redis/azure_managed_redis/1.0/outputs.tf new file mode 100644 index 0000000..9b94e62 --- /dev/null +++ b/datastore/redis/azure_managed_redis/1.0/outputs.tf @@ -0,0 +1,38 @@ +locals { + # Private Endpoint IP for connections + private_ip = azurerm_private_endpoint.redis.private_service_connection[0].private_ip_address + + # Hostname that resolves via Private DNS to the private IP + # Clients in the VNet can use this hostname directly + private_hostname = "${local.cache_name}.privatelink.redisenterprise.cache.azure.net" + + output_attributes = { + # Cache details + cache_name = azurerm_managed_redis.main.name + cache_id = azurerm_managed_redis.main.id + + # Private Endpoint details + private_endpoint_ip = local.private_ip + private_endpoint_id = azurerm_private_endpoint.redis.id + private_endpoint_name = azurerm_private_endpoint.redis.name + + # Private DNS details + private_dns_zone_id = azurerm_private_dns_zone.redis.id + private_dns_zone_name = azurerm_private_dns_zone.redis.name + private_hostname = local.private_hostname + + # Original public hostname (for reference, not accessible) + public_hostname = azurerm_managed_redis.main.hostname + } + + output_interfaces = { + cluster = { + host = local.private_hostname + port = tostring(local.redis_port) + endpoint = "${local.private_hostname}:${local.redis_port}" + auth_token = azurerm_managed_redis.main.default_database[0].primary_access_key + connection_string = "rediss://:${azurerm_managed_redis.main.default_database[0].primary_access_key}@${local.private_hostname}:${local.redis_port}" + secrets = ["auth_token", "connection_string"] + } + } +} diff --git a/datastore/redis/azure_managed_redis/1.0/variables.tf b/datastore/redis/azure_managed_redis/1.0/variables.tf new file mode 100644 index 0000000..e7a8770 --- /dev/null +++ b/datastore/redis/azure_managed_redis/1.0/variables.tf @@ -0,0 +1,71 @@ +variable "instance" { + description = "Facets instance configuration" + type = object({ + kind = string + flavor = string + version = string + spec = object({ + size = string + advanced = optional(object({ + clustering_mode = optional(string) + enable_password_auth = optional(bool) + })) + }) + metadata = optional(object({ + tags = optional(map(string)) + })) + }) + + validation { + condition = contains(["small", "medium", "large", "xlarge"], var.instance.spec.size) + error_message = "Size must be one of: small, medium, large, xlarge" + } + + validation { + condition = var.instance.spec.advanced == null || ( + var.instance.spec.advanced.clustering_mode == null || + contains(["standard", "legacy_compatible"], var.instance.spec.advanced.clustering_mode) + ) + error_message = "Clustering mode must be one of: standard, legacy_compatible" + } +} + +variable "instance_name" { + description = "Name of the Redis instance" + type = string +} + +variable "environment" { + description = "Facets environment configuration" + type = object({ + name = string + unique_name = string + cloud_tags = map(string) + }) +} + +variable "inputs" { + description = "Input dependencies from other Facets modules" + type = object({ + azure_provider = object({ + attributes = object({ + subscription_id = string + client_id = string + client_secret = string + tenant_id = string + }) + }) + network_details = object({ + attributes = object({ + resource_group_name = string + resource_group_id = string + vnet_id = string + vnet_name = string + region = string + private_subnet_ids = list(string) + database_general_subnet_id = optional(string) + database_general_subnet_name = optional(string) + }) + }) + }) +} diff --git a/datastore/redis/azure_managed_redis/1.0/versions.tf b/datastore/redis/azure_managed_redis/1.0/versions.tf new file mode 100644 index 0000000..a6b517a --- /dev/null +++ b/datastore/redis/azure_managed_redis/1.0/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">= 4.0.0" + } + } +}