Files
fleet-terraform/addons/okta-conditional-access

Okta conditoinal access

Since mTLS is used for conditional access with Okta, we need to move the connection request to a separate subdomain that requires mTLS and uses the CA stored in Fleet and the certificate issued to the device seeking access.

This module helps automate that for a terraform deployment. By default the module assumes that okta is added as a subdomain to the Fleet primary domain (e.g. fleet.example.com leverages okta.fleet.example.com), but this can be customized.

A separate load balancer is created to handle the mTLS authorization. The module only handles basic certificate validation and passes the certificate serial on to Fleet via a header in order to handle the final authorization or revocation step.

Requirements

  • A separate subdomain for conditional access
  • A valid TLS certificate issued for the domain name
  • HTTPS listener rules applied to the main Fleet load balancer to handle redirects (generated as an output of this module)
  • The load balancer configured by this module applied as an extra load balancer to the fleet_config on the primary Fleet in terraform
  • The CA certificate in PEM format stored at "resources/conditional-ca.pem"

Obtaining the CA certificate

Run these commands while in your Fleet terraform directory/checkout.

mkdir -p resources
# Replace with your domain name. This is the public cert and doesn't need special protection.
curl 'https://fleet.example.com/api/fleet/conditional_access/scep?operation=GetCACert' --output cacert.tmp
openssl x509 -inform der -in cacert.tmp -out resources/conditional-ca.pem
rm cacert.tmp

Configuration Example

locals {
  domain_name = "fleet.example.com"
  okta_subdomain = "okta.${local.domain_name}"
}

module "okta_acm" {
  source  = "terraform-aws-modules/acm/aws"
  version = "4.3.1"

  domain_name = local.okta_subdomain
  # Assumes you are managing your domain in route53 inside of this config.
  zone_id = aws_route53_zone.main.id

  wait_for_validation = true
}

resource "aws_route53_record" "okta" {
  # If you change the route53_zone to a data source this also needs to become "data.aws_route53_zone.main.id"
  zone_id = aws_route53_zone.main.id
  name    = local.okta_subdomain
  type    = "A"

  alias {
    name                   = module.okta-conditional-access.alb.lb_dns_name
    zone_id                = module.okta-conditional-access.alb.lb_zone_id
    evaluate_target_health = true
  }
}

module "fleet" {
  source          = "github.com/fleetdm/fleet-terraform?depth=1&ref=tf-mod-root-v1.19.0"
  ...
  fleet_config = {
    ...
    # Required to allow the ALB to talk to the ECS containers.
    networking = {
      ingress_sources = {
        security_groups = [module.okta-conditional-access.alb.security_group_id]
      }
    }
    extra_load_balancers = [{
      target_group_arn = module.okta-conditional-access.alb.target_groups["tg-0"].arn
      container_name   = "fleet"
      container_port   = 8080
    }]
  }
  ...
  alb_config = {
    ...
    # If you have existing rules, use concat() to combine these with them.
    # Note: by default the rules use the highest priority indexes starting at 1,
    # but that can be configured inside the module.
    https_listener_rules = module.okta-conditional-access.redirect_rules
  }
}

module "okta-conditional-access" {
  source = "github.com/fleetdm/fleet-terraform/addons/okta-conditional-access?depth=1&ref=tf-mod-addon-okta-conditional-access-v0.5.0"
  customer_prefix = "fleet"
  vpc_id = module.fleet.vpc.vpc_id
  trust_store_s3_config = {
    bucket_prefix = "fleet-okta-trust-store"
  }
  alb_config = {
    name = "fleet-okta"
    # If using the ALB logging module:
    access_logs = {
      bucket  = module.logging_alb.log_s3_bucket_id
      prefix  = "fleet"
      enabled = true
    }
    subnets = module.fleet.vpc.public_subnets
    certificate_arn = module.okta_acm.acm_certificate_arn
    idle_timeout = 60
    trust_store = {
      ca_certificates_bundle_s3_key            = "ca.pem"
      ca_certificates_bundle_s3_object_version = null
      ca_certificates_bundle_file              = "${path.module}/resources/conditional-ca.pem"
      create_trust_store_revocation            = false
      trust_store_revocation_lists             = {}
    }
  }
}

Final comments

Once this is configured, when you attempt to leverage the conditional access, you will be prompted to use the certificate that is installed on your system if it already there during the authoriation redriect. If the certificate is not installed, the load balancer will immediately terminate the connection during the handshake and you will get a connection resset error message.

Requirements

No requirements.

Providers

Name Version
aws 6.28.0

Modules

Name Source Version
lb_trust_store_bucket terraform-aws-modules/s3-bucket/aws 5.9.1
okta_mtls_alb terraform-aws-modules/alb/aws 10.4.0

Resources

Name Type
aws_lb_trust_store.this resource
aws_lb_trust_store_revocation.this resource
aws_s3_object.trust_store_ca resource
aws_security_group.alb resource
aws_caller_identity.current data source
aws_iam_policy_document.alb_trust_store_restricted data source
aws_partition.current data source
aws_region.current data source

Inputs

Name Description Type Default Required
alb_config n/a
object({
name = optional(string, "fleet-okta")
subnets = list(string)
security_groups = optional(list(string), [])
access_logs = optional(map(string), {})
certificate_arn = string
allowed_cidrs = optional(list(string), ["0.0.0.0/0"])
allowed_ipv6_cidrs = optional(list(string), ["::/0"])
egress_cidrs = optional(list(string), ["0.0.0.0/0"])
egress_ipv6_cidrs = optional(list(string), ["::/0"])
extra_target_groups = optional(any, [])
https_listener_rules = optional(any, [])
https_overrides = optional(any, {})
xff_header_processing_mode = optional(string, null)
tls_policy = optional(string, "ELBSecurityPolicy-TLS13-1-2-2021-06")
idle_timeout = optional(number, 60)
internal = optional(bool, false)
enable_deletion_protection = optional(bool, false)
subdomain_prefix = optional(string, "okta")
trust_store = optional(any, {
ca_certificates_bundle_s3_key = "ca.pem"
ca_certificates_bundle_s3_object_version = null
ca_certificates_bundle_file = null
create_trust_store_revocation = false
trust_store_revocation_lists = {}
})
})
n/a yes
customer_prefix n/a string "fleet" no
redirect_priority The priority of the redirect https_listener_rule generated in the output number 1 no
trust_store_s3_config n/a
object({
bucket_prefix = optional(string, "fleet-okta-trust-store")
newer_noncurrent_versions = optional(number, 5)
noncurrent_version_expiration_days = optional(number, 30)
})
{
"bucket_prefix": "fleet-okta-trust-store",
"newer_noncurrent_versions": 5,
"noncurrent_version_expiration_days": 30
}
no
vpc_id n/a string n/a yes

Outputs

Name Description
alb n/a
alb_security_group n/a
lb_trust_store__bucket n/a
redirect_rules n/a