diff --git a/CHANGELOG.md b/CHANGELOG.md index e558dd9..d006c51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features - Azure Database for PostgreSQL Flexible module +- Azure SQL Managed Instance module ## 1.0.6 (2024-09-17) diff --git a/DSF_VERSION_COMPATABILITY.md b/DSF_VERSION_COMPATABILITY.md index f8658cf..2101f17 100644 --- a/DSF_VERSION_COMPATABILITY.md +++ b/DSF_VERSION_COMPATABILITY.md @@ -95,5 +95,9 @@ The following table lists the DSF versions that each module is tested and mainta onboard-azure-postgresql-flexible 4.17+ + + onboard-azure-sql-managed-instance + 4.17+ + \ No newline at end of file diff --git a/examples/onboard-azure-sql-managed-instance/README.md b/examples/onboard-azure-sql-managed-instance/README.md new file mode 100644 index 0000000..65750ac --- /dev/null +++ b/examples/onboard-azure-sql-managed-instance/README.md @@ -0,0 +1,58 @@ +# Onboard Azure SQL Managed Instance example +This example includes additional prerequisites that will need to be completed to fully utilize the module. More details can be found in the [onboarding documentation](https://docs.imperva.com/bundle/onboarding-databases-to-sonar-reference-guide/page/Azure-SQL-Managed-Instance-Onboarding-Steps_48367362.html). + +This example creates 'azurerm' and 'dsfhub' resources. More information regarding authentication to each can be found in the relevant provider documentation: +- [azurerm](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs) +- [dsfhub](https://registry.terraform.io/providers/imperva/dsfhub/latest/docs) + +## Prerequisites +This module expects an Event Hub and a Storage Account Container to have been created in advance, in addition to a corresponding existing AZURE EVENTHUB asset in DSF. Both of these prerequisites and all related resources are handled in the ``onboard-azure-eventhub`` module. It also expects a method to connect to the SQL Managed instance to create a server audit policy. + +### Azure Event Hub Namespace and Event Hub +SQL Managed instance audit logs are sent to an Azure Event Hub and are retrieved by DSF. The Event Hubs are created within an Event Hub Namespace, which can contain one or more Event Hubs. Audit logs of multiple SQL Managed instances can be sent to a single Event Hub. + +### Azure Storage Account and Container +Storage Containers are used to store transactional data for the Event Hub import processes, and one Storage Container is required for each Event Hub. These Storage Containers exist within a Storage Account, which may contain multiple Storage Containers. + +### Database Configuration +Part of the onboarding process involves connecting to your SQL Managed instance and running SQL commands to create an audit policy. This module includes an example for how to connect to the instance from your local machine and create it. + +**Note:** This example requires the ``sqlcmd`` client to be installed, as well as for the newly created SQL Managed instance to be accessible from your local machine. + + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [http](#provider\_http) | n/a | +| [terraform](#provider\_terraform) | n/a | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [onboard-azure-sql-managed-instance-eventhub-1](#module\_onboard-azure-sql-managed-instance-eventhub-1) | ../../modules/onboard-azure-eventhub | n/a | +| [sql-managed-instance-1](#module\_sql-managed-instance-1) | ../../modules/onboard-azure-sql-managed-instance | n/a | + +## Resources + +| Name | Type | +|------|------| +| [terraform_data.configure_database-1](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource | +| [http_http.my-ip](https://registry.terraform.io/providers/hashicorp/http/latest/docs/data-sources/http) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [dsfhub\_host](#input\_dsfhub\_host) | n/a | `any` | n/a | yes | +| [dsfhub\_token](#input\_dsfhub\_token) | n/a | `any` | n/a | yes | + +## Outputs + +No outputs. + \ No newline at end of file diff --git a/examples/onboard-azure-sql-managed-instance/configure_audit_policy.sql b/examples/onboard-azure-sql-managed-instance/configure_audit_policy.sql new file mode 100644 index 0000000..6810ee0 --- /dev/null +++ b/examples/onboard-azure-sql-managed-instance/configure_audit_policy.sql @@ -0,0 +1,101 @@ +DECLARE + @server_audit AS VARCHAR(50) = '$(server_audit)', + @server_audit_spec_name AS VARCHAR(50) = '$(audit_spec_name)', + @server_audit_spec AS VARCHAR(3000), + @server_audit_status INT, + @sql_command AS VARCHAR(3000) + +USE master; + +-- Create server audit +IF (EXISTS (SELECT * FROM sys.dm_server_audit_status where name = @server_audit)) + BEGIN + PRINT 'Server audit "' + @server_audit + '" already exists.'; + END +ELSE + BEGIN + PRINT 'Creating server audit "' + @server_audit + '"'; + + SET @sql_command = 'CREATE SERVER AUDIT ' + @server_audit + ' TO EXTERNAL_MONITOR'; + EXECUTE(@sql_command); + END; + +-- Enable server audit +select @server_audit_status = status FROM sys.dm_server_audit_status where name = @server_audit; + +IF (@server_audit_status = 1) + BEGIN + PRINT 'Server audit "' + @server_audit + '" is already enabled.'; + END; +ELSE + BEGIN + PRINT 'Enabling server audit "' + @server_audit + '"'; + + SET @sql_command = 'ALTER SERVER AUDIT ' + @server_audit + ' WITH (STATE = ON)'; + EXECUTE(@sql_command); + END; + + +-- Create server audit specification +IF (EXISTS (SELECT * FROM sys.server_audit_specifications where name = @server_audit_spec_name)) + BEGIN + PRINT 'Server audit specification "' + @server_audit_spec_name + '" already exists.'; + END; +ELSE + BEGIN + PRINT 'Creating server audit specification "' + @server_audit_spec_name + '"'; + + -- This creates a server audit specification that captures all server-level and database-level events. + -- Modify or add additional groups as needed. + -- For all action groups available, see https://learn.microsoft.com/en-us/sql/relational-databases/security/auditing/sql-server-audit-action-groups-and-actions?view=sql-server-ver15 + SET @server_audit_spec = 'ADD (APPLICATION_ROLE_CHANGE_PASSWORD_GROUP), +ADD (AUDIT_CHANGE_GROUP), +ADD (BACKUP_RESTORE_GROUP), +ADD (BATCH_COMPLETED_GROUP), +ADD (BATCH_STARTED_GROUP), +ADD (BROKER_LOGIN_GROUP), +ADD (DATABASE_CHANGE_GROUP), +ADD (DATABASE_LOGOUT_GROUP), +ADD (DATABASE_MIRRORING_LOGIN_GROUP), +ADD (DATABASE_OBJECT_ACCESS_GROUP), +ADD (DATABASE_OBJECT_CHANGE_GROUP), +ADD (DATABASE_OBJECT_OWNERSHIP_CHANGE_GROUP), +ADD (DATABASE_OBJECT_PERMISSION_CHANGE_GROUP), +ADD (DATABASE_OPERATION_GROUP), +ADD (DATABASE_OWNERSHIP_CHANGE_GROUP), +ADD (DATABASE_PERMISSION_CHANGE_GROUP), +ADD (DATABASE_PRINCIPAL_CHANGE_GROUP), +ADD (DATABASE_PRINCIPAL_IMPERSONATION_GROUP), +ADD (DATABASE_ROLE_MEMBER_CHANGE_GROUP), +ADD (DBCC_GROUP), +ADD (FAILED_DATABASE_AUTHENTICATION_GROUP), +ADD (FAILED_LOGIN_GROUP), +ADD (FULLTEXT_GROUP), +ADD (LOGIN_CHANGE_PASSWORD_GROUP), +ADD (LOGOUT_GROUP), +ADD (SCHEMA_OBJECT_ACCESS_GROUP), +ADD (SCHEMA_OBJECT_CHANGE_GROUP), +ADD (SCHEMA_OBJECT_OWNERSHIP_CHANGE_GROUP), +ADD (SCHEMA_OBJECT_PERMISSION_CHANGE_GROUP), +ADD (SERVER_OBJECT_CHANGE_GROUP), +ADD (SERVER_OBJECT_OWNERSHIP_CHANGE_GROUP), +ADD (SERVER_OBJECT_PERMISSION_CHANGE_GROUP), +ADD (SERVER_OPERATION_GROUP), +ADD (SERVER_PERMISSION_CHANGE_GROUP), +ADD (SERVER_PRINCIPAL_CHANGE_GROUP), +ADD (SERVER_PRINCIPAL_IMPERSONATION_GROUP), +ADD (SERVER_ROLE_MEMBER_CHANGE_GROUP), +ADD (SERVER_STATE_CHANGE_GROUP), +ADD (SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP), +ADD (SUCCESSFUL_LOGIN_GROUP), +ADD (TRACE_CHANGE_GROUP), +ADD (TRANSACTION_GROUP), +ADD (USER_CHANGE_PASSWORD_GROUP), +ADD (USER_DEFINED_AUDIT_GROUP) +WITH (STATE = ON)'; + + SET @sql_command = 'CREATE SERVER AUDIT SPECIFICATION ' + @server_audit_spec_name + ' FOR SERVER AUDIT ' + @server_audit + ' ' + @server_audit_spec; + EXECUTE(@sql_command); + END; + +GO \ No newline at end of file diff --git a/examples/onboard-azure-sql-managed-instance/configure_database.sh b/examples/onboard-azure-sql-managed-instance/configure_database.sh new file mode 100755 index 0000000..89f63c8 --- /dev/null +++ b/examples/onboard-azure-sql-managed-instance/configure_database.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Creates an audit policy on an SQL Server instance using the 'sqlcmd' client + +# Settings +current_directory=$(dirname "$(realpath "${BASH_SOURCE[0]}")") +policy_sql_file="${current_directory}/configure_audit_policy.sql" + +# Functions +function is_pkg_installed { + local pkg="$1" + if ! command -v "${pkg}" &> /dev/null + then + echo "Package '${pkg}' is not installed." + echo "Please see https://learn.microsoft.com/en-us/sql/tools/sqlcmd/sqlcmd-utility for installation instructions for your OS." + echo "Exiting..." + exit 1 + else + return 0 + fi +} + +is_pkg_installed "sqlcmd" + +# Create server audit with server audit specification +if [ ! -r "${policy_sql_file}" ]; then + echo "Unable to read ${policy_sql_file}" + echo "Exiting..." + exit 1 +else + sqlcmd -S tcp:${ENDPOINT} -U ${ADMIN_USER} -P ${ADMIN_PASSWORD} -v server_audit=${SERVER_AUDIT_NAME} -v audit_spec_name=${SERVER_AUDIT_SPEC_NAME} -C < ${policy_sql_file} +fi \ No newline at end of file diff --git a/examples/onboard-azure-sql-managed-instance/main.tf b/examples/onboard-azure-sql-managed-instance/main.tf new file mode 100644 index 0000000..b64f1ff --- /dev/null +++ b/examples/onboard-azure-sql-managed-instance/main.tf @@ -0,0 +1,146 @@ +locals { + azure_location = "East US" + azure_resource_group_name = "My_Resource_Group" + azure_subscription_id = "123456790-wxyz-g8h9-e5f6-a1b2c3d4" + + managed_instance_admin = "testadmin" + managed_instance_admin_password = "Abcd1234" + server_audit_name = "tfmanagedsqlserveraudit" + server_audit_spec_name = "tfmanagedsqlserverauditspec" + + admin_email = "test@example.com" + gateway_id = "a1b2c3d4-e5f6-g8h9-wxyz-123456790" +} + +################################################################################ +# Providers +################################################################################ +terraform { + required_providers { + dsfhub = { + source = "imperva/dsfhub" + } + } +} + +provider "azurerm" { + features {} + subscription_id = local.azure_subscription_id +} + +variable "dsfhub_host" {} # TF_VAR_dsfhub_host env variable +variable "dsfhub_token" {} # TF_VAR_dsfhub_token env variable + +provider "dsfhub" { + dsfhub_host = var.dsfhub_host + dsfhub_token = var.dsfhub_token +} + +################################################################################ +# Prerequisites +# 1. Azure Event Hub Namespace and Event Hub. Includes authorization rules for +# reading and writing to the Event Hub. +# 2. Storage Account and Container +# 3. Method to create server audit policy on the instance. +################################################################################ +# 1 and 2 +module "onboard-azure-sql-managed-instance-eventhub-1" { + source = "../../modules/onboard-azure-eventhub" + + azure_eventhub_admin_email = local.admin_email + azure_eventhub_format = "AzureSQL_Managed" + azure_eventhub_gateway_id = local.gateway_id + + eventhub_name = "sqlmanagedeventhub" + eventhub_namespace_location = local.azure_location + eventhub_namespace_name = "sqlmanagedeventhubns" + eventhub_namespace_resource_group_name = local.azure_resource_group_name + + eventhub_resource_group_name = local.azure_resource_group_name + + storage_account_location = local.azure_location + storage_account_name = "sqlmanagedstorageacc" + storage_account_resource_group_name = local.azure_resource_group_name + storage_container_name = "sqlmanagedstoragecon" +} + +# 3. Run shell script locally to create the server audit policy on the newly created instance +# Get current IP address +data "http" "my-ip" { + url = "http://icanhazip.com" +} + +# Create security group rule for current IP +locals { + allow_tf_localhost_security_rule = { + name = "allow_tf_localhost_security_rule" + access = "Allow" + description = "allow terraform host to connect to managed instance" + destination_address_prefix = "*" + destination_port_range = "3342" + direction = "Inbound" + priority = 1001 + protocol = "Tcp" + source_address_prefix = chomp(data.http.my-ip.response_body) + source_port_range = "*" + } +} + +# Construct public endpoint +locals { + fqdn_parsing = regex("([^.]+)(.*)", module.sql-managed-instance-1.azure-ms-sql-managed-instance.fqdn) # Split after instance name + managed_instance_public_endpoint = "${local.fqdn_parsing[0]}.public${local.fqdn_parsing[1]},3342" # Add ".public" and public port +} + +# Create server audit policy +resource "terraform_data" "configure_database-1" { + depends_on = [module.sql-managed-instance-1] + + triggers_replace = 1 + + provisioner "local-exec" { + environment = { + ADMIN_USER = local.managed_instance_admin + ADMIN_PASSWORD = local.managed_instance_admin_password + ENDPOINT = local.managed_instance_public_endpoint + + SERVER_AUDIT_NAME = local.server_audit_name + SERVER_AUDIT_SPEC_NAME = local.server_audit_spec_name + } + + command = "./configure_database.sh" + + on_failure = fail + } +} + +################################################################################ +# Azure SQL Managed Instance +################################################################################ +module "sql-managed-instance-1" { + source = "../../modules/onboard-azure-sql-managed-instance" + + azure_sql_managed_instance_admin_email = local.admin_email + azure_sql_managed_instance_audit_pull_enabled = true + azure_sql_managed_instance_gateway_id = local.gateway_id + azure_sql_managed_instance_location = local.azure_location + azure_sql_managed_instance_logs_destination_asset_id = module.onboard-azure-sql-managed-instance-eventhub-1.azure-eventhub-asset.asset_id + + diagnostic_setting_eventhub_authorization_rule_id = module.onboard-azure-sql-managed-instance-eventhub-1.eventhub-write-authorization.id + diagnostic_setting_eventhub_name = module.onboard-azure-sql-managed-instance-eventhub-1.eventhub.name + diagnostic_setting_name = "dsfhubdiagnostic" + + managed_instance_administrator_login = local.managed_instance_admin + managed_instance_administrator_login_password = local.managed_instance_admin_password + managed_instance_location = local.azure_location + managed_instance_name = "tf-example-sql-managed-instance" + managed_instance_public_data_endpoint_enabled = true + managed_instance_resource_group_name = local.azure_resource_group_name + + route_table_resource_group_name = local.azure_resource_group_name + + security_group_resource_group_name = local.azure_resource_group_name + security_group_security_rules = [local.allow_tf_localhost_security_rule] + + virtual_network_resource_group_name = local.azure_resource_group_name +} diff --git a/modules/azurerm-mssql-managed-instance/README.md b/modules/azurerm-mssql-managed-instance/README.md new file mode 100644 index 0000000..6c47c9e --- /dev/null +++ b/modules/azurerm-mssql-managed-instance/README.md @@ -0,0 +1,44 @@ + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [azurerm](#provider\_azurerm) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [azurerm_mssql_managed_instance.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/mssql_managed_instance) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [administrator\_login](#input\_administrator\_login) | The administrator login name for the new SQL Managed Instance. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [administrator\_login\_password](#input\_administrator\_login\_password) | The password associated with the administrator\_login user. Needs to comply with Azure's [Password Policy](https://learn.microsoft.com/en-us/sql/relational-databases/security/password-policy?view=sql-server-ver16&redirectedfrom=MSDN) | `string` | n/a | yes | +| [license\_type](#input\_license\_type) | What type of license the Managed Instance will use. Possible values are LicenseIncluded and BasePrice. Defaults to LicenseIncluded. | `string` | `"LicenseIncluded"` | no | +| [location](#input\_location) | Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [name](#input\_name) | The name of the SQL Managed Instance. This needs to be globally unique within Azure. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [public\_data\_endpoint\_enabled](#input\_public\_data\_endpoint\_enabled) | Is the public data endpoint enabled? Default value is false. | `bool` | `false` | no | +| [resource\_group\_name](#input\_resource\_group\_name) | The name of the resource group in which to create the SQL Managed Instance. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [sku\_name](#input\_sku\_name) | Specifies the SKU Name for the SQL Managed Instance. Valid values include GP\_Gen4, GP\_Gen5, GP\_Gen8IM, GP\_Gen8IH, BC\_Gen4, BC\_Gen5, BC\_Gen8IM or BC\_Gen8IH. Defaults to GP\_Gen5. | `string` | `"GP_Gen5"` | no | +| [storage\_size\_in\_gb](#input\_storage\_size\_in\_gb) | Maximum storage space for the SQL Managed instance. This should be a multiple of 32 (GB). Defaults to 32. | `number` | `32` | no | +| [subnet\_id](#input\_subnet\_id) | The subnet resource id that the SQL Managed Instance will be associated with. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [tags](#input\_tags) | A mapping of tags to assign to the resource. | `map(string)` | `null` | no | +| [vcores](#input\_vcores) | Number of cores that should be assigned to the SQL Managed Instance. Values can be 8, 16, or 24 for Gen4 SKUs, or 4, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64, 80, 96 or 128 for Gen5 SKUs. Defaults to 4. | `number` | `4` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [this](#output\_this) | Azure SQL Managed Instance. | + \ No newline at end of file diff --git a/modules/azurerm-mssql-managed-instance/main.tf b/modules/azurerm-mssql-managed-instance/main.tf new file mode 100644 index 0000000..d595be9 --- /dev/null +++ b/modules/azurerm-mssql-managed-instance/main.tf @@ -0,0 +1,14 @@ +resource "azurerm_mssql_managed_instance" "this" { + administrator_login = var.administrator_login + administrator_login_password = var.administrator_login_password + license_type = var.license_type + location = var.location + name = var.name + public_data_endpoint_enabled = var.public_data_endpoint_enabled + resource_group_name = var.resource_group_name + sku_name = var.sku_name + storage_size_in_gb = var.storage_size_in_gb + subnet_id = var.subnet_id + vcores = var.vcores + tags = var.tags +} diff --git a/modules/azurerm-mssql-managed-instance/outputs.tf b/modules/azurerm-mssql-managed-instance/outputs.tf new file mode 100644 index 0000000..6ae1b3d --- /dev/null +++ b/modules/azurerm-mssql-managed-instance/outputs.tf @@ -0,0 +1,4 @@ +output "this" { + description = "Azure SQL Managed Instance." + value = azurerm_mssql_managed_instance.this +} diff --git a/modules/azurerm-mssql-managed-instance/variables.tf b/modules/azurerm-mssql-managed-instance/variables.tf new file mode 100644 index 0000000..9eb3a25 --- /dev/null +++ b/modules/azurerm-mssql-managed-instance/variables.tf @@ -0,0 +1,87 @@ +variable "administrator_login" { + description = "The administrator login name for the new SQL Managed Instance. Changing this forces a new resource to be created." + type = string +} + +variable "administrator_login_password" { + description = "The password associated with the administrator_login user. Needs to comply with Azure's [Password Policy](https://learn.microsoft.com/en-us/sql/relational-databases/security/password-policy?view=sql-server-ver16&redirectedfrom=MSDN)" + type = string +} + +variable "license_type" { + description = "What type of license the Managed Instance will use. Possible values are LicenseIncluded and BasePrice. Defaults to LicenseIncluded." + type = string + default = "LicenseIncluded" + validation { + condition = contains(["LicenseIncluded", "BasePrice"], var.license_type) + error_message = "Invalid license type. Possible values are LicenseIncluded and BasePrice." + } +} + +variable "location" { + description = "Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created." + type = string +} + +variable "name" { + description = "The name of the SQL Managed Instance. This needs to be globally unique within Azure. Changing this forces a new resource to be created." + type = string +} + +variable "public_data_endpoint_enabled" { + description = "Is the public data endpoint enabled? Default value is false." + type = bool + default = false +} + +variable "resource_group_name" { + description = "The name of the resource group in which to create the SQL Managed Instance. Changing this forces a new resource to be created." + type = string +} + +variable "sku_name" { + description = "Specifies the SKU Name for the SQL Managed Instance. Valid values include GP_Gen4, GP_Gen5, GP_Gen8IM, GP_Gen8IH, BC_Gen4, BC_Gen5, BC_Gen8IM or BC_Gen8IH. Defaults to GP_Gen5." + type = string + default = "GP_Gen5" + validation { + condition = contains( + ["GP_Gen4", "GP_Gen5", "GP_GEN8IM", "GP_Gen8IH", "BC_Gen4", "BC_Gen5", "BC_Gen81M", "BC_Gen81H"], + var.sku_name + ) + error_message = "Invalid SKU. Valid values include GP_Gen4, GP_Gen5, GP_Gen8IM, GP_Gen8IH, BC_Gen4, BC_Gen5, BC_Gen8IM or BC_Gen8IH." + } +} + +variable "storage_size_in_gb" { + description = "Maximum storage space for the SQL Managed instance. This should be a multiple of 32 (GB). Defaults to 32." + type = number + default = 32 + validation { + condition = var.storage_size_in_gb % 32 == 0 + error_message = "Invalid storage space. Must be a multiple of 32." + } +} + +variable "subnet_id" { + description = "The subnet resource id that the SQL Managed Instance will be associated with. Changing this forces a new resource to be created." + type = string +} + +variable "vcores" { + description = "Number of cores that should be assigned to the SQL Managed Instance. Values can be 8, 16, or 24 for Gen4 SKUs, or 4, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64, 80, 96 or 128 for Gen5 SKUs. Defaults to 4." + type = number + default = 4 + validation { + condition = contains( + [4, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64, 80, 96, 128], + var.vcores + ) + error_message = "Invalid number of cores. Values can be 8, 16, or 24 for Gen4 SKUs, or 4, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64, 80, 96 or 128 for Gen5 SKUs." + } +} + +variable "tags" { + description = "A mapping of tags to assign to the resource." + type = map(string) + default = null +} diff --git a/modules/azurerm-network-security-group/README.md b/modules/azurerm-network-security-group/README.md new file mode 100644 index 0000000..5ff6737 --- /dev/null +++ b/modules/azurerm-network-security-group/README.md @@ -0,0 +1,37 @@ + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [azurerm](#provider\_azurerm) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [azurerm_network_security_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_group) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [location](#input\_location) | Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [name](#input\_name) | Specifies the name of the network security group. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [resource\_group\_name](#input\_resource\_group\_name) | The name of the resource group in which to create the network security group. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [security\_rules](#input\_security\_rules) | List of security\_rule objects representing security rules, see the [azurerm documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_group#security_rule) for more details. |
list(
object(
{
access = string
description = optional(string)
destination_address_prefix = optional(string)
destination_address_prefixes = optional(list(string))
destination_port_range = optional(string)
destination_port_ranges = optional(list(string))
direction = string
name = string
priority = number
protocol = string
source_address_prefix = optional(string)
source_address_prefixes = optional(list(string))
source_port_range = optional(string)
source_port_ranges = optional(list(string))
}
)
)
| `[]` | no | +| [tags](#input\_tags) | A mapping of tags to assign to the resource. | `map(string)` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [this](#output\_this) | Azure Network Security Group and Rules. | + \ No newline at end of file diff --git a/modules/azurerm-network-security-group/main.tf b/modules/azurerm-network-security-group/main.tf new file mode 100644 index 0000000..29f8bf4 --- /dev/null +++ b/modules/azurerm-network-security-group/main.tf @@ -0,0 +1,28 @@ +resource "azurerm_network_security_group" "this" { + name = var.name + location = var.location + resource_group_name = var.resource_group_name + tags = var.tags + + dynamic "security_rule" { + # If security_rule is not defined, do not create + for_each = var.security_rules != null ? var.security_rules : [] + + content { + access = security_rule.value.access + name = security_rule.value.name + description = security_rule.value.description + destination_address_prefix = security_rule.value.destination_address_prefix + destination_address_prefixes = security_rule.value.destination_address_prefixes + destination_port_range = security_rule.value.destination_port_range + destination_port_ranges = security_rule.value.destination_port_ranges + direction = security_rule.value.direction + priority = security_rule.value.priority + protocol = security_rule.value.protocol + source_address_prefix = security_rule.value.source_address_prefix + source_address_prefixes = security_rule.value.source_address_prefixes + source_port_range = security_rule.value.source_port_range + source_port_ranges = security_rule.value.source_port_ranges + } + } +} diff --git a/modules/azurerm-network-security-group/outputs.tf b/modules/azurerm-network-security-group/outputs.tf new file mode 100644 index 0000000..ee3e58c --- /dev/null +++ b/modules/azurerm-network-security-group/outputs.tf @@ -0,0 +1,4 @@ +output "this" { + description = "Azure Network Security Group and Rules." + value = azurerm_network_security_group.this +} diff --git a/modules/azurerm-network-security-group/variables.tf b/modules/azurerm-network-security-group/variables.tf new file mode 100644 index 0000000..2814804 --- /dev/null +++ b/modules/azurerm-network-security-group/variables.tf @@ -0,0 +1,46 @@ +variable "name" { + description = "Specifies the name of the network security group. Changing this forces a new resource to be created. " + type = string +} + +variable "location" { + description = "Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created." + type = string +} + +variable "resource_group_name" { + description = "The name of the resource group in which to create the network security group. Changing this forces a new resource to be created." + type = string +} + +variable "security_rules" { + description = "List of security_rule objects representing security rules, see the [azurerm documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_group#security_rule) for more details." + type = list( + object( + { + access = string + description = optional(string) + destination_address_prefix = optional(string) + destination_address_prefixes = optional(list(string)) + destination_port_range = optional(string) + destination_port_ranges = optional(list(string)) + direction = string + name = string + priority = number + protocol = string + source_address_prefix = optional(string) + source_address_prefixes = optional(list(string)) + source_port_range = optional(string) + source_port_ranges = optional(list(string)) + } + ) + ) + default = [] +} + +variable "tags" { + description = "A mapping of tags to assign to the resource." + type = map(string) + default = null +} + diff --git a/modules/azurerm-route-table/README.md b/modules/azurerm-route-table/README.md new file mode 100644 index 0000000..7a54929 --- /dev/null +++ b/modules/azurerm-route-table/README.md @@ -0,0 +1,37 @@ + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [azurerm](#provider\_azurerm) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [azurerm_route_table.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/route_table) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [location](#input\_location) | Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [name](#input\_name) | The name of the route table. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [resource\_group\_name](#input\_resource\_group\_name) | The name of the resource group in which to create the route table. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [route](#input\_route) | A list of route objects. See the [azure documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/route_table) for more details. |
list(
object(
{
name = string
address_prefix = string
next_hop_type = string
next_hop_in_ip_address = optional(string)
}
)
)
| `null` | no | +| [tags](#input\_tags) | A mapping of tags to assign to the resource. | `map(string)` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [this](#output\_this) | Azure Route Table and Routes. | + \ No newline at end of file diff --git a/modules/azurerm-route-table/main.tf b/modules/azurerm-route-table/main.tf new file mode 100644 index 0000000..c17ca90 --- /dev/null +++ b/modules/azurerm-route-table/main.tf @@ -0,0 +1,18 @@ +resource "azurerm_route_table" "this" { + location = var.location + name = var.name + resource_group_name = var.resource_group_name + tags = var.tags + + dynamic "route" { + # If route is not defined, do not create + for_each = var.route != null ? var.route : [] + + content { + name = route.value.name + address_prefix = route.value.address_prefix + next_hop_type = route.value.next_hop_type + next_hop_in_ip_address = route.value.next_hop_in_ip_address + } + } +} diff --git a/modules/azurerm-route-table/outputs.tf b/modules/azurerm-route-table/outputs.tf new file mode 100644 index 0000000..d4f4bb7 --- /dev/null +++ b/modules/azurerm-route-table/outputs.tf @@ -0,0 +1,4 @@ +output "this" { + description = "Azure Route Table and Routes." + value = azurerm_route_table.this +} diff --git a/modules/azurerm-route-table/variables.tf b/modules/azurerm-route-table/variables.tf new file mode 100644 index 0000000..d70d5e8 --- /dev/null +++ b/modules/azurerm-route-table/variables.tf @@ -0,0 +1,35 @@ +variable "location" { + description = "Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created." + type = string +} + +variable "name" { + description = "The name of the route table. Changing this forces a new resource to be created. " + type = string +} + +variable "resource_group_name" { + description = "The name of the resource group in which to create the route table. Changing this forces a new resource to be created." + type = string +} + +variable "tags" { + description = "A mapping of tags to assign to the resource." + type = map(string) + default = null +} + +variable "route" { + description = "A list of route objects. See the [azure documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/route_table) for more details." + type = list( + object( + { + name = string + address_prefix = string + next_hop_type = string + next_hop_in_ip_address = optional(string) + } + ) + ) + default = null +} diff --git a/modules/azurerm-virtual-network/README.md b/modules/azurerm-virtual-network/README.md new file mode 100644 index 0000000..292c298 --- /dev/null +++ b/modules/azurerm-virtual-network/README.md @@ -0,0 +1,38 @@ + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [azurerm](#provider\_azurerm) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [azurerm_virtual_network.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [address\_space](#input\_address\_space) | The address space that is used the virtual network. You can supply more than one address space. | `list(string)` | n/a | yes | +| [location](#input\_location) | The location/region where the virtual network is created. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [name](#input\_name) | The name of the virtual network. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [resource\_group\_name](#input\_resource\_group\_name) | The name of the resource group in which to create the virtual network. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [subnets](#input\_subnets) | A list of subnet objects as defined below.

subnet:
- address\_prefixes: A list of address prefixes to use for the subnet
- default\_outbound\_access\_enabled: (Optional) Enable default outbound access to the internet for the subnet. Defaults to true.
- name: The name of the subnet
- route\_table\_id: (Optional) The ID of the Route Table that should be associated with this subnet.
- security\_group: (Optional) The Network Security Group to associate with the subnet. (Referenced by id, ie. azurerm\_network\_security\_group.example.id)
- delegation: (Optional) One or more delegation blocks as defined below.

delegation:
- name: A name for this delegation.
- service\_delegation: A service\_delegation block as defined below.

service\_delegation:
- name: The name of service to delegate to.
- actions: (Optional) A list of Actions which should be delegated. This list is specific to the service to delegate to. See the [azure documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet#service_delegation) for the full list of possible values. |
list(
object(
{
address_prefixes = list(string),
default_outbound_access_enabled = optional(bool, true)
name = string
route_table_id = optional(string)
security_group = optional(string)

delegation = optional(list(
object(
{
name = string

service_delegation = optional(list(
object(
{
name = string
actions = optional(list(string))
}
)
))
}
)
))
}
)
)
| `null` | no | +| [tags](#input\_tags) | A mapping of tags to assign to the resource. | `map(string)` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [this](#output\_this) | Azure Virtual Network and Subnets. | + \ No newline at end of file diff --git a/modules/azurerm-virtual-network/main.tf b/modules/azurerm-virtual-network/main.tf new file mode 100644 index 0000000..e19fa24 --- /dev/null +++ b/modules/azurerm-virtual-network/main.tf @@ -0,0 +1,40 @@ +resource "azurerm_virtual_network" "this" { + address_space = var.address_space + location = var.location + name = var.name + resource_group_name = var.resource_group_name + tags = var.tags + + dynamic "subnet" { + # If subnet is not defined, do not create + for_each = var.subnets != null ? var.subnets : [] + + content { + address_prefixes = subnet.value.address_prefixes + default_outbound_access_enabled = subnet.value.default_outbound_access_enabled + name = subnet.value.name + route_table_id = subnet.value.route_table_id + security_group = subnet.value.security_group + + dynamic "delegation" { + # If delegation is not defined, do not create + for_each = subnet.value.delegation != null ? subnet.value.delegation : [] + + content { + name = delegation.value.name + + dynamic "service_delegation" { + # If service_delegation is not defined, do not create + for_each = delegation.value.service_delegation != null ? delegation.value.service_delegation : [] + + content { + name = service_delegation.value.name + actions = service_delegation.value.actions + } + } + } + } + + } + } +} diff --git a/modules/azurerm-virtual-network/outputs.tf b/modules/azurerm-virtual-network/outputs.tf new file mode 100644 index 0000000..c245e79 --- /dev/null +++ b/modules/azurerm-virtual-network/outputs.tf @@ -0,0 +1,4 @@ +output "this" { + description = "Azure Virtual Network and Subnets." + value = azurerm_virtual_network.this +} diff --git a/modules/azurerm-virtual-network/variables.tf b/modules/azurerm-virtual-network/variables.tf new file mode 100644 index 0000000..48af0bf --- /dev/null +++ b/modules/azurerm-virtual-network/variables.tf @@ -0,0 +1,76 @@ +variable "address_space" { + description = "The address space that is used the virtual network. You can supply more than one address space." + type = list(string) +} + +variable "location" { + description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." + type = string +} + +variable "name" { + description = "The name of the virtual network. Changing this forces a new resource to be created." + type = string +} + +variable "resource_group_name" { + description = "The name of the resource group in which to create the virtual network. Changing this forces a new resource to be created." + type = string +} + +variable "subnets" { + description = < +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [dsfhub](#provider\_dsfhub) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [dsfhub_data_source.this](https://registry.terraform.io/providers/imperva/dsfhub/latest/docs/resources/data_source) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [admin\_email](#input\_admin\_email) | The email address to notify about the asset. | `string` | n/a | yes | +| [asset\_display\_name](#input\_asset\_display\_name) | User-friendly name of the asset, defined by user | `string` | n/a | yes | +| [asset\_id](#input\_asset\_id) | The Azure resource ID of the SQL Managed instance. | `string` | n/a | yes | +| [audit\_pull\_enabled](#input\_audit\_pull\_enabled) | If true, sonargateway will collect the audit logs for this system if it can. | `bool` | `false` | no | +| [auth\_mechanism](#input\_auth\_mechanism) | Specifies the auth mechanism used by the connection | `string` | `null` | no | +| [database\_name](#input\_database\_name) | Specifies the name of the database to connect to (or default DB). | `string` | `"master"` | no | +| [gateway\_id](#input\_gateway\_id) | Unique identifier (UID) attached to the jSonar machine controlling the asset | `string` | n/a | yes | +| [location](#input\_location) | Physical location/region of the SQL Managed instance, e.g. "EAST US" | `string` | `null` | no | +| [logs\_destination\_asset\_id](#input\_logs\_destination\_asset\_id) | The asset\_id of the AZURE EVENTHUB asset that this instance is sending its audit logs to. | `string` | `null` | no | +| [parent\_asset\_id](#input\_parent\_asset\_id) | The asset\_id of the AZURE asset representing the Azure account where this server is located. | `string` | `null` | no | +| [password](#input\_password) | Password to use to connect to the SQL Managed instance. | `string` | `null` | no | +| [reason](#input\_reason) | Used to differentiate connections that belong to the same asset | `string` | `"default"` | no | +| [server\_host\_name](#input\_server\_host\_name) | Hostname of the SQL Managed instance. | `string` | n/a | yes | +| [server\_ip](#input\_server\_ip) | IP address / hostname of the SQL Managed instance. | `string` | n/a | yes | +| [username](#input\_username) | Username of SQL database user to connect with. | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [this](#output\_this) | AZURE SQL MANAGED INSTANCE asset. | + \ No newline at end of file diff --git a/modules/dsfhub-azure-sql-managed-instance/main.tf b/modules/dsfhub-azure-sql-managed-instance/main.tf new file mode 100644 index 0000000..b6c0eab --- /dev/null +++ b/modules/dsfhub-azure-sql-managed-instance/main.tf @@ -0,0 +1,36 @@ +terraform { + required_providers { + dsfhub = { + source = "imperva/dsfhub" + } + } +} + +resource "dsfhub_data_source" "this" { + server_type = "AZURE SQL MANAGED INSTANCE" + + admin_email = var.admin_email + asset_display_name = var.asset_display_name + asset_id = var.asset_id + audit_pull_enabled = var.audit_pull_enabled + database_name = var.database_name + gateway_id = var.gateway_id + location = var.location + logs_destination_asset_id = var.logs_destination_asset_id + parent_asset_id = var.parent_asset_id + server_host_name = var.server_host_name + server_ip = var.server_ip + server_port = "3342" + + dynamic "asset_connection" { + # If auth_mechanism is not defined, do not create a connection + for_each = var.auth_mechanism != null ? [0] : [] + + content { + auth_mechanism = var.auth_mechanism + password = var.password + reason = var.reason + username = var.username + } + } +} diff --git a/modules/dsfhub-azure-sql-managed-instance/outputs.tf b/modules/dsfhub-azure-sql-managed-instance/outputs.tf new file mode 100644 index 0000000..08b55c9 --- /dev/null +++ b/modules/dsfhub-azure-sql-managed-instance/outputs.tf @@ -0,0 +1,4 @@ +output "this" { + description = "AZURE SQL MANAGED INSTANCE asset." + value = dsfhub_data_source.this +} diff --git a/modules/dsfhub-azure-sql-managed-instance/variables.tf b/modules/dsfhub-azure-sql-managed-instance/variables.tf new file mode 100644 index 0000000..acf31f4 --- /dev/null +++ b/modules/dsfhub-azure-sql-managed-instance/variables.tf @@ -0,0 +1,90 @@ +variable "admin_email" { + description = "The email address to notify about the asset." + type = string +} + +variable "asset_display_name" { + description = "User-friendly name of the asset, defined by user" + type = string +} + +variable "asset_id" { + description = "The Azure resource ID of the SQL Managed instance." + type = string +} + +variable "audit_pull_enabled" { + description = "If true, sonargateway will collect the audit logs for this system if it can." + type = bool + default = false +} + +variable "auth_mechanism" { + description = "Specifies the auth mechanism used by the connection" + type = string + default = null + validation { + condition = ( + var.auth_mechanism == null || + can(contains(["password"], var.auth_mechanism)) + ) + error_message = "Invalid authentication mechanism. Supported values: password" + } +} + +variable "database_name" { + description = "Specifies the name of the database to connect to (or default DB)." + type = string + default = "master" +} + +variable "gateway_id" { + description = "Unique identifier (UID) attached to the jSonar machine controlling the asset" + type = string +} + +variable "location" { + description = "Physical location/region of the SQL Managed instance, e.g. \"EAST US\"" + type = string + default = null +} + +variable "logs_destination_asset_id" { + description = "The asset_id of the AZURE EVENTHUB asset that this instance is sending its audit logs to." + type = string + default = null +} + +variable "password" { + description = "Password to use to connect to the SQL Managed instance." + type = string + default = null +} + +variable "parent_asset_id" { + description = "The asset_id of the AZURE asset representing the Azure account where this server is located." + type = string + default = null +} + +variable "reason" { + description = "Used to differentiate connections that belong to the same asset" + type = string + default = "default" +} + +variable "server_host_name" { + description = "Hostname of the SQL Managed instance." + type = string +} + +variable "server_ip" { + description = "IP address / hostname of the SQL Managed instance." + type = string +} + +variable "username" { + description = "Username of SQL database user to connect with." + type = string + default = null +} diff --git a/modules/onboard-azure-sql-managed-instance/README.md b/modules/onboard-azure-sql-managed-instance/README.md new file mode 100644 index 0000000..19371e8 --- /dev/null +++ b/modules/onboard-azure-sql-managed-instance/README.md @@ -0,0 +1,86 @@ +# onboard-azure-sql-managed-instance + +Onboard Azure SQL Managed Instance to DSF Hub. + +## Notes +There are three prerequisites for using this module: +1. An Azure Event Hub Namespace and Event Hub. +2. An Azure Storage Account and Container. This is used by DSF to store a marker which keeps track of what data in the Event Hub has been consumed. +3. A method to create a server audit policy on the managed instance. + +The first two can be created along with an AZURE EVENTHUB asset via the ``onboard-azure-eventhub`` module. + +See the corresponding example for more details. + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [azure-ms-sql-managed-instance](#module\_azure-ms-sql-managed-instance) | ../azurerm-mssql-managed-instance | n/a | +| [azure-sql-managed-instance-asset](#module\_azure-sql-managed-instance-asset) | ../dsfhub-azure-sql-managed-instance | n/a | +| [diagnostic-setting](#module\_diagnostic-setting) | ../azurerm-monitor-diagnostic-setting | n/a | +| [route-table](#module\_route-table) | ../azurerm-route-table | n/a | +| [security-group](#module\_security-group) | ../azurerm-network-security-group | n/a | +| [virtual-network](#module\_virtual-network) | ../azurerm-virtual-network | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [azure\_sql\_managed\_instance\_admin\_email](#input\_azure\_sql\_managed\_instance\_admin\_email) | The email address to notify about the asset. | `string` | n/a | yes | +| [azure\_sql\_managed\_instance\_audit\_pull\_enabled](#input\_azure\_sql\_managed\_instance\_audit\_pull\_enabled) | If true, sonargateway will collect the audit logs for this system if it can. | `bool` | `false` | no | +| [azure\_sql\_managed\_instance\_auth\_mechanism](#input\_azure\_sql\_managed\_instance\_auth\_mechanism) | Specifies the auth mechanism used by the connection | `string` | `null` | no | +| [azure\_sql\_managed\_instance\_database\_name](#input\_azure\_sql\_managed\_instance\_database\_name) | Specifies the name of the database to connect to (or default DB). | `string` | `"master"` | no | +| [azure\_sql\_managed\_instance\_gateway\_id](#input\_azure\_sql\_managed\_instance\_gateway\_id) | Unique identifier (UID) attached to the jSonar machine controlling the asset | `string` | n/a | yes | +| [azure\_sql\_managed\_instance\_location](#input\_azure\_sql\_managed\_instance\_location) | Physical location/region of the SQL Managed instance, e.g. "EAST US" | `string` | `null` | no | +| [azure\_sql\_managed\_instance\_logs\_destination\_asset\_id](#input\_azure\_sql\_managed\_instance\_logs\_destination\_asset\_id) | The asset\_id of the AZURE EVENTHUB asset that this instance is sending its audit logs to. | `string` | `null` | no | +| [azure\_sql\_managed\_instance\_parent\_asset\_id](#input\_azure\_sql\_managed\_instance\_parent\_asset\_id) | The asset\_id of the AZURE asset representing the Azure account where this server is located. | `string` | `null` | no | +| [azure\_sql\_managed\_instance\_password](#input\_azure\_sql\_managed\_instance\_password) | Password to use to connect to the SQL Managed instance. | `string` | `null` | no | +| [azure\_sql\_managed\_instance\_reason](#input\_azure\_sql\_managed\_instance\_reason) | Used to differentiate connections that belong to the same asset | `string` | `"default"` | no | +| [azure\_sql\_managed\_instance\_username](#input\_azure\_sql\_managed\_instance\_username) | Username of SQL database user to connect with. | `string` | `null` | no | +| [diagnostic\_setting\_eventhub\_authorization\_rule\_id](#input\_diagnostic\_setting\_eventhub\_authorization\_rule\_id) | Specifies the ID of an Event Hub Namespace Authorization Rule used to send Diagnostics Data. | `string` | `null` | no | +| [diagnostic\_setting\_eventhub\_name](#input\_diagnostic\_setting\_eventhub\_name) | Specifies the name of the Event Hub where Diagnostics Data should be sent. | `string` | `null` | no | +| [diagnostic\_setting\_name](#input\_diagnostic\_setting\_name) | Specifies the name of the Diagnostic Setting. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [managed\_instance\_administrator\_login](#input\_managed\_instance\_administrator\_login) | The administrator login name for the new SQL Managed Instance. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [managed\_instance\_administrator\_login\_password](#input\_managed\_instance\_administrator\_login\_password) | The password associated with the administrator\_login user. Needs to comply with Azure's [Password Policy](https://learn.microsoft.com/en-us/sql/relational-databases/security/password-policy?view=sql-server-ver16&redirectedfrom=MSDN) | `string` | n/a | yes | +| [managed\_instance\_license\_type](#input\_managed\_instance\_license\_type) | What type of license the Managed Instance will use. Possible values are LicenseIncluded and BasePrice. Defaults to LicenseIncluded. | `string` | `"LicenseIncluded"` | no | +| [managed\_instance\_location](#input\_managed\_instance\_location) | Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [managed\_instance\_name](#input\_managed\_instance\_name) | The name of the SQL Managed Instance. This needs to be globally unique within Azure. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [managed\_instance\_public\_data\_endpoint\_enabled](#input\_managed\_instance\_public\_data\_endpoint\_enabled) | Is the public data endpoint enabled? Default value is false. | `bool` | `false` | no | +| [managed\_instance\_resource\_group\_name](#input\_managed\_instance\_resource\_group\_name) | The name of the resource group in which to create the SQL Managed Instance. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [managed\_instance\_sku\_name](#input\_managed\_instance\_sku\_name) | Specifies the SKU Name for the SQL Managed Instance. Valid values include GP\_Gen4, GP\_Gen5, GP\_Gen8IM, GP\_Gen8IH, BC\_Gen4, BC\_Gen5, BC\_Gen8IM or BC\_Gen8IH. Defaults to GP\_Gen5. | `string` | `"GP_Gen5"` | no | +| [managed\_instance\_storage\_size\_in\_gb](#input\_managed\_instance\_storage\_size\_in\_gb) | Maximum storage space for the SQL Managed instance. This should be a multiple of 32 (GB). Defaults to 32. | `number` | `32` | no | +| [managed\_instance\_tags](#input\_managed\_instance\_tags) | A mapping of tags to assign to the resource. | `map(string)` | `null` | no | +| [managed\_instance\_vcores](#input\_managed\_instance\_vcores) | Number of cores that should be assigned to the SQL Managed Instance. Values can be 8, 16, or 24 for Gen4 SKUs, or 4, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64, 80, 96 or 128 for Gen5 SKUs. Defaults to 4. | `number` | `4` | no | +| [route\_table\_resource\_group\_name](#input\_route\_table\_resource\_group\_name) | The name of the resource group in which to create the network route table. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [security\_group\_resource\_group\_name](#input\_security\_group\_resource\_group\_name) | The name of the resource group in which to create the network security group. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [security\_group\_security\_rules](#input\_security\_group\_security\_rules) | List of security\_rule objects representing security rules, see the [azurerm documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_group#security_rule) for more details. These will be applied in addition to the default security rule settings for Managed Instances. |
list(
object(
{
access = string
description = optional(string)
destination_address_prefix = optional(string)
destination_address_prefixes = optional(list(string))
destination_port_range = optional(string)
destination_port_ranges = optional(list(string))
direction = string
name = string
priority = number
protocol = string
source_address_prefix = optional(string)
source_address_prefixes = optional(list(string))
source_port_range = optional(string)
source_port_ranges = optional(list(string))

}
)
)
| `[]` | no | +| [security\_group\_tags](#input\_security\_group\_tags) | A mapping of tags to assign to the resource. | `map(string)` | `null` | no | +| [virtual\_network\_resource\_group\_name](#input\_virtual\_network\_resource\_group\_name) | The name of the resource group in which to create the virtual network. Changing this forces a new resource to be created. | `string` | n/a | yes | +| [virtual\_network\_tags](#input\_virtual\_network\_tags) | A mapping of tags to assign to the resource. | `map(string)` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [azure-ms-sql-managed-instance](#output\_azure-ms-sql-managed-instance) | Microsoft SQL Azure Managed Instance. | +| [azure-sql-managed-instance-asset](#output\_azure-sql-managed-instance-asset) | AZURE SQL MANAGED INSTANCE asset. | +| [default-managed-instance-security-rules](#output\_default-managed-instance-security-rules) | Security rules required for Managed Instances by default. | +| [diagnostic-setting](#output\_diagnostic-setting) | Diagnostic Setting. | +| [route-table](#output\_route-table) | Azure Managed Instance Route Table. | +| [security-group](#output\_security-group) | Azure Network Security Group. | +| [virtual-network](#output\_virtual-network) | Azure Managed Instance Virtual Network. | + \ No newline at end of file diff --git a/modules/onboard-azure-sql-managed-instance/main.tf b/modules/onboard-azure-sql-managed-instance/main.tf new file mode 100644 index 0000000..0351a13 --- /dev/null +++ b/modules/onboard-azure-sql-managed-instance/main.tf @@ -0,0 +1,240 @@ +# Networking +locals { + default_managed_instance_security_rules = [ + { + name = "allow_management_inbound" + access = "Allow" + description = null + destination_address_prefix = "*" + destination_address_prefixes = null + destination_port_range = null + destination_port_ranges = ["9000", "9003", "1438", "1440", "1452"] + direction = "Inbound" + priority = 106 + protocol = "Tcp" + source_address_prefix = "*" + source_address_prefixes = null + source_port_range = "*" + source_port_ranges = null + }, + { + name = "allow_misubnet_inbound" + access = "Allow" + description = null + destination_address_prefix = "*" + destination_address_prefixes = null + destination_port_range = "*" + destination_port_ranges = null + direction = "Inbound" + priority = 200 + protocol = "*" + source_address_prefix = "10.0.0.0/24" + source_address_prefixes = null + source_port_range = "*" + source_port_ranges = null + }, + { + name = "allow_health_probe_inbound" + access = "Allow" + description = null + destination_address_prefix = "*" + destination_address_prefixes = null + destination_port_range = "*" + destination_port_ranges = null + direction = "Inbound" + priority = 300 + protocol = "*" + source_address_prefix = "AzureLoadBalancer" + source_address_prefixes = null + source_port_range = "*" + source_port_ranges = null + }, + { + name = "allow_tds_inbound" + access = "Allow" + description = null + destination_address_prefix = "*" + destination_address_prefixes = null + destination_port_range = "1433" + destination_port_ranges = null + direction = "Inbound" + priority = 1000 + protocol = "Tcp" + source_address_prefix = "VirtualNetwork" + source_address_prefixes = null + source_port_range = "*" + source_port_ranges = null + }, + { + name = "deny_all_inbound" + access = "Deny" + description = null + destination_address_prefix = "*" + destination_address_prefixes = null + destination_port_range = "*" + destination_port_ranges = null + direction = "Inbound" + priority = 4096 + protocol = "*" + source_address_prefix = "*" + source_address_prefixes = null + source_port_range = "*" + source_port_ranges = null + }, + { + name = "allow_management_outbound" + access = "Allow" + description = null + destination_address_prefix = "*" + destination_address_prefixes = null + destination_port_range = null + destination_port_ranges = ["80", "443", "12000"] + direction = "Outbound" + priority = 102 + protocol = "Tcp" + source_address_prefix = "*" + source_address_prefixes = null + source_port_range = "*" + source_port_ranges = null + }, + { + name = "allow_misubnet_outbound" + access = "Allow" + description = null + destination_address_prefix = "*" + destination_address_prefixes = null + destination_port_range = "*" + destination_port_ranges = null + direction = "Outbound" + priority = 200 + protocol = "*" + source_address_prefix = "10.0.0.0/24" + source_address_prefixes = null + source_port_range = "*" + source_port_ranges = null + }, + { + name = "deny_all_outbound" + access = "Deny" + description = null + destination_address_prefix = "*" + destination_address_prefixes = null + destination_port_range = "*" + destination_port_ranges = null + direction = "Outbound" + priority = 4096 + protocol = "*" + source_address_prefix = "*" + source_address_prefixes = null + source_port_range = "*" + source_port_ranges = null + }, + ] + merged_managed_instance_security_rules = concat( + local.default_managed_instance_security_rules, + var.security_group_security_rules + ) +} + +module "security-group" { + source = "../azurerm-network-security-group" + + location = var.managed_instance_location + name = "${var.managed_instance_name}-security-group" + resource_group_name = var.security_group_resource_group_name + security_rules = local.merged_managed_instance_security_rules + tags = var.security_group_tags +} + +module "route-table" { + source = "../azurerm-route-table" + + location = var.managed_instance_location + name = "${var.managed_instance_name}-route-table" + resource_group_name = var.route_table_resource_group_name +} + +module "virtual-network" { + source = "../azurerm-virtual-network" + + address_space = ["10.0.0.0/16"] + location = var.managed_instance_location + name = "vnet-${var.managed_instance_name}" + resource_group_name = var.virtual_network_resource_group_name + + subnets = [ + { + address_prefixes = ["10.0.0.0/24"] + name = "subnet-${var.managed_instance_name}" + route_table_id = module.route-table.this.id + security_group = module.security-group.this.id + + delegation = [ + { + name = "managedinstancedelegation" + service_delegation = [ + { + name = "Microsoft.Sql/managedInstances" + actions = [ + "Microsoft.Network/virtualNetworks/subnets/join/action", + "Microsoft.Network/virtualNetworks/subnets/prepareNetworkPolicies/action", + "Microsoft.Network/virtualNetworks/subnets/unprepareNetworkPolicies/action" + ] + } + ] + } + ] + } + ] +} + + +# Managed Instance +module "azure-ms-sql-managed-instance" { + source = "../azurerm-mssql-managed-instance" + + administrator_login = var.managed_instance_administrator_login + administrator_login_password = var.managed_instance_administrator_login_password + license_type = var.managed_instance_license_type + location = var.managed_instance_location + name = var.managed_instance_name + public_data_endpoint_enabled = var.managed_instance_public_data_endpoint_enabled + resource_group_name = var.managed_instance_resource_group_name + sku_name = var.managed_instance_sku_name + storage_size_in_gb = var.managed_instance_storage_size_in_gb + subnet_id = tolist(module.virtual-network.this.subnet)[0].id + tags = var.managed_instance_tags + vcores = var.managed_instance_vcores +} + +module "diagnostic-setting" { + source = "../azurerm-monitor-diagnostic-setting" + + enabled_log = [{ category = "SQLSecurityAuditEvents" }] + eventhub_authorization_rule_id = var.diagnostic_setting_eventhub_authorization_rule_id + eventhub_name = var.diagnostic_setting_eventhub_name + metric = null + name = var.diagnostic_setting_name + storage_account_id = null + target_resource_id = module.azure-ms-sql-managed-instance.this.id +} + +module "azure-sql-managed-instance-asset" { + source = "../dsfhub-azure-sql-managed-instance" + + admin_email = var.azure_sql_managed_instance_admin_email + asset_display_name = module.azure-ms-sql-managed-instance.this.name + asset_id = module.azure-ms-sql-managed-instance.this.id + audit_pull_enabled = var.azure_sql_managed_instance_audit_pull_enabled + auth_mechanism = var.azure_sql_managed_instance_auth_mechanism + database_name = var.azure_sql_managed_instance_database_name + gateway_id = var.azure_sql_managed_instance_gateway_id + location = var.azure_sql_managed_instance_location + logs_destination_asset_id = var.azure_sql_managed_instance_logs_destination_asset_id + parent_asset_id = var.azure_sql_managed_instance_parent_asset_id + password = var.azure_sql_managed_instance_password + reason = var.azure_sql_managed_instance_reason + server_host_name = module.azure-ms-sql-managed-instance.this.fqdn + server_ip = module.azure-ms-sql-managed-instance.this.fqdn + username = var.azure_sql_managed_instance_username +} diff --git a/modules/onboard-azure-sql-managed-instance/outputs.tf b/modules/onboard-azure-sql-managed-instance/outputs.tf new file mode 100644 index 0000000..fb96af1 --- /dev/null +++ b/modules/onboard-azure-sql-managed-instance/outputs.tf @@ -0,0 +1,35 @@ +output "default-managed-instance-security-rules" { + description = "Security rules required for Managed Instances by default." + value = local.default_managed_instance_security_rules +} + +output "security-group" { + description = "Azure Network Security Group." + value = module.security-group.this +} + +output "route-table" { + description = "Azure Managed Instance Route Table." + value = module.route-table.this +} + +output "virtual-network" { + description = "Azure Managed Instance Virtual Network." + value = module.virtual-network.this +} + +output "azure-ms-sql-managed-instance" { + description = "Microsoft SQL Azure Managed Instance." + value = module.azure-ms-sql-managed-instance.this +} + +output "diagnostic-setting" { + description = "Diagnostic Setting." + value = module.diagnostic-setting.this +} + +output "azure-sql-managed-instance-asset" { + description = "AZURE SQL MANAGED INSTANCE asset." + value = module.azure-sql-managed-instance-asset.this +} + diff --git a/modules/onboard-azure-sql-managed-instance/variables.tf b/modules/onboard-azure-sql-managed-instance/variables.tf new file mode 100644 index 0000000..b004b11 --- /dev/null +++ b/modules/onboard-azure-sql-managed-instance/variables.tf @@ -0,0 +1,223 @@ +variable "azure_sql_managed_instance_admin_email" { + description = "The email address to notify about the asset." + type = string +} + +variable "azure_sql_managed_instance_audit_pull_enabled" { + description = "If true, sonargateway will collect the audit logs for this system if it can." + type = bool + default = false +} + +variable "azure_sql_managed_instance_auth_mechanism" { + description = "Specifies the auth mechanism used by the connection" + type = string + default = null + validation { + condition = ( + var.azure_sql_managed_instance_auth_mechanism == null || + can(contains(["password"], var.azure_sql_managed_instance_auth_mechanism)) + ) + error_message = "Invalid authentication mechanism. Supported values: password" + } +} + +variable "azure_sql_managed_instance_database_name" { + description = "Specifies the name of the database to connect to (or default DB)." + type = string + default = "master" +} + +variable "azure_sql_managed_instance_gateway_id" { + description = "Unique identifier (UID) attached to the jSonar machine controlling the asset" + type = string +} + +variable "azure_sql_managed_instance_location" { + description = "Physical location/region of the SQL Managed instance, e.g. \"EAST US\"" + type = string + default = null +} + +variable "azure_sql_managed_instance_logs_destination_asset_id" { + description = "The asset_id of the AZURE EVENTHUB asset that this instance is sending its audit logs to." + type = string + default = null +} + +variable "azure_sql_managed_instance_password" { + description = "Password to use to connect to the SQL Managed instance." + type = string + default = null +} + +variable "azure_sql_managed_instance_parent_asset_id" { + description = "The asset_id of the AZURE asset representing the Azure account where this server is located." + type = string + default = null +} + +variable "azure_sql_managed_instance_reason" { + description = "Used to differentiate connections that belong to the same asset" + type = string + default = "default" +} + +variable "azure_sql_managed_instance_username" { + description = "Username of SQL database user to connect with." + type = string + default = null +} + +variable "diagnostic_setting_eventhub_authorization_rule_id" { + description = "Specifies the ID of an Event Hub Namespace Authorization Rule used to send Diagnostics Data. " + type = string + default = null +} + +variable "diagnostic_setting_eventhub_name" { + description = "Specifies the name of the Event Hub where Diagnostics Data should be sent." + type = string + default = null +} + +variable "diagnostic_setting_name" { + description = "Specifies the name of the Diagnostic Setting. Changing this forces a new resource to be created." + type = string +} + +variable "managed_instance_administrator_login" { + description = "The administrator login name for the new SQL Managed Instance. Changing this forces a new resource to be created." + type = string +} + +variable "managed_instance_administrator_login_password" { + description = "The password associated with the administrator_login user. Needs to comply with Azure's [Password Policy](https://learn.microsoft.com/en-us/sql/relational-databases/security/password-policy?view=sql-server-ver16&redirectedfrom=MSDN)" + type = string +} + +variable "managed_instance_license_type" { + description = "What type of license the Managed Instance will use. Possible values are LicenseIncluded and BasePrice. Defaults to LicenseIncluded." + type = string + default = "LicenseIncluded" + validation { + condition = contains(["LicenseIncluded", "BasePrice"], var.managed_instance_license_type) + error_message = "Invalid license type. Possible values are LicenseIncluded and BasePrice." + } +} + +variable "managed_instance_location" { + description = "Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created." + type = string +} + +variable "managed_instance_name" { + description = "The name of the SQL Managed Instance. This needs to be globally unique within Azure. Changing this forces a new resource to be created." + type = string +} + +variable "managed_instance_public_data_endpoint_enabled" { + description = "Is the public data endpoint enabled? Default value is false." + type = bool + default = false +} + +variable "managed_instance_resource_group_name" { + description = "The name of the resource group in which to create the SQL Managed Instance. Changing this forces a new resource to be created." + type = string +} + +variable "managed_instance_sku_name" { + description = "Specifies the SKU Name for the SQL Managed Instance. Valid values include GP_Gen4, GP_Gen5, GP_Gen8IM, GP_Gen8IH, BC_Gen4, BC_Gen5, BC_Gen8IM or BC_Gen8IH. Defaults to GP_Gen5." + type = string + default = "GP_Gen5" + validation { + condition = contains( + ["GP_Gen4", "GP_Gen5", "GP_GEN8IM", "GP_Gen8IH", "BC_Gen4", "BC_Gen5", "BC_Gen81M", "BC_Gen81H"], + var.managed_instance_sku_name + ) + error_message = "Invalid SKU. Valid values include GP_Gen4, GP_Gen5, GP_Gen8IM, GP_Gen8IH, BC_Gen4, BC_Gen5, BC_Gen8IM or BC_Gen8IH." + } +} + +variable "managed_instance_storage_size_in_gb" { + description = "Maximum storage space for the SQL Managed instance. This should be a multiple of 32 (GB). Defaults to 32." + type = number + default = 32 + validation { + condition = var.managed_instance_storage_size_in_gb % 32 == 0 + error_message = "Invalid storage space. Must be a multiple of 32." + } +} + +variable "managed_instance_vcores" { + description = "Number of cores that should be assigned to the SQL Managed Instance. Values can be 8, 16, or 24 for Gen4 SKUs, or 4, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64, 80, 96 or 128 for Gen5 SKUs. Defaults to 4." + type = number + default = 4 + validation { + condition = contains( + [4, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64, 80, 96, 128], + var.managed_instance_vcores + ) + error_message = "Invalid number of cores. Values can be 8, 16, or 24 for Gen4 SKUs, or 4, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64, 80, 96 or 128 for Gen5 SKUs." + } +} + +variable "managed_instance_tags" { + description = "A mapping of tags to assign to the resource." + type = map(string) + default = null +} + +variable "route_table_resource_group_name" { + description = "The name of the resource group in which to create the network route table. Changing this forces a new resource to be created." + type = string +} + +variable "security_group_resource_group_name" { + description = "The name of the resource group in which to create the network security group. Changing this forces a new resource to be created." + type = string +} + +variable "security_group_security_rules" { + description = "List of security_rule objects representing security rules, see the [azurerm documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_group#security_rule) for more details. These will be applied in addition to the default security rule settings for Managed Instances." + type = list( + object( + { + access = string + description = optional(string) + destination_address_prefix = optional(string) + destination_address_prefixes = optional(list(string)) + destination_port_range = optional(string) + destination_port_ranges = optional(list(string)) + direction = string + name = string + priority = number + protocol = string + source_address_prefix = optional(string) + source_address_prefixes = optional(list(string)) + source_port_range = optional(string) + source_port_ranges = optional(list(string)) + + } + ) + ) + default = [] +} + +variable "security_group_tags" { + description = "A mapping of tags to assign to the resource." + type = map(string) + default = null +} + +variable "virtual_network_resource_group_name" { + description = "The name of the resource group in which to create the virtual network. Changing this forces a new resource to be created." + type = string +} + +variable "virtual_network_tags" { + description = "A mapping of tags to assign to the resource." + type = map(string) + default = null +} \ No newline at end of file