Home

Awesome

Modernisation Platform Terraform Loadbalancer Module with Access Logs enabled

Standards Icon Format Code Icon Scorecards Icon SCA Icon Terraform SCA Icon

A Terraform module that creates an application loadbalancer (with loadbalancer security groups) or network loadbalancer in AWS with logging enabled, s3 to store logs and Athena DB to query logs.

An s3 bucket name can be provided in the module by adding the existing_bucket_name variable and adding the bucket name. Otherwise, if no bucket exists one will be created and no variable needs to be set in the module. Application loadbalancers and network loadbalancers do not log to the same S3 bucket location. If you're using existing buckets they also need to have specific permissions applied to them. See the External buckets section for more information.

Either pass in existing security group(s) to attach to the load balancer using the security_groups variable, or define loadbalancer_ingress_rules and loadbalancer_egress_rules variables to create a new security group within the module.

If using the module to create the security group, you can use locals to define the rules for the loadbalancer_ingress_rules and loadbalancer_egress_rules variables as in the below example.

locals {
  loadbalancer_ingress_rules = {
    "cluster_ec2_lb_ingress" = {
      description     = "Cluster EC2 loadbalancer ingress rule"
      from_port       = 8080
      to_port         = 8080
      protocol        = "tcp"
      cidr_blocks     = []
      security_groups = []
    },
    "cluster_ec2_bastion_ingress" = {
      description     = "Cluster EC2 bastion ingress rule"
      from_port       = 3389
      to_port         = 3389
      protocol        = "tcp"
      cidr_blocks     = []
      security_groups = []
    }
  }
  loadbalancer_egress_rules = {
    "cluster_ec2_lb_egress" = {
      description     = "Cluster EC2 loadbalancer egress rule"
      from_port       = 443
      to_port         = 443
      protocol        = "tcp"
      cidr_blocks     = ["0.0.0.0/0"]
      security_groups = []
    }
  }
}

Loadbalancer target groups and listeners need to be created separately.

The use of "aws_glue_catalog_table" resources for application and network loadbalancers means that logs appearing in the S3 bucket will be available to query via Athena without having to carry out any manual Athena config steps.

Module created S3 access_logs bucket

By default the loadbalancer will set up an access_logs bucket for you, unless you set access_logs = false initially for testing or some other reason. Setting this back to true after the lb has been deployed will then create the bucket for you. The reason for the 'depends_on' here is that without the module.s3-bucket resource being created first, the module.lb resource will fail with a validation error.

  depends_on = [
    module.s3-bucket
  ]

External buckets

If you decide to use externally created buckets they need to have been created and have appropriate permissions applied to them BEFORE access_logs = true and existing_bucket_name values are added to the lb code. If you add these values before the bucket is created you will get an error because the lb module will run a check to see if the s3 bucket is writeable and if it is not it will fail.

So to use external_bucket_name the deployment steps are:

  1. Set access_logs = false in the lb create code & create the lb
  2. Create the bucket - making sure the appropriate permissions are applied
  3. Set existing_bucket_name in the lb create code as your-bucket-name-GUID

External bucket permissions

For simplicity the bucket can be created with the following policy attached to it. This applies whether the loadbalancer is an "application" or "network" loadbalancer. This uses the bucket_policy_v2 implementation using the s3_bucket module:

  public-lb-logs-bucket = {
    sse_algorithm = "AES256" # required for Network Loadbalancers
    bucket_policy_v2 = [
      {
        effect = "Allow"
        actions = [
          "s3:PutObject",
        ]
        principals = {
          identifiers = ["arn:aws:iam::652711504416:root"]
          type        = "AWS"
        }
      },
      {
        effect = "Allow"
        actions = [
          "s3:PutObject"
        ]
        principals = {
          identifiers = ["delivery.logs.amazonaws.com"]
          type        = "Service"
        }

        conditions = [
          {
            test     = "StringEquals"
            variable = "s3:x-amz-acl"
            values   = ["bucket-owner-full-control"]
          }
        ]
      },
      {
        effect = "Allow"
        actions = [
          "s3:GetBucketAcl"
        ]
        principals = {
          identifiers = ["delivery.logs.amazonaws.com"]
          type        = "Service"
        }
      }
    ]
    iam_policies = module.baseline_presets.s3_iam_policies
  }

If you want to see exactly what policies are needed for each then refer to NLB Requirements and ALB Requirements

Network Loadbalancer caveats

Application Loadbalancer caveats

Usage


module "lb-access-logs-enabled" {
  source = "github.com/ministryofjustice/modernisation-platform-terraform-loadbalancer"

  providers = {
    # Here we use the default provider for the S3 bucket module, buck replication is disabled but we still
    # Need to pass the provider to the S3 bucket module
    aws.bucket-replication = aws
  }
  vpc_all                             = "${local.vpc_name}-${local.environment}"
  #existing_bucket_name               = "my-bucket-name"
  application_name                    = local.application_name
  public_subnets                      = [data.aws_subnet.public_az_a.id,data.aws_subnet.public_az_b.id,data.aws_subnet.public_az_c.id]
  loadbalancer_ingress_rules          = local.loadbalancer_ingress_rules
  tags                                = local.tags
  account_number                      = local.environment_management.account_ids[terraform.workspace]
  region                              = local.app_data.accounts[local.environment].region
  enable_deletion_protection          = false
  idle_timeout                        = 60
}

<!--- BEGIN_TF_DOCS --->

Requirements

NameVersion
<a name="requirement_terraform"></a> terraform>= 1.0.1
<a name="requirement_aws"></a> aws~> 4.0

Providers

NameVersion
<a name="provider_aws"></a> aws~> 4.0
<a name="provider_template"></a> templaten/a

Modules

NameSourceVersion
<a name="module_s3-bucket"></a> s3-bucketgithub.com/ministryofjustice/modernisation-platform-terraform-s3-bucketv6.1.1

Resources

NameType
aws_athena_database.lb-access-logsresource
aws_athena_named_query.mainresource
aws_athena_workgroup.lb-access-logsresource
aws_lb.loadbalancerresource
aws_security_group.lbresource
aws_elb_service_account.defaultdata source
aws_iam_policy_document.bucket_policydata source
aws_region.currentdata source
aws_vpc.shareddata source
template_file.lb-access-logsdata source

Inputs

NameDescriptionTypeDefaultRequired
<a name="input_account_number"></a> account_numberAccount number of current environmentstringn/ayes
<a name="input_application_name"></a> application_nameName of applicationstringn/ayes
<a name="input_enable_deletion_protection"></a> enable_deletion_protectionIf true, deletion of the load balancer will be disabled via the AWS API. This will prevent Terraform from deleting the load balancer.booln/ayes
<a name="input_existing_bucket_name"></a> existing_bucket_nameThe name of the existing bucket name. If no bucket is provided one will be created for them.string""no
<a name="input_force_destroy_bucket"></a> force_destroy_bucketA boolean that indicates all objects (including any locked objects) should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable.boolfalseno
<a name="input_idle_timeout"></a> idle_timeoutThe time in seconds that the connection is allowed to be idle.stringn/ayes
<a name="input_loadbalancer_egress_rules"></a> loadbalancer_egress_rulesSecurity group egress rules for the loadbalancer<pre>map(object({<br> description = string<br> from_port = number<br> to_port = number<br> protocol = string<br> security_groups = list(string)<br> cidr_blocks = list(string)<br> }))</pre>n/ayes
<a name="input_loadbalancer_ingress_rules"></a> loadbalancer_ingress_rulesSecurity group ingress rules for the loadbalancer<pre>map(object({<br> description = string<br> from_port = number<br> to_port = number<br> protocol = string<br> security_groups = list(string)<br> cidr_blocks = list(string)<br> }))</pre>n/ayes
<a name="input_public_subnets"></a> public_subnetsPublic subnetslist(string)n/ayes
<a name="input_region"></a> regionAWS Region where resources are to be createdstringn/ayes
<a name="input_tags"></a> tagsCommon tags to be used by all resourcesmap(string)n/ayes
<a name="input_vpc_all"></a> vpc_allThe full name of the VPC (including environment) used to create resourcesstringn/ayes

Outputs

NameDescription
<a name="output_athena_db"></a> athena_dbn/a
<a name="output_load_balancer"></a> load_balancern/a
<a name="output_security_group"></a> security_groupn/a
<!--- END_TF_DOCS --->

Looking for issues?

If you're looking to raise an issue with this module, please create a new issue in the Modernisation Platform repository.

<!-- BEGIN_TF_DOCS -->

Requirements

NameVersion
<a name="requirement_terraform"></a> terraform>= 1.0.1
<a name="requirement_aws"></a> aws~> 5.0

Providers

NameVersion
<a name="provider_aws"></a> aws~> 5.0

Modules

NameSourceVersion
<a name="module_s3-bucket"></a> s3-bucketgithub.com/ministryofjustice/modernisation-platform-terraform-s3-bucket568694e50e03630d99cb569eafa06a0b879a1239

Resources

NameType
aws_athena_database.lb-access-logsresource
aws_athena_workgroup.lb-access-logsresource
aws_glue_catalog_table.application_lb_logsresource
aws_glue_catalog_table.network_lb_logsresource
aws_iam_policy.glue_s3resource
aws_iam_role.glueresource
aws_iam_role_policy_attachment.glue_s3resource
aws_iam_role_policy_attachment.glue_serviceresource
aws_lb.loadbalancerresource
aws_lb_target_group.thisresource
aws_lb_target_group_attachment.thisresource
aws_security_group.lbresource
aws_elb_service_account.defaultdata source
aws_iam_policy_document.bucket_policydata source
aws_iam_policy_document.glue_assumedata source
aws_iam_policy_document.glue_s3data source
aws_vpc.shareddata source

Inputs

NameDescriptionTypeDefaultRequired
<a name="input_access_logs"></a> access_logsA boolean that determines whether to have access logsbooltrueno
<a name="input_access_logs_lifecycle_rule"></a> access_logs_lifecycle_ruleCustom lifecycle rule to override the default one<pre>list(object({<br/> id = string<br/> enabled = string<br/> prefix = string<br/> tags = map(string)<br/> transition = list(object({<br/> days = number<br/> storage_class = string<br/> }))<br/> expiration = object({<br/> days = number<br/> })<br/> noncurrent_version_transition = list(object({<br/> days = number<br/> storage_class = string<br/> }))<br/> noncurrent_version_expiration = object({<br/> days = number<br/> })<br/> }))</pre><pre>[<br/> {<br/> "enabled": "Enabled",<br/> "expiration": {<br/> "days": 730<br/> },<br/> "id": "main",<br/> "noncurrent_version_expiration": {<br/> "days": 730<br/> },<br/> "noncurrent_version_transition": [<br/> {<br/> "days": 90,<br/> "storage_class": "STANDARD_IA"<br/> },<br/> {<br/> "days": 365,<br/> "storage_class": "GLACIER"<br/> }<br/> ],<br/> "prefix": "",<br/> "tags": {<br/> "autoclean": "true",<br/> "rule": "log"<br/> },<br/> "transition": [<br/> {<br/> "days": 90,<br/> "storage_class": "STANDARD_IA"<br/> },<br/> {<br/> "days": 365,<br/> "storage_class": "GLACIER"<br/> }<br/> ]<br/> }<br/>]</pre>no
<a name="input_account_number"></a> account_numberAccount number of current environmentstringn/ayes
<a name="input_application_name"></a> application_nameName of applicationstringn/ayes
<a name="input_dns_record_client_routing_policy"></a> dns_record_client_routing_policy(optional) Indicates how traffic is distributed among network load balancer Availability Zones only. Possible values are any_availability_zone (client DNS queries are resolved among healthy LB IP addresses across all LB Availability Zones), partial_availability_zone_affinity (85 percent of client DNS queries will favor load balancer IP addresses in their own Availability Zone, while the remaining queries resolve to any healthy zone) and availability_zone_affinity (Client DNS queries will favor load balancer IP address in their own Availability Zone).string"any_availability_zone"no
<a name="input_drop_invalid_header_fields"></a> drop_invalid_header_fieldsWhether HTTP headers with header fields that are not valid are removed by the load balancer (true) or routed to targets (false).booltrueno
<a name="input_enable_cross_zone_load_balancing"></a> enable_cross_zone_load_balancingA boolean that determines whether cross zone load balancing is enabled. In application load balancers this feature is always enabled and cannot be disabled. In network and gateway load balancers this feature is disabled by default but can be enabled.boolfalseno
<a name="input_enable_deletion_protection"></a> enable_deletion_protectionIf true, deletion of the load balancer will be disabled via the AWS API. This will prevent Terraform from deleting the load balancer.booln/ayes
<a name="input_existing_bucket_name"></a> existing_bucket_nameThe name of the existing bucket name. If no bucket is provided one will be created for them.string""no
<a name="input_force_destroy_bucket"></a> force_destroy_bucketA boolean that indicates all objects (including any locked objects) should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable.boolfalseno
<a name="input_idle_timeout"></a> idle_timeoutThe time in seconds that the connection is allowed to be idle.stringnullno
<a name="input_internal_lb"></a> internal_lbA boolean that determines whether the load balancer is internal or internet-facing.boolfalseno
<a name="input_lb_target_groups"></a> lb_target_groupsMap of load balancer target groups, where key is the name<pre>map(object({<br/> port = optional(number)<br/> attachment_port = optional(number)<br/> deregistration_delay = optional(number)<br/> health_check = optional(object({<br/> enabled = optional(bool)<br/> interval = optional(number)<br/> healthy_threshold = optional(number)<br/> matcher = optional(string)<br/> path = optional(string)<br/> port = optional(number)<br/> timeout = optional(number)<br/> unhealthy_threshold = optional(number)<br/> }))<br/> stickiness = optional(object({<br/> enabled = optional(bool)<br/> type = string<br/> cookie_duration = optional(number)<br/> cookie_name = optional(string)<br/> }))<br/> }))</pre>{}no
<a name="input_load_balancer_type"></a> load_balancer_typeapplication or networkstring"application"no
<a name="input_loadbalancer_egress_rules"></a> loadbalancer_egress_rulesCreate new security group with these egress rules for the loadbalancer. Or use the security_groups var to attach existing group(s)<pre>map(object({<br/> description = string<br/> from_port = number<br/> to_port = number<br/> protocol = string<br/> security_groups = list(string)<br/> cidr_blocks = list(string)<br/> }))</pre>{}no
<a name="input_loadbalancer_ingress_rules"></a> loadbalancer_ingress_rulesCreate new security group with these ingress rules for the loadbalancer. Or use the security_groups var to attach existing group(s)<pre>map(object({<br/> description = string<br/> from_port = number<br/> to_port = number<br/> protocol = string<br/> security_groups = list(string)<br/> cidr_blocks = list(string)<br/> }))</pre>{}no
<a name="input_public_subnets"></a> public_subnetsBadly named variable, use subnets instead. Keeping for backward compatibilitylist(string)[]no
<a name="input_region"></a> regionAWS Region where resources are to be createdstringn/ayes
<a name="input_s3_versioning"></a> s3_versioningA boolean that determines whether s3 will have versioningbooltrueno
<a name="input_security_groups"></a> security_groupsList of existing security group ids to attach to the load balancer. You can use this instead of loadbalancer_ingress_rules,loadbalancer_egress_rules varslist(string)nullno
<a name="input_subnets"></a> subnetsList of subnet IDs. Typically use private subnet for internal LBs and public for public LBslist(string)[]no
<a name="input_tags"></a> tagsCommon tags to be used by all resourcesmap(string)n/ayes
<a name="input_vpc_all"></a> vpc_allThe full name of the VPC (including environment) used to create resourcesstringn/ayes

Outputs

NameDescription
<a name="output_athena_db"></a> athena_dbn/a
<a name="output_lb_target_groups"></a> lb_target_groupsn/a
<a name="output_load_balancer"></a> load_balancern/a
<a name="output_load_balancer_arn"></a> load_balancer_arnn/a
<a name="output_load_balancer_dns_name"></a> load_balancer_dns_namen/a
<a name="output_load_balancer_zone_id"></a> load_balancer_zone_idn/a
<a name="output_security_group"></a> security_groupn/a
<!-- END_TF_DOCS -->