diff --git a/.github/trivy/.trivyignore.yml b/.github/trivy/.trivyignore.yml index 0cbe1db..f5cf375 100644 --- a/.github/trivy/.trivyignore.yml +++ b/.github/trivy/.trivyignore.yml @@ -5,3 +5,7 @@ misconfigurations: statement: allow for legacy metadata service use - id: AVD-AWS-0107 statement: bastion host requires SSH access from configurable CIDR blocks for administrative access + - id: AVD-AZU-0047 + statement: sensor monitoring NIC requires configurable ingress CIDRs for VXLAN and health checks + - id: AVD-AZU-0051 + statement: sensor requires outbound internet access for Fleet connectivity and updates diff --git a/examples/azure/flow-sensor/README.md b/examples/azure/flow-sensor/README.md new file mode 100644 index 0000000..33adafd --- /dev/null +++ b/examples/azure/flow-sensor/README.md @@ -0,0 +1,27 @@ +# Azure VNet Flow Sensor + +VNet Flow Logs sensor deployment. + +## Usage + +```bash +cp terraform.tfvars.example terraform.tfvars +# Edit terraform.tfvars with your values + +terraform init +terraform plan +terraform apply +``` + +## What This Deploys + +- A Corelight sensor VM (single instance) with management and monitoring NICs +- A user-assigned Managed Identity with `Storage Blob Data Reader` on the flow logs storage account +- The identity is attached to the sensor VM for VNet flow log access + +## Variables + +See [`terraform.tfvars.example`](./terraform.tfvars.example) for the full list +of inputs. + +For deployment guidance, sizing, and architecture details, see the official Corelight documentation. diff --git a/examples/azure/flow-sensor/main.tf b/examples/azure/flow-sensor/main.tf new file mode 100644 index 0000000..0c78036 --- /dev/null +++ b/examples/azure/flow-sensor/main.tf @@ -0,0 +1,36 @@ +module "vnet_flow_access" { + source = "../../../modules/azure/sensor-single/submodules/vnet_flow_storage_access" + + resource_group_name = var.resource_group_name + location = var.location + storage_account_id = var.storage_account_id + identity_name = "${var.deployment_name}-vnet-flow-identity" + + tags = var.tags +} + +module "sensor" { + source = "../../../modules/azure/sensor-single" + + deployment_name = var.deployment_name + location = var.location + resource_group_name = var.resource_group_name + corelight_sensor_image_id = var.corelight_sensor_image_id + sensor_ssh_public_key = var.sensor_ssh_public_key + community_string = var.community_string + + management_subnet_id = var.management_subnet_id + monitoring_subnet_id = var.monitoring_subnet_id + + management_interface_public_ip = var.management_interface_public_ip + ssh_allow_cidrs = var.ssh_allow_cidrs + + license_key = var.license_key + fleet_token = var.fleet_token + fleet_url = var.fleet_url + fleet_server_sslname = var.fleet_server_sslname + + user_assigned_identity_ids = [module.vnet_flow_access.identity_id] + + tags = var.tags +} diff --git a/examples/azure/flow-sensor/outputs.tf b/examples/azure/flow-sensor/outputs.tf new file mode 100644 index 0000000..dcd9290 --- /dev/null +++ b/examples/azure/flow-sensor/outputs.tf @@ -0,0 +1,34 @@ +output "sensor_vm_id" { + description = "Resource ID of the flow sensor VM" + value = module.sensor.sensor_vm_id +} + +output "sensor_private_ip_address" { + description = "Private IP address of the sensor management NIC" + value = module.sensor.sensor_private_ip_address +} + +output "sensor_public_ip_address" { + description = "Public IP address of the sensor management NIC (if enabled)" + value = module.sensor.sensor_public_ip_address +} + +output "management_interface_id" { + description = "Network interface ID for management traffic" + value = module.sensor.management_interface_id +} + +output "monitoring_interface_id" { + description = "Network interface ID for monitoring traffic" + value = module.sensor.monitoring_interface_id +} + +output "vnet_flow_identity_id" { + description = "Resource ID of the managed identity for VNet flow log access" + value = module.vnet_flow_access.identity_id +} + +output "vnet_flow_identity_client_id" { + description = "Client ID of the managed identity (for sensor configuration)" + value = module.vnet_flow_access.identity_client_id +} diff --git a/examples/azure/flow-sensor/terraform.tfvars.example b/examples/azure/flow-sensor/terraform.tfvars.example new file mode 100644 index 0000000..7086516 --- /dev/null +++ b/examples/azure/flow-sensor/terraform.tfvars.example @@ -0,0 +1,42 @@ +# Azure Provider Configuration +location = "eastus" + +# Tags +tags = { + terraform = "true" + purpose = "Corelight" + environment = "" # Example: production, staging, dev + project = "" # Example: network-security +} + +# Deployment Configuration +deployment_name = "" # Example: my-flow-sensor (used as prefix for resource names) + +# Resource Group +resource_group_name = "" # Example: corelight-flow-sensor-rg + +# Sensor Configuration +corelight_sensor_image_id = "" # Request from your Corelight account executive +sensor_ssh_public_key = "" # Contents of your SSH public key file +community_string = "" # Password for sensor API access + +# Licensing (Choose ONE option below) + +# Option A: License Key +license_key = "" # Your Corelight license key + +# Option B: Fleet Integration (comment out license_key if using Fleet) +# fleet_token = "" # From Fleet UI +# fleet_url = "" # Example: https://fleet.example.com:1443/fleet/v1/internal/softsensor/websocket +# fleet_server_sslname = "" # Example: fleet.example.com + +# Network Configuration +management_subnet_id = "" # Example: /subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Network/virtualNetworks/xxx/subnets/management +monitoring_subnet_id = "" # Example: /subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Network/virtualNetworks/xxx/subnets/monitoring +management_interface_public_ip = true # Set to false if not using public IP + +# Security Configuration +ssh_allow_cidrs = [""] # Example: ["10.0.0.0/8", "192.168.1.0/24"] + +# VNet Flow Logs Configuration +storage_account_id = "" # Example: /subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Storage/storageAccounts/vnetflowlogs diff --git a/examples/azure/flow-sensor/tests/example.tftest.hcl b/examples/azure/flow-sensor/tests/example.tftest.hcl new file mode 100644 index 0000000..311a8a9 --- /dev/null +++ b/examples/azure/flow-sensor/tests/example.tftest.hcl @@ -0,0 +1,58 @@ +# Unit tests for Azure Flow Sensor Example +# These tests validate the example configuration + +mock_provider "azurerm" {} + +mock_provider "cloudinit" {} + +run "verify_default_configuration" { + command = plan + + variables { + deployment_name = "test-flow-sensor" + resource_group_name = "test-rg" + corelight_sensor_image_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Compute/images/corelight-sensor" + sensor_ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC7vbqajDRbQ3S3IGxwpasfG+JOSGX3gfpTeMKJT0hZpBA3E3t2I9oo5f3D5tNMFfxOrBdNjFqQ3NzsswFpBKST5HToAWpzuoFCHQsgqxP1JruHp+bh3faCheNBsqMSvp5bDPTN5F2JTbsOateCRYjM3DDmvxqP+xEePhXoLnJWFLhfp8jmeXlKjqKqSfvLrhQgKJmr+NFI9q9bFv3dPnfXPIn+akE37dyhLMpWnqXiPuDjRoSVisWQq/RvPuTGlOtWJGnvpqpUl3kn3aBDwN0b0+F3u5HG0gyVwpJRV9mA0Gs9E4A5iN1l/LjW+5ZjHqO8g3bRqpLniwRBjFZjG0wjI7F0gWluKuQvGT9PI6AAV9XZPH3EQ4w3O8FgPpsqJ9s7+HqXXt4DNdT7xELhE9bPJjnuKlPLPBFkVTBPoZ2r0E3BVjF51wvG8AJRJ3rUF7VPuDRPq5k6cqRpqYvOIHvjGPlU2gQ5SgusTXn3xfvcP0diP5F9I5itsOSikM2tSj0= test@example.com" + community_string = "test-community" + management_subnet_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/management" + monitoring_subnet_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/monitoring" + license_key = "test-license-key" + storage_account_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Storage/storageAccounts/testflowlogs" + } +} + +run "verify_with_fleet_integration" { + command = plan + + variables { + deployment_name = "test-flow-sensor" + resource_group_name = "test-rg" + corelight_sensor_image_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Compute/images/corelight-sensor" + sensor_ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC7vbqajDRbQ3S3IGxwpasfG+JOSGX3gfpTeMKJT0hZpBA3E3t2I9oo5f3D5tNMFfxOrBdNjFqQ3NzsswFpBKST5HToAWpzuoFCHQsgqxP1JruHp+bh3faCheNBsqMSvp5bDPTN5F2JTbsOateCRYjM3DDmvxqP+xEePhXoLnJWFLhfp8jmeXlKjqKqSfvLrhQgKJmr+NFI9q9bFv3dPnfXPIn+akE37dyhLMpWnqXiPuDjRoSVisWQq/RvPuTGlOtWJGnvpqpUl3kn3aBDwN0b0+F3u5HG0gyVwpJRV9mA0Gs9E4A5iN1l/LjW+5ZjHqO8g3bRqpLniwRBjFZjG0wjI7F0gWluKuQvGT9PI6AAV9XZPH3EQ4w3O8FgPpsqJ9s7+HqXXt4DNdT7xELhE9bPJjnuKlPLPBFkVTBPoZ2r0E3BVjF51wvG8AJRJ3rUF7VPuDRPq5k6cqRpqYvOIHvjGPlU2gQ5SgusTXn3xfvcP0diP5F9I5itsOSikM2tSj0= test@example.com" + community_string = "test-community" + management_subnet_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/management" + monitoring_subnet_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/monitoring" + fleet_token = "test-fleet-token" + fleet_url = "https://fleet.example.com:1443/fleet/v1/internal/softsensor/websocket" + fleet_server_sslname = "fleet.example.com" + storage_account_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Storage/storageAccounts/testflowlogs" + } +} + +run "verify_with_ssh_restrictions" { + command = plan + + variables { + deployment_name = "test-flow-sensor" + resource_group_name = "test-rg" + corelight_sensor_image_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Compute/images/corelight-sensor" + sensor_ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC7vbqajDRbQ3S3IGxwpasfG+JOSGX3gfpTeMKJT0hZpBA3E3t2I9oo5f3D5tNMFfxOrBdNjFqQ3NzsswFpBKST5HToAWpzuoFCHQsgqxP1JruHp+bh3faCheNBsqMSvp5bDPTN5F2JTbsOateCRYjM3DDmvxqP+xEePhXoLnJWFLhfp8jmeXlKjqKqSfvLrhQgKJmr+NFI9q9bFv3dPnfXPIn+akE37dyhLMpWnqXiPuDjRoSVisWQq/RvPuTGlOtWJGnvpqpUl3kn3aBDwN0b0+F3u5HG0gyVwpJRV9mA0Gs9E4A5iN1l/LjW+5ZjHqO8g3bRqpLniwRBjFZjG0wjI7F0gWluKuQvGT9PI6AAV9XZPH3EQ4w3O8FgPpsqJ9s7+HqXXt4DNdT7xELhE9bPJjnuKlPLPBFkVTBPoZ2r0E3BVjF51wvG8AJRJ3rUF7VPuDRPq5k6cqRpqYvOIHvjGPlU2gQ5SgusTXn3xfvcP0diP5F9I5itsOSikM2tSj0= test@example.com" + community_string = "test-community" + management_subnet_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/management" + monitoring_subnet_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/test-vnet/subnets/monitoring" + license_key = "test-license-key" + storage_account_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Storage/storageAccounts/testflowlogs" + ssh_allow_cidrs = ["10.0.0.0/8", "192.168.1.0/24"] + management_interface_public_ip = true + } +} diff --git a/examples/azure/flow-sensor/variables.tf b/examples/azure/flow-sensor/variables.tf new file mode 100644 index 0000000..c801967 --- /dev/null +++ b/examples/azure/flow-sensor/variables.tf @@ -0,0 +1,99 @@ +variable "deployment_name" { + description = "Name prefix for all resources (used to avoid naming conflicts)" + type = string + default = "corelight-vnet-flow-sensor" +} + +variable "location" { + description = "Azure region where resources will be deployed" + type = string + default = "eastus" +} + +variable "resource_group_name" { + description = "The resource group where all resources will be deployed" + type = string +} + +variable "corelight_sensor_image_id" { + description = "The resource ID of the Corelight sensor image" + type = string +} + +variable "sensor_ssh_public_key" { + description = "SSH public key content for the sensor VM" + type = string +} + +variable "community_string" { + description = "Community string (API password) for sensor management" + type = string + sensitive = true +} + +# Licensing (Choose ONE option) + +variable "license_key" { + description = "Corelight sensor license key. Optional if fleet_url is configured." + type = string + sensitive = true + default = "" +} + +variable "fleet_token" { + description = "Fleet pairing token from the Fleet UI" + type = string + sensitive = true + default = "" +} + +variable "fleet_url" { + description = "URL of the Fleet instance" + type = string + default = "" +} + +variable "fleet_server_sslname" { + description = "SSL hostname for the Fleet server" + type = string + default = "" +} + +# Network Configuration + +variable "management_subnet_id" { + description = "Subnet ID for the management network interface" + type = string +} + +variable "monitoring_subnet_id" { + description = "Subnet ID for the monitoring network interface" + type = string +} + +variable "management_interface_public_ip" { + description = "Whether to associate a public IP with the management interface" + type = bool + default = false +} + +variable "ssh_allow_cidrs" { + description = "CIDR blocks allowed to SSH to the flow sensor" + type = list(string) + default = [] +} + +# VNet Flow Logs Configuration + +variable "storage_account_id" { + description = "The resource ID of the storage account containing VNet flow logs" + type = string +} + +# Tags + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) + default = {} +} diff --git a/examples/azure/flow-sensor/versions.tf b/examples/azure/flow-sensor/versions.tf new file mode 100644 index 0000000..cff7c5c --- /dev/null +++ b/examples/azure/flow-sensor/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.9.0" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">= 4.0" + } + } +} + +provider "azurerm" { + features {} +} diff --git a/modules/azure/sensor-single/README.md b/modules/azure/sensor-single/README.md new file mode 100644 index 0000000..ecdddc8 --- /dev/null +++ b/modules/azure/sensor-single/README.md @@ -0,0 +1,112 @@ +# Azure Sensor Single Module + +Deploys a single Corelight sensor VM on Azure with management and monitoring network interfaces. + +## Usage + +```hcl +module "sensor" { + source = "github.com/corelight/terraform//modules/azure/sensor-single?ref=v29.0.5-1" + + location = "eastus" + resource_group_name = "my-resource-group" + + corelight_sensor_image_id = "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Compute/images/corelight-sensor" + sensor_ssh_public_key = file("~/.ssh/id_rsa.pub") + community_string = var.community_string + + management_subnet_id = azurerm_subnet.management.id + monitoring_subnet_id = azurerm_subnet.monitoring.id + + fleet_token = var.fleet_token + fleet_url = var.fleet_url + fleet_server_sslname = var.fleet_server_sslname + + tags = { + Environment = "production" + } +} +``` + +### With VNet Flow Log Access + +```hcl +module "vnet_flow_access" { + source = "github.com/corelight/terraform//modules/azure/sensor-single/submodules/vnet_flow_storage_access?ref=v29.0.5-1" + + resource_group_name = "my-resource-group" + location = "eastus" + storage_account_id = azurerm_storage_account.flow_logs.id +} + +module "sensor" { + source = "github.com/corelight/terraform//modules/azure/sensor-single?ref=v29.0.5-1" + + # ... other variables ... + + user_assigned_identity_ids = [module.vnet_flow_access.identity_id] +} +``` + +## Prerequisites + +- A Corelight sensor image must be available in the subscription. Use [`scripts/azure/copy-azure-image.sh`](../../../scripts/azure/copy-azure-image.sh) to copy the VHD and create a VM image. +- The management subnet must have outbound internet access (via NAT gateway, Azure Firewall, or other mechanism) for Fleet connectivity. + + +## Requirements + +| Name | Version | +| ---- | ------- | +| [terraform](#requirement\_terraform) | >= 1.9.0 | +| [azurerm](#requirement\_azurerm) | >= 4.0 | + +## Providers + +| Name | Version | +| ---- | ------- | +| [azurerm](#provider\_azurerm) | >= 4.0 | + +## Inputs + +| Name | Description | Type | Default | Required | +| ---- | ----------- | ---- | ------- | :------: | +| [community\_string](#input\_community\_string) | The community string (API password) for sensor management, often referenced by Fleet | `string` | n/a | yes | +| [corelight\_sensor\_image\_id](#input\_corelight\_sensor\_image\_id) | The resource ID of the Corelight sensor image | `string` | n/a | yes | +| [location](#input\_location) | The Azure region where resources will be deployed | `string` | n/a | yes | +| [management\_subnet\_id](#input\_management\_subnet\_id) | The subnet ID for the management network interface | `string` | n/a | yes | +| [monitoring\_subnet\_id](#input\_monitoring\_subnet\_id) | The subnet ID for the monitoring network interface | `string` | n/a | yes | +| [resource\_group\_name](#input\_resource\_group\_name) | The name of the resource group where resources will be deployed | `string` | n/a | yes | +| [sensor\_ssh\_public\_key](#input\_sensor\_ssh\_public\_key) | The SSH public key which will be added to the sensor VM | `string` | n/a | yes | +| [custom\_sensor\_user\_data](#input\_custom\_sensor\_user\_data) | Custom user data for the sensor if the default doesn't apply | `string` | `""` | no | +| [deployment\_name](#input\_deployment\_name) | Name prefix for all resources (used to avoid naming conflicts) | `string` | `"corelight-sensor"` | no | +| [egress\_allow\_cidrs](#input\_egress\_allow\_cidrs) | The IP ranges allowed outbound for both NICs. Typically can be left as default. | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [fleet\_http\_proxy](#input\_fleet\_http\_proxy) | (optional) the proxy URL for HTTP traffic from the fleet | `string` | `""` | no | +| [fleet\_https\_proxy](#input\_fleet\_https\_proxy) | (optional) the proxy URL for HTTPS traffic from the fleet | `string` | `""` | no | +| [fleet\_no\_proxy](#input\_fleet\_no\_proxy) | (optional) hosts or domains to bypass the proxy for fleet traffic | `string` | `""` | no | +| [fleet\_server\_sslname](#input\_fleet\_server\_sslname) | (optional) the SSL hostname for the fleet server | `string` | `"1.broala.fleet.product.corelight.io"` | no | +| [fleet\_token](#input\_fleet\_token) | (optional) the pairing token from the Fleet UI. Must be set if 'fleet\_url' is provided | `string` | `""` | no | +| [fleet\_url](#input\_fleet\_url) | (optional) the URL of the fleet instance from the Fleet UI. Must be set if 'fleet\_token' is provided | `string` | `""` | no | +| [health\_check\_allow\_cidrs](#input\_health\_check\_allow\_cidrs) | CIDRs allowed to health check the sensor (port 41080) | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [license\_key](#input\_license\_key) | Your Corelight sensor license key. Optional if fleet\_url is configured. | `string` | `""` | no | +| [management\_interface\_public\_ip](#input\_management\_interface\_public\_ip) | Whether to assign a public IP to the management NIC | `bool` | `false` | no | +| [management\_nsg\_id](#input\_management\_nsg\_id) | ID of a pre-existing NSG for the management NIC. If empty, one will be created. | `string` | `""` | no | +| [monitoring\_ingress\_allow\_cidrs](#input\_monitoring\_ingress\_allow\_cidrs) | CIDRs allowed to send mirrored traffic (VXLAN 4789) to the monitoring NIC | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [monitoring\_nsg\_id](#input\_monitoring\_nsg\_id) | ID of a pre-existing NSG for the monitoring NIC. If empty, one will be created. | `string` | `""` | no | +| [os\_disk\_size\_gb](#input\_os\_disk\_size\_gb) | The size of the OS disk in GB. Not recommended to set lower than 500GB | `number` | `500` | no | +| [sensor\_admin\_username](#input\_sensor\_admin\_username) | The admin username for the sensor VM | `string` | `"corelight"` | no | +| [ssh\_allow\_cidrs](#input\_ssh\_allow\_cidrs) | List of CIDRs to grant SSH (port 22) access to the management NIC | `list(string)` | `[]` | no | +| [tags](#input\_tags) | Tags to apply to all resources deployed by this module | `map(string)` | `{}` | no | +| [user\_assigned\_identity\_ids](#input\_user\_assigned\_identity\_ids) | List of user-assigned managed identity IDs to attach to the VM (e.g. for VNet flow log access) | `list(string)` | `[]` | no | +| [virtual\_machine\_size](#input\_virtual\_machine\_size) | The Azure VM size for the sensor | `string` | `"Standard_D4s_v3"` | no | + +## Outputs + +| Name | Description | +| ---- | ----------- | +| [management\_interface\_id](#output\_management\_interface\_id) | Network interface ID for management traffic | +| [monitoring\_interface\_id](#output\_monitoring\_interface\_id) | Network interface ID for monitoring traffic | +| [sensor\_private\_ip\_address](#output\_sensor\_private\_ip\_address) | Private IP address of the management NIC | +| [sensor\_public\_ip\_address](#output\_sensor\_public\_ip\_address) | Public IP address of the management NIC (if enabled) | +| [sensor\_vm\_id](#output\_sensor\_vm\_id) | Resource ID of the sensor VM | + diff --git a/modules/azure/sensor-single/main.tf b/modules/azure/sensor-single/main.tf new file mode 100644 index 0000000..5504e58 --- /dev/null +++ b/modules/azure/sensor-single/main.tf @@ -0,0 +1,194 @@ +resource "azurerm_network_security_group" "management" { + count = var.management_nsg_id == "" ? 1 : 0 + name = "${var.deployment_name}-mgmt-nsg" + location = var.location + resource_group_name = var.resource_group_name + tags = var.tags +} + +resource "azurerm_network_security_group" "monitoring" { + count = var.monitoring_nsg_id == "" ? 1 : 0 + name = "${var.deployment_name}-mon-nsg" + location = var.location + resource_group_name = var.resource_group_name + tags = var.tags +} + +resource "azurerm_network_security_rule" "mgmt_egress" { + count = var.management_nsg_id == "" ? 1 : 0 + name = "AllowOutbound" + priority = 100 + direction = "Outbound" + access = "Allow" + protocol = "*" + source_port_range = "*" + destination_port_range = "*" + source_address_prefix = "*" + destination_address_prefixes = var.egress_allow_cidrs + resource_group_name = var.resource_group_name + network_security_group_name = azurerm_network_security_group.management[0].name +} + +resource "azurerm_network_security_rule" "mgmt_ssh_ingress" { + count = var.management_nsg_id == "" && length(var.ssh_allow_cidrs) > 0 ? 1 : 0 + name = "AllowSSH" + priority = 110 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "22" + source_address_prefixes = var.ssh_allow_cidrs + destination_address_prefix = "*" + resource_group_name = var.resource_group_name + network_security_group_name = azurerm_network_security_group.management[0].name +} + +resource "azurerm_network_security_rule" "mon_egress" { + count = var.monitoring_nsg_id == "" ? 1 : 0 + name = "AllowOutbound" + priority = 100 + direction = "Outbound" + access = "Allow" + protocol = "*" + source_port_range = "*" + destination_port_range = "*" + source_address_prefix = "*" + destination_address_prefixes = var.egress_allow_cidrs + resource_group_name = var.resource_group_name + network_security_group_name = azurerm_network_security_group.monitoring[0].name +} + +resource "azurerm_network_security_rule" "mon_vxlan_ingress" { + count = var.monitoring_nsg_id == "" ? 1 : 0 + name = "AllowVXLAN" + priority = 110 + direction = "Inbound" + access = "Allow" + protocol = "Udp" + source_port_range = "*" + destination_port_range = "4789" + source_address_prefixes = var.monitoring_ingress_allow_cidrs + destination_address_prefix = "*" + resource_group_name = var.resource_group_name + network_security_group_name = azurerm_network_security_group.monitoring[0].name +} + +resource "azurerm_network_security_rule" "mon_health_check_ingress" { + count = var.monitoring_nsg_id == "" ? 1 : 0 + name = "AllowHealthCheck" + priority = 120 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "41080" + source_address_prefixes = var.health_check_allow_cidrs + destination_address_prefix = "*" + resource_group_name = var.resource_group_name + network_security_group_name = azurerm_network_security_group.monitoring[0].name +} + +resource "azurerm_network_interface" "management" { + name = "${var.deployment_name}-mgmt-nic" + location = var.location + resource_group_name = var.resource_group_name + + ip_configuration { + name = "management-ip-config" + subnet_id = var.management_subnet_id + private_ip_address_allocation = "Dynamic" + public_ip_address_id = var.management_interface_public_ip ? azurerm_public_ip.management[0].id : null + } + + tags = var.tags +} + +resource "azurerm_network_interface_security_group_association" "management" { + network_interface_id = azurerm_network_interface.management.id + network_security_group_id = var.management_nsg_id == "" ? azurerm_network_security_group.management[0].id : var.management_nsg_id +} + +resource "azurerm_public_ip" "management" { + count = var.management_interface_public_ip ? 1 : 0 + name = "${var.deployment_name}-mgmt-pip" + location = var.location + resource_group_name = var.resource_group_name + allocation_method = "Static" + sku = "Standard" + tags = var.tags +} + +resource "azurerm_network_interface" "monitoring" { + name = "${var.deployment_name}-mon-nic" + location = var.location + resource_group_name = var.resource_group_name + accelerated_networking_enabled = true + + ip_configuration { + name = "monitoring-ip-config" + subnet_id = var.monitoring_subnet_id + private_ip_address_allocation = "Dynamic" + } + + tags = var.tags +} + +resource "azurerm_network_interface_security_group_association" "monitoring" { + network_interface_id = azurerm_network_interface.monitoring.id + network_security_group_id = var.monitoring_nsg_id == "" ? azurerm_network_security_group.monitoring[0].id : var.monitoring_nsg_id +} + +module "sensor_config" { + count = var.custom_sensor_user_data == "" ? 1 : 0 + source = "../../_shared/config/sensor" + + fleet_community_string = var.community_string + sensor_license = var.license_key + fleet_token = var.fleet_token + fleet_url = var.fleet_url + fleet_server_sslname = var.fleet_server_sslname + fleet_http_proxy = var.fleet_http_proxy + fleet_https_proxy = var.fleet_https_proxy + fleet_no_proxy = var.fleet_no_proxy + sensor_management_interface_name = "eth0" + sensor_monitoring_interface_name = "eth1" + base64_encode_config = true +} + +resource "azurerm_linux_virtual_machine" "sensor" { + name = "${var.deployment_name}-vm" + location = var.location + resource_group_name = var.resource_group_name + size = var.virtual_machine_size + admin_username = var.sensor_admin_username + + admin_ssh_key { + public_key = var.sensor_ssh_public_key + username = var.sensor_admin_username + } + + source_image_id = var.corelight_sensor_image_id + custom_data = var.custom_sensor_user_data == "" ? module.sensor_config[0].cloudinit_config.rendered : var.custom_sensor_user_data + + network_interface_ids = [ + azurerm_network_interface.management.id, + azurerm_network_interface.monitoring.id, + ] + + os_disk { + caching = "ReadWrite" + storage_account_type = "StandardSSD_LRS" + disk_size_gb = var.os_disk_size_gb + } + + dynamic "identity" { + for_each = length(var.user_assigned_identity_ids) > 0 ? [1] : [] + content { + type = "UserAssigned" + identity_ids = var.user_assigned_identity_ids + } + } + + tags = var.tags +} diff --git a/modules/azure/sensor-single/outputs.tf b/modules/azure/sensor-single/outputs.tf new file mode 100644 index 0000000..f7bc257 --- /dev/null +++ b/modules/azure/sensor-single/outputs.tf @@ -0,0 +1,24 @@ +output "sensor_vm_id" { + description = "Resource ID of the sensor VM" + value = azurerm_linux_virtual_machine.sensor.id +} + +output "sensor_private_ip_address" { + description = "Private IP address of the management NIC" + value = azurerm_network_interface.management.private_ip_address +} + +output "sensor_public_ip_address" { + description = "Public IP address of the management NIC (if enabled)" + value = var.management_interface_public_ip ? azurerm_public_ip.management[0].ip_address : null +} + +output "management_interface_id" { + description = "Network interface ID for management traffic" + value = azurerm_network_interface.management.id +} + +output "monitoring_interface_id" { + description = "Network interface ID for monitoring traffic" + value = azurerm_network_interface.monitoring.id +} diff --git a/modules/azure/sensor-single/submodules/vnet_flow_storage_access/README.md b/modules/azure/sensor-single/submodules/vnet_flow_storage_access/README.md new file mode 100644 index 0000000..128e336 --- /dev/null +++ b/modules/azure/sensor-single/submodules/vnet_flow_storage_access/README.md @@ -0,0 +1,50 @@ +# VNet Flow Storage Access Module + +Creates a user-assigned Managed Identity with `Storage Blob Data Reader` access to a storage account containing Azure VNet Flow Logs. + +## Usage + +```hcl +module "vnet_flow_storage_access" { + source = "github.com/corelight/terraform//modules/azure/sensor-single/submodules/vnet_flow_storage_access?ref=v29.0.5-1" + + resource_group_name = "my-resource-group" + location = "eastus" + storage_account_id = "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Storage/storageAccounts/vnetflowlogs" +} +``` + +Attach the identity to the sensor VM and use the `identity_client_id` output for the sensor's VNet flow service configuration. + + +## Requirements + +| Name | Version | +| ---- | ------- | +| [terraform](#requirement\_terraform) | >= 1.9.0 | +| [azurerm](#requirement\_azurerm) | >= 4.0 | + +## Providers + +| Name | Version | +| ---- | ------- | +| [azurerm](#provider\_azurerm) | >= 4.0 | + +## Inputs + +| Name | Description | Type | Default | Required | +| ---- | ----------- | ---- | ------- | :------: | +| [location](#input\_location) | The Azure region for the managed identity | `string` | n/a | yes | +| [resource\_group\_name](#input\_resource\_group\_name) | The resource group where the managed identity will be created | `string` | n/a | yes | +| [storage\_account\_id](#input\_storage\_account\_id) | The resource ID of the storage account containing VNet flow logs | `string` | n/a | yes | +| [identity\_name](#input\_identity\_name) | Name of the user-assigned managed identity for VNet flow log access | `string` | `"corelight-vnet-flow-identity"` | no | +| [tags](#input\_tags) | Tags to apply to resources created by this module | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +| ---- | ----------- | +| [identity\_client\_id](#output\_identity\_client\_id) | Client ID of the managed identity (for Workload Identity or AZURE\_CLIENT\_ID) | +| [identity\_id](#output\_identity\_id) | Resource ID of the user-assigned managed identity (for attaching to VMSS) | +| [identity\_principal\_id](#output\_identity\_principal\_id) | Principal ID of the managed identity | + diff --git a/modules/azure/sensor-single/submodules/vnet_flow_storage_access/main.tf b/modules/azure/sensor-single/submodules/vnet_flow_storage_access/main.tf new file mode 100644 index 0000000..8271542 --- /dev/null +++ b/modules/azure/sensor-single/submodules/vnet_flow_storage_access/main.tf @@ -0,0 +1,12 @@ +resource "azurerm_user_assigned_identity" "vnet_flow" { + name = var.identity_name + resource_group_name = var.resource_group_name + location = var.location + tags = var.tags +} + +resource "azurerm_role_assignment" "storage_blob_reader" { + scope = var.storage_account_id + role_definition_name = "Storage Blob Data Reader" + principal_id = azurerm_user_assigned_identity.vnet_flow.principal_id +} diff --git a/modules/azure/sensor-single/submodules/vnet_flow_storage_access/outputs.tf b/modules/azure/sensor-single/submodules/vnet_flow_storage_access/outputs.tf new file mode 100644 index 0000000..d8f3ae0 --- /dev/null +++ b/modules/azure/sensor-single/submodules/vnet_flow_storage_access/outputs.tf @@ -0,0 +1,14 @@ +output "identity_id" { + description = "Resource ID of the user-assigned managed identity (for attaching to VMSS)" + value = azurerm_user_assigned_identity.vnet_flow.id +} + +output "identity_client_id" { + description = "Client ID of the managed identity (for Workload Identity or AZURE_CLIENT_ID)" + value = azurerm_user_assigned_identity.vnet_flow.client_id +} + +output "identity_principal_id" { + description = "Principal ID of the managed identity" + value = azurerm_user_assigned_identity.vnet_flow.principal_id +} diff --git a/modules/azure/sensor-single/submodules/vnet_flow_storage_access/variables.tf b/modules/azure/sensor-single/submodules/vnet_flow_storage_access/variables.tf new file mode 100644 index 0000000..c712a36 --- /dev/null +++ b/modules/azure/sensor-single/submodules/vnet_flow_storage_access/variables.tf @@ -0,0 +1,26 @@ +variable "resource_group_name" { + description = "The resource group where the managed identity will be created" + type = string +} + +variable "location" { + description = "The Azure region for the managed identity" + type = string +} + +variable "identity_name" { + description = "Name of the user-assigned managed identity for VNet flow log access" + type = string + default = "corelight-vnet-flow-identity" +} + +variable "storage_account_id" { + description = "The resource ID of the storage account containing VNet flow logs" + type = string +} + +variable "tags" { + description = "Tags to apply to resources created by this module" + type = map(string) + default = {} +} diff --git a/modules/azure/sensor-single/submodules/vnet_flow_storage_access/versions.tf b/modules/azure/sensor-single/submodules/vnet_flow_storage_access/versions.tf new file mode 100644 index 0000000..45c48fb --- /dev/null +++ b/modules/azure/sensor-single/submodules/vnet_flow_storage_access/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.9.0" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">= 4.0" + } + } +} diff --git a/modules/azure/sensor-single/variables.tf b/modules/azure/sensor-single/variables.tf new file mode 100644 index 0000000..86607db --- /dev/null +++ b/modules/azure/sensor-single/variables.tf @@ -0,0 +1,194 @@ +# Deployment + +variable "deployment_name" { + description = "Name prefix for all resources (used to avoid naming conflicts)" + type = string + default = "corelight-sensor" +} + +variable "location" { + description = "The Azure region where resources will be deployed" + type = string +} + +variable "resource_group_name" { + description = "The name of the resource group where resources will be deployed" + type = string +} + +# Sensor image — use scripts/azure/copy-azure-image.sh to copy the VHD and create the image + +variable "corelight_sensor_image_id" { + description = "The resource ID of the Corelight sensor image" + type = string +} + +variable "sensor_ssh_public_key" { + description = "The SSH public key which will be added to the sensor VM" + type = string +} + +variable "community_string" { + description = "The community string (API password) for sensor management, often referenced by Fleet" + type = string + sensitive = true +} + +# Networking — subnets must exist before deploying this module + +variable "management_subnet_id" { + description = "The subnet ID for the management network interface" + type = string +} + +variable "monitoring_subnet_id" { + description = "The subnet ID for the monitoring network interface" + type = string +} + +# Licensing — choose ONE of the two options below: +# +# Option 1: Standalone — set license_key only. The sensor runs independently +# without centralized management. +# +# Option 2: Fleet-managed — set fleet_url + fleet_token (and optionally +# fleet_server_sslname). The sensor is managed via the Corelight +# Fleet UI which handles configuration, updates, and monitoring. +# license_key can be omitted when using Fleet. + +variable "license_key" { + description = "Your Corelight sensor license key. Optional if fleet_url is configured." + type = string + sensitive = true + default = "" + + validation { + condition = var.license_key != "" || var.fleet_url != "" + error_message = "Either license_key must be provided or fleet_url must be configured." + } +} + +# Fleet configuration (Option 2) — required when managing the sensor via Fleet + +variable "fleet_token" { + description = "(optional) the pairing token from the Fleet UI. Must be set if 'fleet_url' is provided" + type = string + default = "" + sensitive = true +} + +variable "fleet_url" { + description = "(optional) the URL of the fleet instance from the Fleet UI. Must be set if 'fleet_token' is provided" + type = string + default = "" +} + +variable "fleet_server_sslname" { + description = "(optional) the SSL hostname for the fleet server" + type = string + default = "1.broala.fleet.product.corelight.io" +} + +variable "fleet_http_proxy" { + description = "(optional) the proxy URL for HTTP traffic from the fleet" + type = string + default = "" +} + +variable "fleet_https_proxy" { + description = "(optional) the proxy URL for HTTPS traffic from the fleet" + type = string + default = "" +} + +variable "fleet_no_proxy" { + description = "(optional) hosts or domains to bypass the proxy for fleet traffic" + type = string + default = "" +} + +# VM configuration + +variable "sensor_admin_username" { + description = "The admin username for the sensor VM" + type = string + default = "corelight" +} + +variable "virtual_machine_size" { + description = "The Azure VM size for the sensor" + type = string + default = "Standard_D4s_v3" +} + +variable "os_disk_size_gb" { + description = "The size of the OS disk in GB. Not recommended to set lower than 500GB" + type = number + default = 500 +} + +variable "custom_sensor_user_data" { + description = "Custom user data for the sensor if the default doesn't apply" + type = string + default = "" +} + +# Networking — NSG and access control + +variable "management_nsg_id" { + description = "ID of a pre-existing NSG for the management NIC. If empty, one will be created." + type = string + default = "" +} + +variable "monitoring_nsg_id" { + description = "ID of a pre-existing NSG for the monitoring NIC. If empty, one will be created." + type = string + default = "" +} + +variable "management_interface_public_ip" { + description = "Whether to assign a public IP to the management NIC" + type = bool + default = false +} + +variable "egress_allow_cidrs" { + description = "The IP ranges allowed outbound for both NICs. Typically can be left as default." + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "ssh_allow_cidrs" { + description = "List of CIDRs to grant SSH (port 22) access to the management NIC" + type = list(string) + default = [] +} + +variable "monitoring_ingress_allow_cidrs" { + description = "CIDRs allowed to send mirrored traffic (VXLAN 4789) to the monitoring NIC" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "health_check_allow_cidrs" { + description = "CIDRs allowed to health check the sensor (port 41080)" + type = list(string) + default = ["0.0.0.0/0"] +} + +# Identity — attach managed identities for VNet flow log access or other Azure RBAC roles + +variable "user_assigned_identity_ids" { + description = "List of user-assigned managed identity IDs to attach to the VM (e.g. for VNet flow log access)" + type = list(string) + default = [] +} + +# Tags + +variable "tags" { + description = "Tags to apply to all resources deployed by this module" + type = map(string) + default = {} +} diff --git a/modules/azure/sensor-single/versions.tf b/modules/azure/sensor-single/versions.tf new file mode 100644 index 0000000..45c48fb --- /dev/null +++ b/modules/azure/sensor-single/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.9.0" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">= 4.0" + } + } +}