diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
index 69eecd92..26ae5fba 100644
--- a/.github/dependabot.yaml
+++ b/.github/dependabot.yaml
@@ -50,6 +50,7 @@ updates:
- "infrastructure/modules/ecr"
- "infrastructure/modules/ecs-cluster"
- "infrastructure/modules/ecs-service"
+ - "infrastructure/modules/efs"
- "infrastructure/modules/elasticache"
- "infrastructure/modules/github-config"
- "infrastructure/modules/guardduty"
diff --git a/README.md b/README.md
index 1f4d50e6..29670309 100644
--- a/README.md
+++ b/README.md
@@ -317,6 +317,7 @@ Rules:
| `ecr` | — | ECR repository with security controls |
| `ecs-cluster` | terraform-aws-modules/ecs/aws//modules/cluster | ECS Fargate cluster |
| `ecs-service` | terraform-aws-modules/ecs/aws//modules/service | ECS service and task definition |
+| `efs` | terraform-aws-modules/efs/aws | EFS file system with access points and mount targets |
| `elasticache` | — | ElastiCache cluster (Redis/Memcached) |
| `github-config` | — | GitHub OIDC provider and runner configuration |
| `guardduty` | — | GuardDuty threat detection |
diff --git a/infrastructure/modules/cloudwatch-logs/.terraform.lock.hcl b/infrastructure/modules/cloudwatch-logs/.terraform.lock.hcl
index 82c67711..3211986b 100644
--- a/infrastructure/modules/cloudwatch-logs/.terraform.lock.hcl
+++ b/infrastructure/modules/cloudwatch-logs/.terraform.lock.hcl
@@ -8,6 +8,7 @@ provider "registry.terraform.io/hashicorp/aws" {
"h1:8m5zm0JaUac+YikXZoZDTV7R0iYL+q3IvTL4Ac8GfDA=",
"h1:Dg9X3Gde96OCT3TovLKAKumnR64VqIIQ37m/9NRJ00Y=",
"h1:F1r7mTJ289EBvU7Z7XAvWMHUt3VJ3zSFH8SUjLPFiqM=",
+ "h1:LmIMOhIeFTfSiUDY4gthZTikT5CqEIGMhgQbv/mdurE=",
"h1:iXIFHX6zIvG0hoEi1fImeuHG9V4JOop6O6L6f+MOL2c=",
"h1:lpXqosKH8yAahK3SA1P5Pdy1ziXJcY+blUidY0q9yGk=",
"zh:1ab1d78f2336fed42b4e13fa0077a0be9d86a7899897cda5b9f1a60051ec2e93",
diff --git a/infrastructure/modules/efs/.terraform.lock.hcl b/infrastructure/modules/efs/.terraform.lock.hcl
new file mode 100644
index 00000000..19b85462
--- /dev/null
+++ b/infrastructure/modules/efs/.terraform.lock.hcl
@@ -0,0 +1,30 @@
+# This file is maintained automatically by "terraform init".
+# Manual edits may be lost in future updates.
+
+provider "registry.terraform.io/hashicorp/aws" {
+ version = "6.55.0"
+ constraints = ">= 6.14.0, >= 6.28.0"
+ hashes = [
+ "h1:3OSTaIx7d6T+N6gwNtw0gR+IwExbHqb0y4v529Vey/4=",
+ "h1:99+MYIg/y3gmsZkhAcffwOpMat+liRJ8b+eyCIax6hk=",
+ "h1:QIQoZv637BE40vg7/0gf/5gQbY/ZTngSuJWDl7fJr2s=",
+ "h1:tBAaeIprhWQmlsq/CoHLXWOLZZYRdCVA1wbNv2co55g=",
+ "h1:yM6149NHHl8NWU/OCIV2/x6tLrNgDTRKgR8cVrbk9BU=",
+ "zh:1161fb2d032ad982587b2662a5229e5d06598c5b7fc5c86b2ad64d49225047cd",
+ "zh:1f412b09bbece216da0ba08106f3bbb42d8c8971c02d032ab518629915086966",
+ "zh:2c8b789450bb67181b5f0546714bf6336ba21183c307e001fe848c22dac1f8a6",
+ "zh:31eec91f896743bab641c06930fe0c277143f17dd25b2510991c08e013c8da67",
+ "zh:4419d3e906f1ca9c99703b2c4c5082f58aaeb8b8b82e2657a187a6bdf42d8881",
+ "zh:58e9a7e0581e8cd5f35eb2ce308b2d572073c112facdd0a60aee032146b146b5",
+ "zh:72fdb02a0cb6351626df460c047d1471f26dad781160cc95abd84f8849daf950",
+ "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
+ "zh:aa527913348c33969d80527d424917876657741493f294e160f1191dbc7c1d45",
+ "zh:b66e5abd756064f55ff06e1ef83eb8d0b7ea6d96625cfcd24408df5472d5f899",
+ "zh:ba9ab4ba151ac69a854407e27d3a12a2eb260fc4205bbb5c25618e6c8ce69568",
+ "zh:e1bf63e8a6b9790836f74848b458cdf679a7841e66a25b4b9f6034efc9310b34",
+ "zh:ec4adcec426f7181faa5de976cedbd35e15d7d1ac2914bbd9cab9f4f8b018b7f",
+ "zh:f17b485ae74bb4272d2bd680e7d47b0cfd073d192a4f10c8bdfd9d9f25c990a1",
+ "zh:f6650c2d0d3e614c3ccd623bb027a52bce1f5285c4f1824a26f265eb7529a45a",
+ "zh:f7383732f8704099db2166a3516e0a803bf46f42c30cd20a0471b55658ae6e51",
+ ]
+}
diff --git a/infrastructure/modules/efs/README.md b/infrastructure/modules/efs/README.md
new file mode 100644
index 00000000..f0eaa86e
--- /dev/null
+++ b/infrastructure/modules/efs/README.md
@@ -0,0 +1,596 @@
+# EFS
+
+NHS Screening wrapper around the community
+[`terraform-aws-modules/efs/aws`](https://registry.terraform.io/modules/terraform-aws-modules/efs/aws/latest)
+module that enforces the platform's baseline controls and consumes
+the shared `context.tf` for naming and tagging.
+
+## What this module enforces
+
+|Control|How it is enforced|
+|---|---|
+|Encryption at rest|KMS encryption is mandatory; `var.kms_key_arn` is required|
+|Encryption in transit|TLS is always required; non-secure transport is denied by default|
+|Security groups|Mount targets require explicit security groups from the caller; no defaults|
+|Destructive operations|Delete operations are denied by default; must be explicitly allowed via policy|
+|Tagging|All EFS resources tagged via `module.this.tags`|
+|Creation gate|Resource creation gated by `module.this.enabled`|
+
+## Usage
+
+### Minimal EFS with KMS encryption
+
+Simplest setup: multi-AZ, general-purpose file system with default security policies.
+
+```hcl
+module "application_efs" {
+ source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/efs?ref=v1.0.0"
+
+ service = "bcss"
+ project = "application"
+ environment = "production"
+ name = "app-data"
+
+ kms_key_arn = module.efs_kms.key_arn
+
+ mount_targets = {
+ "az1" = {
+ subnet_id = aws_subnet.private[0].id
+ security_groups = [aws_security_group.efs.id]
+ }
+ "az2" = {
+ subnet_id = aws_subnet.private[1].id
+ security_groups = [aws_security_group.efs.id]
+ }
+ }
+}
+```
+
+### Single-AZ EFS for cost savings
+
+Use `availability_zone_name` for cheaper single-AZ deployments (no high availability).
+
+```hcl
+module "dev_efs" {
+ source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/efs?ref=v1.0.0"
+
+ service = "bcss"
+ project = "development"
+ environment = "dev"
+ name = "dev-storage"
+
+ kms_key_arn = module.efs_kms.key_arn
+ availability_zone_name = "eu-west-2a" # Single AZ deployment
+
+ mount_targets = {
+ "primary" = {
+ subnet_id = aws_subnet.private[0].id
+ security_groups = [aws_security_group.efs_dev.id]
+ }
+ }
+}
+```
+
+### High-performance EFS with provisioned throughput
+
+For workloads requiring consistent throughput (databases, analytics).
+
+```hcl
+module "database_efs" {
+ source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/efs?ref=v1.0.0"
+
+ service = "bcss"
+ project = "database"
+ environment = "production"
+ name = "db-storage"
+
+ kms_key_arn = module.efs_kms.key_arn
+ performance_mode = "maxIO"
+ throughput_mode = "provisioned"
+ provisioned_throughput_in_mibps = 100
+
+ mount_targets = {
+ "az1" = {
+ subnet_id = aws_subnet.private[0].id
+ security_groups = [aws_security_group.efs_db.id]
+ }
+ "az2" = {
+ subnet_id = aws_subnet.private[1].id
+ security_groups = [aws_security_group.efs_db.id]
+ }
+ }
+}
+```
+
+### EFS with lifecycle policies and automated backup
+
+Transition older data to infrequent access storage class automatically.
+
+```hcl
+module "archive_efs" {
+ source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/efs?ref=v1.0.0"
+
+ service = "bcss"
+ project = "archive"
+ environment = "production"
+ name = "archive-storage"
+
+ kms_key_arn = module.efs_kms.key_arn
+
+ # Transition data to infrequent access after 30 days of inactivity
+ lifecycle_policy = {
+ transition_to_ia = "AFTER_30_DAYS"
+ transition_to_primary_storage_class = "AFTER_1_DAY" # Restore to standard if accessed
+ }
+
+ protection = {
+ enable_backup = true # Enable automated backup via AWS Backup
+ }
+
+ mount_targets = {
+ "az1" = {
+ subnet_id = aws_subnet.private[0].id
+ security_groups = [aws_security_group.efs.id]
+ }
+ }
+}
+```
+
+### EFS with Access Points for application isolation
+
+Use Access Points to enforce POSIX user identity and root directory isolation for multi-tenant workloads.
+
+```hcl
+module "multitenant_efs" {
+ source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/efs?ref=v1.0.0"
+
+ service = "bcss"
+ project = "multitenant"
+ environment = "production"
+ name = "shared-storage"
+
+ kms_key_arn = module.efs_kms.key_arn
+
+ # Each application gets its own access point with enforced user identity
+ access_points = {
+ "web-app" = {
+ enforced_user_id = "1001"
+ root_directory_path = "/web-app"
+ permissions_mode = "700"
+ }
+ "api-service" = {
+ enforced_user_id = "1002"
+ root_directory_path = "/api-service"
+ permissions_mode = "700"
+ }
+ "batch-job" = {
+ enforced_user_id = "1003"
+ root_directory_path = "/batch"
+ permissions_mode = "755"
+ }
+ }
+
+ mount_targets = {
+ "az1" = {
+ subnet_id = aws_subnet.private[0].id
+ security_groups = [aws_security_group.efs.id]
+ }
+ "az2" = {
+ subnet_id = aws_subnet.private[1].id
+ security_groups = [aws_security_group.efs.id]
+ }
+ }
+}
+```
+
+### EFS with cross-region replication for disaster recovery
+
+Replicate to secondary region for business continuity.
+
+```hcl
+module "replicated_efs" {
+ source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/efs?ref=v1.0.0"
+
+ service = "bcss"
+ project = "critical-data"
+ environment = "production"
+ name = "replicated-storage"
+
+ kms_key_arn = module.efs_kms.key_arn
+
+ # Enable replication to eu-west-1
+ replication_configuration = {
+ destination = "eu-west-1"
+ }
+
+ protection = {
+ replication_overwrite = "DISABLED" # Prevent accidental overwrites
+ }
+
+ mount_targets = {
+ "az1" = {
+ subnet_id = aws_subnet.private[0].id
+ security_groups = [aws_security_group.efs.id]
+ }
+ "az2" = {
+ subnet_id = aws_subnet.private[1].id
+ security_groups = [aws_security_group.efs.id]
+ }
+ }
+}
+```
+
+### Secure EFS with TLS 1.2 enforcement and IP restrictions
+
+Enforce strong TLS version and restrict access to specific network ranges.
+
+```hcl
+module "secure_efs" {
+ source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/efs?ref=v1.0.0"
+
+ service = "bcss"
+ project = "secure-data"
+ environment = "production"
+ name = "secure-storage"
+
+ kms_key_arn = module.efs_kms.key_arn
+
+ # Enforce TLS 1.2 minimum and restrict to VPC CIDR
+ require_tls_version = "1.2"
+ allowed_source_ips = ["10.0.0.0/8"] # Your VPC CIDR
+
+ # Prevent accidental deletion (must explicitly allow in custom policy)
+ deny_destructive_operations = true
+
+ mount_targets = {
+ "az1" = {
+ subnet_id = aws_subnet.private[0].id
+ security_groups = [aws_security_group.efs_secure.id]
+ }
+ "az2" = {
+ subnet_id = aws_subnet.private[1].id
+ security_groups = [aws_security_group.efs_secure.id]
+ }
+ }
+}
+```
+
+### EFS with custom IAM policy for admin-only deletions
+
+Demonstrate how to allow destructive operations only for specific IAM roles.
+
+```hcl
+module "efs_with_admin_policy" {
+ source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/efs?ref=v1.0.0"
+
+ service = "bcss"
+ project = "production-data"
+ environment = "production"
+ name = "protected-storage"
+
+ kms_key_arn = module.efs_kms.key_arn
+
+ # Enable all default security policies
+ deny_unsecure_transport = true
+ require_tls_version = "1.2"
+ deny_destructive_operations = true
+
+ # Custom policy: allow deletion only by infrastructure-admin role
+ file_system_policy = jsonencode({
+ Statement = [
+ {
+ Sid = "AllowInfraAdminDelete"
+ Effect = "Allow"
+ Principal = {
+ AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/infrastructure-admin"
+ }
+ Action = [
+ "elasticfilesystem:DeleteFileSystem",
+ "elasticfilesystem:DeleteAccessPoint"
+ ]
+ Resource = "*"
+ }
+ ]
+ })
+
+ mount_targets = {
+ "az1" = {
+ subnet_id = aws_subnet.private[0].id
+ security_groups = [aws_security_group.efs.id]
+ }
+ "az2" = {
+ subnet_id = aws_subnet.private[1].id
+ security_groups = [aws_security_group.efs.id]
+ }
+ }
+}
+```
+
+### Comprehensive production setup with all features
+
+Multi-AZ, high-performance, encrypted, replicated, with access points and security policies.
+
+```hcl
+module "production_efs" {
+ source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/efs?ref=v1.0.0"
+
+ service = "bcss"
+ project = "platform"
+ environment = "production"
+ name = "platform-data"
+
+ kms_key_arn = module.efs_kms.key_arn
+
+ # Performance: provisioned throughput for predictable performance
+ performance_mode = "generalPurpose"
+ throughput_mode = "provisioned"
+ provisioned_throughput_in_mibps = 50
+
+ # Lifecycle: optimize storage costs over time
+ lifecycle_policy = {
+ transition_to_ia = "AFTER_60_DAYS"
+ }
+
+ # Protection: backup and prevent accidental overwrites
+ protection = {
+ enable_backup = true
+ replication_overwrite = "DISABLED"
+ }
+
+ # Replication: disaster recovery to secondary region
+ replication_configuration = {
+ destination = "eu-west-1"
+ }
+
+ # Security: enforce TLS 1.2 and restrict to VPC
+ require_tls_version = "1.2"
+ allowed_source_ips = ["10.0.0.0/8"]
+
+ # Access control: application isolation via access points
+ access_points = {
+ "api" = {
+ enforced_user_id = "1000"
+ root_directory_path = "/api"
+ permissions_mode = "750"
+ }
+ "database" = {
+ enforced_user_id = "1001"
+ root_directory_path = "/db"
+ permissions_mode = "700"
+ }
+ }
+
+ # Multi-AZ mount targets for high availability
+ mount_targets = {
+ "az1" = {
+ subnet_id = aws_subnet.private[0].id
+ security_groups = [aws_security_group.efs.id]
+ }
+ "az2" = {
+ subnet_id = aws_subnet.private[1].id
+ security_groups = [aws_security_group.efs.id]
+ }
+ "az3" = {
+ subnet_id = aws_subnet.private[2].id
+ security_groups = [aws_security_group.efs.id]
+ }
+ }
+}
+```
+
+## Security Group Requirements
+
+EFS communication uses NFS (NFSv4.1), not SSH or HTTP. Your security groups must allow:
+
+```hcl
+# Allow NFS protocol
+resource "aws_security_group_rule" "efs_nfs" {
+ type = "ingress"
+ from_port = 2049
+ to_port = 2049
+ protocol = "tcp"
+ cidr_blocks = ["10.0.0.0/8"] # Your application's CIDR
+ security_group_id = aws_security_group.efs.id
+}
+
+# Allow NFS port mapper (optional, only if needed)
+resource "aws_security_group_rule" "efs_portmapper" {
+ type = "ingress"
+ from_port = 111
+ to_port = 111
+ protocol = "tcp"
+ cidr_blocks = ["10.0.0.0/8"]
+ security_group_id = aws_security_group.efs.id
+}
+```
+
+## Conventions
+
+- **Encryption:** KMS encryption is mandatory. Callers must provide a KMS key ARN via `var.kms_key_arn`. AWS managed keys are not used.
+- **Security groups:** Callers must provide security groups for each mount target. See [Security Group Requirements](#security-group-requirements) above.
+- **Mount targets:** Each mount target requires a subnet and security groups. For high availability, provision one mount target per AZ.
+- **Custom naming:** By default, the EFS name is derived from `module.this.id`. Override with `var.custom_name` if needed.
+- **Access Points:** When using access points, applications must use the access point ARN (not the file system ARN) in their NFS mount.
+
+## File System Policy (Resource-Based Access Control)
+
+This module automatically adds security-focused policy statements to the EFS file system policy. These are enabled by default and can be controlled or disabled as needed:
+
+### Default Security Statements
+
+| Statement | Default | Purpose |
+| --- | --- | --- |
+| `DenyUnsecureTransport` | Enabled | Denies all EFS operations over non-TLS connections (`aws:SecureTransport = false`) |
+| `DenyOldTLSVersion` | Disabled | Denies operations using TLS versions older than specified via `var.require_tls_version` |
+| `DenyUnauthorizedSourceIPs` | Disabled | Restricts EFS access to specific CIDR blocks via `var.allowed_source_ips` |
+| `DenyDestructiveOperations` | Enabled | Denies `DeleteFileSystem`, `DeleteAccessPoint`, etc. by default (callers must explicitly allow via custom policy) |
+
+### Controlling Policy Statements
+
+```hcl
+# Require TLS 1.2 or higher
+require_tls_version = "1.2"
+
+# Restrict to specific VPC CIDR blocks
+allowed_source_ips = ["10.0.0.0/8", "172.16.0.0/12"]
+
+# Disable automatic deny of destructive operations (not recommended)
+deny_destructive_operations = false
+
+# Disable all automatic policy statements
+deny_unsecure_transport = false
+require_tls_version = null
+allowed_source_ips = []
+```
+
+### Custom Policy Statements
+
+Caller-provided policy statements (via `var.file_system_policy`) are merged with the default security statements. To allow destructive operations:
+
+```hcl
+file_system_policy = jsonencode({
+ Statement = [
+ {
+ Sid = "AllowDeleteForAdmins"
+ Effect = "Allow"
+ Principal = {
+ AWS = "arn:aws:iam::ACCOUNT:role/AdminRole"
+ }
+ Action = ["elasticfilesystem:DeleteFileSystem"]
+ Resource = "*"
+ }
+ ]
+})
+```
+
+## Validation
+
+The following cross-variable constraints are enforced in `validations.tf`:
+
+- **Provisioned throughput mode requires throughput setting:** If `throughput_mode = "provisioned"`, `provisioned_throughput_in_mibps` must be set.
+- **Replication requires mount targets:** If `replication_configuration` is set, at least one mount target must be configured.
+- **Access points require mount targets:** If `access_points` is set, at least one mount target must be configured.
+
+## Outputs
+
+- `efs_id` — The EFS file system ID (e.g., `fs-12345678`)
+- `efs_arn` — The ARN of the file system
+- `efs_dns_name` — The DNS name for NFS mounting (e.g., `fs-12345678.efs.eu-west-2.amazonaws.com`)
+- `efs_size_in_bytes` — Latest metered size of the file system
+- `kms_key_arn` — The KMS key ARN used for encryption
+- `mount_targets` — Map of mount target IDs and subnet associations
+- `access_points` — Map of access point IDs and ARNs (if configured)
+- `replication_configuration` — The destination file system ID (if replication enabled)
+- `file_system_policy_id` — The policy ID (if custom policy attached)
+
+## What this module does NOT do
+
+- Create KMS keys. Use the `kms` module and pass the key ARN via `var.kms_key_arn`.
+- Create security groups. Callers must provide and manage security groups for mount targets.
+- Create subnets. Callers must provide target subnets for mount targets.
+- Create DNS records. Use Route 53 or your DNS provider to create records pointing to `efs_dns_name` if needed.
+- Enable data sync or backup scheduling. Use AWS DataSync or AWS Backup to configure automated workflows.
+- Configure VPC endpoints for EFS. The module uses standard EFS DNS; VPC endpoints are optional and managed separately.
+
+
+
+
+## Requirements
+
+| Name | Version |
+| ---- | ------- |
+| [terraform](#requirement\_terraform) | >= 1.13 |
+| [aws](#requirement\_aws) | >= 6.28 |
+
+## Providers
+
+| Name | Version |
+| ---- | ------- |
+| [aws](#provider\_aws) | 6.55.0 |
+| [terraform](#provider\_terraform) | n/a |
+
+## Modules
+
+| Name | Source | Version |
+| ---- | ------ | ------- |
+| [efs](#module\_efs) | terraform-aws-modules/efs/aws | 2.2.0 |
+| [this](#module\_this) | ../tags | n/a |
+
+## Resources
+
+| Name | Type |
+| ---- | ---- |
+| [aws_efs_access_point.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_access_point) | resource |
+| [aws_efs_file_system_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_file_system_policy) | resource |
+| [terraform_data.validations](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+| ---- | ----------- | ---- | ------- | :------: |
+| [access\_points](#input\_access\_points) | Map of EFS Access Point configurations for application-level mount points.
Access Points enforce POSIX user identities and enforce a file system root.
Leave as {} to create no access points.
Example:
access\_points = {
"app-root" = {
enforced\_user\_id = "1000"
root\_directory\_path = "/app"
permissions\_mode = "755"
}
"db-root" = {
enforced\_user\_id = "1001"
root\_directory\_path = "/data"
permissions\_mode = "700"
}
} | `any` | `{}` | no |
+| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no |
+| [allowed\_source\_ips](#input\_allowed\_source\_ips) | List of CIDR blocks allowed to access the EFS. When set, a Deny statement restricts access to these IPs. Leave as [] to skip IP-based restrictions. | `list(string)` | `[]` | no |
+| [application\_role](#input\_application\_role) | The role the application is performing | `string` | `"General"` | no |
+| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no |
+| [availability\_zone\_name](#input\_availability\_zone\_name) | AWS Availability Zone for One Zone storage class. When set, the file system uses single-AZ storage for lower cost. Leave null for multi-AZ. | `string` | `null` | no |
+| [aws\_region](#input\_aws\_region) | The AWS region | `string` | `"eu-west-2"` | no |
+| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"project": null,
"regex_replace_chars": null,
"region": null,
"service": null,
"stack": null,
"tags": {},
"terraform_source": null,
"workspace": null
} | no |
+| [creation\_token](#input\_creation\_token) | A unique name (max 64 chars) used as reference when creating the file system. Enables idempotent creation. When null, Terraform generates a token. | `string` | `null` | no |
+| [custom\_name](#input\_custom\_name) | Optional explicit EFS name. When null, the name is derived from module.this.id. | `string` | `null` | no |
+| [data\_classification](#input\_data\_classification) | Used to identify the data classification of the resource, e.g 1-5 | `string` | `"n/a"` | no |
+| [data\_type](#input\_data\_type) | The tag data\_type | `string` | `"None"` | no |
+| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no |
+| [deny\_destructive\_operations](#input\_deny\_destructive\_operations) | Whether to add a Deny statement for destructive operations (DeleteFileSystem, DeleteAccessPoint) by default. Callers must explicitly allow these via var.file\_system\_policy. Recommended: true. | `bool` | `true` | no |
+| [deny\_unsecure\_transport](#input\_deny\_unsecure\_transport) | Whether to automatically add a Deny statement for non-TLS (unsecure) transport to the file system policy. Recommended: true. | `bool` | `true` | no |
+| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no |
+| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no |
+| [environment](#input\_environment) | ID element. Usually used to indicate role, e.g. 'prd', 'dev', 'test', 'preprod', 'prod', 'uat' | `string` | `null` | no |
+| [file\_system\_policy](#input\_file\_system\_policy) | Optional IAM policy document (as JSON string) to attach to the file system.
Controls who can perform what actions on the EFS resource.
When null, no resource-based policy is attached.
Security baseline: Callers should consider denying nonsecure (non-TLS) transport:
- aws:SecureTransport condition set to false | `string` | `null` | no |
+| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no |
+| [kms\_key\_arn](#input\_kms\_key\_arn) | ARN of the KMS key used for encryption at rest. Encryption is mandatory; this variable is required. | `string` | n/a | yes |
+| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no |
+| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no |
+| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no |
+| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` | [
"default"
]
| no |
+| [lifecycle\_policy](#input\_lifecycle\_policy) | Optional lifecycle policy for automatic transition to other storage classes.
Leave as {} to disable. Example:
lifecycle\_policy = {
transition\_to\_ia = "AFTER\_30\_DAYS"
transition\_to\_primary\_storage\_class = "AFTER\_1\_DAY"
} | `any` | `{}` | no |
+| [mount\_targets](#input\_mount\_targets) | Map of mount target configurations. Each mount target must specify:
- subnet\_id: Subnet where the mount target resides (required)
- security\_groups: List of security group IDs (required; caller must enforce ingress rules for NFSv4.1)
- ip\_address: (optional) Static IP address for the mount target
- ip\_address\_type: (optional) IP address type (ipv4 or ipv6)
- ipv6\_address: (optional) IPv6 address for the mount target
Example:
mount\_targets = {
"az1" = {
subnet\_id = "subnet-12345"
security\_groups = ["sg-12345"]
}
"az2" = {
subnet\_id = "subnet-67890"
security\_groups = ["sg-67890"]
ip\_address = "10.0.1.100"
}
} | `any` | `{}` | no |
+| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no |
+| [on\_off\_pattern](#input\_on\_off\_pattern) | Used to turn resources on and off based on a time pattern | `string` | `"n/a"` | no |
+| [owner](#input\_owner) | The name and or NHS.net email address of the service owner | `string` | `"None"` | no |
+| [performance\_mode](#input\_performance\_mode) | The file system performance mode. Either 'generalPurpose' or 'maxIO'. | `string` | `"generalPurpose"` | no |
+| [project](#input\_project) | ID element. A project identifier, indicating the name or role of the project the resource is for, such as `website` or `api` | `string` | `null` | no |
+| [protection](#input\_protection) | Configuration for replication protection and backup policy.
Defaults:
- enable\_backup: true (enables automated backups via AWS Backup)
- replication\_overwrite: DISABLED (prevents accidental overwrites of replicas during replication) | object({
enable_backup = optional(bool, true)
replication_overwrite = optional(string, "DISABLED")
}) | `{}` | no |
+| [provisioned\_throughput\_in\_mibps](#input\_provisioned\_throughput\_in\_mibps) | The throughput to provision for the file system in MiB/s. Required when throughput\_mode is 'provisioned'. Range: 1–1024. | `number` | `null` | no |
+| [public\_facing](#input\_public\_facing) | Whether this resource is public facing | `bool` | `false` | no |
+| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no |
+| [region](#input\_region) | ID element \_(Rarely used, not included by default)\_. Usually an abbreviation of the selected AWS region e.g. 'uw2', 'ew2' or 'gbl' for resources like IAM roles that have no region | `string` | `null` | no |
+| [replication\_configuration](#input\_replication\_configuration) | Replication configuration for cross-region disaster recovery.
Leave as {} to disable replication.
Example:
replication\_configuration = {
destination = "eu-west-1"
} | `any` | `{}` | no |
+| [require\_tls\_version](#input\_require\_tls\_version) | Minimum TLS version to enforce via file system policy (e.g., '1.2', '1.3'). When set, a Deny statement is added for lower versions. Leave null to skip TLS version enforcement. | `string` | `null` | no |
+| [service](#input\_service) | ID element. Usually an abbreviation of your service directorate name, e.g. 'bcss' or 'csms', to help ensure generated IDs are globally unique | `string` | `null` | no |
+| [service\_category](#input\_service\_category) | The tag service\_category | `string` | `"n/a"` | no |
+| [stack](#input\_stack) | ID element. The name of the stack/component, e.g. `database`, `web`, `waf`, `eks` | `string` | `null` | no |
+| [tag\_version](#input\_tag\_version) | Used to identify the tagging version in use | `string` | `"1.0"` | no |
+| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no |
+| [terraform\_source](#input\_terraform\_source) | Source location to record in the Terraform\_source tag. Defaults to the caller module path when not set. | `string` | `null` | no |
+| [throughput\_mode](#input\_throughput\_mode) | Throughput mode for the file system. Either 'bursting' (default) or 'provisioned'. | `string` | `"bursting"` | no |
+| [tool](#input\_tool) | The tool used to deploy the resource | `string` | `"Terraform"` | no |
+| [workspace](#input\_workspace) | ID element. The Terraform workspace, to help ensure generated IDs are unique across workspaces | `string` | `null` | no |
+
+## Outputs
+
+| Name | Description |
+| ---- | ----------- |
+| [access\_points](#output\_access\_points) | Map of EFS Access Point IDs and their ARNs, keyed by the input map keys. |
+| [efs\_arn](#output\_efs\_arn) | The Amazon Resource Name (ARN) of the EFS file system. |
+| [efs\_dns\_name](#output\_efs\_dns\_name) | The DNS name of the EFS file system. |
+| [efs\_encrypted](#output\_efs\_encrypted) | Always true; encryption is enforced by this module. |
+| [efs\_id](#output\_efs\_id) | The ID of the EFS file system. |
+| [efs\_size\_in\_bytes](#output\_efs\_size\_in\_bytes) | The latest metered size of the EFS in bytes. |
+| [file\_system\_policy\_id](#output\_file\_system\_policy\_id) | The file system policy ID (if policy was attached). |
+| [kms\_key\_arn](#output\_kms\_key\_arn) | The ARN of the KMS key used for encryption. |
+| [mount\_targets](#output\_mount\_targets) | Map of mount target IDs and their associated subnet IDs. |
+| [replication\_configuration](#output\_replication\_configuration) | The replication configuration of the EFS file system, if enabled. |
+
+
+
diff --git a/infrastructure/modules/efs/context.tf b/infrastructure/modules/efs/context.tf
new file mode 100644
index 00000000..9a4652b0
--- /dev/null
+++ b/infrastructure/modules/efs/context.tf
@@ -0,0 +1,377 @@
+# tflint-ignore-file: terraform_standard_module_structure, terraform_unused_declarations
+#
+# ONLY EDIT THIS FILE IN github.com/NHSDigital/screening-terraform-modules-aws/infrastructure/modules/tags
+# All other instances of this file should be a copy of that one
+#
+#
+# Copy this file from https://github.com/NHSDigital/screening-terraform-modules-aws/blob/master/infrastructure/modules/tags/exports/context.tf
+# and then place it in your Terraform module to automatically get
+# tag module standard configuration inputs suitable for passing
+# to other modules.
+#
+# curl -sL https://raw.githubusercontent.com/NHSDigital/screening-terraform-modules-aws/master/infrastructure/modules/tags/exports/context.tf -o context.tf
+#
+# Modules should access the whole context as `module.this.context`
+# to get the input variables with nulls for defaults,
+# for example `context = module.this.context`,
+# and access individual variables as `module.this.`,
+# with final values filled in.
+#
+# For example, when using defaults, `module.this.context.delimiter`
+# will be null, and `module.this.delimiter` will be `-` (hyphen).
+#
+
+module "this" {
+ # tflint-ignore: terraform_module_pinned_source
+ source = "../tags"
+
+ enabled = var.enabled
+ service = var.service
+ project = var.project
+ region = var.region
+ environment = var.environment
+ stack = var.stack
+ workspace = var.workspace
+ name = var.name
+ delimiter = var.delimiter
+ attributes = var.attributes
+ tags = var.tags
+ additional_tag_map = var.additional_tag_map
+ label_order = var.label_order
+ regex_replace_chars = var.regex_replace_chars
+ id_length_limit = var.id_length_limit
+ label_key_case = var.label_key_case
+ label_value_case = var.label_value_case
+ terraform_source = coalesce(var.terraform_source, path.module)
+ descriptor_formats = var.descriptor_formats
+ labels_as_tags = var.labels_as_tags
+
+ context = var.context
+}
+
+# Copy contents of screening-terraform-modules-aws/tags/variables.tf here
+# tflint-ignore: terraform_unused_declarations
+variable "aws_region" {
+ type = string
+ description = "The AWS region"
+ default = "eu-west-2"
+ validation {
+ condition = contains(["eu-west-1", "eu-west-2", "us-east-1"], var.aws_region)
+ error_message = "AWS Region must be one of eu-west-1, eu-west-2, us-east-1"
+ }
+}
+
+variable "context" {
+ type = any
+ default = {
+ enabled = true
+ service = null
+ project = null
+ region = null
+ environment = null
+ stack = null
+ workspace = null
+ name = null
+ delimiter = null
+ attributes = []
+ tags = {}
+ additional_tag_map = {}
+ regex_replace_chars = null
+ label_order = []
+ id_length_limit = null
+ label_key_case = null
+ label_value_case = null
+ terraform_source = null
+ descriptor_formats = {}
+ # Note: we have to use [] instead of null for unset lists due to
+ # https://github.com/hashicorp/terraform/issues/28137
+ # which was not fixed until Terraform 1.0.0,
+ # but we want the default to be all the labels in `label_order`
+ # and we want users to be able to prevent all tag generation
+ # by setting `labels_as_tags` to `[]`, so we need
+ # a different sentinel to indicate "default"
+ labels_as_tags = ["unset"]
+ }
+ description = <<-EOT
+ Single object for setting entire context at once.
+ See description of individual variables for details.
+ Leave string and numeric variables as `null` to use default value.
+ Individual variable settings (non-null) override settings in context object,
+ except for attributes, tags, and additional_tag_map, which are merged.
+ EOT
+
+ validation {
+ condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+ error_message = "Allowed values: `lower`, `title`, `upper`."
+ }
+
+ validation {
+ condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+ error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+ }
+}
+
+variable "terraform_source" {
+ type = string
+ default = null
+ description = "Source location to record in the Terraform_source tag. Defaults to the caller module path when not set."
+}
+
+variable "enabled" {
+ type = bool
+ default = null
+ description = "Set to false to prevent the module from creating any resources"
+}
+
+variable "service" {
+ type = string
+ default = null
+ description = "ID element. Usually an abbreviation of your service directorate name, e.g. 'bcss' or 'csms', to help ensure generated IDs are globally unique"
+}
+
+variable "region" {
+ type = string
+ default = null
+ description = "ID element _(Rarely used, not included by default)_. Usually an abbreviation of the selected AWS region e.g. 'uw2', 'ew2' or 'gbl' for resources like IAM roles that have no region"
+}
+
+variable "project" {
+ type = string
+ default = null
+ description = "ID element. A project identifier, indicating the name or role of the project the resource is for, such as `website` or `api`"
+}
+variable "stack" {
+ type = string
+ default = null
+ description = "ID element. The name of the stack/component, e.g. `database`, `web`, `waf`, `eks`"
+}
+variable "workspace" {
+ type = string
+ default = null
+ description = "ID element. The Terraform workspace, to help ensure generated IDs are unique across workspaces"
+}
+variable "environment" {
+ type = string
+ default = null
+ description = "ID element. Usually used to indicate role, e.g. 'prd', 'dev', 'test', 'preprod', 'prod', 'uat'"
+}
+
+variable "name" {
+ type = string
+ default = null
+ description = <<-EOT
+ ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
+ This is the only ID element not also included as a `tag`.
+ The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input.
+ EOT
+}
+
+variable "delimiter" {
+ type = string
+ default = null
+ description = <<-EOT
+ Delimiter to be used between ID elements.
+ Defaults to `-` (hyphen). Set to `""` to use no delimiter at all.
+ EOT
+}
+
+variable "attributes" {
+ type = list(string)
+ default = []
+ description = <<-EOT
+ ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
+ in the order they appear in the list. New attributes are appended to the
+ end of the list. The elements of the list are joined by the `delimiter`
+ and treated as a single ID element.
+ EOT
+}
+
+variable "labels_as_tags" {
+ type = set(string)
+ default = ["default"]
+ description = <<-EOT
+ Set of labels (ID elements) to include as tags in the `tags` output.
+ Default is to include all labels.
+ Tags with empty values will not be included in the `tags` output.
+ Set to `[]` to suppress all generated tags.
+ **Notes:**
+ The value of the `name` tag, if included, will be the `id`, not the `name`.
+ Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
+ changed in later chained modules. Attempts to change it will be silently ignored.
+ EOT
+}
+
+variable "tags" {
+ type = map(string)
+ default = {}
+ description = <<-EOT
+ Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
+ Neither the tag keys nor the tag values will be modified by this module.
+ EOT
+}
+
+variable "additional_tag_map" {
+ type = map(string)
+ default = {}
+ description = <<-EOT
+ Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
+ This is for some rare cases where resources want additional configuration of tags
+ and therefore take a list of maps with tag key, value, and additional configuration.
+ EOT
+}
+
+variable "label_order" {
+ type = list(string)
+ default = null
+ description = <<-EOT
+ The order in which the labels (ID elements) appear in the `id`.
+ Defaults to ["namespace", "environment", "stage", "name", "attributes"].
+ You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present.
+ EOT
+}
+
+variable "regex_replace_chars" {
+ type = string
+ default = null
+ description = <<-EOT
+ Terraform regular expression (regex) string.
+ Characters matching the regex will be removed from the ID elements.
+ If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits.
+ EOT
+}
+
+variable "id_length_limit" {
+ type = number
+ default = null
+ description = <<-EOT
+ Limit `id` to this many characters (minimum 6).
+ Set to `0` for unlimited length.
+ Set to `null` for keep the existing setting, which defaults to `0`.
+ Does not affect `id_full`.
+ EOT
+ validation {
+ condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0
+ error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length."
+ }
+}
+
+variable "label_key_case" {
+ type = string
+ default = null
+ description = <<-EOT
+ Controls the letter case of the `tags` keys (label names) for tags generated by this module.
+ Does not affect keys of tags passed in via the `tags` input.
+ Possible values: `lower`, `title`, `upper`.
+ Default value: `title`.
+ EOT
+
+ validation {
+ condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+ error_message = "Allowed values: `lower`, `title`, `upper`."
+ }
+}
+
+variable "label_value_case" {
+ type = string
+ default = null
+ description = <<-EOT
+ Controls the letter case of ID elements (labels) as included in `id`,
+ set as tag values, and output by this module individually.
+ Does not affect values of tags passed in via the `tags` input.
+ Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+ Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
+ Default value: `lower`.
+ EOT
+
+ validation {
+ condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+ error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+ }
+}
+
+variable "descriptor_formats" {
+ type = any
+ default = {}
+ description = <<-EOT
+ Describe additional descriptors to be output in the `descriptors` output map.
+ Map of maps. Keys are names of descriptors. Values are maps of the form
+ `{
+ format = string
+ labels = list(string)
+ }`
+ (Type is `any` so the map values can later be enhanced to provide additional options.)
+ `format` is a Terraform format string to be passed to the `format()` function.
+ `labels` is a list of labels, in order, to pass to `format()` function.
+ Label values will be normalized before being passed to `format()` so they will be
+ identical to how they appear in `id`.
+ Default is `{}` (`descriptors` output will be empty).
+ EOT
+}
+
+variable "owner" {
+ type = string
+ description = "The name and or NHS.net email address of the service owner"
+ default = "None"
+}
+
+variable "tag_version" {
+ type = string
+ description = "Used to identify the tagging version in use"
+ default = "1.0"
+}
+
+variable "data_classification" {
+ type = string
+ description = "Used to identify the data classification of the resource, e.g 1-5"
+ default = "n/a"
+ validation {
+ condition = contains(["n/a", "1", "2", "3", "4", "5"], var.data_classification)
+ error_message = "Data Classification must be \"n/a\" or between 1-5"
+ }
+}
+
+variable "data_type" {
+ type = string
+ description = "The tag data_type"
+ default = "None"
+ validation {
+ condition = contains(["None", "PCD", "PID", "Anonymised", "UserAccount", "Audit"], var.data_type)
+ error_message = "Data Type must be one of None, PCD, PID, Anonymised, UserAccount, Audit"
+ }
+}
+
+
+variable "public_facing" {
+ type = bool
+ description = "Whether this resource is public facing"
+ default = false
+}
+
+variable "service_category" {
+ type = string
+ description = "The tag service_category"
+ default = "n/a"
+ validation {
+ condition = contains(["n/a", "Bronze", "Silver", "Gold", "Platinum"], var.service_category)
+ error_message = "The Service Category must be one of n/a, Bronze, Silver, Gold, Platinum"
+ }
+}
+variable "on_off_pattern" {
+ type = string
+ description = "Used to turn resources on and off based on a time pattern"
+ default = "n/a"
+}
+
+variable "application_role" {
+ type = string
+ description = "The role the application is performing"
+ default = "General"
+}
+
+variable "tool" {
+ type = string
+ description = "The tool used to deploy the resource"
+ default = "Terraform"
+}
+
+#### End of copy of screening-terraform-modules-aws/tags/variables.tf
diff --git a/infrastructure/modules/efs/locals.tf b/infrastructure/modules/efs/locals.tf
new file mode 100644
index 00000000..45d35821
--- /dev/null
+++ b/infrastructure/modules/efs/locals.tf
@@ -0,0 +1,87 @@
+################################################################
+# Local values
+#
+# EFS name is derived from the context, with optional override.
+# File system policy is built with optional security statements.
+################################################################
+
+locals {
+ # Naming logic — derive from context, allow caller override
+ efs_name = var.custom_name != null ? var.custom_name : module.this.id
+
+ # Build list of default security statements to add to the policy
+ default_policy_statements = concat(
+ # Deny unsecure (non-TLS) transport
+ var.deny_unsecure_transport ? [
+ {
+ Sid = "DenyUnsecureTransport"
+ Effect = "Deny"
+ Principal = "*"
+ Action = "elasticfilesystem:*"
+ Resource = "*"
+ Condition = {
+ Bool = {
+ "aws:SecureTransport" = "false"
+ }
+ }
+ }
+ ] : [],
+
+ # Deny old TLS versions (require TLS 1.2+)
+ var.require_tls_version != null ? [
+ {
+ Sid = "DenyOldTLSVersion"
+ Effect = "Deny"
+ Principal = "*"
+ Action = "elasticfilesystem:*"
+ Resource = "*"
+ Condition = {
+ StringLessThan = {
+ "aws:TlsVersion" = var.require_tls_version
+ }
+ }
+ }
+ ] : [],
+
+ # Deny access from IPs outside allowed list
+ length(var.allowed_source_ips) > 0 ? [
+ {
+ Sid = "DenyUnauthorizedSourceIPs"
+ Effect = "Deny"
+ Principal = "*"
+ Action = "elasticfilesystem:*"
+ Resource = "*"
+ Condition = {
+ NotIpAddress = {
+ "aws:SourceIp" = var.allowed_source_ips
+ }
+ }
+ }
+ ] : [],
+
+ # Deny destructive operations by default
+ var.deny_destructive_operations ? [
+ {
+ Sid = "DenyDestructiveOperations"
+ Effect = "Deny"
+ Principal = "*"
+ Action = [
+ "elasticfilesystem:DeleteFileSystem",
+ "elasticfilesystem:DeleteAccessPoint",
+ "elasticfilesystem:DeleteMountTarget",
+ "elasticfilesystem:DeleteReplicationConfiguration"
+ ]
+ Resource = "*"
+ }
+ ] : []
+ )
+
+ # File system policy: merge caller policy with default security statements
+ file_system_policy_doc = length(local.default_policy_statements) > 0 || var.file_system_policy != null ? jsonencode({
+ Version = "2012-10-17"
+ Statement = concat(
+ var.file_system_policy != null ? jsondecode(var.file_system_policy).Statement : [],
+ local.default_policy_statements
+ )
+ }) : null
+}
diff --git a/infrastructure/modules/efs/main.tf b/infrastructure/modules/efs/main.tf
new file mode 100644
index 00000000..42282774
--- /dev/null
+++ b/infrastructure/modules/efs/main.tf
@@ -0,0 +1,117 @@
+################################################################
+# EFS (Elastic File System)
+#
+# Thin NHS wrapper around the community terraform-aws-modules/efs/aws
+# module that enforces the screening platform's baseline controls:
+#
+# * Encryption: KMS encryption at rest is mandatory
+# * Transport: NFSv4.1 security groups are caller-supplied (required)
+# * Throughput: caller controls performance mode and throughput mode
+# * Access: optional backup/transition lifecycle policies
+# * Policy: auto-generated security statements for transport, TLS, IPs, and destructive ops
+#
+# Naming and tagging are derived from context.tf via module.this.
+# Cross-variable input constraints are enforced in validations.tf.
+################################################################
+
+module "efs" {
+ source = "terraform-aws-modules/efs/aws"
+ version = "2.2.0"
+
+ create = module.this.enabled
+
+ # ----------------------------------------------------------------
+ # Naming and metadata.
+ # ----------------------------------------------------------------
+ name = local.efs_name
+ creation_token = var.creation_token
+
+ # ----------------------------------------------------------------
+ # Encryption at rest: mandatory KMS encryption.
+ # ----------------------------------------------------------------
+ encrypted = true
+ kms_key_arn = var.kms_key_arn
+
+ # ----------------------------------------------------------------
+ # Performance and throughput modes. Defaults are suitable for
+ # general purpose; caller can override for higher-performance needs.
+ # ----------------------------------------------------------------
+ performance_mode = var.performance_mode
+ throughput_mode = var.throughput_mode
+ provisioned_throughput_in_mibps = var.throughput_mode == "provisioned" ? var.provisioned_throughput_in_mibps : null
+
+ # ----------------------------------------------------------------
+ # Transition and lifecycle policies (optional).
+ # ----------------------------------------------------------------
+ lifecycle_policy = var.lifecycle_policy
+
+ # ----------------------------------------------------------------
+ # Mount targets: caller must provide security group(s) and subnets.
+ # ----------------------------------------------------------------
+ mount_targets = var.mount_targets
+
+ # ----------------------------------------------------------------
+ # Backup policy (optional).
+ # ----------------------------------------------------------------
+ create_backup_policy = var.protection.enable_backup
+ enable_backup_policy = var.protection.enable_backup
+
+ # ----------------------------------------------------------------
+ # Replication configuration (optional).
+ # ----------------------------------------------------------------
+ create_replication_configuration = length(var.replication_configuration) > 0
+ replication_configuration_destination = var.replication_configuration
+
+ # ----------------------------------------------------------------
+ # Tagging: all resources tagged via module.this.
+ # ----------------------------------------------------------------
+ tags = module.this.tags
+}
+
+################################################################
+# File System Policy (optional, resource-based access control)
+################################################################
+
+resource "aws_efs_file_system_policy" "this" {
+ count = module.this.enabled && local.file_system_policy_doc != null ? 1 : 0
+
+ file_system_id = module.efs.id
+ policy = local.file_system_policy_doc
+
+ depends_on = [module.efs]
+}
+
+################################################################
+# Access Points (optional, application-level isolation)
+################################################################
+resource "aws_efs_access_point" "this" {
+ for_each = module.this.enabled ? var.access_points : {}
+
+ file_system_id = module.efs.id
+
+ # Root directory enforces a chroot jail for the access point
+ root_directory {
+ path = each.value.root_directory_path
+
+ creation_info {
+ owner_gid = each.value.enforced_user_id
+ owner_uid = each.value.enforced_user_id
+ permissions = each.value.permissions_mode
+ }
+ }
+
+ # Enforce a POSIX user identity for all requests through this access point
+ posix_user {
+ gid = each.value.enforced_user_id
+ uid = each.value.enforced_user_id
+ }
+
+ tags = merge(
+ module.this.tags,
+ {
+ Name = "${module.this.id}-${each.key}"
+ }
+ )
+
+ depends_on = [module.efs]
+}
diff --git a/infrastructure/modules/efs/outputs.tf b/infrastructure/modules/efs/outputs.tf
new file mode 100644
index 00000000..42c7582b
--- /dev/null
+++ b/infrastructure/modules/efs/outputs.tf
@@ -0,0 +1,49 @@
+output "efs_id" {
+ description = "The ID of the EFS file system."
+ value = module.efs.id
+}
+
+output "efs_arn" {
+ description = "The Amazon Resource Name (ARN) of the EFS file system."
+ value = module.efs.arn
+}
+
+output "efs_dns_name" {
+ description = "The DNS name of the EFS file system."
+ value = module.efs.dns_name
+}
+
+output "efs_size_in_bytes" {
+ description = "The latest metered size of the EFS in bytes."
+ value = module.efs.size_in_bytes
+}
+
+output "efs_encrypted" {
+ description = "Always true; encryption is enforced by this module."
+ value = true
+}
+
+output "kms_key_arn" {
+ description = "The ARN of the KMS key used for encryption."
+ value = var.kms_key_arn
+}
+
+output "mount_targets" {
+ description = "Map of mount target IDs and their associated subnet IDs."
+ value = module.efs.mount_targets
+}
+
+output "replication_configuration" {
+ description = "The replication configuration of the EFS file system, if enabled."
+ value = try(module.efs.replication_configuration_destination_file_system_id, null)
+}
+
+output "file_system_policy_id" {
+ description = "The file system policy ID (if policy was attached)."
+ value = try(aws_efs_file_system_policy.this[0].id, null)
+}
+
+output "access_points" {
+ description = "Map of EFS Access Point IDs and their ARNs, keyed by the input map keys."
+ value = { for k, ap in aws_efs_access_point.this : k => { id = ap.id, arn = ap.arn } }
+}
diff --git a/infrastructure/modules/efs/validations.tf b/infrastructure/modules/efs/validations.tf
new file mode 100644
index 00000000..a1ec4adc
--- /dev/null
+++ b/infrastructure/modules/efs/validations.tf
@@ -0,0 +1,58 @@
+################################################################
+# Input validation
+#
+# Validates cross-variable constraints that cannot be expressed
+# through individual variable validation blocks:
+#
+# * throughput_mode = "provisioned" requires provisioned_throughput_in_mibps
+# * replication_configuration must have mount_targets defined
+# * access_points must have mount_targets defined
+# * lifecycle_policy keys must be valid EFS policy keys
+# * replication_overwrite must be "DISABLED" or "ENABLED"
+# * access_point permissions_mode must be valid octal (3-4 digits)
+# * availability_zone_name (one-zone) requires exactly one mount target
+################################################################
+
+resource "terraform_data" "validations" {
+ count = module.this.enabled ? 1 : 0
+
+ lifecycle {
+ precondition {
+ condition = !(var.throughput_mode == "provisioned" && var.provisioned_throughput_in_mibps == null)
+ error_message = "throughput_mode = 'provisioned' requires provisioned_throughput_in_mibps to be set."
+ }
+ precondition {
+ condition = !(length(var.replication_configuration) > 0 && length(var.mount_targets) == 0)
+ error_message = "replication_configuration requires at least one mount target to be configured."
+ }
+ precondition {
+ condition = !(length(var.access_points) > 0 && length(var.mount_targets) == 0)
+ error_message = "access_points requires at least one mount target to be configured."
+ }
+ precondition {
+ condition = var.lifecycle_policy == null ? true : (
+ alltrue([
+ for key in keys(var.lifecycle_policy) : contains([
+ "transition_to_ia",
+ "transition_to_primary_storage_class"
+ ], key)
+ ])
+ )
+ error_message = "lifecycle_policy keys must be one of: transition_to_ia, transition_to_primary_storage_class."
+ }
+ precondition {
+ condition = var.protection.replication_overwrite == null ? true : contains(["DISABLED", "ENABLED"], var.protection.replication_overwrite)
+ error_message = "protection.replication_overwrite must be 'DISABLED' or 'ENABLED'."
+ }
+ precondition {
+ condition = alltrue([
+ for ap_key, ap in var.access_points : can(regex("^[0-7]{3,4}$", ap.permissions_mode))
+ ])
+ error_message = "access_point permissions_mode must be valid octal notation (e.g., '755', '700')."
+ }
+ precondition {
+ condition = !(var.availability_zone_name != null && length(var.mount_targets) != 1)
+ error_message = "availability_zone_name (one-zone storage) requires exactly one mount target; found ${length(var.mount_targets)}."
+ }
+ }
+}
diff --git a/infrastructure/modules/efs/variables.tf b/infrastructure/modules/efs/variables.tf
new file mode 100644
index 00000000..d7c07fc9
--- /dev/null
+++ b/infrastructure/modules/efs/variables.tf
@@ -0,0 +1,237 @@
+################################################################
+# EFS-specific inputs.
+################################################################
+
+variable "availability_zone_name" {
+ description = "AWS Availability Zone for One Zone storage class. When set, the file system uses single-AZ storage for lower cost. Leave null for multi-AZ."
+ type = string
+ default = null
+}
+
+variable "creation_token" {
+ description = "A unique name (max 64 chars) used as reference when creating the file system. Enables idempotent creation. When null, Terraform generates a token."
+ type = string
+ default = null
+}
+
+variable "custom_name" {
+ description = "Optional explicit EFS name. When null, the name is derived from module.this.id."
+ type = string
+ default = null
+}
+
+variable "kms_key_arn" {
+ description = "ARN of the KMS key used for encryption at rest. Encryption is mandatory; this variable is required."
+ type = string
+
+ validation {
+ condition = can(regex("^arn:aws:kms:", var.kms_key_arn))
+ error_message = "kms_key_arn must be a valid KMS ARN (arn:aws:kms:...)."
+ }
+}
+
+variable "performance_mode" {
+ description = "The file system performance mode. Either 'generalPurpose' or 'maxIO'."
+ type = string
+ default = "generalPurpose"
+
+ validation {
+ condition = contains(["generalPurpose", "maxIO"], var.performance_mode)
+ error_message = "performance_mode must be either 'generalPurpose' or 'maxIO'."
+ }
+}
+
+variable "throughput_mode" {
+ description = "Throughput mode for the file system. Either 'bursting' (default) or 'provisioned'."
+ type = string
+ default = "bursting"
+
+ validation {
+ condition = contains(["bursting", "provisioned"], var.throughput_mode)
+ error_message = "throughput_mode must be either 'bursting' or 'provisioned'."
+ }
+}
+
+variable "provisioned_throughput_in_mibps" {
+ description = "The throughput to provision for the file system in MiB/s. Required when throughput_mode is 'provisioned'. Range: 1–1024."
+ type = number
+ default = null
+
+ validation {
+ condition = var.provisioned_throughput_in_mibps == null || (
+ var.provisioned_throughput_in_mibps >= 1 && var.provisioned_throughput_in_mibps <= 1024
+ )
+ error_message = "provisioned_throughput_in_mibps must be between 1 and 1024 MiB/s when set."
+ }
+}
+
+variable "mount_targets" {
+ description = <<-EOT
+ Map of mount target configurations. Each mount target must specify:
+ - subnet_id: Subnet where the mount target resides (required)
+ - security_groups: List of security group IDs (required; caller must enforce ingress rules for NFSv4.1)
+ - ip_address: (optional) Static IP address for the mount target
+ - ip_address_type: (optional) IP address type (ipv4 or ipv6)
+ - ipv6_address: (optional) IPv6 address for the mount target
+
+ Example:
+ mount_targets = {
+ "az1" = {
+ subnet_id = "subnet-12345"
+ security_groups = ["sg-12345"]
+ }
+ "az2" = {
+ subnet_id = "subnet-67890"
+ security_groups = ["sg-67890"]
+ ip_address = "10.0.1.100"
+ }
+ }
+ EOT
+ type = any
+ default = {}
+
+ validation {
+ condition = alltrue([
+ for mt_key, mt in var.mount_targets : (
+ can(mt.subnet_id) &&
+ can(mt.security_groups) &&
+ length(mt.security_groups) > 0
+ )
+ ])
+ error_message = "Each mount target must have subnet_id and non-empty security_groups list."
+ }
+}
+
+variable "lifecycle_policy" {
+ description = <<-EOT
+ Optional lifecycle policy for automatic transition to other storage classes.
+ Leave as {} to disable. Example:
+ lifecycle_policy = {
+ transition_to_ia = "AFTER_30_DAYS"
+ transition_to_primary_storage_class = "AFTER_1_DAY"
+ }
+ EOT
+ type = any
+ default = {}
+}
+
+variable "protection" {
+ description = <<-EOT
+ Configuration for replication protection and backup policy.
+
+ Defaults:
+ - enable_backup: true (enables automated backups via AWS Backup)
+ - replication_overwrite: DISABLED (prevents accidental overwrites of replicas during replication)
+ EOT
+ type = object({
+ enable_backup = optional(bool, true)
+ replication_overwrite = optional(string, "DISABLED")
+ })
+ default = {}
+}
+
+variable "file_system_policy" {
+ description = <<-EOT
+ Optional IAM policy document (as JSON string) to attach to the file system.
+ Controls who can perform what actions on the EFS resource.
+ When null, no resource-based policy is attached.
+
+ Security baseline: Callers should consider denying nonsecure (non-TLS) transport:
+ - aws:SecureTransport condition set to false
+ EOT
+ type = string
+ default = null
+
+ validation {
+ condition = var.file_system_policy == null || (
+ can(jsondecode(var.file_system_policy)) &&
+ can(jsondecode(var.file_system_policy).Statement)
+ )
+ error_message = "file_system_policy must be a valid JSON IAM policy document."
+ }
+}
+
+variable "deny_unsecure_transport" {
+ description = "Whether to automatically add a Deny statement for non-TLS (unsecure) transport to the file system policy. Recommended: true."
+ type = bool
+ default = true
+}
+
+variable "access_points" {
+ description = <<-EOT
+ Map of EFS Access Point configurations for application-level mount points.
+ Access Points enforce POSIX user identities and enforce a file system root.
+ Leave as {} to create no access points.
+
+ Example:
+ access_points = {
+ "app-root" = {
+ enforced_user_id = "1000"
+ root_directory_path = "/app"
+ permissions_mode = "755"
+ }
+ "db-root" = {
+ enforced_user_id = "1001"
+ root_directory_path = "/data"
+ permissions_mode = "700"
+ }
+ }
+ EOT
+ type = any
+ default = {}
+
+ validation {
+ condition = alltrue([
+ for ap_key, ap in var.access_points : (
+ can(ap.enforced_user_id) &&
+ can(ap.root_directory_path) &&
+ can(ap.permissions_mode)
+ )
+ ])
+ error_message = "Each access point must have enforced_user_id, root_directory_path, and permissions_mode."
+ }
+}
+
+variable "replication_configuration" {
+ description = <<-EOT
+ Replication configuration for cross-region disaster recovery.
+ Leave as {} to disable replication.
+
+ Example:
+ replication_configuration = {
+ destination = "eu-west-1"
+ }
+ EOT
+ type = any
+ default = {}
+}
+
+variable "require_tls_version" {
+ description = "Minimum TLS version to enforce via file system policy (e.g., '1.2', '1.3'). When set, a Deny statement is added for lower versions. Leave null to skip TLS version enforcement."
+ type = string
+ default = null
+
+ validation {
+ condition = var.require_tls_version == null || can(regex("^1\\.[2-3]$", var.require_tls_version))
+ error_message = "require_tls_version must be null, '1.2', or '1.3'."
+ }
+}
+
+variable "allowed_source_ips" {
+ description = "List of CIDR blocks allowed to access the EFS. When set, a Deny statement restricts access to these IPs. Leave as [] to skip IP-based restrictions."
+ type = list(string)
+ default = []
+
+ validation {
+ condition = alltrue([
+ for cidr in var.allowed_source_ips : can(regex("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}(/[0-9]{1,2})?$", cidr))
+ ])
+ error_message = "allowed_source_ips must contain valid CIDR blocks (e.g., '10.0.0.0/8')."
+ }
+}
+
+variable "deny_destructive_operations" {
+ description = "Whether to add a Deny statement for destructive operations (DeleteFileSystem, DeleteAccessPoint) by default. Callers must explicitly allow these via var.file_system_policy. Recommended: true."
+ type = bool
+ default = true
+}
diff --git a/infrastructure/modules/efs/versions.tf b/infrastructure/modules/efs/versions.tf
new file mode 100644
index 00000000..bdaae8ef
--- /dev/null
+++ b/infrastructure/modules/efs/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.13"
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 6.28"
+ }
+ }
+}
diff --git a/mise.lock b/mise.lock
index 0a809bcc..1a0c0f04 100644
--- a/mise.lock
+++ b/mise.lock
@@ -24,30 +24,6 @@ checksum = "sha256:4592b096e553f67799ae70d4b6167eeda3ec74587d68c7aecbf4e7b1df136
url = "https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-15.2.0-h8acb6b2_19.conda"
checksum = "sha256:2370ef0ffcbae5bede3c4bf136add4abc257245eb91f724c99bb4a43116c5a83"
-[conda-packages.linux-x64."_openmp_mutex-4.5-20_gnu"]
-url = "https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda"
-checksum = "sha256:1dd3fffd892081df9726d7eb7e0dea6198962ba775bd88842135a4ddb4deb3c9"
-
-[conda-packages.linux-x64."libgcc-15.2.0-he0feb66_19"]
-url = "https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda"
-checksum = "sha256:8e0a3b5e41272e5678499b5dfc4cddb673f9e935de01eb0767ce857001229f46"
-
-[conda-packages.linux-x64."libgomp-15.2.0-he0feb66_19"]
-url = "https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda"
-checksum = "sha256:5abe4ab9d93f6c9757d654f1969ae2267d4505315c1f2f8fe705fd60af084f1b"
-
-[conda-packages.linux-x64-musl."_openmp_mutex-4.5-20_gnu"]
-url = "https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda"
-checksum = "sha256:1dd3fffd892081df9726d7eb7e0dea6198962ba775bd88842135a4ddb4deb3c9"
-
-[conda-packages.linux-x64-musl."libgcc-15.2.0-he0feb66_19"]
-url = "https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda"
-checksum = "sha256:8e0a3b5e41272e5678499b5dfc4cddb673f9e935de01eb0767ce857001229f46"
-
-[conda-packages.linux-x64-musl."libgomp-15.2.0-he0feb66_19"]
-url = "https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda"
-checksum = "sha256:5abe4ab9d93f6c9757d654f1969ae2267d4505315c1f2f8fe705fd60af084f1b"
-
[conda-packages.windows-x64."_openmp_mutex-4.5-20_gnu"]
url = "https://conda.anaconda.org/conda-forge/win-64/_openmp_mutex-4.5-20_gnu.conda"
checksum = "sha256:8a1cee28bd0ee7451ada1cd50b64720e57e17ff994fc62dd8329bef570d382e4"
@@ -75,36 +51,43 @@ backend = "aqua:rhysd/actionlint"
[tools.actionlint."platforms.linux-arm64"]
checksum = "sha256:325e971b6ba9bfa504672e29be93c24981eeb1c07576d730e9f7c8805afff0c6"
url = "https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_arm64.tar.gz"
+url_api = "https://api.github.com/repos/rhysd/actionlint/releases/assets/384924897"
provenance = "github-attestations"
[tools.actionlint."platforms.linux-arm64-musl"]
checksum = "sha256:325e971b6ba9bfa504672e29be93c24981eeb1c07576d730e9f7c8805afff0c6"
url = "https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_arm64.tar.gz"
+url_api = "https://api.github.com/repos/rhysd/actionlint/releases/assets/384924897"
provenance = "github-attestations"
[tools.actionlint."platforms.linux-x64"]
checksum = "sha256:8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8"
url = "https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz"
+url_api = "https://api.github.com/repos/rhysd/actionlint/releases/assets/384924896"
provenance = "github-attestations"
[tools.actionlint."platforms.linux-x64-musl"]
checksum = "sha256:8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8"
url = "https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz"
+url_api = "https://api.github.com/repos/rhysd/actionlint/releases/assets/384924896"
provenance = "github-attestations"
[tools.actionlint."platforms.macos-arm64"]
checksum = "sha256:aba9ced2dee8d27fecca3dc7feb1a7f9a52caefa1eb46f3271ea66b6e0e6953f"
url = "https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_darwin_arm64.tar.gz"
+url_api = "https://api.github.com/repos/rhysd/actionlint/releases/assets/384924893"
provenance = "github-attestations"
[tools.actionlint."platforms.macos-x64"]
checksum = "sha256:5b44c3bc2255115c9b69e30efc0fecdf498fdb63c5d58e17084fd5f16324c644"
url = "https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_darwin_amd64.tar.gz"
+url_api = "https://api.github.com/repos/rhysd/actionlint/releases/assets/384924880"
provenance = "github-attestations"
[tools.actionlint."platforms.windows-x64"]
checksum = "sha256:6e7241b51e6817ea6a047693d8e6fed13b31819c9a0dd6c5a726e1592d22f6e9"
url = "https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_windows_amd64.zip"
+url_api = "https://api.github.com/repos/rhysd/actionlint/releases/assets/384924919"
provenance = "github-attestations"
[[tools.gitleaks]]
@@ -114,30 +97,37 @@ backend = "aqua:gitleaks/gitleaks"
[tools.gitleaks."platforms.linux-arm64"]
checksum = "sha256:e4a487ee7ccd7d3a7f7ec08657610aa3606637dab924210b3aee62570fb4b080"
url = "https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_arm64.tar.gz"
+url_api = "https://api.github.com/repos/gitleaks/gitleaks/releases/assets/378332057"
[tools.gitleaks."platforms.linux-arm64-musl"]
checksum = "sha256:e4a487ee7ccd7d3a7f7ec08657610aa3606637dab924210b3aee62570fb4b080"
url = "https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_arm64.tar.gz"
+url_api = "https://api.github.com/repos/gitleaks/gitleaks/releases/assets/378332057"
[tools.gitleaks."platforms.linux-x64"]
checksum = "sha256:551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb"
url = "https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz"
+url_api = "https://api.github.com/repos/gitleaks/gitleaks/releases/assets/378332058"
[tools.gitleaks."platforms.linux-x64-musl"]
checksum = "sha256:551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb"
url = "https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz"
+url_api = "https://api.github.com/repos/gitleaks/gitleaks/releases/assets/378332058"
[tools.gitleaks."platforms.macos-arm64"]
checksum = "sha256:b40ab0ae55c505963e365f271a8d3846efbc170aa17f2607f13df610a9aeb6a5"
url = "https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_darwin_arm64.tar.gz"
+url_api = "https://api.github.com/repos/gitleaks/gitleaks/releases/assets/378332059"
[tools.gitleaks."platforms.macos-x64"]
checksum = "sha256:dfe101a4db2255fc85120ac7f3d25e4342c3c20cf749f2c20a18081af1952709"
url = "https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_darwin_x64.tar.gz"
+url_api = "https://api.github.com/repos/gitleaks/gitleaks/releases/assets/378332053"
[tools.gitleaks."platforms.windows-x64"]
checksum = "sha256:d29144deff3a68aa93ced33dddf84b7fdc26070add4aa0f4513094c8332afc4e"
url = "https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_windows_x64.zip"
+url_api = "https://api.github.com/repos/gitleaks/gitleaks/releases/assets/378333178"
[[tools.go]]
version = "1.26.5"
@@ -182,36 +172,43 @@ backend = "aqua:jqlang/jq"
[tools.jq."platforms.linux-arm64"]
checksum = "sha256:8b85c817833814ddca00a144c33705546355afccf0cf39b188f3cdb48b852309"
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-linux-arm64"
+url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012756"
provenance = "github-attestations"
[tools.jq."platforms.linux-arm64-musl"]
checksum = "sha256:8b85c817833814ddca00a144c33705546355afccf0cf39b188f3cdb48b852309"
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-linux-arm64"
+url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012756"
provenance = "github-attestations"
[tools.jq."platforms.linux-x64"]
checksum = "sha256:b1c22172dd303f3be49e935aa56aa48a8b7a46e0bc838b4997d3bb451495870f"
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-linux-amd64"
+url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012752"
provenance = "github-attestations"
[tools.jq."platforms.linux-x64-musl"]
checksum = "sha256:b1c22172dd303f3be49e935aa56aa48a8b7a46e0bc838b4997d3bb451495870f"
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-linux-amd64"
+url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012752"
provenance = "github-attestations"
[tools.jq."platforms.macos-arm64"]
checksum = "sha256:2d75340ba57a4b4b4c8708a21c2dc8e958a48aaa8bba13b27f77f6e4c0eca07e"
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-macos-arm64"
+url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012783"
provenance = "github-attestations"
[tools.jq."platforms.macos-x64"]
checksum = "sha256:e94b266e3c26690550006abe63152b782280f4e14374accdf04cbde844f00bc0"
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-macos-amd64"
+url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012782"
provenance = "github-attestations"
[tools.jq."platforms.windows-x64"]
checksum = "sha256:a6fc67fedaf9128a3309a1e2ebb8b986aeccf70122ee46d2cb4849e423f0c627"
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-windows-amd64.exe"
+url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012788"
provenance = "github-attestations"
[[tools.make]]
@@ -242,28 +239,20 @@ conda_deps = [
[tools.make."platforms.linux-x64"]
checksum = "sha256:d652c7bd4d3b6f82b0f6d063b0d8df6f54cc47531092d7ff008e780f3261bdda"
url = "https://conda.anaconda.org/conda-forge/linux-64/make-4.4.1-hb9d3cd8_2.conda"
-conda_deps = [
- "libgcc-15.2.0-he0feb66_19",
- "_openmp_mutex-4.5-20_gnu",
- "libgomp-15.2.0-he0feb66_19",
-]
[tools.make."platforms.linux-x64-musl"]
checksum = "sha256:d652c7bd4d3b6f82b0f6d063b0d8df6f54cc47531092d7ff008e780f3261bdda"
url = "https://conda.anaconda.org/conda-forge/linux-64/make-4.4.1-hb9d3cd8_2.conda"
-conda_deps = [
- "libgcc-15.2.0-he0feb66_19",
- "_openmp_mutex-4.5-20_gnu",
- "libgomp-15.2.0-he0feb66_19",
-]
[tools.make."platforms.macos-arm64"]
checksum = "sha256:90ca65e788406d9029ae23ad4bd944a8b5353ad5f59bd6ce326f980cde46f37e"
url = "https://conda.anaconda.org/conda-forge/osx-arm64/make-4.4.1-hc9fafa5_2.conda"
+conda_deps = []
[tools.make."platforms.macos-x64"]
checksum = "sha256:5a5ab3ee828309185e0a76ca80f5da85f31d8480d923abb508ca00fe194d1b5a"
url = "https://conda.anaconda.org/conda-forge/osx-64/make-4.4.1-h00291cd_2.conda"
+conda_deps = []
[tools.make."platforms.windows-x64"]
checksum = "sha256:a810cdca3d5fa50d562cda23c0c1195b45ff5f9b0c41e0d4c8c2dd3c043ff4f2"
@@ -315,26 +304,32 @@ backend = "aqua:pre-commit/pre-commit"
[tools.pre-commit."platforms.linux-arm64"]
checksum = "sha256:ea8a0c84902e48c1875558f2f362ed8476773aa5fc8c16c5d8f2acc2a2830a65"
url = "https://github.com/pre-commit/pre-commit/releases/download/v4.6.0/pre-commit-4.6.0.pyz"
+url_api = "https://api.github.com/repos/pre-commit/pre-commit/releases/assets/401888770"
[tools.pre-commit."platforms.linux-arm64-musl"]
checksum = "sha256:ea8a0c84902e48c1875558f2f362ed8476773aa5fc8c16c5d8f2acc2a2830a65"
url = "https://github.com/pre-commit/pre-commit/releases/download/v4.6.0/pre-commit-4.6.0.pyz"
+url_api = "https://api.github.com/repos/pre-commit/pre-commit/releases/assets/401888770"
[tools.pre-commit."platforms.linux-x64"]
checksum = "sha256:ea8a0c84902e48c1875558f2f362ed8476773aa5fc8c16c5d8f2acc2a2830a65"
url = "https://github.com/pre-commit/pre-commit/releases/download/v4.6.0/pre-commit-4.6.0.pyz"
+url_api = "https://api.github.com/repos/pre-commit/pre-commit/releases/assets/401888770"
[tools.pre-commit."platforms.linux-x64-musl"]
checksum = "sha256:ea8a0c84902e48c1875558f2f362ed8476773aa5fc8c16c5d8f2acc2a2830a65"
url = "https://github.com/pre-commit/pre-commit/releases/download/v4.6.0/pre-commit-4.6.0.pyz"
+url_api = "https://api.github.com/repos/pre-commit/pre-commit/releases/assets/401888770"
[tools.pre-commit."platforms.macos-arm64"]
checksum = "sha256:ea8a0c84902e48c1875558f2f362ed8476773aa5fc8c16c5d8f2acc2a2830a65"
url = "https://github.com/pre-commit/pre-commit/releases/download/v4.6.0/pre-commit-4.6.0.pyz"
+url_api = "https://api.github.com/repos/pre-commit/pre-commit/releases/assets/401888770"
[tools.pre-commit."platforms.macos-x64"]
checksum = "sha256:ea8a0c84902e48c1875558f2f362ed8476773aa5fc8c16c5d8f2acc2a2830a65"
url = "https://github.com/pre-commit/pre-commit/releases/download/v4.6.0/pre-commit-4.6.0.pyz"
+url_api = "https://api.github.com/repos/pre-commit/pre-commit/releases/assets/401888770"
[[tools.python]]
version = "3.12.13"
@@ -382,30 +377,37 @@ backend = "aqua:koalaman/shellcheck"
[tools.shellcheck."platforms.linux-arm64"]
checksum = "sha256:12b331c1d2db6b9eb13cfca64306b1b157a86eb69db83023e261eaa7e7c14588"
url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.aarch64.tar.xz"
+url_api = "https://api.github.com/repos/koalaman/shellcheck/releases/assets/279056934"
[tools.shellcheck."platforms.linux-arm64-musl"]
checksum = "sha256:12b331c1d2db6b9eb13cfca64306b1b157a86eb69db83023e261eaa7e7c14588"
url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.aarch64.tar.xz"
+url_api = "https://api.github.com/repos/koalaman/shellcheck/releases/assets/279056934"
[tools.shellcheck."platforms.linux-x64"]
checksum = "sha256:8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198"
url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz"
+url_api = "https://api.github.com/repos/koalaman/shellcheck/releases/assets/279056942"
[tools.shellcheck."platforms.linux-x64-musl"]
checksum = "sha256:8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198"
url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz"
+url_api = "https://api.github.com/repos/koalaman/shellcheck/releases/assets/279056942"
[tools.shellcheck."platforms.macos-arm64"]
checksum = "sha256:56affdd8de5527894dca6dc3d7e0a99a873b0f004d7aabc30ae407d3f48b0a79"
url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.darwin.aarch64.tar.xz"
+url_api = "https://api.github.com/repos/koalaman/shellcheck/releases/assets/279056932"
[tools.shellcheck."platforms.macos-x64"]
checksum = "sha256:3c89db4edcab7cf1c27bff178882e0f6f27f7afdf54e859fa041fca10febe4c6"
url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.darwin.x86_64.tar.xz"
+url_api = "https://api.github.com/repos/koalaman/shellcheck/releases/assets/279056930"
[tools.shellcheck."platforms.windows-x64"]
checksum = "sha256:8a4e35ab0b331c85d73567b12f2a444df187f483e5079ceffa6bda1faa2e740e"
url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.zip"
+url_api = "https://api.github.com/repos/koalaman/shellcheck/releases/assets/279056944"
[[tools.terraform]]
version = "1.13.5"
@@ -446,30 +448,37 @@ backend = "aqua:terraform-docs/terraform-docs"
[tools.terraform-docs."platforms.linux-arm64"]
checksum = "sha256:d12bd7b73c1fc9c64efc79f8157dd713dabd559f1ecf3cfc0f42e32279a155fd"
url = "https://github.com/terraform-docs/terraform-docs/releases/download/v0.24.0/terraform-docs-v0.24.0-linux-arm64.tar.gz"
+url_api = "https://api.github.com/repos/terraform-docs/terraform-docs/releases/assets/416956212"
[tools.terraform-docs."platforms.linux-arm64-musl"]
checksum = "sha256:d12bd7b73c1fc9c64efc79f8157dd713dabd559f1ecf3cfc0f42e32279a155fd"
url = "https://github.com/terraform-docs/terraform-docs/releases/download/v0.24.0/terraform-docs-v0.24.0-linux-arm64.tar.gz"
+url_api = "https://api.github.com/repos/terraform-docs/terraform-docs/releases/assets/416956212"
[tools.terraform-docs."platforms.linux-x64"]
checksum = "sha256:9005daf969de0b50134493a2c00078b49f5f5b39d021cda7c89bf4d4f3d776d3"
url = "https://github.com/terraform-docs/terraform-docs/releases/download/v0.24.0/terraform-docs-v0.24.0-linux-amd64.tar.gz"
+url_api = "https://api.github.com/repos/terraform-docs/terraform-docs/releases/assets/416956208"
[tools.terraform-docs."platforms.linux-x64-musl"]
checksum = "sha256:9005daf969de0b50134493a2c00078b49f5f5b39d021cda7c89bf4d4f3d776d3"
url = "https://github.com/terraform-docs/terraform-docs/releases/download/v0.24.0/terraform-docs-v0.24.0-linux-amd64.tar.gz"
+url_api = "https://api.github.com/repos/terraform-docs/terraform-docs/releases/assets/416956208"
[tools.terraform-docs."platforms.macos-arm64"]
checksum = "sha256:f6b114f4b032f3f9202ab6c23bfd28c3c8e68aeeb8a8f12fc118bf2073081d71"
url = "https://github.com/terraform-docs/terraform-docs/releases/download/v0.24.0/terraform-docs-v0.24.0-darwin-arm64.tar.gz"
+url_api = "https://api.github.com/repos/terraform-docs/terraform-docs/releases/assets/416956237"
[tools.terraform-docs."platforms.macos-x64"]
checksum = "sha256:3c3f7f18f908457fd1209cbe341418f7f6bae78c08126cfbe8de0d1b06aa8781"
url = "https://github.com/terraform-docs/terraform-docs/releases/download/v0.24.0/terraform-docs-v0.24.0-darwin-amd64.tar.gz"
+url_api = "https://api.github.com/repos/terraform-docs/terraform-docs/releases/assets/416956256"
[tools.terraform-docs."platforms.windows-x64"]
checksum = "sha256:afc02cbdf63726d3e5fb26a077ca1f24acd4820d7e44c21b03a3e030f9266490"
url = "https://github.com/terraform-docs/terraform-docs/releases/download/v0.24.0/terraform-docs-v0.24.0-windows-amd64.zip"
+url_api = "https://api.github.com/repos/terraform-docs/terraform-docs/releases/assets/416956245"
[[tools.tflint]]
version = "0.62.1"
@@ -478,36 +487,43 @@ backend = "aqua:terraform-linters/tflint"
[tools.tflint."platforms.linux-arm64"]
checksum = "sha256:9f3bce43f7f58f05ddcf193f0bf1f7e7a9c7a79d7f46f72dd38e97f96fcaf14c"
url = "https://github.com/terraform-linters/tflint/releases/download/v0.62.1/tflint_linux_arm64.zip"
+url_api = "https://api.github.com/repos/terraform-linters/tflint/releases/assets/419448758"
provenance = "github-attestations"
[tools.tflint."platforms.linux-arm64-musl"]
checksum = "sha256:9f3bce43f7f58f05ddcf193f0bf1f7e7a9c7a79d7f46f72dd38e97f96fcaf14c"
url = "https://github.com/terraform-linters/tflint/releases/download/v0.62.1/tflint_linux_arm64.zip"
+url_api = "https://api.github.com/repos/terraform-linters/tflint/releases/assets/419448758"
provenance = "github-attestations"
[tools.tflint."platforms.linux-x64"]
checksum = "sha256:c004ec45ade3caf87cd4089feb1d2af9f7df57b13140a36df8a63c0a8cc69f14"
url = "https://github.com/terraform-linters/tflint/releases/download/v0.62.1/tflint_linux_amd64.zip"
+url_api = "https://api.github.com/repos/terraform-linters/tflint/releases/assets/419448794"
provenance = "github-attestations"
[tools.tflint."platforms.linux-x64-musl"]
checksum = "sha256:c004ec45ade3caf87cd4089feb1d2af9f7df57b13140a36df8a63c0a8cc69f14"
url = "https://github.com/terraform-linters/tflint/releases/download/v0.62.1/tflint_linux_amd64.zip"
+url_api = "https://api.github.com/repos/terraform-linters/tflint/releases/assets/419448794"
provenance = "github-attestations"
[tools.tflint."platforms.macos-arm64"]
checksum = "sha256:927866fef68382138b8fed038721a03c0928ce9486e1616b18bd3dd11e7cdacb"
url = "https://github.com/terraform-linters/tflint/releases/download/v0.62.1/tflint_darwin_arm64.zip"
+url_api = "https://api.github.com/repos/terraform-linters/tflint/releases/assets/419448760"
provenance = "github-attestations"
[tools.tflint."platforms.macos-x64"]
checksum = "sha256:7f55df3a25deb610c267f600eb4247657e3ff776d0322916ceecd7b58142a73a"
url = "https://github.com/terraform-linters/tflint/releases/download/v0.62.1/tflint_darwin_amd64.zip"
+url_api = "https://api.github.com/repos/terraform-linters/tflint/releases/assets/419448796"
provenance = "github-attestations"
[tools.tflint."platforms.windows-x64"]
checksum = "sha256:bef472fd62dcbdb616bbdf67800f1bcf8db64598529c09610154686555180edd"
url = "https://github.com/terraform-linters/tflint/releases/download/v0.62.1/tflint_windows_amd64.zip"
+url_api = "https://api.github.com/repos/terraform-linters/tflint/releases/assets/419448795"
provenance = "github-attestations"
[[tools.vale]]
@@ -517,30 +533,37 @@ backend = "aqua:vale-cli/vale"
[tools.vale."platforms.linux-arm64"]
checksum = "sha256:45295ab07fd99c87a9cac20dce5d230d7b63ffec1394b38c8d5d4496347a069b"
url = "https://github.com/vale-cli/vale/releases/download/v3.6.0/vale_3.6.0_Linux_arm64.tar.gz"
+url_api = "https://api.github.com/repos/vale-cli/vale/releases/assets/174588938"
[tools.vale."platforms.linux-arm64-musl"]
checksum = "sha256:45295ab07fd99c87a9cac20dce5d230d7b63ffec1394b38c8d5d4496347a069b"
url = "https://github.com/vale-cli/vale/releases/download/v3.6.0/vale_3.6.0_Linux_arm64.tar.gz"
+url_api = "https://api.github.com/repos/vale-cli/vale/releases/assets/174588938"
[tools.vale."platforms.linux-x64"]
checksum = "sha256:d27cd03f920202a7be5bba3fd9e784a22fe8b33f838911853588ce3ce594a25d"
url = "https://github.com/vale-cli/vale/releases/download/v3.6.0/vale_3.6.0_Linux_64-bit.tar.gz"
+url_api = "https://api.github.com/repos/vale-cli/vale/releases/assets/174588941"
[tools.vale."platforms.linux-x64-musl"]
checksum = "sha256:d27cd03f920202a7be5bba3fd9e784a22fe8b33f838911853588ce3ce594a25d"
url = "https://github.com/vale-cli/vale/releases/download/v3.6.0/vale_3.6.0_Linux_64-bit.tar.gz"
+url_api = "https://api.github.com/repos/vale-cli/vale/releases/assets/174588941"
[tools.vale."platforms.macos-arm64"]
checksum = "sha256:df71e5fcbefca9413479737fa946e105249ec73e2a328ef1784e5558d92414b0"
url = "https://github.com/vale-cli/vale/releases/download/v3.6.0/vale_3.6.0_macOS_arm64.tar.gz"
+url_api = "https://api.github.com/repos/vale-cli/vale/releases/assets/174588939"
[tools.vale."platforms.macos-x64"]
checksum = "sha256:4d1df61fc3b1e4109bd965b478dad6cb8998e2ca7836100b936c16c331c8c9f2"
url = "https://github.com/vale-cli/vale/releases/download/v3.6.0/vale_3.6.0_macOS_64-bit.tar.gz"
+url_api = "https://api.github.com/repos/vale-cli/vale/releases/assets/174588940"
[tools.vale."platforms.windows-x64"]
checksum = "sha256:6b205cd9ee4df016d42287cd0b2b8f234acb45900fc371cda97a5c49bc69b350"
url = "https://github.com/vale-cli/vale/releases/download/v3.6.0/vale_3.6.0_Windows_64-bit.zip"
+url_api = "https://api.github.com/repos/vale-cli/vale/releases/assets/174588942"
[[tools.yq]]
version = "4.53.3"
@@ -549,27 +572,41 @@ backend = "aqua:mikefarah/yq"
[tools.yq."platforms.linux-arm64"]
checksum = "sha256:578648e463a11c1b6db6010cbf41eafed6bee79466fcffa1bb446672cf7945ea"
url = "https://github.com/mikefarah/yq/releases/download/v4.53.3/yq_linux_arm64"
+url_api = "https://api.github.com/repos/mikefarah/yq/releases/assets/440146727"
+provenance = "cosign"
[tools.yq."platforms.linux-arm64-musl"]
checksum = "sha256:578648e463a11c1b6db6010cbf41eafed6bee79466fcffa1bb446672cf7945ea"
url = "https://github.com/mikefarah/yq/releases/download/v4.53.3/yq_linux_arm64"
+url_api = "https://api.github.com/repos/mikefarah/yq/releases/assets/440146727"
+provenance = "cosign"
[tools.yq."platforms.linux-x64"]
checksum = "sha256:fa52a4e758c63d38299163fbdd1edfb4c4963247918bf9c1c5d31d84789eded4"
url = "https://github.com/mikefarah/yq/releases/download/v4.53.3/yq_linux_amd64"
+url_api = "https://api.github.com/repos/mikefarah/yq/releases/assets/440146735"
+provenance = "cosign"
[tools.yq."platforms.linux-x64-musl"]
checksum = "sha256:fa52a4e758c63d38299163fbdd1edfb4c4963247918bf9c1c5d31d84789eded4"
url = "https://github.com/mikefarah/yq/releases/download/v4.53.3/yq_linux_amd64"
+url_api = "https://api.github.com/repos/mikefarah/yq/releases/assets/440146735"
+provenance = "cosign"
[tools.yq."platforms.macos-arm64"]
-checksum = "sha256:877de31753a4dd2401aa048937aa9a7fc4d5f6ce858cf31508c5802954297213"
-url = "https://github.com/mikefarah/yq/releases/download/v4.53.3/yq_darwin_arm64"
+checksum = "sha256:b4ba1ecce3c47f00803f4f964de38394326c7a32eb6540616e04fb2935a0f08d"
+url = "https://github.com/mikefarah/yq/releases/download/v4.53.3/yq_darwin_amd64"
+url_api = "https://api.github.com/repos/mikefarah/yq/releases/assets/440146744"
+provenance = "cosign"
[tools.yq."platforms.macos-x64"]
checksum = "sha256:b4ba1ecce3c47f00803f4f964de38394326c7a32eb6540616e04fb2935a0f08d"
url = "https://github.com/mikefarah/yq/releases/download/v4.53.3/yq_darwin_amd64"
+url_api = "https://api.github.com/repos/mikefarah/yq/releases/assets/440146744"
+provenance = "cosign"
[tools.yq."platforms.windows-x64"]
checksum = "sha256:e279bc506a452eeafcdf364f91a025455e402a8001169083caf01f4b64a544e2"
url = "https://github.com/mikefarah/yq/releases/download/v4.53.3/yq_windows_amd64.exe"
+url_api = "https://api.github.com/repos/mikefarah/yq/releases/assets/440146721"
+provenance = "cosign"
diff --git a/scripts/config/generate-available-modules.yaml b/scripts/config/generate-available-modules.yaml
index 05111041..2913a4f1 100644
--- a/scripts/config/generate-available-modules.yaml
+++ b/scripts/config/generate-available-modules.yaml
@@ -69,6 +69,10 @@ ecs-service:
description: "ECS service and task definition"
wraps: "terraform-aws-modules/ecs/aws//modules/service"
+efs:
+ description: "EFS file system with access points and mount targets"
+ wraps: "terraform-aws-modules/efs/aws"
+
elasticache:
description: "ElastiCache cluster (Redis/Memcached)"
wraps: "—"