diff --git a/deployment/terraform/aws/main.tf b/deployment/terraform/aws/main.tf new file mode 100644 index 000000000..75a8f7212 --- /dev/null +++ b/deployment/terraform/aws/main.tf @@ -0,0 +1,93 @@ +provider "aws" { + alias = "region_1" + region = var.region_1 +} + +provider "aws" { + alias = "region_2" + region = var.region_2 +} + +# Validator +module "validator" { + source = "./validator" + providers = { + aws = aws.region_1 + } +} + +# Private Sentries +module "private_sentries" { + source = "./private-sentries" + + providers = { + aws = aws.region_1 + aws.peer = aws.region_1 + } + + peer_vpc = module.validator.vpc +} + +# Public Sentries region 1 +module "public_sentries_1" { + source = "./public-sentries" + nodes_count = 1 + + # enable_ipv6 = false + + providers = { + aws = aws.region_1 + aws.peer = aws.region_1 + } + + region_index = 1 + peer_vpc = module.private_sentries.vpc +} + +# Public Sentries region 2 +module "public_sentries_2" { + source = "./public-sentries" + nodes_count = 1 + + # enable_ipv6 = false + + providers = { + aws = aws.region_2 + aws.peer = aws.region_1 + } + + region_index = 2 + peer_vpc = module.private_sentries.vpc +} + +# Observers region 1 +module "observers_1" { + source = "./observers" + + providers = { + aws = aws.region_1 + aws.peer = aws.region_1 + } + + root_domain_name = var.root_domain_name + enable_tls = var.enable_tls + + region_index = 1 + peer_vpc = module.private_sentries.vpc +} + +# Observers region 2 +module "observers_2" { + source = "./observers" + + providers = { + aws = aws.region_2 + aws.peer = aws.region_1 + } + + root_domain_name = var.root_domain_name + enable_tls = var.enable_tls + + region_index = 2 + peer_vpc = module.private_sentries.vpc +} \ No newline at end of file diff --git a/deployment/terraform/aws/observers/acm.tf b/deployment/terraform/aws/observers/acm.tf new file mode 100644 index 000000000..179243db0 --- /dev/null +++ b/deployment/terraform/aws/observers/acm.tf @@ -0,0 +1,25 @@ +resource "aws_acm_certificate" "this_acm_cert" { + count = local.enable_tls ? 1 : 0 + + domain_name = "on.${data.aws_route53_zone.this_zone[0].name}" + validation_method = "DNS" +} + +resource "aws_route53_record" "this_acm_val_records" { + count = local.enable_tls ? length(aws_acm_certificate.this_acm_cert[0].domain_validation_options) : 0 + + name = tolist(aws_acm_certificate.this_acm_cert[0].domain_validation_options)[count.index].resource_record_name + records = [tolist(aws_acm_certificate.this_acm_cert[0].domain_validation_options)[count.index].resource_record_value] + type = tolist(aws_acm_certificate.this_acm_cert[0].domain_validation_options)[count.index].resource_record_type + + allow_overwrite = true + ttl = 60 + zone_id = data.aws_route53_zone.this_zone[0].zone_id +} + +resource "aws_acm_certificate_validation" "this_acm_cert_validation" { + count = local.enable_tls ? 1 : 0 + + certificate_arn = aws_acm_certificate.this_acm_cert[0].arn + validation_record_fqdns = aws_route53_record.this_acm_val_records[*].fqdn +} diff --git a/deployment/terraform/aws/observers/elb.tf b/deployment/terraform/aws/observers/elb.tf new file mode 100644 index 000000000..a6e6d7265 --- /dev/null +++ b/deployment/terraform/aws/observers/elb.tf @@ -0,0 +1,162 @@ +resource "aws_lb" "this_nlb" { + name = "observers-network-lb" + internal = false + load_balancer_type = "network" + subnets = module.this_vpc.public_subnets + + enable_cross_zone_load_balancing = true + # enable_deletion_protection = true + + tags = { + Name = "Observers NLB" + } +} + +locals { + tls_cert_arn = var.enable_tls ? aws_acm_certificate_validation.this_acm_cert_validation[0].certificate_arn : "" + ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06" # TLS 1.3 (recommended) +} + +resource "aws_lb_listener" "rest" { + count = local.enable_tls ? 0 : 1 + + load_balancer_arn = aws_lb.this_nlb.arn + port = "80" + protocol = "TCP" + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.rest.arn + } +} + +resource "aws_lb_listener" "grpc" { + count = local.enable_tls ? 0 : 1 + + load_balancer_arn = aws_lb.this_nlb.arn + port = "9090" + protocol = "TCP" + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.grpc.arn + } +} + +resource "aws_lb_listener" "rpc" { + count = local.enable_tls ? 0 : 1 + + load_balancer_arn = aws_lb.this_nlb.arn + port = "8080" + protocol = "TCP" + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.rpc.arn + } +} + +resource "aws_lb_listener" "tls_rest" { + count = local.enable_tls ? 1 : 0 + + load_balancer_arn = aws_lb.this_nlb.arn + port = "443" + protocol = "TLS" + certificate_arn = local.tls_cert_arn + ssl_policy = local.ssl_policy + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.rest.arn + } + + depends_on = [ + aws_acm_certificate_validation.this_acm_cert_validation[0] + ] +} + +resource "aws_lb_listener" "tls_grpc" { + count = local.enable_tls ? 1 : 0 + + load_balancer_arn = aws_lb.this_nlb.arn + port = "8443" + protocol = "TLS" + certificate_arn = local.tls_cert_arn + ssl_policy = local.ssl_policy + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.grpc.arn + } + + depends_on = [ + aws_acm_certificate_validation.this_acm_cert_validation[0] + ] +} + +resource "aws_lb_listener" "tls_rpc" { + count = local.enable_tls ? 1 : 0 + + load_balancer_arn = aws_lb.this_nlb.arn + port = "26657" + protocol = "TLS" + certificate_arn = local.tls_cert_arn + ssl_policy = local.ssl_policy + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.rpc.arn + } + + depends_on = [ + aws_acm_certificate_validation.this_acm_cert_validation[0] + ] +} + +resource "aws_lb_target_group" "rest" { + name = "observers-rest-target-group" + port = 1317 + protocol = "TCP" + vpc_id = module.this_vpc.vpc_id + preserve_client_ip = false +} + +resource "aws_lb_target_group" "grpc" { + name = "observers-grpc-target-group" + port = 9090 + protocol = "TCP" + vpc_id = module.this_vpc.vpc_id + preserve_client_ip = false +} + +resource "aws_lb_target_group" "rpc" { + name = "observers-rpc-target-group" + port = 26657 + protocol = "TCP" + vpc_id = module.this_vpc.vpc_id + preserve_client_ip = false +} + +resource "aws_lb_target_group_attachment" "rest_targets" { + count = length(aws_instance.this_nodes) + + target_group_arn = aws_lb_target_group.rest.arn + target_id = aws_instance.this_nodes[count.index].id + port = 80 +} + +resource "aws_lb_target_group_attachment" "grpc_targets" { + count = length(aws_instance.this_nodes) + + target_group_arn = aws_lb_target_group.grpc.arn + target_id = aws_instance.this_nodes[count.index].id + port = 9090 +} + +resource "aws_lb_target_group_attachment" "rpc_targets" { + count = length(aws_instance.this_nodes) + + target_group_arn = aws_lb_target_group.rpc.arn + target_id = aws_instance.this_nodes[count.index].id + port = 26657 +} \ No newline at end of file diff --git a/deployment/terraform/aws/observers/locals.tf b/deployment/terraform/aws/observers/locals.tf new file mode 100644 index 000000000..02dd04920 --- /dev/null +++ b/deployment/terraform/aws/observers/locals.tf @@ -0,0 +1,3 @@ +locals { + enable_tls = var.enable_tls && var.root_domain_name != "" +} \ No newline at end of file diff --git a/deployment/terraform/aws/observers/main.tf b/deployment/terraform/aws/observers/main.tf new file mode 100644 index 000000000..81e33786d --- /dev/null +++ b/deployment/terraform/aws/observers/main.tf @@ -0,0 +1,43 @@ +data "aws_ami" "ubuntu" { + most_recent = true + owners = ["099720109477"] + + filter { + name = "name" + values = ["ubuntu-minimal/images/hvm-ssd/ubuntu-focal-20.04-amd64-minimal-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } +} + +resource "aws_key_pair" "key_pair" { + public_key = file(var.ssh_public_key_path) +} + +resource "aws_instance" "this_nodes" { + count = var.nodes_count + + ami = data.aws_ami.ubuntu.id + instance_type = "t3.medium" + + subnet_id = element(module.this_vpc.public_subnets, count.index % length(module.this_vpc.public_subnets)) + vpc_security_group_ids = [ + module.this_dev_sg.security_group_id, + module.this_private_sg.security_group_id + ] + + key_name = aws_key_pair.key_pair.id + monitoring = true + + tags = { + Name = "Observer Node [${count.index}]" + } + + root_block_device { + encrypted = true + volume_size = 30 + } +} \ No newline at end of file diff --git a/deployment/terraform/aws/observers/outputs.tf b/deployment/terraform/aws/observers/outputs.tf new file mode 100644 index 000000000..e69de29bb diff --git a/deployment/terraform/aws/observers/provider.tf b/deployment/terraform/aws/observers/provider.tf new file mode 100644 index 000000000..9abe0ebfd --- /dev/null +++ b/deployment/terraform/aws/observers/provider.tf @@ -0,0 +1,9 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.72" + configuration_aliases = [aws, aws.peer] + } + } +} \ No newline at end of file diff --git a/deployment/terraform/aws/observers/route53.tf b/deployment/terraform/aws/observers/route53.tf new file mode 100644 index 000000000..cbe1d5398 --- /dev/null +++ b/deployment/terraform/aws/observers/route53.tf @@ -0,0 +1,26 @@ +locals { + enable_routing = var.root_domain_name == "" ? 0 : 1 +} + +data "aws_route53_zone" "this_zone" { + count = local.enable_routing + name = var.root_domain_name +} + +data "aws_region" "current" {} + +resource "aws_route53_record" "on" { + count = local.enable_routing + + zone_id = data.aws_route53_zone.this_zone[0].zone_id + name = "on.${data.aws_route53_zone.this_zone[0].name}" + type = "CNAME" + ttl = "300" + + latency_routing_policy { + region = data.aws_region.current.name + } + + set_identifier = "Observers NLB [${var.region_index}]" + records = ["${aws_lb.this_nlb.dns_name}"] +} \ No newline at end of file diff --git a/deployment/terraform/aws/observers/security.tf b/deployment/terraform/aws/observers/security.tf new file mode 100644 index 000000000..a5e993e1e --- /dev/null +++ b/deployment/terraform/aws/observers/security.tf @@ -0,0 +1,55 @@ +module "this_dev_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "observer-dev-security-group" + description = "Observer nodes security group for development" + + vpc_id = module.this_vpc.vpc_id + + ingress_cidr_blocks = ["0.0.0.0/0"] + ingress_rules = ["all-icmp", "ssh-tcp"] + egress_rules = ["all-all"] +} + +module "this_private_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "observer-private-security-group" + description = "Observer nodes security group for internal connections" + + vpc_id = module.this_vpc.vpc_id + + egress_rules = ["all-all"] + ingress_with_cidr_blocks = [ + { + from_port = 26656 + to_port = 26656 + protocol = "tcp" + description = "Allow p2p from internal IPs" + cidr_blocks = "10.0.0.0/8" + }, + { + from_port = 26657 + to_port = 26657 + protocol = "tcp" + description = "Allow RPC from internal IPs" + cidr_blocks = "10.0.0.0/8" + }, + { + from_port = 9090 + to_port = 9090 + protocol = "tcp" + description = "Allow gRPC from internal IPs" + cidr_blocks = "10.0.0.0/8" + }, + { + from_port = 1317 + to_port = 1317 + protocol = "tcp" + description = "Allow REST from internal IPs" + cidr_blocks = "10.0.0.0/8" + }, + ] +} \ No newline at end of file diff --git a/deployment/terraform/aws/observers/variables.tf b/deployment/terraform/aws/observers/variables.tf new file mode 100644 index 000000000..ceb1eb93c --- /dev/null +++ b/deployment/terraform/aws/observers/variables.tf @@ -0,0 +1,38 @@ +variable "ssh_public_key_path" { + description = "SSH public key file path" + default = "~/.ssh/id_rsa.pub" +} + +variable "ssh_private_key_path" { + description = "SSH private key file path" + default = "~/.ssh/id_rsa" +} + +variable "ssh_username" { + description = "SSH username" + default = "ubuntu" +} + +variable "nodes_count" { + description = "Number of Observer nodes" + default = 5 +} + +variable "region_index" { + description = "Observer Region Index" + default = 0 +} + +variable "enable_tls" { + description = "Enable TLS on LB listeners" + default = false +} + +variable "root_domain_name" { + description = "Root domain name" + default = "" +} + +variable "peer_vpc" { + description = "Peer VPC" +} diff --git a/deployment/terraform/aws/observers/vpc.tf b/deployment/terraform/aws/observers/vpc.tf new file mode 100644 index 000000000..c15b48a0a --- /dev/null +++ b/deployment/terraform/aws/observers/vpc.tf @@ -0,0 +1,22 @@ +data "aws_availability_zones" "available" { + state = "available" +} + +locals { + vpc_network_prefix = "10.${30 + var.region_index}" +} + +module "this_vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "3.14.0" + + name = "observers-vpc-1" + cidr = "${local.vpc_network_prefix}.0.0/16" + + azs = [data.aws_availability_zones.available.names[0], data.aws_availability_zones.available.names[1]] + + public_subnets = ["${local.vpc_network_prefix}.1.0/24", "${local.vpc_network_prefix}.2.0/24"] + + enable_nat_gateway = true + enable_dns_hostnames = true +} diff --git a/deployment/terraform/aws/observers/vpc_peering.tf b/deployment/terraform/aws/observers/vpc_peering.tf new file mode 100644 index 000000000..afc910b27 --- /dev/null +++ b/deployment/terraform/aws/observers/vpc_peering.tf @@ -0,0 +1,21 @@ +module "this_vpc_peerings" { + source = "grem11n/vpc-peering/aws" + version = "4.1.0" + + providers = { + aws.this = aws + aws.peer = aws.peer + } + + this_vpc_id = module.this_vpc.vpc_id + peer_vpc_id = var.peer_vpc.vpc_id + + this_rts_ids = module.this_vpc.public_route_table_ids + peer_rts_ids = [element(var.peer_vpc.public_route_table_ids, 0)] + + auto_accept_peering = true + + tags = { + Name = "Observers to Private Sentries peering" + } +} \ No newline at end of file diff --git a/deployment/terraform/aws/private-sentries/main.tf b/deployment/terraform/aws/private-sentries/main.tf new file mode 100644 index 000000000..98a97396a --- /dev/null +++ b/deployment/terraform/aws/private-sentries/main.tf @@ -0,0 +1,54 @@ +data "aws_ami" "ubuntu" { + most_recent = true + owners = ["099720109477"] + + filter { + name = "name" + values = ["ubuntu-minimal/images/hvm-ssd/ubuntu-focal-20.04-amd64-minimal-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } +} + +resource "aws_key_pair" "key_pair" { + public_key = file(var.ssh_public_key_path) +} + +resource "aws_instance" "this_nodes" { + count = var.nodes_count + + ami = data.aws_ami.ubuntu.id + instance_type = "t3.medium" + + subnet_id = element(module.this_vpc.public_subnets, 0) + vpc_security_group_ids = [ + module.this_dev_sg.security_group_id, + module.this_private_sg.security_group_id, + module.this_public_sg.security_group_id + ] + + key_name = aws_key_pair.key_pair.id + monitoring = true + + tags = { + Name = "Private Sentry Node ${count.index}" + } + + root_block_device { + encrypted = true + volume_size = 30 + } +} + +resource "aws_eip" "this_eip" { + count = length(aws_instance.this_nodes) > 0 ? 1 : 0 + instance = aws_instance.this_nodes[0].id + vpc = true + + tags = { + Name = "Private Sentry Node ${count.index} Elastic IP" + } +} \ No newline at end of file diff --git a/deployment/terraform/aws/private-sentries/outputs.tf b/deployment/terraform/aws/private-sentries/outputs.tf new file mode 100644 index 000000000..12bcae675 --- /dev/null +++ b/deployment/terraform/aws/private-sentries/outputs.tf @@ -0,0 +1,3 @@ +output "vpc" { + value = module.this_vpc +} \ No newline at end of file diff --git a/deployment/terraform/aws/private-sentries/provider.tf b/deployment/terraform/aws/private-sentries/provider.tf new file mode 100644 index 000000000..9abe0ebfd --- /dev/null +++ b/deployment/terraform/aws/private-sentries/provider.tf @@ -0,0 +1,9 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.72" + configuration_aliases = [aws, aws.peer] + } + } +} \ No newline at end of file diff --git a/deployment/terraform/aws/private-sentries/security.tf b/deployment/terraform/aws/private-sentries/security.tf new file mode 100644 index 000000000..60269d6a3 --- /dev/null +++ b/deployment/terraform/aws/private-sentries/security.tf @@ -0,0 +1,62 @@ +module "this_dev_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "private-sentry-dev-security-group" + description = "Private Sentry nodes security group for development" + vpc_id = module.this_vpc.vpc_id + + ingress_cidr_blocks = ["0.0.0.0/0"] + ingress_rules = ["all-icmp", "ssh-tcp"] + egress_rules = ["all-all"] +} + +module "this_private_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "private-sentry-private-security-group" + description = "Private Sentry nodes security group for internal connections" + vpc_id = module.this_vpc.vpc_id + + egress_rules = ["all-all"] + ingress_with_cidr_blocks = [ + { + from_port = 26656 + to_port = 26656 + protocol = "tcp" + description = "Allow p2p from internal IPs" + cidr_blocks = "10.0.0.0/8" + }, + { + from_port = 26657 + to_port = 26657 + protocol = "tcp" + description = "Allow RPC from internal IPs" + cidr_blocks = "10.0.0.0/8" + }, + ] +} + +module "this_public_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "private-sentry-public-security-group" + description = "Private Sentry nodes security group for external connections" + vpc_id = module.this_vpc.vpc_id + + # ingress_cidr_blocks = ["10.0.0.0/8"] + egress_rules = ["all-all"] + + + ingress_with_cidr_blocks = [ + { + from_port = 26656 + to_port = 26656 + protocol = "tcp" + description = "Allow P2P from Some Organization" + cidr_blocks = "10.1.1.1/32" # whitelist IP + }, + ] +} \ No newline at end of file diff --git a/deployment/terraform/aws/private-sentries/variables.tf b/deployment/terraform/aws/private-sentries/variables.tf new file mode 100644 index 000000000..a29d22eb9 --- /dev/null +++ b/deployment/terraform/aws/private-sentries/variables.tf @@ -0,0 +1,23 @@ +variable "ssh_public_key_path" { + description = "SSH public key file path" + default = "~/.ssh/id_rsa.pub" +} + +variable "ssh_private_key_path" { + description = "SSH private key file path" + default = "~/.ssh/id_rsa" +} + +variable "ssh_username" { + description = "SSH username" + default = "ubuntu" +} + +variable "peer_vpc" { + description = "Peer VPC" +} + +variable "nodes_count" { + description = "Number of Private Sentry nodes" + default = 2 +} \ No newline at end of file diff --git a/deployment/terraform/aws/private-sentries/vpc.tf b/deployment/terraform/aws/private-sentries/vpc.tf new file mode 100644 index 000000000..56412bda2 --- /dev/null +++ b/deployment/terraform/aws/private-sentries/vpc.tf @@ -0,0 +1,22 @@ +data "aws_availability_zones" "available" { + state = "available" +} + +locals { + vpc_network_prefix = "10.10" +} + +module "this_vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "3.14.0" + + name = "private-sentries-vpc" + cidr = "${local.vpc_network_prefix}.0.0/16" + + azs = [data.aws_availability_zones.available.names[0]] + + public_subnets = ["${local.vpc_network_prefix}.1.0/24"] + + enable_nat_gateway = true + enable_dns_hostnames = true +} diff --git a/deployment/terraform/aws/private-sentries/vpc_peering.tf b/deployment/terraform/aws/private-sentries/vpc_peering.tf new file mode 100644 index 000000000..f8aa45dcc --- /dev/null +++ b/deployment/terraform/aws/private-sentries/vpc_peering.tf @@ -0,0 +1,21 @@ +module "this_vpc_peering" { + source = "grem11n/vpc-peering/aws" + version = "4.1.0" + + providers = { + aws.this = aws + aws.peer = aws.peer + } + + this_vpc_id = module.this_vpc.vpc_id + peer_vpc_id = var.peer_vpc.vpc_id + + this_rts_ids = [element(module.this_vpc.public_route_table_ids, 0)] + peer_rts_ids = [element(var.peer_vpc.public_route_table_ids, 0)] + + auto_accept_peering = true + + tags = { + Name = "Private Sentries to Validator VPC peering" + } +} \ No newline at end of file diff --git a/deployment/terraform/aws/public-sentries/main.tf b/deployment/terraform/aws/public-sentries/main.tf new file mode 100644 index 000000000..ea06eba57 --- /dev/null +++ b/deployment/terraform/aws/public-sentries/main.tf @@ -0,0 +1,92 @@ +data "aws_ami" "ubuntu" { + most_recent = true + owners = ["099720109477"] + + filter { + name = "name" + values = ["ubuntu-minimal/images/hvm-ssd/ubuntu-focal-20.04-amd64-minimal-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } +} + +resource "aws_key_pair" "key_pair" { + public_key = file(var.ssh_public_key_path) +} + +resource "aws_instance" "this_nodes" { + count = var.nodes_count + + ami = data.aws_ami.ubuntu.id + instance_type = "t3.medium" + + subnet_id = element(module.this_vpc.public_subnets, 0) + ipv6_address_count = var.enable_ipv6 ? 1 : 0 + + vpc_security_group_ids = [ + module.this_dev_sg.security_group_id, + module.this_public_sg.security_group_id + ] + + key_name = aws_key_pair.key_pair.id + monitoring = true + + tags = { + Name = "Public Sentry Node ${count.index}" + } + + root_block_device { + encrypted = true + volume_size = 30 + } +} + +resource "aws_instance" "this_seed_node" { + ami = data.aws_ami.ubuntu.id + instance_type = "t3.medium" + + subnet_id = element(module.this_vpc.public_subnets, 0) + ipv6_address_count = var.enable_ipv6 ? 1 : 0 + + vpc_security_group_ids = [ + module.this_dev_sg.security_group_id, + module.this_seed_sg.security_group_id + ] + + key_name = aws_key_pair.key_pair.id + monitoring = true + + tags = { + Name = "Public Sentries' Seed Node" + } + + root_block_device { + encrypted = true + volume_size = 30 + } +} + +resource "aws_eip" "this_nodes_eips" { + count = var.enable_ipv6 ? 0 : length(aws_instance.this_nodes) + + instance = aws_instance.this_nodes[count.index].id + vpc = true + + tags = { + Name = "Public Sentry Node [${count.index}] Elastic IP" + } +} + +resource "aws_eip" "this_seed_eip" { + count = var.enable_ipv6 ? 0 : 1 + + instance = aws_instance.this_seed_node.id + vpc = true + + tags = { + Name = "Public Sentries' Seed Node Elastic IP" + } +} \ No newline at end of file diff --git a/deployment/terraform/aws/public-sentries/outputs.tf b/deployment/terraform/aws/public-sentries/outputs.tf new file mode 100644 index 000000000..e69de29bb diff --git a/deployment/terraform/aws/public-sentries/provider.tf b/deployment/terraform/aws/public-sentries/provider.tf new file mode 100644 index 000000000..9abe0ebfd --- /dev/null +++ b/deployment/terraform/aws/public-sentries/provider.tf @@ -0,0 +1,9 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.72" + configuration_aliases = [aws, aws.peer] + } + } +} \ No newline at end of file diff --git a/deployment/terraform/aws/public-sentries/security.tf b/deployment/terraform/aws/public-sentries/security.tf new file mode 100644 index 000000000..aca46045d --- /dev/null +++ b/deployment/terraform/aws/public-sentries/security.tf @@ -0,0 +1,80 @@ +module "this_dev_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "public-sentry-dev-security-group" + description = "Public Sentry nodes security group for development" + vpc_id = module.this_vpc.vpc_id + + ingress_cidr_blocks = ["0.0.0.0/0"] + ingress_ipv6_cidr_blocks = ["::/0"] + ingress_rules = ["all-icmp", "ssh-tcp"] + egress_rules = ["all-all"] +} + +module "this_public_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "public-sentry-public-security-group" + description = "Public Sentry nodes security group for external connections" + vpc_id = module.this_vpc.vpc_id + + egress_rules = ["all-all"] + ingress_with_cidr_blocks = [ + { + from_port = 26656 + to_port = 26656 + protocol = "tcp" + description = "Allow p2p from all external IPs" + cidr_blocks = "0.0.0.0/0" + }, + { + from_port = 26657 + to_port = 26657 + protocol = "tcp" + description = "Allow RPC from all external IPs" + cidr_blocks = "0.0.0.0/0" + }, + ] + + ingress_with_ipv6_cidr_blocks = [ + { + from_port = 26656 + to_port = 26656 + protocol = "tcp" + description = "Allow p2p from all external IPs" + ipv6_cidr_blocks = "::/0" + }, + { + from_port = 26657 + to_port = 26657 + protocol = "tcp" + description = "Allow RPC from all external IPs" + ipv6_cidr_blocks = "::/0" + }, + ] +} + +module "this_seed_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "public-sentries-seed-security-group" + description = "Public Sentries Seed node security group for external connections" + vpc_id = module.this_vpc.vpc_id + + # ingress_cidr_blocks = ["10.0.0.0/8"] + egress_rules = ["all-all"] + + + ingress_with_cidr_blocks = [ + { + from_port = 26656 + to_port = 26656 + protocol = "tcp" + description = "Allow P2P from all" + cidr_blocks = "0.0.0.0/0" + }, + ] +} \ No newline at end of file diff --git a/deployment/terraform/aws/public-sentries/variables.tf b/deployment/terraform/aws/public-sentries/variables.tf new file mode 100644 index 000000000..70e82e716 --- /dev/null +++ b/deployment/terraform/aws/public-sentries/variables.tf @@ -0,0 +1,33 @@ +variable "ssh_public_key_path" { + description = "SSH public key file path" + default = "~/.ssh/id_rsa.pub" +} + +variable "ssh_private_key_path" { + description = "SSH private key file path" + default = "~/.ssh/id_rsa" +} + +variable "ssh_username" { + description = "SSH username" + default = "ubuntu" +} + +variable "nodes_count" { + description = "Number of Public Sentry nodes" + default = 2 +} + +variable "region_index" { + description = "Public Sentries Region Index" + default = 0 +} + +variable "enable_ipv6" { + description = "Enable public IPv6 addresses" + default = true +} + +variable "peer_vpc" { + description = "Peer VPC" +} \ No newline at end of file diff --git a/deployment/terraform/aws/public-sentries/vpc.tf b/deployment/terraform/aws/public-sentries/vpc.tf new file mode 100644 index 000000000..4db3769bc --- /dev/null +++ b/deployment/terraform/aws/public-sentries/vpc.tf @@ -0,0 +1,26 @@ +data "aws_availability_zones" "available" { + state = "available" +} + +locals { + vpc_network_prefix = "10.${20 + var.region_index}" +} + +module "this_vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "3.14.0" + + name = "public-sentries-vpc" + cidr = "${local.vpc_network_prefix}.0.0/16" + + enable_ipv6 = var.enable_ipv6 + public_subnet_assign_ipv6_address_on_creation = var.enable_ipv6 + public_subnet_ipv6_prefixes = var.enable_ipv6 ? [1] : [] + + azs = [data.aws_availability_zones.available.names[0]] + + public_subnets = ["${local.vpc_network_prefix}.1.0/24"] + + enable_nat_gateway = true + enable_dns_hostnames = true +} diff --git a/deployment/terraform/aws/public-sentries/vpc_peering.tf b/deployment/terraform/aws/public-sentries/vpc_peering.tf new file mode 100644 index 000000000..7b6d904f1 --- /dev/null +++ b/deployment/terraform/aws/public-sentries/vpc_peering.tf @@ -0,0 +1,21 @@ +module "this_vpc_peering" { + source = "grem11n/vpc-peering/aws" + version = "4.1.0" + + providers = { + aws.this = aws + aws.peer = aws.peer + } + + this_vpc_id = module.this_vpc.vpc_id + peer_vpc_id = var.peer_vpc.vpc_id + + this_rts_ids = [element(module.this_vpc.public_route_table_ids, 0)] + peer_rts_ids = [element(var.peer_vpc.public_route_table_ids, 0)] + + auto_accept_peering = true + + tags = { + Name = "Public Sentries to Private Sentries VPC peering" + } +} \ No newline at end of file diff --git a/deployment/terraform/aws/validator/main.tf b/deployment/terraform/aws/validator/main.tf new file mode 100644 index 000000000..71252462d --- /dev/null +++ b/deployment/terraform/aws/validator/main.tf @@ -0,0 +1,41 @@ +data "aws_ami" "ubuntu" { + most_recent = true + owners = ["099720109477"] + + filter { + name = "name" + values = ["ubuntu-minimal/images/hvm-ssd/ubuntu-focal-20.04-amd64-minimal-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } +} + +resource "aws_key_pair" "key_pair" { + public_key = file(var.ssh_public_key_path) +} + +resource "aws_instance" "this_node" { + ami = data.aws_ami.ubuntu.id + instance_type = "t3.medium" + + subnet_id = element(module.this_vpc.public_subnets, 0) + vpc_security_group_ids = [ + module.this_dev_sg.security_group_id, + module.this_private_sg.security_group_id + ] + + key_name = aws_key_pair.key_pair.id + monitoring = true + + tags = { + Name = "Validator Node" + } + + root_block_device { + encrypted = true + volume_size = 30 + } +} \ No newline at end of file diff --git a/deployment/terraform/aws/validator/outputs.tf b/deployment/terraform/aws/validator/outputs.tf new file mode 100644 index 000000000..12bcae675 --- /dev/null +++ b/deployment/terraform/aws/validator/outputs.tf @@ -0,0 +1,3 @@ +output "vpc" { + value = module.this_vpc +} \ No newline at end of file diff --git a/deployment/terraform/aws/validator/provider.tf b/deployment/terraform/aws/validator/provider.tf new file mode 100644 index 000000000..95016b08a --- /dev/null +++ b/deployment/terraform/aws/validator/provider.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.72" + } + } +} \ No newline at end of file diff --git a/deployment/terraform/aws/validator/security.tf b/deployment/terraform/aws/validator/security.tf new file mode 100644 index 000000000..fd5eac6bc --- /dev/null +++ b/deployment/terraform/aws/validator/security.tf @@ -0,0 +1,38 @@ +module "this_dev_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "validator-dev-security-group" + description = "Validator security group for development" + vpc_id = module.this_vpc.vpc_id + + ingress_cidr_blocks = ["0.0.0.0/0"] + ingress_rules = ["all-icmp", "ssh-tcp"] + egress_rules = ["all-all"] +} +module "this_private_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "validator-private-security-group" + description = "Validator node security group for internal connections" + vpc_id = module.this_vpc.vpc_id + + egress_rules = ["all-all"] + ingress_with_cidr_blocks = [ + { + from_port = 26656 + to_port = 26656 + protocol = "tcp" + description = "Allow p2p from internal IPs" + cidr_blocks = "10.0.0.0/8" + }, + { + from_port = 26657 + to_port = 26657 + protocol = "tcp" + description = "Allow RPC from internal IPs" + cidr_blocks = "10.0.0.0/8" + }, + ] +} \ No newline at end of file diff --git a/deployment/terraform/aws/validator/variables.tf b/deployment/terraform/aws/validator/variables.tf new file mode 100644 index 000000000..552f80a43 --- /dev/null +++ b/deployment/terraform/aws/validator/variables.tf @@ -0,0 +1,14 @@ +variable "ssh_public_key_path" { + description = "SSH public key file path" + default = "~/.ssh/id_rsa.pub" +} + +variable "ssh_private_key_path" { + description = "SSH private key file path" + default = "~/.ssh/id_rsa" +} + +variable "ssh_username" { + description = "SSH username" + default = "ubuntu" +} \ No newline at end of file diff --git a/deployment/terraform/aws/validator/vpc.tf b/deployment/terraform/aws/validator/vpc.tf new file mode 100644 index 000000000..628cc1fdf --- /dev/null +++ b/deployment/terraform/aws/validator/vpc.tf @@ -0,0 +1,22 @@ +data "aws_availability_zones" "available" { + state = "available" +} + +locals { + vpc_network_prefix = "10.0" +} + +module "this_vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "3.14.0" + + name = "validator-vpc" + cidr = "${local.vpc_network_prefix}.0.0/16" + + azs = [data.aws_availability_zones.available.names[0]] + + public_subnets = ["${local.vpc_network_prefix}.1.0/24"] + + enable_nat_gateway = true + enable_dns_hostnames = true +} \ No newline at end of file diff --git a/deployment/terraform/aws/variables.tf b/deployment/terraform/aws/variables.tf new file mode 100644 index 000000000..9d43acb89 --- /dev/null +++ b/deployment/terraform/aws/variables.tf @@ -0,0 +1,19 @@ +variable "region_1" { + description = "AWS Region 1" + default = "us-west-1" +} + +variable "region_2" { + description = "AWS Region 2" + default = "us-east-2" +} + +variable "root_domain_name" { + description = "Root domain name for dcl observer endpoints" + default = "" +} + +variable "enable_tls" { + description = "Enable tls for observer endpoints" + default = false +} \ No newline at end of file diff --git a/docs/deployment-aws.png b/docs/deployment-aws.png index 102869644..1dcd8e954 100644 Binary files a/docs/deployment-aws.png and b/docs/deployment-aws.png differ diff --git a/docs/deployment-design-aws.md b/docs/deployment-design-aws.md index b5c0f70bc..de5d85344 100644 --- a/docs/deployment-design-aws.md +++ b/docs/deployment-design-aws.md @@ -29,28 +29,32 @@ - Tendermint: -`config.toml` file: + `config.toml` file: -```toml -[p2p] -pex = false -persistent_peers = # `Private Sentry` nodes with private IPs -addr_book_strict = false + ```toml + [p2p] + pex = false + persistent_peers = # `Private Sentry` nodes with private IPs + addr_book_strict = false -[statesync] # only for `Non-genesis Validator` nodes -enable = true -rpc_servers = # existing `Genesis Validator` / `Sentry` nodes' RPC endpoints -trust_height = "trust-height" -trust_hash = "trust-hash" -``` + [statesync] # only for `Non-genesis Validator` nodes + enable = true + rpc_servers = # existing `Genesis Validator` / `Sentry` nodes' RPC endpoints + trust_height = "trust-height" + trust_hash = "trust-hash" -`app.toml` file: + [consensus] + create_empty_blocks = false + create_empty_blocks_interval = "600s" # 10 mins + ``` -```toml -[state-sync] -snapshot-interval = "snapshot-interval" -snapshot-keep-recent = "snapshot-keep-recent" -``` + `app.toml` file: + + ```toml + [state-sync] + snapshot-interval = "snapshot-interval" + snapshot-keep-recent = "snapshot-keep-recent" + ``` - AWS: - Instance type = EC2 instance @@ -68,30 +72,30 @@ snapshot-keep-recent = "snapshot-keep-recent" - Tendermint: -`config.toml` file: + `config.toml` file: -```toml -[p2p] -pex = true -persistent_peers = # `Validator` node with private IP + other orgs' validator/sentry nodes with public IPs -private_peer_ids = # `Validator` node id -unconditional_peers = # `Validator` node id -addr_book_strict = false + ```toml + [p2p] + pex = true + persistent_peers = # `Validator` node with private IP + other orgs' validator/sentry nodes with public IPs + private_peer_ids = # `Validator` node id + unconditional_peers = # `Validator` node id + addr_book_strict = false -[statesync] -enable = true -rpc_servers = # `Validator` node's RPC endpoint -trust_height = "trust-height" -trust_hash = "trust-hash" -``` + [statesync] + enable = true + rpc_servers = # `Validator` node's RPC endpoint + trust_height = "trust-height" + trust_hash = "trust-hash" + ``` -`app.toml` file: + `app.toml` file: -```toml -[state-sync] -snapshot-interval = "snapshot-interval" -snapshot-keep-recent = "snapshot-keep-recent" -``` + ```toml + [state-sync] + snapshot-interval = "snapshot-interval" + snapshot-keep-recent = "snapshot-keep-recent" + ``` - AWS: - Instance type = EC2 instance @@ -110,27 +114,27 @@ snapshot-keep-recent = "snapshot-keep-recent" - Tendermint: -`config.toml` file: + `config.toml` file: -```toml -[p2p] -pex = true -persistent_peers = # `Private Sentry` nodes with private IPs -addr_book_strict = false + ```toml + [p2p] + pex = true + persistent_peers = # `Private Sentry` nodes with private IPs + addr_book_strict = false -[statesync] -enable = true -rpc_servers = # `Private Sentry` nodes' RPC endpoints -trust_height = "trust-height" -trust_hash = "trust-hash" -``` + [statesync] + enable = true + rpc_servers = # `Private Sentry` nodes' RPC endpoints + trust_height = "trust-height" + trust_hash = "trust-hash" + ``` -`app.toml` file: + `app.toml` file: -```toml -[api] -enable = true -``` + ```toml + [api] + enable = true + ``` - AWS: - Instance type = EC2 instance @@ -147,27 +151,27 @@ enable = true - Tendermint: -`config.toml` file: + `config.toml` file: -```toml -[p2p] -pex = true -persistent_peers = # `Private Sentry` nodes with private IPs + ```toml + [p2p] + pex = true + persistent_peers = # `Private Sentry` nodes with private IPs -[statesync] -enable = true -rpc_servers = # `Private Sentry` nodes' RPC endpoints -trust_height = "trust-height" -trust_hash = "trust-hash" -``` + [statesync] + enable = true + rpc_servers = # `Private Sentry` nodes' RPC endpoints + trust_height = "trust-height" + trust_hash = "trust-hash" + ``` -`app.toml` file: + `app.toml` file: -```toml -[state-sync] -snapshot-interval = "snapshot-interval" -snapshot-keep-recent = "snapshot-keep-recent" -``` + ```toml + [state-sync] + snapshot-interval = "snapshot-interval" + snapshot-keep-recent = "snapshot-keep-recent" + ``` - AWS: - Instance type = EC2 instance @@ -185,20 +189,20 @@ snapshot-keep-recent = "snapshot-keep-recent" - Tendermint: -`config.toml` file: + `config.toml` file: -```toml -[p2p] -pex = true -seed_mode = true -persistent_peers = # `Public Sentry` nodes with public IP + ```toml + [p2p] + pex = true + seed_mode = true + persistent_peers = # `Public Sentry` nodes with public IP -[statesync] -enable = true -rpc_servers = # `Private Sentry` nodes' RPC endpoints -trust_height = "trust-height" -trust_hash = "trust-hash" -``` + [statesync] + enable = true + rpc_servers = # `Private Sentry` nodes' RPC endpoints + trust_height = "trust-height" + trust_hash = "trust-hash" + ``` - AWS: - Instance type = EC2 instance diff --git a/docs/design/deployment-aws.drawio b/docs/design/deployment-aws.drawio index a39809421..06432b2f9 100644 --- a/docs/design/deployment-aws.drawio +++ b/docs/design/deployment-aws.drawio @@ -1 +1 @@ -7V1bc6M4Fv41qd19SEoS90fnNrtbPT2pZGe6al9c2FZsprHxAM5lf/1KgDC6YDsON8eqTlUbGQvQ+c5Fn44OF8bN8u2X2F8vfo1mOLxAYPZ2YdxeIGQA1yD/0Zb3vAVC18pb5nEwK9q2DU/B/3DRCIrWTTDDCXdiGkVhGqz5xmm0WuFpyrX5cRy98qc9RyF/1bU/x1LD09QP5dYfwSxdsMewve0X/8TBfFFc2kVO/sXSZycXT5Is/Fn0Wmky7i6MmziK0vzT8u0Gh3T02Ljkv7uv+ba8sRiv0kN+8NePxPnl33+Oxw+T5PffJ+Dlr1dwCYu7ffHDTfHEox9PpOEmjDaz4sbTdzYa6yhYpdmIWtfkj1zwBlxY5JsbenSFLKFBPHb4Bigf0T74BvHY4Rug2D0Urg/FG6w0SEdc90C4PqjcIPkzrqNNGgYrfFNiD5DGeezPAiKTmyiMYtK2ilZk9K4X6TIkR5B8fF0EKX5a+1M6qq9EcUjbc7RKC/RDxI6Lgae9EnynPrlWXBxnksDx3QvOBZKfE4b+Ogkm5a9iPN3ESfCCH3GSd05bCRLX9PPybU619sp/TcyreRxt1tnt/4tcS/ntmHwcTykwxn6Y0o7SOPqJ2YNeIIP8u6fou34OwlAYgBccpwFRrFEYzGn/aUQv5xdHIX7OeiSjEqzm37KjWwMUI6G6xMxPFnhWPFKBYnIJ/FarH7DUOmKvcLTEafxOTmHGqtDowlKh4vC1ovXAKXpdVDQeWqzVL2zNvOx7q47kQ6GRH9FO5ErqOX98IJ0B2jECj3dP/9ke0C/sTDCTmHya0083IcWipMgMAptlOJqmdGxL+XzzJzh8iJIgDTIcTKI0jZa1AqzgWqkOOUaY7UTNyAoazD8U0nIs78qSBGYDhbjYL1uQlidJSxp4vJqNqFsiR9PQT5Jgmo2RH6dyc2VoifqtZiXa5TGt6htREjjxIUYqDQXAvhvdl1LAM8n9CTIgdx9t4ine9eCw8PLkMeY43Q9oWa4VoVkKobG2GId+SswZ7+YVgiyu8ECtZAU20OBh40KL7yN/1uJnVWcq9iQCUOopHwuppwxZ5YN/AmymDLbb70/ZGCVRuMmUF4GJnxDUIJAdrOPoLVgG6buMSgKDTO951DHjPMXU1yhMwDKYzWgfxNEQ/+JvHQ8fKNzu0voicit+vI2XqkjcpW+1RgJcQdf1GgEOuRYnbTL8fBfR83OC2xE080uCuFhwEMXpIppHKz+827YK9mJ7zreIGu1MuH/iNH0vQg5/k0a86GUTU29KasxX1nwfhKzLOhvVpCUybDUkPil/ZPLaDk1X8CKtq7scCTxsJiEZaHIP0ylOki+i1MZupb4kAbjN5ljHSrUPLYaS/H5LF2T8Efgtnv8t0VOuQU+5YO9Trq823TJtizeptgOkAL60u9VgsGxsPoLP3QevpZMExy+Zon6PKBUlTaV+4nS6YJ5WBc+6Ia2BreAnb02iBo4k/uJkTixHTuEkj1BRnH1K4ifr/EGfgzd6H5KWwDr0BysSIKymlNw7DE+7DGstzDzjiseZJ6OM+dYqyFhb8xgzkfYE2hNoT7AN6UqdZGQO7N8TmIakpU/kebL7LvwA+Hu0pgbWD/+hncKwnEJuY+udgi3M5nr3CaaEtj+IAGY+pWZzwGmINQyxg6no4yDGmzSrd4RZEsJkwqATbhpbRN+AhCvyzTUCAGydSjOMUJ2cumGcLyESgCASRwczzs6ejmoYKCI6/71yWhFF7rjjmgtt4Zd32TC/Jc+87oMYv/rZb7OgLQySlIQoF8j2l9R8rCbJugTLyfNeuYLu4r3YxKGQi9EIQJENuV4vHb6HFikxS6bEejJJ3S6XWQXUh75a5nouDzjPvfKOXi9zhRjfOcx8NYc2edo9AAI9jlK/CJZ2zqaOtyvW7slXg4tkYgKFAJU2LYk8WTsLSwLBiZgST9B+z/SuPM8CyHYMx7OFqPdgo2I6vEuEBjSvDA84pmvbBgRdr8lbchAzABvThlEx9hgVy+LDCtQIjC4NfuJuoK5MDHuAimQf8TzPsRhJQtVM7RCY2t7SJOMcGQonQCzW3fWoVZq2vETJ0MBmaFqHd+/ssOJAXJUHscV5amOcBos3dnr9LjJlDsp+adLtmwd6fVRjpg82w5/knCT5/EFTVLWxHKCx7G1Z62U9VS5oma5LANSmpRyNrp1rt4UFLSjMhKDlSrYSAdV6FmjNVioi0zh48VNMlXEzWWE5R1wr5lkrZkK7C9L38fbkp0xLubnwvuCGtN/Z9+jebTPCOVhvd/ip+kQlfuqqWCssOX5unwFqS5dlVdZrhYNeK6xLSmYAQ0KK0rahAjLDkjFmWC1BzJCzjc8ktHZPIrRW5Ato+fAjVBOidSMfy9Ti4Afks+I4ai3bBHwgbvFbpFtaygZaF/kTvZq1h3aFbwiZF26xPbNl4Vta+MKIfNZTHiV9JKw4QWR0oftIG35xREAv4hdmbSzdoG4pcc/5LaFFjrI1Qap5mPMjSMvdtD0SpHIhnHLpXvOjWi8b4EeVGkv5Ue/euLObU9vyOk3xozXhO4uzBfJKxY8quKv2+FHWsXKtQ9zEo4nSAe3byY1wPVHK5xSxNa2+9lQgOeswC9/AGuM4S1TvGFzmLXJGo68HLjaejcQeCHgC3W64roJvtzwZSqyteT4XSVA6j3mkcWgqS798rqFTWXREeI4zNdMZ3EyNRQHa7Z6S2xX3/5QJ4lWXq4jerLaiN8WG2NGLH4T+JAizEnngv1SACMhmXqHH9ckwXBquWryiyOryXZrQZ0vQZ1ZGcG+pBbst31oW5PzqsY556GYd67NF7D6nGDs2sWgmTMc958GE7TajFrSFSSRLLeiR9zJlClsXLxs032XuSQwcFt/Fdtl+eT99WklNRhdJTYrys5oA0YHA+REgA9jLo6j8rgN0rZdnFqDvcFP1JJQj7rToPWhnW6Z10H4yQfvuAvVDC9o1W/4F2HJDUbW6U7Ycwl4Smxuc1R1edQ32krAOARLmdV4H+xUUFYu+4/Q1in+Sxm+RPyP/EYWiVjPWpqIhU7HKR3gckvEdT9joNmI4XGHTi2HLhsNRGA6nLcPhypOVsnCSXLpTz1POep5yFoWTXEUKhbpyEvth894cnPg2pZ2mZm8owIKpoe5ds4Awh7A6YHg9eSKqGV5tob8Cw7vDWtRP4wEfSJmqd5J0y/tC9hLKM7Xa6GCr3Wv+DLtPTc9ro6rp+Q/ZXJmfV7w3BaKyPBJH0Rtt2V1P3uejKfpBU/S5CT4Vit6TKXp1JrLMlww6E/kY0Vxaogm4VO046DZD2dO7f7QX11OjuqmR23tGDHsrjQ65tbLqkFvhu04pI8bT7+A+tXB796vrBhZuF6S+TolpHV0fTIk5JhYxxKUS1nFviTJAZ1ScWEbFMbhzRKMGZNx1mmcBIZTQdE6LA0WIMvjFAXmPnprrkV8qdnpczz7bLZYEkFVITfSIrw5tTolYpDtIJeJfPtmJWkHoHKpX/dR5hcLri4Uqvx89v6U6r1DWek0gak7iXAlE9irmMrVCrr6GoKL8GhLf59pg9CTvb9UMotZWzSCW3qtWm5FYTxGVnGKVRLQVkVzZ2IJC79gkq2vADphMhHvKDVsODzbFm7JMGWmsrYUpg8z3PGE8u2BvYrNDqraTmHyap+XIaLg1BjfUKtxsU7BurgJwUAG41sqlKcpba/r6ROlryN5gz6pL9L3NEx3KT51YVcSj8hwEiteRhYMM1TSltTeeezVVeLTqn9Jmbhf0reXsBgZJc17sJzUP3fPtHVpdvJ831FkiMLp5Q513HsJnZZIGXm7VOrQO8Yll/36sDjFUkgadVyKWM0Meideko68ofqid63HONaZDOraMcYyTKHxpqvSB5/HW1HEsBaQ6XpTvZT2xQSNaprN0UDblc0Xx5Fnxl1hY3435kub8uBm1jpilkMM4itJqQELV+tdohukZ/wc= \ No newline at end of file +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 \ No newline at end of file