- Introduced Aggregator.sol for Chainlink-compatible oracle functionality, including round-based updates and access control. - Added OracleWithCCIP.sol to extend Aggregator with CCIP cross-chain messaging capabilities. - Created .gitmodules to include OpenZeppelin contracts as a submodule. - Developed a comprehensive deployment guide in NEXT_STEPS_COMPLETE_GUIDE.md for Phase 2 and smart contract deployment. - Implemented Vite configuration for the orchestration portal, supporting both Vue and React frameworks. - Added server-side logic for the Multi-Cloud Orchestration Portal, including API endpoints for environment management and monitoring. - Created scripts for resource import and usage validation across non-US regions. - Added tests for CCIP error handling and integration to ensure robust functionality. - Included various new files and directories for the orchestration portal and deployment scripts.
485 lines
16 KiB
HCL
485 lines
16 KiB
HCL
# Phase 1: Initial Deployment - 5 US Commercial Azure Regions
|
|
# DeFi Oracle Meta Mainnet (ChainID 138)
|
|
#
|
|
# Architecture:
|
|
# - West Europe: Admin/control-plane only (no workload)
|
|
# - 5 US Commercial Azure regions: VMs with Standard_D8plsv6
|
|
# - Ubuntu 22.04 Gen 2, Docker Engine, NVM, Node 22 LTS, JDK 17
|
|
# - Nginx Proxy Server to connect Cloudflare to the 5 regions
|
|
# - SSH access for management
|
|
#
|
|
# NOTE: Phase 3 (36-region global AKS deployment) is archived in phases/phase3/
|
|
|
|
terraform {
|
|
required_version = ">= 1.0"
|
|
|
|
required_providers {
|
|
azurerm = {
|
|
source = "hashicorp/azurerm"
|
|
version = "~> 3.0"
|
|
}
|
|
}
|
|
|
|
# Backend configuration is in backend.tf (separate file)
|
|
}
|
|
|
|
provider "azurerm" {
|
|
features {
|
|
resource_group {
|
|
prevent_deletion_if_contains_resources = var.environment == "prod" ? true : false
|
|
}
|
|
key_vault {
|
|
purge_soft_delete_on_destroy = var.environment == "prod" ? false : true
|
|
recover_soft_deleted_key_vaults = true
|
|
}
|
|
}
|
|
}
|
|
|
|
# Local values for naming and configuration
|
|
locals {
|
|
cloud_provider = "az"
|
|
|
|
env_codes = {
|
|
prod = "p"
|
|
dev = "d"
|
|
test = "t"
|
|
staging = "s"
|
|
}
|
|
|
|
env_code = local.env_codes[var.environment]
|
|
|
|
# 5 US Commercial Azure regions for Phase 1
|
|
us_regions = [
|
|
"eastus",
|
|
"westus",
|
|
"centralus",
|
|
"eastus2",
|
|
"westus2"
|
|
]
|
|
|
|
# Region code mapping for US regions
|
|
us_region_codes = {
|
|
eastus = "eus"
|
|
westus = "wus"
|
|
centralus = "cus"
|
|
eastus2 = "eus2"
|
|
westus2 = "wus2"
|
|
}
|
|
|
|
# Region-specific address spaces (to avoid conflicts if VPN/ExpressRoute connects regions)
|
|
region_address_spaces = {
|
|
eastus = "10.1.0.0/16"
|
|
westus = "10.2.0.0/16"
|
|
centralus = "10.3.0.0/16"
|
|
eastus2 = "10.4.0.0/16"
|
|
westus2 = "10.5.0.0/16"
|
|
westeurope = "10.10.0.0/16"
|
|
}
|
|
|
|
# Region-specific subnet prefixes
|
|
region_subnet_prefixes = {
|
|
eastus = "10.1.1.0/24"
|
|
westus = "10.2.1.0/24"
|
|
centralus = "10.3.1.0/24"
|
|
eastus2 = "10.4.1.0/24"
|
|
westus2 = "10.5.1.0/24"
|
|
westeurope = "10.10.1.0/24"
|
|
}
|
|
|
|
# Phase 1 configuration for each US region
|
|
# NOTE: Standard_D8pls_v6 is ARM64-only, but we're using x64 Ubuntu image
|
|
# Using Standard_D8s_v6 (x64) for compatibility with x64 Ubuntu 22.04 Gen 2 image
|
|
# East US has capacity restrictions for D8s_v6, using D8s_v5 as fallback
|
|
phase1_config = {
|
|
for region in local.us_regions : region => {
|
|
location = region
|
|
region_code = lookup(local.us_region_codes, region, substr(region, 0, 3))
|
|
# Use Standard_D8s_v4 for eastus (quota restrictions), Standard_D8s_v6 for others
|
|
vm_size = region == "eastus" ? "Standard_D8s_v4" : "Standard_D8s_v6"
|
|
vm_count = 1 # 1 VM per region for Phase 1
|
|
}
|
|
}
|
|
|
|
# Common tags
|
|
common_tags = merge(var.tags, {
|
|
Environment = var.environment
|
|
CostCenter = "Blockchain"
|
|
Owner = "DevOps Team"
|
|
NamingConvention = "az-env-region-resource-instance"
|
|
DeploymentPhase = "phase1"
|
|
Project = "DeFi Oracle Meta Mainnet"
|
|
ChainID = "138"
|
|
})
|
|
|
|
# Naming for admin resources (West Europe)
|
|
name_prefix = "${local.cloud_provider}-${local.env_code}-wst"
|
|
kv_secrets = "${local.name_prefix}-kv-secrets-001"
|
|
resource_group_name = var.resource_group_name != "" ? var.resource_group_name : "${local.cloud_provider}-${local.env_code}-wst-rg-comp-001"
|
|
}
|
|
|
|
# Resource Groups for each US region
|
|
resource "azurerm_resource_group" "phase1_us_regions" {
|
|
for_each = local.phase1_config
|
|
|
|
name = "${local.cloud_provider}-${local.env_code}-${each.value.region_code}-rg-comp-001"
|
|
location = each.value.location
|
|
|
|
tags = merge(local.common_tags, {
|
|
Region = each.value.location
|
|
Deployment = "phase1-us-regions"
|
|
})
|
|
}
|
|
|
|
# Storage accounts for boot diagnostics (one per region)
|
|
# Improved naming to reduce collision risk: add region index for additional uniqueness
|
|
resource "azurerm_storage_account" "boot_diagnostics" {
|
|
for_each = local.phase1_config
|
|
|
|
# Improved naming: add region index to reduce collision risk
|
|
name = substr("${local.cloud_provider}${local.env_code}${each.value.region_code}diag${substr(md5("${each.value.location}-boot-${each.key}"), 0, 6)}", 0, 24)
|
|
resource_group_name = azurerm_resource_group.phase1_us_regions[each.key].name
|
|
location = each.value.location
|
|
account_tier = "Standard"
|
|
account_replication_type = "LRS"
|
|
min_tls_version = "TLS1_2"
|
|
|
|
tags = merge(local.common_tags, {
|
|
Region = each.value.location
|
|
Purpose = "boot-diagnostics"
|
|
Deployment = "phase1-us-regions"
|
|
})
|
|
}
|
|
|
|
# Storage accounts for backups (one per region)
|
|
module "storage_phase1" {
|
|
for_each = local.phase1_config
|
|
source = "../../modules/storage"
|
|
|
|
resource_group_name = azurerm_resource_group.phase1_us_regions[each.key].name
|
|
location = each.value.location
|
|
cluster_name = "${local.cloud_provider}-${local.env_code}-${each.value.region_code}-vm"
|
|
environment = var.environment
|
|
tags = merge(local.common_tags, {
|
|
Region = each.value.location
|
|
Deployment = "phase1-us-regions"
|
|
})
|
|
}
|
|
|
|
# Log Analytics Workspace (one per region for monitoring)
|
|
module "monitoring_phase1" {
|
|
for_each = local.phase1_config
|
|
source = "../../modules/monitoring"
|
|
|
|
resource_group_name = azurerm_resource_group.phase1_us_regions[each.key].name
|
|
location = each.value.location
|
|
cluster_name = "${local.cloud_provider}-${local.env_code}-${each.value.region_code}-vm"
|
|
environment = var.environment
|
|
tags = merge(local.common_tags, {
|
|
Region = each.value.location
|
|
Deployment = "phase1-us-regions"
|
|
})
|
|
}
|
|
|
|
# Recovery Services Vault (one per region for backups)
|
|
module "backup_phase1" {
|
|
for_each = local.phase1_config
|
|
source = "../../modules/backup"
|
|
|
|
resource_group_name = azurerm_resource_group.phase1_us_regions[each.key].name
|
|
location = each.value.location
|
|
cluster_name = "${local.cloud_provider}-${local.env_code}-${each.value.region_code}-vm"
|
|
environment = var.environment
|
|
tags = merge(local.common_tags, {
|
|
Region = each.value.location
|
|
Deployment = "phase1-us-regions"
|
|
})
|
|
}
|
|
|
|
# Networking for each US region
|
|
module "networking_phase1" {
|
|
for_each = local.phase1_config
|
|
source = "../../modules/networking-vm"
|
|
|
|
resource_group_name = azurerm_resource_group.phase1_us_regions[each.key].name
|
|
location = each.value.location
|
|
cluster_name = "${local.cloud_provider}-${local.env_code}-${each.value.region_code}-vm"
|
|
environment = var.environment
|
|
vnet_address_space = lookup(local.region_address_spaces, each.value.location, "10.0.0.0/16")
|
|
subnet_address_prefix = lookup(local.region_subnet_prefixes, each.value.location, "10.0.1.0/24")
|
|
allowed_ssh_ips = var.allowed_ssh_ips
|
|
allowed_rpc_ips = var.allowed_rpc_ips
|
|
allowed_p2p_ips = var.allowed_p2p_ips
|
|
allowed_metrics_ips = var.allowed_metrics_ips
|
|
tags = merge(local.common_tags, {
|
|
Region = each.value.location
|
|
Deployment = "phase1-us-regions"
|
|
})
|
|
}
|
|
|
|
# VMs for each US region
|
|
module "vm_phase1" {
|
|
for_each = local.phase1_config
|
|
source = "../../modules/vm-deployment"
|
|
|
|
resource_group_name = azurerm_resource_group.phase1_us_regions[each.key].name
|
|
location = each.value.location
|
|
cluster_name = "${local.cloud_provider}-${local.env_code}-${each.value.region_code}-vm"
|
|
node_type = "besu-node"
|
|
node_count = each.value.vm_count
|
|
vm_size = each.value.vm_size
|
|
subnet_id = module.networking_phase1[each.key].vm_subnet_id
|
|
network_security_group_id = module.networking_phase1[each.key].vm_nsg_id
|
|
admin_username = var.vm_admin_username
|
|
ssh_public_key = var.ssh_public_key
|
|
storage_account_name = azurerm_storage_account.boot_diagnostics[each.key].name # Boot diagnostics storage
|
|
key_vault_id = module.keyvault.key_vault_id
|
|
genesis_file_path = "" # Will be configured later
|
|
use_phase1_cloud_init = true # Use Phase 1 cloud-init with NVM, Node 22, JDK 17
|
|
tags = merge(local.common_tags, {
|
|
Region = each.value.location
|
|
Deployment = "phase1-us-regions"
|
|
Environment = var.environment
|
|
})
|
|
}
|
|
|
|
# Networking for Nginx Proxy (West Europe admin region)
|
|
module "networking_admin" {
|
|
source = "../../modules/networking-vm"
|
|
|
|
resource_group_name = azurerm_resource_group.main[0].name
|
|
location = var.location # West Europe
|
|
cluster_name = "${local.cloud_provider}-${local.env_code}-wst-proxy"
|
|
environment = var.environment
|
|
vnet_address_space = lookup(local.region_address_spaces, var.location, "10.10.0.0/16")
|
|
subnet_address_prefix = lookup(local.region_subnet_prefixes, var.location, "10.10.1.0/24")
|
|
allowed_ssh_ips = var.allowed_ssh_ips
|
|
allowed_rpc_ips = var.allowed_rpc_ips
|
|
allowed_p2p_ips = var.allowed_p2p_ips
|
|
allowed_metrics_ips = var.allowed_metrics_ips
|
|
subnet_nsg_enabled = false # Nginx proxy uses NIC-level NSG, not subnet-level
|
|
enable_besu_rules = false # Nginx proxy doesn't need Besu-specific rules
|
|
tags = merge(local.common_tags, {
|
|
Region = var.location
|
|
Deployment = "admin-control-plane"
|
|
})
|
|
}
|
|
|
|
# Nginx Proxy Server (deployed in West Europe admin region)
|
|
module "nginx_proxy" {
|
|
source = "../../modules/nginx-proxy"
|
|
|
|
resource_group_name = azurerm_resource_group.main[0].name
|
|
location = var.location # West Europe
|
|
cluster_name = "${local.cloud_provider}-${local.env_code}-wst-proxy"
|
|
subnet_id = module.networking_admin.vm_subnet_id
|
|
backend_vms = {
|
|
for k, v in module.vm_phase1 : k => {
|
|
region = v.location
|
|
private_ips = v.private_ip_addresses # Backend VMs use private IPs only
|
|
public_ips = [] # No public IPs for backend VMs (Cloudflare Tunnel handles connectivity)
|
|
}
|
|
}
|
|
admin_username = var.vm_admin_username
|
|
ssh_public_key = var.ssh_public_key
|
|
environment = var.environment
|
|
tags = local.common_tags
|
|
|
|
depends_on = [
|
|
module.vm_phase1,
|
|
module.networking_phase1,
|
|
module.networking_admin
|
|
]
|
|
}
|
|
|
|
# West Europe admin resource group (for Key Vault and Nginx Proxy)
|
|
resource "azurerm_resource_group" "main" {
|
|
count = var.use_well_architected ? 0 : 1
|
|
name = local.resource_group_name
|
|
location = var.location # West Europe
|
|
|
|
tags = merge(local.common_tags, {
|
|
Region = var.location
|
|
Deployment = "admin-control-plane"
|
|
})
|
|
}
|
|
|
|
# Key Vault (shared, in West Europe admin region)
|
|
module "keyvault" {
|
|
source = "../../modules/secrets"
|
|
|
|
resource_group_name = var.use_well_architected ? var.security_resource_group_name : azurerm_resource_group.main[0].name
|
|
location = var.location
|
|
key_vault_name = var.key_vault_name != "" ? var.key_vault_name : local.kv_secrets
|
|
environment = var.environment
|
|
allowed_ips = var.key_vault_allowed_ips
|
|
allowed_subnets = var.key_vault_allowed_subnets
|
|
tags = local.common_tags
|
|
}
|
|
|
|
# Data source for current client config (for Key Vault access policies)
|
|
data "azurerm_client_config" "current" {}
|
|
|
|
# Key Vault access policies for VM Managed Identities
|
|
# NOTE: VMs need access to Key Vault to retrieve secrets using Managed Identity
|
|
# Use static keys from phase1_config to avoid for_each dependency issues
|
|
resource "azurerm_key_vault_access_policy" "vm_phase1" {
|
|
for_each = local.phase1_config
|
|
|
|
key_vault_id = module.keyvault.key_vault_id
|
|
tenant_id = data.azurerm_client_config.current.tenant_id
|
|
# Use the principal_id from the VM module output (will be known after VM creation)
|
|
object_id = module.vm_phase1[each.key].principal_ids[0]
|
|
|
|
secret_permissions = [
|
|
"Get",
|
|
"List"
|
|
]
|
|
|
|
key_permissions = [
|
|
"Get",
|
|
"List"
|
|
]
|
|
|
|
certificate_permissions = [
|
|
"Get",
|
|
"List"
|
|
]
|
|
|
|
depends_on = [
|
|
module.vm_phase1,
|
|
module.keyvault
|
|
]
|
|
}
|
|
|
|
# Key Vault access policy for Nginx Proxy Managed Identity
|
|
resource "azurerm_key_vault_access_policy" "nginx_proxy" {
|
|
key_vault_id = module.keyvault.key_vault_id
|
|
tenant_id = data.azurerm_client_config.current.tenant_id
|
|
object_id = module.nginx_proxy.principal_id
|
|
|
|
secret_permissions = [
|
|
"Get",
|
|
"List"
|
|
]
|
|
|
|
key_permissions = [
|
|
"Get",
|
|
"List"
|
|
]
|
|
|
|
certificate_permissions = [
|
|
"Get",
|
|
"List"
|
|
]
|
|
|
|
depends_on = [
|
|
module.nginx_proxy,
|
|
module.keyvault
|
|
]
|
|
}
|
|
|
|
# Outputs
|
|
output "phase1_us_regions" {
|
|
value = {
|
|
for k, v in module.vm_phase1 : k => {
|
|
region = local.phase1_config[k].location
|
|
resource_group = azurerm_resource_group.phase1_us_regions[k].name
|
|
vm_names = v.vm_names
|
|
public_ips = v.public_ip_addresses
|
|
private_ips = v.private_ip_addresses
|
|
boot_diagnostics_storage = azurerm_storage_account.boot_diagnostics[k].name
|
|
backup_storage = module.storage_phase1[k].backup_storage_account_name
|
|
}
|
|
}
|
|
description = "Phase 1 US region deployment information"
|
|
}
|
|
|
|
output "nginx_proxy" {
|
|
value = {
|
|
fqdn = module.nginx_proxy.fqdn
|
|
public_ip = module.nginx_proxy.public_ip
|
|
private_ip = module.nginx_proxy.private_ip
|
|
backend_count = length(local.us_regions)
|
|
note = "Backend VMs use private IPs only. Deploy VPN/ExpressRoute or Cloudflare Tunnel on backend VMs for connectivity."
|
|
}
|
|
description = "Nginx Proxy Server information (Cloudflare Tunnel integration point)"
|
|
}
|
|
|
|
output "key_vault_name" {
|
|
value = module.keyvault.key_vault_name
|
|
description = "Name of the Key Vault (West Europe admin region)"
|
|
}
|
|
|
|
output "monitoring" {
|
|
value = {
|
|
for k, v in module.monitoring_phase1 : k => {
|
|
log_analytics_workspace_id = v.log_analytics_workspace_id
|
|
log_analytics_workspace_name = v.log_analytics_workspace_name
|
|
}
|
|
}
|
|
description = "Log Analytics Workspace information for monitoring"
|
|
}
|
|
|
|
output "backups" {
|
|
value = {
|
|
for k, v in module.backup_phase1 : k => {
|
|
recovery_services_vault_id = v.recovery_services_vault_id
|
|
recovery_services_vault_name = v.recovery_services_vault_name
|
|
backup_policy_id = v.backup_policy_id
|
|
}
|
|
}
|
|
description = "Recovery Services Vault information for backups"
|
|
}
|
|
|
|
# Comprehensive outputs with SSH strings and resource IDs
|
|
output "ssh_connection_strings" {
|
|
value = {
|
|
for k, v in module.vm_phase1 : k => {
|
|
for idx, vm_name in v.vm_names : vm_name => "ssh ${var.vm_admin_username}@${length(v.public_ip_addresses) > idx ? v.public_ip_addresses[idx] : v.private_ip_addresses[idx]}"
|
|
}
|
|
}
|
|
description = "SSH connection strings for VMs (uses public IP if available, otherwise private IP)"
|
|
}
|
|
|
|
output "nginx_proxy_ssh" {
|
|
value = "ssh ${var.vm_admin_username}@${module.nginx_proxy.public_ip}"
|
|
description = "SSH connection string for Nginx Proxy"
|
|
}
|
|
|
|
output "resource_ids" {
|
|
value = {
|
|
resource_groups = {
|
|
for k, v in azurerm_resource_group.phase1_us_regions : k => v.id
|
|
}
|
|
vms = {
|
|
for k, v in module.vm_phase1 : k => {
|
|
for idx, vm_id in v.vm_ids : v.vm_names[idx] => vm_id
|
|
}
|
|
}
|
|
key_vault = {
|
|
id = module.keyvault.key_vault_id
|
|
uri = module.keyvault.key_vault_uri
|
|
}
|
|
}
|
|
description = "Resource IDs for all deployed resources"
|
|
}
|
|
|
|
output "storage_accounts" {
|
|
value = {
|
|
boot_diagnostics = {
|
|
for k, v in azurerm_storage_account.boot_diagnostics : k => {
|
|
name = v.name
|
|
location = v.location
|
|
}
|
|
}
|
|
backups = {
|
|
for k, v in module.storage_phase1 : k => {
|
|
backup_storage = v.backup_storage_account_name
|
|
shared_storage = v.shared_storage_account_name
|
|
}
|
|
}
|
|
}
|
|
description = "Storage account information for boot diagnostics and backups"
|
|
}
|
|
|