Terraform cleanup (#26379)
- Remove `dogfood/aws/` folder - Update links in docs and guides
This commit is contained in:
@@ -5,25 +5,25 @@
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user