diff --git a/.github/workflows/terraform-lint.yml b/.github/workflows/terraform-lint.yml
new file mode 100644
index 0000000..867ca0a
--- /dev/null
+++ b/.github/workflows/terraform-lint.yml
@@ -0,0 +1,69 @@
+name: Terraform Lint
+
+on:
+ push:
+ branches: [ main, develop ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ tflint:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Cache plugin dir
+ uses: actions/cache@v3
+ with:
+ path: ~/.tflint.d/plugins
+ key: ${{ hashFiles('.tflint.hcl') }}
+
+ - uses: terraform-linters/setup-tflint@v4
+ with:
+ tflint_version: v0.50.3
+
+ - name: Show version
+ run: tflint --version
+
+ - name: Init TFLint
+ run: tflint --init
+
+ - name: Run TFLint
+ run: tflint --format compact
+
+ - name: Run TFLint on modules
+ run: |
+ find . -name "*.tf" -exec dirname {} \; | sort -u | while read dir; do
+ echo "Linting $dir"
+ (cd "$dir" && tflint --format compact)
+ done
+
+ terraform-fmt:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: hashicorp/setup-terraform@v3
+ with:
+ terraform_version: "1.8.0"
+
+ - name: Terraform Format Check
+ run: terraform fmt -check -recursive -diff
+
+ terraform-validate:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: hashicorp/setup-terraform@v3
+ with:
+ terraform_version: "1.8.0"
+
+ - name: Terraform Init
+ run: terraform init -backend=false
+
+ - name: Terraform Validate
+ run: terraform validate
diff --git a/.tflint.hcl b/.tflint.hcl
new file mode 100644
index 0000000..a7b6284
--- /dev/null
+++ b/.tflint.hcl
@@ -0,0 +1,53 @@
+plugin "terraform" {
+ enabled = true
+ preset = "recommended"
+}
+
+plugin "azurerm" {
+ enabled = true
+ version = "0.29.0"
+ source = "github.com/terraform-linters/tflint-ruleset-azurerm"
+}
+
+rule "terraform_deprecated_interpolation" {
+ enabled = true
+}
+
+rule "terraform_unused_declarations" {
+ enabled = true
+}
+
+rule "terraform_comment_syntax" {
+ enabled = true
+}
+
+rule "terraform_documented_outputs" {
+ enabled = true
+}
+
+rule "terraform_documented_variables" {
+ enabled = true
+}
+
+rule "terraform_typed_variables" {
+ enabled = true
+}
+
+rule "terraform_module_pinned_source" {
+ enabled = true
+}
+
+rule "terraform_naming_convention" {
+ enabled = true
+ format = "snake_case"
+}
+
+rule "terraform_standard_module_structure" {
+ enabled = true
+}
+
+# Azure-specific rules
+rule "azurerm_resource_missing_tags" {
+ enabled = true
+ tags = ["Name", "Environment"]
+}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..445eef6
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,5 @@
+{
+ "recommendations": [
+ "hashicorp.terraform"
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..4d87e2f
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,27 @@
+{
+ "terraform.validation.enableEnhancedValidation": true,
+ "terraform.languageServer.enable": true,
+ "terraform.codelens.referenceCount": true,
+ "files.associations": {
+ "*.tf": "terraform",
+ "*.tfvars": "terraform",
+ "*.tfvars.example": "terraform",
+ ".tflint.hcl": "hcl"
+ },
+ "editor.formatOnSave": true,
+ "editor.codeActionsOnSave": {
+ "source.formatDocument": "explicit"
+ },
+ "[terraform]": {
+ "editor.defaultFormatter": "hashicorp.terraform",
+ "editor.formatOnSave": true,
+ "editor.insertSpaces": true,
+ "editor.tabSize": 2
+ },
+ "[hcl]": {
+ "editor.defaultFormatter": "hashicorp.terraform",
+ "editor.formatOnSave": true,
+ "editor.insertSpaces": true,
+ "editor.tabSize": 2
+ }
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..25a452f
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,121 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "Terraform: Format",
+ "type": "shell",
+ "command": "terraform",
+ "args": [
+ "fmt",
+ "-recursive"
+ ],
+ "group": "build",
+ "presentation": {
+ "echo": true,
+ "reveal": "always",
+ "focus": false,
+ "panel": "shared"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Terraform: Validate",
+ "type": "shell",
+ "command": "terraform",
+ "args": [
+ "validate"
+ ],
+ "group": "test",
+ "presentation": {
+ "echo": true,
+ "reveal": "always",
+ "focus": false,
+ "panel": "shared"
+ },
+ "dependsOn": "Terraform: Init (no backend)"
+ },
+ {
+ "label": "Terraform: Init (no backend)",
+ "type": "shell",
+ "command": "terraform",
+ "args": [
+ "init",
+ "-backend=false"
+ ],
+ "group": "build",
+ "presentation": {
+ "echo": true,
+ "reveal": "silent",
+ "focus": false,
+ "panel": "shared"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "TFLint: Initialize",
+ "type": "shell",
+ "command": "tflint",
+ "args": [
+ "--init"
+ ],
+ "group": "build",
+ "presentation": {
+ "echo": true,
+ "reveal": "silent",
+ "focus": false,
+ "panel": "shared"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "TFLint: Run",
+ "type": "shell",
+ "command": "tflint",
+ "args": [
+ "--format",
+ "compact"
+ ],
+ "group": "test",
+ "presentation": {
+ "echo": true,
+ "reveal": "always",
+ "focus": false,
+ "panel": "shared"
+ },
+ "dependsOn": "TFLint: Initialize",
+ "problemMatcher": [
+ {
+ "owner": "tflint",
+ "fileLocation": "relative",
+ "pattern": {
+ "regexp": "^([^:]+):(\\d+):(\\d+):\\s+(Error|Warning|Notice):\\s+(.*)$",
+ "file": 1,
+ "line": 2,
+ "column": 3,
+ "severity": 4,
+ "message": 5
+ }
+ }
+ ]
+ },
+ {
+ "label": "Terraform: Full Lint Check",
+ "dependsOrder": "sequence",
+ "dependsOn": [
+ "Terraform: Format",
+ "Terraform: Validate",
+ "TFLint: Run"
+ ],
+ "group": {
+ "kind": "test",
+ "isDefault": true
+ },
+ "presentation": {
+ "echo": true,
+ "reveal": "always",
+ "focus": false,
+ "panel": "shared"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 7a7e0e0..d9a49d6 100644
--- a/README.md
+++ b/README.md
@@ -2,32 +2,120 @@
This Terraform module provisions an Azure VNET that is required for XC Cloud Azure VNET Site. It creates a VNET, subnets, route tables, and network security groups with whitelisted IP ranges.
+> **Note**: This module is developed and maintained by the [F5 DevCentral](https://github.com/f5devcentral) community. You can use this module as an example for your own development projects.
+
+## Features
+
+- Creates Azure Virtual Network with configurable CIDR blocks
+- Supports multiple subnet types: outside, inside, and local subnets
+- Configurable network security groups with customizable rules
+- Route tables for inside subnets with BGP route propagation control
+- Multi-availability zone support
+- Flexible resource group management (create new or use existing)
+- Comprehensive tagging support
+
## Requirements
-| Name | Version |
-|------|---------|
-| [terraform](https://github.com/hashicorp/terraform) | >= 1.0 |
-| [azurerm](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs) | >= 4.4.0 |
-| [random](https://registry.terraform.io/providers/hashicorp/random/latest/docs) | >= 3.0 |
+| Name | Version |
+| ------------------------------------------------------------------------------------------------------------------- | --------- |
+| [terraform](https://github.com/hashicorp/terraform) | >= 1.4.0 |
+| [azurerm](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs) | >= 4.39.0 |
+| [random](https://registry.terraform.io/providers/hashicorp/random/latest/docs) | >= 3.7.2 |
## Usage
+### Basic Example
-To use this module and create a Azure VNET configured for XC Cloud Azure VNET Site, include the following code in your Terraform configuration:
+To use this module and create an Azure VNET configured for XC Cloud Azure VNET Site, include the following code in your Terraform configuration:
```hcl
module "azure_vnet" {
- source = "f5devcentral/azure-vnet-site-networking/volterra"
- version = "0.0.3"
+ source = "f5devcentral/azure-vnet-site-networking/xc"
+ version = "0.0.4"
resource_group_name = "azure_terraform_demo"
- location = var.azure_rg_location
- vnet_cidr = "192.168.0.0/16"
- outside_subnets = ["192.168.11.0/24", "192.168.12.0/24", "192.168.13.0/24"]
- inside_subnets = ["192.168.21.0/24", "192.168.22.0/24", "192.168.23.0/24"]
+ location = "East US"
+ vnet_cidr = "192.168.0.0/16"
+ outside_subnets = ["192.168.11.0/24", "192.168.12.0/24", "192.168.13.0/24"]
+ inside_subnets = ["192.168.21.0/24", "192.168.22.0/24", "192.168.23.0/24"]
+ local_subnets = ["192.168.31.0/24", "192.168.32.0/24", "192.168.33.0/24"]
+
+ tags = {
+ Environment = "production"
+ Project = "xc-azure-site"
+ }
+}
+```
+
+### Using Existing Resource Group
+
+```hcl
+module "azure_vnet" {
+ source = "f5devcentral/azure-vnet-site-networking/xc"
+ version = "0.0.4"
+
+ create_resource_group = false
+ resource_group_name = "existing-rg"
+ location = "West US 2"
+ name = "my-xc-vnet"
+ vnet_cidr = "10.0.0.0/16"
+ outside_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
+ inside_subnets = ["10.0.11.0/24", "10.0.12.0/24"]
}
```
+## Inputs
+
+| Name | Description | Type | Default |
+| ----------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------- | ------- |
+| bgp_route_propagation_enabled | Whether to enable BGP route propagation. | `bool` | `true` |
+| create_inside_route_table | Whether to create an inside route table for the inside subnets. | `bool` | `true` |
+| create_inside_security_group | Whether to create an inside security group. | `bool` | `true` |
+| create_outside_security_group | Whether to create an outside security group. | `bool` | `true` |
+| create_resource_group | Whether to create a new resource group for the VNET. Changing this forces a new resource to be created. | `bool` | `true` |
+| create_vnet | Whether to create a new VNET. Changing this forces a new resource to be created. | `bool` | `true` |
+| inside_subnets | Inside Subnet CIDR Blocks. | `list(string)` | `[]` |
+| local_subnets | Local Subnet CIDR Blocks. | `list(string)` | `[]` |
+| location | The location/region where the VNET will be created. Changing this forces a new resource to be created. | `string` | n/a |
+| name | The name of the VNET. | `string` | `null` |
+| outside_subnets | Outside Subnet CIDR Blocks. | `list(string)` | `[]` |
+| resource_group_name | The name of the resource group in which to create the VNET. Changing this forces a new resource to be created. | `string` | n/a |
+| tags | A map of tags to add to all resources. | `map(string)` | `{}` |
+| vnet_cidr | The Primary IPv4 block cannot be modified. All subnets prefixes in this VNET must be part of this CIDR block. | `string` | `null` |
+
+## Outputs
+
+| Name | Description |
+| --------------------------- | --------------------------------------- |
+| az_names | Availability zones. |
+| inside_route_table_ids | The IDs of the inside route tables. |
+| inside_route_table_names | The names of the inside route tables. |
+| inside_security_group_name | The Name of the inside security group. |
+| inside_subnet_ids | The IDs of the inside subnets. |
+| inside_subnet_names | The Names of the inside subnets. |
+| local_subnet_ids | The IDs of the local subnets. |
+| local_subnet_names | The Names of the local subnets. |
+| location | The location/region where the VNET. |
+| outside_security_group_name | The Name of the outside security group. |
+| outside_subnet_ids | The IDs of the outside subnets. |
+| outside_subnet_names | The Names of the outside subnets. |
+| resource_group_name | The name of the resource group. |
+| vnet_cidr | The CIDR block of the VNET. |
+| vnet_id | The ID of the VNET. |
+| vnet_name | The name of the VNET. |
+
+## Examples
+
+For detailed examples, please check the `examples/` directory:
+
+- [vnet-for-ingress-egress-gw](./examples/vnet-for-ingress-egress-gw/) - Example for creating a VNET for XC Cloud Ingress/Egress Gateway
+
+## Modules
+
+This module includes the following sub-modules:
+
+- `azure-nsg-rules` - Creates and manages Network Security Group rules for the outside subnets
+
## Contributing
Contributions to this module are welcome! Please see the contribution guidelines for more information.
diff --git a/examples/vnet-for-ingress-egress-gw/README.md b/examples/vnet-for-ingress-egress-gw/README.md
index 2582356..94ab107 100644
--- a/examples/vnet-for-ingress-egress-gw/README.md
+++ b/examples/vnet-for-ingress-egress-gw/README.md
@@ -1,16 +1,207 @@
-# Azure VNET for F5 XC Cloud Ingress/Egress GW Azure VNET Site
+# Azure VNET for F5 XC Cloud Ingress/Egress Gateway Site
-The following example will create an Azure VNET with 3 AZs, 3 subnets per AZ, and a security group. The security groups will be configured with whitelisted IP ranges for the XC Cloud Ingress/Egress GW Azure VNET Site.
+This example demonstrates how to create an Azure Virtual Network (VNET) optimized for F5 Distributed Cloud (XC) Ingress/Egress Gateway deployments. It provisions a complete networking foundation with multi-availability zone support and pre-configured security groups.
+
+## Architecture Overview
+
+This example creates:
+
+- **Azure VNET**: A virtual network with configurable CIDR blocks
+- **Outside Subnets**: 3 subnets across availability zones for external traffic
+- **Inside Subnets**: 3 subnets across availability zones for internal traffic
+- **Network Security Groups**: Pre-configured with F5 XC IP ranges
+- **Multi-AZ Design**: Distributed across 3 availability zones for high availability
+
+## Prerequisites
+
+1. **Azure Subscription**: Active Azure subscription with appropriate permissions
+2. **Terraform**: Version >= 1.4.0
+3. **Azure CLI**: Configured with appropriate credentials (optional if using service principal)
+4. **Existing Resource Group**: This example uses an existing resource group
+
+## Configuration
+
+### 1. Authentication
+
+This example supports two authentication methods:
+
+#### Option A: Azure CLI (Recommended for development)
+```bash
+az login
+az account set --subscription "your-subscription-id"
+```
+
+#### Option B: Service Principal (Recommended for CI/CD)
+Configure the variables in `terraform.tfvars`:
+```hcl
+azure_subscription_id = "your-subscription-id"
+azure_subscription_tenant_id = "your-tenant-id"
+azure_service_principal_appid = "your-app-id"
+azure_service_principal_password = "your-password"
+```
+
+### 2. Variables Configuration
+
+Copy the example variables file:
+```bash
+cp terraform.tfvars.example terraform.tfvars
+```
+
+Edit `terraform.tfvars` with your values:
+```hcl
+azure_subscription_id = "12345678-1234-1234-1234-123456789abc"
+azure_subscription_tenant_id = "87654321-4321-4321-4321-cba987654321"
+azure_rg_location = "West US 2"
+```
+
+## Usage
+
+### Quick Start
+
+1. **Initialize Terraform**:
+ ```bash
+ terraform init
+ ```
+
+2. **Review the plan**:
+ ```bash
+ terraform plan
+ ```
+
+3. **Apply the configuration**:
+ ```bash
+ terraform apply
+ ```
+
+4. **Clean up** (when done):
+ ```bash
+ terraform destroy
+ ```
+
+### Example Configuration
```hcl
module "azure_vnet" {
source = "../.."
create_resource_group = false
- resource_group_name = "azure_terraform_demo"
- location = var.azure_rg_location
- vnet_cidr = "192.168.0.0/16"
- outside_subnets = ["192.168.11.0/24", "192.168.12.0/24", "192.168.13.0/24"]
- inside_subnets = ["192.168.21.0/24", "192.168.22.0/24", "192.168.23.0/24"]
+ resource_group_name = "azure_terraform_demo"
+ location = var.azure_rg_location
+ vnet_cidr = "192.168.0.0/16"
+ outside_subnets = ["192.168.11.0/24", "192.168.12.0/24", "192.168.13.0/24"]
+ inside_subnets = ["192.168.21.0/24", "192.168.22.0/24", "192.168.23.0/24"]
+
+ tags = {
+ Environment = "demo"
+ Project = "f5-xc-gateway"
+ Example = "ingress-egress-gw"
+ }
+}
+```
+
+## Network Design
+
+### Subnets
+
+| Subnet Type | CIDR Blocks | Purpose | Availability Zones |
+| ----------- | ----------------------------------------------------- | -------------------------------- | ------------------ |
+| Outside | 192.168.11.0/24
192.168.12.0/24
192.168.13.0/24 | External traffic, load balancers | AZ1, AZ2, AZ3 |
+| Inside | 192.168.21.0/24
192.168.22.0/24
192.168.23.0/24 | Internal applications, workloads | AZ1, AZ2, AZ3 |
+
+### Security Groups
+
+The module automatically creates Network Security Groups with:
+- **F5 XC Regional Access**: Allow traffic from F5 Distributed Cloud IP ranges
+- **VNET Communication**: Allow internal communication between subnets
+- **Default Deny**: Block all other inbound traffic
+
+## Outputs
+
+After deployment, the following outputs are available:
+
+| Output | Description |
+| --------------------- | ----------------------------------------- |
+| `vnet_name` | Name of the created VNET |
+| `vnet_id` | Azure resource ID of the VNET |
+| `vnet_cidr` | CIDR block of the VNET |
+| `outside_subnet_ids` | List of outside subnet IDs |
+| `inside_subnet_ids` | List of inside subnet IDs |
+| `location` | Azure region where resources were created |
+| `resource_group_name` | Resource group name |
+
+### Accessing Outputs
+
+```bash
+# Get all outputs
+terraform output
+
+# Get specific output
+terraform output vnet_name
+terraform output outside_subnet_ids
+```
+
+## Customization
+
+### Different Regions
+
+To deploy in a different Azure region:
+
+```hcl
+variable "azure_rg_location" {
+ type = string
+ default = "East US" # Change to your preferred region
}
-```
\ No newline at end of file
+```
+
+### Custom CIDR Blocks
+
+To use different IP ranges:
+
+```hcl
+vnet_cidr = "10.0.0.0/16"
+outside_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
+inside_subnets = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
+```
+
+### Additional Subnets
+
+To add local subnets:
+
+```hcl
+local_subnets = ["192.168.31.0/24", "192.168.32.0/24", "192.168.33.0/24"]
+```
+
+## Validation
+
+### Verify Resources
+
+1. **Check VNET creation**:
+ ```bash
+ az network vnet list --resource-group azure_terraform_demo --output table
+ ```
+
+2. **Verify subnets**:
+ ```bash
+ az network vnet subnet list --resource-group azure_terraform_demo --vnet-name --output table
+ ```
+
+3. **Check security groups**:
+ ```bash
+ az network nsg list --resource-group azure_terraform_demo --output table
+ ```
+
+### Test Connectivity
+
+After deployment, you can:
+1. Deploy F5 XC Site instances in the outside subnets
+2. Deploy application workloads in the inside subnets
+3. Verify connectivity according to your F5 XC site configuration
+
+## Next Steps
+
+After deploying this networking foundation:
+
+1. **Deploy F5 XC Site**: Use the created subnets for your F5 Distributed Cloud site
+2. **Configure Applications**: Deploy your applications in the inside subnets
+3. **Set up Monitoring**: Configure Azure Monitor and Network Watcher
+4. **Implement Additional Security**: Add custom NSG rules if needed
diff --git a/examples/vnet-for-ingress-egress-gw/main.tf b/examples/vnet-for-ingress-egress-gw/main.tf
index 2b1b8bd..e299309 100644
--- a/examples/vnet-for-ingress-egress-gw/main.tf
+++ b/examples/vnet-for-ingress-egress-gw/main.tf
@@ -11,9 +11,9 @@ module "azure_vnet" {
source = "../.."
create_resource_group = false
- resource_group_name = "azure_terraform_demo"
- location = var.azure_rg_location
- vnet_cidr = "192.168.0.0/16"
- outside_subnets = ["192.168.11.0/24", "192.168.12.0/24", "192.168.13.0/24"]
- inside_subnets = ["192.168.21.0/24", "192.168.22.0/24", "192.168.23.0/24"]
+ resource_group_name = "azure_terraform_demo"
+ location = var.azure_rg_location
+ vnet_cidr = "192.168.0.0/16"
+ outside_subnets = ["192.168.11.0/24", "192.168.12.0/24", "192.168.13.0/24"]
+ inside_subnets = ["192.168.21.0/24", "192.168.22.0/24", "192.168.23.0/24"]
}
diff --git a/examples/vnet-for-ingress-egress-gw/versions.tf b/examples/vnet-for-ingress-egress-gw/versions.tf
index 33b28df..bbbcfae 100644
--- a/examples/vnet-for-ingress-egress-gw/versions.tf
+++ b/examples/vnet-for-ingress-egress-gw/versions.tf
@@ -1,18 +1,14 @@
terraform {
required_version = ">= 1.4.0"
-
+
required_providers {
random = {
source = "hashicorp/random"
- version = ">=3.0"
- }
- volterra = {
- source = "volterraedge/volterra"
- version = "=0.11.26"
+ version = ">=3.7.2"
}
azurerm = {
source = "hashicorp/azurerm"
- version = ">=4.4.0"
+ version = ">=4.39.0"
}
}
}
diff --git a/main.tf b/main.tf
index af289ae..7fa13e2 100644
--- a/main.tf
+++ b/main.tf
@@ -6,82 +6,23 @@ resource "random_string" "random" {
}
locals {
- create_outside_subnet = local.outside_subnets_len > 0
- create_local_subnet = local.local_subnets_len > 0
- create_inside_subnet = local.inside_subnets_len > 0
- outside_subnets_len = length(var.outside_subnets)
- local_subnets_len = length(var.local_subnets)
- inside_subnets_len = length(var.inside_subnets)
- resource_group_name = var.resource_group_name
- create_resource_group = var.create_resource_group
- vnet_name = var.name != null ? var.name : format("%s-vnet", random_string.random.result)
- vnet_id = var.create_vnet ? azurerm_virtual_network.this[0].id : data.azurerm_virtual_network.this[0].id
- vnet_cidr = var.create_vnet ? tolist(azurerm_virtual_network.this[0].address_space)[0] : tolist(data.azurerm_virtual_network.this[0].address_space)[0]
- existing_vnet_name = var.create_vnet ? azurerm_virtual_network.this[0].name : local.vnet_name
- az_names = slice(["1","2","3"], 0, max(local.outside_subnets_len, local.local_subnets_len, local.inside_subnets_len))
- americas_tcp_80_443_range = [
- "5.182.215.0/25",
- "84.54.61.0/25",
- "23.158.32.0/25",
- "84.54.62.0/25",
- "185.94.142.0/25",
- "185.94.143.0/25",
- "159.60.190.0/24",
- "159.60.168.0/24",
- ]
- europe_tcp_80_443_range = [
- "5.182.213.0/25",
- "5.182.212.0/25",
- "5.182.213.128/25",
- "5.182.214.0/25",
- "84.54.60.0/25",
- "185.56.154.0/25",
- "159.60.160.0/24",
- "159.60.162.0/24",
- "159.60.188.0/24",
- ]
- asia_tcp_80_443_range = [
- "103.135.56.0/25",
- "103.135.57.0/25",
- "103.135.56.128/25",
- "103.135.59.0/25",
- "103.135.58.128/25",
- "103.135.58.0/25",
- "159.60.189.0/24",
- "159.60.166.0/24",
- "159.60.164.0/24",
- ]
- americas_udp_4500_range = [
- "5.182.215.0/25",
- "84.54.61.0/25",
- "23.158.32.0/25",
- "84.54.62.0/25",
- "185.94.142.0/25",
- "185.94.143.0/25",
- "159.60.190.0/24",
- ]
- europe_udp_4500_range = [
- "5.182.213.0/25",
- "5.182.212.0/25",
- "5.182.213.128/25",
- "5.182.214.0/25",
- "84.54.60.0/25",
- "185.56.154.0/25",
- "159.60.160.0/24",
- "159.60.162.0/24",
- "159.60.188.0/24",
- ]
- asia_udp_4500_range = [
- "103.135.56.0/25",
- "103.135.57.0/25",
- "103.135.56.128/25",
- "103.135.59.0/25",
- "103.135.58.128/25",
- "103.135.58.0/25",
- "159.60.189.0/24",
- "159.60.166.0/24",
- "159.60.164.0/24",
- ]
+ create_outside_subnet = local.outside_subnets_len > 0
+ create_local_subnet = local.local_subnets_len > 0
+ create_inside_subnet = local.inside_subnets_len > 0
+ outside_subnets_len = length(var.outside_subnets)
+ local_subnets_len = length(var.local_subnets)
+ inside_subnets_len = length(var.inside_subnets)
+ resource_group_name = var.resource_group_name
+ vnet_name = var.name != null ? var.name : format("%s-vnet", random_string.random.result)
+ vnet_id = var.create_vnet ? azurerm_virtual_network.this[0].id : data.azurerm_virtual_network.this[0].id
+ vnet_cidr = var.create_vnet ? tolist(azurerm_virtual_network.this[0].address_space)[0] : tolist(data.azurerm_virtual_network.this[0].address_space)[0]
+ existing_vnet_name = var.create_vnet ? azurerm_virtual_network.this[0].name : local.vnet_name
+ az_names = slice(["1", "2", "3"], 0, max(local.outside_subnets_len, local.local_subnets_len, local.inside_subnets_len))
+
+ common_tags = merge(var.tags, {
+ Name = local.vnet_name
+ Environment = lookup(var.tags, "Environment", "dev")
+ })
}
resource "azurerm_resource_group" "this" {
@@ -89,6 +30,12 @@ resource "azurerm_resource_group" "this" {
name = local.resource_group_name
location = var.location
+
+ tags = local.common_tags
+
+ lifecycle {
+ ignore_changes = [tags]
+ }
}
data "azurerm_virtual_network" "this" {
@@ -101,17 +48,21 @@ data "azurerm_virtual_network" "this" {
resource "azurerm_virtual_network" "this" {
count = var.create_vnet ? 1 : 0
- name = local.vnet_name
- address_space = [var.vnet_cidr]
+ name = local.vnet_name
+ address_space = [var.vnet_cidr]
location = var.location
resource_group_name = local.resource_group_name
- tags = var.tags
+ tags = local.common_tags
+
+ lifecycle {
+ ignore_changes = [tags]
+ }
- depends_on = [
+ depends_on = [
azurerm_resource_group.this
- ]
+ ]
}
resource "azurerm_subnet" "local" {
@@ -122,7 +73,7 @@ resource "azurerm_subnet" "local" {
virtual_network_name = local.existing_vnet_name
address_prefixes = [element(var.local_subnets, count.index)]
- depends_on = [
+ depends_on = [
azurerm_virtual_network.this
]
}
@@ -135,7 +86,7 @@ resource "azurerm_subnet" "outside" {
virtual_network_name = local.existing_vnet_name
address_prefixes = [element(var.outside_subnets, count.index)]
- depends_on = [
+ depends_on = [
azurerm_virtual_network.this
]
}
@@ -148,7 +99,7 @@ resource "azurerm_subnet" "inside" {
virtual_network_name = local.existing_vnet_name
address_prefixes = [element(var.inside_subnets, count.index)]
- depends_on = [
+ depends_on = [
azurerm_virtual_network.this
]
}
@@ -160,9 +111,13 @@ resource "azurerm_network_security_group" "outside" {
location = var.location
resource_group_name = local.resource_group_name
- tags = var.tags
+ tags = local.common_tags
- depends_on = [
+ lifecycle {
+ ignore_changes = [tags]
+ }
+
+ depends_on = [
azurerm_resource_group.this,
azurerm_virtual_network.this
]
@@ -178,7 +133,7 @@ module "outside_nsg_rules" {
outside_subnets = var.outside_subnets
local_subnets = var.local_subnets
- depends_on = [
+ depends_on = [
azurerm_resource_group.this,
azurerm_network_security_group.outside
]
@@ -190,10 +145,14 @@ resource "azurerm_network_security_group" "inside" {
name = format("%s-inside-nsg", local.vnet_name)
location = var.location
resource_group_name = local.resource_group_name
-
- tags = var.tags
- depends_on = [
+ tags = local.common_tags
+
+ lifecycle {
+ ignore_changes = [tags]
+ }
+
+ depends_on = [
azurerm_virtual_network.this
]
}
@@ -227,9 +186,13 @@ resource "azurerm_route_table" "inside" {
resource_group_name = local.resource_group_name
bgp_route_propagation_enabled = var.bgp_route_propagation_enabled
- tags = var.tags
+ tags = local.common_tags
- depends_on = [
+ lifecycle {
+ ignore_changes = [tags]
+ }
+
+ depends_on = [
azurerm_virtual_network.this
]
}
diff --git a/modules/azure-nsg-rules/README.md b/modules/azure-nsg-rules/README.md
new file mode 100644
index 0000000..d5ecddc
--- /dev/null
+++ b/modules/azure-nsg-rules/README.md
@@ -0,0 +1,82 @@
+# Azure NSG Rules Module for F5 Distributed Cloud
+
+This submodule creates Azure Network Security Group (NSG) rules specifically designed for F5 Distributed Cloud (XC) connectivity. It applies pre-configured security rules that allow traffic from F5 XC regional data centers while maintaining security best practices.
+
+## Purpose
+
+This module is designed to be used internally by the parent `azure-vnet-site-networking` module. It creates the necessary NSG rules to allow F5 Distributed Cloud traffic while blocking unauthorized access.
+
+## Security Rules Created
+
+The module creates the following types of security rules:
+
+### F5 XC Regional Traffic Rules
+- **Americas TCP 80/443**: Allows HTTP/HTTPS traffic from F5 XC Americas region
+- **Europe TCP 80/443**: Allows HTTP/HTTPS traffic from F5 XC Europe region
+- **Asia TCP 80/443**: Allows HTTP/HTTPS traffic from F5 XC Asia region
+- **Americas UDP 4500**: Allows IPSec/IKE traffic from F5 XC Americas region
+- **Europe UDP 4500**: Allows IPSec/IKE traffic from F5 XC Europe region
+- **Asia UDP 4500**: Allows IPSec/IKE traffic from F5 XC Asia region
+
+### Default Traffic Rules
+- **VNET Communication**: Allows communication within the VNET subnets
+- **Load Balancer Access**: Allows Azure Load Balancer health probes
+- **Deny All Other**: Denies all other inbound traffic (default deny)
+
+## Requirements
+
+| Name | Version |
+| --------- | --------- |
+| terraform | >= 1.4.0 |
+| azurerm | >= 4.39.0 |
+
+## Usage
+
+This module is typically used internally by the parent module, but can be used standalone:
+
+```hcl
+module "nsg_rules" {
+ source = "./modules/azure-nsg-rules"
+
+ resource_group_name = "my-resource-group"
+ network_security_group_name = "my-nsg"
+ outside_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
+ local_subnets = ["10.0.10.0/24", "10.0.11.0/24"]
+ create_udp_security_group_rules = true
+ priority_start = 120
+}
+```
+
+## Inputs
+
+| Name | Description | Type | Default |
+| ------------------------------- | -------------------------------------------------------- | -------------- | ------- |
+| resource_group_name | The name of the resource group containing the NSG | `string` | n/a |
+| network_security_group_name | The name of the network security group to add rules to | `string` | n/a |
+| outside_subnets | A list of CIDR blocks for the outside subnets | `list(string)` | `[]` |
+| local_subnets | A list of CIDR blocks for the local subnets | `list(string)` | `[]` |
+| create_udp_security_group_rules | Whether to create UDP security group rules for IPSec/IKE | `bool` | `true` |
+| priority_start | The starting priority for the security rules | `number` | `120` |
+| create_default_rules | Whether to create default VNET and deny rules | `bool` | `true` |
+
+## Outputs
+
+This module does not expose any outputs.
+
+## F5 XC IP Ranges
+
+The module includes F5 Distributed Cloud IP ranges for all regions (Americas, Europe, and Asia) for both TCP (80/443) and UDP (4500) traffic.
+
+For the current and complete list of F5 Distributed Cloud IP ranges, please refer to the official documentation:
+
+**[F5 Distributed Cloud Network Reference](https://docs.cloud.f5.com/docs-v2/platform/reference/network-cloud-ref)**
+
+> **Note**: F5 XC IP ranges may change over time. This module contains the IP ranges that were current at the time of release. For the most up-to-date ranges, always consult the official F5 documentation above.
+
+## Maintenance
+
+This module requires periodic updates to maintain current F5 XC IP ranges. Monitor F5 documentation for any changes to regional IP ranges.
+
+## License
+
+This module is licensed under the Apache 2.0 License.
diff --git a/modules/azure-nsg-rules/main.tf b/modules/azure-nsg-rules/main.tf
index 608daaa..a7b7b93 100644
--- a/modules/azure-nsg-rules/main.tf
+++ b/modules/azure-nsg-rules/main.tf
@@ -1,8 +1,3 @@
-data "azurerm_network_security_group" "this" {
- name = var.network_security_group_name
- resource_group_name = var.resource_group_name
-}
-
locals {
americas_tcp_80_443_range = [
"5.182.215.0/25",
@@ -13,6 +8,9 @@ locals {
"185.94.143.0/25",
"159.60.190.0/24",
"159.60.168.0/24",
+ "159.60.180.0/24",
+ "159.60.174.0/24",
+ "159.60.176.0/24",
]
europe_tcp_80_443_range = [
"5.182.213.0/25",
@@ -24,6 +22,8 @@ locals {
"159.60.160.0/24",
"159.60.162.0/24",
"159.60.188.0/24",
+ "159.60.182.0/24",
+ "159.60.178.0/24",
]
asia_tcp_80_443_range = [
"103.135.56.0/25",
@@ -35,6 +35,11 @@ locals {
"159.60.189.0/24",
"159.60.166.0/24",
"159.60.164.0/24",
+ "159.60.170.0/24",
+ "159.60.172.0/24",
+ "159.60.191.0/24",
+ "159.60.184.0/24",
+ "159.60.186.0/24",
]
americas_udp_4500_range = [
"5.182.215.0/25",
@@ -44,6 +49,10 @@ locals {
"185.94.142.0/25",
"185.94.143.0/25",
"159.60.190.0/24",
+ "159.60.168.0/24",
+ "159.60.180.0/24",
+ "159.60.174.0/24",
+ "159.60.176.0/24",
]
europe_udp_4500_range = [
"5.182.213.0/25",
@@ -55,6 +64,8 @@ locals {
"159.60.160.0/24",
"159.60.162.0/24",
"159.60.188.0/24",
+ "159.60.182.0/24",
+ "159.60.178.0/24",
]
asia_udp_4500_range = [
"103.135.56.0/25",
@@ -66,6 +77,10 @@ locals {
"159.60.189.0/24",
"159.60.166.0/24",
"159.60.164.0/24",
+ "159.60.170.0/24",
+ "159.60.172.0/24",
+ "159.60.184.0/24",
+ "159.60.186.0/24",
]
}
@@ -79,8 +94,8 @@ resource "azurerm_network_security_rule" "americas_tcp_80_443_range" {
destination_port_ranges = ["80", "443"]
source_address_prefixes = local.americas_tcp_80_443_range
destination_address_prefixes = setunion(var.outside_subnets, var.local_subnets)
- resource_group_name = var.resource_group_name
- network_security_group_name = var.network_security_group_name
+ resource_group_name = var.resource_group_name
+ network_security_group_name = var.network_security_group_name
}
resource "azurerm_network_security_rule" "europe_tcp_80_443_range" {
@@ -93,8 +108,8 @@ resource "azurerm_network_security_rule" "europe_tcp_80_443_range" {
destination_port_ranges = ["80", "443"]
source_address_prefixes = local.europe_tcp_80_443_range
destination_address_prefixes = setunion(var.outside_subnets, var.local_subnets)
- resource_group_name = var.resource_group_name
- network_security_group_name = var.network_security_group_name
+ resource_group_name = var.resource_group_name
+ network_security_group_name = var.network_security_group_name
}
resource "azurerm_network_security_rule" "asia_tcp_80_443_range" {
@@ -107,12 +122,12 @@ resource "azurerm_network_security_rule" "asia_tcp_80_443_range" {
destination_port_ranges = ["80", "443"]
source_address_prefixes = local.asia_tcp_80_443_range
destination_address_prefixes = setunion(var.outside_subnets, var.local_subnets)
- resource_group_name = var.resource_group_name
- network_security_group_name = var.network_security_group_name
+ resource_group_name = var.resource_group_name
+ network_security_group_name = var.network_security_group_name
}
resource "azurerm_network_security_rule" "americas_udp_4500_range" {
- count = var.create_udp_security_group_rules ? 1 : 0
+ count = var.create_udp_security_group_rules ? 1 : 0
name = "AllowAmericasUdp-4500"
priority = sum([var.priority_start, 3])
@@ -123,12 +138,12 @@ resource "azurerm_network_security_rule" "americas_udp_4500_range" {
destination_port_range = "4500"
source_address_prefixes = local.americas_udp_4500_range
destination_address_prefixes = setunion(var.outside_subnets, var.local_subnets)
- resource_group_name = var.resource_group_name
- network_security_group_name = var.network_security_group_name
+ resource_group_name = var.resource_group_name
+ network_security_group_name = var.network_security_group_name
}
resource "azurerm_network_security_rule" "europe_udp_4500_range" {
- count = var.create_udp_security_group_rules ? 1 : 0
+ count = var.create_udp_security_group_rules ? 1 : 0
name = "AllowEuropeUdp-4500"
priority = sum([var.priority_start, 4])
@@ -139,12 +154,12 @@ resource "azurerm_network_security_rule" "europe_udp_4500_range" {
destination_port_range = "4500"
source_address_prefixes = local.europe_udp_4500_range
destination_address_prefixes = setunion(var.outside_subnets, var.local_subnets)
- resource_group_name = var.resource_group_name
- network_security_group_name = var.network_security_group_name
+ resource_group_name = var.resource_group_name
+ network_security_group_name = var.network_security_group_name
}
resource "azurerm_network_security_rule" "asia_udp_4500_range" {
- count = var.create_udp_security_group_rules ? 1 : 0
+ count = var.create_udp_security_group_rules ? 1 : 0
name = "AllowAsiaUdp-4500"
priority = sum([var.priority_start, 5])
@@ -155,56 +170,56 @@ resource "azurerm_network_security_rule" "asia_udp_4500_range" {
destination_port_range = "4500"
source_address_prefixes = local.asia_udp_4500_range
destination_address_prefixes = setunion(var.outside_subnets, var.local_subnets)
- resource_group_name = var.resource_group_name
- network_security_group_name = var.network_security_group_name
+ resource_group_name = var.resource_group_name
+ network_security_group_name = var.network_security_group_name
}
## Default rules to prevent traffic from being passed through the default "140 - all" rule created by XC Cloud.
resource "azurerm_network_security_rule" "vnet" {
- count = var.create_default_rules ? 1 : 0
-
- name = "AllowVnetInBound"
- priority = sum([var.priority_start, 10])
- direction = "Inbound"
- access = "Allow"
- protocol = "*"
- source_port_range = "*"
- destination_port_range = "*"
- source_address_prefix = "VirtualNetwork"
- destination_address_prefix = "VirtualNetwork"
+ count = var.create_default_rules ? 1 : 0
+
+ name = "AllowVnetInBound"
+ priority = sum([var.priority_start, 10])
+ direction = "Inbound"
+ access = "Allow"
+ protocol = "*"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "VirtualNetwork"
+ destination_address_prefix = "VirtualNetwork"
resource_group_name = var.resource_group_name
network_security_group_name = var.network_security_group_name
}
resource "azurerm_network_security_rule" "load_balancer" {
- count = var.create_default_rules ? 1 : 0
-
- name = "AllowAzureLoadBalancerInBound"
- priority = sum([var.priority_start, 11])
- direction = "Inbound"
- access = "Allow"
- protocol = "*"
- source_port_range = "*"
- destination_port_range = "*"
- source_address_prefix = "AzureLoadBalancer"
- destination_address_prefix = "*"
+ count = var.create_default_rules ? 1 : 0
+
+ name = "AllowAzureLoadBalancerInBound"
+ priority = sum([var.priority_start, 11])
+ direction = "Inbound"
+ access = "Allow"
+ protocol = "*"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "AzureLoadBalancer"
+ destination_address_prefix = "*"
resource_group_name = var.resource_group_name
network_security_group_name = var.network_security_group_name
}
resource "azurerm_network_security_rule" "deny_other" {
- count = var.create_default_rules ? 1 : 0
-
- name = "DenyOther"
- priority = sum([var.priority_start, 12])
- direction = "Inbound"
- access = "Deny"
- protocol = "*"
- source_port_range = "*"
- destination_port_range = "*"
- source_address_prefix = "*"
- destination_address_prefix = "*"
+ count = var.create_default_rules ? 1 : 0
+
+ name = "DenyOther"
+ priority = sum([var.priority_start, 12])
+ direction = "Inbound"
+ access = "Deny"
+ protocol = "*"
+ source_port_range = "*"
+ destination_port_range = "*"
+ source_address_prefix = "*"
+ destination_address_prefix = "*"
resource_group_name = var.resource_group_name
network_security_group_name = var.network_security_group_name
}
\ No newline at end of file
diff --git a/modules/azure-nsg-rules/variables.tf b/modules/azure-nsg-rules/variables.tf
index a46d8e8..c653af8 100644
--- a/modules/azure-nsg-rules/variables.tf
+++ b/modules/azure-nsg-rules/variables.tf
@@ -1,13 +1,13 @@
variable "resource_group_name" {
description = "The name of the resource group in which to create the VNET. Changing this forces a new resource to be created."
- type = string
- nullable = false
+ type = string
+ nullable = false
}
variable "network_security_group_name" {
description = "The name of the network security group in which to create the rules. Changing this forces a new resource to be created."
- type = string
- nullable = false
+ type = string
+ nullable = false
}
variable "create_udp_security_group_rules" {
diff --git a/modules/azure-nsg-rules/versions.tf b/modules/azure-nsg-rules/versions.tf
index ee64e34..08fdd79 100644
--- a/modules/azure-nsg-rules/versions.tf
+++ b/modules/azure-nsg-rules/versions.tf
@@ -1,10 +1,10 @@
terraform {
required_version = ">= 1.4.0"
-
+
required_providers {
azurerm = {
source = "hashicorp/azurerm"
- version = ">= 4.4.0"
+ version = ">= 4.39.0"
}
}
}
diff --git a/outputs.tf b/outputs.tf
index db9f658..25304ca 100644
--- a/outputs.tf
+++ b/outputs.tf
@@ -24,56 +24,56 @@ output "vnet_cidr" {
}
output "outside_subnet_ids" {
- value = azurerm_subnet.outside.*.id
+ value = azurerm_subnet.outside[*].id
description = "The IDs of the outside subnets."
}
output "inside_subnet_ids" {
- value = azurerm_subnet.inside.*.id
+ value = azurerm_subnet.inside[*].id
description = "The IDs of the inside subnets."
}
output "local_subnet_ids" {
- value = azurerm_subnet.local.*.id
+ value = azurerm_subnet.local[*].id
description = "The IDs of the local subnets."
}
output "outside_subnet_names" {
- value = azurerm_subnet.outside.*.name
+ value = azurerm_subnet.outside[*].name
description = "The Names of the outside subnets."
}
output "inside_subnet_names" {
- value = azurerm_subnet.inside.*.name
+ value = azurerm_subnet.inside[*].name
description = "The Names of the inside subnets."
}
output "local_subnet_names" {
- value = azurerm_subnet.local.*.name
+ value = azurerm_subnet.local[*].name
description = "The Names of the local subnets."
}
output "inside_route_table_ids" {
- value = azurerm_route_table.inside.*.id
+ value = azurerm_route_table.inside[*].id
description = "The IDs of the inside route tables."
}
output "inside_route_table_names" {
- value = azurerm_route_table.inside.*.name
+ value = azurerm_route_table.inside[*].name
description = "The names of the inside route tables."
}
output "outside_security_group_name" {
- value = azurerm_network_security_group.outside.*.name
+ value = azurerm_network_security_group.outside[*].name
description = "The Name of the outside security group."
}
output "inside_security_group_name" {
- value = azurerm_network_security_group.inside.*.name
+ value = azurerm_network_security_group.inside[*].name
description = "The Name of the inside security group."
}
output "az_names" {
value = local.az_names
description = "Availability zones."
-}
\ No newline at end of file
+}
diff --git a/variables.tf b/variables.tf
index 121a369..c1fe002 100644
--- a/variables.tf
+++ b/variables.tf
@@ -7,13 +7,13 @@ variable "name" {
variable "location" {
description = "The location/region where the VNET will be created. Changing this forces a new resource to be created."
type = string
- nullable = false
+ nullable = false
}
variable "resource_group_name" {
description = "The name of the resource group in which to create the VNET. Changing this forces a new resource to be created."
- type = string
- nullable = false
+ type = string
+ nullable = false
}
variable "create_vnet" {
@@ -28,12 +28,6 @@ variable "create_resource_group" {
default = true
}
-variable "create_outside_route_table" {
- description = "Whether to create an outside route table for the outside or local subnets."
- type = bool
- default = true
-}
-
variable "create_outside_security_group" {
description = "Whether to create an outside security group."
type = bool
@@ -52,12 +46,6 @@ variable "create_inside_security_group" {
default = true
}
-variable "create_udp_security_group_rules" {
- description = "Whether to create UDP security group rules."
- type = bool
- default = true
-}
-
variable "tags" {
description = "A map of tags to add to all resources."
type = map(string)
@@ -92,4 +80,4 @@ variable "bgp_route_propagation_enabled" {
description = "Whether to enable BGP route propagation."
type = bool
default = true
-}
\ No newline at end of file
+}
diff --git a/versions.tf b/versions.tf
index 910b32f..75c6156 100644
--- a/versions.tf
+++ b/versions.tf
@@ -1,14 +1,14 @@
terraform {
required_version = ">= 1.4.0"
-
+
required_providers {
random = {
source = "hashicorp/random"
- version = ">=3.0.0"
+ version = ">=3.7.2"
}
azurerm = {
source = "hashicorp/azurerm"
- version = ">= 4.4.0"
+ version = ">= 4.39.0"
}
}
}