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
41 changes: 41 additions & 0 deletions charts/movement-node/templates/ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{{- if .Values.ingress.enabled }}
{{- if or (eq .Values.node.type "vfn") (eq .Values.node.type "fullnode") }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "movement-node.fullname" . }}
labels:
{{- include "movement-node.labels" . | nindent 4 }}
annotations:
{{- if .Values.ingress.annotations }}
{{- toYaml .Values.ingress.annotations | nindent 4 }}
{{- end }}
{{- if .Values.ingress.tls.enabled }}
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
{{- end }}
spec:
ingressClassName: {{ .Values.ingress.className | default "nginx" }}
{{- if .Values.ingress.tls.enabled }}
tls:
- hosts:
- {{ .Values.ingress.hostname }}
{{- if .Values.ingress.tls.secretName }}
secretName: {{ .Values.ingress.tls.secretName }}
{{- else if .Values.ingress.tls.wildcardSecretName }}
secretName: {{ .Values.ingress.tls.wildcardSecretName }}
{{- end }}
{{- end }}
rules:
- host: {{ .Values.ingress.hostname }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ include "movement-node.fullname" . }}
port:
number: {{ .Values.service.ports.api }}
{{- end }}
{{- end }}
4 changes: 3 additions & 1 deletion charts/movement-node/templates/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ metadata:
{{- if .Values.service.annotations }}
annotations:
{{- toYaml .Values.service.annotations | nindent 4 }}
{{- else if and (or (eq .Values.node.type "vfn") (eq .Values.node.type "fullnode")) .Values.loadBalancer.annotations }}
{{- else if and (or (eq .Values.node.type "vfn") (eq .Values.node.type "fullnode")) (not .Values.ingress.enabled) .Values.loadBalancer.annotations }}
annotations:
{{- toYaml .Values.loadBalancer.annotations | nindent 4 }}
{{- end }}
Expand All @@ -16,6 +16,8 @@ spec:
type: {{ .Values.service.type }}
{{- else if eq .Values.node.type "validator" }}
type: ClusterIP
{{- else if .Values.ingress.enabled }}
type: ClusterIP
{{- else }}
type: LoadBalancer
{{- end }}
Expand Down
21 changes: 20 additions & 1 deletion charts/movement-node/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,31 @@ service:

# Load balancer configuration (for vfn and fullnode)
loadBalancer:
# Auto-enabled for vfn and fullnode
# Auto-enabled for vfn and fullnode (when ingress is disabled)
enabled: false # Leave false for auto-config
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"

# Ingress configuration (for HTTPS access to API)
ingress:
# Enable ingress for TLS termination (recommended for production)
enabled: false
# Ingress class name (nginx, traefik, etc.)
className: "nginx"
# Hostname for this node (e.g., node-01.nodeinfra.testnet.movementnetwork.xyz)
hostname: ""
# Additional ingress annotations
annotations: {}
# TLS configuration
tls:
enabled: true
# Use a shared wildcard certificate secret (recommended)
# This secret is created by cert-manager and synced by reflector
wildcardSecretName: "wildcard-tls"
# Or use a per-node certificate secret
secretName: ""

# Monitoring configuration
monitoring:
enabled: true
Expand Down
20 changes: 20 additions & 0 deletions examples/public-fullnode/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,23 @@ BOOTSTRAP_S3_REGION=us-west-2
# FULLNODE_NAMESPACE=movement-l1
# FULLNODE_SERVICE_NAME=public-fullnode
# FULLNODE_CONFIG_FILE=charts/movement-node/files/fullnode.yaml

# Ingress Configuration (TLS)
# Enable HTTPS ingress instead of direct LoadBalancer exposure.
# When enabled:
# - Terraform provisions: NGINX Ingress Controller, cert-manager, wildcard TLS certificate
# - cert-manager uses Route53 DNS-01 challenge
# - Fullnode service uses ClusterIP
# - Traffic flows: HTTPS:443 → NGINX Ingress (TLS termination) → Service:8080
#
# CHAIN_NAME: Network name subdomain (default: testnet)
# INGRESS_DOMAIN: Base domain / Route53 zone (default: scratchpad.movementnetwork.xyz)
#
# INGRESS_ENABLED=true
# CHAIN_NAME=testnet
# INGRESS_DOMAIN=scratchpad.movementnetwork.xyz
#
# With INGRESS_ENABLED=true, CHAIN_NAME=testnet, and FULLNODE_SERVICE_NAME=public-fullnode:
# → Fullnode accessible at https://public-fullnode.testnet.scratchpad.movementnetwork.xyz
#
INGRESS_ENABLED=false
33 changes: 33 additions & 0 deletions examples/public-fullnode/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ def build_terraform_vars(env_vars: dict) -> dict:
t.strip() for t in env_vars["NODE_INSTANCE_TYPES"].split(",")
]

# Ingress configuration
enable_ingress = env_vars.get("INGRESS_ENABLED", "false").lower() in ("true", "1", "yes")
variables["enable_ingress"] = enable_ingress
if enable_ingress:
variables["chain_name"] = env_vars.get("CHAIN_NAME", "testnet")
variables["ingress_domain"] = env_vars.get("INGRESS_DOMAIN", "scratchpad.movementnetwork.xyz")

return variables


Expand Down Expand Up @@ -101,6 +108,26 @@ def build_helm_config(env_vars: dict, outputs: dict) -> dict:
"storage.parameters.throughput": "500",
}

# Ingress configuration for TLS (check outputs first, then env vars)
ingress_enabled = outputs.get("ingress_enabled", False)
if not ingress_enabled:
ingress_enabled = env_vars.get("INGRESS_ENABLED", "false").lower() in ("true", "1", "yes")
chain_name = env_vars.get("CHAIN_NAME", "testnet")
ingress_base_domain = env_vars.get("INGRESS_DOMAIN", "scratchpad.movementnetwork.xyz")
ingress_domain = f"{chain_name}.{ingress_base_domain}"

if ingress_enabled:
ingress_hostname = f"{service_name}.{ingress_domain}"
set_values.update({
"ingress.enabled": "true",
"ingress.hostname": ingress_hostname,
"ingress.className": "nginx",
"ingress.tls.enabled": "true",
# TLS secret uses chart default (wildcard-tls) unless INGRESS_TLS_SECRET is set
})
if env_vars.get("INGRESS_TLS_SECRET"):
set_values["ingress.tls.wildcardSecretName"] = env_vars["INGRESS_TLS_SECRET"]

# Add bootstrap if enabled
if outputs.get("fullnode_bootstrap_enabled"):
# Parse S3 URI to extract bucket and prefix
Expand Down Expand Up @@ -145,12 +172,18 @@ def deploy(env_vars: dict, force_create: bool, validate: bool) -> None:
outputs = cluster.terraform.get_outputs() or {}
helm_config = build_helm_config(env_vars, outputs)

# Check if ingress is requested
ingress_enabled = outputs.get("ingress_enabled", False)
if not ingress_enabled:
ingress_enabled = env_vars.get("INGRESS_ENABLED", "false").lower() in ("true", "1", "yes")

cluster.deploy(
env_vars=env_vars,
terraform_vars=terraform_vars,
helm_config=helm_config,
skip_if_exists=not force_create,
validate=validate,
ingress_enabled=ingress_enabled,
)


Expand Down
82 changes: 80 additions & 2 deletions examples/public-fullnode/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,66 @@ provider "aws" {
region = var.region
}

# Kubernetes provider (configured after EKS is created)
provider "kubernetes" {
host = module.eks.cluster_endpoint
cluster_ca_certificate = base64decode(module.eks.cluster_ca_certificate)

exec {
api_version = "client.authentication.k8s.io/v1beta1"
command = "aws"
args = [
"eks",
"get-token",
"--cluster-name",
module.eks.cluster_id,
"--region",
var.region
]
}
}

# Helm provider
provider "helm" {
kubernetes {
host = module.eks.cluster_endpoint
cluster_ca_certificate = base64decode(module.eks.cluster_ca_certificate)

exec {
api_version = "client.authentication.k8s.io/v1beta1"
command = "aws"
args = [
"eks",
"get-token",
"--cluster-name",
module.eks.cluster_id,
"--region",
var.region
]
}
}
}

# kubectl provider
provider "kubectl" {
host = module.eks.cluster_endpoint
cluster_ca_certificate = base64decode(module.eks.cluster_ca_certificate)
load_config_file = false

exec {
api_version = "client.authentication.k8s.io/v1beta1"
command = "aws"
args = [
"eks",
"get-token",
"--cluster-name",
module.eks.cluster_id,
"--region",
var.region
]
}
}

locals {
fullnode_bootstrap_enabled = var.fullnode_bootstrap_s3_bucket != ""
fullnode_bootstrap_prefix = trim(var.fullnode_bootstrap_s3_prefix, "/")
Expand All @@ -19,8 +79,8 @@ module "network" {
validator_name = var.validator_name
region = var.region
vpc_cidr = var.vpc_cidr
dns_enabled = var.enable_dns
dns_zone_name = var.dns_zone_name
dns_enabled = var.enable_dns || var.enable_ingress
dns_zone_name = var.enable_dns ? var.dns_zone_name : (var.enable_ingress ? var.ingress_domain : "")

# Cost optimization: single NAT gateway for demo
single_nat_gateway = true
Expand Down Expand Up @@ -107,3 +167,21 @@ resource "aws_iam_role_policy" "fullnode_s3_read" {
role = aws_iam_role.fullnode_s3[0].id
policy = data.aws_iam_policy_document.fullnode_s3_read[0].json
}

# Ingress infrastructure (NGINX Ingress Controller + cert-manager + wildcard TLS)
module "ingress" {
source = "../../terraform-modules/movement-ingress"
count = var.enable_ingress ? 1 : 0

cluster_name = module.eks.cluster_name
cluster_oidc_issuer_url = "https://${module.eks.oidc_provider_url}"
cluster_oidc_provider_arn = module.eks.oidc_provider_arn
route53_zone_id = module.network.dns_zone_id
route53_zone_name = var.ingress_domain
wildcard_domain = "*.${var.chain_name}.${var.ingress_domain}"
node_namespace = var.fullnode_namespace

tags = var.tags

depends_on = [module.eks]
}
16 changes: 16 additions & 0 deletions examples/public-fullnode/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,19 @@ output "configure_kubectl" {
description = "Command to configure kubectl"
value = "aws eks update-kubeconfig --region ${var.region} --name ${module.eks.cluster_name}"
}

# Ingress outputs (when enabled)
output "ingress_enabled" {
description = "Whether ingress is enabled"
value = var.enable_ingress
}

output "ingress_base_domain" {
description = "Base domain for ingress (Route53 zone, not including chain_name prefix)"
value = var.enable_ingress ? var.ingress_domain : ""
}

output "ingress_namespace" {
description = "NGINX Ingress Controller namespace"
value = var.enable_ingress ? module.ingress[0].ingress_namespace : ""
}
19 changes: 19 additions & 0 deletions examples/public-fullnode/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,22 @@ variable "fullnode_dns_name" {
type = string
default = ""
}

# Ingress Configuration
variable "enable_ingress" {
description = "Enable NGINX Ingress Controller with TLS for fullnode access"
type = bool
default = false
}

variable "chain_name" {
description = "Chain/network name for ingress subdomain (e.g., testnet, mainnet)"
type = string
default = "testnet"
}

variable "ingress_domain" {
description = "Base domain for ingress (Route53 zone)"
type = string
default = "scratchpad.movementnetwork.xyz"
}
12 changes: 12 additions & 0 deletions examples/public-fullnode/versions.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,17 @@ terraform {
source = "hashicorp/aws"
version = ">= 5.0, < 7.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.35"
}
helm = {
source = "hashicorp/helm"
version = "~> 2.17"
}
kubectl = {
source = "gavinbunney/kubectl"
version = "~> 1.14"
}
}
}
20 changes: 20 additions & 0 deletions examples/validator-vfn/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,23 @@ DEPLOY_FULLNODE=false
VALIDATOR_KEYS_SECRET_NAME=
# This is K8s secrete name where used by k8s for indexing the secrets
VALIDATOR_KEYS_SECRET=validator-identity

# Ingress Configuration (TLS)
# Enable HTTPS ingress instead of direct LoadBalancer exposure.
# When enabled:
# - Terraform provisions: NGINX Ingress Controller, cert-manager, wildcard TLS certificate
# - cert-manager uses Route53 DNS-01 challenge
# - VFN/fullnode services use ClusterIP
# - Traffic flows: HTTPS:443 → NGINX Ingress (TLS termination) → Service:8080
#
# CHAIN_NAME: Network name subdomain (default: testnet)
# INGRESS_DOMAIN: Base domain / Route53 zone (default: scratchpad.movementnetwork.xyz)
#
# INGRESS_ENABLED=true
# CHAIN_NAME=testnet
# INGRESS_DOMAIN=scratchpad.movementnetwork.xyz
#
# With INGRESS_ENABLED=true, CHAIN_NAME=testnet, and VFN_NAME=vfn-01:
# → VFN accessible at https://vfn-01.testnet.scratchpad.movementnetwork.xyz
#
INGRESS_ENABLED=false
Loading
Loading