Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 49 additions & 7 deletions .bedrock/.terragrunt/00_variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ variable "create_core" {
# the scaffolding (security groups, ALB, target group, listener, Route53,
# log group, service shell, initial task-def stub).
#
# Personality (image, env vars, secrets) is owned by the deploy-col-mar-mm.yml
# workflow, which builds the image, pushes to GHCR, and registers a new
# task-def revision per deploy. Both ECS service.task_definition and ECS
# task_definition.container_definitions are in lifecycle.ignore_changes;
# Terraform never updates them after first apply.
# Personality (image, public env vars, desired count) is owned by
# deploy-col-mar-mm.yml. Private keys and the Alchemy key live in Secrets
# Manager (01_secrets_manager.tf) and are injected with ECS valueFrom.
# Both ECS service.task_definition and container_definitions are in
# lifecycle.ignore_changes; Terraform never updates them after first apply.
#
# Service map fields (all scaffolding):
# create bool - toggle the entire service stack
Expand Down Expand Up @@ -66,8 +66,9 @@ variable "futures_mm_service" {
# UNIFIED MARGIN KEEPER - SCAFFOLDING ONLY
################################################################################
# Single ECS service for coordinated perps + futures liquidation (replaces
# derivatives-marketplace svc-perps-keeper-*). Runtime config is owned by
# deploy-keeper.yml via GitHub Variables / Secrets.
# derivatives-marketplace svc-perps-keeper-*). Public runtime config is owned
# by deploy-keeper.yml from config/<env>.env. The liquidator key, Alchemy key,
# and webhook secret are injected from Secrets Manager.
################################################################################

variable "keeper_service" {
Expand Down Expand Up @@ -118,3 +119,44 @@ variable "foundation_tags" {
variable "provider_profile" {
description = "AWS profile name used by the default provider"
}

################################################################################
# Secrets Manager (gitignored secret.auto.tfvars — never commit values)
################################################################################
# Same shape in 02-dev and 04-lmn:
# alchemy_api_key = "..."
# liquidator_private_key = "0x..."
# futures_mm_private_key = "0x..."
# perps_mm_private_key = "0x..."
# webhook_secret = "" # optional; keeper WEBHOOK_SECRET

variable "alchemy_api_key" {
description = "Alchemy API key injected into the keeper and both market makers"
type = string
sensitive = true
}

variable "liquidator_private_key" {
description = "Keeper signer. Injected as LIQUIDATOR_PRIVATE_KEY"
type = string
sensitive = true
}

variable "futures_mm_private_key" {
description = "Portfolio market-maker signer on the futures ECS service. Injected as PRIVATE_KEY"
type = string
sensitive = true
}

variable "perps_mm_private_key" {
description = "Perps market-maker signer. Injected as PRIVATE_KEY on the perps ECS service. CI does not roll that service."
type = string
sensitive = true
}

variable "webhook_secret" {
description = "Optional keeper WEBHOOK_SECRET. Empty string injects an empty value."
type = string
sensitive = true
default = ""
}
27 changes: 23 additions & 4 deletions .bedrock/.terragrunt/01_github_actions_iam.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
# - update ECS services to point at the new revisions
# - PassRole the existing bedrock-foundation-role into ECS tasks
#
# All runtime config (env vars, secrets, contract addresses, RPC keys) is
# managed in GitHub Variables / Secrets and baked into each task-def
# revision by the workflow. There are no AWS Secrets Manager resources to
# read here.
# Public runtime config is baked into each task-def revision by the workflow
# from config/<env>.env. Private keys are not. CI may DescribeSecret so it
# can write valueFrom ARNs; GetSecretValue stays on bedrock-foundation-role.
#
# OIDC provider bootstrap (run once per account if not already present):
# aws iam create-open-id-connect-provider \
Expand Down Expand Up @@ -178,6 +177,16 @@ resource "aws_iam_role_policy" "github_ecs_update_futures_mm" {
"ecs:DescribeClusters"
]
Resource = "*"
},
{
Sid = "DescribeFuturesMmSecret"
Effect = "Allow"
Action = [
"secretsmanager:DescribeSecret"
]
Resource = [
aws_secretsmanager_secret.futures_mm[0].arn
]
}
]
})
Expand Down Expand Up @@ -238,6 +247,16 @@ resource "aws_iam_role_policy" "github_ecs_update_keeper" {
"ecs:DescribeClusters"
]
Resource = "*"
},
{
Sid = "DescribeKeeperSecret"
Effect = "Allow"
Action = [
"secretsmanager:DescribeSecret"
]
Resource = [
aws_secretsmanager_secret.keeper[0].arn
]
}
]
})
Expand Down
126 changes: 126 additions & 0 deletions .bedrock/.terragrunt/01_secrets_manager.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
################################################################################
# SECRETS MANAGER
################################################################################
# ECS injects these at task start via valueFrom. The task definition stores
# the secret ARN, not the value. GitHub Actions only calls DescribeSecret
# (see 01_github_actions_iam.tf) so CI never receives the secret string.
#
# bedrock-foundation-role is the task execution role (local.titanio_role_arn).

resource "aws_iam_policy" "col_mar_secret_access" {
count = (var.keeper_service.create || var.futures_mm_service.create || var.perps_mm_service.create) ? 1 : 0
provider = aws.use1
name = "${local.shortname}-secret-access-${substr(var.account_shortname, 8, 3)}"
description = "Allow ECS tasks to read Collateral Margin secrets from Secrets Manager"

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
]
Resource = compact([
var.keeper_service.create ? aws_secretsmanager_secret.keeper[0].arn : "",
var.futures_mm_service.create ? aws_secretsmanager_secret.futures_mm[0].arn : "",
var.perps_mm_service.create ? aws_secretsmanager_secret.perps_mm[0].arn : "",
])
}
]
})

tags = merge(
var.default_tags,
var.foundation_tags,
{
Name = "Collateral Margin Secret Access Policy",
Capability = null,
},
)
}

resource "aws_iam_role_policy_attachment" "col_mar_secret_access" {
count = (var.keeper_service.create || var.futures_mm_service.create || var.perps_mm_service.create) ? 1 : 0
provider = aws.use1
role = "bedrock-foundation-role"
policy_arn = aws_iam_policy.col_mar_secret_access[0].arn
}

################################################################################
# Keeper
################################################################################

resource "aws_secretsmanager_secret" "keeper" {
count = var.keeper_service.create ? 1 : 0
provider = aws.use1
name = "${local.shortname}-keeper-secrets-v3-${substr(var.account_shortname, 8, 3)}"
description = "Collateral-margin keeper secrets (liquidator key, Alchemy key, webhook secret)"
tags = merge(var.default_tags, var.foundation_tags, {
Name = "${local.shortname}-keeper-secrets-v3-${substr(var.account_shortname, 8, 3)}"
})
}

resource "aws_secretsmanager_secret_version" "keeper" {
count = var.keeper_service.create ? 1 : 0
provider = aws.use1
secret_id = aws_secretsmanager_secret.keeper[0].id
secret_string = jsonencode({
liquidator_private_key = var.liquidator_private_key
alchemy_api_key = var.alchemy_api_key
webhook_secret = var.webhook_secret
})
}

################################################################################
# Futures / portfolio market maker
################################################################################
# deploy-col-mar-mm.yml runs the portfolio app on this service and injects
# private_key as PRIVATE_KEY.

resource "aws_secretsmanager_secret" "futures_mm" {
count = var.futures_mm_service.create ? 1 : 0
provider = aws.use1
name = "${local.shortname}-futures-mm-secrets-v3-${substr(var.account_shortname, 8, 3)}"
description = "Portfolio market-maker secrets (signer key and Alchemy key)"
tags = merge(var.default_tags, var.foundation_tags, {
Name = "${local.shortname}-futures-mm-secrets-v3-${substr(var.account_shortname, 8, 3)}"
})
}

resource "aws_secretsmanager_secret_version" "futures_mm" {
count = var.futures_mm_service.create ? 1 : 0
provider = aws.use1
secret_id = aws_secretsmanager_secret.futures_mm[0].id
secret_string = jsonencode({
private_key = var.futures_mm_private_key
alchemy_api_key = var.alchemy_api_key
})
}

################################################################################
# Perps market maker
################################################################################
# CI does not roll this service. The secret is here so the task definition
# can inject PRIVATE_KEY the same way, and so the key is not left in GitHub.

resource "aws_secretsmanager_secret" "perps_mm" {
count = var.perps_mm_service.create ? 1 : 0
provider = aws.use1
name = "${local.shortname}-perps-mm-secrets-v3-${substr(var.account_shortname, 8, 3)}"
description = "Perps market-maker secrets (signer key and Alchemy key)"
tags = merge(var.default_tags, var.foundation_tags, {
Name = "${local.shortname}-perps-mm-secrets-v3-${substr(var.account_shortname, 8, 3)}"
})
}

resource "aws_secretsmanager_secret_version" "perps_mm" {
count = var.perps_mm_service.create ? 1 : 0
provider = aws.use1
secret_id = aws_secretsmanager_secret.perps_mm[0].id
secret_string = jsonencode({
private_key = var.perps_mm_private_key
alchemy_api_key = var.alchemy_api_key
})
}
36 changes: 32 additions & 4 deletions .bedrock/.terragrunt/04_futures_mm_svc.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
################################################################################
# Mirror of 04_perps_mm_svc.tf for MAKER_APP=futures. Same CI/CD-owned
# personality model: Terraform builds infra, deploy-col-mar-mm.yml owns
# image / env vars / secrets / desired_count after first apply.
# image / public env vars / desired_count after first apply.
# PRIVATE_KEY and ALCHEMY_API_KEY are injected from Secrets Manager.
#
# Replaces the legacy futures market-maker Lambda (futures-marketplace,
# 10_market_maker_lambda.tf). DNS name `futuresmm.{env}.hashpower.exchange`
Expand Down Expand Up @@ -185,16 +186,32 @@ resource "aws_alb_listener" "futures_mm_int_443_use1" {
)
}

# Public alias in the Hashpower zone. Dev zones live in the workload account.
# LMN writes hashpower.exchange in titanio-net (aws.titanio-net).
resource "aws_route53_record" "futures_mm_int_use1" {
count = var.futures_mm_service.create ? 1 : 0
count = var.futures_mm_service.create && !local.is_lmn ? 1 : 0
provider = aws.use1
zone_id = local.hp_dns["exc"].zone_id
name = "futuresmm.${local.hp_dns["exc"].name}"
type = "A"

alias {
name = aws_alb.futures_mm_int_use1[count.index].dns_name
zone_id = aws_alb.futures_mm_int_use1[count.index].zone_id
name = aws_alb.futures_mm_int_use1[0].dns_name
zone_id = aws_alb.futures_mm_int_use1[0].zone_id
evaluate_target_health = true
}
}

resource "aws_route53_record" "futures_mm_int_lmn" {
count = var.futures_mm_service.create && local.is_lmn ? 1 : 0
provider = aws.titanio-net
zone_id = local.hp_dns["exc"].zone_id
name = "futuresmm.${local.hp_dns["exc"].name}"
type = "A"

alias {
name = aws_alb.futures_mm_int_use1[0].dns_name
zone_id = aws_alb.futures_mm_int_use1[0].zone_id
evaluate_target_health = true
}
}
Expand Down Expand Up @@ -276,6 +293,17 @@ resource "aws_ecs_task_definition" "futures_mm_use1" {
}
]

secrets = [
{
name = "PRIVATE_KEY"
valueFrom = "${aws_secretsmanager_secret.futures_mm[0].arn}:private_key::"
},
{
name = "ALCHEMY_API_KEY"
valueFrom = "${aws_secretsmanager_secret.futures_mm[0].arn}:alchemy_api_key::"
}
]

logConfiguration = {
logDriver = "awslogs"
options = {
Expand Down
33 changes: 30 additions & 3 deletions .bedrock/.terragrunt/04_perps_mm_svc.tf
Original file line number Diff line number Diff line change
Expand Up @@ -197,16 +197,32 @@ resource "aws_alb_listener" "perps_mm_int_443_use1" {
)
}

# Public alias in the Hashpower zone. Dev zones live in the workload account.
# LMN writes hashpower.exchange in titanio-net (aws.titanio-net).
resource "aws_route53_record" "perps_mm_int_use1" {
count = var.perps_mm_service.create ? 1 : 0
count = var.perps_mm_service.create && !local.is_lmn ? 1 : 0
provider = aws.use1
zone_id = local.hp_dns["exc"].zone_id
name = "perpsmm.${local.hp_dns["exc"].name}"
type = "A"

alias {
name = aws_alb.perps_mm_int_use1[count.index].dns_name
zone_id = aws_alb.perps_mm_int_use1[count.index].zone_id
name = aws_alb.perps_mm_int_use1[0].dns_name
zone_id = aws_alb.perps_mm_int_use1[0].zone_id
evaluate_target_health = true
}
}

resource "aws_route53_record" "perps_mm_int_lmn" {
count = var.perps_mm_service.create && local.is_lmn ? 1 : 0
provider = aws.titanio-net
zone_id = local.hp_dns["exc"].zone_id
name = "perpsmm.${local.hp_dns["exc"].name}"
type = "A"

alias {
name = aws_alb.perps_mm_int_use1[0].dns_name
zone_id = aws_alb.perps_mm_int_use1[0].zone_id
evaluate_target_health = true
}
}
Expand Down Expand Up @@ -298,6 +314,17 @@ resource "aws_ecs_task_definition" "perps_mm_use1" {
}
]

secrets = [
{
name = "PRIVATE_KEY"
valueFrom = "${aws_secretsmanager_secret.perps_mm[0].arn}:private_key::"
},
{
name = "ALCHEMY_API_KEY"
valueFrom = "${aws_secretsmanager_secret.perps_mm[0].arn}:alchemy_api_key::"
}
]

logConfiguration = {
logDriver = "awslogs"
options = {
Expand Down
Loading
Loading