From 0e8e169311194c7418733c691341ce5483e99eeb Mon Sep 17 00:00:00 2001 From: Paul Balaji Date: Thu, 25 Jan 2024 17:42:43 +0000 Subject: [PATCH] feat: aws validator terraform example (#3170) --- rust/terraform/.gitignore | 2 + rust/terraform/README.md | 7 + rust/terraform/globals.tf | 100 ++++++++++ rust/terraform/main.tf | 20 ++ rust/terraform/modules/efs/main.tf | 42 ++++ rust/terraform/modules/efs/outputs.tf | 19 ++ rust/terraform/modules/efs/variables.tf | 38 ++++ rust/terraform/modules/iam_kms/main.tf | 179 ++++++++++++++++++ rust/terraform/modules/iam_kms/outputs.tf | 35 ++++ rust/terraform/modules/iam_kms/variables.tf | 19 ++ rust/terraform/modules/s3/main.tf | 65 +++++++ rust/terraform/modules/s3/outputs.tf | 7 + rust/terraform/modules/s3/variables.tf | 9 + rust/terraform/modules/validator/main.tf | 138 ++++++++++++++ rust/terraform/modules/validator/outputs.tf | 9 + rust/terraform/modules/validator/variables.tf | 64 +++++++ rust/terraform/outputs.tf | 4 + rust/terraform/variables.tf | 5 + 18 files changed, 762 insertions(+) create mode 100644 rust/terraform/.gitignore create mode 100644 rust/terraform/README.md create mode 100644 rust/terraform/globals.tf create mode 100644 rust/terraform/main.tf create mode 100644 rust/terraform/modules/efs/main.tf create mode 100644 rust/terraform/modules/efs/outputs.tf create mode 100644 rust/terraform/modules/efs/variables.tf create mode 100644 rust/terraform/modules/iam_kms/main.tf create mode 100644 rust/terraform/modules/iam_kms/outputs.tf create mode 100644 rust/terraform/modules/iam_kms/variables.tf create mode 100644 rust/terraform/modules/s3/main.tf create mode 100644 rust/terraform/modules/s3/outputs.tf create mode 100644 rust/terraform/modules/s3/variables.tf create mode 100644 rust/terraform/modules/validator/main.tf create mode 100644 rust/terraform/modules/validator/outputs.tf create mode 100644 rust/terraform/modules/validator/variables.tf create mode 100644 rust/terraform/outputs.tf create mode 100644 rust/terraform/variables.tf diff --git a/rust/terraform/.gitignore b/rust/terraform/.gitignore new file mode 100644 index 0000000000..282617e472 --- /dev/null +++ b/rust/terraform/.gitignore @@ -0,0 +1,2 @@ +.terraform +.terraform.lock.* diff --git a/rust/terraform/README.md b/rust/terraform/README.md new file mode 100644 index 0000000000..67e1f7330c --- /dev/null +++ b/rust/terraform/README.md @@ -0,0 +1,7 @@ +# Terraform Module for Hyperlane Validator + +This Terraform module is designed to set up the necessary infrastructure for a Hyperlane validator on AWS. It automates the creation of resources such as ECS clusters, VPCs, subnets, and security groups required for running a validator node. + +> **Note:** This module is intended to be an example of running a validator for a core supported network. You may have to modify the validator module to support more advanced configurations. It is recommended to test thoroughly before using in a production environment. + +For more information, read the [Deploy with Terraform](https://hyp-v3-docs-git-feat-aws-agent-guide-abacus-works.vercel.app/docs/operate/deploy-with-terraform) documentation. diff --git a/rust/terraform/globals.tf b/rust/terraform/globals.tf new file mode 100644 index 0000000000..92e6549404 --- /dev/null +++ b/rust/terraform/globals.tf @@ -0,0 +1,100 @@ +provider "aws" { + region = var.aws_region # Set the AWS region for the provider +} + +resource "aws_ecs_cluster" "validator_cluster" { + name = "hyperlane-validator-cluster" # Name of the ECS cluster for the validator +} + +resource "aws_vpc" "validator_vpc" { + cidr_block = "10.0.0.0/16" # Define the IP range for the VPC + enable_dns_support = true # Enable DNS support in the VPC + enable_dns_hostnames = true # Enable DNS hostnames in the VPC +} + +data "aws_availability_zones" "available" {} # Fetch the list of available AZs + +resource "aws_subnet" "public_subnet" { + vpc_id = aws_vpc.validator_vpc.id # Associate with the VPC + cidr_block = "10.0.2.0/24" # Define the IP range for the public subnet + availability_zone = data.aws_availability_zones.available.names[0] # Use the first available AZ + map_public_ip_on_launch = true # Automatically assign public IP on instance launch +} + +resource "aws_subnet" "validator_subnet" { + vpc_id = aws_vpc.validator_vpc.id # Associate with the VPC + cidr_block = "10.0.1.0/24" # Define the IP range for the validator subnet + availability_zone = data.aws_availability_zones.available.names[0] # Use the first available AZ + map_public_ip_on_launch = false # Do not assign public IP on instance launch +} + +resource "aws_internet_gateway" "vpc_igw" { + vpc_id = aws_vpc.validator_vpc.id # Attach the internet gateway to the VPC +} + +resource "aws_eip" "nat_gateway_eip" { + domain = "vpc" # Allocate an Elastic IP in the VPC domain +} + +resource "aws_nat_gateway" "validator_nat_gateway" { + allocation_id = aws_eip.nat_gateway_eip.id # Associate the EIP with the NAT gateway + subnet_id = aws_subnet.public_subnet.id # Place the NAT gateway in the public subnet + depends_on = [aws_internet_gateway.vpc_igw] # Ensure IGW is created before the NAT gateway +} + +resource "aws_route_table" "public_route_table" { + vpc_id = aws_vpc.validator_vpc.id # Associate the route table with the VPC + + route { + cidr_block = "0.0.0.0/0" # Route all traffic + gateway_id = aws_internet_gateway.vpc_igw.id # Through the internet gateway + } +} + +resource "aws_route_table" "private_route_table" { + vpc_id = aws_vpc.validator_vpc.id # Associate the route table with the VPC + + route { + cidr_block = "0.0.0.0/0" # Route all traffic + nat_gateway_id = aws_nat_gateway.validator_nat_gateway.id # Through the NAT gateway + } +} + +resource "aws_route_table_association" "public_subnet_association" { + subnet_id = aws_subnet.public_subnet.id # Associate the public subnet + route_table_id = aws_route_table.public_route_table.id # With the public route table +} + +resource "aws_route_table_association" "private_subnet_association" { + subnet_id = aws_subnet.validator_subnet.id # Associate the validator subnet + route_table_id = aws_route_table.private_route_table.id # With the private route table +} + +resource "aws_security_group" "validator_sg" { + name = "validator-sg" # Name of the security group for the validator + vpc_id = aws_vpc.validator_vpc.id # Associate with the VPC + + # prometheus + ingress { + from_port = 9090 # Prometheus metrics port + to_port = 9090 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] # Allow traffic from any IP + } + + # efs mounting + ingress { + from_port = 2049 # NFS port for EFS + to_port = 2049 + protocol = "tcp" + cidr_blocks = [aws_subnet.validator_subnet.cidr_block] # Allow traffic from the validator subnet + } + + # all egress + egress { + from_port = 0 # Allow all outbound traffic + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] # To any IP + } +} diff --git a/rust/terraform/main.tf b/rust/terraform/main.tf new file mode 100644 index 0000000000..e67fe80b78 --- /dev/null +++ b/rust/terraform/main.tf @@ -0,0 +1,20 @@ +# Configure a Hyperlane Validator +# Replaces https://docs.hyperlane.xyz/docs/operate/validators/run-validators +module "your_validator_name" { + source = "./modules/validator" + + validator_name = "your-validator-name" + origin_chain_name = "originChainName" + + aws_region = var.aws_region + validator_cluster_id = aws_ecs_cluster.validator_cluster.id + validator_subnet_id = aws_subnet.validator_subnet.id + validator_sg_id = aws_security_group.validator_sg.id + validator_nat_gateway_id = aws_nat_gateway.validator_nat_gateway.id + + # Disabling the validator task allows you to set up all the required infrastructure + # without running the actual validator yet. This is useful when setting up a validator for + # the first time, so that you can find out the validator address and fund it before it + # performs the announcement transaction. + # validator_task_disabled = true +} diff --git a/rust/terraform/modules/efs/main.tf b/rust/terraform/modules/efs/main.tf new file mode 100644 index 0000000000..ba3c499e66 --- /dev/null +++ b/rust/terraform/modules/efs/main.tf @@ -0,0 +1,42 @@ +# This resource defines an EFS file system that acts as persistent storage for the validator. +# The `creation_token` is used to ensure idempotent creation of the file system. +resource "aws_efs_file_system" "validator_fs" { + creation_token = var.creation_token # Unique token to guarantee the idempotence of the resource + + # Tags are key-value pairs that help with the organization and identification of AWS resources. + tags = { + Name = var.creation_token # Name tag using the creation token for easy identification + } +} + +# The EFS access point serves as a custom entry point into the file system. +# It enforces the specified POSIX user and group, and the root directory settings. +resource "aws_efs_access_point" "validator_ap" { + file_system_id = aws_efs_file_system.validator_fs.id # Associates the access point with the file system + + # The POSIX user configuration sets the owner's user and group IDs for all file system requests. + posix_user { + gid = var.posix_user_gid # POSIX group ID + uid = var.posix_user_uid # POSIX user ID + } + + # The root directory configuration specifies the path and creation settings within the EFS. + root_directory { + path = var.root_directory_path # The path where the root directory is mounted + + # The creation info sets the ownership and permissions for the root directory upon creation. + creation_info { + owner_gid = var.posix_user_gid # Group ID of the directory owner + owner_uid = var.posix_user_uid # User ID of the directory owner + permissions = var.root_directory_permissions # Permissions for the root directory + } + } +} + +# This resource creates a mount target within a specific subnet, allowing EC2 instances to access the EFS file system. +# The mount target is secured by associating it with one or more security groups. +resource "aws_efs_mount_target" "validator_mt" { + file_system_id = aws_efs_file_system.validator_fs.id # Associates the mount target with the file system + subnet_id = var.subnet_id # The subnet ID where the mount target is placed + security_groups = var.security_group_ids # Security groups that define the access rules for the mount target +} diff --git a/rust/terraform/modules/efs/outputs.tf b/rust/terraform/modules/efs/outputs.tf new file mode 100644 index 0000000000..8fb002fd29 --- /dev/null +++ b/rust/terraform/modules/efs/outputs.tf @@ -0,0 +1,19 @@ +output "file_system_id" { + description = "The ID of the EFS file system" + value = aws_efs_file_system.validator_fs.id +} + +output "access_point_id" { + description = "The ID of the EFS access point" + value = aws_efs_access_point.validator_ap.id +} + +output "mount_target_id" { + description = "The ID of the EFS mount target" + value = aws_efs_mount_target.validator_mt.id +} + +output "access_point_arn" { + description = "The ARN of the EFS access point" + value = aws_efs_access_point.validator_ap.arn +} diff --git a/rust/terraform/modules/efs/variables.tf b/rust/terraform/modules/efs/variables.tf new file mode 100644 index 0000000000..43803193fb --- /dev/null +++ b/rust/terraform/modules/efs/variables.tf @@ -0,0 +1,38 @@ +variable "creation_token" { + description = "Unique string to ensure the idempotent creation of the file system" + type = string +} + +variable "subnet_id" { + description = "The ID of the subnet to create the mount target in" + type = string +} + +variable "security_group_ids" { + description = "A list of security group IDs to associate with the mount target" + type = list(string) +} + +variable "posix_user_gid" { + description = "The POSIX group ID for the EFS access point" + type = number + default = 1000 +} + +variable "posix_user_uid" { + description = "The POSIX user ID for the EFS access point" + type = number + default = 1000 +} + +variable "root_directory_path" { + description = "Path to the root directory on the EFS volume" + type = string + default = "/hyperlane_db" +} + +variable "root_directory_permissions" { + description = "Permissions to apply to the root directory on the EFS volume" + type = string + default = "700" +} diff --git a/rust/terraform/modules/iam_kms/main.tf b/rust/terraform/modules/iam_kms/main.tf new file mode 100644 index 0000000000..c6e49c2c12 --- /dev/null +++ b/rust/terraform/modules/iam_kms/main.tf @@ -0,0 +1,179 @@ +# Creates an IAM user for the validator to interact with AWS services +resource "aws_iam_user" "ecs_user" { + name = "${var.validator_name}-exec-user" # The name of the IAM user is derived from the validator's name +} + +# Creates a KMS key for the validator to sign transactions securely +resource "aws_kms_key" "validator_signer_key" { + description = "KMS Key for Hyperlane Validator Signing" + key_usage = "SIGN_VERIFY" # Specifies that the key is used for signing and verification + customer_master_key_spec = "ECC_SECG_P256K1" # Specifies the type of key to be used +} + +# Creates an alias for the KMS key to make it easier to reference +resource "aws_kms_alias" "validator_signer_key_alias" { + name = "alias/${var.validator_name}" # The alias name includes the validator's name for easy identification + target_key_id = aws_kms_key.validator_signer_key.key_id # Associates the alias with the created KMS key +} + +# Defines an IAM policy that grants permissions to use the KMS key for signing operations +resource "aws_iam_policy" "validator_user_kms_policy" { + name = "${var.validator_name}-user-kms-policy" + description = "Allow ECS tasks to use the KMS key for signing" + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = [ + "kms:GetPublicKey", # Allows retrieval of the public key + "kms:Sign", # Allows signing operations + "kms:Verify" # Allows verification of signatures + ], + Resource = aws_kms_key.validator_signer_key.arn # Specifies the KMS key resource + } + ] + }) +} + +# Attaches the KMS policy to the IAM user, granting it the defined permissions +resource "aws_iam_user_policy_attachment" "validator_user_kms_policy_attachment" { + user = aws_iam_user.ecs_user.name # The IAM user to attach the policy to + policy_arn = aws_iam_policy.validator_user_kms_policy.arn # The ARN of the policy to attach +} + +# Generates an access key for the IAM user to authenticate with AWS services +resource "aws_iam_access_key" "ecs_user_key" { + user = aws_iam_user.ecs_user.name # The IAM user for which to create the access key +} + +# Stores the access key ID in SSM Parameter Store for secure retrieval +resource "aws_ssm_parameter" "key_id" { + name = "/ecs/${var.validator_name}/access-key-id" # The parameter name includes the validator's name + type = "String" # The type of the parameter is a simple string + value = aws_iam_access_key.ecs_user_key.id # The value is the access key ID +} + +# Stores the access key secret in SSM Parameter Store for secure retrieval +resource "aws_ssm_parameter" "key_secret" { + name = "/ecs/${var.validator_name}/secret-access-key" # The parameter name includes the validator's name + type = "String" # The type of the parameter is a simple string + value = aws_iam_access_key.ecs_user_key.secret # The value is the access key secret +} + +# Creates an IAM role for ECS tasks to assume during execution +resource "aws_iam_role" "ecs_execution_role" { + name = "${var.validator_name}-exec-role" # The name of the role includes the validator's name + + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Action = "sts:AssumeRole", + Effect = "Allow", + Principal = { + Service = "ecs-tasks.amazonaws.com" # Specifies that ECS tasks can assume this role + } + } + ] + }) +} + +# Attaches the AmazonECSTaskExecutionRolePolicy to the ECS execution role +resource "aws_iam_role_policy_attachment" "ecs_execution_policy" { + role = aws_iam_role.ecs_execution_role.name # The ECS execution role to attach the policy to + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" # The ARN of the Amazon-managed policy +} + +# Defines an IAM policy to allow ECS tasks to write logs to CloudWatch +resource "aws_iam_policy" "cloudwatch_logs_policy" { + name = "${var.validator_name}-cloudwatch-logs-policy" + description = "IAM policy for ECS tasks to interact with CloudWatch Logs" + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = [ + "logs:CreateLogStream", # Allows creation of log streams + "logs:PutLogEvents" # Allows putting log events into log streams + ], + Resource = "arn:aws:logs:${var.aws_region}:*:log-group:/aws/ecs/${var.aws_log_group}:log-stream:*" # Specifies the log group resource + } + ] + }) +} + +# Attaches the CloudWatch logs policy to the ECS execution role +resource "aws_iam_role_policy_attachment" "cloudwatch_logs_policy_attachment" { + role = aws_iam_role.ecs_execution_role.name # The ECS execution role to attach the policy to + policy_arn = aws_iam_policy.cloudwatch_logs_policy.arn # The ARN of the CloudWatch logs policy +} + +# Defines an IAM policy to allow ECS tasks to read SSM parameters for access keys +resource "aws_iam_policy" "ssm_read_policy" { + name = "${var.validator_name}-ssm-read-policy" + description = "Allow ECS tasks to read parameters" + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = ["ssm:GetParameters"], # Allows retrieval of SSM parameters + Resource = [ + aws_ssm_parameter.key_id.arn, # The ARN of the access key ID parameter + aws_ssm_parameter.key_secret.arn # The ARN of the access key secret parameter + ] + } + ] + }) +} + +# Attaches the SSM read policy to the ECS execution role +resource "aws_iam_role_policy_attachment" "ssm_read_policy_execution_attachment" { + role = aws_iam_role.ecs_execution_role.name # The ECS execution role to attach the policy to + policy_arn = aws_iam_policy.ssm_read_policy.arn # The ARN of the SSM read policy +} + +# Creates an IAM role for ECS tasks to perform specific actions +resource "aws_iam_role" "ecs_task_role" { + name = "${var.validator_name}-task-role" # The name of the task role includes the validator's name + + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Action = "sts:AssumeRole", + Effect = "Allow", + Principal = { + Service = "ecs-tasks.amazonaws.com" # Specifies that ECS tasks can assume this role + } + } + ] + }) +} + +# Defines an IAM policy to allow ECS tasks to perform actions on the EFS file system +resource "aws_iam_policy" "ecs_task_policy" { + name = "${var.validator_name}-task-policy" # The name of the policy includes the validator's name + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = "elasticfilesystem:*", # Allows all actions on the EFS file system + Resource = var.efs_access_point_arn # Specifies the EFS access point resource + } + ] + }) +} + +# Attaches the EFS policy to the ECS task role +resource "aws_iam_role_policy_attachment" "ecs_task_policy_attachment" { + role = aws_iam_role.ecs_task_role.name # The ECS task role to attach the policy to + policy_arn = aws_iam_policy.ecs_task_policy.arn # The ARN of the EFS policy +} diff --git a/rust/terraform/modules/iam_kms/outputs.tf b/rust/terraform/modules/iam_kms/outputs.tf new file mode 100644 index 0000000000..81b18a625c --- /dev/null +++ b/rust/terraform/modules/iam_kms/outputs.tf @@ -0,0 +1,35 @@ +output "ecs_user_arn" { + value = aws_iam_user.ecs_user.arn +} + +output "ecs_user_access_key_id_arn" { + value = aws_ssm_parameter.key_id.arn +} + +output "ecs_user_secret_access_key_arn" { + value = aws_ssm_parameter.key_secret.arn +} + +output "validator_signer_key_arn" { + value = aws_kms_key.validator_signer_key.arn +} + +output "validator_signer_key_alias" { + value = aws_kms_alias.validator_signer_key_alias.name +} + +output "validator_execution_role_arn" { + value = aws_iam_role.ecs_execution_role.arn +} + +output "validator_task_role_arn" { + value = aws_iam_role.ecs_task_role.arn +} + +output "aws_access_key_id" { + value = aws_iam_access_key.ecs_user_key.id +} + +output "aws_secret_access_key" { + value = aws_iam_access_key.ecs_user_key.secret +} diff --git a/rust/terraform/modules/iam_kms/variables.tf b/rust/terraform/modules/iam_kms/variables.tf new file mode 100644 index 0000000000..7f6568d42f --- /dev/null +++ b/rust/terraform/modules/iam_kms/variables.tf @@ -0,0 +1,19 @@ +variable "aws_region" { + description = "AWS region" + type = string +} + +variable "validator_name" { + description = "The name of the validator" + type = string +} + +variable "aws_log_group" { + description = "The name of the log group to write to" + type = string +} + +variable "efs_access_point_arn" { + description = "The ARN of the EFS access point" + type = string +} diff --git a/rust/terraform/modules/s3/main.tf b/rust/terraform/modules/s3/main.tf new file mode 100644 index 0000000000..784bea146a --- /dev/null +++ b/rust/terraform/modules/s3/main.tf @@ -0,0 +1,65 @@ +# This resource creates an S3 bucket used to store validator signatures. +# The `force_destroy` attribute is set to true to allow the bucket to be destroyed even if it contains objects. +resource "aws_s3_bucket" "validator_bucket" { + bucket = "${var.validator_name}-signatures" + force_destroy = true # Enables deletion of non-empty bucket during destroy operation +} + +# This resource applies a public access block configuration to the validator signatures bucket. +# It prevents public ACLs from being applied to the bucket and ignores any public ACLs already on the bucket. +resource "aws_s3_bucket_public_access_block" "validator_bucket_public_access_block" { + bucket = aws_s3_bucket.validator_bucket.id + + block_public_acls = true # Blocks public ACLs from being added to the bucket + ignore_public_acls = true # Ignores any public ACLs currently associated with the bucket + block_public_policy = false # Allows public bucket policies (not recommended for sensitive data) + restrict_public_buckets = false # Allows unrestricted public access to the bucket (not recommended for sensitive data) +} + +# This resource defines a bucket policy that allows public read access to the bucket and its objects. +# It also grants additional permissions to a specific IAM role to delete and put objects in the bucket. +resource "aws_s3_bucket_policy" "validator_bucket_policy" { + bucket = aws_s3_bucket.validator_bucket.id + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Principal = "*", + Action = [ + "s3:GetObject", # Allows retrieval of objects from the bucket + "s3:ListBucket" # Allows listing of the objects within the bucket + ], + Resource = [ + "${aws_s3_bucket.validator_bucket.arn}", # Bucket ARN + "${aws_s3_bucket.validator_bucket.arn}/*" # All objects within the bucket + ] + }, + { + Effect = "Allow", + Principal = { + AWS = var.validator_iam_user_arn # IAM user ARN of validator + }, + Action = [ + "s3:PutObject", # Allows uploading of new objects to the bucket + "s3:GetObject", # Allows retrieval of objects from the bucket + "s3:ListBucket", # Allows listing of the objects within the bucket + "s3:DeleteObject", # Allows deletion of objects within the bucket + ], + Resource = [ + "${aws_s3_bucket.validator_bucket.arn}", # Bucket ARN + "${aws_s3_bucket.validator_bucket.arn}/*" # All objects within the bucket + ] + } + ] + }) +} + +# This resource enables versioning for the S3 bucket to keep multiple versions of an object in the same bucket. +# Versioning is useful for data retention and recovery, as it allows you to recover from unintended user actions and application failures. +resource "aws_s3_bucket_versioning" "validator_bucket_versioning" { + bucket = aws_s3_bucket.validator_bucket.id + versioning_configuration { + status = "Enabled" # Enables versioning for the specified bucket + } +} diff --git a/rust/terraform/modules/s3/outputs.tf b/rust/terraform/modules/s3/outputs.tf new file mode 100644 index 0000000000..b51bb31375 --- /dev/null +++ b/rust/terraform/modules/s3/outputs.tf @@ -0,0 +1,7 @@ +output "validator_bucket_id" { + value = aws_s3_bucket.validator_bucket.id +} + +output "validator_bucket_arn" { + value = aws_s3_bucket.validator_bucket.arn +} diff --git a/rust/terraform/modules/s3/variables.tf b/rust/terraform/modules/s3/variables.tf new file mode 100644 index 0000000000..8740623102 --- /dev/null +++ b/rust/terraform/modules/s3/variables.tf @@ -0,0 +1,9 @@ +variable "validator_name" { + description = "The name of the validator" + type = string +} + +variable "validator_iam_user_arn" { + description = "The ARN of the IAM user that will write to the S3 bucket" + type = string +} diff --git a/rust/terraform/modules/validator/main.tf b/rust/terraform/modules/validator/main.tf new file mode 100644 index 0000000000..4667de95c3 --- /dev/null +++ b/rust/terraform/modules/validator/main.tf @@ -0,0 +1,138 @@ +# Sets up roles, permissions and KMS key +# Replaces https://docs.hyperlane.xyz/docs/operate/set-up-agent-keys +module "iam_kms" { + source = "../iam_kms" + + aws_region = var.aws_region + aws_log_group = var.aws_log_group + validator_name = var.validator_name + efs_access_point_arn = module.efs.access_point_arn +} + +# Creates bucket for posting validator signatures +# Replaces https://docs.hyperlane.xyz/docs/operate/validators/validator-aws +module "s3" { + source = "../s3" + + validator_name = var.validator_name + validator_iam_user_arn = module.iam_kms.ecs_user_arn +} + +# Creates file system and mounting point for the validator task +module "efs" { + source = "../efs" + + creation_token = "${var.validator_name}-db-fs" + subnet_id = var.validator_subnet_id + security_group_ids = [var.validator_sg_id] +} + +# A template for running the validator task +resource "aws_ecs_task_definition" "validator" { + family = var.validator_name + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = var.validator_cpu + memory = var.validator_memory + execution_role_arn = module.iam_kms.validator_execution_role_arn + task_role_arn = module.iam_kms.validator_task_role_arn + + container_definitions = jsonencode([ + { + name = "validator", + image = "gcr.io/abacus-labs-dev/hyperlane-agent:${var.validator_image_version}", + user = "1000:1000", + secrets = [ + { + name = "AWS_ACCESS_KEY_ID", + valueFrom = module.iam_kms.ecs_user_access_key_id_arn + }, + { + name = "AWS_SECRET_ACCESS_KEY", + valueFrom = module.iam_kms.ecs_user_secret_access_key_arn + } + ], + mountPoints = [ + { + sourceVolume = "hyperlane_db", + containerPath = "/hyperlane_db" + }, + ], + portMappings = [ + { + containerPort = 9090, # Prometheus metrics port + hostPort = 9090 + } + ], + command = [ + "./validator", + "--db", + "/hyperlane_db", + "--originChainName", + var.origin_chain_name, + "--validator.type", + "aws", + "--validator.id", + module.iam_kms.validator_signer_key_alias, + "--chains.${var.origin_chain_name}.type", + "aws", + "--chains.${var.origin_chain_name}.id", + module.iam_kms.validator_signer_key_alias, + "--checkpointSyncer.type", + "s3", + "--checkpointSyncer.bucket", + module.s3.validator_bucket_id, + "--checkpointSyncer.region", + var.aws_region, + "--validator.region", + var.aws_region + ], + logConfiguration = { + logDriver = "awslogs", + options = { + "awslogs-group" = var.aws_log_group, + "awslogs-region" = var.aws_region, + "awslogs-stream-prefix" = "ecs" + } + } + } + ]) + + volume { + name = "hyperlane_db" + + efs_volume_configuration { + file_system_id = module.efs.file_system_id + transit_encryption = "ENABLED" + + authorization_config { + access_point_id = module.efs.access_point_id + iam = "ENABLED" + } + } + } +} + +# An ECS service for running the validator ECS task +resource "aws_ecs_service" "validator_service" { + name = var.validator_name + cluster = var.validator_cluster_id + task_definition = aws_ecs_task_definition.validator.arn + launch_type = "FARGATE" + + # avoid rolling deployments to not lock agent db + deployment_maximum_percent = 100 + deployment_minimum_healthy_percent = 0 + + network_configuration { + subnets = [var.validator_subnet_id] + security_groups = [var.validator_sg_id] + } + + desired_count = var.validator_task_disabled ? 0 : 1 + + # implicit dependency on nat gateway existing + tags = { + NatGatewayID = var.validator_nat_gateway_id + } +} diff --git a/rust/terraform/modules/validator/outputs.tf b/rust/terraform/modules/validator/outputs.tf new file mode 100644 index 0000000000..f13b9463c4 --- /dev/null +++ b/rust/terraform/modules/validator/outputs.tf @@ -0,0 +1,9 @@ +output "validator_info" { + value = { + aws_access_key_id = module.iam_kms.aws_access_key_id, + aws_secret_access_key = module.iam_kms.aws_secret_access_key, + aws_kms_alias = module.iam_kms.validator_signer_key_alias, + aws_s3_bucket_id = module.s3.validator_bucket_id, + aws_region = var.aws_region, + } +} diff --git a/rust/terraform/modules/validator/variables.tf b/rust/terraform/modules/validator/variables.tf new file mode 100644 index 0000000000..46b8fc51f8 --- /dev/null +++ b/rust/terraform/modules/validator/variables.tf @@ -0,0 +1,64 @@ +variable "aws_region" { + description = "AWS region" + type = string +} + +variable "validator_cluster_id" { + description = "ID of the validator cluster" + type = string +} + +variable "validator_subnet_id" { + description = "ID of the validator subnet" + type = string +} + +variable "validator_sg_id" { + description = "ID of the validator security group" + type = string +} + +variable "validator_nat_gateway_id" { + description = "ID of the validator NAT gateway" + type = string +} + +variable "validator_name" { + description = "The name of the validator" + type = string +} + +variable "origin_chain_name" { + description = "The origin chain of the validator" + type = string +} + +variable "validator_cpu" { + description = "CPU units used by the validator. Default 1 vCPU." + type = string + default = "1024" +} + +variable "validator_memory" { + description = "Memory units used by the validator. Default 6GB." + type = string + default = "6144" +} + +variable "aws_log_group" { + description = "The name of the log group to write to" + type = string + default = "DefaultLogGroup" +} + +variable "validator_image_version" { + description = "The name of the log group to write to" + type = string + default = "f44589e-20231130-114734" +} + +variable "validator_task_disabled" { + description = "Whether to run the validator in addition to auxiliary setup" + type = bool + default = false +} diff --git a/rust/terraform/outputs.tf b/rust/terraform/outputs.tf new file mode 100644 index 0000000000..a3f84a13cb --- /dev/null +++ b/rust/terraform/outputs.tf @@ -0,0 +1,4 @@ +output "your_validator_name" { + value = module.your_validator_name.validator_info + sensitive = true +} diff --git a/rust/terraform/variables.tf b/rust/terraform/variables.tf new file mode 100644 index 0000000000..3af94fb041 --- /dev/null +++ b/rust/terraform/variables.tf @@ -0,0 +1,5 @@ +variable "aws_region" { + description = "AWS region" + type = string + default = "us-east-1" +}