Skip to content

Commit bb47aa9

Browse files
committed
feat: Add X-Ray policy, VPC connectory security group, and ability to toggle resources independently
1 parent 1eb7706 commit bb47aa9

File tree

4 files changed

+198
-21
lines changed

4 files changed

+198
-21
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/antonbabenko/pre-commit-terraform
3-
rev: v1.73.0
3+
rev: v1.74.2
44
hooks:
55
- id: terraform_fmt
66
- id: terraform_validate

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,16 @@ No modules.
5050
| [aws_apprunner_observability_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apprunner_observability_configuration) | resource |
5151
| [aws_apprunner_service.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apprunner_service) | resource |
5252
| [aws_apprunner_vpc_connector.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/apprunner_vpc_connector) | resource |
53+
| [aws_iam_policy.access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
5354
| [aws_iam_role.access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
5455
| [aws_iam_role.instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
5556
| [aws_iam_role_policy_attachment.access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
57+
| [aws_iam_role_policy_attachment.access_additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
5658
| [aws_iam_role_policy_attachment.instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
59+
| [aws_iam_role_policy_attachment.instance_xray](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
60+
| [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
61+
| [aws_security_group_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
62+
| [aws_iam_policy_document.access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
5763
| [aws_iam_policy_document.access_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
5864
| [aws_iam_policy_document.instance_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
5965
| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source |
@@ -77,9 +83,11 @@ No modules.
7783
| <a name="input_create_autoscaling_configuration"></a> [create\_autoscaling\_configuration](#input\_create\_autoscaling\_configuration) | Determines whether an Auto Scaling Configuration will be created | `bool` | `true` | no |
7884
| <a name="input_create_custom_domain_association"></a> [create\_custom\_domain\_association](#input\_create\_custom\_domain\_association) | Determines whether a Custom Domain Association will be created | `bool` | `false` | no |
7985
| <a name="input_create_instance_iam_role"></a> [create\_instance\_iam\_role](#input\_create\_instance\_iam\_role) | Determines whether an IAM role is created or to use an existing IAM role | `bool` | `true` | no |
86+
| <a name="input_create_observability_configuration"></a> [create\_observability\_configuration](#input\_create\_observability\_configuration) | Determines whether an Observability Configuration will be created | `bool` | `false` | no |
87+
| <a name="input_create_security_group"></a> [create\_security\_group](#input\_create\_security\_group) | Determines if a security group is created for the VPC connector | `bool` | `true` | no |
88+
| <a name="input_create_service"></a> [create\_service](#input\_create\_service) | Determines whether the service will be created | `bool` | `true` | no |
8089
| <a name="input_create_vpc_connector"></a> [create\_vpc\_connector](#input\_create\_vpc\_connector) | Determines whether a VPC Connector will be created | `bool` | `false` | no |
8190
| <a name="input_domain_name"></a> [domain\_name](#input\_domain\_name) | The custom domain endpoint to association. Specify a base domain e.g., `example.com` or a subdomain e.g., `subdomain.example.com` | `string` | `""` | no |
82-
| <a name="input_enable_observability_configuration"></a> [enable\_observability\_configuration](#input\_enable\_observability\_configuration) | Determines whether an Observability Configuration will be created | `bool` | `false` | no |
8391
| <a name="input_enable_www_subdomain"></a> [enable\_www\_subdomain](#input\_enable\_www\_subdomain) | Whether to associate the subdomain with the App Runner service in addition to the base domain. Defaults to `true` | `bool` | `null` | no |
8492
| <a name="input_encryption_configuration"></a> [encryption\_configuration](#input\_encryption\_configuration) | The encryption configuration for the service | `any` | `{}` | no |
8593
| <a name="input_health_check_configuration"></a> [health\_check\_configuration](#input\_health\_check\_configuration) | The health check configuration for the service | `any` | `{}` | no |
@@ -91,14 +99,20 @@ No modules.
9199
| <a name="input_instance_iam_role_policies"></a> [instance\_iam\_role\_policies](#input\_instance\_iam\_role\_policies) | IAM policies to attach to the IAM role | `map(string)` | `{}` | no |
92100
| <a name="input_instance_iam_role_use_name_prefix"></a> [instance\_iam\_role\_use\_name\_prefix](#input\_instance\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`iam_role_name`) is used as a prefix | `bool` | `true` | no |
93101
| <a name="input_network_configuration"></a> [network\_configuration](#input\_network\_configuration) | The network configuration for the service | `any` | `{}` | no |
102+
| <a name="input_observability_configuration"></a> [observability\_configuration](#input\_observability\_configuration) | The observability configuration for the service | `any` | `{}` | no |
94103
| <a name="input_observability_configuration_name"></a> [observability\_configuration\_name](#input\_observability\_configuration\_name) | The name of the Observability Configuration | `string` | `""` | no |
95-
| <a name="input_observability_trace_configuration"></a> [observability\_trace\_configuration](#input\_observability\_trace\_configuration) | The name of the Trace Configuration | `any` | `{}` | no |
104+
| <a name="input_observability_trace_configuration"></a> [observability\_trace\_configuration](#input\_observability\_trace\_configuration) | The Observability Configuration trace coniguration | `any` | `{}` | no |
105+
| <a name="input_private_ecr_arn"></a> [private\_ecr\_arn](#input\_private\_ecr\_arn) | The ARN of the private ECR repository that contains the service image to launch | `string` | `null` | no |
106+
| <a name="input_security_group_description"></a> [security\_group\_description](#input\_security\_group\_description) | Description for the security group created | `string` | `null` | no |
107+
| <a name="input_security_group_rules"></a> [security\_group\_rules](#input\_security\_group\_rules) | List of security group rules to add to the security group created | `any` | `{}` | no |
108+
| <a name="input_security_group_use_name_prefix"></a> [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Determines whether the security group name (`security_group_name`) is used as a prefix | `bool` | `true` | no |
96109
| <a name="input_service_name"></a> [service\_name](#input\_service\_name) | The name of the service | `string` | `""` | no |
97110
| <a name="input_source_configuration"></a> [source\_configuration](#input\_source\_configuration) | The source configuration for the service | `any` | `{}` | no |
98111
| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no |
99112
| <a name="input_vpc_connector_name"></a> [vpc\_connector\_name](#input\_vpc\_connector\_name) | The name of the VPC Connector | `string` | `""` | no |
100113
| <a name="input_vpc_connector_security_groups"></a> [vpc\_connector\_security\_groups](#input\_vpc\_connector\_security\_groups) | The security groups to use for the VPC Connector | `list(string)` | `[]` | no |
101114
| <a name="input_vpc_connector_subnets"></a> [vpc\_connector\_subnets](#input\_vpc\_connector\_subnets) | The subnets to use for the VPC Connector | `list(string)` | `[]` | no |
115+
| <a name="input_vpc_id"></a> [vpc\_id](#input\_vpc\_id) | ID of the VPC where the security will be provisioned | `string` | `null` | no |
102116

103117
## Outputs
104118

main.tf

Lines changed: 127 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ data "aws_partition" "current" {}
55
################################################################################
66

77
resource "aws_apprunner_service" "this" {
8-
count = var.create ? 1 : 0
8+
count = var.create && var.create_service ? 1 : 0
99

1010
auto_scaling_configuration_arn = try(aws_apprunner_auto_scaling_configuration_version.this[0].arn, null)
1111

@@ -56,11 +56,11 @@ resource "aws_apprunner_service" "this" {
5656
}
5757

5858
dynamic "observability_configuration" {
59-
for_each = var.enable_observability_configuration ? [1] : []
59+
for_each = length(var.observability_configuration) > 0 ? [var.observability_configuration] : []
6060

6161
content {
62-
observability_configuration_arn = aws_apprunner_observability_configuration.this[0].arn
63-
observability_enabled = var.enable_observability_configuration
62+
observability_configuration_arn = var.create_observability_configuration ? aws_apprunner_observability_configuration.this[0].arn : try(observability_configuration.value.observability_configuration_arn, null)
63+
observability_enabled = try(observability_configuration.value.observability_enabled, null)
6464
}
6565
}
6666

@@ -147,11 +147,13 @@ resource "aws_apprunner_service" "this" {
147147
################################################################################
148148

149149
locals {
150+
create_access_iam_role = var.create && var.create_service && var.create_access_iam_role
151+
150152
access_iam_role_name = try(coalesce(var.access_iam_role_name, "${var.service_name}-access"), "")
151153
}
152154

153155
data "aws_iam_policy_document" "access_assume_role" {
154-
count = var.create && var.create_access_iam_role ? 1 : 0
156+
count = local.create_access_iam_role ? 1 : 0
155157

156158
statement {
157159
sid = "AccessAssumeRole"
@@ -165,7 +167,7 @@ data "aws_iam_policy_document" "access_assume_role" {
165167
}
166168

167169
resource "aws_iam_role" "access" {
168-
count = var.create && var.create_access_iam_role ? 1 : 0
170+
count = local.create_access_iam_role ? 1 : 0
169171

170172
name = var.access_iam_role_use_name_prefix ? null : local.access_iam_role_name
171173
name_prefix = var.access_iam_role_use_name_prefix ? "${local.access_iam_role_name}-" : null
@@ -179,8 +181,57 @@ resource "aws_iam_role" "access" {
179181
tags = var.tags
180182
}
181183

184+
data "aws_iam_policy_document" "access" {
185+
count = local.create_access_iam_role && var.private_ecr_arn != null ? 1 : 0
186+
187+
dynamic "statement" {
188+
for_each = var.private_ecr_arn != null ? [1] : []
189+
190+
content {
191+
sid = "ReadPrivateEcr"
192+
actions = [
193+
"ecr:BatchGetImage",
194+
"ecr:DescribeImages",
195+
"ecr:GetDownloadUrlForLayer",
196+
]
197+
resources = [var.private_ecr_arn]
198+
}
199+
}
200+
201+
dynamic "statement" {
202+
for_each = var.private_ecr_arn != null ? [1] : []
203+
204+
content {
205+
sid = "AuthPrivateEcr"
206+
actions = [
207+
"ecr:DescribeImages",
208+
"ecr:GetAuthorizationToken",
209+
]
210+
resources = ["*"]
211+
}
212+
}
213+
}
214+
215+
resource "aws_iam_policy" "access" {
216+
count = local.create_access_iam_role && var.private_ecr_arn != null ? 1 : 0
217+
218+
name = var.access_iam_role_use_name_prefix ? null : local.access_iam_role_name
219+
name_prefix = var.access_iam_role_use_name_prefix ? "${local.access_iam_role_name}-" : null
220+
path = var.access_iam_role_path
221+
description = var.access_iam_role_description
222+
223+
policy = data.aws_iam_policy_document.access[0].json
224+
}
225+
182226
resource "aws_iam_role_policy_attachment" "access" {
183-
for_each = { for k, v in var.access_iam_role_policies : k => v if var.create && var.create_access_iam_role }
227+
count = local.create_access_iam_role && var.private_ecr_arn != null ? 1 : 0
228+
229+
policy_arn = aws_iam_policy.access[0].arn
230+
role = aws_iam_role.access[0].name
231+
}
232+
233+
resource "aws_iam_role_policy_attachment" "access_additional" {
234+
for_each = { for k, v in var.access_iam_role_policies : k => v if local.create_access_iam_role }
184235

185236
policy_arn = each.value
186237
role = aws_iam_role.access[0].name
@@ -191,6 +242,8 @@ resource "aws_iam_role_policy_attachment" "access" {
191242
################################################################################
192243

193244
locals {
245+
create_instance_iam_role = var.create && var.create_service && var.create_instance_iam_role
246+
194247
instance_iam_role_name = try(coalesce(var.instance_iam_role_name, "${var.service_name}-instance"), "")
195248
}
196249

@@ -209,7 +262,7 @@ data "aws_iam_policy_document" "instance_assume_role" {
209262
}
210263

211264
resource "aws_iam_role" "instance" {
212-
count = var.create && var.create_instance_iam_role ? 1 : 0
265+
count = local.create_instance_iam_role ? 1 : 0
213266

214267
name = var.instance_iam_role_use_name_prefix ? null : local.instance_iam_role_name
215268
name_prefix = var.instance_iam_role_use_name_prefix ? "${local.instance_iam_role_name}-" : null
@@ -223,8 +276,15 @@ resource "aws_iam_role" "instance" {
223276
tags = var.tags
224277
}
225278

279+
resource "aws_iam_role_policy_attachment" "instance_xray" {
280+
count = local.create_instance_iam_role && try(var.observability_trace_configuration.vendor, null) == "AWSXRAY" ? 1 : 0
281+
282+
policy_arn = "arn:${data.aws_partition.current.id}:iam::aws:policy/AWSXRayDaemonWriteAccess"
283+
role = aws_iam_role.instance[0].name
284+
}
285+
226286
resource "aws_iam_role_policy_attachment" "instance" {
227-
for_each = { for k, v in var.instance_iam_role_policies : k => v if var.create && var.create_instance_iam_role }
287+
for_each = { for k, v in var.instance_iam_role_policies : k => v if local.create_instance_iam_role }
228288

229289
policy_arn = each.value
230290
role = aws_iam_role.instance[0].name
@@ -234,16 +294,63 @@ resource "aws_iam_role_policy_attachment" "instance" {
234294
# VPC Connector
235295
################################################################################
236296

237-
resource "aws_apprunner_vpc_connector" "this" {
238-
count = var.create && var.create_vpc_connector ? 1 : 0
297+
locals {
298+
create_vpc_connector = var.create && var.create_vpc_connector
239299

240300
vpc_connector_name = try(coalesce(var.vpc_connector_name, var.service_name), "")
301+
}
302+
303+
resource "aws_apprunner_vpc_connector" "this" {
304+
count = local.create_vpc_connector ? 1 : 0
305+
306+
vpc_connector_name = local.vpc_connector_name
241307
subnets = var.vpc_connector_subnets
242-
security_groups = var.vpc_connector_security_groups
308+
security_groups = compact(concat(var.vpc_connector_security_groups, try(aws_security_group.this[0].id, [])))
243309

244310
tags = var.tags
245311
}
246312

313+
################################################################################
314+
# VPC Connector - Security Group
315+
################################################################################
316+
317+
resource "aws_security_group" "this" {
318+
count = var.create_security_group && local.create_vpc_connector ? 1 : 0
319+
320+
name = var.security_group_use_name_prefix ? null : local.vpc_connector_name
321+
name_prefix = var.security_group_use_name_prefix ? "${local.vpc_connector_name}-" : null
322+
description = var.security_group_description
323+
vpc_id = var.vpc_id
324+
325+
tags = merge(
326+
var.tags,
327+
{ "Name" = local.vpc_connector_name },
328+
)
329+
330+
lifecycle {
331+
create_before_destroy = true
332+
}
333+
}
334+
335+
resource "aws_security_group_rule" "this" {
336+
for_each = { for k, v in var.security_group_rules : k => v if var.create_security_group && local.create_vpc_connector }
337+
338+
# Required
339+
security_group_id = aws_security_group.this[0].id
340+
protocol = each.value.protocol
341+
from_port = each.value.from_port
342+
to_port = each.value.to_port
343+
type = "egress"
344+
345+
# Optional
346+
description = try(each.value.description, null)
347+
cidr_blocks = try(each.value.cidr_blocks, null)
348+
ipv6_cidr_blocks = try(each.value.ipv6_cidr_blocks, null)
349+
prefix_list_ids = try(each.value.prefix_list_ids, [])
350+
self = try(each.value.self, null)
351+
source_security_group_id = try(each.value.source_security_group_id, null)
352+
}
353+
247354
################################################################################
248355
# Autoscaling
249356
################################################################################
@@ -264,8 +371,12 @@ resource "aws_apprunner_auto_scaling_configuration_version" "this" {
264371
# Custom Domain Association
265372
################################################################################
266373

374+
locals {
375+
create_custom_domain_association = var.create && var.create_custom_domain_association
376+
}
377+
267378
resource "aws_apprunner_custom_domain_association" "this" {
268-
count = var.create && var.create_custom_domain_association ? 1 : 0
379+
count = local.create_custom_domain_association ? 1 : 0
269380

270381
domain_name = var.domain_name
271382
enable_www_subdomain = var.enable_www_subdomain
@@ -278,7 +389,7 @@ resource "aws_apprunner_custom_domain_association" "this" {
278389
# name = dvo.name
279390
# record = dvo.value
280391
# type = dvo.type
281-
# } if var.create && var.create_custom_domain_association
392+
# } if local.create_custom_domain_association
282393
# }
283394

284395
# allow_overwrite = true
@@ -290,7 +401,7 @@ resource "aws_apprunner_custom_domain_association" "this" {
290401
# }
291402

292403
# resource "aws_route53_record" "cname" {
293-
# count = var.create && var.create_custom_domain_association ? 1 : 0
404+
# count = local.create_custom_domain_association ? 1 : 0
294405

295406
# allow_overwrite = true
296407
# name = var.domain_name
@@ -305,7 +416,7 @@ resource "aws_apprunner_custom_domain_association" "this" {
305416
################################################################################
306417

307418
resource "aws_apprunner_observability_configuration" "this" {
308-
count = var.create && var.enable_observability_configuration ? 1 : 0
419+
count = var.create && var.create_observability_configuration ? 1 : 0
309420

310421
observability_configuration_name = try(coalesce(var.observability_configuration_name, var.service_name), "")
311422

0 commit comments

Comments
 (0)