Terraform cleanup (#26379)

- Remove `dogfood/aws/` folder
- Update links in docs and guides
This commit is contained in:
Noah Talerman
2025-02-24 17:35:50 -05:00
committed by GitHub
parent dd6124bff9
commit a14fd0d263
24 changed files with 7 additions and 2113 deletions
+5 -5
View File
@@ -5,25 +5,25 @@
![Deploy Fleet on AWS ECS](../website/assets/images/articles/deploy-fleet-on-aws-ecs-800x450@2x.png)
Terraform reference architecture can be found [here](https://github.com/fleetdm/fleet/tree/main/infrastructure/dogfood/terraform/aws)
Terraform reference architecture can be found [here](https://github.com/fleetdm/fleet-terraform)
### Infrastructure dependencies
#### MySQL
In AWS we recommend running Aurora with MySQL Engine, see [here for terraform details](https://github.com/fleetdm/fleet/blob/main/infrastructure/dogfood/terraform/aws/rds.tf#L64).
In AWS we recommend running Aurora with MySQL Engine.
#### Redis
In AWS we recommend running ElastiCache (Redis Engine) see [here for terraform details](https://github.com/fleetdm/fleet/blob/main/infrastructure/dogfood/terraform/aws/redis.tf#L13)
In AWS we recommend running ElastiCache (Redis Engine).
#### Fleet server
Running Fleet in ECS consists of two main components the [ECS Service](https://github.com/fleetdm/fleet/blob/main/infrastructure/dogfood/terraform/aws/ecs.tf#L84) & [Load Balancer](https://github.com/fleetdm/fleet/blob/main/infrastructure/dogfood/terraform/aws/ecs.tf#L59). In our example the ALB is [handling TLS termination](https://github.com/fleetdm/fleet/blob/main/infrastructure/dogfood/terraform/aws/ecs.tf#L46)
Running Fleet in ECS consists of two main components the ECS service & load balancer.
#### Fleet migrations
Migrations in ECS can be achieved by running [dedicated ECS tasks](https://github.com/fleetdm/fleet/tree/main/infrastructure/dogfood/terraform/aws#migrating-the-db) that run the `fleet prepare --no-prompt=true db` command. See [terraform for more details](https://github.com/fleetdm/fleet/blob/main/infrastructure/dogfood/terraform/aws/ecs.tf#L261)
Migrations in ECS can be achieved by running dedicated ECS tasks that run the `fleet prepare --no-prompt=true db` command.
Alternatively you can bake the prepare command into the same task definition see [here for a discussion](https://github.com/fleetdm/fleet/pull/1761#discussion_r697599457), but this is not recommended for production environments.
+1 -1
View File
@@ -45,7 +45,7 @@ Note that Firehose logging has limits [discussed in the documentation](https://d
To send logs to Snowflake, you must first configure Fleet to send logs to [Amazon Kinesis Data Firehose (Firehose)](#amazon-kinesis-data-firehose). This is because you'll use the Snowflake Snowpipe integration to direct logs to Snowflake.
If you're using Fleet's [terraform reference architecture](https://github.com/fleetdm/fleet/blob/main/infrastructure/dogfood/terraform/aws/firehose.tf), Firehose is already configured as your log destination.
If you're using Fleet's [best practice Terraform](https://github.com/fleetdm/fleet-terraform), Firehose is already configured as your log destination.
With Fleet configured to send logs to Firehose, you then want to load the data from Firehose into a Snowflake database. AWS provides instructions on how to direct logs to a Snowflake database [here in the AWS documentation](https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/automate-data-stream-ingestion-into-a-snowflake-database-by-using-snowflake-snowpipe-amazon-s3-amazon-sns-and-amazon-kinesis-data-firehose.html)
+1 -1
View File
@@ -20,7 +20,7 @@ Note: Please prefix versions with `fleet-v` (e.g., `fleet-v4.0.0`) in git tags,
- [fleetctl package.json](https://github.com/fleetdm/fleet/blob/main/tools/fleetctl-npm/package.json) (do not yet `npm publish`)
- [Helm chart.yaml](https://github.com/fleetdm/fleet/blob/main/charts/fleet/Chart.yaml) and [values file](https://github.com/fleetdm/fleet/blob/main/charts/fleet/values.yaml)
- Terraform variables ([AWS](https://github.com/fleetdm/fleet/blob/main/infrastructure/dogfood/terraform/aws/variables.tf)/[GCP](https://github.com/fleetdm/fleet/blob/main/infrastructure/dogfood/terraform/gcp/variables.tf))
- GCP Terraform [variables](https://github.com/fleetdm/fleet/blob/main/infrastructure/dogfood/terraform/gcp/variables.tf)
- [Kubernetes `fleet-deployment.yml` file](https://github.com/fleetdm/fleet/blob/main/docs/Deploy/kubernetes/fleet-deployment.yml)
- All Terraform (*.tf) files referencing the previous version of Fleet.
- The full list can be found by using git grep:
@@ -1 +0,0 @@
1.0.4
-80
View File
@@ -1,80 +0,0 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "4.32.0"
constraints = ">= 3.30.0, >= 3.73.0, 4.32.0"
hashes = [
"h1:8AKJChT1Sgqjfdn16BayH5DonF3B9g7qQ6N+IKPulP4=",
"zh:062c30cd8bcf29f8ee34c2b2509e4e8695c2bcac8b7a8145e1c72e83d4e68b13",
"zh:1503fabaace96a7eea4d73ced36a02a75ec587760850e58162e7eff419dcbb31",
"zh:39a1fa36f8cb999f048bf0000d9dab40b8b0c77df35584fb08aa8bd6c5052dee",
"zh:471a755d43b51cd7be3e386cebc151ad8d548c5dea798343620476887e721882",
"zh:61ed56fab811e62b8286e606d003f7eeb7e940ef99bb49c1d283d91c0b748cc7",
"zh:80607dfe5f7770d136d5c451308b9861084ffad08139de8014e48672ec43ea3f",
"zh:863bf0a6576f7a969a89631525250d947fbb207d3d13e7ca4f74d86bd97cdda3",
"zh:9a8f2e77e4f99dbb618eb8ad17218a4698833754b50d46da5727323a2050a400",
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
"zh:9b74ff6e638c2a470b3599d57c2081e0095976da0a54b6590884d571f930b53b",
"zh:da4fc553d50ae833d860ec95120e271c29b4cb636917ab5991327362b7486bb7",
"zh:f4b86e7df4e846a38774e8e648b41c5ebaddcefa913cfa1864568086b7735575",
]
}
provider "registry.terraform.io/hashicorp/null" {
version = "3.2.1"
hashes = [
"h1:FbGfc+muBsC17Ohy5g806iuI1hQc4SIexpYCrQHQd8w=",
"zh:58ed64389620cc7b82f01332e27723856422820cfd302e304b5f6c3436fb9840",
"zh:62a5cc82c3b2ddef7ef3a6f2fedb7b9b3deff4ab7b414938b08e51d6e8be87cb",
"zh:63cff4de03af983175a7e37e52d4bd89d990be256b16b5c7f919aff5ad485aa5",
"zh:74cb22c6700e48486b7cabefa10b33b801dfcab56f1a6ac9b6624531f3d36ea3",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:79e553aff77f1cfa9012a2218b8238dd672ea5e1b2924775ac9ac24d2a75c238",
"zh:a1e06ddda0b5ac48f7e7c7d59e1ab5a4073bbcf876c73c0299e4610ed53859dc",
"zh:c37a97090f1a82222925d45d84483b2aa702ef7ab66532af6cbcfb567818b970",
"zh:e4453fbebf90c53ca3323a92e7ca0f9961427d2f0ce0d2b65523cc04d5d999c2",
"zh:e80a746921946d8b6761e77305b752ad188da60688cfd2059322875d363be5f5",
"zh:fbdb892d9822ed0e4cb60f2fedbdbb556e4da0d88d3b942ae963ed6ff091e48f",
"zh:fca01a623d90d0cad0843102f9b8b9fe0d3ff8244593bd817f126582b52dd694",
]
}
provider "registry.terraform.io/hashicorp/random" {
version = "3.4.3"
constraints = ">= 2.2.0"
hashes = [
"h1:xZGZf18JjMS06pFa4NErzANI98qi59SEcBsOcS2P2yQ=",
"zh:41c53ba47085d8261590990f8633c8906696fa0a3c4b384ff6a7ecbf84339752",
"zh:59d98081c4475f2ad77d881c4412c5129c56214892f490adf11c7e7a5a47de9b",
"zh:686ad1ee40b812b9e016317e7f34c0d63ef837e084dea4a1f578f64a6314ad53",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:84103eae7251384c0d995f5a257c72b0096605048f757b749b7b62107a5dccb3",
"zh:8ee974b110adb78c7cd18aae82b2729e5124d8f115d484215fd5199451053de5",
"zh:9dd4561e3c847e45de603f17fa0c01ae14cae8c4b7b4e6423c9ef3904b308dda",
"zh:bb07bb3c2c0296beba0beec629ebc6474c70732387477a65966483b5efabdbc6",
"zh:e891339e96c9e5a888727b45b2e1bb3fcbdfe0fd7c5b4396e4695459b38c8cb1",
"zh:ea4739860c24dfeaac6c100b2a2e357106a89d18751f7693f3c31ecf6a996f8d",
"zh:f0c76ac303fd0ab59146c39bc121c5d7d86f878e9a69294e29444d4c653786f8",
"zh:f143a9a5af42b38fed328a161279906759ff39ac428ebcfe55606e05e1518b93",
]
}
provider "registry.terraform.io/hashicorp/tls" {
version = "4.0.4"
hashes = [
"h1:pe9vq86dZZKCm+8k1RhzARwENslF3SXb9ErHbQfgjXU=",
"zh:23671ed83e1fcf79745534841e10291bbf34046b27d6e68a5d0aab77206f4a55",
"zh:45292421211ffd9e8e3eb3655677700e3c5047f71d8f7650d2ce30242335f848",
"zh:59fedb519f4433c0fdb1d58b27c210b27415fddd0cd73c5312530b4309c088be",
"zh:5a8eec2409a9ff7cd0758a9d818c74bcba92a240e6c5e54b99df68fff312bbd5",
"zh:5e6a4b39f3171f53292ab88058a59e64825f2b842760a4869e64dc1dc093d1fe",
"zh:810547d0bf9311d21c81cc306126d3547e7bd3f194fc295836acf164b9f8424e",
"zh:824a5f3617624243bed0259d7dd37d76017097dc3193dac669be342b90b2ab48",
"zh:9361ccc7048be5dcbc2fafe2d8216939765b3160bd52734f7a9fd917a39ecbd8",
"zh:aa02ea625aaf672e649296bce7580f62d724268189fe9ad7c1b36bb0fa12fa60",
"zh:c71b4cd40d6ec7815dfeefd57d88bc592c0c42f5e5858dcc88245d371b4b8b1e",
"zh:dabcd52f36b43d250a3d71ad7abfa07b5622c69068d989e60b79b2bb4f220316",
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
]
}
@@ -1,97 +0,0 @@
data "aws_iam_policy_document" "fleet" {
statement {
effect = "Allow"
actions = ["cloudwatch:PutMetricData"]
resources = ["*"]
}
// allow fleet application to obtain the database password from secrets manager
statement {
effect = "Allow"
actions = ["secretsmanager:GetSecretValue"]
resources = [aws_secretsmanager_secret.database_password_secret.arn]
}
// useful when there is a static number of mysql cluster members
dynamic "statement" {
for_each = module.aurora_mysql.rds_cluster_instance_dbi_resource_ids
content {
effect = "Allow"
actions = ["rds-db:connect"]
resources = ["arn:aws:rds-db:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:dbuser:${statement.value}/${var.database_user}"]
}
}
// allow access to any database via IAM that has the var.database_user user
// useful when you are autoscaling mysql read replicas dynamically
statement {
effect = "Allow"
actions = ["rds-db:connect"]
resources = ["arn:aws:rds-db:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:dbuser:*/${var.database_user}"]
}
// allow fleet application to write to kinesis firehose for osquery log destination configuration
statement {
effect = "Allow"
actions = [
"firehose:DescribeDeliveryStream",
"firehose:PutRecord",
"firehose:PutRecordBatch",
]
resources = [aws_kinesis_firehose_delivery_stream.osquery_results.arn, aws_kinesis_firehose_delivery_stream.osquery_status.arn]
}
// These actions are required for osquery file carving APIs
// We use wildcards on these actions for buckets that are single-use.
statement { #tfsec:ignore:aws-iam-no-policy-wildcards
effect = "Allow"
actions = [
"s3:GetObject*",
"s3:PutObject*",
"s3:ListBucket*",
"s3:ListMultipartUploadParts*",
"s3:DeleteObject",
"s3:CreateMultipartUpload",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts",
"s3:GetBucketLocation"
]
resources = [
aws_s3_bucket.osquery-carve.arn,
"${aws_s3_bucket.osquery-carve.arn}/*"
]
}
}
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
identifiers = ["ecs.amazonaws.com", "ecs-tasks.amazonaws.com"]
type = "Service"
}
}
}
resource "aws_iam_role" "main" {
name = "fleetdm-role"
description = "IAM role that Fleet application assumes when running in ECS"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
resource "aws_iam_role_policy_attachment" "role_attachment" {
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
role = aws_iam_role.main.name
}
resource "aws_iam_policy" "main" {
name = "fleet-iam-policy"
description = "IAM policy that Fleet application uses to define access to AWS resources"
policy = data.aws_iam_policy_document.fleet.json
}
resource "aws_iam_role_policy_attachment" "attachment" {
policy_arn = aws_iam_policy.main.arn
role = aws_iam_role.main.name
}
@@ -1,82 +0,0 @@
# Security group for the public internet facing load balancer
resource "aws_security_group" "lb" {
name = "${var.prefix} load balancer"
description = "${var.prefix} Load balancer security group"
vpc_id = module.vpc.vpc_id
}
# Allow traffic from public internet
resource "aws_security_group_rule" "lb-ingress" {
description = "${var.prefix}: allow traffic from public internet"
type = "ingress"
from_port = "443"
to_port = "443"
protocol = "tcp"
// Internet connectivity here is by design
cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:aws-vpc-no-public-ingress-sgr
security_group_id = aws_security_group.lb.id
}
resource "aws_security_group_rule" "lb-http-ingress" {
description = "${var.prefix}: allow traffic from public internet"
type = "ingress"
from_port = "80"
to_port = "80"
protocol = "tcp"
// Internet connectivity here is by design
cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:aws-vpc-no-public-ingress-sgr
security_group_id = aws_security_group.lb.id
}
# Allow outbound traffic
resource "aws_security_group_rule" "lb-egress" {
description = "${var.prefix}: allow all outbound traffic"
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
// Egress filtering is not currently provided by our Terraform templates.
cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:aws-vpc-no-public-egress-sgr:exp:2022-10-01
security_group_id = aws_security_group.lb.id
}
# Security group for the backends that run the application.
# Allows traffic from the load balancer
resource "aws_security_group" "backend" {
name = "${var.prefix} backend"
description = "${var.prefix} Backend security group"
vpc_id = module.vpc.vpc_id
}
# Allow traffic from the load balancer to the backends
resource "aws_security_group_rule" "backend-ingress" {
description = "${var.prefix}: allow traffic from load balancer"
type = "ingress"
from_port = "8080"
to_port = "8080"
protocol = "tcp"
source_security_group_id = aws_security_group.lb.id
security_group_id = aws_security_group.backend.id
}
# Allow outbound traffic from the backends
resource "aws_security_group_rule" "backend-egress" {
description = "${var.prefix}: allow all outbound traffic"
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
// Egress filtering is not currently provided by our Terraform templates.
cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:aws-vpc-no-public-egress-sgr:exp:2022-10-01
security_group_id = aws_security_group.backend.id
}
-373
View File
@@ -1,373 +0,0 @@
data "aws_region" "current" {}
resource "aws_route53_record" "record" {
name = "fleet-alb-${terraform.workspace}"
type = "A"
zone_id = aws_route53_zone.dogfood_fleetdm_com.zone_id
alias {
evaluate_target_health = false
name = aws_alb.main.dns_name
zone_id = aws_alb.main.zone_id
}
}
resource "aws_alb" "main" {
// Exposed to the Internet by design
internal = false #tfsec:ignore:aws-elb-alb-not-public
security_groups = [aws_security_group.lb.id, aws_security_group.backend.id]
subnets = module.vpc.public_subnets
idle_timeout = 905
name = "fleetdm"
drop_invalid_header_fields = true
}
resource "aws_alb_target_group" "main" {
name = "fleetdm"
protocol = "HTTP"
target_type = "ip"
port = "8080"
vpc_id = module.vpc.vpc_id
deregistration_delay = 30
load_balancing_algorithm_type = "least_outstanding_requests"
health_check {
path = "/healthz"
matcher = "200"
timeout = 10
interval = 15
healthy_threshold = 5
unhealthy_threshold = 5
}
depends_on = [aws_alb.main]
}
resource "aws_alb_listener" "https-fleetdm" {
load_balancer_arn = aws_alb.main.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-FS-1-2-Res-2019-08"
certificate_arn = aws_acm_certificate_validation.dogfood_fleetdm_com.certificate_arn
default_action {
target_group_arn = aws_alb_target_group.main.arn
type = "forward"
}
}
resource "aws_alb_listener" "http" {
load_balancer_arn = aws_alb.main.arn
port = "80"
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
resource "aws_ecs_cluster" "fleet" {
name = "${var.prefix}-backend"
setting {
name = "containerInsights"
value = "enabled"
}
}
resource "aws_ecs_service" "fleet" {
name = "fleet"
launch_type = "FARGATE"
cluster = aws_ecs_cluster.fleet.id
task_definition = aws_ecs_task_definition.backend.arn
desired_count = 5
deployment_minimum_healthy_percent = 100
deployment_maximum_percent = 200
health_check_grace_period_seconds = 30
load_balancer {
target_group_arn = aws_alb_target_group.main.arn
container_name = "fleet"
container_port = 8080
}
// https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service#ignoring-changes-to-desired-count
lifecycle {
ignore_changes = [desired_count]
}
network_configuration {
subnets = module.vpc.private_subnets
security_groups = [aws_security_group.backend.id]
}
depends_on = [aws_alb_listener.http, aws_alb_listener.https-fleetdm]
}
// Customer keys are not supported in our Fleet Terraforms at the moment. We will evaluate the
// possibility of providing this capability in the future.
resource "aws_cloudwatch_log_group" "backend" { #tfsec:ignore:aws-cloudwatch-log-group-customer-key:exp:2022-07-01
name = "fleetdm"
retention_in_days = var.cloudwatch_log_retention
}
resource "aws_ecs_task_definition" "backend" {
family = "fleet"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
execution_role_arn = aws_iam_role.main.arn
task_role_arn = aws_iam_role.main.arn
cpu = var.fleet_backend_cpu
memory = var.fleet_backend_mem
container_definitions = jsonencode(
[
{
name = "fleet"
image = var.fleet_image
cpu = var.fleet_backend_cpu
memory = var.fleet_backend_mem
mountPoints = []
volumesFrom = []
essential = true
portMappings = [
{
# This port is the same that the contained application also uses
containerPort = 8080
protocol = "tcp"
}
]
networkMode = "awsvpc"
logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-group = aws_cloudwatch_log_group.backend.name
awslogs-region = data.aws_region.current.name
awslogs-stream-prefix = "fleet"
}
},
ulimits = [
{
name = "nofile"
softLimit = 999999
hardLimit = 999999
}
],
secrets = [
{
name = "FLEET_MYSQL_PASSWORD"
valueFrom = aws_secretsmanager_secret.database_password_secret.arn
},
{
name = "FLEET_MYSQL_READ_REPLICA_PASSWORD"
valueFrom = aws_secretsmanager_secret.database_password_secret.arn
}
]
environment = [
{
name = "FLEET_MYSQL_USERNAME"
value = var.database_user
},
{
name = "FLEET_MYSQL_DATABASE"
value = var.database_name
},
{
name = "FLEET_MYSQL_ADDRESS"
value = "${module.aurora_mysql.rds_cluster_endpoint}:3306"
},
{
name = "FLEET_MYSQL_READ_REPLICA_USERNAME"
value = var.database_user
},
{
name = "FLEET_MYSQL_READ_REPLICA_DATABASE"
value = var.database_name
},
{
name = "FLEET_MYSQL_READ_REPLICA_ADDRESS"
value = "${module.aurora_mysql.rds_cluster_reader_endpoint}:3306"
},
{
name = "FLEET_REDIS_ADDRESS"
value = "${aws_elasticache_replication_group.default.primary_endpoint_address}:6379"
},
{
name = "FLEET_REDIS_USE_TLS"
value = "true"
},
{
name = "FLEET_FIREHOSE_STATUS_STREAM"
value = aws_kinesis_firehose_delivery_stream.osquery_status.name
},
{
name = "FLEET_FIREHOSE_RESULT_STREAM"
value = aws_kinesis_firehose_delivery_stream.osquery_results.name
},
{
name = "FLEET_FIREHOSE_REGION"
value = data.aws_region.current.name
},
{
name = "FLEET_OSQUERY_STATUS_LOG_PLUGIN"
value = "firehose"
},
{
name = "FLEET_OSQUERY_RESULT_LOG_PLUGIN"
value = "firehose"
},
{
name = "FLEET_SERVER_TLS"
value = "false"
},
{
name = "FLEET_VULNERABILITIES_DATABASES_PATH"
value = var.vuln_db_path
},
{
name = "FLEET_OSQUERY_ENABLE_ASYNC_HOST_PROCESSING"
value = var.async_host_processing
},
{
name = "FLEET_LOGGING_DEBUG"
value = var.logging_debug
},
{
name = "FLEET_LOGGING_JSON"
value = var.logging_json
},
{
name = "FLEET_S3_BUCKET"
value = aws_s3_bucket.osquery-carve.bucket
},
{
name = "FLEET_S3_PREFIX"
value = "carve_results/"
},
{
name = "FLEET_LICENSE_KEY"
value = var.fleet_license
}
]
}
])
}
resource "aws_ecs_task_definition" "migration" {
family = "fleet-migrate"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
execution_role_arn = aws_iam_role.main.arn
task_role_arn = aws_iam_role.main.arn
cpu = var.cpu_migrate
memory = var.mem_migrate
container_definitions = jsonencode(
[
{
name = "fleet-prepare-db"
image = var.fleet_image
cpu = var.cpu_migrate
memory = var.mem_migrate
mountPoints = []
volumesFrom = []
essential = true
portMappings = [
{
# This port is the same that the contained application also uses
containerPort = 8080
protocol = "tcp"
}
]
networkMode = "awsvpc"
logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-group = aws_cloudwatch_log_group.backend.name
awslogs-region = data.aws_region.current.name
awslogs-stream-prefix = "fleet"
}
},
command = ["fleet", "prepare", "--no-prompt=true", "db"]
secrets = [
{
name = "FLEET_MYSQL_PASSWORD"
valueFrom = aws_secretsmanager_secret.database_password_secret.arn
}
]
environment = [
{
name = "FLEET_MYSQL_USERNAME"
value = var.database_user
},
{
name = "FLEET_MYSQL_DATABASE"
value = var.database_name
},
{
name = "FLEET_MYSQL_ADDRESS"
value = "${module.aurora_mysql.rds_cluster_endpoint}:3306"
},
{
name = "FLEET_REDIS_ADDRESS"
value = "${aws_elasticache_replication_group.default.primary_endpoint_address}:6379"
},
{
name = "FLEET_REDIS_USE_TLS"
value = "true"
}
]
}
])
}
resource "aws_appautoscaling_target" "ecs_target" {
max_capacity = var.fleet_max_capacity
min_capacity = var.fleet_min_capacity
resource_id = "service/${aws_ecs_cluster.fleet.name}/${aws_ecs_service.fleet.name}"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
}
resource "aws_appautoscaling_policy" "ecs_policy_memory" {
name = "fleet-memory-autoscaling"
policy_type = "TargetTrackingScaling"
resource_id = aws_appautoscaling_target.ecs_target.resource_id
scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension
service_namespace = aws_appautoscaling_target.ecs_target.service_namespace
target_tracking_scaling_policy_configuration {
predefined_metric_specification {
predefined_metric_type = "ECSServiceAverageMemoryUtilization"
}
target_value = var.memory_tracking_target_value
}
}
resource "aws_appautoscaling_policy" "ecs_policy_cpu" {
name = "fleet-cpu-autoscaling"
policy_type = "TargetTrackingScaling"
resource_id = aws_appautoscaling_target.ecs_target.resource_id
scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension
service_namespace = aws_appautoscaling_target.ecs_target.service_namespace
target_tracking_scaling_policy_configuration {
predefined_metric_specification {
predefined_metric_type = "ECSServiceAverageCPUUtilization"
}
target_value = var.cpu_tracking_target_value
}
}
output "fleet_ecs_cluster_arn" {
value = aws_ecs_cluster.fleet.arn
}
output "fleet_ecs_cluster_id" {
value = aws_ecs_cluster.fleet.id
}
@@ -1,161 +0,0 @@
// Customer keys are not supported in our Fleet Terraforms at the moment. We will evaluate the
// possibility of providing this capability in the future.
// No versioning on this bucket is by design.
// Bucket logging is not supported in our Fleet Terraforms at the moment. It can be enabled by the
// organizations deploying Fleet, and we will evaluate the possibility of providing this capability
// in the future.
resource "aws_s3_bucket" "osquery-results" { #tfsec:ignore:aws-s3-encryption-customer-key:exp:2022-07-01 #tfsec:ignore:aws-s3-enable-versioning #tfsec:ignore:aws-s3-enable-bucket-logging:exp:2022-06-15
bucket = var.osquery_results_s3_bucket
acl = "private"
lifecycle_rule {
enabled = true
expiration {
days = 1
}
}
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
}
resource "aws_s3_bucket_public_access_block" "osquery-results" {
bucket = aws_s3_bucket.osquery-results.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
// Customer keys are not supported in our Fleet Terraforms at the moment. We will evaluate the
// possibility of providing this capability in the future.
// No versioning on this bucket is by design.
// Bucket logging is not supported in our Fleet Terraforms at the moment. It can be enabled by the
// organizations deploying Fleet, and we will evaluate the possibility of providing this capability
// in the future.
resource "aws_s3_bucket" "osquery-status" { #tfsec:ignore:aws-s3-encryption-customer-key:exp:2022-07-01 #tfsec:ignore:aws-s3-enable-versioning #tfsec:ignore:aws-s3-enable-bucket-logging:exp:2022-06-15
bucket = var.osquery_status_s3_bucket
acl = "private"
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
}
resource "aws_s3_bucket_lifecycle_configuration" "osquery-status" {
bucket = aws_s3_bucket.osquery-status.id
rule {
id = "rule-1"
status = "Enabled"
expiration {
days = 1
}
}
}
resource "aws_s3_bucket_public_access_block" "osquery-status" {
bucket = aws_s3_bucket.osquery-status.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
data "aws_iam_policy_document" "osquery_results_policy_doc" {
statement {
effect = "Allow"
actions = [
"s3:AbortMultipartUpload",
"s3:GetBucketLocation",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
"s3:PutObject"
]
// This bucket is single-purpose and using a wildcard is not problematic
resources = [aws_s3_bucket.osquery-results.arn, "${aws_s3_bucket.osquery-results.arn}/*"] #tfsec:ignore:aws-iam-no-policy-wildcards
}
}
data "aws_iam_policy_document" "osquery_status_policy_doc" {
statement {
effect = "Allow"
actions = [
"s3:AbortMultipartUpload",
"s3:GetBucketLocation",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
"s3:PutObject"
]
// This bucket is single-purpose and using a wildcard is not problematic
resources = [aws_s3_bucket.osquery-status.arn, "${aws_s3_bucket.osquery-status.arn}/*"] #tfsec:ignore:aws-iam-no-policy-wildcards
}
}
resource "aws_iam_policy" "firehose-results" {
name = "osquery_results_firehose_policy"
policy = data.aws_iam_policy_document.osquery_results_policy_doc.json
}
resource "aws_iam_policy" "firehose-status" {
name = "osquery_status_firehose_policy"
policy = data.aws_iam_policy_document.osquery_status_policy_doc.json
}
resource "aws_iam_role" "firehose-results" {
assume_role_policy = data.aws_iam_policy_document.osquery_firehose_assume_role.json
}
resource "aws_iam_role" "firehose-status" {
assume_role_policy = data.aws_iam_policy_document.osquery_firehose_assume_role.json
}
resource "aws_iam_role_policy_attachment" "firehose-results" {
policy_arn = aws_iam_policy.firehose-results.arn
role = aws_iam_role.firehose-results.name
}
resource "aws_iam_role_policy_attachment" "firehose-status" {
policy_arn = aws_iam_policy.firehose-status.arn
role = aws_iam_role.firehose-status.name
}
data "aws_iam_policy_document" "osquery_firehose_assume_role" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
identifiers = ["firehose.amazonaws.com"]
type = "Service"
}
}
}
resource "aws_kinesis_firehose_delivery_stream" "osquery_results" {
name = "osquery_results"
destination = "s3"
s3_configuration {
role_arn = aws_iam_role.firehose-results.arn
bucket_arn = aws_s3_bucket.osquery-results.arn
}
}
resource "aws_kinesis_firehose_delivery_stream" "osquery_status" {
name = "osquery_status"
destination = "s3"
s3_configuration {
role_arn = aws_iam_role.firehose-status.arn
bucket_arn = aws_s3_bucket.osquery-status.arn
}
}
@@ -1,21 +0,0 @@
provider "aws" {
region = var.region
}
terraform {
// these values should match what is bootstrapped in ./remote-state
backend "s3" {
bucket = "fleet-terraform-remote-state"
region = "us-east-2"
key = "fleet"
dynamodb_table = "fleet-terraform-state-lock"
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "4.32.0"
}
}
}
data "aws_caller_identity" "current" {}
@@ -1,363 +0,0 @@
terraform {
// these values should match what is bootstrapped in ./remote-state
backend "s3" {
bucket = "fleet-terraform-remote-state"
region = "us-east-2"
key = "fleet-monitoring/"
dynamodb_table = "fleet-terraform-state-lock"
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "3.57.0"
}
}
}
provider "aws" {
region = "us-east-2"
}
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
data "terraform_remote_state" "fleet" {
backend = "s3"
config = {
bucket = "fleet-terraform-remote-state"
region = "us-east-2"
key = "env:/${terraform.workspace}/fleet"
}
}
locals {
fleet_ecs_service_name = data.terraform_remote_state.fleet.outputs.fleet_ecs_service_name
alb_target_group_name = data.terraform_remote_state.fleet.outputs.aws_alb_target_group_name
alb_name = data.terraform_remote_state.fleet.outputs.aws_alb_name
}
// sns topic to send cloudwatch alarms to
// sns topic encryption is not currently supported but is something we will investigate providing in
// the future
resource "aws_sns_topic" "cloudwatch_alarm_topic" { #tfsec:ignore:aws-sns-enable-topic-encryption:exp:2022-08-01
name = "cloudwatch-alarm-${terraform.workspace}"
}
resource "aws_sns_topic_policy" "default" {
arn = aws_sns_topic.cloudwatch_alarm_topic.arn
policy = data.aws_iam_policy_document.sns_topic_policy.json
}
data "aws_iam_policy_document" "sns_topic_policy" {
statement {
sid = "AllowManageSNS"
actions = [
"sns:Subscribe",
"sns:SetTopicAttributes",
"sns:RemovePermission",
"sns:Receive",
"sns:Publish",
"sns:ListSubscriptionsByTopic",
"sns:GetTopicAttributes",
"sns:DeleteTopic",
"sns:AddPermission",
]
effect = "Allow"
resources = [aws_sns_topic.cloudwatch_alarm_topic.arn]
principals {
type = "AWS"
identifiers = ["*"]
}
condition {
test = "StringEquals"
variable = "AWS:SourceOwner"
values = [data.aws_caller_identity.current.account_id]
}
}
statement {
sid = "Allow CloudwatchEvents"
actions = ["sns:Publish"]
resources = [aws_sns_topic.cloudwatch_alarm_topic.arn]
principals {
type = "Service"
identifiers = ["events.amazonaws.com"]
}
}
statement {
sid = "Allow RDS Event Notification"
actions = ["sns:Publish"]
resources = [aws_sns_topic.cloudwatch_alarm_topic.arn]
principals {
type = "Service"
identifiers = ["rds.amazonaws.com"]
}
}
}
// Database alarms
resource "aws_cloudwatch_metric_alarm" "cpu_utilization_too_high" {
for_each = data.terraform_remote_state.fleet.outputs.mysql_cluster_members
alarm_name = "rds_cpu_utilization_too_high-${each.key}-${terraform.workspace}"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = "CPUUtilization"
namespace = "AWS/RDS"
period = "300"
statistic = "Average"
threshold = 80
alarm_description = "Average database CPU utilization over last 5 minutes too high"
alarm_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
ok_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
dimensions = {
DBInstanceIdentifier = each.key
}
}
resource "aws_db_event_subscription" "default" {
name = "rds-event-sub-${terraform.workspace}"
sns_topic = aws_sns_topic.cloudwatch_alarm_topic.arn
source_type = "db-instance"
source_ids = data.terraform_remote_state.fleet.outputs.mysql_cluster_members
event_categories = [
"failover",
"failure",
"low storage",
"maintenance",
"notification",
"recovery",
]
depends_on = [
aws_sns_topic_policy.default
]
}
// ECS Alarms
resource "aws_cloudwatch_metric_alarm" "alb_healthyhosts" {
alarm_name = "backend-healthyhosts-${terraform.workspace}"
comparison_operator = "LessThanThreshold"
evaluation_periods = "1"
metric_name = "HealthyHostCount"
namespace = "AWS/ApplicationELB"
period = "60"
statistic = "Minimum"
threshold = data.terraform_remote_state.fleet.outputs.fleet_min_capacity
alarm_description = "This alarm indicates the number of Healthy Fleet hosts is lower than expected. Please investigate the load balancer \"${local.alb_name}\" or the target group \"${local.alb_target_group_name}\" and the fleet backend service \"${local.fleet_ecs_service_name}\""
actions_enabled = "true"
alarm_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
ok_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
dimensions = {
TargetGroup = data.terraform_remote_state.fleet.outputs.target_group_arn_suffix
LoadBalancer = data.terraform_remote_state.fleet.outputs.load_balancer_arn_suffix
}
}
// alarm for target response time (anomaly detection)
resource "aws_cloudwatch_metric_alarm" "target_response_time" {
alarm_name = "backend-target-response-time-${terraform.workspace}"
comparison_operator = "GreaterThanUpperThreshold"
evaluation_periods = "2"
threshold_metric_id = "e1"
alarm_description = "This alarm indicates the Fleet server response time is greater than it usually is. Please investigate the ecs service \"${local.fleet_ecs_service_name}\" because the backend might need to be scaled up."
alarm_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
ok_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
insufficient_data_actions = []
metric_query {
id = "e1"
expression = "ANOMALY_DETECTION_BAND(m1)"
label = "TargetResponseTime (Expected)"
return_data = "true"
}
metric_query {
id = "m1"
return_data = "true"
metric {
metric_name = "TargetResponseTime"
namespace = "AWS/ApplicationELB"
period = "120"
stat = "p99"
unit = "Count"
dimensions = {
TargetGroup = data.terraform_remote_state.fleet.outputs.target_group_arn_suffix
LoadBalancer = data.terraform_remote_state.fleet.outputs.load_balancer_arn_suffix
}
}
}
}
resource "aws_cloudwatch_metric_alarm" "httpcode_elb_5xx_count" {
alarm_name = "backend-load-balancer-5XX-${terraform.workspace}"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "HTTPCode_ELB_5XX_Count"
namespace = "AWS/ApplicationELB"
period = "60"
statistic = "Sum"
threshold = "25"
alarm_description = "This alarm indicates there are an abnormal amount of load balancer 5XX responses i.e it cannot talk with the Fleet backend target"
treat_missing_data = "notBreaching"
alarm_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
ok_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
dimensions = {
LoadBalancer = data.terraform_remote_state.fleet.outputs.load_balancer_arn_suffix
}
}
// Elasticache (redis) alerts https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/CacheMetrics.WhichShouldIMonitor.html
resource "aws_cloudwatch_metric_alarm" "redis_cpu" {
for_each = data.terraform_remote_state.fleet.outputs.redis_cluster_members
alarm_name = "redis-cpu-utilization-${each.key}-${terraform.workspace}"
alarm_description = "Redis cluster CPU utilization node ${each.key}"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = "CPUUtilization"
namespace = "AWS/ElastiCache"
period = "300"
statistic = "Average"
alarm_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
ok_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
threshold = "70"
dimensions = {
CacheClusterId = each.key
}
}
resource "aws_cloudwatch_metric_alarm" "redis_cpu_engine_utilization" {
for_each = data.terraform_remote_state.fleet.outputs.redis_cluster_members
alarm_name = "redis-cpu-engine-utilization-${each.key}-${terraform.workspace}"
alarm_description = "Redis cluster CPU Engine utilization node ${each.key}"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = "EngineCPUUtilization"
namespace = "AWS/ElastiCache"
period = "300"
statistic = "Average"
alarm_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
ok_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
threshold = "25"
dimensions = {
CacheClusterId = each.key
}
}
resource "aws_cloudwatch_metric_alarm" "redis-database-memory-percentage" {
alarm_name = "redis-database-memory-percentage-${terraform.workspace}"
alarm_description = "Percentage of the memory available for the cluster that is in use. This is calculated using used_memory/maxmemory."
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = "DatabaseMemoryUsagePercentage"
namespace = "AWS/ElastiCache"
period = "300"
statistic = "Average"
alarm_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
ok_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
threshold = "80"
}
resource "aws_cloudwatch_metric_alarm" "redis-current-connections" {
for_each = data.terraform_remote_state.fleet.outputs.redis_cluster_members
alarm_name = "redis-current-connections-${each.key}-${terraform.workspace}"
alarm_description = "Redis current connections for node ${each.key}"
comparison_operator = "LessThanLowerOrGreaterThanUpperThreshold"
evaluation_periods = "5"
threshold_metric_id = "e1"
alarm_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
ok_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
insufficient_data_actions = []
metric_query {
id = "e1"
expression = "ANOMALY_DETECTION_BAND(m1,20)"
label = "Current Connections (Expected)"
return_data = "true"
}
metric_query {
id = "m1"
return_data = "true"
metric {
metric_name = "CurrConnections"
namespace = "AWS/ElastiCache"
period = "600"
stat = "Average"
unit = "Count"
dimensions = {
CacheClusterId = each.key
}
}
}
}
resource "aws_cloudwatch_metric_alarm" "redis-replication-lag" {
alarm_name = "redis-replication-lag-${terraform.workspace}"
alarm_description = "This metric is only applicable for a node running as a read replica. It represents how far behind, in seconds, the replica is in applying changes from the primary node. For Redis engine version 5.0.6 onwards, the lag can be measured in milliseconds."
comparison_operator = "GreaterThanUpperThreshold"
evaluation_periods = "3"
threshold_metric_id = "e1"
alarm_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
ok_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
insufficient_data_actions = []
metric_query {
id = "e1"
expression = "ANOMALY_DETECTION_BAND(m1)"
label = "ReplicationLag (expected)"
return_data = "true"
}
metric_query {
id = "m1"
return_data = "true"
metric {
metric_name = "ReplicationLag"
namespace = "AWS/ElastiCache"
period = "300"
stat = "p90"
}
}
}
// ACM Certificate Manager
resource "aws_cloudwatch_metric_alarm" "acm_certificate_expired" {
alarm_name = "acm-cert-expiry-${terraform.workspace}"
comparison_operator = "LessThanThreshold"
evaluation_periods = "1"
period = "86400" // 1 day in seconds
threshold = 30 // days
statistic = "Average"
namespace = "AWS/CertificateManager"
metric_name = "DaysToExpiry"
actions_enabled = "true"
alarm_description = "ACM Certificate will expire soon"
alarm_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
ok_actions = [aws_sns_topic.cloudwatch_alarm_topic.arn]
dimensions = {
CertificateArn = data.terraform_remote_state.fleet.outputs.acm_certificate_arn
}
}
@@ -1,71 +0,0 @@
output "nameservers_fleetdm" {
value = aws_route53_zone.dogfood_fleetdm_com.name_servers
}
output "backend_security_group" {
value = aws_security_group.backend.arn
}
output "private_subnets" {
value = module.vpc.private_subnets
}
output "fleet-backend-task-revision" {
value = aws_ecs_task_definition.backend.revision
}
output "fleet-migration-task-revision" {
value = aws_ecs_task_definition.migration.revision
}
output "redis_cluster_members" {
value = toset(aws_elasticache_replication_group.default.member_clusters)
}
output "mysql_cluster_members" {
value = toset(module.aurora_mysql.rds_cluster_instance_ids)
}
output "acm_certificate_arn" {
value = aws_acm_certificate.dogfood_fleetdm_com.arn
}
output "load_balancer_arn_suffix" {
value = aws_alb.main.arn_suffix
}
output "target_group_arn_suffix" {
value = aws_alb_target_group.main.arn_suffix
}
output "fleet_min_capacity" {
value = var.fleet_min_capacity
}
output "fleet_ecs_service_name" {
value = aws_ecs_service.fleet.name
}
output "aws_alb_target_group_name" {
value = aws_alb_target_group.main.name
}
output "aws_alb_name" {
value = aws_alb.main.name
}
output "backend_security_group_id" {
value = aws_security_group.backend.id
}
output "private_subnet" {
value = module.vpc.private_subnets[0]
}
output "ecs_cluster_name" {
value = aws_ecs_cluster.fleet.name
}
output "migrate_task_definition_family" {
value = aws_ecs_task_definition.migration.family
}
@@ -1,30 +0,0 @@
resource "aws_acm_certificate" "certificate" {
domain_name = var.domain_name
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
resource "aws_route53_record" "record" {
for_each = {
for dvo in aws_acm_certificate.certificate.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = var.zone_id
}
resource "aws_acm_certificate_validation" "percona" {
certificate_arn = aws_acm_certificate.certificate.arn
validation_record_fqdns = [for record in aws_route53_record.record : record.fqdn]
}
@@ -1,137 +0,0 @@
data "aws_ami" "percona" {
most_recent = true
filter {
name = "name"
values = ["PMM2 Server *"]
}
owners = ["679593333241"] # Percona
}
resource "aws_route53_record" "record" {
name = "percona"
type = "A"
zone_id = var.zone_id
alias {
evaluate_target_health = false
name = aws_lb.main.dns_name
zone_id = aws_lb.main.zone_id
}
}
resource "aws_lb" "main" {
name = "percona"
// Exposed to the Internet by design
internal = false #tfsec:ignore:aws-elb-alb-not-public
security_groups = [aws_security_group.lb.id, aws_security_group.backend.id]
subnets = var.public_subnets
idle_timeout = 905
drop_invalid_header_fields = true
}
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.main.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-FS-1-2-Res-2019-08"
certificate_arn = aws_acm_certificate_validation.percona.certificate_arn
default_action {
target_group_arn = aws_lb_target_group.percona.arn
type = "forward"
}
}
resource "aws_lb_target_group" "percona" {
name = "percona"
protocol = "HTTP"
target_type = "instance"
port = "80"
vpc_id = var.vpc_id
}
resource "aws_lb_target_group_attachment" "percona" {
target_group_arn = aws_lb_target_group.percona.arn
target_id = aws_instance.percona.id
}
// No permanent EBS encrypted volume at the moment.
resource "aws_instance" "percona" { #tfsec:ignore:aws-ec2-enable-at-rest-encryption:exp:2022-07-15
ami = data.aws_ami.percona.id
instance_type = "m5.large"
subnet_id = var.private_subnet
vpc_security_group_ids = [aws_security_group.backend.id]
iam_instance_profile = aws_iam_instance_profile.profile.name
metadata_options {
http_tokens = "required"
}
}
resource "aws_iam_instance_profile" "profile" {
name = "percona-profile"
role = aws_iam_role.role.name
}
resource "aws_iam_role" "role" {
name = "percona-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
// This bucket is single-purpose and using a wildcard is not problematic
resource "aws_iam_policy" "policy" { #tfsec:ignore:aws-iam-no-policy-wildcards
name = "percona-policy"
description = "policy to discover rds instances"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1508404837000",
"Effect": "Allow",
"Action": [
"rds:DescribeDBInstances",
"cloudwatch:GetMetricStatistics",
"cloudwatch:ListMetrics"
],
"Resource": [
"*"
]
},
{
"Sid": "Stmt1508410723001",
"Effect": "Allow",
"Action": [
"logs:DescribeLogStreams",
"logs:GetLogEvents",
"logs:FilterLogEvents"
],
"Resource": [
"arn:aws:logs:*:*:log-group:RDSOSMetrics:*"
]
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "test-attach" {
role = aws_iam_role.role.name
policy_arn = aws_iam_policy.policy.arn
}
@@ -1,73 +0,0 @@
resource "aws_security_group" "lb" {
name = "percona load balancer"
description = "percona Load balancer security group"
vpc_id = var.vpc_id
}
resource "aws_security_group_rule" "lb-ingress" {
description = "percona: allow traffic from public internet"
type = "ingress"
from_port = "443"
to_port = "443"
protocol = "tcp"
// Internet connectivity here is by design
cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:aws-vpc-no-public-ingress-sgr
security_group_id = aws_security_group.lb.id
}
resource "aws_security_group_rule" "lb-http-ingress" {
description = "percona: allow traffic from public internet"
type = "ingress"
from_port = "80"
to_port = "80"
protocol = "tcp"
// Internet connectivity here is by design
cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:aws-vpc-no-public-ingress-sgr
security_group_id = aws_security_group.lb.id
}
resource "aws_security_group_rule" "backend-egress" {
description = "percona: allow all outbound traffic"
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
// Egress filtering is not currently provided by our Terraform templates.
cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:aws-vpc-no-public-egress-sgr:exp:2022-10-01
security_group_id = aws_security_group.backend.id
}
resource "aws_security_group" "backend" {
name = "percona backend"
description = "percona Backend security group"
vpc_id = var.vpc_id
}
resource "aws_security_group_rule" "lb-egress" {
description = "percona: allow all outbound traffic"
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
// Egress filtering is not currently provided by our Terraform templates.
cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:aws-vpc-no-public-egress-sgr:exp:2022-10-01
security_group_id = aws_security_group.lb.id
}
resource "aws_security_group_rule" "backend-ingress" {
description = "percona: allow traffic from load balancer"
type = "ingress"
from_port = "80"
to_port = "80"
protocol = "tcp"
source_security_group_id = aws_security_group.lb.id
security_group_id = aws_security_group.backend.id
}
@@ -1,24 +0,0 @@
variable "zone_id" {
description = "R53 Zone ID to host Percona in"
type = string
}
variable "domain_name" {
description = "Domain name for Percona DNS"
type = string
}
variable "public_subnets" {
description = "Public subnets for the Percona LB"
type = list(string)
}
variable "private_subnet" {
description = "Private subnets for the Percona App instance"
type = string
}
variable "vpc_id" {
description = "VPC ID"
type = string
}
@@ -1,46 +0,0 @@
resource "aws_route53_zone" "dogfood_fleetdm_com" {
name = var.domain_fleetdm
}
resource "aws_route53_record" "dogfood_fleetdm_com" {
zone_id = aws_route53_zone.dogfood_fleetdm_com.zone_id
name = var.domain_fleetdm
type = "A"
alias {
name = aws_alb.main.dns_name
zone_id = aws_alb.main.zone_id
evaluate_target_health = false
}
}
resource "aws_acm_certificate" "dogfood_fleetdm_com" {
domain_name = var.domain_fleetdm
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
resource "aws_route53_record" "dogfood_fleetdm_com_validation" {
for_each = {
for dvo in aws_acm_certificate.dogfood_fleetdm_com.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = aws_route53_zone.dogfood_fleetdm_com.zone_id
}
resource "aws_acm_certificate_validation" "dogfood_fleetdm_com" {
certificate_arn = aws_acm_certificate.dogfood_fleetdm_com.arn
validation_record_fqdns = [for record in aws_route53_record.dogfood_fleetdm_com_validation : record.fqdn]
}
-129
View File
@@ -1,129 +0,0 @@
resource "random_password" "database_password" {
length = 32
special = false
}
// Customer keys are not supported in our Fleet Terraforms at the moment. We will evaluate the
// possibility of providing this capability in the future.
resource "aws_secretsmanager_secret" "database_password_secret" { #tfsec:ignore:aws-ssm-secret-use-customer-key:exp:2022-07-01
name = "/fleet/database/password/master"
recovery_window_in_days = 0
}
resource "aws_secretsmanager_secret_version" "database_password_secret_version" {
secret_id = aws_secretsmanager_secret.database_password_secret.id
secret_string = random_password.database_password.result
}
// if you want to use RDS Serverless option prefer the following commented block
//module "aurora_mysql_serverless" {
// source = "terraform-aws-modules/rds-aurora/aws"
// version = "5.2.0"
//
// name = "${local.name}-mysql"
// engine = "aurora-mysql"
// engine_mode = "serverless"
// storage_encrypted = true
// username = "fleet"
// password = random_password.database_password.result
// create_random_password = false
// database_name = "fleet"
// enable_http_endpoint = true
//
// vpc_id = module.vpc.vpc_id
// subnets = module.vpc.database_subnets
// create_security_group = true
// allowed_cidr_blocks = concat(module.vpc.private_subnets_cidr_blocks, var.extra_security_group_cidrs)
//
// replica_scale_enabled = false
// replica_count = 0
//
// monitoring_interval = 60
//
// apply_immediately = true
// skip_final_snapshot = true
//
// db_parameter_group_name = aws_db_parameter_group.example_mysql.id
// db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.example_mysql.id
//
// scaling_configuration = {
// auto_pause = true
// min_capacity = 2
// max_capacity = 16
// seconds_until_auto_pause = 300
// timeout_action = "ForceApplyCapacityChange"
// }
//}
variable "db_instance_type_writer" {
default = "db.t4g.medium"
}
variable "db_instance_type_reader" {
default = "db.t4g.medium"
}
module "aurora_mysql" {
source = "terraform-aws-modules/rds-aurora/aws"
version = "5.2.0"
name = "${local.name}-mysql-iam"
engine = "aurora-mysql"
engine_version = "8.0.mysql_aurora.3.05.2"
instance_type = var.db_instance_type_writer
instance_type_replica = var.db_instance_type_reader
iam_database_authentication_enabled = true
storage_encrypted = true
username = var.database_user
password = random_password.database_password.result
create_random_password = false
database_name = var.database_name
enable_http_endpoint = false
backup_retention_period = var.rds_backup_retention_period
snapshot_identifier = var.rds_initial_snapshot
#performance_insights_enabled = true
vpc_id = module.vpc.vpc_id
subnets = module.vpc.database_subnets
create_security_group = true
allowed_cidr_blocks = concat(module.vpc.private_subnets_cidr_blocks, var.extra_security_group_cidrs)
replica_count = 1
replica_scale_enabled = true
replica_scale_min = 1
replica_scale_max = 3
monitoring_interval = 60
iam_role_name = "${local.name}-rds-enhanced-monitoring"
iam_role_use_name_prefix = true
iam_role_description = "${local.name} RDS enhanced monitoring IAM role"
iam_role_path = "/autoscaling/"
iam_role_max_session_duration = 7200
apply_immediately = true
skip_final_snapshot = true
db_parameter_group_name = aws_db_parameter_group.example_mysql.id
db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.example_mysql.id
}
resource "aws_db_parameter_group" "example_mysql" {
name = "${local.name}-aurora-db-mysql-parameter-group"
family = "aurora-mysql8.0"
description = "${local.name}-aurora-db-mysql-parameter-group"
}
resource "aws_rds_cluster_parameter_group" "example_mysql" {
name = "${local.name}-aurora-mysql-cluster-parameter-group"
family = "aurora-mysql8.0"
description = "${local.name}-aurora-mysql-cluster-parameter-group"
}
resource "null_resource" "rds_guardian" {
triggers = {
rds_cluster = module.aurora_mysql.rds_cluster_endpoint
}
lifecycle {
prevent_destroy = true
}
}
@@ -1,76 +0,0 @@
## Terraform
### Bootstrapping remote state
First we need to bootstrap our terraform remote state management. This lives outside the main project to avoid "chicken before the egg"
issues. We are going to create the remote state S3 bucket and DynamoDB state locking table and then use hardcoded values
in parent folder `main.tf`.
1. `cd remote-state`
2. `terraform init`
3. `terraform apply`
### Creating the Fleet infrastructure
Create a new `tfvars` file for example:
```terraform
fleet_backend_cpu = 512
fleet_backend_mem = 4096 // 4GB needed for vuln processing
redis_instance = "cache.t3.micro"
fleet_min_capacity = 2
fleet_max_capacity = 5
```
If you have a Fleet license key you can include it in the `tfvars` file which will enable the paid features.
```terraform
fleet_license = "<your license key here"
```
**To deploy the infrastructure**:
1. `terraform init && terraform workspace new prod` (workspace is optional terraform defaults to the `default` workspace)
2. `terraform plan -var-file=<your_tfvars_file>`
3. `terraform apply -var-file=<your_tfvars_file>`
**To deploy cloudwatch alarms** (requires infrastruture to be deployed)
1. `cd monitoring`
2. `terraform init && terraform workspace new prod` (workspace is optional terraform defaults to the `default` workspace)
3. `terraform plan -var-file=<your_tfvars_file>`
4. `terraform apply -var-file=<your_tfvars_file>`
Check out [AWS Chatbot](https://docs.aws.amazon.com/chatbot/latest/adminguide/setting-up.html) for a quick and easy way to hook up Cloudwatch Alarms into a Slack channel.
**To deploy Percona PMM advanced MySQL monitoring**
1. See [Percona deployment](https://www.percona.com/doc/percona-monitoring-and-management/1.x/deploy/server/ami.html#running-pmm-server-using-aws-marketplace) scenario for details
2. Deploy infrastructure using `percona` directory
1. Create tfvars file
2. Add the required variables (vpc_id, subnets, etc.)
3. run `terraform apply -var-file=default.tfvars`
3. Add RDS Aurora MySQL by following this [guide](https://www.percona.com/doc/percona-monitoring-and-management/1.x/amazon-rds.html)
### Configuration
Typical settings to override in an existing environment:
`module.vpc.vpc_id` -- the VPC ID output from VPC module. If you are introducing fleet to an existing VPC, you could replace all instances with your VPC ID.
In this reference architecture we are placing ECS, RDS MySQL, and Redis (ElastiCache) in separate subnets, each associated to a route table, allowing communication between.
This is not required, as long as Fleet can resolve the MySQL and Redis hosts, that should be adequate.
#### HTTPS
The ALB is in the public subnet with an ENI to bridge into the private subnet. SSL is terminated at the ALB and `fleet serve` is launched with `FLEET_SERVER_TLS=false` as an
environment variable.
Replace `cert_arn` with the **certificate ARN** that applies to your environment. This is the **certificate ARN** used in the **ALB HTTPS Listener**.
### Migrating the DB
After applying terraform run the following to migrate the database(`<private_subnet_id>` and `<desired_security_group>` can be obtained from the terraform output after applying, any value will suffice):
```
aws ecs run-task --cluster fleet-backend --task-definition fleet-migrate:<latest_version> --launch-type FARGATE --network-configuration "awsvpcConfiguration={subnets=[<private_subnet_id>],securityGroups=[<desired_security_group>]}"
```
### Conecting a host
Use your Route53 entry as your `fleet-url` [following these details.](https://fleetdm.com/docs/using-fleet/adding-hosts)
@@ -1,63 +0,0 @@
variable "maintenance_window" {
default = ""
}
variable "engine_version" {
default = "6.x"
}
variable "number_cache_clusters" {
default = 3
}
variable "redis_instance" {
default = "cache.m5.large"
}
resource "aws_elasticache_replication_group" "default" {
availability_zones = var.redis_azs
engine = "redis"
parameter_group_name = "default.redis6.x"
subnet_group_name = module.vpc.elasticache_subnet_group_name
security_group_ids = [aws_security_group.redis.id]
replication_group_id = "fleetdm-redis"
number_cache_clusters = var.number_cache_clusters
node_type = var.redis_instance
engine_version = var.engine_version
port = "6379"
maintenance_window = var.maintenance_window
snapshot_retention_limit = 0
automatic_failover_enabled = true
at_rest_encryption_enabled = true
transit_encryption_enabled = true
apply_immediately = true
replication_group_description = "fleetdm-redis"
}
resource "aws_security_group" "redis" { #tfsec:ignore:aws-vpc-add-description-to-security-group
// description = "Security group for Redis" // cannot add description without recreation
name = local.security_group_name
vpc_id = module.vpc.vpc_id
}
locals {
security_group_name = "${var.prefix}-elasticache-redis"
}
resource "aws_security_group_rule" "ingress" {
description = "Redis from private VPC"
type = "ingress"
from_port = "6379"
to_port = "6379"
protocol = "tcp"
cidr_blocks = concat(module.vpc.private_subnets_cidr_blocks, var.extra_security_group_cidrs)
security_group_id = aws_security_group.redis.id
}
resource "aws_security_group_rule" "egress" {
description = "Redis VPC egress"
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
// Egress filtering is not currently provided by our Terraform templates.
cidr_blocks = ["0.0.0.0/0"] #tfsec:ignore:aws-vpc-no-public-egress-sgr:exp:2022-10-01
security_group_id = aws_security_group.redis.id
}
@@ -1,69 +0,0 @@
variable "prefix" {
default = "fleet"
}
variable "region" {
default = "us-east-2"
}
provider "aws" {
region = var.region
}
// Customer keys are not supported in our Fleet Terraforms at the moment. We will evaluate the
// possibility of providing this capability in the future.
// Bucket logging is not supported in our Fleet Terraforms at the moment. It can be enabled by the
// organizations deploying Fleet, and we will evaluate the possibility of providing this capability
// in the future.
resource "aws_s3_bucket" "remote_state" { #tfsec:ignore:aws-s3-encryption-customer-key:exp:2022-07-01 #tfsec:ignore:aws-s3-enable-bucket-logging:exp:2022-06-15
bucket = "${var.prefix}-terraform-remote-state"
acl = "private"
versioning {
enabled = true
}
lifecycle {
prevent_destroy = true
}
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
tags = {
Name = "S3 Remote Terraform State Store"
}
}
resource "aws_s3_bucket_public_access_block" "fleet_terraform_state" {
bucket = aws_s3_bucket.remote_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_dynamodb_table" "fleet_terraform_state_lock" {
name = "${var.prefix}-terraform-state-lock"
hash_key = "LockID"
billing_mode = "PAY_PER_REQUEST"
attribute {
name = "LockID"
type = "S"
}
tags = {
Name = "DynamoDB Terraform State Lock Table"
}
// Customer keys are not supported in our Fleet Terraforms at the moment. We will evaluate the
// possibility of providing this capability in the future.
server_side_encryption { #tfsec:ignore:aws-dynamodb-table-customer-key:exp:2022-07-01
enabled = true // enabled server side encryption
}
point_in_time_recovery {
enabled = true
}
}
@@ -1,35 +0,0 @@
// file carving destination with versioning disabled and lifecycle to ensure files get deleted and
// no version is ever kept
// Customer keys are not supported in our Fleet Terraforms at the moment. We will evaluate the
// possibility of providing this capability in the future.
// Bucket logging is not supported in our Fleet Terraforms at the moment. It can be enabled by the
// organizations deploying Fleet, and we will evaluate the possibility of providing this capability
// in the future.
resource "aws_s3_bucket" "osquery-carve" { #tfsec:ignore:aws-s3-enable-versioning #tfsec:ignore:aws-s3-encryption-customer-key:exp:2022-07-01 #tfsec:ignore:aws-s3-enable-bucket-logging:exp:2022-06-15
bucket = "osquery-carve-${terraform.workspace}"
acl = "private"
lifecycle_rule {
enabled = true
expiration {
days = 7
}
}
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
}
resource "aws_s3_bucket_public_access_block" "osquery-carve" {
bucket = aws_s3_bucket.osquery-carve.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
@@ -1,24 +0,0 @@
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
name = "fleet-vpc"
cidr = "10.10.0.0/16"
azs = var.vpc_azs
private_subnets = ["10.10.1.0/24", "10.10.2.0/24", "10.10.3.0/24"]
public_subnets = ["10.10.11.0/24", "10.10.12.0/24", "10.10.13.0/24"]
database_subnets = ["10.10.21.0/24", "10.10.22.0/24", "10.10.23.0/24"]
elasticache_subnets = ["10.10.31.0/24", "10.10.32.0/24", "10.10.33.0/24"]
create_database_subnet_group = true
create_database_subnet_route_table = true
create_elasticache_subnet_group = true
create_elasticache_subnet_route_table = true
enable_vpn_gateway = false
one_nat_gateway_per_az = false
single_nat_gateway = true
enable_nat_gateway = true
}
@@ -1,151 +0,0 @@
resource "aws_ecs_task_definition" "vuln-processing" {
family = "fleet-vuln-processing"
cpu = 2048
memory = 4096
execution_role_arn = aws_iam_role.main.arn
task_role_arn = aws_iam_role.main.arn
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
container_definitions = jsonencode([
{
name = "fleet-vuln-processing"
image = var.fleet_image
essential = true
command = ["fleet", "vuln_processing"]
networkMode = "awsvpc"
secrets = [
{
name = "FLEET_MYSQL_PASSWORD"
valueFrom = aws_secretsmanager_secret.database_password_secret.arn
}
]
environment = [
{
name = "FLEET_MYSQL_USERNAME"
value = module.aurora_mysql.rds_cluster_master_username
},
{
name = "FLEET_MYSQL_DATABASE"
value = module.aurora_mysql.rds_cluster_database_name
},
{
name = "FLEET_MYSQL_ADDRESS"
value = "${module.aurora_mysql.rds_cluster_endpoint}:3306"
},
{
name = "FLEET_VULNERABILITIES_DATABASES_PATH"
value = "/home/fleet/vuln_data"
},
{
name = "FLEET_LOGGING_DEBUG"
value = "true"
},
{
name = "FLEET_LICENSE_KEY"
value = var.fleet_license
}
],
logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-group = aws_cloudwatch_log_group.backend.name
awslogs-region = data.aws_region.current.name
awslogs-stream-prefix = "fleet-vuln-processing"
}
}
}
])
}
resource "aws_cloudwatch_event_rule" "vuln_processing" {
name_prefix = "${local.name}-vuln-processing"
schedule_expression = "rate(1 hour)"
is_enabled = false
}
resource "aws_cloudwatch_event_target" "vuln_processing" {
arn = aws_ecs_cluster.fleet.arn
rule = aws_cloudwatch_event_rule.vuln_processing.name
role_arn = aws_iam_role.run_cloudwatch.arn
ecs_target {
task_definition_arn = aws_ecs_task_definition.vuln-processing.arn
task_count = 1
launch_type = "FARGATE"
network_configuration {
assign_public_ip = false
subnets = module.vpc.private_subnets
security_groups = [aws_security_group.backend.id]
}
}
}
data "aws_iam_policy_document" "assume_events" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["events.amazonaws.com"]
}
}
}
data "aws_iam_policy_document" "cloudwatch_task" {
statement {
effect = "Allow"
actions = ["iam:PassRole"]
resources = ["*"]
}
statement {
effect = "Allow"
actions = ["ecs:RunTask"]
resources = ["*"]
condition {
test = "ArnEquals"
variable = "ecs:cluster"
values = [aws_ecs_cluster.fleet.arn]
}
}
}
data "aws_iam_policy_document" "assume_role_policy" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
resource "aws_iam_role" "run_cloudwatch" {
name = "${local.name}-cloudwatch-run"
assume_role_policy = data.aws_iam_policy_document.assume_events.json
}
resource "aws_iam_policy" "run_cloudwatch" {
name = "${local.name}-cloudwatch-run"
policy = data.aws_iam_policy_document.cloudwatch_task.json
}
resource "aws_iam_role_policy_attachment" "run_cloudwatch" {
role = aws_iam_role.run_cloudwatch.name
policy_arn = aws_iam_policy.run_cloudwatch.arn
}
resource "aws_iam_role_policy_attachment" "ecs_role_attachment" {
role = aws_iam_role.main.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole"
}
resource "aws_iam_role_policy_attachment" "ecs_task" {
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceEventsRole"
role = aws_iam_role.main.name
}