diff --git a/.github/workflows/terraform.yml b/.github/workflows/terraform.yml index 059be32d9c..211f2bf37c 100644 --- a/.github/workflows/terraform.yml +++ b/.github/workflows/terraform.yml @@ -87,6 +87,7 @@ jobs: "multi-runner", "compute-providers/aws/microvm", "compute-providers/aws/microvm/trust-policy", + "microvm-foundation", "runner-binaries-syncer", "runners", "setup-iam-permissions", @@ -158,7 +159,8 @@ jobs: "termination-watcher", "multi-runner", "multi-runner-v2", - "external-managed-ssm-secrets" + "external-managed-ssm-secrets", + "microvm-foundation" ] defaults: run: diff --git a/docs/examples/index.md b/docs/examples/index.md index f0558966bd..b7bdf60811 100644 --- a/docs/examples/index.md +++ b/docs/examples/index.md @@ -11,3 +11,5 @@ Examples are located in the [examples](https://github.com/github-aws-runners/ter - _[Termination watcher](termination-watcher.md)_: Example usages of termination watcher. - _[Dedicated Mac Hosts](dedicated-mac-hosts.md)_: Example usage of setting up dedicated hosts for macOS runners. - _[Externally managed SSM secrets](external-managed-ssm-secrets.md)_: Example usage of externally managed SSM secrets for the GitHub App credentials. +- _[MicroVM foundation](microvm-foundation.md)_: Example usage of the regional Lambda MicroVM image-build and Network Connector prerequisites. +- _[Lambda MicroVM](microvm.md)_: Example usage of Linux ARM64 ephemeral runners backed by Lambda MicroVM images. diff --git a/docs/examples/microvm-foundation.md b/docs/examples/microvm-foundation.md new file mode 100644 index 0000000000..b92a148a9c --- /dev/null +++ b/docs/examples/microvm-foundation.md @@ -0,0 +1,3 @@ +# Lambda MicroVM foundation + +--8<-- "examples/microvm-foundation/README.md" diff --git a/docs/modules/public/microvm-foundation.md b/docs/modules/public/microvm-foundation.md new file mode 100644 index 0000000000..17129c131e --- /dev/null +++ b/docs/modules/public/microvm-foundation.md @@ -0,0 +1,3 @@ +# Lambda MicroVM regional foundation + +--8<-- "modules/microvm-foundation/README.md" diff --git a/examples/microvm-foundation/.terraform.lock.hcl b/examples/microvm-foundation/.terraform.lock.hcl new file mode 100644 index 0000000000..2fc14d932b --- /dev/null +++ b/examples/microvm-foundation/.terraform.lock.hcl @@ -0,0 +1,47 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.63.0" + constraints = ">= 6.61.0" + hashes = [ + "h1:dRlYHkc+r6fgzF57WC7Zjcmb6sF/6TTGDEgwGK+LAZY=", + "zh:005d56736afd17d963998c405cee6f434dbc23a415109f9435ff1542879ae611", + "zh:026ef126321a86ad7080b5d858e2527f96f5289678cbcd8856296e229c43339d", + "zh:06e0b58b2d1eddb5137fc86bee7ad2d07953c0bc3f57cccfc5ae0d2456068a3a", + "zh:07221735d61ababed84734e5ffcfc5bd59d01f29f029166ba5f2175895dceed1", + "zh:1a72db00583112bdb8c19b213a78a3f5de754fffc08f07e061f4e326289fab7d", + "zh:32968e74a53b03e97a084dc7050c22ef661fb5b3ea8a44f5a63e47bc45ad0e7c", + "zh:4b357dfe4b820e3e4acd2881cff8288b2186491e63416751f0d12692ba478ceb", + "zh:81e30884d7de686265e7d87bb92527e802878c65a378470ede2a1e9f4e40ccc9", + "zh:82e137297f6a5a08b9ce2138f7aabea245ad99495d9d9eff502f752d6ca90dbd", + "zh:8eb83b67099f0ea9df238a979dff933ff50ce06a2e3ff05a48556a10f10dd204", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:d0ba30886cbe41850fee689f51ef9088578f323cfd21817bb409951d43c465eb", + "zh:dd48e7089784454bc03d713e9057f5ca0ea1613bd402125054a51894957b7925", + "zh:f250fa81e54cf60fcb0e9c0fc4ac043f1ecc2ac24967f628b3609364fcab3d04", + "zh:f38fc09fc25a8d2cf89a4d4cd6a5ef7cb1aad72798dbdcad58b8876b6a551a54", + "zh:f7c7380fdf126e1901f2084588dbfd724c76cb131ccfa795a541219111103c06", + ] +} + +provider "registry.terraform.io/hashicorp/time" { + version = "0.14.1" + constraints = ">= 0.13.0" + hashes = [ + "h1:r93SxP++6gUlwCHDQ5OkRmcU8B0yv6ZA9nF0Dh6NJmA=", + "zh:0837ca5b057e5cff94dff7de2fcccafb4abaa33c45de193fe2853e684818a267", + "zh:15a122f72d9e0f34fc5384cc7ec089319641fee5c319748a3aa02fc42f459969", + "zh:342fb83093a280ea7ee0654feae1f5867c62eb8eebc1ab46f9a7ab0b4c878a62", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:99f169834d3370b8341381c6a9c7a8b01fb26027531faa38e6fb49cc23916f68", + "zh:9f482917c7a28cf2436578be7aa9f04f8c811aba8b5949e0223ea987a2757a91", + "zh:ac6b5b8732826f2d1129a8a4a038ac7a7a9ca7b77d2a4608e5703be1a1e2bff0", + "zh:c54782a27d58ce04f6696c6fc0b2cf1e2fba6bed239fb520521a7bce7d7193cb", + "zh:c8d0ddc8f575ecb44f025d54edbfe118e26397fe328a67be62325766f31eb6e7", + "zh:d043b96f204edd2353bf6b2a34e645ffdee2e9634d9bb747331320444810a538", + "zh:e32c288501ca9a6c9d22b52e839dd391fc7083d54ee6b8dc296ce0e6bd3e57ef", + "zh:e47fcc7bb4e9ab5cc522c3b06e4fa9c0bf94b84be8210bc6b1655c44acb2addc", + "zh:f61bf218322bcbe0bd2d56bba738e7fa485e9b54244e13aa12de741b37d450c0", + ] +} diff --git a/examples/microvm-foundation/README.md b/examples/microvm-foundation/README.md new file mode 100644 index 0000000000..aa54e94fa0 --- /dev/null +++ b/examples/microvm-foundation/README.md @@ -0,0 +1,77 @@ +# MicroVM foundation example + +This example creates the regional dependencies required by the Lambda MicroVM +image build and runner runtime using the reusable module in this repository. + +Set real VPC and subnet IDs in `terraform.tfvars` (copy +`terraform.tfvars.example`). The module validates that every selected subnet +belongs to its configured VPC. + +```bash +terraform init +terraform apply +terraform output +``` + +Apply this foundation before building an image with the direct Packer commands +documented in `../../images/microvm/README.md`. Use the outputs as the build inputs: + +- `artifact_bucket_name` -> `MICROVM_ARTIFACT_BUCKET` +- `build_role_arn` -> `MICROVM_BUILD_ROLE_ARN` +- `connector_arns.cicd` -> `MICROVM_EGRESS_NETWORK_CONNECTOR_ARN` +- `usage_policy_arn` -> attach to the control-plane role used by the runner example + +The foundation module owns regional storage, build IAM, Network Connectors, +and the reusable runtime policy. It does not publish an image or create the +runner control plane; those steps remain explicit and can be performed after +the foundation is available. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.4.0 | +| [aws](#requirement\_aws) | >= 6.61 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [microvm\_foundation](#module\_microvm\_foundation) | ../../modules/microvm-foundation | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [artifact\_bucket\_name](#input\_artifact\_bucket\_name) | Optional globally unique S3 bucket name. When null, AWS generates the bucket name. | `string` | `null` | no | +| [artifact\_retention\_days](#input\_artifact\_retention\_days) | Number of days to retain current and noncurrent build artifacts. | `number` | `30` | no | +| [aws\_profile](#input\_aws\_profile) | Optional local AWS CLI profile. Leave null when credentials are provided by the environment or role. | `string` | `null` | no | +| [aws\_region](#input\_aws\_region) | AWS region in which to create the MicroVM foundation. | `string` | `"eu-west-1"` | no | +| [build\_policy\_name\_prefix](#input\_build\_policy\_name\_prefix) | Name prefix for the Lambda MicroVM build policy. | `string` | `"github-actions-runner-microvm-build-policy-"` | no | +| [build\_role\_name\_prefix](#input\_build\_role\_name\_prefix) | Name prefix for the Lambda MicroVM build role. | `string` | `"github-actions-runner-microvm-build-"` | no | +| [ecr\_repository\_arns](#input\_ecr\_repository\_arns) | Optional private ECR repository ARNs used by the image build. | `set(string)` | `[]` | no | +| [image\_name\_prefix](#input\_image\_name\_prefix) | Reserved Lambda MicroVM image-name namespace used by the runtime policy. | `string` | `"github-actions-runner-ubuntu-arm64"` | no | +| [network\_connector\_operator\_role\_name\_prefix](#input\_network\_connector\_operator\_role\_name\_prefix) | Name prefix for the Lambda Network Connector operator role. | `string` | `"github-actions-runner-microvm-network-operator-"` | no | +| [network\_connectors](#input\_network\_connectors) | VPC and subnet configuration for regional Lambda MicroVM egress connectors. |
map(object({
name = string
vpc_id = string
subnet_ids = set(string)
network_protocol = optional(string, "IPv4")
})) | n/a | yes |
+| [tags](#input\_tags) | Additional tags applied by the foundation module. | `map(string)` | {
"Component": "microvm-foundation"
} | no |
+| [usage\_policy\_name\_prefix](#input\_usage\_policy\_name\_prefix) | Name prefix for the Lambda MicroVM runtime usage policy. | `string` | `"github-actions-runner-microvm-runtime-usage-policy-"` | no |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [artifact\_bucket\_name](#output\_artifact\_bucket\_name) | S3 bucket to pass to the MicroVM image build. |
+| [artifact\_prefix](#output\_artifact\_prefix) | S3 prefix used for MicroVM build artifacts. |
+| [build\_role\_arn](#output\_build\_role\_arn) | Lambda build role ARN to pass to the image build. |
+| [connector\_arns](#output\_connector\_arns) | Regional Network Connector ARNs keyed by configuration name. |
+| [usage\_policy\_arn](#output\_usage\_policy\_arn) | Unattached runtime usage policy for the runner control-plane role. |
+
diff --git a/examples/microvm-foundation/main.tf b/examples/microvm-foundation/main.tf
new file mode 100644
index 0000000000..27ed22c816
--- /dev/null
+++ b/examples/microvm-foundation/main.tf
@@ -0,0 +1,15 @@
+module "microvm_foundation" {
+ source = "../../modules/microvm-foundation"
+
+ aws_region = var.aws_region
+ tags = var.tags
+ build_policy_name_prefix = var.build_policy_name_prefix
+ build_role_name_prefix = var.build_role_name_prefix
+ network_connector_operator_role_name_prefix = var.network_connector_operator_role_name_prefix
+ usage_policy_name_prefix = var.usage_policy_name_prefix
+ artifact_bucket_name = var.artifact_bucket_name
+ artifact_retention_days = var.artifact_retention_days
+ image_name_prefix = var.image_name_prefix
+ ecr_repository_arns = var.ecr_repository_arns
+ network_connectors = var.network_connectors
+}
diff --git a/examples/microvm-foundation/outputs.tf b/examples/microvm-foundation/outputs.tf
new file mode 100644
index 0000000000..709d43f933
--- /dev/null
+++ b/examples/microvm-foundation/outputs.tf
@@ -0,0 +1,24 @@
+output "artifact_bucket_name" {
+ description = "S3 bucket to pass to the MicroVM image build."
+ value = module.microvm_foundation.artifact_bucket_name
+}
+
+output "artifact_prefix" {
+ description = "S3 prefix used for MicroVM build artifacts."
+ value = module.microvm_foundation.artifact_prefix
+}
+
+output "build_role_arn" {
+ description = "Lambda build role ARN to pass to the image build."
+ value = module.microvm_foundation.build_role_arn
+}
+
+output "connector_arns" {
+ description = "Regional Network Connector ARNs keyed by configuration name."
+ value = module.microvm_foundation.connector_arns
+}
+
+output "usage_policy_arn" {
+ description = "Unattached runtime usage policy for the runner control-plane role."
+ value = module.microvm_foundation.usage_policy_arn
+}
diff --git a/examples/microvm-foundation/providers.tf b/examples/microvm-foundation/providers.tf
new file mode 100644
index 0000000000..9e8a8a7627
--- /dev/null
+++ b/examples/microvm-foundation/providers.tf
@@ -0,0 +1,4 @@
+provider "aws" {
+ region = var.aws_region
+ profile = var.aws_profile
+}
diff --git a/examples/microvm-foundation/terraform.tfvars.example b/examples/microvm-foundation/terraform.tfvars.example
new file mode 100644
index 0000000000..ef864c384c
--- /dev/null
+++ b/examples/microvm-foundation/terraform.tfvars.example
@@ -0,0 +1,15 @@
+aws_region = "eu-west-1"
+
+network_connectors = {
+ cicd = {
+ name = "github-actions-runner-egress"
+ vpc_id = "vpc-0123456789abcdef0"
+ subnet_ids = ["subnet-0123456789abcdef0", "subnet-0fedcba9876543210"]
+ }
+}
+
+# Add the private ECR repository that contains the regional Ubuntu base image
+# when the image build pulls from ECR.
+# ecr_repository_arns = [
+# "arn:aws:ecr:eu-west-1:123456789012:repository/actions-runner-base-image",
+# ]
diff --git a/examples/microvm-foundation/variables.tf b/examples/microvm-foundation/variables.tf
new file mode 100644
index 0000000000..4afd3804a5
--- /dev/null
+++ b/examples/microvm-foundation/variables.tf
@@ -0,0 +1,79 @@
+variable "aws_profile" {
+ type = string
+ description = "Optional local AWS CLI profile. Leave null when credentials are provided by the environment or role."
+ default = null
+ nullable = true
+}
+
+variable "aws_region" {
+ type = string
+ description = "AWS region in which to create the MicroVM foundation."
+ default = "eu-west-1"
+}
+
+variable "tags" {
+ type = map(string)
+ description = "Additional tags applied by the foundation module."
+ default = {
+ Component = "microvm-foundation"
+ }
+}
+
+variable "build_policy_name_prefix" {
+ type = string
+ description = "Name prefix for the Lambda MicroVM build policy."
+ default = "github-actions-runner-microvm-build-policy-"
+}
+
+variable "usage_policy_name_prefix" {
+ type = string
+ description = "Name prefix for the Lambda MicroVM runtime usage policy."
+ default = "github-actions-runner-microvm-runtime-usage-policy-"
+}
+
+variable "build_role_name_prefix" {
+ type = string
+ description = "Name prefix for the Lambda MicroVM build role."
+ default = "github-actions-runner-microvm-build-"
+}
+
+variable "network_connector_operator_role_name_prefix" {
+ type = string
+ description = "Name prefix for the Lambda Network Connector operator role."
+ default = "github-actions-runner-microvm-network-operator-"
+}
+
+variable "artifact_bucket_name" {
+ type = string
+ description = "Optional globally unique S3 bucket name. When null, AWS generates the bucket name."
+ default = null
+ nullable = true
+}
+
+variable "artifact_retention_days" {
+ type = number
+ description = "Number of days to retain current and noncurrent build artifacts."
+ default = 30
+}
+
+variable "image_name_prefix" {
+ type = string
+ description = "Reserved Lambda MicroVM image-name namespace used by the runtime policy."
+ default = "github-actions-runner-ubuntu-arm64"
+}
+
+variable "ecr_repository_arns" {
+ type = set(string)
+ description = "Optional private ECR repository ARNs used by the image build."
+ default = []
+}
+
+variable "network_connectors" {
+ type = map(object({
+ name = string
+ vpc_id = string
+ subnet_ids = set(string)
+ network_protocol = optional(string, "IPv4")
+ }))
+ description = "VPC and subnet configuration for regional Lambda MicroVM egress connectors."
+}
diff --git a/examples/microvm-foundation/versions.tf b/examples/microvm-foundation/versions.tf
new file mode 100644
index 0000000000..e72a26b153
--- /dev/null
+++ b/examples/microvm-foundation/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.4.0"
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 6.61"
+ }
+ }
+}
diff --git a/mkdocs.yaml b/mkdocs.yaml
index 1b190a4a67..4f34f85a49 100644
--- a/mkdocs.yaml
+++ b/mkdocs.yaml
@@ -65,6 +65,7 @@ nav:
- AMI Housekeeper: modules/public/ami-housekeeper.md
- Lambda Downloader: modules/public/download-lambda.md
- Setup IAM permissions: modules/public/setup-iam-permissions.md
+ - MicroVM foundation: modules/public/microvm-foundation.md
- Submodules (internal):
- Runners: modules/internal/runners.md
- Syncer: modules/internal/runner-binaries-syncer.md
diff --git a/modules/microvm-foundation/README.md b/modules/microvm-foundation/README.md
new file mode 100644
index 0000000000..fadbedc0f7
--- /dev/null
+++ b/modules/microvm-foundation/README.md
@@ -0,0 +1,134 @@
+# Lambda MicroVM Regional Foundation
+
+This module creates the regional AWS prerequisites for building and running
+Lambda MicroVM GitHub Actions runners and is intended to be deployed once per
+AWS Region.
+
+It manages:
+
+- A private, encrypted, versioned S3 bucket for content-addressed image build artifacts.
+- A Lambda-trusted build role with scoped S3, CloudWatch Logs, and optional ECR pull access.
+- Dedicated no-ingress security groups and native Lambda Network Connector resources for each configured VPC/subnet set.
+- A Lambda-trusted Network Connector operator role and propagation barrier.
+- An unattached runtime usage policy for the reserved image namespace and connector inventory.
+
+The module does not create MicroVM images, runner execution roles, or the
+runner control plane. Attach `usage_policy_arn` to the control-plane role that
+owns the runtime launch operations. The caller must also grant the Terraform
+identity `iam:PassRole` for the operator role with
+`iam:PassedToService=lambda.amazonaws.com`.
+
+The module deliberately does not configure an AWS provider. Configure the
+provider in the root module or example so credentials and account selection
+remain caller-owned.
+
+```hcl
+provider "aws" {
+ region = "eu-west-1"
+}
+
+module "microvm_foundation" {
+ source = "../../modules/microvm-foundation"
+
+ aws_region = "eu-west-1"
+ tags = { Environment = "example" }
+ build_policy_name_prefix = "github-actions-runner-microvm-build-policy-"
+ build_role_name_prefix = "github-actions-runner-microvm-build-"
+ network_connector_operator_role_name_prefix = "github-actions-runner-microvm-network-operator-"
+ usage_policy_name_prefix = "github-actions-runner-microvm-runtime-usage-policy-"
+
+ image_name_prefix = "github-actions-runner-ubuntu-arm64"
+
+ network_connectors = {
+ cicd = {
+ name = "github-actions-runner-egress"
+ vpc_id = "vpc-0123456789abcdef0"
+ subnet_ids = ["subnet-0123456789abcdef0"]
+ }
+ }
+}
+```
+
+The companion `examples/microvm-foundation` directory is a complete setup
+example. Apply it before following the direct Packer build instructions in
+`images/microvm/README.md` or using the `examples/microvm` runner example.
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | >= 1.4.0 |
+| [aws](#requirement\_aws) | >= 6.61 |
+| [time](#requirement\_time) | >= 0.13 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | >= 6.61 |
+| [time](#provider\_time) | >= 0.13 |
+
+## Modules
+
+No modules.
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_iam_policy.build](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_policy.usage](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
+| [aws_iam_role.build](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+| [aws_iam_role.operator](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+| [aws_iam_role_policy_attachment.build](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+| [aws_iam_role_policy_attachment.operator](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
+| [aws_lambdacore_network_connector.connector](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambdacore_network_connector) | resource |
+| [aws_s3_bucket.artifacts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
+| [aws_s3_bucket_lifecycle_configuration.artifacts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource |
+| [aws_s3_bucket_ownership_controls.artifacts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource |
+| [aws_s3_bucket_policy.artifacts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
+| [aws_s3_bucket_public_access_block.artifacts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
+| [aws_s3_bucket_server_side_encryption_configuration.artifacts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource |
+| [aws_s3_bucket_versioning.artifacts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource |
+| [aws_security_group.connector](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
+| [aws_vpc_security_group_egress_rule.ipv4](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource |
+| [aws_vpc_security_group_egress_rule.ipv6](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource |
+| [time_sleep.operator_role_propagation](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource |
+| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
+| [aws_iam_policy_document.artifact_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.build](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.lambda_service_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.network_connector_assume_operator_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.usage](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source |
+| [aws_subnet.selected](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet) | data source |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [artifact\_bucket\_name](#input\_artifact\_bucket\_name) | Optional name for the regional MicroVM build-artifact bucket. When null, AWS generates the bucket name. | `string` | `null` | no |
+| [artifact\_retention\_days](#input\_artifact\_retention\_days) | Number of days to retain current and noncurrent MicroVM build artifacts. | `number` | `30` | no |
+| [aws\_region](#input\_aws\_region) | AWS region in which to create the Lambda MicroVM prerequisites. | `string` | n/a | yes |
+| [build\_policy\_name\_prefix](#input\_build\_policy\_name\_prefix) | Name prefix for the Lambda MicroVM build policy. | `string` | n/a | yes |
+| [build\_role\_name\_prefix](#input\_build\_role\_name\_prefix) | Name prefix for the Lambda MicroVM build role. | `string` | n/a | yes |
+| [ecr\_repository\_arns](#input\_ecr\_repository\_arns) | Optional regional ECR repository ARNs from which MicroVM image builds can pull runner base images. | `set(string)` | `[]` | no |
+| [image\_name\_prefix](#input\_image\_name\_prefix) | IAM namespace prefix reserved for externally published Lambda MicroVM image names. This module does not create or enumerate images. | `string` | n/a | yes |
+| [network\_connector\_operator\_role\_name\_prefix](#input\_network\_connector\_operator\_role\_name\_prefix) | Name prefix for the Lambda Network Connector operator role. | `string` | n/a | yes |
+| [network\_connectors](#input\_network\_connectors) | Regional Lambda MicroVM Network Connectors keyed by a stable consumer-defined identity. | map(object({
name = string
vpc_id = string
subnet_ids = set(string)
network_protocol = optional(string, "IPv4")
})) | n/a | yes |
+| [tags](#input\_tags) | A map of module-specific tags to apply to resources. | `map(string)` | n/a | yes |
+| [usage\_policy\_name\_prefix](#input\_usage\_policy\_name\_prefix) | Name prefix for the Lambda MicroVM runtime usage policy. | `string` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [artifact\_bucket\_arn](#output\_artifact\_bucket\_arn) | ARN of the regional S3 bucket used for Lambda MicroVM build artifacts. |
+| [artifact\_bucket\_name](#output\_artifact\_bucket\_name) | Name of the regional S3 bucket used for Lambda MicroVM build artifacts. |
+| [artifact\_prefix](#output\_artifact\_prefix) | Bucket prefix to which the MicroVM image publisher uploads content-addressed build artifacts. |
+| [build\_role\_arn](#output\_build\_role\_arn) | ARN of the Lambda-trusted role used during MicroVM image builds. |
+| [connector\_arns](#output\_connector\_arns) | Map of connector key to the ARN of each Lambda Network Connector. |
+| [security\_group\_ids](#output\_security\_group\_ids) | Map of connector key to its dedicated no-ingress security group ID. |
+| [usage\_policy\_arn](#output\_usage\_policy\_arn) | ARN of the reusable regional policy for operating MicroVM images in the reserved namespace and passing their Network Connectors. |
+
diff --git a/modules/microvm-foundation/build.tf b/modules/microvm-foundation/build.tf
new file mode 100644
index 0000000000..160f3d9399
--- /dev/null
+++ b/modules/microvm-foundation/build.tf
@@ -0,0 +1,84 @@
+# Lambda assumes this role while building an image snapshot.
+data "aws_iam_policy_document" "build" {
+ statement {
+ sid = "ReadRegionalBuildArtifact"
+ effect = "Allow"
+ actions = ["s3:GetObject"]
+ resources = ["${aws_s3_bucket.artifacts.arn}/${local.artifact_prefix}/*"]
+ }
+
+ statement {
+ sid = "CreateMicrovmBuildLogGroups"
+ effect = "Allow"
+ actions = ["logs:CreateLogGroup"]
+ resources = [local.log_group_arn_pattern]
+ }
+
+ statement {
+ sid = "WriteMicrovmBuildLogs"
+ effect = "Allow"
+ actions = [
+ "logs:CreateLogStream",
+ "logs:PutLogEvents",
+ ]
+ resources = [local.log_stream_arn_pattern]
+ }
+
+ dynamic "statement" {
+ for_each = length(var.ecr_repository_arns) > 0 ? [true] : []
+ content {
+ sid = "AuthorizePrivateEcrPull"
+ effect = "Allow"
+ actions = ["ecr:GetAuthorizationToken"]
+ resources = ["*"]
+ }
+ }
+
+ dynamic "statement" {
+ for_each = length(var.ecr_repository_arns) > 0 ? [true] : []
+ content {
+ sid = "PullPrivateEcrImage"
+ effect = "Allow"
+ actions = [
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:BatchGetImage",
+ "ecr:GetDownloadUrlForLayer",
+ ]
+ resources = var.ecr_repository_arns
+ }
+ }
+}
+
+resource "aws_iam_policy" "build" {
+ name_prefix = var.build_policy_name_prefix
+ description = "Regional permissions used by Lambda while building MicroVM images."
+ policy = data.aws_iam_policy_document.build.json
+ tags = var.tags
+}
+
+resource "aws_iam_role_policy_attachment" "build" {
+ role = aws_iam_role.build.name
+ policy_arn = aws_iam_policy.build.arn
+}
+
+data "aws_iam_policy_document" "lambda_service_assume_role" {
+ statement {
+ sid = "LambdaMicrovmService"
+ effect = "Allow"
+ actions = [
+ "sts:AssumeRole",
+ "sts:TagSession",
+ ]
+
+ principals {
+ type = "Service"
+ identifiers = ["lambda.amazonaws.com"]
+ }
+ }
+}
+
+resource "aws_iam_role" "build" {
+ name_prefix = var.build_role_name_prefix
+ assume_role_policy = data.aws_iam_policy_document.lambda_service_assume_role.json
+ tags = var.tags
+}
diff --git a/modules/microvm-foundation/data.tf b/modules/microvm-foundation/data.tf
new file mode 100644
index 0000000000..dc90004cc6
--- /dev/null
+++ b/modules/microvm-foundation/data.tf
@@ -0,0 +1,31 @@
+data "aws_caller_identity" "current" {}
+
+data "aws_partition" "current" {}
+
+data "aws_subnet" "selected" {
+ for_each = local.network_connector_subnets
+ id = each.value.subnet_id
+}
+
+locals {
+ artifact_prefix = "lambda-microvms"
+
+ image_arn_pattern = "arn:${data.aws_partition.current.partition}:lambda:${var.aws_region}:${data.aws_caller_identity.current.account_id}:microvm-image:${var.image_name_prefix}-*"
+ log_group_arn_pattern = "arn:${data.aws_partition.current.partition}:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/microvms/${var.image_name_prefix}-*"
+ log_stream_arn_pattern = "${local.log_group_arn_pattern}:log-stream:*"
+
+ network_connector_subnets = merge({}, [
+ for connector_key, connector in var.network_connectors : {
+ for subnet_id in connector.subnet_ids :
+ "${connector_key}/${subnet_id}" => {
+ connector_key = connector_key
+ subnet_id = subnet_id
+ }
+ }
+ ]...)
+
+ connector_arns = {
+ for connector_key, connector in aws_lambdacore_network_connector.connector :
+ connector_key => connector.arn
+ }
+}
diff --git a/modules/microvm-foundation/network_connector.tf b/modules/microvm-foundation/network_connector.tf
new file mode 100644
index 0000000000..3e41d161b3
--- /dev/null
+++ b/modules/microvm-foundation/network_connector.tf
@@ -0,0 +1,31 @@
+resource "aws_lambdacore_network_connector" "connector" {
+ for_each = var.network_connectors
+
+ name = each.value.name
+ operator_role = aws_iam_role.operator.arn
+
+ configuration {
+ vpc_egress_configuration {
+ associated_compute_resource_types = ["MicroVm"]
+ network_protocol = each.value.network_protocol
+ security_group_ids = [aws_security_group.connector[each.key].id]
+ subnet_ids = sort(tolist(each.value.subnet_ids))
+ }
+ }
+
+ lifecycle {
+ precondition {
+ condition = alltrue([
+ for subnet_id in each.value.subnet_ids :
+ data.aws_subnet.selected["${each.key}/${subnet_id}"].vpc_id == each.value.vpc_id
+ ])
+ error_message = "Every subnet in network_connectors[${each.key}] must belong to its configured vpc_id."
+ }
+ }
+
+ depends_on = [
+ time_sleep.operator_role_propagation,
+ aws_vpc_security_group_egress_rule.ipv4,
+ aws_vpc_security_group_egress_rule.ipv6,
+ ]
+}
diff --git a/modules/microvm-foundation/network_connector_operator.tf b/modules/microvm-foundation/network_connector_operator.tf
new file mode 100644
index 0000000000..fc7d03fd74
--- /dev/null
+++ b/modules/microvm-foundation/network_connector_operator.tf
@@ -0,0 +1,41 @@
+data "aws_iam_policy_document" "network_connector_assume_operator_role" {
+ statement {
+ sid = "LambdaNetworkConnectorService"
+ effect = "Allow"
+ actions = ["sts:AssumeRole"]
+
+ principals {
+ type = "Service"
+ identifiers = ["network-connectors.lambda.amazonaws.com"]
+ }
+ }
+}
+
+resource "aws_iam_role" "operator" {
+ name_prefix = var.network_connector_operator_role_name_prefix
+ assume_role_policy = data.aws_iam_policy_document.network_connector_assume_operator_role.json
+ tags = var.tags
+}
+
+resource "aws_iam_role_policy_attachment" "operator" {
+ role = aws_iam_role.operator.name
+ policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AWSLambdaNetworkConnectorOperatorPolicy"
+}
+
+# IAM reports role and policy writes before they are consistently available to
+# Lambda. Wait before allowing the native Network Connector resource to create
+# any connector.
+resource "time_sleep" "operator_role_propagation" {
+ depends_on = [aws_iam_role_policy_attachment.operator]
+
+ create_duration = "30s"
+
+ triggers = {
+ operator_role_unique_id = aws_iam_role.operator.unique_id
+ operator_trust_policy_sha256 = sha256(aws_iam_role.operator.assume_role_policy)
+ }
+
+ lifecycle {
+ replace_triggered_by = [aws_iam_role_policy_attachment.operator]
+ }
+}
diff --git a/modules/microvm-foundation/network_connector_security_group.tf b/modules/microvm-foundation/network_connector_security_group.tf
new file mode 100644
index 0000000000..7d88e37d2d
--- /dev/null
+++ b/modules/microvm-foundation/network_connector_security_group.tf
@@ -0,0 +1,40 @@
+# A connector gets its own no-ingress security group. Route tables and NACLs on
+# the caller-selected subnets determine actual destinations reachable through
+# the IPv4 or dual-stack egress rules.
+resource "aws_security_group" "connector" {
+ #checkov:skip=CKV2_AWS_5:The security group is consumed by the Lambda Network Connector rather than by a Terraform-native ENI resource.
+ for_each = var.network_connectors
+
+ name = "microvm-${each.value.name}-${var.aws_region}"
+ description = "Outbound egress for the ${each.value.name} Lambda MicroVM Network Connector"
+ vpc_id = each.value.vpc_id
+
+ tags = merge(var.tags, {
+ Name = "microvm-${each.value.name}-${var.aws_region}"
+ })
+}
+
+resource "aws_vpc_security_group_egress_rule" "ipv4" {
+ #checkov:skip=CKV_AWS_382:The connector requires outbound access; subnet routes and NACLs provide the network destination boundary.
+ for_each = var.network_connectors
+
+ security_group_id = aws_security_group.connector[each.key].id
+ description = "Lambda MicroVM connector IPv4 egress"
+ ip_protocol = "-1"
+ cidr_ipv4 = "0.0.0.0/0"
+ tags = var.tags
+}
+
+resource "aws_vpc_security_group_egress_rule" "ipv6" {
+ #checkov:skip=CKV_AWS_382:Dual-stack connector egress is intentional; subnet routes and NACLs provide the network destination boundary.
+ for_each = {
+ for connector_key, connector in var.network_connectors :
+ connector_key => connector if connector.network_protocol == "DualStack"
+ }
+
+ security_group_id = aws_security_group.connector[each.key].id
+ description = "Lambda MicroVM connector IPv6 egress"
+ ip_protocol = "-1"
+ cidr_ipv6 = "::/0"
+ tags = var.tags
+}
diff --git a/modules/microvm-foundation/outputs.tf b/modules/microvm-foundation/outputs.tf
new file mode 100644
index 0000000000..9fd52ab4bf
--- /dev/null
+++ b/modules/microvm-foundation/outputs.tf
@@ -0,0 +1,34 @@
+output "artifact_bucket_name" {
+ description = "Name of the regional S3 bucket used for Lambda MicroVM build artifacts."
+ value = aws_s3_bucket.artifacts.id
+}
+
+output "artifact_bucket_arn" {
+ description = "ARN of the regional S3 bucket used for Lambda MicroVM build artifacts."
+ value = aws_s3_bucket.artifacts.arn
+}
+
+output "artifact_prefix" {
+ description = "Bucket prefix to which the MicroVM image publisher uploads content-addressed build artifacts."
+ value = local.artifact_prefix
+}
+
+output "build_role_arn" {
+ description = "ARN of the Lambda-trusted role used during MicroVM image builds."
+ value = aws_iam_role.build.arn
+}
+
+output "usage_policy_arn" {
+ description = "ARN of the reusable regional policy for operating MicroVM images in the reserved namespace and passing their Network Connectors."
+ value = aws_iam_policy.usage.arn
+}
+
+output "connector_arns" {
+ description = "Map of connector key to the ARN of each Lambda Network Connector."
+ value = local.connector_arns
+}
+
+output "security_group_ids" {
+ description = "Map of connector key to its dedicated no-ingress security group ID."
+ value = { for connector_key, security_group in aws_security_group.connector : connector_key => security_group.id }
+}
diff --git a/modules/microvm-foundation/storage.tf b/modules/microvm-foundation/storage.tf
new file mode 100644
index 0000000000..73ff52e172
--- /dev/null
+++ b/modules/microvm-foundation/storage.tf
@@ -0,0 +1,103 @@
+# Lambda MicroVM image source artifacts must be stored in an S3 bucket in the
+# same region as the image. A separate helper deployment owns the bucket in each
+# supported region.
+resource "aws_s3_bucket" "artifacts" {
+ #checkov:skip=CKV_AWS_145:SSE-S3 protects ephemeral content-addressed build inputs; this helper has no CMK artifact contract.
+ #checkov:skip=CKV_AWS_144:Lambda MicroVM builds require same-region artifacts, so this regional bucket intentionally has no cross-region replication.
+ #checkov:skip=CKV_AWS_18:CloudTrail records control-plane access and the bucket contains short-lived build inputs; separate S3 access logging is not required.
+ #checkov:skip=CKV2_AWS_62:The publisher uploads artifacts synchronously and no event-driven consumer requires S3 notifications.
+ bucket = var.artifact_bucket_name
+ tags = var.tags
+}
+
+resource "aws_s3_bucket_ownership_controls" "artifacts" {
+ bucket = aws_s3_bucket.artifacts.id
+
+ rule {
+ object_ownership = "BucketOwnerEnforced"
+ }
+}
+
+resource "aws_s3_bucket_versioning" "artifacts" {
+ bucket = aws_s3_bucket.artifacts.id
+
+ versioning_configuration {
+ status = "Enabled"
+ }
+}
+
+resource "aws_s3_bucket_server_side_encryption_configuration" "artifacts" {
+ bucket = aws_s3_bucket.artifacts.id
+
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = "AES256"
+ }
+ }
+}
+
+resource "aws_s3_bucket_public_access_block" "artifacts" {
+ bucket = aws_s3_bucket.artifacts.id
+ block_public_acls = true
+ block_public_policy = true
+ ignore_public_acls = true
+ restrict_public_buckets = true
+ skip_destroy = true
+}
+
+resource "aws_s3_bucket_lifecycle_configuration" "artifacts" {
+ bucket = aws_s3_bucket.artifacts.id
+
+ rule {
+ id = "expire-microvm-build-artifacts"
+
+ # The bucket is dedicated to MicroVM build artifacts, so lifecycle cleanup
+ # applies to every object, including abandoned uploads outside the expected
+ # publisher prefix.
+ filter {}
+
+ expiration {
+ days = var.artifact_retention_days
+ }
+
+ noncurrent_version_expiration {
+ noncurrent_days = var.artifact_retention_days
+ }
+
+ abort_incomplete_multipart_upload {
+ days_after_initiation = 7
+ }
+
+ status = "Enabled"
+ }
+
+ depends_on = [aws_s3_bucket_versioning.artifacts]
+}
+
+data "aws_iam_policy_document" "artifact_bucket" {
+ statement {
+ sid = "DenyInsecureTransport"
+ effect = "Deny"
+ actions = ["s3:*"]
+ resources = [
+ aws_s3_bucket.artifacts.arn,
+ "${aws_s3_bucket.artifacts.arn}/*",
+ ]
+
+ principals {
+ type = "*"
+ identifiers = ["*"]
+ }
+
+ condition {
+ test = "Bool"
+ variable = "aws:SecureTransport"
+ values = ["false"]
+ }
+ }
+}
+
+resource "aws_s3_bucket_policy" "artifacts" {
+ bucket = aws_s3_bucket.artifacts.id
+ policy = data.aws_iam_policy_document.artifact_bucket.json
+}
diff --git a/modules/microvm-foundation/usage_policy.tf b/modules/microvm-foundation/usage_policy.tf
new file mode 100644
index 0000000000..8abe004bee
--- /dev/null
+++ b/modules/microvm-foundation/usage_policy.tf
@@ -0,0 +1,58 @@
+# Consumer modules can attach this policy to a control-plane role they own.
+# This helper deliberately leaves the managed policy unattached.
+data "aws_iam_policy_document" "usage" {
+ statement {
+ sid = "UseConfiguredMicrovmImages"
+ effect = "Allow"
+ actions = [
+ "lambda:CreateMicrovmAuthToken",
+ "lambda:GetMicrovm",
+ "lambda:GetMicrovmImage",
+ "lambda:GetMicrovmImageVersion",
+ "lambda:ListMicrovmImageVersions",
+ "lambda:ResumeMicrovm",
+ "lambda:RunMicrovm",
+ "lambda:SuspendMicrovm",
+ "lambda:TerminateMicrovm",
+ ]
+ resources = [local.image_arn_pattern]
+ }
+
+ #checkov:skip=CKV_AWS_111:ListMicrovms and ListMicrovmImages do not support resource-level permissions.
+ #checkov:skip=CKV_AWS_356:Lambda MicroVM account-level list actions require Resource '*'.
+ statement {
+ sid = "DiscoverMicrovmRuntimeState"
+ effect = "Allow"
+ actions = [
+ "lambda:ListMicrovmImages",
+ "lambda:ListMicrovms",
+ ]
+ resources = ["*"]
+ }
+
+ statement {
+ sid = "ReadConfiguredNetworkConnectors"
+ effect = "Allow"
+ actions = ["lambda:GetNetworkConnector"]
+ resources = values(local.connector_arns)
+ }
+
+ #checkov:skip=CKV_AWS_111:PassNetworkConnector and ListNetworkConnectors do not support resource-level permissions.
+ #checkov:skip=CKV_AWS_356:Lambda requires Resource '*' for PassNetworkConnector and the account-level list operation.
+ statement {
+ sid = "PassAndDiscoverNetworkConnectors"
+ effect = "Allow"
+ actions = [
+ "lambda:ListNetworkConnectors",
+ "lambda:PassNetworkConnector",
+ ]
+ resources = ["*"]
+ }
+}
+
+resource "aws_iam_policy" "usage" {
+ name_prefix = var.usage_policy_name_prefix
+ description = "Permissions to discover and operate configured Lambda MicroVM images and to read and pass their regional Network Connectors."
+ policy = data.aws_iam_policy_document.usage.json
+ tags = var.tags
+}
diff --git a/modules/microvm-foundation/variables.tf b/modules/microvm-foundation/variables.tf
new file mode 100644
index 0000000000..81c84d4fd1
--- /dev/null
+++ b/modules/microvm-foundation/variables.tf
@@ -0,0 +1,152 @@
+variable "aws_region" {
+ type = string
+ description = "AWS region in which to create the Lambda MicroVM prerequisites."
+}
+
+variable "tags" {
+ type = map(string)
+ description = "A map of module-specific tags to apply to resources."
+}
+
+variable "build_policy_name_prefix" {
+ type = string
+ description = "Name prefix for the Lambda MicroVM build policy."
+
+ validation {
+ condition = length(var.build_policy_name_prefix) >= 1 && length(var.build_policy_name_prefix) <= 64 && can(regex("^[a-zA-Z0-9-_]+-$", var.build_policy_name_prefix))
+ error_message = "build_policy_name_prefix must be 1 to 64 characters, contain only letters, numbers, hyphens, or underscores, and end with a hyphen."
+ }
+}
+
+variable "usage_policy_name_prefix" {
+ type = string
+ description = "Name prefix for the Lambda MicroVM runtime usage policy."
+
+ validation {
+ condition = length(var.usage_policy_name_prefix) >= 1 && length(var.usage_policy_name_prefix) <= 64 && can(regex("^[a-zA-Z0-9-_]+-$", var.usage_policy_name_prefix))
+ error_message = "usage_policy_name_prefix must be 1 to 64 characters, contain only letters, numbers, hyphens, or underscores, and end with a hyphen."
+ }
+}
+
+variable "build_role_name_prefix" {
+ type = string
+ description = "Name prefix for the Lambda MicroVM build role."
+
+ validation {
+ condition = length(var.build_role_name_prefix) >= 1 && length(var.build_role_name_prefix) <= 64 && can(regex("^[a-zA-Z0-9-_]+-$", var.build_role_name_prefix))
+ error_message = "build_role_name_prefix must be 1 to 64 characters, contain only letters, numbers, hyphens, or underscores, and end with a hyphen."
+ }
+}
+
+variable "network_connector_operator_role_name_prefix" {
+ type = string
+ description = "Name prefix for the Lambda Network Connector operator role."
+
+ validation {
+ condition = length(var.network_connector_operator_role_name_prefix) >= 1 && length(var.network_connector_operator_role_name_prefix) <= 64 && can(regex("^[a-zA-Z0-9-_]+-$", var.network_connector_operator_role_name_prefix))
+ error_message = "network_connector_operator_role_name_prefix must be 1 to 64 characters, contain only letters, numbers, hyphens, or underscores, and end with a hyphen."
+ }
+}
+
+variable "artifact_bucket_name" {
+ type = string
+ description = "Optional name for the regional MicroVM build-artifact bucket. When null, AWS generates the bucket name."
+ default = null
+ nullable = true
+
+ validation {
+ condition = var.artifact_bucket_name == null || length(var.artifact_bucket_name) > 0
+ error_message = "artifact_bucket_name must be null or a non-empty string."
+ }
+}
+
+variable "artifact_retention_days" {
+ type = number
+ description = "Number of days to retain current and noncurrent MicroVM build artifacts."
+ default = 30
+
+ validation {
+ condition = var.artifact_retention_days >= 1 && var.artifact_retention_days <= 3650
+ error_message = "artifact_retention_days must be between 1 and 3650."
+ }
+}
+
+variable "image_name_prefix" {
+ type = string
+ description = "IAM namespace prefix reserved for externally published Lambda MicroVM image names. This module does not create or enumerate images."
+
+ validation {
+ condition = (
+ length(var.image_name_prefix) >= 1
+ && length(var.image_name_prefix) <= 62
+ && can(regex("^[a-zA-Z0-9-_]+$", var.image_name_prefix))
+ )
+ error_message = "image_name_prefix must be a 1 to 62 character IAM namespace containing only letters, numbers, hyphens, or underscores; the publisher validates each complete image name."
+ }
+}
+
+variable "ecr_repository_arns" {
+ type = set(string)
+ description = "Optional regional ECR repository ARNs from which MicroVM image builds can pull runner base images."
+ default = []
+}
+
+variable "network_connectors" {
+ type = map(object({
+ name = string
+ vpc_id = string
+ subnet_ids = set(string)
+ network_protocol = optional(string, "IPv4")
+ }))
+ description = "Regional Lambda MicroVM Network Connectors keyed by a stable consumer-defined identity."
+
+ validation {
+ condition = length(var.network_connectors) > 0
+ error_message = "network_connectors must contain at least one connector."
+ }
+
+ validation {
+ condition = alltrue([
+ for connector in values(var.network_connectors) : (
+ length(connector.name) >= 1
+ && length(connector.name) <= 64
+ && can(regex("^[a-zA-Z0-9_-]+$", connector.name))
+ )
+ ])
+ error_message = "Each network connector name must contain only letters, numbers, hyphens, or underscores and be at most 64 characters."
+ }
+
+ validation {
+ condition = (
+ length(distinct([for connector in values(var.network_connectors) : connector.name])) == length(var.network_connectors)
+ )
+ error_message = "Each network connector name must be unique within the region."
+ }
+
+ validation {
+ condition = alltrue([
+ for connector in values(var.network_connectors) : can(regex("^vpc-[0-9a-f]+$", connector.vpc_id))
+ ])
+ error_message = "Each network connector vpc_id must be a valid VPC ID."
+ }
+
+ validation {
+ condition = alltrue([
+ for connector in values(var.network_connectors) : (
+ length(connector.subnet_ids) >= 1
+ && length(connector.subnet_ids) <= 16
+ && alltrue([
+ for subnet_id in connector.subnet_ids : can(regex("^subnet-[0-9a-f]+$", subnet_id))
+ ])
+ )
+ ])
+ error_message = "Each network connector must contain 1 to 16 valid subnet IDs."
+ }
+
+ validation {
+ condition = alltrue([
+ for connector in values(var.network_connectors) : contains(["IPv4", "DualStack"], connector.network_protocol)
+ ])
+ error_message = "Each network connector network_protocol must be IPv4 or DualStack."
+ }
+}
diff --git a/modules/microvm-foundation/versions.tf b/modules/microvm-foundation/versions.tf
new file mode 100644
index 0000000000..ccc4e5a4ef
--- /dev/null
+++ b/modules/microvm-foundation/versions.tf
@@ -0,0 +1,14 @@
+terraform {
+ required_version = ">= 1.4.0"
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 6.61"
+ }
+ time = {
+ source = "hashicorp/time"
+ version = ">= 0.13"
+ }
+ }
+}