From 39565cc5ab1245e4e6a6368c19fd0aa9a187733a Mon Sep 17 00:00:00 2001 From: jamesthompson26-nhs Date: Tue, 17 Mar 2026 16:05:41 +0000 Subject: [PATCH 1/8] CCM-14316: Gateway Cluster Deployment --- .../terraform/components/reporting/README.md | 1 + .../autoscaling_group_powerbi_gateway.tf | 25 ------- .../autoscaling_schedule_scale_in.tf | 11 --- .../autoscaling_schedule_scale_out.tf | 11 --- .../ec2_instances_powerbi_gateway.tf | 3 +- .../iam_instance_profile_powerbi_gateway.tf | 18 ----- .../launch_template_powerbi_gateway_asg.tf | 71 ------------------- .../terraform/components/reporting/locals.tf | 11 +-- .../ssm_maintenance_window_patch_window.tf | 12 ---- ...tenance_window_target_windows_instances.tf | 15 ---- .../ssm_maintenance_window_task_patch_task.tf | 29 -------- ...ssm_parameter_powerbi_gateway_client_id.tf | 14 ---- ...parameter_powerbi_gateway_client_secret.tf | 14 ---- ..._parameter_powerbi_gateway_recovery_key.tf | 2 +- ...ssm_parameter_powerbi_gateway_tenant_id.tf | 14 ---- .../reporting/templates/cloudinit_config.tmpl | 27 +------ .../components/reporting/variables.tf | 6 ++ .../terraform/etc/env_eu-west-2_main.tfvars | 2 + 18 files changed, 16 insertions(+), 270 deletions(-) delete mode 100644 infrastructure/terraform/components/reporting/autoscaling_group_powerbi_gateway.tf delete mode 100644 infrastructure/terraform/components/reporting/autoscaling_schedule_scale_in.tf delete mode 100644 infrastructure/terraform/components/reporting/autoscaling_schedule_scale_out.tf delete mode 100644 infrastructure/terraform/components/reporting/launch_template_powerbi_gateway_asg.tf delete mode 100644 infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_client_id.tf delete mode 100644 infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_client_secret.tf delete mode 100644 infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_tenant_id.tf diff --git a/infrastructure/terraform/components/reporting/README.md b/infrastructure/terraform/components/reporting/README.md index 82985d40..4c57c0ed 100644 --- a/infrastructure/terraform/components/reporting/README.md +++ b/infrastructure/terraform/components/reporting/README.md @@ -13,6 +13,7 @@ No requirements. | [account\_name](#input\_account\_name) | The name of the AWS Account to deploy into (see globals.tfvars) | `string` | n/a | yes | | [app\_deployer\_role\_name](#input\_app\_deployer\_role\_name) | Name of the app deployer role that is allowed to deploy Comms Mgr applications but not create other IAM roles | `string` | n/a | yes | | [app\_deployer\_role\_permission\_account\_ids](#input\_app\_deployer\_role\_permission\_account\_ids) | All AWS Account IDs for this project that have the AppDeployer role created | `map(string)` | `{}` | no | +| [athena\_driver\_url](#input\_athena\_driver\_url) | Amazon Athena ODBC MSI download URL for PowerBI gateway bootstrap | `string` | `"https://downloads.athena.us-east-1.amazonaws.com/drivers/ODBC/v2.1.0.0/Windows/AmazonAthenaODBC-2.1.0.0.msi"` | no | | [aws\_account\_id](#input\_aws\_account\_id) | The AWS Account ID (numeric) | `string` | n/a | yes | | [batch\_client\_ids](#input\_batch\_client\_ids) | List of client ids that require additional batch identifier dimensions when aggregating data | `list(string)` |
[
"NULL"
]
| no | | [cloudtrail\_log\_group\_name](#input\_cloudtrail\_log\_group\_name) | The name of the Cloudtrail log group name on the account (see globals.tfvars) | `string` | n/a | yes | diff --git a/infrastructure/terraform/components/reporting/autoscaling_group_powerbi_gateway.tf b/infrastructure/terraform/components/reporting/autoscaling_group_powerbi_gateway.tf deleted file mode 100644 index 5e7c6872..00000000 --- a/infrastructure/terraform/components/reporting/autoscaling_group_powerbi_gateway.tf +++ /dev/null @@ -1,25 +0,0 @@ -resource "aws_autoscaling_group" "powerbi_gateway" { - count = var.enable_powerbi_gateway ? 1 : 0 - - name = local.csi - - launch_template { - id = aws_launch_template.powerbi_gateway[0].id - version = "$Latest" - } - - vpc_zone_identifier = module.powerbi_gateway_vpc[0].private_subnets - desired_capacity = var.desired_capacity - min_size = var.min_size - max_size = var.max_size - - tag { - key = "Name" - value = "${local.csi}-powerbi-gateway-instance" - propagate_at_launch = true - } - - health_check_type = "EC2" - health_check_grace_period = 300 - wait_for_capacity_timeout = "0" -} diff --git a/infrastructure/terraform/components/reporting/autoscaling_schedule_scale_in.tf b/infrastructure/terraform/components/reporting/autoscaling_schedule_scale_in.tf deleted file mode 100644 index 57d330a2..00000000 --- a/infrastructure/terraform/components/reporting/autoscaling_schedule_scale_in.tf +++ /dev/null @@ -1,11 +0,0 @@ -resource "aws_autoscaling_schedule" "scale_in" { - count = var.enable_powerbi_gateway && var.scale_in_recurrence_schedule != null ? 1 : 0 - - scheduled_action_name = "${local.csi}-scale-in" - desired_capacity = 0 - min_size = 0 - max_size = -1 - autoscaling_group_name = aws_autoscaling_group.powerbi_gateway[0].name - - recurrence = coalesce(var.scale_in_recurrence_schedule, null) -} diff --git a/infrastructure/terraform/components/reporting/autoscaling_schedule_scale_out.tf b/infrastructure/terraform/components/reporting/autoscaling_schedule_scale_out.tf deleted file mode 100644 index 50c71f55..00000000 --- a/infrastructure/terraform/components/reporting/autoscaling_schedule_scale_out.tf +++ /dev/null @@ -1,11 +0,0 @@ -resource "aws_autoscaling_schedule" "scale_out" { - count = var.enable_powerbi_gateway && var.scale_out_recurrence_schedule != null ? 1 : 0 - - scheduled_action_name = "${local.csi}-scale-out" - desired_capacity = var.desired_capacity - min_size = var.min_size - max_size = var.max_size - autoscaling_group_name = aws_autoscaling_group.powerbi_gateway[0].name - - recurrence = coalesce(var.scale_in_recurrence_schedule, null) -} diff --git a/infrastructure/terraform/components/reporting/ec2_instances_powerbi_gateway.tf b/infrastructure/terraform/components/reporting/ec2_instances_powerbi_gateway.tf index 9ec3ffa6..3ccd593b 100644 --- a/infrastructure/terraform/components/reporting/ec2_instances_powerbi_gateway.tf +++ b/infrastructure/terraform/components/reporting/ec2_instances_powerbi_gateway.tf @@ -8,6 +8,7 @@ resource "aws_instance" "powerbi_gateway_standalone" { } tags = { - Name = format("%s-powerbi-gateway-standalone-%02d", local.csi, count.index + 1) + "Name" = format("%s-powerbi-gateway-standalone-%02d", local.csi, count.index + 1) + "Patch Group" = aws_ssm_patch_group.windows_patch_group[0].patch_group } } diff --git a/infrastructure/terraform/components/reporting/iam_instance_profile_powerbi_gateway.tf b/infrastructure/terraform/components/reporting/iam_instance_profile_powerbi_gateway.tf index 53b16b72..e2a1efee 100644 --- a/infrastructure/terraform/components/reporting/iam_instance_profile_powerbi_gateway.tf +++ b/infrastructure/terraform/components/reporting/iam_instance_profile_powerbi_gateway.tf @@ -223,22 +223,4 @@ data "aws_iam_policy_document" "powerbi_gateway_permissions_policy" { aws_kms_key.s3.arn ] } - - statement { - sid = "AllowSSMAccess" - effect = "Allow" - - actions = [ - "ssm:GetParameter", - "ssm:GetParameters", - "ssm:GetParameterHistory", - ] - - resources = [ - aws_ssm_parameter.powerbi_gateway_recovery_key[0].arn, - aws_ssm_parameter.powerbi_gateway_client_id[0].arn, - aws_ssm_parameter.powerbi_gateway_client_secret[0].arn, - aws_ssm_parameter.powerbi_gateway_tenant_id[0].arn - ] - } } diff --git a/infrastructure/terraform/components/reporting/launch_template_powerbi_gateway_asg.tf b/infrastructure/terraform/components/reporting/launch_template_powerbi_gateway_asg.tf deleted file mode 100644 index 00c0a657..00000000 --- a/infrastructure/terraform/components/reporting/launch_template_powerbi_gateway_asg.tf +++ /dev/null @@ -1,71 +0,0 @@ -resource "aws_launch_template" "powerbi_gateway" { - count = var.enable_powerbi_gateway ? 1 : 0 - - name = local.csi - description = "Template for the Power BI On-Premises Gateway" - update_default_version = true - image_id = "resolve:ssm:/aws/service/ami-windows-latest/Windows_Server-2022-English-Full-Base" - instance_type = var.instance_type - user_data = data.cloudinit_config.powerbi_gateway[0].rendered - instance_initiated_shutdown_behavior = var.enable_spot ? "terminate" : "stop" - ebs_optimized = true - - block_device_mappings { - device_name = "/dev/sda1" - ebs { - delete_on_termination = true - encrypted = true - kms_key_id = aws_kms_key.ebs[0].arn - volume_size = var.root_volume_size - volume_type = "gp3" - } - } - - iam_instance_profile { - name = aws_iam_instance_profile.powerbi_gateway[0].name - } - - dynamic "instance_market_options" { - for_each = var.enable_spot ? [1] : [] - content { - market_type = "spot" - spot_options { - max_price = var.spot_max_price - spot_instance_type = "one-time" - } - } - } - - monitoring { - enabled = true - } - - network_interfaces { - delete_on_termination = true - associate_public_ip_address = false - security_groups = [ - aws_security_group.powerbi_gateway[0].id - ] - subnet_id = element(module.powerbi_gateway_vpc[0].private_subnets, count.index) - } - - metadata_options { - http_endpoint = "enabled" - http_tokens = "required" - http_put_response_hop_limit = 5 - } - - tag_specifications { - resource_type = "instance" - tags = merge(local.deployment_default_tags, - { - "Patch Group" = "${local.csi}-windows-group" - } - ) - } - - tag_specifications { - resource_type = "volume" - tags = local.deployment_default_tags - } -} diff --git a/infrastructure/terraform/components/reporting/locals.tf b/infrastructure/terraform/components/reporting/locals.tf index 3d4c48ab..761faa69 100644 --- a/infrastructure/terraform/components/reporting/locals.tf +++ b/infrastructure/terraform/components/reporting/locals.tf @@ -54,26 +54,17 @@ locals { this_account = local.base_parameter_bundle.account_ids[local.base_parameter_bundle.account_name] - # Check if each required SSM parameter exists individually - recovery_key = length(aws_ssm_parameter.powerbi_gateway_recovery_key) > 0 ? aws_ssm_parameter.powerbi_gateway_recovery_key[0].name : null - client_secret = length(aws_ssm_parameter.powerbi_gateway_client_secret) > 0 ? aws_ssm_parameter.powerbi_gateway_client_secret[0].name : null - client_id = length(aws_ssm_parameter.powerbi_gateway_client_id) > 0 ? aws_ssm_parameter.powerbi_gateway_client_id[0].name : null - tenant_id = length(aws_ssm_parameter.powerbi_gateway_tenant_id) > 0 ? aws_ssm_parameter.powerbi_gateway_tenant_id[0].name : null - # Create the powerbi_gateway_script only if var.enable_powerbi_gateway is true powerbi_gateway_script = var.enable_powerbi_gateway ? templatefile("${path.module}/templates/cloudinit_config.tmpl", { odbc_dsn_name = "${local.csi}-dsn" odbc_description = "AWS Simba Athena ODBC Connection for ${local.csi}" + athena_driver_url = var.athena_driver_url region = var.region catalog = "AWSDataCatalog" database = aws_glue_catalog_database.reporting.name workgroup = aws_athena_workgroup.user.name authentication_type = "Instance Profile" gateway_name = "${local.csi}-gateway" - recovery_key = local.recovery_key - client_secret = local.client_secret - client_id = local.client_id - tenant_id = local.tenant_id }) : null use_core_glue_catalog_resources = length(var.core_account_ids) > 0 ? true : false diff --git a/infrastructure/terraform/components/reporting/ssm_maintenance_window_patch_window.tf b/infrastructure/terraform/components/reporting/ssm_maintenance_window_patch_window.tf index ea6005ec..456bc65b 100644 --- a/infrastructure/terraform/components/reporting/ssm_maintenance_window_patch_window.tf +++ b/infrastructure/terraform/components/reporting/ssm_maintenance_window_patch_window.tf @@ -19,15 +19,3 @@ resource "aws_ssm_maintenance_window" "patch_window_wednesday" { cutoff = 1 allow_unassociated_targets = true } - -## Remove me later - replaced by above two windows -resource "aws_ssm_maintenance_window" "patch_window" { - count = var.enable_powerbi_gateway ? 1 : 0 - - name = "${local.csi}-windows-patch-window" - description = "Windows Server 2022 Patch Window" - schedule = "cron(0 3 ? * SUN *)" # Every Sunday at 3 AM - duration = 4 - cutoff = 1 - allow_unassociated_targets = true -} diff --git a/infrastructure/terraform/components/reporting/ssm_maintenance_window_target_windows_instances.tf b/infrastructure/terraform/components/reporting/ssm_maintenance_window_target_windows_instances.tf index af913ec6..ee1854bc 100644 --- a/infrastructure/terraform/components/reporting/ssm_maintenance_window_target_windows_instances.tf +++ b/infrastructure/terraform/components/reporting/ssm_maintenance_window_target_windows_instances.tf @@ -25,18 +25,3 @@ resource "aws_ssm_maintenance_window_target" "windows_instances_wednesday" { values = [aws_instance.powerbi_gateway_standalone[1].id] } } - -## Remove me later - replaced by above two targets -resource "aws_ssm_maintenance_window_target" "windows_instances" { - count = var.enable_powerbi_gateway ? 1 : 0 - - description = "Windows Server 2022 Maintenance Window Target" - window_id = aws_ssm_maintenance_window.patch_window[0].id - resource_type = "INSTANCE" - name = "${local.csi}-maintenance-window-target" - - targets { - key = "tag:Patch Group" - values = ["${local.csi}-windows-group"] - } -} diff --git a/infrastructure/terraform/components/reporting/ssm_maintenance_window_task_patch_task.tf b/infrastructure/terraform/components/reporting/ssm_maintenance_window_task_patch_task.tf index a17e151e..4f7d5b9e 100644 --- a/infrastructure/terraform/components/reporting/ssm_maintenance_window_task_patch_task.tf +++ b/infrastructure/terraform/components/reporting/ssm_maintenance_window_task_patch_task.tf @@ -61,32 +61,3 @@ resource "aws_ssm_maintenance_window_task" "patch_task_wednesday" { max_concurrency = "1" max_errors = "1" } - -## Remove me later - replaced by above two tasks -resource "aws_ssm_maintenance_window_task" "patch_task" { - count = var.enable_powerbi_gateway ? 1 : 0 - - description = "Windows Server 2022 Patch Task" - window_id = aws_ssm_maintenance_window.patch_window[0].id - task_arn = "AWS-RunPatchBaseline" - task_type = "RUN_COMMAND" - - targets { - key = "WindowTargetIds" - values = [aws_ssm_maintenance_window_target.windows_instances[0].id] - } - - task_invocation_parameters { - run_command_parameters { - comment = "Patching Windows Instances" - parameter { - name = "Operation" - values = ["Install"] - } - } - } - - priority = 1 - max_concurrency = "2" - max_errors = "1" -} diff --git a/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_client_id.tf b/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_client_id.tf deleted file mode 100644 index 581498a8..00000000 --- a/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_client_id.tf +++ /dev/null @@ -1,14 +0,0 @@ -resource "aws_ssm_parameter" "powerbi_gateway_client_id" { - count = var.enable_powerbi_gateway ? 1 : 0 - - name = "/${local.csi}/powerbi-gateway-client-id" - description = "The Client (Application) ID for the Service Principal" - type = "SecureString" - value = "CLIENT_ID_PLACEHOLDER" - - lifecycle { - ignore_changes = [ - value, - ] - } -} diff --git a/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_client_secret.tf b/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_client_secret.tf deleted file mode 100644 index abff954e..00000000 --- a/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_client_secret.tf +++ /dev/null @@ -1,14 +0,0 @@ -resource "aws_ssm_parameter" "powerbi_gateway_client_secret" { - count = var.enable_powerbi_gateway ? 1 : 0 - - name = "/${local.csi}/powerbi-gateway-client-secret" - description = "The Client Secret for the Service Principal" - type = "SecureString" - value = "CLIENT_SECRET_PLACEHOLDER" - - lifecycle { - ignore_changes = [ - value, - ] - } -} diff --git a/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_recovery_key.tf b/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_recovery_key.tf index 367ebb26..eec82770 100644 --- a/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_recovery_key.tf +++ b/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_recovery_key.tf @@ -2,7 +2,7 @@ resource "aws_ssm_parameter" "powerbi_gateway_recovery_key" { count = var.enable_powerbi_gateway ? 1 : 0 name = "/${local.csi}/powerbi-gateway-recovery-key" - description = "The Recovery Key for the On-Premises Gateway" + description = "The Recovery Key for the On-Premises Gateway - Updated manually with the actual key value after deployment" type = "SecureString" value = "RECOVERY_KEY_PLACEHOLDER" diff --git a/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_tenant_id.tf b/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_tenant_id.tf deleted file mode 100644 index eac5550c..00000000 --- a/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_tenant_id.tf +++ /dev/null @@ -1,14 +0,0 @@ -resource "aws_ssm_parameter" "powerbi_gateway_tenant_id" { - count = var.enable_powerbi_gateway ? 1 : 0 - - name = "/${local.csi}/powerbi-gateway-tenant-id" - description = "The Tenant ID for the Service Principal" - type = "SecureString" - value = "TENANT_ID_PLACEHOLDER" - - lifecycle { - ignore_changes = [ - value, - ] - } -} diff --git a/infrastructure/terraform/components/reporting/templates/cloudinit_config.tmpl b/infrastructure/terraform/components/reporting/templates/cloudinit_config.tmpl index 5555c9f1..fa3c5a84 100644 --- a/infrastructure/terraform/components/reporting/templates/cloudinit_config.tmpl +++ b/infrastructure/terraform/components/reporting/templates/cloudinit_config.tmpl @@ -19,18 +19,14 @@ if (-not (Get-Command choco -ErrorAction SilentlyContinue)) { } # Install PowerBI On-Premises Gateway and Desktop -choco install -y powerbigateway --version=3000.298.8 --ignore-checksums -choco install -y powerbi --ignore-checksums - -# Install vim -choco install -y vim +choco install -y powerbigateway --ignore-checksums # Install Powershell 7 choco install -y powershell-core # Install Amazon Athena ODBC 2.x Driver -`$athenaDriverUrl = "https://downloads.athena.us-east-1.amazonaws.com/drivers/ODBC/v2.0.6.0/Windows/AmazonAthenaODBC-2.0.6.0.msi" -`$athenaDriverInstaller = "C:\scripts\SimbaAthenaODBC.msi" +`$athenaDriverUrl = "${athena_driver_url}" +`$athenaDriverInstaller = "C:\scripts\AmazonAthenaODBC.msi" Invoke-WebRequest -Uri `$athenaDriverUrl -OutFile `$athenaDriverInstaller # Silent installation of Amazon Athena ODBC driver @@ -85,23 +81,6 @@ if (Test-Path `$pwshPath) { Write-Output "AWS CLI installation failed or not found in PATH." exit 1 } - - # Get the client (application) and tenant id's - `$clientId = aws ssm get-parameter --name ${client_id} --with-decryption --query Parameter.Value --output text - `$tenantId = aws ssm get-parameter --name ${tenant_id} --with-decryption --query Parameter.Value --output text - - # Get and convert the client secret and recovery key to SecureStrings - `$clientSecretPlainText = aws ssm get-parameter --name ${client_secret} --with-decryption --query Parameter.Value --output text - `$clientSecret = `$clientSecretPlainText | ConvertTo-SecureString -AsPlainText -Force - `$recoveryKeyPlainText = aws ssm get-parameter --name ${recovery_key} --with-decryption --query Parameter.Value --output text - `$recoveryKey = `$recoveryKeyPlainText | ConvertTo-SecureString -AsPlainText -Force - - # Login to the PowerBI Service using the Service Principal (proposed, untested) https://learn.microsoft.com/en-us/powershell/module/datagateway.profile/connect-datagatewayserviceaccount?view=datagateway-ps - # Connect-DataGatewayServiceAccount -ApplicationId `$clientId -ClientSecret `$clientSecret -Tenant `$tenantId - # Install the gateway - # Install-DataGateway -AcceptConditions - # Register the Gateway (proposed, untested) https://learn.microsoft.com/en-us/powershell/module/datagateway/add-datagatewaycluster?view=datagateway-ps - # Add-DataGatewayCluster -RecoveryKey `$recoveryKey -Name ${gateway_name} -RegionKey uksouth } } else { diff --git a/infrastructure/terraform/components/reporting/variables.tf b/infrastructure/terraform/components/reporting/variables.tf index 3a1c983a..b7e2c7e5 100644 --- a/infrastructure/terraform/components/reporting/variables.tf +++ b/infrastructure/terraform/components/reporting/variables.tf @@ -103,6 +103,12 @@ variable "enable_powerbi_gateway" { default = true } +variable "athena_driver_url" { + type = string + description = "Amazon Athena ODBC MSI download URL for PowerBI gateway bootstrap" + default = "https://downloads.athena.us-east-1.amazonaws.com/drivers/ODBC/v2.1.0.0/Windows/AmazonAthenaODBC-2.1.0.0.msi" +} + variable "powerbi_gateway_instance_count" { description = "Number of standalone Power BI On-Premises Gateway instances created directly from the launch template." type = number diff --git a/infrastructure/terraform/etc/env_eu-west-2_main.tfvars b/infrastructure/terraform/etc/env_eu-west-2_main.tfvars index d3662737..150c6057 100644 --- a/infrastructure/terraform/etc/env_eu-west-2_main.tfvars +++ b/infrastructure/terraform/etc/env_eu-west-2_main.tfvars @@ -15,6 +15,8 @@ core_account_ids = [ # PowerBI On-Premises Gateway variables: enable_powerbi_gateway = true +instance_type = "t3.xlarge" +root_volume_size = 200 public_subnet_cidrs = [ "10.0.1.0/24", From e4994ce0801305d6350bb9e604acdd757c05cea9 Mon Sep 17 00:00:00 2001 From: jamesthompson26-nhs Date: Tue, 5 May 2026 10:12:18 +0100 Subject: [PATCH 2/8] CCM-15541: Fix Gitignores --- .gitleaksignore | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitleaksignore b/.gitleaksignore index 374d0269..732cac32 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -1,6 +1,6 @@ # SEE: https://github.com/gitleaks/gitleaks/blob/master/README.md#gitleaksignore -9469a5a10e20b5c3275ba055e65ba98e7d11e9d2:infrastructure/terraform/components/reporting/README.md:ipv4:16 -9469a5a10e20b5c3275ba055e65ba98e7d11e9d2:infrastructure/terraform/components/reporting/variables.tf:ipv4:109 -ca243cb73d3804a14f3eeefa8073c96802420c52:infrastructure/terraform/etc/env_eu-west-2_int.tfvars:generic-api-key:29 -ca243cb73d3804a14f3eeefa8073c96802420c52:infrastructure/terraform/etc/env_eu-west-2_prod.tfvars:generic-api-key:43 +39565cc5ab1245e4e6a6368c19fd0aa9a187733a:infrastructure/terraform/components/reporting/README.md:ipv4:16 +39565cc5ab1245e4e6a6368c19fd0aa9a187733a:infrastructure/terraform/components/reporting/variables.tf:ipv4:109 +d38af4e4f6c36ca9c3d843193b434386a9bad5ee:infrastructure/terraform/etc/env_eu-west-2_int.tfvars:generic-api-key:29 +d38af4e4f6c36ca9c3d843193b434386a9bad5ee:infrastructure/terraform/etc/env_eu-west-2_prod.tfvars:generic-api-key:43 From d1168f0aba7e5db8a0d0566f08210c2609fa6fc5 Mon Sep 17 00:00:00 2001 From: jamesthompson26-nhs Date: Tue, 5 May 2026 10:14:49 +0100 Subject: [PATCH 3/8] CCM-15541: Fix Gitignores --- .gitleaksignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitleaksignore b/.gitleaksignore index 732cac32..70ce96c8 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -2,5 +2,7 @@ 39565cc5ab1245e4e6a6368c19fd0aa9a187733a:infrastructure/terraform/components/reporting/README.md:ipv4:16 39565cc5ab1245e4e6a6368c19fd0aa9a187733a:infrastructure/terraform/components/reporting/variables.tf:ipv4:109 +ca243cb73d3804a14f3eeefa8073c96802420c52:infrastructure/terraform/etc/env_eu-west-2_int.tfvars:generic-api-key:29 +ca243cb73d3804a14f3eeefa8073c96802420c52:infrastructure/terraform/etc/env_eu-west-2_prod.tfvars:generic-api-key:43 d38af4e4f6c36ca9c3d843193b434386a9bad5ee:infrastructure/terraform/etc/env_eu-west-2_int.tfvars:generic-api-key:29 d38af4e4f6c36ca9c3d843193b434386a9bad5ee:infrastructure/terraform/etc/env_eu-west-2_prod.tfvars:generic-api-key:43 From 429486b307d987cfebc4bfb2b2605dfbb717e45c Mon Sep 17 00:00:00 2001 From: jamesthompson26-nhs Date: Tue, 5 May 2026 11:29:41 +0100 Subject: [PATCH 4/8] CCM-15541: Remove PowerBI install - will do manually --- .../components/reporting/templates/cloudinit_config.tmpl | 3 --- 1 file changed, 3 deletions(-) diff --git a/infrastructure/terraform/components/reporting/templates/cloudinit_config.tmpl b/infrastructure/terraform/components/reporting/templates/cloudinit_config.tmpl index fa3c5a84..399b8d98 100644 --- a/infrastructure/terraform/components/reporting/templates/cloudinit_config.tmpl +++ b/infrastructure/terraform/components/reporting/templates/cloudinit_config.tmpl @@ -18,9 +18,6 @@ if (-not (Get-Command choco -ErrorAction SilentlyContinue)) { iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) } -# Install PowerBI On-Premises Gateway and Desktop -choco install -y powerbigateway --ignore-checksums - # Install Powershell 7 choco install -y powershell-core From e863a3e19766fd9d9e4e0a70e2fa90e6a9f009e9 Mon Sep 17 00:00:00 2001 From: jamesthompson26-nhs Date: Tue, 5 May 2026 11:48:30 +0100 Subject: [PATCH 5/8] CCM-15541: Testing Server 2025 --- .../reporting/launch_template_powerbi_gateway_standalone.tf | 2 +- .../reporting/ssm_maintenance_window_patch_window.tf | 4 ++-- .../ssm_maintenance_window_target_windows_instances.tf | 4 ++-- .../reporting/ssm_maintenance_window_task_patch_task.tf | 4 ++-- .../reporting/ssm_patch_baseline_windows_patch_baseline.tf | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/infrastructure/terraform/components/reporting/launch_template_powerbi_gateway_standalone.tf b/infrastructure/terraform/components/reporting/launch_template_powerbi_gateway_standalone.tf index 1199f786..8a5946e2 100644 --- a/infrastructure/terraform/components/reporting/launch_template_powerbi_gateway_standalone.tf +++ b/infrastructure/terraform/components/reporting/launch_template_powerbi_gateway_standalone.tf @@ -4,7 +4,7 @@ resource "aws_launch_template" "powerbi_gateway_standalone" { name = "${local.csi}-standalone" description = "Template for the Power BI On-Premises Gateway (standalone instances)" update_default_version = true - image_id = "resolve:ssm:/aws/service/ami-windows-latest/Windows_Server-2022-English-Full-Base" + image_id = "resolve:ssm:/aws/service/ami-windows-latest/Windows_Server-2025-English-Full-Base" instance_type = var.instance_type user_data = data.cloudinit_config.powerbi_gateway[0].rendered instance_initiated_shutdown_behavior = var.enable_spot ? "terminate" : "stop" diff --git a/infrastructure/terraform/components/reporting/ssm_maintenance_window_patch_window.tf b/infrastructure/terraform/components/reporting/ssm_maintenance_window_patch_window.tf index 456bc65b..e5f35a5e 100644 --- a/infrastructure/terraform/components/reporting/ssm_maintenance_window_patch_window.tf +++ b/infrastructure/terraform/components/reporting/ssm_maintenance_window_patch_window.tf @@ -2,7 +2,7 @@ resource "aws_ssm_maintenance_window" "patch_window_sunday" { count = var.enable_powerbi_gateway ? 1 : 0 name = "${local.csi}-windows-patch-window-sun" - description = "Windows Server 2022 Sunday Patch Window" + description = "Windows Server 2025 Sunday Patch Window" schedule = "cron(0 3 ? * SUN *)" # Every Sunday at 3 AM duration = 4 cutoff = 1 @@ -13,7 +13,7 @@ resource "aws_ssm_maintenance_window" "patch_window_wednesday" { count = var.enable_powerbi_gateway ? 1 : 0 name = "${local.csi}-windows-patch-window-wed" - description = "Windows Server 2022 Wednesday Patch Window" + description = "Windows Server 2025 Wednesday Patch Window" schedule = "cron(0 3 ? * WED *)" # Every Wednesday at 3 AM duration = 4 cutoff = 1 diff --git a/infrastructure/terraform/components/reporting/ssm_maintenance_window_target_windows_instances.tf b/infrastructure/terraform/components/reporting/ssm_maintenance_window_target_windows_instances.tf index ee1854bc..d4193bc1 100644 --- a/infrastructure/terraform/components/reporting/ssm_maintenance_window_target_windows_instances.tf +++ b/infrastructure/terraform/components/reporting/ssm_maintenance_window_target_windows_instances.tf @@ -1,7 +1,7 @@ resource "aws_ssm_maintenance_window_target" "windows_instances_sunday" { count = var.enable_powerbi_gateway && var.powerbi_gateway_instance_count >= 1 ? 1 : 0 - description = "Windows Server 2022 Sunday Maintenance Window Target " + description = "Windows Server 2025 Sunday Maintenance Window Target " window_id = aws_ssm_maintenance_window.patch_window_sunday[0].id resource_type = "INSTANCE" name = "${local.csi}-maintenance-window-target-sun" @@ -15,7 +15,7 @@ resource "aws_ssm_maintenance_window_target" "windows_instances_sunday" { resource "aws_ssm_maintenance_window_target" "windows_instances_wednesday" { count = var.enable_powerbi_gateway && var.powerbi_gateway_instance_count >= 2 ? 1 : 0 - description = "Windows Server 2022 Wednesday Maintenance Window Target" + description = "Windows Server 2025 Wednesday Maintenance Window Target" window_id = aws_ssm_maintenance_window.patch_window_wednesday[0].id resource_type = "INSTANCE" name = "${local.csi}-maintenance-window-target-wed" diff --git a/infrastructure/terraform/components/reporting/ssm_maintenance_window_task_patch_task.tf b/infrastructure/terraform/components/reporting/ssm_maintenance_window_task_patch_task.tf index 4f7d5b9e..3e0e5aa5 100644 --- a/infrastructure/terraform/components/reporting/ssm_maintenance_window_task_patch_task.tf +++ b/infrastructure/terraform/components/reporting/ssm_maintenance_window_task_patch_task.tf @@ -1,7 +1,7 @@ resource "aws_ssm_maintenance_window_task" "patch_task_sunday" { count = var.enable_powerbi_gateway ? 1 : 0 - description = "Windows Server 2022 Sunday Patch Task" + description = "Windows Server 2025 Sunday Patch Task" window_id = aws_ssm_maintenance_window.patch_window_sunday[0].id task_arn = "AWS-RunPatchBaseline" task_type = "RUN_COMMAND" @@ -33,7 +33,7 @@ resource "aws_ssm_maintenance_window_task" "patch_task_sunday" { resource "aws_ssm_maintenance_window_task" "patch_task_wednesday" { count = var.enable_powerbi_gateway && var.powerbi_gateway_instance_count >= 2 ? 1 : 0 - description = "Windows Server 2022 Wednesday Patch Task" + description = "Windows Server 2025 Wednesday Patch Task" window_id = aws_ssm_maintenance_window.patch_window_wednesday[0].id task_arn = "AWS-RunPatchBaseline" task_type = "RUN_COMMAND" diff --git a/infrastructure/terraform/components/reporting/ssm_patch_baseline_windows_patch_baseline.tf b/infrastructure/terraform/components/reporting/ssm_patch_baseline_windows_patch_baseline.tf index 992c477c..59a8fd1c 100644 --- a/infrastructure/terraform/components/reporting/ssm_patch_baseline_windows_patch_baseline.tf +++ b/infrastructure/terraform/components/reporting/ssm_patch_baseline_windows_patch_baseline.tf @@ -2,12 +2,12 @@ resource "aws_ssm_patch_baseline" "windows_patch_baseline" { count = var.enable_powerbi_gateway ? 1 : 0 name = "${local.csi}-windows-patch-baseline" - description = "Windows Server 2022 Patch Baseline" + description = "Windows Server 2025 Patch Baseline" operating_system = "WINDOWS" approval_rule { patch_filter { key = "PRODUCT" - values = ["WindowsServer2022"] + values = ["WindowsServer2025"] } patch_filter { key = "CLASSIFICATION" From b476ac7da6815b396a0b7bf338f211aec32553bf Mon Sep 17 00:00:00 2001 From: jamesthompson26-nhs Date: Thu, 7 May 2026 15:22:09 +0100 Subject: [PATCH 6/8] CCM-15541: Reporting PowerBI Gateway Component --- docs/diagrams/reporting.drawio | 2 +- .../terraform/bin/test_mandatory_tfvars.sh | 3 - .../components/powerbi-gateway/.tool-versions | 1 + .../components/powerbi-gateway/README.md | 44 ++++++ ...oudwatch_metric_alarm_patch_task_failed.tf | 0 ...alarm_powerbi_gateway_standalone_status.tf | 0 .../data_cloudinit_config_powerbi_gateway.tf | 0 .../ec2_instances_powerbi_gateway.tf | 0 .../iam_instance_profile_powerbi_gateway.tf | 72 +++++----- .../kms_key_ebs.tf | 0 ...nch_template_powerbi_gateway_standalone.tf | 0 .../components/powerbi-gateway/locals.tf | 68 ++++++++++ .../locals_reporting_remote_state.tf | 16 +++ .../powerbi-gateway/locals_tfscaffold.tf | 27 ++++ .../module_powerbi_gateway_vpc.tf | 0 .../components/powerbi-gateway/provider.tf | 23 ++++ .../ssm_maintenance_window_patch_window.tf | 0 ...tenance_window_target_windows_instances.tf | 0 .../ssm_maintenance_window_task_patch_task.tf | 0 ..._parameter_powerbi_gateway_recovery_key.tf | 0 ...m_patch_baseline_windows_patch_baseline.tf | 0 .../ssm_patch_group_windows_patch_group.tf | 0 .../templates/cloudinit_config.tmpl | 117 ++++++++++++++++ .../components/powerbi-gateway/variables.tf | 126 ++++++++++++++++++ .../terraform/components/reporting/README.md | 28 ++-- .../terraform/components/reporting/locals.tf | 13 -- .../terraform/components/reporting/outputs.tf | 35 +++++ .../reporting/s3_bucket_access_logs.tf | 2 - ...recipient_with_more_than_five_messages.sql | 1 - ...meter_completed_batch_report_client_ids.tf | 1 - ...meter_completed_comms_report_client_ids.tf | 1 - .../components/reporting/variables.tf | 84 ------------ infrastructure/terraform/deploy.sh | 10 +- 33 files changed, 512 insertions(+), 162 deletions(-) create mode 100644 infrastructure/terraform/components/powerbi-gateway/.tool-versions create mode 100644 infrastructure/terraform/components/powerbi-gateway/README.md rename infrastructure/terraform/components/{reporting => powerbi-gateway}/cloudwatch_metric_alarm_patch_task_failed.tf (100%) rename infrastructure/terraform/components/{reporting => powerbi-gateway}/cloudwatch_metric_alarm_powerbi_gateway_standalone_status.tf (100%) rename infrastructure/terraform/components/{reporting => powerbi-gateway}/data_cloudinit_config_powerbi_gateway.tf (100%) rename infrastructure/terraform/components/{reporting => powerbi-gateway}/ec2_instances_powerbi_gateway.tf (100%) rename infrastructure/terraform/components/{reporting => powerbi-gateway}/iam_instance_profile_powerbi_gateway.tf (69%) rename infrastructure/terraform/components/{reporting => powerbi-gateway}/kms_key_ebs.tf (100%) rename infrastructure/terraform/components/{reporting => powerbi-gateway}/launch_template_powerbi_gateway_standalone.tf (100%) create mode 100644 infrastructure/terraform/components/powerbi-gateway/locals.tf create mode 100644 infrastructure/terraform/components/powerbi-gateway/locals_reporting_remote_state.tf create mode 100644 infrastructure/terraform/components/powerbi-gateway/locals_tfscaffold.tf rename infrastructure/terraform/components/{reporting => powerbi-gateway}/module_powerbi_gateway_vpc.tf (100%) create mode 100644 infrastructure/terraform/components/powerbi-gateway/provider.tf rename infrastructure/terraform/components/{reporting => powerbi-gateway}/ssm_maintenance_window_patch_window.tf (100%) rename infrastructure/terraform/components/{reporting => powerbi-gateway}/ssm_maintenance_window_target_windows_instances.tf (100%) rename infrastructure/terraform/components/{reporting => powerbi-gateway}/ssm_maintenance_window_task_patch_task.tf (100%) rename infrastructure/terraform/components/{reporting => powerbi-gateway}/ssm_parameter_powerbi_gateway_recovery_key.tf (100%) rename infrastructure/terraform/components/{reporting => powerbi-gateway}/ssm_patch_baseline_windows_patch_baseline.tf (100%) rename infrastructure/terraform/components/{reporting => powerbi-gateway}/ssm_patch_group_windows_patch_group.tf (100%) create mode 100644 infrastructure/terraform/components/powerbi-gateway/templates/cloudinit_config.tmpl create mode 100644 infrastructure/terraform/components/powerbi-gateway/variables.tf diff --git a/docs/diagrams/reporting.drawio b/docs/diagrams/reporting.drawio index 49c1c5ed..c62e304d 100644 --- a/docs/diagrams/reporting.drawio +++ b/docs/diagrams/reporting.drawio @@ -157,4 +157,4 @@ - \ No newline at end of file + diff --git a/infrastructure/terraform/bin/test_mandatory_tfvars.sh b/infrastructure/terraform/bin/test_mandatory_tfvars.sh index fc8c4845..db1c9dc0 100755 --- a/infrastructure/terraform/bin/test_mandatory_tfvars.sh +++ b/infrastructure/terraform/bin/test_mandatory_tfvars.sh @@ -81,6 +81,3 @@ for tfvars_file in ./etc/env_eu-west-2_*; do fi done done - - - diff --git a/infrastructure/terraform/components/powerbi-gateway/.tool-versions b/infrastructure/terraform/components/powerbi-gateway/.tool-versions new file mode 100644 index 00000000..3dd74c72 --- /dev/null +++ b/infrastructure/terraform/components/powerbi-gateway/.tool-versions @@ -0,0 +1 @@ +terraform 1.10.1 diff --git a/infrastructure/terraform/components/powerbi-gateway/README.md b/infrastructure/terraform/components/powerbi-gateway/README.md new file mode 100644 index 00000000..a0afbf57 --- /dev/null +++ b/infrastructure/terraform/components/powerbi-gateway/README.md @@ -0,0 +1,44 @@ + + + + +## Requirements + +No requirements. +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [account\_ids](#input\_account\_ids) | All AWS Account IDs for this project | `map(string)` | `{}` | no | +| [account\_name](#input\_account\_name) | The name of the AWS Account to deploy into (see globals.tfvars) | `string` | n/a | yes | +| [athena\_driver\_url](#input\_athena\_driver\_url) | Amazon Athena ODBC MSI download URL for PowerBI gateway bootstrap | `string` | `"https://downloads.athena.us-east-1.amazonaws.com/drivers/ODBC/v2.1.0.0/Windows/AmazonAthenaODBC-2.1.0.0.msi"` | no | +| [aws\_account\_id](#input\_aws\_account\_id) | The AWS Account ID (numeric) | `string` | n/a | yes | +| [component](#input\_component) | The name of the component | `string` | `"powerbi-gateway"` | no | +| [core\_account\_id](#input\_core\_account\_id) | The core account that contains the corresponding Glue Catalog | `string` | `1234567890` | no | +| [core\_account\_ids](#input\_core\_account\_ids) | List of all corresponding core account id's that exist in the Non-Prod domain | `list(string)` | `[]` | no | +| [default\_kms\_deletion\_window\_in\_days](#input\_default\_kms\_deletion\_window\_in\_days) | Default number of days to set KMS key deletion window | `number` | `14` | no | +| [default\_tags](#input\_default\_tags) | A map of default tags to apply to all taggable resources within the component | `map(string)` | `{}` | no | +| [enable\_powerbi\_gateway](#input\_enable\_powerbi\_gateway) | Deploy EC2 instance for PowerBI On-Premises Gateway | `bool` | `true` | no | +| [enable\_spot](#input\_enable\_spot) | run Power BI On-Premises Gateway as spot instances | `bool` | `false` | no | +| [environment](#input\_environment) | The name of the environment | `string` | n/a | yes | +| [group](#input\_group) | The group variables are being inherited from (often synonmous with account short-name) | `string` | `"n/a"` | no | +| [instance\_type](#input\_instance\_type) | The EC2 instance type. | `string` | `"t3.medium"` | no | +| [module](#input\_module) | The variable encapsulating the name of this module | `string` | `"n/a"` | no | +| [powerbi\_gateway\_instance\_count](#input\_powerbi\_gateway\_instance\_count) | Number of standalone Power BI On-Premises Gateway instances created directly from the launch template. | `number` | `2` | no | +| [private\_subnet\_cidrs](#input\_private\_subnet\_cidrs) | List of CIDR blocks for private subnets. | `list(string)` | `[]` | no | +| [project](#input\_project) | The name of the Project we are bootstrapping tfscaffold for | `string` | n/a | yes | +| [public\_subnet\_cidrs](#input\_public\_subnet\_cidrs) | List of CIDR blocks for public subnets. | `list(string)` | `[]` | no | +| [region](#input\_region) | The AWS Region | `string` | n/a | yes | +| [root\_volume\_size](#input\_root\_volume\_size) | Size of root volume for the Power BI On-Premises Gateway instances - 30GB minimum for Windows Server | `number` | `80` | no | +| [spot\_max\_price](#input\_spot\_max\_price) | max spot price for Power BI On-Premises Gateway instances | `string` | `"0.3"` | no | +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [powerbi\_gateway\_vpc](#module\_powerbi\_gateway\_vpc) | terraform-aws-modules/vpc/aws | 5.5.1 | +## Outputs + +No outputs. + + + diff --git a/infrastructure/terraform/components/reporting/cloudwatch_metric_alarm_patch_task_failed.tf b/infrastructure/terraform/components/powerbi-gateway/cloudwatch_metric_alarm_patch_task_failed.tf similarity index 100% rename from infrastructure/terraform/components/reporting/cloudwatch_metric_alarm_patch_task_failed.tf rename to infrastructure/terraform/components/powerbi-gateway/cloudwatch_metric_alarm_patch_task_failed.tf diff --git a/infrastructure/terraform/components/reporting/cloudwatch_metric_alarm_powerbi_gateway_standalone_status.tf b/infrastructure/terraform/components/powerbi-gateway/cloudwatch_metric_alarm_powerbi_gateway_standalone_status.tf similarity index 100% rename from infrastructure/terraform/components/reporting/cloudwatch_metric_alarm_powerbi_gateway_standalone_status.tf rename to infrastructure/terraform/components/powerbi-gateway/cloudwatch_metric_alarm_powerbi_gateway_standalone_status.tf diff --git a/infrastructure/terraform/components/reporting/data_cloudinit_config_powerbi_gateway.tf b/infrastructure/terraform/components/powerbi-gateway/data_cloudinit_config_powerbi_gateway.tf similarity index 100% rename from infrastructure/terraform/components/reporting/data_cloudinit_config_powerbi_gateway.tf rename to infrastructure/terraform/components/powerbi-gateway/data_cloudinit_config_powerbi_gateway.tf diff --git a/infrastructure/terraform/components/reporting/ec2_instances_powerbi_gateway.tf b/infrastructure/terraform/components/powerbi-gateway/ec2_instances_powerbi_gateway.tf similarity index 100% rename from infrastructure/terraform/components/reporting/ec2_instances_powerbi_gateway.tf rename to infrastructure/terraform/components/powerbi-gateway/ec2_instances_powerbi_gateway.tf diff --git a/infrastructure/terraform/components/reporting/iam_instance_profile_powerbi_gateway.tf b/infrastructure/terraform/components/powerbi-gateway/iam_instance_profile_powerbi_gateway.tf similarity index 69% rename from infrastructure/terraform/components/reporting/iam_instance_profile_powerbi_gateway.tf rename to infrastructure/terraform/components/powerbi-gateway/iam_instance_profile_powerbi_gateway.tf index e2a1efee..0589734c 100644 --- a/infrastructure/terraform/components/reporting/iam_instance_profile_powerbi_gateway.tf +++ b/infrastructure/terraform/components/powerbi-gateway/iam_instance_profile_powerbi_gateway.tf @@ -82,8 +82,8 @@ data "aws_iam_policy_document" "powerbi_gateway_permissions_policy" { ] resources = [ - aws_s3_bucket.data.arn, - "${aws_s3_bucket.data.arn}/*" + local.reporting.powerbi_data_bucket_arn, + "${local.reporting.powerbi_data_bucket_arn}/*" ] } @@ -99,8 +99,8 @@ data "aws_iam_policy_document" "powerbi_gateway_permissions_policy" { ] resources = [ - aws_s3_bucket.results.arn, - "${aws_s3_bucket.results.arn}/*" + local.reporting.powerbi_results_bucket_arn, + "${local.reporting.powerbi_results_bucket_arn}/*" ] } @@ -118,7 +118,7 @@ data "aws_iam_policy_document" "powerbi_gateway_permissions_policy" { ] resources = [ - aws_athena_workgroup.user.arn + local.reporting.powerbi_user_workgroup_arn ] } @@ -169,40 +169,40 @@ data "aws_iam_policy_document" "powerbi_gateway_permissions_policy" { resources = concat( local.core_glue_catalog_resources, # Access to all core account catalogs is required as they are all accessible via the default catalog in the environment's account [ - aws_glue_catalog_database.reporting.arn, + local.reporting.powerbi_reporting_database_arn, "arn:aws:glue:${var.region}:${var.core_account_id}:catalog", "arn:aws:glue:${var.region}:${local.this_account}:catalog", # Tables - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_plan_completed_summary", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_plan_completed_summary_batch", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_plan_status", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_status", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_status_summary", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_status_summary_batch", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/client_latest_name", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/request_item_plan_completed_summary", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/request_item_plan_completed_summary_batch", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/request_item_plan_status", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/request_item_status", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/request_item_status_summary", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/request_item_status_summary_batch", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/client_latest_name", # Views - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_plan_completed_summary_all", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_status_summary_all", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_status_summary_all_email_filter", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_status_smsnudge_staging", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_plan_status_smsnudge", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_plan_read_status_smsnudge", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_status_smsnudge", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/dates", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/letters_invoice_units_monthly", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/letters_invoice_units_weekly", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/latency_percentiles", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/daily_recipient_count", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/daily_recipient_distribution", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/raw_latency_3m", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/delivered_messages", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/monthly_app_recipients_distribution", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/monthly_app_recipients_multiple_clients", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/monthly_messages_per_recipient", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/monthly_recipient_with_more_than_five_messages", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/monthly_recipients_distribution", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/monthly_recipients_distribution_by_integrator", - "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/billing_transactions", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/request_item_plan_completed_summary_all", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/request_item_status_summary_all", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/request_item_status_summary_all_email_filter", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/request_item_status_smsnudge_staging", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/request_item_plan_status_smsnudge", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/request_item_plan_read_status_smsnudge", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/request_item_status_smsnudge", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/dates", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/letters_invoice_units_monthly", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/letters_invoice_units_weekly", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/latency_percentiles", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/daily_recipient_count", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/daily_recipient_distribution", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/raw_latency_3m", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/delivered_messages", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/monthly_app_recipients_distribution", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/monthly_app_recipients_multiple_clients", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/monthly_messages_per_recipient", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/monthly_recipient_with_more_than_five_messages", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/monthly_recipients_distribution", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/monthly_recipients_distribution_by_integrator", + "arn:aws:glue:${var.region}:${local.this_account}:table/${local.reporting.powerbi_reporting_database_name}/billing_transactions", ] ) } @@ -220,7 +220,7 @@ data "aws_iam_policy_document" "powerbi_gateway_permissions_policy" { ] resources = [ - aws_kms_key.s3.arn + local.reporting.powerbi_s3_kms_key_arn ] } } diff --git a/infrastructure/terraform/components/reporting/kms_key_ebs.tf b/infrastructure/terraform/components/powerbi-gateway/kms_key_ebs.tf similarity index 100% rename from infrastructure/terraform/components/reporting/kms_key_ebs.tf rename to infrastructure/terraform/components/powerbi-gateway/kms_key_ebs.tf diff --git a/infrastructure/terraform/components/reporting/launch_template_powerbi_gateway_standalone.tf b/infrastructure/terraform/components/powerbi-gateway/launch_template_powerbi_gateway_standalone.tf similarity index 100% rename from infrastructure/terraform/components/reporting/launch_template_powerbi_gateway_standalone.tf rename to infrastructure/terraform/components/powerbi-gateway/launch_template_powerbi_gateway_standalone.tf diff --git a/infrastructure/terraform/components/powerbi-gateway/locals.tf b/infrastructure/terraform/components/powerbi-gateway/locals.tf new file mode 100644 index 00000000..8289c8b4 --- /dev/null +++ b/infrastructure/terraform/components/powerbi-gateway/locals.tf @@ -0,0 +1,68 @@ +locals { + # Compound Scope Identifier + csi = replace( + format( + "%s-%s-%s", + var.project, + var.environment, + var.component, + ), + "_", + "" + ) + + base_parameter_bundle = { + project = var.project + environment = var.environment + component = var.component + group = var.group + region = var.region + account_ids = var.account_ids + account_name = var.account_name + default_kms_deletion_window_in_days = var.default_kms_deletion_window_in_days + default_tags = local.deployment_default_tags + } + + parameter_bundle = merge( + local.base_parameter_bundle, { + iam_resource_arns = { + any_authorised_user_in_this_account = "arn:aws:iam::${local.this_account}:root" + }, + } + ) + + deployment_default_tags = { + AccountId = var.account_ids[var.account_name] + AccountName = var.account_name + Project = var.project + Environment = var.environment + Component = var.component + Group = var.group + Module = var.module + } + + this_account = local.base_parameter_bundle.account_ids[local.base_parameter_bundle.account_name] + + # Create the powerbi_gateway_script only if var.enable_powerbi_gateway is true + powerbi_gateway_script = var.enable_powerbi_gateway ? templatefile("${path.module}/templates/cloudinit_config.tmpl", { + odbc_dsn_name = "${local.csi}-dsn" + odbc_description = "AWS Simba Athena ODBC Connection for ${local.csi}" + athena_driver_url = var.athena_driver_url + region = var.region + catalog = "AWSDataCatalog" + database = data.terraform_remote_state.reporting.outputs.powerbi_reporting_database_name + workgroup = data.terraform_remote_state.reporting.outputs.powerbi_user_workgroup_name + authentication_type = "Instance Profile" + gateway_name = "${local.csi}-gateway" + }) : null + + use_core_glue_catalog_resources = length(var.core_account_ids) > 0 ? true : false + + core_glue_catalog_resources = local.use_core_glue_catalog_resources ? flatten([ + for account_id in var.core_account_ids : [ + "arn:aws:glue:${var.region}:${account_id}:catalog", + ] + ]) : [] + + reporting = data.terraform_remote_state.reporting.outputs +} diff --git a/infrastructure/terraform/components/powerbi-gateway/locals_reporting_remote_state.tf b/infrastructure/terraform/components/powerbi-gateway/locals_reporting_remote_state.tf new file mode 100644 index 00000000..44710645 --- /dev/null +++ b/infrastructure/terraform/components/powerbi-gateway/locals_reporting_remote_state.tf @@ -0,0 +1,16 @@ +data "terraform_remote_state" "reporting" { + backend = "s3" + + config = { + bucket = local.terraform_state_bucket + key = format( + "%s/%s/%s/%s/%s.tfstate", + var.project, + var.aws_account_id, + var.region, + var.environment, + "reporting" + ) + region = var.region + } +} diff --git a/infrastructure/terraform/components/powerbi-gateway/locals_tfscaffold.tf b/infrastructure/terraform/components/powerbi-gateway/locals_tfscaffold.tf new file mode 100644 index 00000000..bf93f3a6 --- /dev/null +++ b/infrastructure/terraform/components/powerbi-gateway/locals_tfscaffold.tf @@ -0,0 +1,27 @@ +locals { + terraform_state_bucket = format( + "%s-tfscaffold-%s-%s", + var.project, + var.aws_account_id, + var.region, + ) + + # Central account component deployed with "nhs" project name, not "nhs-notify" for legacy reasons. + terraform_state_bucket_acct = format( + "%s-tfscaffold-%s-%s", + "nhs", + var.aws_account_id, + var.region, + ) + + default_tags = merge( + var.default_tags, + { + Project = var.project + Environment = var.environment + Component = var.component + Group = var.group + Name = local.csi + }, + ) +} diff --git a/infrastructure/terraform/components/reporting/module_powerbi_gateway_vpc.tf b/infrastructure/terraform/components/powerbi-gateway/module_powerbi_gateway_vpc.tf similarity index 100% rename from infrastructure/terraform/components/reporting/module_powerbi_gateway_vpc.tf rename to infrastructure/terraform/components/powerbi-gateway/module_powerbi_gateway_vpc.tf diff --git a/infrastructure/terraform/components/powerbi-gateway/provider.tf b/infrastructure/terraform/components/powerbi-gateway/provider.tf new file mode 100644 index 00000000..e5b9a49e --- /dev/null +++ b/infrastructure/terraform/components/powerbi-gateway/provider.tf @@ -0,0 +1,23 @@ +### +# Default provider, used for all deployments to eu-west-2 +# If no provider name is supplied for a resource then it uses this one +### +provider "aws" { + region = var.region + + default_tags { + tags = local.deployment_default_tags + } +} + +### +# Provider specifically for deploying to us-east-1, e.g. for cloudfront, ACM, etc. +### +provider "aws" { + alias = "us-east-1" + region = "us-east-1" + + default_tags { + tags = local.deployment_default_tags + } +} diff --git a/infrastructure/terraform/components/reporting/ssm_maintenance_window_patch_window.tf b/infrastructure/terraform/components/powerbi-gateway/ssm_maintenance_window_patch_window.tf similarity index 100% rename from infrastructure/terraform/components/reporting/ssm_maintenance_window_patch_window.tf rename to infrastructure/terraform/components/powerbi-gateway/ssm_maintenance_window_patch_window.tf diff --git a/infrastructure/terraform/components/reporting/ssm_maintenance_window_target_windows_instances.tf b/infrastructure/terraform/components/powerbi-gateway/ssm_maintenance_window_target_windows_instances.tf similarity index 100% rename from infrastructure/terraform/components/reporting/ssm_maintenance_window_target_windows_instances.tf rename to infrastructure/terraform/components/powerbi-gateway/ssm_maintenance_window_target_windows_instances.tf diff --git a/infrastructure/terraform/components/reporting/ssm_maintenance_window_task_patch_task.tf b/infrastructure/terraform/components/powerbi-gateway/ssm_maintenance_window_task_patch_task.tf similarity index 100% rename from infrastructure/terraform/components/reporting/ssm_maintenance_window_task_patch_task.tf rename to infrastructure/terraform/components/powerbi-gateway/ssm_maintenance_window_task_patch_task.tf diff --git a/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_recovery_key.tf b/infrastructure/terraform/components/powerbi-gateway/ssm_parameter_powerbi_gateway_recovery_key.tf similarity index 100% rename from infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_recovery_key.tf rename to infrastructure/terraform/components/powerbi-gateway/ssm_parameter_powerbi_gateway_recovery_key.tf diff --git a/infrastructure/terraform/components/reporting/ssm_patch_baseline_windows_patch_baseline.tf b/infrastructure/terraform/components/powerbi-gateway/ssm_patch_baseline_windows_patch_baseline.tf similarity index 100% rename from infrastructure/terraform/components/reporting/ssm_patch_baseline_windows_patch_baseline.tf rename to infrastructure/terraform/components/powerbi-gateway/ssm_patch_baseline_windows_patch_baseline.tf diff --git a/infrastructure/terraform/components/reporting/ssm_patch_group_windows_patch_group.tf b/infrastructure/terraform/components/powerbi-gateway/ssm_patch_group_windows_patch_group.tf similarity index 100% rename from infrastructure/terraform/components/reporting/ssm_patch_group_windows_patch_group.tf rename to infrastructure/terraform/components/powerbi-gateway/ssm_patch_group_windows_patch_group.tf diff --git a/infrastructure/terraform/components/powerbi-gateway/templates/cloudinit_config.tmpl b/infrastructure/terraform/components/powerbi-gateway/templates/cloudinit_config.tmpl new file mode 100644 index 00000000..399b8d98 --- /dev/null +++ b/infrastructure/terraform/components/powerbi-gateway/templates/cloudinit_config.tmpl @@ -0,0 +1,117 @@ + + +# Create scripts directory if it doesn't exist +if (!(Test-Path -Path "C:\scripts")) { + New-Item -ItemType Directory -Path "C:\scripts" +} + +# Create the install_powerbi_gateway.ps1 script +$installPowerBiGatewayScript = @" +# Set execution policy to bypass +Set-ExecutionPolicy Bypass -Scope Process -Force + +# Enable TLS 1.2 +[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 + +# Download and install Chocolatey (if not already installed) +if (-not (Get-Command choco -ErrorAction SilentlyContinue)) { + iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) +} + +# Install Powershell 7 +choco install -y powershell-core + +# Install Amazon Athena ODBC 2.x Driver +`$athenaDriverUrl = "${athena_driver_url}" +`$athenaDriverInstaller = "C:\scripts\AmazonAthenaODBC.msi" +Invoke-WebRequest -Uri `$athenaDriverUrl -OutFile `$athenaDriverInstaller + +# Silent installation of Amazon Athena ODBC driver +Start-Process -FilePath `$athenaDriverInstaller -ArgumentList "/quiet" -Wait + +# Configure the ODBC Connection: +`$odbcDsnName = "${odbc_dsn_name}" +`$odbcDescription = "${odbc_description}" +`$region = "${region}" +`$catalog = "${catalog}" +`$database = "${database}" +`$workgroup = "${workgroup}" +`$authenticationType = "${authentication_type}" + +# Path to the ODBC DSN registry key +`$odbcDsnPath = "HKLM:HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBC.INI\`$odbcDsnName" +`$odbcDsnListPath = "HKLM:\HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBC.INI\ODBC Data Sources" + +# Create the DSN key and set values +New-Item -Path `$odbcDsnPath -Force +Set-ItemProperty -Path `$odbcDsnPath -Name "Description" -Value `$odbcDescription +Set-ItemProperty -Path `$odbcDsnPath -Name "AwsRegion" -Value `$region +Set-ItemProperty -Path `$odbcDsnPath -Name "Catalog" -Value `$catalog +Set-ItemProperty -Path `$odbcDsnPath -Name "Schema" -Value `$database +Set-ItemProperty -Path `$odbcDsnPath -Name "Workgroup" -Value `$workgroup +Set-ItemProperty -Path `$odbcDsnPath -Name "AuthenticationType" -Value `$authenticationType + +# Add the DSN to the list of ODBC Data Sources +Set-ItemProperty -Path `$odbcDsnListPath -Name `$odbcDsnName -Value "Amazon Athena ODBC (x64)" + +Write-Output "ODBC DSN '`$odbcDsnName' created successfully." + +Write-Output "Power BI on-premises data gateway and Amazon Athena ODBC driver installation completed." + +# Check if PowerShell 7 (pwsh) is installed and available +`$pwshPath = "C:\Program Files\PowerShell\7\pwsh.exe" +if (Test-Path `$pwshPath) { + # Invoke PowerShell 7 to run the remaining commands + & `$pwshPath -ExecutionPolicy Bypass -Command { + # Set execution policy to bypass + Set-ExecutionPolicy Bypass -Scope Process -Force + + # Install DataGateway CMDLets + Install-Module -Name DataGateway -Force -AllowClobber -Scope AllUsers + + # Install AWSCLI + choco install -y awscli + `$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + + # Verify AWS CLI installation + if (!(Get-Command aws -ErrorAction SilentlyContinue)) { + Write-Output "AWS CLI installation failed or not found in PATH." + exit 1 + } + } +} +else { + Write-Output "AWSCLI & PowerShell 7 installation failed or path not found." +} + +# Function to disable Internet Explorer Enhanced Security Configuration (ESC) +# Required due to ESC rejecting Microsoft's own SSO endpoints - https://login.microsoftonline.com, https://aadcdn.msftauth.net, https://fs.nhs.net +function Disable-InternetExplorerESC { + `$AdminKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}" + `$UserKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}" + Set-ItemProperty -Path `$AdminKey -Name "IsInstalled" -Value 0 -Force + Set-ItemProperty -Path `$UserKey -Name "IsInstalled" -Value 0 -Force + Stop-Process -Name Explorer -Force + Write-Host "IE Enhanced Security Configuration (ESC) has been disabled." -ForegroundColor Green +} + +# Function to enable Internet Explorer Enhanced Security Configuration (ESC) +function Enable-InternetExplorerESC { + `$AdminKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}" + `$UserKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}" + Set-ItemProperty -Path `$AdminKey -Name "IsInstalled" -Value 1 -Force + Set-ItemProperty -Path `$UserKey -Name "IsInstalled" -Value 1 -Force + Stop-Process -Name Explorer -Force + Write-Host "IE Enhanced Security Configuration (ESC) has been enabled." -ForegroundColor Green +} + +# Disable IE ESC +Disable-InternetExplorerESC + +"@ +Set-Content -Path "C:\scripts\install_powerbi_gateway.ps1" -Value $installPowerBiGatewayScript + +# Execute the script +powershell.exe -ExecutionPolicy Bypass -File "C:\scripts\install_powerbi_gateway.ps1" > "C:\scripts\install_powerbi_gateway.log" 2>&1 + + diff --git a/infrastructure/terraform/components/powerbi-gateway/variables.tf b/infrastructure/terraform/components/powerbi-gateway/variables.tf new file mode 100644 index 00000000..edbffdbe --- /dev/null +++ b/infrastructure/terraform/components/powerbi-gateway/variables.tf @@ -0,0 +1,126 @@ +variable "project" { + type = string + description = "The name of the Project we are bootstrapping tfscaffold for" +} + +variable "account_ids" { + type = map(string) + description = "All AWS Account IDs for this project" + default = {} +} + +variable "aws_account_id" { + type = string + description = "The AWS Account ID (numeric)" +} + +variable "account_name" { + type = string + description = "The name of the AWS Account to deploy into (see globals.tfvars)" +} + +variable "default_tags" { + type = map(string) + description = "A map of default tags to apply to all taggable resources within the component" + default = {} +} + +variable "region" { + type = string + description = "The AWS Region" +} + +variable "environment" { + type = string + description = "The name of the environment" +} + +variable "component" { + type = string + description = "The name of the component" + default = "powerbi-gateway" +} + +variable "group" { + type = string + description = "The group variables are being inherited from (often synonmous with account short-name)" + default = "n/a" +} + +variable "module" { + type = string + description = "The variable encapsulating the name of this module" + default = "n/a" +} + +variable "default_kms_deletion_window_in_days" { + type = number + description = "Default number of days to set KMS key deletion window" + default = 14 +} + +variable "core_account_id" { + type = string + description = "The core account that contains the corresponding Glue Catalog" + default = 1234567890 +} + +variable "enable_powerbi_gateway" { + type = bool + description = "Deploy EC2 instance for PowerBI On-Premises Gateway" + default = true +} + +variable "athena_driver_url" { + type = string + description = "Amazon Athena ODBC MSI download URL for PowerBI gateway bootstrap" + default = "https://downloads.athena.us-east-1.amazonaws.com/drivers/ODBC/v2.1.0.0/Windows/AmazonAthenaODBC-2.1.0.0.msi" +} + +variable "powerbi_gateway_instance_count" { + description = "Number of standalone Power BI On-Premises Gateway instances created directly from the launch template." + type = number + default = 2 +} + +variable "public_subnet_cidrs" { + description = "List of CIDR blocks for public subnets." + type = list(string) + default = [] +} + +variable "private_subnet_cidrs" { + description = "List of CIDR blocks for private subnets." + type = list(string) + default = [] +} + +variable "instance_type" { + description = "The EC2 instance type." + type = string + default = "t3.medium" +} + +variable "enable_spot" { + type = bool + description = "run Power BI On-Premises Gateway as spot instances" + default = false +} + +variable "spot_max_price" { + type = string + description = "max spot price for Power BI On-Premises Gateway instances" + default = "0.3" +} + +variable "root_volume_size" { + type = number + description = "Size of root volume for the Power BI On-Premises Gateway instances - 30GB minimum for Windows Server" + default = 80 +} + +variable "core_account_ids" { + description = "List of all corresponding core account id's that exist in the Non-Prod domain" + type = list(string) + default = [] +} diff --git a/infrastructure/terraform/components/reporting/README.md b/infrastructure/terraform/components/reporting/README.md index 4c57c0ed..4be165b5 100644 --- a/infrastructure/terraform/components/reporting/README.md +++ b/infrastructure/terraform/components/reporting/README.md @@ -13,7 +13,6 @@ No requirements. | [account\_name](#input\_account\_name) | The name of the AWS Account to deploy into (see globals.tfvars) | `string` | n/a | yes | | [app\_deployer\_role\_name](#input\_app\_deployer\_role\_name) | Name of the app deployer role that is allowed to deploy Comms Mgr applications but not create other IAM roles | `string` | n/a | yes | | [app\_deployer\_role\_permission\_account\_ids](#input\_app\_deployer\_role\_permission\_account\_ids) | All AWS Account IDs for this project that have the AppDeployer role created | `map(string)` | `{}` | no | -| [athena\_driver\_url](#input\_athena\_driver\_url) | Amazon Athena ODBC MSI download URL for PowerBI gateway bootstrap | `string` | `"https://downloads.athena.us-east-1.amazonaws.com/drivers/ODBC/v2.1.0.0/Windows/AmazonAthenaODBC-2.1.0.0.msi"` | no | | [aws\_account\_id](#input\_aws\_account\_id) | The AWS Account ID (numeric) | `string` | n/a | yes | | [batch\_client\_ids](#input\_batch\_client\_ids) | List of client ids that require additional batch identifier dimensions when aggregating data | `list(string)` |
[
"NULL"
]
| no | | [cloudtrail\_log\_group\_name](#input\_cloudtrail\_log\_group\_name) | The name of the Cloudtrail log group name on the account (see globals.tfvars) | `string` | n/a | yes | @@ -24,44 +23,37 @@ No requirements. | [core\_env](#input\_core\_env) | The core environment that contains the corresponding Glue table/S3 buckets etc. | `string` | `"internal-dev"` | no | | [default\_kms\_deletion\_window\_in\_days](#input\_default\_kms\_deletion\_window\_in\_days) | Default number of days to set KMS key deletion window | `number` | `14` | no | | [default\_tags](#input\_default\_tags) | A map of default tags to apply to all taggable resources within the component | `map(string)` | `{}` | no | -| [desired\_capacity](#input\_desired\_capacity) | The desired number of instances in the Power BI On-Premises Gateway Auto Scaling group. | `number` | `1` | no | | [destination\_backup\_vault\_arn](#input\_destination\_backup\_vault\_arn) | ARN of the destination backup vault to copy periodic backups to | `string` | `""` | no | | [email\_filter\_client\_ids](#input\_email\_filter\_client\_ids) | List of client ids that need email-only sending groups to be hidden in certain views | `list(string)` |
[
"NULL"
]
| no | -| [enable\_powerbi\_gateway](#input\_enable\_powerbi\_gateway) | Deploy EC2 instance for PowerBI On-Premises Gateway | `bool` | `true` | no | | [enable\_s3\_backup](#input\_enable\_s3\_backup) | Enable AWS S3 Backup of the data bucket | `bool` | `true` | no | -| [enable\_spot](#input\_enable\_spot) | run Power BI On-Premises Gateway as spot instances | `bool` | `false` | no | | [enable\_vault\_lock\_configuration](#input\_enable\_vault\_lock\_configuration) | Enable vault lock, preventing the deletion of a vault that contains 1 or more Recovery Points | `bool` | `false` | no | | [environment](#input\_environment) | The name of the environment | `string` | n/a | yes | | [group](#input\_group) | The group variables are being inherited from (often synonmous with account short-name) | `string` | `"n/a"` | no | -| [instance\_type](#input\_instance\_type) | The EC2 instance type. | `string` | `"t3.medium"` | no | | [log\_retention\_days](#input\_log\_retention\_days) | How many days to retain the logs generated by the step function | `number` | `30` | no | -| [max\_size](#input\_max\_size) | The maximum number of instances in the Power BI On-Premises Gateway Auto Scaling group. | `number` | `1` | no | -| [min\_size](#input\_min\_size) | The minimum number of instances in the Power BI On-Premises Gateway Auto Scaling group. | `number` | `1` | no | | [module](#input\_module) | The variable encapsulating the name of this module | `string` | `"n/a"` | no | | [parent\_acct\_environment](#input\_parent\_acct\_environment) | Name of the environment responsible for the acct resources used, affects things like DNS zone. Useful for named dev environments | `string` | `"main"` | no | | [periodic\_s3backup\_copy\_retention\_days](#input\_periodic\_s3backup\_copy\_retention\_days) | number of days to retain weekly s3 backups in the destination vault | `number` | `31` | no | | [periodic\_s3backup\_retention\_days](#input\_periodic\_s3backup\_retention\_days) | number of days to retain weekly s3 backups | `number` | `31` | no | | [periodic\_s3backup\_schedule](#input\_periodic\_s3backup\_schedule) | Crontab formatted schedule for Periodic S3 Backups | `string` | `"cron(0 5 ? * 7 *)"` | no | -| [powerbi\_gateway\_instance\_count](#input\_powerbi\_gateway\_instance\_count) | Number of standalone Power BI On-Premises Gateway instances created directly from the launch template. | `number` | `2` | no | -| [private\_subnet\_cidrs](#input\_private\_subnet\_cidrs) | List of CIDR blocks for private subnets. | `list(string)` | `[]` | no | | [project](#input\_project) | The name of the Project we are bootstrapping tfscaffold for | `string` | n/a | yes | -| [public\_subnet\_cidrs](#input\_public\_subnet\_cidrs) | List of CIDR blocks for public subnets. | `list(string)` | `[]` | no | | [region](#input\_region) | The AWS Region | `string` | n/a | yes | -| [root\_volume\_size](#input\_root\_volume\_size) | Size of root volume for the Power BI On-Premises Gateway instances - 30GB minimum for Windows Server | `number` | `80` | no | -| [scale\_in\_recurrence\_schedule](#input\_scale\_in\_recurrence\_schedule) | The cron expression for the scale in schedule. Set to null if no recurrence is needed. | `string` | `null` | no | -| [scale\_out\_recurrence\_schedule](#input\_scale\_out\_recurrence\_schedule) | The cron expression for the scale out schedule. Set to null if no recurrence is needed. | `string` | `null` | no | | [shared\_infra\_account\_id](#input\_shared\_infra\_account\_id) | The AWS Account ID of the shared infrastructure account | `string` | `"000000000000"` | no | | [sms\_nudge\_client\_id](#input\_sms\_nudge\_client\_id) | Client id for the SMS Nudge umbrella client used to filter smsnudge views | `string` | `"NULL"` | no | -| [spot\_max\_price](#input\_spot\_max\_price) | max spot price for Power BI On-Premises Gateway instances | `string` | `"0.3"` | no | | [superuser\_role\_name](#input\_superuser\_role\_name) | Name of the superuser role that is allowed to create other IAM roles | `string` | n/a | yes | ## Modules -| Name | Source | Version | -|------|--------|---------| -| [powerbi\_gateway\_vpc](#module\_powerbi\_gateway\_vpc) | terraform-aws-modules/vpc/aws | 5.5.1 | +No modules. ## Outputs -No outputs. +| Name | Description | +|------|-------------| +| [powerbi\_data\_bucket\_arn](#output\_powerbi\_data\_bucket\_arn) | Data bucket ARN used by the Power BI gateway component | +| [powerbi\_reporting\_database\_arn](#output\_powerbi\_reporting\_database\_arn) | Glue reporting database ARN for Power BI gateway access policy | +| [powerbi\_reporting\_database\_name](#output\_powerbi\_reporting\_database\_name) | Glue reporting database name for Power BI gateway access policy | +| [powerbi\_results\_bucket\_arn](#output\_powerbi\_results\_bucket\_arn) | Results bucket ARN used by the Power BI gateway component | +| [powerbi\_s3\_kms\_key\_arn](#output\_powerbi\_s3\_kms\_key\_arn) | KMS key ARN for S3 data encryption used by Power BI gateway access policy | +| [powerbi\_user\_workgroup\_arn](#output\_powerbi\_user\_workgroup\_arn) | Athena user workgroup ARN used by the Power BI gateway access policy | +| [powerbi\_user\_workgroup\_name](#output\_powerbi\_user\_workgroup\_name) | Athena user workgroup name used by the Power BI gateway bootstrap | diff --git a/infrastructure/terraform/components/reporting/locals.tf b/infrastructure/terraform/components/reporting/locals.tf index 761faa69..7dc0ea8c 100644 --- a/infrastructure/terraform/components/reporting/locals.tf +++ b/infrastructure/terraform/components/reporting/locals.tf @@ -54,19 +54,6 @@ locals { this_account = local.base_parameter_bundle.account_ids[local.base_parameter_bundle.account_name] - # Create the powerbi_gateway_script only if var.enable_powerbi_gateway is true - powerbi_gateway_script = var.enable_powerbi_gateway ? templatefile("${path.module}/templates/cloudinit_config.tmpl", { - odbc_dsn_name = "${local.csi}-dsn" - odbc_description = "AWS Simba Athena ODBC Connection for ${local.csi}" - athena_driver_url = var.athena_driver_url - region = var.region - catalog = "AWSDataCatalog" - database = aws_glue_catalog_database.reporting.name - workgroup = aws_athena_workgroup.user.name - authentication_type = "Instance Profile" - gateway_name = "${local.csi}-gateway" - }) : null - use_core_glue_catalog_resources = length(var.core_account_ids) > 0 ? true : false core_glue_catalog_resources = local.use_core_glue_catalog_resources ? flatten([ diff --git a/infrastructure/terraform/components/reporting/outputs.tf b/infrastructure/terraform/components/reporting/outputs.tf index 9dcc2f39..7dfcbf14 100644 --- a/infrastructure/terraform/components/reporting/outputs.tf +++ b/infrastructure/terraform/components/reporting/outputs.tf @@ -1 +1,36 @@ # Define the outputs for the component. The outputs may well be referenced by other component in the same or different environments using terraform_remote_state data sources... + +output "powerbi_data_bucket_arn" { + description = "Data bucket ARN used by the Power BI gateway component" + value = aws_s3_bucket.data.arn +} + +output "powerbi_results_bucket_arn" { + description = "Results bucket ARN used by the Power BI gateway component" + value = aws_s3_bucket.results.arn +} + +output "powerbi_reporting_database_name" { + description = "Glue reporting database name for Power BI gateway access policy" + value = aws_glue_catalog_database.reporting.name +} + +output "powerbi_reporting_database_arn" { + description = "Glue reporting database ARN for Power BI gateway access policy" + value = aws_glue_catalog_database.reporting.arn +} + +output "powerbi_user_workgroup_name" { + description = "Athena user workgroup name used by the Power BI gateway bootstrap" + value = aws_athena_workgroup.user.name +} + +output "powerbi_user_workgroup_arn" { + description = "Athena user workgroup ARN used by the Power BI gateway access policy" + value = aws_athena_workgroup.user.arn +} + +output "powerbi_s3_kms_key_arn" { + description = "KMS key ARN for S3 data encryption used by Power BI gateway access policy" + value = aws_kms_key.s3.arn +} diff --git a/infrastructure/terraform/components/reporting/s3_bucket_access_logs.tf b/infrastructure/terraform/components/reporting/s3_bucket_access_logs.tf index 631f7c24..764c76f6 100644 --- a/infrastructure/terraform/components/reporting/s3_bucket_access_logs.tf +++ b/infrastructure/terraform/components/reporting/s3_bucket_access_logs.tf @@ -146,5 +146,3 @@ resource "aws_s3_bucket_lifecycle_configuration" "access_logs" { } } } - - diff --git a/infrastructure/terraform/components/reporting/scripts/sql/views/monthly_recipient_with_more_than_five_messages.sql b/infrastructure/terraform/components/reporting/scripts/sql/views/monthly_recipient_with_more_than_five_messages.sql index 5b08ffa4..0ade1c36 100644 --- a/infrastructure/terraform/components/reporting/scripts/sql/views/monthly_recipient_with_more_than_five_messages.sql +++ b/infrastructure/terraform/components/reporting/scripts/sql/views/monthly_recipient_with_more_than_five_messages.sql @@ -21,4 +21,3 @@ FROM ( ) WHERE monthlytotalmessages >= 5 GROUP BY 1, 2, 3, 4 - diff --git a/infrastructure/terraform/components/reporting/ssm_parameter_completed_batch_report_client_ids.tf b/infrastructure/terraform/components/reporting/ssm_parameter_completed_batch_report_client_ids.tf index 3e0d80cb..9ddbfdbe 100644 --- a/infrastructure/terraform/components/reporting/ssm_parameter_completed_batch_report_client_ids.tf +++ b/infrastructure/terraform/components/reporting/ssm_parameter_completed_batch_report_client_ids.tf @@ -8,4 +8,3 @@ resource "aws_ssm_parameter" "completed_batch_report_client_ids" { ignore_changes = [value] } } - diff --git a/infrastructure/terraform/components/reporting/ssm_parameter_completed_comms_report_client_ids.tf b/infrastructure/terraform/components/reporting/ssm_parameter_completed_comms_report_client_ids.tf index 633c5dd0..984e3682 100644 --- a/infrastructure/terraform/components/reporting/ssm_parameter_completed_comms_report_client_ids.tf +++ b/infrastructure/terraform/components/reporting/ssm_parameter_completed_comms_report_client_ids.tf @@ -8,4 +8,3 @@ resource "aws_ssm_parameter" "completed_comms_report_client_ids" { ignore_changes = [value] } } - diff --git a/infrastructure/terraform/components/reporting/variables.tf b/infrastructure/terraform/components/reporting/variables.tf index b7e2c7e5..9340c410 100644 --- a/infrastructure/terraform/components/reporting/variables.tf +++ b/infrastructure/terraform/components/reporting/variables.tf @@ -97,90 +97,6 @@ variable "log_retention_days" { default = 30 } -variable "enable_powerbi_gateway" { - type = bool - description = "Deploy EC2 instance for PowerBI On-Premises Gateway" - default = true -} - -variable "athena_driver_url" { - type = string - description = "Amazon Athena ODBC MSI download URL for PowerBI gateway bootstrap" - default = "https://downloads.athena.us-east-1.amazonaws.com/drivers/ODBC/v2.1.0.0/Windows/AmazonAthenaODBC-2.1.0.0.msi" -} - -variable "powerbi_gateway_instance_count" { - description = "Number of standalone Power BI On-Premises Gateway instances created directly from the launch template." - type = number - default = 2 -} - -variable "public_subnet_cidrs" { - description = "List of CIDR blocks for public subnets." - type = list(string) - default = [] -} - -variable "private_subnet_cidrs" { - description = "List of CIDR blocks for private subnets." - type = list(string) - default = [] -} - -variable "instance_type" { - description = "The EC2 instance type." - type = string - default = "t3.medium" -} - -variable "desired_capacity" { - description = "The desired number of instances in the Power BI On-Premises Gateway Auto Scaling group." - type = number - default = 1 -} - -variable "min_size" { - description = "The minimum number of instances in the Power BI On-Premises Gateway Auto Scaling group." - type = number - default = 1 -} - -variable "max_size" { - description = "The maximum number of instances in the Power BI On-Premises Gateway Auto Scaling group." - type = number - default = 1 -} - -variable "enable_spot" { - type = bool - description = "run Power BI On-Premises Gateway as spot instances" - default = false -} - -variable "spot_max_price" { - type = string - description = "max spot price for Power BI On-Premises Gateway instances" - default = "0.3" -} - -variable "root_volume_size" { - type = number - description = "Size of root volume for the Power BI On-Premises Gateway instances - 30GB minimum for Windows Server" - default = 80 -} - -variable "scale_out_recurrence_schedule" { - description = "The cron expression for the scale out schedule. Set to null if no recurrence is needed." - type = string - default = null -} - -variable "scale_in_recurrence_schedule" { - description = "The cron expression for the scale in schedule. Set to null if no recurrence is needed." - type = string - default = null -} - variable "core_account_ids" { description = "List of all corresponding core account id's that exist in the Non-Prod domain" type = list(string) diff --git a/infrastructure/terraform/deploy.sh b/infrastructure/terraform/deploy.sh index dcf64c44..66354c92 100755 --- a/infrastructure/terraform/deploy.sh +++ b/infrastructure/terraform/deploy.sh @@ -31,11 +31,17 @@ cd "${basedir}" || exit 1; terraform_environment="${DEPLOY_ENVIRONMENT}"; # determine the order of components to process +if [[ -n "${TERRAFORM_COMPONENTS:-}" ]]; then + component_list="${TERRAFORM_COMPONENTS}"; +elif [[ "${TERRAFORM_ACTION}" == "destroy" ]]; then + component_list="powerbi-gateway reporting"; +else + component_list="reporting powerbi-gateway"; +fi; + if [[ "${TERRAFORM_ACTION}" == "destroy" ]]; then - component_list="reporting"; plan_action="plan-destroy"; else - component_list="reporting"; plan_action="plan"; fi; From 143cbcced2ce90790daccc608ef61a30c8f6bc70 Mon Sep 17 00:00:00 2001 From: jamesthompson26-nhs Date: Thu, 7 May 2026 15:49:37 +0100 Subject: [PATCH 7/8] CCM-15541: Reporting PowerBI Gateway Component --- ...dwatch_metric_alarm_powerbi_gateway_standalone_status.tf | 2 +- .../powerbi-gateway/ec2_instances_powerbi_gateway.tf | 2 +- .../powerbi-gateway/iam_instance_profile_powerbi_gateway.tf | 6 +++--- .../powerbi-gateway/module_powerbi_gateway_vpc.tf | 6 +++--- .../ssm_parameter_powerbi_gateway_recovery_key.tf | 2 +- scripts/config/gitleaks.toml | 4 +++- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/infrastructure/terraform/components/powerbi-gateway/cloudwatch_metric_alarm_powerbi_gateway_standalone_status.tf b/infrastructure/terraform/components/powerbi-gateway/cloudwatch_metric_alarm_powerbi_gateway_standalone_status.tf index a84763fb..91d4e855 100644 --- a/infrastructure/terraform/components/powerbi-gateway/cloudwatch_metric_alarm_powerbi_gateway_standalone_status.tf +++ b/infrastructure/terraform/components/powerbi-gateway/cloudwatch_metric_alarm_powerbi_gateway_standalone_status.tf @@ -3,7 +3,7 @@ resource "aws_cloudwatch_metric_alarm" "powerbi_gateway_standalone_status_check_ for idx, instance in aws_instance.powerbi_gateway_standalone : idx => { id = instance.id - name = format("%s-powerbi-gateway-standalone-%02d-status-check-failed", local.csi, idx + 1) + name = format("%s-standalone-%02d-status-check-failed", local.csi, idx + 1) } } : {} diff --git a/infrastructure/terraform/components/powerbi-gateway/ec2_instances_powerbi_gateway.tf b/infrastructure/terraform/components/powerbi-gateway/ec2_instances_powerbi_gateway.tf index 3ccd593b..aff7991f 100644 --- a/infrastructure/terraform/components/powerbi-gateway/ec2_instances_powerbi_gateway.tf +++ b/infrastructure/terraform/components/powerbi-gateway/ec2_instances_powerbi_gateway.tf @@ -8,7 +8,7 @@ resource "aws_instance" "powerbi_gateway_standalone" { } tags = { - "Name" = format("%s-powerbi-gateway-standalone-%02d", local.csi, count.index + 1) + "Name" = format("%s-standalone-%02d", local.csi, count.index + 1) "Patch Group" = aws_ssm_patch_group.windows_patch_group[0].patch_group } } diff --git a/infrastructure/terraform/components/powerbi-gateway/iam_instance_profile_powerbi_gateway.tf b/infrastructure/terraform/components/powerbi-gateway/iam_instance_profile_powerbi_gateway.tf index 0589734c..4d340697 100644 --- a/infrastructure/terraform/components/powerbi-gateway/iam_instance_profile_powerbi_gateway.tf +++ b/infrastructure/terraform/components/powerbi-gateway/iam_instance_profile_powerbi_gateway.tf @@ -1,7 +1,7 @@ resource "aws_iam_instance_profile" "powerbi_gateway" { count = var.enable_powerbi_gateway ? 1 : 0 - name = "${local.csi}-powerbi-gateway" + name = local.csi role = aws_iam_role.powerbi_gateway_role[0].name } @@ -23,7 +23,7 @@ data "aws_iam_policy_document" "powerbi_gateway_assume_role_policy" { resource "aws_iam_role" "powerbi_gateway_role" { count = var.enable_powerbi_gateway ? 1 : 0 - name = "${local.csi}-powerbi-gateway" + name = local.csi description = "PowerBI Gateway Instance Role" path = "/" assume_role_policy = data.aws_iam_policy_document.powerbi_gateway_assume_role_policy[0].json @@ -33,7 +33,7 @@ resource "aws_iam_role" "powerbi_gateway_role" { resource "aws_iam_policy" "powerbi_gateway_permissions_policy" { count = var.enable_powerbi_gateway ? 1 : 0 - name = "${local.csi}-powerbi-gateway" + name = local.csi description = "PowerBI Gateway Instance Permissions" path = "/" policy = data.aws_iam_policy_document.powerbi_gateway_permissions_policy[0].json diff --git a/infrastructure/terraform/components/powerbi-gateway/module_powerbi_gateway_vpc.tf b/infrastructure/terraform/components/powerbi-gateway/module_powerbi_gateway_vpc.tf index d8e55ada..55939f92 100644 --- a/infrastructure/terraform/components/powerbi-gateway/module_powerbi_gateway_vpc.tf +++ b/infrastructure/terraform/components/powerbi-gateway/module_powerbi_gateway_vpc.tf @@ -6,7 +6,7 @@ module "powerbi_gateway_vpc" { create_vpc = var.enable_powerbi_gateway - name = "${local.csi}-powerbi-gateway-vpc" + name = "${local.csi}-vpc" cidr = "10.0.0.0/16" azs = data.aws_availability_zones.available[0].names @@ -30,7 +30,7 @@ data "aws_availability_zones" "available" { resource "aws_security_group" "powerbi_gateway" { count = var.enable_powerbi_gateway ? 1 : 0 - name = "${local.csi}-powerbi-gateway-security-group" + name = "${local.csi}-security-group" vpc_id = module.powerbi_gateway_vpc[0].vpc_id egress { @@ -62,6 +62,6 @@ resource "aws_security_group" "powerbi_gateway" { } tags = { - Name = "${local.csi}-powerbi-gateway-sg" + Name = "${local.csi}-sg" } } diff --git a/infrastructure/terraform/components/powerbi-gateway/ssm_parameter_powerbi_gateway_recovery_key.tf b/infrastructure/terraform/components/powerbi-gateway/ssm_parameter_powerbi_gateway_recovery_key.tf index eec82770..3a01dc64 100644 --- a/infrastructure/terraform/components/powerbi-gateway/ssm_parameter_powerbi_gateway_recovery_key.tf +++ b/infrastructure/terraform/components/powerbi-gateway/ssm_parameter_powerbi_gateway_recovery_key.tf @@ -1,7 +1,7 @@ resource "aws_ssm_parameter" "powerbi_gateway_recovery_key" { count = var.enable_powerbi_gateway ? 1 : 0 - name = "/${local.csi}/powerbi-gateway-recovery-key" + name = "/${local.csi}/gateway-recovery-key" description = "The Recovery Key for the On-Premises Gateway - Updated manually with the actual key value after deployment" type = "SecureString" value = "RECOVERY_KEY_PLACEHOLDER" diff --git a/scripts/config/gitleaks.toml b/scripts/config/gitleaks.toml index 3f748294..106544af 100644 --- a/scripts/config/gitleaks.toml +++ b/scripts/config/gitleaks.toml @@ -22,7 +22,9 @@ paths = [ '''poetry.lock''', '''yarn.lock''', '''Gemfile.lock''', - '''infrastructure/terraform/components/reporting/templates/cloudinit_config.tmpl''' + '''infrastructure/terraform/components/reporting/templates/cloudinit_config.tmpl''', + '''infrastructure/terraform/components/powerbi-gateway/variables.tf''', + '''infrastructure/terraform/components/powerbi-gateway/README.md''' ] # Exclude Chrome version in user agent From eb98e30495f24e1c27b438da069bad7d938d1c02 Mon Sep 17 00:00:00 2001 From: jamesthompson26-nhs Date: Thu, 7 May 2026 16:02:15 +0100 Subject: [PATCH 8/8] CCM-15541: Reporting PowerBI Gateway Component --- .../terraform/components/reporting/README.md | 13 +- ...oudwatch_metric_alarm_patch_task_failed.tf | 18 ++ ...alarm_powerbi_gateway_standalone_status.tf | 25 ++ .../data_cloudinit_config_powerbi_gateway.tf | 11 + .../ec2_instances_powerbi_gateway.tf | 13 + .../iam_instance_profile_powerbi_gateway.tf | 226 ++++++++++++++++++ .../components/reporting/kms_key_ebs.tf | 107 +++++++++ ...nch_template_powerbi_gateway_standalone.tf | 67 ++++++ .../terraform/components/reporting/locals.tf | 13 + .../reporting/module_powerbi_gateway_vpc.tf | 67 ++++++ .../ssm_maintenance_window_patch_window.tf | 33 +++ ...tenance_window_target_windows_instances.tf | 42 ++++ .../ssm_maintenance_window_task_patch_task.tf | 92 +++++++ ..._parameter_powerbi_gateway_recovery_key.tf | 14 ++ ...m_patch_baseline_windows_patch_baseline.tf | 24 ++ .../ssm_patch_group_windows_patch_group.tf | 6 + .../components/reporting/variables.tf | 54 +++++ 17 files changed, 824 insertions(+), 1 deletion(-) create mode 100644 infrastructure/terraform/components/reporting/cloudwatch_metric_alarm_patch_task_failed.tf create mode 100644 infrastructure/terraform/components/reporting/cloudwatch_metric_alarm_powerbi_gateway_standalone_status.tf create mode 100644 infrastructure/terraform/components/reporting/data_cloudinit_config_powerbi_gateway.tf create mode 100644 infrastructure/terraform/components/reporting/ec2_instances_powerbi_gateway.tf create mode 100644 infrastructure/terraform/components/reporting/iam_instance_profile_powerbi_gateway.tf create mode 100644 infrastructure/terraform/components/reporting/kms_key_ebs.tf create mode 100644 infrastructure/terraform/components/reporting/launch_template_powerbi_gateway_standalone.tf create mode 100644 infrastructure/terraform/components/reporting/module_powerbi_gateway_vpc.tf create mode 100644 infrastructure/terraform/components/reporting/ssm_maintenance_window_patch_window.tf create mode 100644 infrastructure/terraform/components/reporting/ssm_maintenance_window_target_windows_instances.tf create mode 100644 infrastructure/terraform/components/reporting/ssm_maintenance_window_task_patch_task.tf create mode 100644 infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_recovery_key.tf create mode 100644 infrastructure/terraform/components/reporting/ssm_patch_baseline_windows_patch_baseline.tf create mode 100644 infrastructure/terraform/components/reporting/ssm_patch_group_windows_patch_group.tf diff --git a/infrastructure/terraform/components/reporting/README.md b/infrastructure/terraform/components/reporting/README.md index 4be165b5..8db61524 100644 --- a/infrastructure/terraform/components/reporting/README.md +++ b/infrastructure/terraform/components/reporting/README.md @@ -13,6 +13,7 @@ No requirements. | [account\_name](#input\_account\_name) | The name of the AWS Account to deploy into (see globals.tfvars) | `string` | n/a | yes | | [app\_deployer\_role\_name](#input\_app\_deployer\_role\_name) | Name of the app deployer role that is allowed to deploy Comms Mgr applications but not create other IAM roles | `string` | n/a | yes | | [app\_deployer\_role\_permission\_account\_ids](#input\_app\_deployer\_role\_permission\_account\_ids) | All AWS Account IDs for this project that have the AppDeployer role created | `map(string)` | `{}` | no | +| [athena\_driver\_url](#input\_athena\_driver\_url) | Amazon Athena ODBC MSI download URL for PowerBI gateway bootstrap | `string` | `"https://downloads.athena.us-east-1.amazonaws.com/drivers/ODBC/v2.1.0.0/Windows/AmazonAthenaODBC-2.1.0.0.msi"` | no | | [aws\_account\_id](#input\_aws\_account\_id) | The AWS Account ID (numeric) | `string` | n/a | yes | | [batch\_client\_ids](#input\_batch\_client\_ids) | List of client ids that require additional batch identifier dimensions when aggregating data | `list(string)` |
[
"NULL"
]
| no | | [cloudtrail\_log\_group\_name](#input\_cloudtrail\_log\_group\_name) | The name of the Cloudtrail log group name on the account (see globals.tfvars) | `string` | n/a | yes | @@ -25,24 +26,34 @@ No requirements. | [default\_tags](#input\_default\_tags) | A map of default tags to apply to all taggable resources within the component | `map(string)` | `{}` | no | | [destination\_backup\_vault\_arn](#input\_destination\_backup\_vault\_arn) | ARN of the destination backup vault to copy periodic backups to | `string` | `""` | no | | [email\_filter\_client\_ids](#input\_email\_filter\_client\_ids) | List of client ids that need email-only sending groups to be hidden in certain views | `list(string)` |
[
"NULL"
]
| no | +| [enable\_powerbi\_gateway](#input\_enable\_powerbi\_gateway) | Deploy supporting resources for the legacy PowerBI On-Premises Gateway | `bool` | `true` | no | | [enable\_s3\_backup](#input\_enable\_s3\_backup) | Enable AWS S3 Backup of the data bucket | `bool` | `true` | no | +| [enable\_spot](#input\_enable\_spot) | run Power BI On-Premises Gateway as spot instances | `bool` | `false` | no | | [enable\_vault\_lock\_configuration](#input\_enable\_vault\_lock\_configuration) | Enable vault lock, preventing the deletion of a vault that contains 1 or more Recovery Points | `bool` | `false` | no | | [environment](#input\_environment) | The name of the environment | `string` | n/a | yes | | [group](#input\_group) | The group variables are being inherited from (often synonmous with account short-name) | `string` | `"n/a"` | no | +| [instance\_type](#input\_instance\_type) | The EC2 instance type. | `string` | `"t3.medium"` | no | | [log\_retention\_days](#input\_log\_retention\_days) | How many days to retain the logs generated by the step function | `number` | `30` | no | | [module](#input\_module) | The variable encapsulating the name of this module | `string` | `"n/a"` | no | | [parent\_acct\_environment](#input\_parent\_acct\_environment) | Name of the environment responsible for the acct resources used, affects things like DNS zone. Useful for named dev environments | `string` | `"main"` | no | | [periodic\_s3backup\_copy\_retention\_days](#input\_periodic\_s3backup\_copy\_retention\_days) | number of days to retain weekly s3 backups in the destination vault | `number` | `31` | no | | [periodic\_s3backup\_retention\_days](#input\_periodic\_s3backup\_retention\_days) | number of days to retain weekly s3 backups | `number` | `31` | no | | [periodic\_s3backup\_schedule](#input\_periodic\_s3backup\_schedule) | Crontab formatted schedule for Periodic S3 Backups | `string` | `"cron(0 5 ? * 7 *)"` | no | +| [powerbi\_gateway\_instance\_count](#input\_powerbi\_gateway\_instance\_count) | Number of standalone Power BI On-Premises Gateway instances created directly from the launch template. | `number` | `2` | no | +| [private\_subnet\_cidrs](#input\_private\_subnet\_cidrs) | List of CIDR blocks for private subnets. | `list(string)` | `[]` | no | | [project](#input\_project) | The name of the Project we are bootstrapping tfscaffold for | `string` | n/a | yes | +| [public\_subnet\_cidrs](#input\_public\_subnet\_cidrs) | List of CIDR blocks for public subnets. | `list(string)` | `[]` | no | | [region](#input\_region) | The AWS Region | `string` | n/a | yes | +| [root\_volume\_size](#input\_root\_volume\_size) | Size of root volume for the Power BI On-Premises Gateway instances - 30GB minimum for Windows Server | `number` | `80` | no | | [shared\_infra\_account\_id](#input\_shared\_infra\_account\_id) | The AWS Account ID of the shared infrastructure account | `string` | `"000000000000"` | no | | [sms\_nudge\_client\_id](#input\_sms\_nudge\_client\_id) | Client id for the SMS Nudge umbrella client used to filter smsnudge views | `string` | `"NULL"` | no | +| [spot\_max\_price](#input\_spot\_max\_price) | max spot price for Power BI On-Premises Gateway instances | `string` | `"0.3"` | no | | [superuser\_role\_name](#input\_superuser\_role\_name) | Name of the superuser role that is allowed to create other IAM roles | `string` | n/a | yes | ## Modules -No modules. +| Name | Source | Version | +|------|--------|---------| +| [powerbi\_gateway\_vpc](#module\_powerbi\_gateway\_vpc) | terraform-aws-modules/vpc/aws | 5.5.1 | ## Outputs | Name | Description | diff --git a/infrastructure/terraform/components/reporting/cloudwatch_metric_alarm_patch_task_failed.tf b/infrastructure/terraform/components/reporting/cloudwatch_metric_alarm_patch_task_failed.tf new file mode 100644 index 00000000..bf230769 --- /dev/null +++ b/infrastructure/terraform/components/reporting/cloudwatch_metric_alarm_patch_task_failed.tf @@ -0,0 +1,18 @@ +resource "aws_cloudwatch_metric_alarm" "patch_task_failed" { + count = var.enable_powerbi_gateway ? 1 : 0 + + alarm_name = "${local.csi}-patch-task-failed" + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = 1 + metric_name = "FailedCommands" + namespace = "AWS/SSM-RunCommand" + period = 300 + statistic = "Sum" + threshold = 1 + alarm_description = "Alarm when the AWS-RunPatchBaseline maintenance window task reports a failed run" + treat_missing_data = "notBreaching" + + dimensions = { + DocumentName = "AWS-RunPatchBaseline" + } +} diff --git a/infrastructure/terraform/components/reporting/cloudwatch_metric_alarm_powerbi_gateway_standalone_status.tf b/infrastructure/terraform/components/reporting/cloudwatch_metric_alarm_powerbi_gateway_standalone_status.tf new file mode 100644 index 00000000..a84763fb --- /dev/null +++ b/infrastructure/terraform/components/reporting/cloudwatch_metric_alarm_powerbi_gateway_standalone_status.tf @@ -0,0 +1,25 @@ +resource "aws_cloudwatch_metric_alarm" "powerbi_gateway_standalone_status_check_failed" { + for_each = var.enable_powerbi_gateway ? { + for idx, instance in aws_instance.powerbi_gateway_standalone : + idx => { + id = instance.id + name = format("%s-powerbi-gateway-standalone-%02d-status-check-failed", local.csi, idx + 1) + } + } : {} + + alarm_name = each.value.name + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = 2 + datapoints_to_alarm = 2 + metric_name = "StatusCheckFailed" + namespace = "AWS/EC2" + period = 300 + statistic = "Maximum" + threshold = 1 + alarm_description = "Instance or system status check failed for a standalone Power BI gateway host" + treat_missing_data = "breaching" + + dimensions = { + InstanceId = each.value.id + } +} diff --git a/infrastructure/terraform/components/reporting/data_cloudinit_config_powerbi_gateway.tf b/infrastructure/terraform/components/reporting/data_cloudinit_config_powerbi_gateway.tf new file mode 100644 index 00000000..0d36a054 --- /dev/null +++ b/infrastructure/terraform/components/reporting/data_cloudinit_config_powerbi_gateway.tf @@ -0,0 +1,11 @@ +data "cloudinit_config" "powerbi_gateway" { + count = var.enable_powerbi_gateway ? 1 : 0 + + gzip = false + base64_encode = true + + part { + content_type = "text/cloud-config" + content = local.powerbi_gateway_script + } +} diff --git a/infrastructure/terraform/components/reporting/ec2_instances_powerbi_gateway.tf b/infrastructure/terraform/components/reporting/ec2_instances_powerbi_gateway.tf new file mode 100644 index 00000000..9ec3ffa6 --- /dev/null +++ b/infrastructure/terraform/components/reporting/ec2_instances_powerbi_gateway.tf @@ -0,0 +1,13 @@ +resource "aws_instance" "powerbi_gateway_standalone" { + count = var.enable_powerbi_gateway ? var.powerbi_gateway_instance_count : 0 + + associate_public_ip_address = false + launch_template { + id = aws_launch_template.powerbi_gateway_standalone[0].id + version = "$Latest" + } + + tags = { + Name = format("%s-powerbi-gateway-standalone-%02d", local.csi, count.index + 1) + } +} diff --git a/infrastructure/terraform/components/reporting/iam_instance_profile_powerbi_gateway.tf b/infrastructure/terraform/components/reporting/iam_instance_profile_powerbi_gateway.tf new file mode 100644 index 00000000..e2a1efee --- /dev/null +++ b/infrastructure/terraform/components/reporting/iam_instance_profile_powerbi_gateway.tf @@ -0,0 +1,226 @@ +resource "aws_iam_instance_profile" "powerbi_gateway" { + count = var.enable_powerbi_gateway ? 1 : 0 + + name = "${local.csi}-powerbi-gateway" + role = aws_iam_role.powerbi_gateway_role[0].name +} + +data "aws_iam_policy_document" "powerbi_gateway_assume_role_policy" { + count = var.enable_powerbi_gateway ? 1 : 0 + + statement { + effect = "Allow" + + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + + actions = ["sts:AssumeRole"] + } +} + +resource "aws_iam_role" "powerbi_gateway_role" { + count = var.enable_powerbi_gateway ? 1 : 0 + + name = "${local.csi}-powerbi-gateway" + description = "PowerBI Gateway Instance Role" + path = "/" + assume_role_policy = data.aws_iam_policy_document.powerbi_gateway_assume_role_policy[0].json +} + + +resource "aws_iam_policy" "powerbi_gateway_permissions_policy" { + count = var.enable_powerbi_gateway ? 1 : 0 + + name = "${local.csi}-powerbi-gateway" + description = "PowerBI Gateway Instance Permissions" + path = "/" + policy = data.aws_iam_policy_document.powerbi_gateway_permissions_policy[0].json +} + +resource "aws_iam_role_policy_attachment" "powerbi_gateway_permissions_policy_attachment" { + count = var.enable_powerbi_gateway ? 1 : 0 + + role = aws_iam_role.powerbi_gateway_role[0].name + policy_arn = aws_iam_policy.powerbi_gateway_permissions_policy[0].arn +} + +resource "aws_iam_role_policy_attachment" "powerbi_gateway_ssm_policy_attachment" { + count = var.enable_powerbi_gateway ? 1 : 0 + + role = aws_iam_role.powerbi_gateway_role[0].name + policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" +} + +data "aws_iam_policy_document" "powerbi_gateway_permissions_policy" { + count = var.enable_powerbi_gateway ? 1 : 0 + + statement { + sid = "AllowLogs" + effect = "Allow" + + actions = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ] + + resources = [ + "arn:aws:logs:${local.parameter_bundle.region}:${local.this_account}:log-group:*", + ] + } + + statement { + sid = "AllowS3DataBucket" + effect = "Allow" + + actions = [ + "s3:GetBucketLocation", + "s3:GetObject", + "s3:ListBucket" + ] + + resources = [ + aws_s3_bucket.data.arn, + "${aws_s3_bucket.data.arn}/*" + ] + } + + statement { + sid = "AllowS3ResultsBucket" + effect = "Allow" + + actions = [ + "s3:GetBucketLocation", + "s3:GetObject", + "s3:ListBucket", + "s3:PutObject" + ] + + resources = [ + aws_s3_bucket.results.arn, + "${aws_s3_bucket.results.arn}/*" + ] + } + + statement { + sid = "AllowAthenaAccess1" + effect = "Allow" + + actions = [ + "athena:GetQueryResults", + "athena:GetQueryResultsStream", + "athena:GetQueryExecution", + "athena:StartQueryExecution", + "athena:GetWorkGroup", + "athena:GetNamedQuery" + ] + + resources = [ + aws_athena_workgroup.user.arn + ] + } + + statement { + sid = "AllowAthenaAccess2" + effect = "Allow" + + actions = [ + "athena:GetDatabase", + "athena:GetTableMetadata", + "athena:GetDataCatalog", + "athena:GetTable" + ] + + resources = [ + "arn:aws:athena:${var.region}:${local.this_account}:datacatalog/AWSDataCatalog" + ] + } + + statement { + sid = "AllowAthenaAccess3" + effect = "Allow" + + actions = [ + "athena:ListDataCatalogs", + "athena:ListDatabases", + "athena:ListTableMetadata", + "athena:ListWorkGroups" + ] + + resources = ["*"] # Access to List all above is required. Condition keys not supported for these resources. + } + + statement { + sid = "AllowGlueAccess" + effect = "Allow" + + actions = [ + "glue:GetTable", + "glue:GetTables", + "glue:BatchGetTable", + "glue:GetDatabase", + "glue:GetDatabases", + "glue:GetPartition", + "glue:GetPartitions" + ] + + resources = concat( + local.core_glue_catalog_resources, # Access to all core account catalogs is required as they are all accessible via the default catalog in the environment's account + [ + aws_glue_catalog_database.reporting.arn, + "arn:aws:glue:${var.region}:${var.core_account_id}:catalog", + "arn:aws:glue:${var.region}:${local.this_account}:catalog", + # Tables + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_plan_completed_summary", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_plan_completed_summary_batch", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_plan_status", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_status", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_status_summary", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_status_summary_batch", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/client_latest_name", + # Views + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_plan_completed_summary_all", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_status_summary_all", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_status_summary_all_email_filter", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_status_smsnudge_staging", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_plan_status_smsnudge", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_plan_read_status_smsnudge", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/request_item_status_smsnudge", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/dates", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/letters_invoice_units_monthly", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/letters_invoice_units_weekly", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/latency_percentiles", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/daily_recipient_count", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/daily_recipient_distribution", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/raw_latency_3m", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/delivered_messages", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/monthly_app_recipients_distribution", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/monthly_app_recipients_multiple_clients", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/monthly_messages_per_recipient", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/monthly_recipient_with_more_than_five_messages", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/monthly_recipients_distribution", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/monthly_recipients_distribution_by_integrator", + "arn:aws:glue:${var.region}:${local.this_account}:table/${aws_glue_catalog_database.reporting.name}/billing_transactions", + ] + ) + } + + statement { + sid = "AllowS3KMSAccess" + effect = "Allow" + + actions = [ + "kms:Decrypt", + "kms:Encrypt", + "kms:GenerateDataKey", + "kms:GenerateDataKeyWithoutPlaintext", + "kms:DescribeKey" + ] + + resources = [ + aws_kms_key.s3.arn + ] + } +} diff --git a/infrastructure/terraform/components/reporting/kms_key_ebs.tf b/infrastructure/terraform/components/reporting/kms_key_ebs.tf new file mode 100644 index 00000000..1e72bb7c --- /dev/null +++ b/infrastructure/terraform/components/reporting/kms_key_ebs.tf @@ -0,0 +1,107 @@ +resource "aws_kms_key" "ebs" { + count = var.enable_powerbi_gateway ? 1 : 0 + + description = "CMK for encrypting EBS volumes" + deletion_window_in_days = local.parameter_bundle.default_kms_deletion_window_in_days + enable_key_rotation = true + policy = data.aws_iam_policy_document.ebs[0].json +} + +resource "aws_kms_alias" "ebs" { + count = var.enable_powerbi_gateway ? 1 : 0 + + name = "alias/${local.csi}-ebs" + target_key_id = aws_kms_key.ebs[0].key_id +} + + +data "aws_iam_policy_document" "ebs" { + count = var.enable_powerbi_gateway ? 1 : 0 + + statement { + sid = "AllowLocalIAMAdministration" + effect = "Allow" + + actions = [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion", + "kms:Decrypt", + "kms:Encrypt", + "kms:GenerateDataKey", + "kms:TagResource" + ] + + resources = [ + "*", + ] + + principals { + type = "AWS" + identifiers = [ + local.parameter_bundle.iam_resource_arns.any_authorised_user_in_this_account, + + ] + } + } + + statement { + sid = "AllowUsageAccess" + effect = "Allow" + + actions = [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*", + ] + + resources = [ + "*", + ] + + principals { + type = "AWS" + identifiers = [ + local.parameter_bundle.iam_resource_arns.any_authorised_user_in_this_account, + "arn:aws:iam::${local.this_account}:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling" + + ] + } + } + statement { + sid = "AllowAutoscalingToUse" + effect = "Allow" + + actions = [ + "kms:CreateGrant", + ] + + resources = [ + "*", + ] + + principals { + type = "AWS" + identifiers = [ + "arn:aws:iam::${local.this_account}:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling", + ] + } + + condition { + test = "Bool" + variable = "kms:GrantIsForAWSResource" + values = ["true"] + } + } +} diff --git a/infrastructure/terraform/components/reporting/launch_template_powerbi_gateway_standalone.tf b/infrastructure/terraform/components/reporting/launch_template_powerbi_gateway_standalone.tf new file mode 100644 index 00000000..1199f786 --- /dev/null +++ b/infrastructure/terraform/components/reporting/launch_template_powerbi_gateway_standalone.tf @@ -0,0 +1,67 @@ +resource "aws_launch_template" "powerbi_gateway_standalone" { + count = var.enable_powerbi_gateway ? 1 : 0 + + name = "${local.csi}-standalone" + description = "Template for the Power BI On-Premises Gateway (standalone instances)" + update_default_version = true + image_id = "resolve:ssm:/aws/service/ami-windows-latest/Windows_Server-2022-English-Full-Base" + instance_type = var.instance_type + user_data = data.cloudinit_config.powerbi_gateway[0].rendered + instance_initiated_shutdown_behavior = var.enable_spot ? "terminate" : "stop" + ebs_optimized = true + + block_device_mappings { + device_name = "/dev/sda1" + ebs { + delete_on_termination = true + encrypted = true + kms_key_id = aws_kms_key.ebs[0].arn + volume_size = var.root_volume_size + volume_type = "gp3" + } + } + + iam_instance_profile { + name = aws_iam_instance_profile.powerbi_gateway[0].name + } + + dynamic "instance_market_options" { + for_each = var.enable_spot ? [1] : [] + content { + market_type = "spot" + spot_options { + max_price = var.spot_max_price + spot_instance_type = "one-time" + } + } + } + + monitoring { + enabled = true + } + + network_interfaces { + delete_on_termination = true + associate_public_ip_address = false + security_groups = [ + aws_security_group.powerbi_gateway[0].id + ] + subnet_id = element(module.powerbi_gateway_vpc[0].private_subnets, count.index) + } + + metadata_options { + http_endpoint = "enabled" + http_tokens = "required" + http_put_response_hop_limit = 5 + } + + tag_specifications { + resource_type = "instance" + tags = local.deployment_default_tags + } + + tag_specifications { + resource_type = "volume" + tags = local.deployment_default_tags + } +} diff --git a/infrastructure/terraform/components/reporting/locals.tf b/infrastructure/terraform/components/reporting/locals.tf index 7dc0ea8c..761faa69 100644 --- a/infrastructure/terraform/components/reporting/locals.tf +++ b/infrastructure/terraform/components/reporting/locals.tf @@ -54,6 +54,19 @@ locals { this_account = local.base_parameter_bundle.account_ids[local.base_parameter_bundle.account_name] + # Create the powerbi_gateway_script only if var.enable_powerbi_gateway is true + powerbi_gateway_script = var.enable_powerbi_gateway ? templatefile("${path.module}/templates/cloudinit_config.tmpl", { + odbc_dsn_name = "${local.csi}-dsn" + odbc_description = "AWS Simba Athena ODBC Connection for ${local.csi}" + athena_driver_url = var.athena_driver_url + region = var.region + catalog = "AWSDataCatalog" + database = aws_glue_catalog_database.reporting.name + workgroup = aws_athena_workgroup.user.name + authentication_type = "Instance Profile" + gateway_name = "${local.csi}-gateway" + }) : null + use_core_glue_catalog_resources = length(var.core_account_ids) > 0 ? true : false core_glue_catalog_resources = local.use_core_glue_catalog_resources ? flatten([ diff --git a/infrastructure/terraform/components/reporting/module_powerbi_gateway_vpc.tf b/infrastructure/terraform/components/reporting/module_powerbi_gateway_vpc.tf new file mode 100644 index 00000000..d8e55ada --- /dev/null +++ b/infrastructure/terraform/components/reporting/module_powerbi_gateway_vpc.tf @@ -0,0 +1,67 @@ +module "powerbi_gateway_vpc" { + count = var.enable_powerbi_gateway ? 1 : 0 + + source = "terraform-aws-modules/vpc/aws" + version = "5.5.1" # Adjust to the latest version + + create_vpc = var.enable_powerbi_gateway + + name = "${local.csi}-powerbi-gateway-vpc" + cidr = "10.0.0.0/16" + + azs = data.aws_availability_zones.available[0].names + public_subnets = var.public_subnet_cidrs + private_subnets = var.private_subnet_cidrs + + enable_nat_gateway = true + single_nat_gateway = false + enable_dns_support = true + enable_dns_hostnames = true + create_igw = true +} + +data "aws_availability_zones" "available" { + count = var.enable_powerbi_gateway ? 1 : 0 + + state = "available" +} + +#trivy:ignore:aws-ec2-no-public-egress-sgr +resource "aws_security_group" "powerbi_gateway" { + count = var.enable_powerbi_gateway ? 1 : 0 + + name = "${local.csi}-powerbi-gateway-security-group" + vpc_id = module.powerbi_gateway_vpc[0].vpc_id + + egress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 5671 + to_port = 5672 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 9350 + to_port = 9354 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${local.csi}-powerbi-gateway-sg" + } +} diff --git a/infrastructure/terraform/components/reporting/ssm_maintenance_window_patch_window.tf b/infrastructure/terraform/components/reporting/ssm_maintenance_window_patch_window.tf new file mode 100644 index 00000000..ea6005ec --- /dev/null +++ b/infrastructure/terraform/components/reporting/ssm_maintenance_window_patch_window.tf @@ -0,0 +1,33 @@ +resource "aws_ssm_maintenance_window" "patch_window_sunday" { + count = var.enable_powerbi_gateway ? 1 : 0 + + name = "${local.csi}-windows-patch-window-sun" + description = "Windows Server 2022 Sunday Patch Window" + schedule = "cron(0 3 ? * SUN *)" # Every Sunday at 3 AM + duration = 4 + cutoff = 1 + allow_unassociated_targets = true +} + +resource "aws_ssm_maintenance_window" "patch_window_wednesday" { + count = var.enable_powerbi_gateway ? 1 : 0 + + name = "${local.csi}-windows-patch-window-wed" + description = "Windows Server 2022 Wednesday Patch Window" + schedule = "cron(0 3 ? * WED *)" # Every Wednesday at 3 AM + duration = 4 + cutoff = 1 + allow_unassociated_targets = true +} + +## Remove me later - replaced by above two windows +resource "aws_ssm_maintenance_window" "patch_window" { + count = var.enable_powerbi_gateway ? 1 : 0 + + name = "${local.csi}-windows-patch-window" + description = "Windows Server 2022 Patch Window" + schedule = "cron(0 3 ? * SUN *)" # Every Sunday at 3 AM + duration = 4 + cutoff = 1 + allow_unassociated_targets = true +} diff --git a/infrastructure/terraform/components/reporting/ssm_maintenance_window_target_windows_instances.tf b/infrastructure/terraform/components/reporting/ssm_maintenance_window_target_windows_instances.tf new file mode 100644 index 00000000..af913ec6 --- /dev/null +++ b/infrastructure/terraform/components/reporting/ssm_maintenance_window_target_windows_instances.tf @@ -0,0 +1,42 @@ +resource "aws_ssm_maintenance_window_target" "windows_instances_sunday" { + count = var.enable_powerbi_gateway && var.powerbi_gateway_instance_count >= 1 ? 1 : 0 + + description = "Windows Server 2022 Sunday Maintenance Window Target " + window_id = aws_ssm_maintenance_window.patch_window_sunday[0].id + resource_type = "INSTANCE" + name = "${local.csi}-maintenance-window-target-sun" + + targets { + key = "InstanceIds" + values = [aws_instance.powerbi_gateway_standalone[0].id] + } +} + +resource "aws_ssm_maintenance_window_target" "windows_instances_wednesday" { + count = var.enable_powerbi_gateway && var.powerbi_gateway_instance_count >= 2 ? 1 : 0 + + description = "Windows Server 2022 Wednesday Maintenance Window Target" + window_id = aws_ssm_maintenance_window.patch_window_wednesday[0].id + resource_type = "INSTANCE" + name = "${local.csi}-maintenance-window-target-wed" + + targets { + key = "InstanceIds" + values = [aws_instance.powerbi_gateway_standalone[1].id] + } +} + +## Remove me later - replaced by above two targets +resource "aws_ssm_maintenance_window_target" "windows_instances" { + count = var.enable_powerbi_gateway ? 1 : 0 + + description = "Windows Server 2022 Maintenance Window Target" + window_id = aws_ssm_maintenance_window.patch_window[0].id + resource_type = "INSTANCE" + name = "${local.csi}-maintenance-window-target" + + targets { + key = "tag:Patch Group" + values = ["${local.csi}-windows-group"] + } +} diff --git a/infrastructure/terraform/components/reporting/ssm_maintenance_window_task_patch_task.tf b/infrastructure/terraform/components/reporting/ssm_maintenance_window_task_patch_task.tf new file mode 100644 index 00000000..a17e151e --- /dev/null +++ b/infrastructure/terraform/components/reporting/ssm_maintenance_window_task_patch_task.tf @@ -0,0 +1,92 @@ +resource "aws_ssm_maintenance_window_task" "patch_task_sunday" { + count = var.enable_powerbi_gateway ? 1 : 0 + + description = "Windows Server 2022 Sunday Patch Task" + window_id = aws_ssm_maintenance_window.patch_window_sunday[0].id + task_arn = "AWS-RunPatchBaseline" + task_type = "RUN_COMMAND" + + targets { + key = "WindowTargetIds" + values = [aws_ssm_maintenance_window_target.windows_instances_sunday[0].id] + } + + task_invocation_parameters { + run_command_parameters { + comment = "Patching Sunday instances" + parameter { + name = "Operation" + values = ["Install"] + } + parameter { + name = "RebootOption" + values = ["RebootIfNeeded"] + } + } + } + + priority = 1 + max_concurrency = "1" + max_errors = "1" +} + +resource "aws_ssm_maintenance_window_task" "patch_task_wednesday" { + count = var.enable_powerbi_gateway && var.powerbi_gateway_instance_count >= 2 ? 1 : 0 + + description = "Windows Server 2022 Wednesday Patch Task" + window_id = aws_ssm_maintenance_window.patch_window_wednesday[0].id + task_arn = "AWS-RunPatchBaseline" + task_type = "RUN_COMMAND" + + targets { + key = "WindowTargetIds" + values = [aws_ssm_maintenance_window_target.windows_instances_wednesday[0].id] + } + + task_invocation_parameters { + run_command_parameters { + comment = "Patching Wednesday instance" + parameter { + name = "Operation" + values = ["Install"] + } + parameter { + name = "RebootOption" + values = ["RebootIfNeeded"] + } + } + } + + priority = 1 + max_concurrency = "1" + max_errors = "1" +} + +## Remove me later - replaced by above two tasks +resource "aws_ssm_maintenance_window_task" "patch_task" { + count = var.enable_powerbi_gateway ? 1 : 0 + + description = "Windows Server 2022 Patch Task" + window_id = aws_ssm_maintenance_window.patch_window[0].id + task_arn = "AWS-RunPatchBaseline" + task_type = "RUN_COMMAND" + + targets { + key = "WindowTargetIds" + values = [aws_ssm_maintenance_window_target.windows_instances[0].id] + } + + task_invocation_parameters { + run_command_parameters { + comment = "Patching Windows Instances" + parameter { + name = "Operation" + values = ["Install"] + } + } + } + + priority = 1 + max_concurrency = "2" + max_errors = "1" +} diff --git a/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_recovery_key.tf b/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_recovery_key.tf new file mode 100644 index 00000000..367ebb26 --- /dev/null +++ b/infrastructure/terraform/components/reporting/ssm_parameter_powerbi_gateway_recovery_key.tf @@ -0,0 +1,14 @@ +resource "aws_ssm_parameter" "powerbi_gateway_recovery_key" { + count = var.enable_powerbi_gateway ? 1 : 0 + + name = "/${local.csi}/powerbi-gateway-recovery-key" + description = "The Recovery Key for the On-Premises Gateway" + type = "SecureString" + value = "RECOVERY_KEY_PLACEHOLDER" + + lifecycle { + ignore_changes = [ + value, + ] + } +} diff --git a/infrastructure/terraform/components/reporting/ssm_patch_baseline_windows_patch_baseline.tf b/infrastructure/terraform/components/reporting/ssm_patch_baseline_windows_patch_baseline.tf new file mode 100644 index 00000000..992c477c --- /dev/null +++ b/infrastructure/terraform/components/reporting/ssm_patch_baseline_windows_patch_baseline.tf @@ -0,0 +1,24 @@ +resource "aws_ssm_patch_baseline" "windows_patch_baseline" { + count = var.enable_powerbi_gateway ? 1 : 0 + + name = "${local.csi}-windows-patch-baseline" + description = "Windows Server 2022 Patch Baseline" + operating_system = "WINDOWS" + approval_rule { + patch_filter { + key = "PRODUCT" + values = ["WindowsServer2022"] + } + patch_filter { + key = "CLASSIFICATION" + values = ["SecurityUpdates", "CriticalUpdates"] + } + patch_filter { + key = "MSRC_SEVERITY" + values = [ + "Critical", + "Important", + ] + } + } +} diff --git a/infrastructure/terraform/components/reporting/ssm_patch_group_windows_patch_group.tf b/infrastructure/terraform/components/reporting/ssm_patch_group_windows_patch_group.tf new file mode 100644 index 00000000..d316a3cf --- /dev/null +++ b/infrastructure/terraform/components/reporting/ssm_patch_group_windows_patch_group.tf @@ -0,0 +1,6 @@ +resource "aws_ssm_patch_group" "windows_patch_group" { + count = var.enable_powerbi_gateway ? 1 : 0 + + baseline_id = aws_ssm_patch_baseline.windows_patch_baseline[0].id + patch_group = "${local.csi}-windows-group" +} diff --git a/infrastructure/terraform/components/reporting/variables.tf b/infrastructure/terraform/components/reporting/variables.tf index 9340c410..2d8130c3 100644 --- a/infrastructure/terraform/components/reporting/variables.tf +++ b/infrastructure/terraform/components/reporting/variables.tf @@ -97,6 +97,60 @@ variable "log_retention_days" { default = 30 } +variable "enable_powerbi_gateway" { + type = bool + description = "Deploy supporting resources for the legacy PowerBI On-Premises Gateway" + default = true +} + +variable "athena_driver_url" { + type = string + description = "Amazon Athena ODBC MSI download URL for PowerBI gateway bootstrap" + default = "https://downloads.athena.us-east-1.amazonaws.com/drivers/ODBC/v2.1.0.0/Windows/AmazonAthenaODBC-2.1.0.0.msi" +} + +variable "powerbi_gateway_instance_count" { + description = "Number of standalone Power BI On-Premises Gateway instances created directly from the launch template." + type = number + default = 2 +} + +variable "public_subnet_cidrs" { + description = "List of CIDR blocks for public subnets." + type = list(string) + default = [] +} + +variable "private_subnet_cidrs" { + description = "List of CIDR blocks for private subnets." + type = list(string) + default = [] +} + +variable "instance_type" { + description = "The EC2 instance type." + type = string + default = "t3.medium" +} + +variable "enable_spot" { + type = bool + description = "run Power BI On-Premises Gateway as spot instances" + default = false +} + +variable "spot_max_price" { + type = string + description = "max spot price for Power BI On-Premises Gateway instances" + default = "0.3" +} + +variable "root_volume_size" { + type = number + description = "Size of root volume for the Power BI On-Premises Gateway instances - 30GB minimum for Windows Server" + default = 80 +} + variable "core_account_ids" { description = "List of all corresponding core account id's that exist in the Non-Prod domain" type = list(string)