diff --git a/infrastructure/dogfood/terraform/aws-tf-module/.terraform.lock.hcl b/infrastructure/dogfood/terraform/aws-tf-module/.terraform.lock.hcl index b9f404027a..f63e59a9d1 100644 --- a/infrastructure/dogfood/terraform/aws-tf-module/.terraform.lock.hcl +++ b/infrastructure/dogfood/terraform/aws-tf-module/.terraform.lock.hcl @@ -3,8 +3,9 @@ provider "registry.terraform.io/hashicorp/aws" { version = "4.57.0" - constraints = ">= 2.67.0, >= 3.0.0, >= 3.73.0, >= 4.6.0, >= 4.9.0, >= 4.18.0, >= 4.27.0, >= 4.30.0, >= 4.40.0, 4.57.0" + constraints = ">= 2.67.0, >= 3.0.0, >= 3.73.0, >= 4.6.0, >= 4.8.0, >= 4.9.0, >= 4.18.0, >= 4.27.0, >= 4.30.0, >= 4.40.0, 4.57.0" hashes = [ + "h1:07cB50nnQkmdSHw5ehVEOny4czNYxAAdp00RUKWVa/w=", "h1:0bd5IKkEF1TGE4tgm0VuVMFQg2s6GOXJBU+/b/siYKw=", "zh:07d89ad94267b7d6285fd65fbd67f8680e111abf9bbcbcac2e30154262fbbe46", "zh:0eeee044e6fc285c20241d3de7f9b79450cab2df1452a9c18c0bed1090085a25", @@ -29,6 +30,7 @@ provider "registry.terraform.io/hashicorp/external" { constraints = ">= 1.0.0" hashes = [ "h1:bROCw6g5D/3fFnWeJ01L4IrdnJl1ILU8DGDgXCtYzaY=", + "h1:gznGscVJ0USxy4CdihpjRKPsKvyGr/zqPvBoFLJTQDc=", "zh:001e2886dc81fc98cf17cf34c0d53cb2dae1e869464792576e11b0f34ee92f54", "zh:2eeac58dd75b1abdf91945ac4284c9ccb2bfb17fa9bdb5f5d408148ff553b3ee", "zh:2fc39079ba61411a737df2908942e6970cb67ed2f4fb19090cd44ce2082903dd", @@ -49,6 +51,7 @@ provider "registry.terraform.io/hashicorp/local" { constraints = ">= 1.0.0" hashes = [ "h1:R97FTYETo88sT2VHfMgkPU3lzCsZLunPftjSI5vfKe8=", + "h1:ZUEYUmm2t4vxwzxy1BvN1wL6SDWrDxfH7pxtzX8c6d0=", "zh:53604cd29cb92538668fe09565c739358dc53ca56f9f11312b9d7de81e48fab9", "zh:66a46e9c508716a1c98efbf793092f03d50049fa4a83cd6b2251e9a06aca2acf", "zh:70a6f6a852dd83768d0778ce9817d81d4b3f073fab8fa570bff92dcb0824f732", @@ -65,9 +68,11 @@ provider "registry.terraform.io/hashicorp/local" { } provider "registry.terraform.io/hashicorp/null" { - version = "3.2.1" + version = "3.2.1" + constraints = ">= 2.0.0" hashes = [ "h1:FbGfc+muBsC17Ohy5g806iuI1hQc4SIexpYCrQHQd8w=", + "h1:ydA0/SNRVB1o95btfshvYsmxA+jZFRZcvKzZSB+4S1M=", "zh:58ed64389620cc7b82f01332e27723856422820cfd302e304b5f6c3436fb9840", "zh:62a5cc82c3b2ddef7ef3a6f2fedb7b9b3deff4ab7b414938b08e51d6e8be87cb", "zh:63cff4de03af983175a7e37e52d4bd89d990be256b16b5c7f919aff5ad485aa5", @@ -87,6 +92,7 @@ provider "registry.terraform.io/hashicorp/random" { version = "3.5.1" constraints = ">= 2.2.0" hashes = [ + "h1:IL9mSatmwov+e0+++YX2V6uel+dV6bn+fC/cnGDK3Ck=", "h1:VSnd9ZIPyfKHOObuQCaKfnjIHRtR7qTw19Rz8tJxm+k=", "zh:04e3fbd610cb52c1017d282531364b9c53ef72b6bc533acb2a90671957324a64", "zh:119197103301ebaf7efb91df8f0b6e0dd31e6ff943d231af35ee1831c599188d", @@ -106,6 +112,7 @@ provider "registry.terraform.io/hashicorp/random" { provider "registry.terraform.io/hashicorp/tls" { version = "4.0.4" hashes = [ + "h1:GZcFizg5ZT2VrpwvxGBHQ/hO9r6g0vYdQqx3bFD3anY=", "h1:pe9vq86dZZKCm+8k1RhzARwENslF3SXb9ErHbQfgjXU=", "zh:23671ed83e1fcf79745534841e10291bbf34046b27d6e68a5d0aab77206f4a55", "zh:45292421211ffd9e8e3eb3655677700e3c5047f71d8f7650d2ce30242335f848", diff --git a/infrastructure/dogfood/terraform/aws-tf-module/main.tf b/infrastructure/dogfood/terraform/aws-tf-module/main.tf index 53e97c9e8a..7b5dbac087 100644 --- a/infrastructure/dogfood/terraform/aws-tf-module/main.tf +++ b/infrastructure/dogfood/terraform/aws-tf-module/main.tf @@ -101,17 +101,79 @@ module "main" { } } extra_iam_policies = concat(module.firehose-logging.fleet_extra_iam_policies, module.osquery-carve.fleet_extra_iam_policies, module.ses.fleet_extra_iam_policies) - extra_execution_iam_policies = concat(module.mdm.extra_execution_iam_policies, [aws_iam_policy.sentry.arn]) + extra_execution_iam_policies = concat(module.mdm.extra_execution_iam_policies, [aws_iam_policy.sentry.arn]) #, module.saml_auth_proxy.fleet_extra_execution_policies) extra_environment_variables = merge(module.mdm.extra_environment_variables, module.firehose-logging.fleet_extra_environment_variables, module.osquery-carve.fleet_extra_environment_variables, module.ses.fleet_extra_environment_variables, local.extra_environment_variables) extra_secrets = merge(module.mdm.extra_secrets, local.sentry_secrets) + # extra_load_balancers = [{ + # target_group_arn = module.saml_auth_proxy.lb_target_group_arn + # container_name = "fleet" + # container_port = 8080 + # }] } - alb_config = { - name = local.customer - access_logs = { - bucket = module.logging_alb.log_s3_bucket_id - prefix = local.customer - enabled = true - } + alb_config = { + name = local.customer + access_logs = { + bucket = module.logging_alb.log_s3_bucket_id + prefix = local.customer + enabled = true + } +# extra_target_groups = [ +# { +# name = module.saml_auth_proxy.name +# backend_protocol = "HTTP" +# backend_port = 80 +# target_type = "ip" +# health_check = { +# path = "/_health" +# matcher = "200" +# timeout = 10 +# interval = 15 +# healthy_threshold = 5 +# unhealthy_threshold = 5 +# } +# } +# ] +# https_listener_rules = [{ +# https_listener_index = 0 +# priority = 9000 +# actions = [{ +# type = "forward" +# target_group_index = 1 +# }] +# conditions = [{ +# path_patterns = ["/device/*", "/api/*/fleet/device/*", "/saml/*"] +# }] +# }, { +# https_listener_index = 0 +# priority = 1 +# actions = [{ +# type = "forward" +# target_group_index = 0 +# }] +# conditions = [{ +# path_patterns = ["/api/*/fleet/device/*/migrate_mdm", "/api/*/fleet/device/*/rotate_encryption_key"] +# }] +# }, { +# https_listener_index = 0 +# priority = 2 +# actions = [{ +# type = "forward" +# target_group_index = 0 +# }] +# conditions = [{ +# path_patterns = ["/api/*/fleet/device/*/debug/errors", "/api/*/fleet/device/*/desktop"] +# }] +# }, { +# https_listener_index = 0 +# priority = 3 +# actions = [{ +# type = "forward" +# target_group_index = 0 +# }] +# conditions = [{ +# path_patterns = ["/api/*/fleet/device/*/refetch", "/api/*/fleet/device/*/transparency"] +# }] +# }] } } @@ -297,3 +359,20 @@ module "waf" { name = local.customer lb_arn = module.main.byo-vpc.byo-db.alb.lb_arn } + +# module "saml_auth_proxy" { +# # source = "github.com/fleetdm/fleet//terraform/addons/saml-auth-proxy?ref=main" +# # public_alb_security_group_id = module.main.byo-vpc.byo-db.alb.security_group_id +# idp_metadata_url = "https://dev-99185346.okta.com/app/exkbcrjeqmahXWvW45d7/sso/saml/metadata" +# customer_prefix = local.customer +# ecs_cluster = module.main.byo-vpc.byo-db.byo-ecs.service.cluster +# ecs_execution_iam_role_arn = module.main.byo-vpc.byo-db.byo-ecs.execution_iam_role_arn +# ecs_iam_role_arn = module.main.byo-vpc.byo-db.byo-ecs.iam_role_arn +# security_groups = module.main.byo-vpc.byo-db.byo-ecs.service.network_configuration[0].security_groups +# base_url = "https://dogfood.fleetdm.com/" +# subnets = module.main.byo-vpc.byo-db.byo-ecs.service.network_configuration[0].subnets +# vpc_id = module.main.vpc.vpc_id +# logging_options = null # Figure it out later +# alb_target_group_arn = module.main.byo-vpc.byo-db.alb.target_group_arns[1] +# cookie_max_age = "15m" +# } diff --git a/terraform/addons/saml-auth-proxy/files/saml-auth-proxy.sh b/terraform/addons/saml-auth-proxy/files/saml-auth-proxy.sh new file mode 100644 index 0000000000..e3c5db8ecb --- /dev/null +++ b/terraform/addons/saml-auth-proxy/files/saml-auth-proxy.sh @@ -0,0 +1,5 @@ +mkdir -p $(dirname ${SAML_PROXY_SP_CERT_PATH:?}) +mkdir -p $(dirname ${SAML_PROXY_SP_KEY_PATH:?}) +echo "${SAML_PROXY_SP_CERT_BYTES:?}" > "${SAML_PROXY_SP_CERT_PATH:?}" +echo "${SAML_PROXY_SP_KEY_BYTES:?}" > "${SAML_PROXY_SP_KEY_PATH:?}" +/usr/bin/saml-auth-proxy diff --git a/terraform/addons/saml-auth-proxy/main.tf b/terraform/addons/saml-auth-proxy/main.tf new file mode 100644 index 0000000000..2148e41c4f --- /dev/null +++ b/terraform/addons/saml-auth-proxy/main.tf @@ -0,0 +1,229 @@ +data "aws_region" "current" {} + +resource "aws_cloudwatch_log_group" "saml_auth_proxy" { + name = "${var.customer_prefix}-saml-auth-proxy" +} + +data "aws_iam_policy_document" "saml_auth_proxy" { + // allow saml_auth_proxy to obtain certs from secrets manager + statement { + effect = "Allow" + actions = ["secretsmanager:GetSecretValue"] + resources = [aws_secretsmanager_secret.saml_auth_proxy_cert.arn] + } +} + +resource "aws_iam_policy" "saml_auth_proxy" { + name = "${var.customer_prefix}-saml-auth-proxy" + description = "IAM Policy to provide saml_auth_proxy access to secrets" + policy = data.aws_iam_policy_document.saml_auth_proxy.json +} + +resource "aws_secretsmanager_secret" "saml_auth_proxy_cert" { + name_prefix = "${var.customer_prefix}-saml-auth-proxy-cert" +} + +resource "aws_security_group" "saml_auth_proxy_alb" { + #checkov:skip=CKV2_AWS_5:False positive + vpc_id = var.vpc_id + description = "Fleet ALB Security Group" + + ingress { + description = "Internal HTTP back to Fleet" + from_port = 8080 + to_port = 8080 + protocol = "tcp" + security_groups = [aws_security_group.saml_auth_proxy_service.id] + } + + egress { + description = "Egress to all" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = [] + } +} + +resource "aws_security_group" "saml_auth_proxy_service" { + #checkov:skip=CKV2_AWS_5:False positive + vpc_id = var.vpc_id + description = "Fleet ALB Security Group" + + ingress { + description = "Internal HTTP back to Fleet" + from_port = 8080 + to_port = 8080 + protocol = "tcp" + cidr_blocks = ["10.0.0.0/8"] + } + + egress { + description = "Egress to all" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = [] + } +} + + +module "saml_auth_proxy_alb" { + source = "terraform-aws-modules/alb/aws" + version = "8.2.1" + + name = "${var.customer_prefix}-saml-auth-proxy" + + load_balancer_type = "application" + + vpc_id = var.vpc_id + subnets = var.subnets + security_groups = [aws_security_group.saml_auth_proxy_alb.id] + # FIXME: Get this working eventually. + # access_logs = var.alb_config.access_logs + + internal = true + target_groups = [ + { + name = "${var.customer_prefix}-saml-to-fleet" + backend_protocol = "HTTP" + backend_port = 8080 + target_type = "ip" + health_check = { + path = "/healthz" + matcher = "200" + timeout = 10 + interval = 15 + healthy_threshold = 5 + unhealthy_threshold = 5 + } + } + ] + + http_tcp_listeners = [ + { + port = 8080 + protocol = "HTTP" + target_group_index = 0 + } + ] +} + +resource "aws_ecs_task_definition" "saml_auth_proxy" { + family = "${var.customer_prefix}-saml-auth-proxy" + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + execution_role_arn = var.ecs_execution_iam_role_arn + task_role_arn = var.ecs_iam_role_arn + cpu = 256 + memory = 1024 + container_definitions = jsonencode( + [ + { + name = "${var.customer_prefix}-saml-auth-proxy" + image = var.saml_auth_proxy_image + cpu = 256 + memory = 512 + mountPoints = [] + volumesFrom = [] + essential = true + ulimits = [ + { + softLimit = 9999, + hardLimit = 9999, + name = "nofile" + } + ] + portMappings = [ + { + # This port is the same that the contained application also uses + containerPort = 8080 + protocol = "tcp" + } + ] + networkMode = "awsvpc" + logConfiguration = { + logDriver = "awslogs" + options = var.logging_options != null ? var.logging_options : { + awslogs-group = "${var.customer_prefix}-saml-auth-proxy" + awslogs-region = data.aws_region.current.name + awslogs-stream-prefix = "saml-auth-proxy" + } + } + workingDirectory = "/go", + secrets = [ + { + name = "SAML_PROXY_SP_CERT_BYTES" + valueFrom = "${aws_secretsmanager_secret.saml_auth_proxy_cert.arn}:cert::" + }, + { + name = "SAML_PROXY_SP_KEY_BYTES" + valueFrom = "${aws_secretsmanager_secret.saml_auth_proxy_cert.arn}:key::" + }, + ] + environment = [ + { + name = "SAML_PROXY_SP_CERT_PATH" + value = "/tmp/saml-auth-proxy/cert.pem" + }, + { + name = "SAML_PROXY_SP_KEY_PATH" + value = "/tmp/saml-auth-proxy/key.pem" + }, + { + name = "SAML_PROXY_BACKEND_URL" + value = "http://${module.saml_auth_proxy_alb.lb_dns_name}:8080/" + }, + { + name = "SAML_PROXY_IDP_METADATA_URL" + value = var.idp_metadata_url + }, + { + name = "SAML_PROXY_BASE_URL" + value = var.base_url + }, + { + name = "SAML_PROXY_COOKIE_MAX_AGE" + value = var.cookie_max_age + }, + { + name = "SAML_PROXY_COOKIE_NAME" + value = "saml_auth_proxy_token" + }, + { + name = "SAML_PROXY_ALLOW_IDP_INITIATED" + value = "true" + }, + ] + entryPoint = ["/bin/sh"], + command = ["-c", file("${path.module}/files/saml-auth-proxy.sh")] + } + ] + ) + lifecycle { + create_before_destroy = true + } +} + +resource "aws_ecs_service" "saml_auth_proxy" { + name = "${var.customer_prefix}_saml_auth_proxy" + launch_type = "FARGATE" + cluster = var.ecs_cluster + task_definition = aws_ecs_task_definition.saml_auth_proxy.arn + desired_count = var.proxy_containers + deployment_minimum_healthy_percent = 100 + deployment_maximum_percent = 200 + + network_configuration { + subnets = var.subnets + security_groups = [aws_security_group.saml_auth_proxy_service.id] + } + + load_balancer { + target_group_arn = var.alb_target_group_arn + container_name = "${var.customer_prefix}-saml-auth-proxy" + container_port = 8080 + } +} diff --git a/terraform/addons/saml-auth-proxy/outputs.tf b/terraform/addons/saml-auth-proxy/outputs.tf new file mode 100644 index 0000000000..74fae54e09 --- /dev/null +++ b/terraform/addons/saml-auth-proxy/outputs.tf @@ -0,0 +1,13 @@ +output "fleet_extra_execution_policies" { + value = [ + aws_iam_policy.saml_auth_proxy.arn + ] +} + +output "name" { + value = "${var.customer_prefix}-saml-auth-proxy" +} + +output "lb_target_group_arn" { + value = module.saml_auth_proxy_alb.target_group_arns[0] +} diff --git a/terraform/addons/saml-auth-proxy/variables.tf b/terraform/addons/saml-auth-proxy/variables.tf new file mode 100644 index 0000000000..66aa6677d7 --- /dev/null +++ b/terraform/addons/saml-auth-proxy/variables.tf @@ -0,0 +1,71 @@ +variable "cookie_max_age" { + type = string + default = "1h" +} + +variable "alb_target_group_arn" { + type = string +} + +# variable "public_alb_security_group_id" { +# type = string +# } + +variable "idp_metadata_url" { + type = string +} + +variable "customer_prefix" { + type = string + description = "customer prefix to use to namespace all resources" + default = "fleet" +} + +variable "ecs_cluster" { + type = string +} + +variable "ecs_execution_iam_role_arn" { + type = string +} + +variable "ecs_iam_role_arn" { + type = string +} + +variable "proxy_containers" { + type = number + default = 1 +} + +variable "logging_options" { + type = object({ + awslogs-group = string + awslogs-region = string + awslogs-stream-prefix = string + }) +} + +variable "saml_auth_proxy_image" { + type = string + default = "itzg/saml-auth-proxy:1.12.0@sha256:ddff17caa00c1aad64d6c7b2e1d5eb93d97321c34d8ad12a25cfd8ce203db723" +} + +variable "security_groups" { + type = list(string) + nullable = false +} + +variable "base_url" { + type = string +} + +variable "subnets" { + type = list(string) + nullable = false +} + +variable "vpc_id" { + type = string + nullable = false +} diff --git a/terraform/byo-vpc/byo-db/byo-ecs/main.tf b/terraform/byo-vpc/byo-db/byo-ecs/main.tf index 6ed388d6d7..08723b0e2a 100644 --- a/terraform/byo-vpc/byo-db/byo-ecs/main.tf +++ b/terraform/byo-vpc/byo-db/byo-ecs/main.tf @@ -7,6 +7,13 @@ locals { name = k valueFrom = v }] + load_balancers = concat([ + { + target_group_arn = var.fleet_config.loadbalancer.arn + container_name = "fleet" + container_port = 8080 + } + ], var.fleet_config.extra_load_balancers) } data "aws_region" "current" {} @@ -21,10 +28,13 @@ resource "aws_ecs_service" "fleet" { deployment_maximum_percent = 200 health_check_grace_period_seconds = 30 - load_balancer { - target_group_arn = var.fleet_config.loadbalancer.arn - container_name = "fleet" - container_port = 8080 + dynamic "load_balancer" { + for_each = local.load_balancers + content { + target_group_arn = load_balancer.value.target_group_arn + container_name = load_balancer.value.container_name + container_port = load_balancer.value.container_port + } } lifecycle { diff --git a/terraform/byo-vpc/byo-db/byo-ecs/variables.tf b/terraform/byo-vpc/byo-db/byo-ecs/variables.tf index c4c08bc485..8e0e7049a2 100644 --- a/terraform/byo-vpc/byo-db/byo-ecs/variables.tf +++ b/terraform/byo-vpc/byo-db/byo-ecs/variables.tf @@ -57,6 +57,7 @@ variable "fleet_config" { loadbalancer = object({ arn = string }) + extra_load_balancers = optional(list(any), []) networking = object({ subnets = list(string) security_groups = optional(list(string), null) @@ -131,6 +132,7 @@ variable "fleet_config" { loadbalancer = { arn = null } + extra_load_balacners = [] networking = { subnets = null security_groups = null diff --git a/terraform/byo-vpc/byo-db/main.tf b/terraform/byo-vpc/byo-db/main.tf index 12c488f9b5..f5e1dc3452 100644 --- a/terraform/byo-vpc/byo-db/main.tf +++ b/terraform/byo-vpc/byo-db/main.tf @@ -37,7 +37,7 @@ module "alb" { security_groups = concat(var.alb_config.security_groups, [aws_security_group.alb.id]) access_logs = var.alb_config.access_logs - target_groups = [ + target_groups = concat([ { name = var.alb_config.name backend_protocol = "HTTP" @@ -52,7 +52,7 @@ module "alb" { unhealthy_threshold = 5 } } - ] + ], var.alb_config.extra_target_groups) # Require TLS 1.2 as earlier versions are insecure listener_ssl_policy_default = "ELBSecurityPolicy-TLS-1-2-2017-01" diff --git a/terraform/byo-vpc/byo-db/variables.tf b/terraform/byo-vpc/byo-db/variables.tf index bc27ae7d85..3753cfe1ad 100644 --- a/terraform/byo-vpc/byo-db/variables.tf +++ b/terraform/byo-vpc/byo-db/variables.tf @@ -129,6 +129,7 @@ variable "fleet_config" { }), { arn = null }) + extra_load_balancers = optional(list(any), []) networking = optional(object({ subnets = list(string) security_groups = optional(list(string), null) @@ -206,6 +207,7 @@ variable "fleet_config" { loadbalancer = { arn = null } + extra_load_balancers = [] networking = { subnets = null security_groups = null @@ -252,6 +254,7 @@ variable "alb_config" { access_logs = optional(map(string), {}) certificate_arn = string allowed_cidrs = optional(list(string), ["0.0.0.0/0"]) + extra_target_groups = optional(any, []) https_listener_rules = optional(any, []) }) } diff --git a/terraform/byo-vpc/variables.tf b/terraform/byo-vpc/variables.tf index 3ba456b293..f6839f192b 100644 --- a/terraform/byo-vpc/variables.tf +++ b/terraform/byo-vpc/variables.tf @@ -218,6 +218,7 @@ variable "fleet_config" { }), { arn = null }) + extra_load_balancers = optional(list(any), []) networking = optional(object({ subnets = list(string) security_groups = optional(list(string), null) @@ -295,6 +296,7 @@ variable "fleet_config" { loadbalancer = { arn = null } + extra_load_balancers = [] networking = { subnets = null security_groups = null @@ -341,6 +343,7 @@ variable "alb_config" { access_logs = optional(map(string), {}) certificate_arn = string allowed_cidrs = optional(list(string), ["0.0.0.0/0"]) + extra_target_groups = optional(any, []) https_listener_rules = optional(any, []) }) } diff --git a/terraform/variables.tf b/terraform/variables.tf index e102c4b339..b4afdb3d21 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -270,6 +270,7 @@ variable "fleet_config" { }), { arn = null }) + extra_load_balancers = optional(list(any), []) networking = optional(object({ subnets = list(string) security_groups = optional(list(string), null) @@ -347,6 +348,7 @@ variable "fleet_config" { loadbalancer = { arn = null } + extra_load_balancers = [] networking = { subnets = null security_groups = null @@ -391,6 +393,7 @@ variable "alb_config" { security_groups = optional(list(string), []) access_logs = optional(map(string), {}) allowed_cidrs = optional(list(string), ["0.0.0.0/0"]) + extra_target_groups = optional(any, []) https_listener_rules = optional(any, []) }) default = {}