Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure SQL Managed Instance module #17

Merged
merged 16 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions examples/onboard-azure-sql-managed-instance/README.md
Original file line number Diff line number Diff line change
@@ -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.

<!-- BEGIN_TF_DOCS -->
## Requirements

No requirements.

## Providers

| Name | Version |
|------|---------|
| <a name="provider_http"></a> [http](#provider\_http) | n/a |
| <a name="provider_terraform"></a> [terraform](#provider\_terraform) | n/a |

## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_onboard-azure-sql-managed-instance-eventhub-1"></a> [onboard-azure-sql-managed-instance-eventhub-1](#module\_onboard-azure-sql-managed-instance-eventhub-1) | ../../modules/onboard-azure-eventhub | n/a |
| <a name="module_sql-managed-instance-1"></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 |
|------|-------------|------|---------|:--------:|
| <a name="input_dsfhub_host"></a> [dsfhub\_host](#input\_dsfhub\_host) | n/a | `any` | n/a | yes |
| <a name="input_dsfhub_token"></a> [dsfhub\_token](#input\_dsfhub\_token) | n/a | `any` | n/a | yes |

## Outputs

No outputs.
<!-- END_TF_DOCS -->
101 changes: 101 additions & 0 deletions examples/onboard-azure-sql-managed-instance/configure_audit_policy.sql
Original file line number Diff line number Diff line change
@@ -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
31 changes: 31 additions & 0 deletions examples/onboard-azure-sql-managed-instance/configure_database.sh
Original file line number Diff line number Diff line change
@@ -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
146 changes: 146 additions & 0 deletions examples/onboard-azure-sql-managed-instance/main.tf
Original file line number Diff line number Diff line change
@@ -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
}
44 changes: 44 additions & 0 deletions modules/azurerm-mssql-managed-instance/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!-- BEGIN_TF_DOCS -->
## Requirements

No requirements.

## Providers

| Name | Version |
|------|---------|
| <a name="provider_azurerm"></a> [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 |
|------|-------------|------|---------|:--------:|
| <a name="input_administrator_login"></a> [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 |
| <a name="input_administrator_login_password"></a> [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 |
| <a name="input_license_type"></a> [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 |
| <a name="input_location"></a> [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 |
| <a name="input_name"></a> [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 |
| <a name="input_public_data_endpoint_enabled"></a> [public\_data\_endpoint\_enabled](#input\_public\_data\_endpoint\_enabled) | Is the public data endpoint enabled? Default value is false. | `bool` | `false` | no |
| <a name="input_resource_group_name"></a> [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 |
| <a name="input_sku_name"></a> [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 |
| <a name="input_storage_size_in_gb"></a> [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 |
| <a name="input_subnet_id"></a> [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 |
| <a name="input_tags"></a> [tags](#input\_tags) | A mapping of tags to assign to the resource. | `map(string)` | `null` | no |
| <a name="input_vcores"></a> [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 |
|------|-------------|
| <a name="output_this"></a> [this](#output\_this) | Azure SQL Managed Instance. |
<!-- END_TF_DOCS -->
Loading