diff --git a/.github/workflows/commit-to-pr.yaml b/.github/workflows/commit-to-pr.yaml index 2bd9dc0..dc34f33 100644 --- a/.github/workflows/commit-to-pr.yaml +++ b/.github/workflows/commit-to-pr.yaml @@ -20,7 +20,7 @@ jobs: validation: strategy: matrix: - folder: ["add", "folders", "here"] + folder: ["./", "examples/security_group"] name: Terraform validate for ${{ matrix.folder }} runs-on: ubuntu-20.04 steps: @@ -41,7 +41,7 @@ jobs: linting: strategy: matrix: - folder: ["add", "folders", "here"] + folder: ["./", "examples/security_group"] name: Terraform lint for ${{ matrix.folder }} runs-on: ubuntu-20.04 steps: @@ -59,7 +59,7 @@ jobs: plan: strategy: matrix: - folder: ["add", "folders", "here"] + folder: ["examples/security_group"] name: Terraform plan for ${{ matrix.folder }} runs-on: ubuntu-20.04 needs: [validation, linting] diff --git a/.terraform-version b/.terraform-version new file mode 100644 index 0000000..8e03717 --- /dev/null +++ b/.terraform-version @@ -0,0 +1 @@ +1.5.1 \ No newline at end of file diff --git a/README.md b/README.md index 323b31c..5cc34f4 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,72 @@ The below documentation is intended to assist users in utilising the module, the the module itself, and the [examples](#examples) section which has examples of how to utilise the module. +## Requirements +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | ~> 1.5.0 | +| [aws](#requirement\_aws) | >= 5.61.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 5.67.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_security_group.groups](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.rule](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [application\_name](#input\_application\_name) | Name of the application utilising resource. | `string` | n/a | yes | +| [environment](#input\_environment) | Which environment this is being instantiated in. | `string` | n/a | yes | +| [raw\_security\_groups](#input\_raw\_security\_groups) | Data structure
---------------
A list of dictionaries, where each dictionary has the following attributes:

REQUIRED
---------
- suffix : Security group suffix to use for naming and unique identifiers
- description : Description to give to the security group

OPTIONAL
---------
- rules: A list of dictionaries, where each dictionary has the following values:
-- name : Friendly name used through Terraform for instantiation and cross-referencing
-- type : Ingress/egress
-- from\_port : Start port
-- to\_port : End port
-- protocol : Protocol. If not icmp, icmpv6, tcp, udp, or all use the protocol number.
-- description : Friendly description of the rule, required for auditing purposes.

In addition, the following optional args are available:
-- cidr\_blocks : List of CIDR blocks. Cannot be specified with source\_security\_group\_id or self.
-- ipv6\_cidr\_blocks : List of IPv6 CIDR blocks. Cannot be specified with source\_security\_group\_id or self.
-- prefix\_list\_ids : List of Prefix List IDs.
-- self : Whether the security group itself will be added as a source to this ingress rule. Cannot be specified with cidr\_blocks, ipv6\_cidr\_blocks, or source\_security\_group\_id.
-- source\_security\_group\_id : Security group id to allow access to/from, depending on the type. Cannot be specified with cidr\_blocks, ipv6\_cidr\_blocks, or self. |
list(
object({
suffix : string,
description : string,
rules : optional(list(
object({
name = string,
type = string,
from_port = string,
to_port = string,
protocol = string,
description = string,
cidr_blocks = optional(list(string), null),
ipv6_cidr_blocks = optional(list(string), null),
prefix_list_ids = optional(list(string), null),
self = optional(bool, null),
source_security_group_id = optional(string, null)
})
), [])

})
)
| n/a | yes | +| [vpc\_config](#input\_vpc\_config) | AWS VPC ID within which to create the security group. | `string` | n/a | yes | + +## Outputs + +No outputs. ## Data structure - +``` +Data structure +--------------- +A list of dictionaries, where each dictionary has the following attributes: + +REQUIRED +--------- +- suffix : Security group suffix to use for naming and unique identifiers +- description : Description to give to the security group + +OPTIONAL +--------- +- rules: A list of dictionaries, where each dictionary has the following values: +-- name : Friendly name used through Terraform for instantiation and cross-referencing +-- type : Ingress/egress +-- from_port : Start port +-- to_port : End port +-- protocol : Protocol. If not icmp, icmpv6, tcp, udp, or all use the protocol number. +-- description : Friendly description of the rule, required for auditing purposes. + +In addition, the following optional args are available: +-- cidr_blocks : List of CIDR blocks. Cannot be specified with source_security_group_id or self. +-- ipv6_cidr_blocks : List of IPv6 CIDR blocks. Cannot be specified with source_security_group_id or self. +-- prefix_list_ids : List of Prefix List IDs. +-- self : Whether the security group itself will be added as a source to this ingress rule. Cannot be specified with cidr_blocks, ipv6_cidr_blocks, or source_security_group_id. +-- source_security_group_id : Security group id to allow access to/from, depending on the type. Cannot be specified with cidr_blocks, ipv6_cidr_blocks, or self. +``` ## Examples See `examples` folder for an example setup. diff --git a/aws_security_group.tf b/aws_security_group.tf new file mode 100644 index 0000000..08fd3f3 --- /dev/null +++ b/aws_security_group.tf @@ -0,0 +1,11 @@ +resource "aws_security_group" "groups" { + for_each = { for group in var.raw_security_groups : group.suffix => group } + + name = upper(format("aws-%s-%s-%s-SG", + var.environment, + var.application_name, + each.value["suffix"]) + ) + description = format("%s - Managed by Terraform", each.value["description"]) + vpc_id = var.vpc_config +} \ No newline at end of file diff --git a/aws_security_group_rule.tf b/aws_security_group_rule.tf new file mode 100644 index 0000000..6daba57 --- /dev/null +++ b/aws_security_group_rule.tf @@ -0,0 +1,30 @@ +locals { + actual_security_group_rules = flatten([ + for group in var.raw_security_groups : [ + for rule in group.rules : merge(rule, { + identifier : format("%s/%s", group.suffix, rule.name), + security_group_id = aws_security_group.groups[group.suffix].id + }) + ] + ]) +} + +resource "aws_security_group_rule" "rule" { + for_each = { for rule in local.actual_security_group_rules : rule.identifier => rule } + type = each.value["type"] + from_port = each.value["from_port"] + to_port = each.value["to_port"] + protocol = each.value["protocol"] + security_group_id = each.value["security_group_id"] + description = each.value["description"] + + cidr_blocks = try(each.value["cidr_blocks"], null) + ipv6_cidr_blocks = try(each.value["ipv6_cidr_blocks"], null) + prefix_list_ids = try(each.value["prefix_list_ids"], null) + self = try(each.value["self"], null) + source_security_group_id = try(each.value["source_security_group_id"], null) + + depends_on = [ + aws_security_group.groups + ] +} \ No newline at end of file diff --git a/examples/security_group/.terraform-version b/examples/security_group/.terraform-version new file mode 100644 index 0000000..8e03717 --- /dev/null +++ b/examples/security_group/.terraform-version @@ -0,0 +1 @@ +1.5.1 \ No newline at end of file diff --git a/examples/security_group/data.tf b/examples/security_group/data.tf new file mode 100644 index 0000000..287398b --- /dev/null +++ b/examples/security_group/data.tf @@ -0,0 +1,2 @@ +# Get default VPC +data "aws_vpc" "vpc" {} \ No newline at end of file diff --git a/examples/security_group/locals.tf b/examples/security_group/locals.tf new file mode 100644 index 0000000..49187f0 --- /dev/null +++ b/examples/security_group/locals.tf @@ -0,0 +1,37 @@ +locals { + raw_security_groups = [ + { + "suffix" : "lambda", + "description" : "Lambda Function security group", + "rules" : [ + { + "name" : "All traffic from VPC", + "type" : "ingress", + "from_port" : "0", + "to_port" : "0", + "protocol" : "-1", + "description" : "VPC Traffic (Ingress)", + "cidr_blocks" : ["1.2.3.0/24"] + }, + { + "name" : "All traffic to VPC", + "type" : "egress", + "from_port" : "0", + "to_port" : "0", + "protocol" : "-1", + "description" : "VPC Traffic (Egress)", + "cidr_blocks" : ["1.2.3.0/24"] + }, + { + "name" : "Public endpoint traffic", + "type" : "egress", + "from_port" : "443", + "to_port" : "443", + "protocol" : "tcp", + "description" : "Public Endpoint traffic (Egress)", + "cidr_blocks" : ["0.0.0.0/0"] + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/security_group/main.tf b/examples/security_group/main.tf new file mode 100644 index 0000000..72f8b92 --- /dev/null +++ b/examples/security_group/main.tf @@ -0,0 +1,21 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.61.0" + } + } + required_version = "~> 1.5.0" +} + +provider "aws" { + region = "eu-west-2" +} + +module "security_group" { + source = "github.com/sudoblark/sudoblark.terraform.module.aws.security_group?ref=1.0.0" + environment = var.environment + application_name = var.application_name + raw_security_groups = local.raw_security_groups + vpc_config = data.aws_vpc.vpc.id +} \ No newline at end of file diff --git a/examples/security_group/variables.tf b/examples/security_group/variables.tf new file mode 100644 index 0000000..9ccf4b8 --- /dev/null +++ b/examples/security_group/variables.tf @@ -0,0 +1,15 @@ +variable "environment" { + description = "Which environment this is being instantiated in." + type = string + validation { + condition = contains(["dev", "test", "prod"], var.environment) + error_message = "Must be either dev, test or prod" + } + default = "prod" +} + +variable "application_name" { + description = "Name of the application utilising the resource resource." + type = string + default = "demo-app" +} \ No newline at end of file diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..f022823 --- /dev/null +++ b/main.tf @@ -0,0 +1,9 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.61.0" + } + } + required_version = "~> 1.5.0" +} \ No newline at end of file diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..3b427e5 --- /dev/null +++ b/variables.tf @@ -0,0 +1,73 @@ +# Input variable definitions +variable "environment" { + description = "Which environment this is being instantiated in." + type = string + validation { + condition = contains(["dev", "test", "prod"], var.environment) + error_message = "Must be either dev, test or prod" + } +} + +variable "application_name" { + description = "Name of the application utilising resource." + type = string +} + +variable "vpc_config" { + description = "AWS VPC ID within which to create the security group." + type = string +} + +variable "raw_security_groups" { + description = <