Terraform module to deploy CTFd into scalable AWS infrastructure
This has been used in a moderately sized CTF > 1000 participants and performed well with a setup similar to the example below, though you may want to scale out a little.
The CTFd setup Looks something like this:
flowchart TB
subgraph "Uploads"
S3Uploads[S3]
end
subgraph "Logs"
S3Logs[S3]
end
subgraph "RDS (mysql - Serverless or Provisioned)"
RDS[RDS autoscale]
end
subgraph "ElasticCache (redis)"
REDIS[REDIS] --> ElasticCache1[Instance 1]
REDIS[REDIS] --> ElasticCache[...]
REDIS[REDIS] --> ElasticCacheN[Instance n]
end
LB[ALB] --> Ingress
subgraph ECS[ECS]
Ingress --> Service[CTFd service]
Service --> Instance1[CTFd Instance 1]
Service --> Instance[CTFd Instance ...]
Service --> InstanceN[CTFd Instance n - HorizontalAutoScaling]
Instance1 --> RDS[RDS]
Instance --> RDS
InstanceN --> RDS
Instance1 --> REDIS[REDIS]
Instance --> REDIS
InstanceN --> REDIS
Instance1 --> S3Uploads
Instance --> S3Uploads
InstanceN --> S3Uploads
end
ECS --> S3Logs
Name | Version |
---|---|
terraform | >= 1.7.3 |
aws | >= 5.38.0 |
docker | 3.0.2 |
random | 3.6.0 |
Name | Version |
---|---|
random | 3.6.0 |
Name | Description | Type | Default | Required |
---|---|---|---|---|
app_name | Name of application (ex: "ctfd") | string |
"ctfd" |
no |
aws_region | Region to deploy CTFd into | string |
"us-east-1" |
no |
create_cdn | Whether to create a cloudfront CDN deployment. | bool |
false |
no |
create_in_aws | Create AWS resources. If false an instance will be spun up locally with docker | bool |
true |
no |
ctf_domain | Domain to use for the CTFd deployment. Only used if create_cdn is true |
string |
"" |
no |
ctf_domain_zone_id | zone id for the route53 zone for the ctf_domain. Only used if create_cdn is true |
string |
"" |
no |
ctfd_image | Docker image for the ctfd frontend. | string |
"ctfd/ctfd" |
no |
db_character_set | The database character set. | string |
"utf8mb4" |
no |
db_cluster_instance_type | Type of instances to create in the RDS cluster. Only used if db_serverless set to false |
string |
"db.r5.large" |
no |
db_collation | The database collation. | string |
"utf8mb4_bin" |
no |
db_deletion_protection | If true database will not be able to be deleted without manual intervention | bool |
true |
no |
db_engine | Engine for the RDS cluster | string |
"aurora-mysql" |
no |
db_engine_version | Engine version for the RDS cluster | string |
"8.0.mysql_aurora.3.04.1" |
no |
db_name | Name for the database in RDS | string |
"ctfd" |
no |
db_port | Port to connect to the RDS cluster on | number |
3306 |
no |
db_serverless | Configure serverless RDS cluster | bool |
true |
no |
db_serverless_max_capacity | Maximum capacity for serverless RDS. Only used if db_serverless set to true |
number |
128 |
no |
db_serverless_min_capacity | Minimum capacity for serverless RDS. Only used if db_serverless set to true |
number |
1 |
no |
db_skip_final_snapshot | If true database will not be snapshoted before deletion. | bool |
false |
no |
db_user | Username for the RDS database | string |
"ctfd" |
no |
elasticache_cluster_instance_type | Instance type for instance in ElastiCache cluster | string |
"cache.r6g.large" |
no |
elasticache_cluster_instances | Number of instances in ElastiCache cluster | number |
3 |
no |
elasticache_cluster_port | Port to connect to the ElastiCache cluster on | number |
6379 |
no |
elasticache_encryption_key_arn | Encryption key for use with ElastiCache at-rest encryption. Unencrypted if this is empty. | string |
"" |
no |
force_destroy_challenge_bucket | Whether the S3 bucket containing the CTFD challenge data should be force destroyed | bool |
false |
no |
force_destroy_log_bucket | Whether the S3 bucket containing the logging data should be force destroyed | bool |
false |
no |
frontend_desired_count | Desired number of task instances for the frontend service. | number |
2 |
no |
frontend_maximum_percent | health percent for the frontend service. | number |
150 |
no |
frontend_minimum_healthy_percent | Minimum health percent for the frontend service. | number |
75 |
no |
https_certificate_arn | SSL Certificate ARN to be used for the HTTPS server. | string |
"" |
no |
rds_encryption_key_arn | Encryption key for use with RDS at-rest encryption. Unencrypted if this is empty. | string |
"" |
no |
registry_password | Password for container registry. Needed if using a private registry for a custom CTFd image. | string |
null |
no |
registry_server | Container registry server. Needed if using a private registry for a custom CTFd image. | string |
"registry.gitlab.com" |
no |
registry_username | Username for container registry. Needed if using a private registry for a custom CTFd image. | string |
null |
no |
s3_encryption_key_arn | Encryption key for use with S3 bucket at-rest encryption. Unencrypted if this is empty. | string |
"" |
no |
Name | Description |
---|---|
challenge_bucket_id | Challenge bucket name |
lb_dns_name | DNS name for the Load Balancer |
lb_port | Port that CTFd is reachable on |
log_bucket_id | Logging bucket name |
private_subnet_ids | List of private subnets that contain backend infrastructure (RDS, ElastiCache, EC2) |
public_subnet_ids | List of public subnets that contain frontend infrastructure (ALB) |
vpc_id | Id for the VPC created for CTFd |
terraform {
required_version = ">= 1.7.3"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.38.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
module "ctfd" {
source = "../../" # Actually set to "1nval1dctf/ctfd/aws"
db_deletion_protection = false
elasticache_cluster_instance_type = "cache.t2.micro"
elasticache_cluster_instances = 2
db_serverless = true
}
terraform {
required_version = ">= 1.7.3"
}
module "ctfd" {
source = "../../" # Actually set to "1nval1dctf/ctfd/aws"
db_user = "ctfd"
db_name = "ctfd"
create_in_aws = false
}
wget https://dl.google.com/go/go1.19.5.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.19.5.linux-amd64.tar.gz
rm go1.19.5.linux-amd64.tar.gz
LATEST_URL=$(curl https://releases.hashicorp.com/terraform/index.json | jq -r '.versions[].builds[].url | select(.|test("alpha|beta|rc")|not) | select(.|contains("linux_amd64"))' | sort -t. -k 1,1n -k 2,2n -k 3,3n | tail -1)
curl ${LATEST_URL} > /tmp/terraform.zip
(cd /tmp && unzip /tmp/terraform.zip && chmod +x /tmp/terraform && sudo mv /tmp/terraform /usr/local/bin/)
Follow: https://github.com/antonbabenko/pre-commit-terraform#how-to-install
Default tests will run through various validation steps then spin up an instance with docker.
make
To test the AWS backed version run.
make test_aws
⚠️ Warning: This will spin up CTFd in AWS which will cost you some money.