waf_cloudfront_AWS

4 juillet 2023

Sécuriser une infrastructure cloud passe par de nombreux systèmes, comme respecter le principe du moindre privilège à travers IAM et empêcher des attaques sur notre SI. La première chose à faire est toujours de contrôler les portes d’entrée sur notre système et son chemin d’accès qui passe par un WAF.

Architecture

L’architecture cible de ce tutoriel ressemble à cela. Son but est d’avoir une liste d’IP sécurisé et de confiance. C’est-à-dire whitelist seulement les IPs que l’on connaît, par exemple provenant d’un VPN utilisé par nos utilisateurs, ou d’un wifi spécifique. Rentrons plus dans le détail de ce que constitue notre WAF.

Le WAF (AWS web Application Firewall) est un système de sécurité qui contrôle le trafic entrant d’une infrastructure. De manière générale, le WAF se met en amont des services qui sont directement en contact avec l’utilisateur final afin de contrôler leurs entrées. Par exemple un cloudFront.

Le WAF peut incorporer des règles pouvant notamment imposer des restrictions à des adresses IP, des en-têtes HTTP et des chaînes URI spécifiques. Les règles d'AWS WAF permettent d’empêcher des attaques Web courantes, telles que l'injection de code SQL et le Cross-Site Scripting, qui exploitent les vulnérabilités d'une application.

diagram_schema

  • Le DNS (CloudFare, amazon route 53) sert à transférer des demandes de nom en adresse IP. Dans notre infrastructure, le DNS rajoute également des headers permettant la détection de bot, de comportements suspects... C’est ce que l’on appelle un filtre DNS.
  • Le WAF applique les règles de filtrage. Par exemple, si le DNS envoie une requête contenant le header “requestanomaly”, la réponse doit être un 403.
  • Le CloudFront est l’interface Front chez AWS, qui va afficher notre page web et transféré les requêtes vers notre logique métier.
  • Le S3 et les lambda constituent notre back en serverless

Dans cet article, nous allons donc expliquer comment connecter un système de whitelist facilement à un WAF. En bonus, nous allons montrer la simplicité d’ajout de règles pour augmenter la sécurité.

Tout cela sera réalisé en terraform, c’est-à-dire un langage qui permet de décrire à quoi ressemble notre infrastructure. Pour cet exemple, le cloud provider utilisé est AWS, mais la structure avec des composants équivalents serait similaire sur d’autres cloud provider.

Résumé des étapes

TL:DR si tu es un petit malin qui n’a pas besoin d’une description détaillée des étapes, tu peux directement check sur ce github.

  1. Création d’un WAF totalement bloquant
  2. Ajout d’une règle pour autoriser certaine IP
  3. (Optionnel) ajout d’une règle pour bloquer les mauvaises requêtes
  4. Connecter le WAF a un cloudFront

Prérequis

  • Un compte AWS
  • terraform

Étape 1 : WAF bloquant

 

La première étape est de créer un système totalement bloquant. Toutes les requêtes, qu’importe leur origine, sont donc annulées et n’atteindront pas notre CloudFront.

Prenons un instant pour regarder les paramètres :

resource "aws_wafv2_web_acl" "production_waf_acl" {
  provider = aws.us-east-1
  name        = "production-waf-acl"
  description = "production-waf-acl"
  scope       = "CLOUDFRONT"

#scope : le scope du WAF est CLOUDFRONT car il est en dehors du système de régions #provider : Il est nécessaire de setup aws-us-east-1 . En effet, cloudFront n’est pas régional mais est situé au plus proche de l’utilisateur (comme les CDN) et doit être connecté au cloudFront.

  # By default block actions
  default_action {
    block {}
  }
} #end of block aws_wafv2_web_acl

On a donc créé ici un WAF en terraform qui, peu importe la requête qu’il reçoit en entrée, refuse la requête et renvoie une erreur 403 à l’utilisateur qui demande à accéder au service derrière le WAF.

Étape 2 : ajout d’une règle pour autoriser certaine IP

 rule {
    name     = "ip-whitelisting"
    priority = 1

    action {
      allow {}
    }

    statement {
      ip_set_reference_statement {
        arn = aws_wafv2_ip_set.production.arn
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = false
      metric_name                = "false"
      sampled_requests_enabled   = true
    }

  }
resource "aws_wafv2_ip_set" "production" {
  provider = aws.us-east-1
  name               = "production-ip-whitelisting-rules"
  scope              = "CLOUDFRONT"
  ip_address_version = "IPV4"
  addresses =  
    "127.0. 0.1/32", #your ip adress 
  ])
}

Le block rule s’ajoute dans le WAF et permet de rajouter des règles. Le fait d’approuver ou de refuser une requête se fait de manière séquentielle avec le paramètre “priority”. Dès qu’une requête est concernée par une règle, elle est validée ou refusée selon si la règle contient un allow ou un block. Les règles commencent par celle de priority 1 et continue par ordre croissant.

Si aucune règle ne concerne la requête, alors le default est pris en compte. Dans notre cas, la règle est donc bloquée.

Ainsi les adresses non présentes dans notre IP-set seront automatiquement refusées.

Étape 3 : ajout d’une règle pour bloquer les mauvaises requêtes

rule {
    name     = "bot-detection"
    priority = 2

    action {
      block {}
    }


    statement {
      regex_pattern_set_reference_statement {
        arn = aws_wafv2_regex_pattern_set.bot_regex_pattern.arn

        field_to_match {
          single_header {
            name = "akamai-bot"
          }
        }
        text_transformation {
          priority = 2
          type     = "LOWERCASE"
        }
      }
    }
    visibility_config {
      cloudwatch_metrics_enabled = false
      metric_name                = "false"
      sampled_requests_enabled   = true
    }

  }

On rajoute également un regex pattern set qui sert de condition à notre règle, si une string est contenu dans le pattern, alors la requête sera rejetée, et ce même si notre IP est whitelistée.

resource "aws_wafv2_regex_pattern_set" "bot_regex_pattern" {
  provider = aws.us-east-1

  name  = "pattern"
  scope = "CLOUDFRONT"

  regular_expression {
    regex_string = "requestanomaly|scraperreputation|wrong-format-bmp-endpoint|100-continue|wrong-capitalization|backslash|urlencoded|suspicious-hash|evasion-backslash|evasion-urlencoded"
  }
}

Le CDN s’occupe de rajouter ces headers. L’utilisateur ne peut donc pas modifier à la volée les headers de ses propres requêtes afin d’atteindre notre cloudfront.

Il est également possible d’ajouter les rules créées par AWS afin d’ajouter une étape de sécurité supplémentaire. Cette partie doit être à adapter avec votre propre logique métier et ce qui se passe concrètement derrière (ec2 avec du linux, des databases Mongo, du serverless).

rule {
    name     = "AWS-AWSManagedRulesKnownBadInputsRuleSet"
    priority = 1
    override_action {
      none {}
    }
    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesKnownBadInputsRuleSet"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = false
      metric_name                = "false"
      sampled_requests_enabled   = true
    }
  }

Étape 4 : connexion du WAF au front

resource "aws_cloudfront_distribution" "tf" {
  origin {
    domain_name = aws_s3_bucket.bucketExample.bucket_regional_domain_name
    origin_id   = "myS3Origin"

    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.origin_access_identity.cloudfront_access_identity_path
    }
  }

  web_acl_id="${aws_wafv2_web_acl.production_waf_acl.arn}"
  enabled = true
  default_root_object = "index.html"

  default_cache_behavior {
    viewer_protocol_policy = "redirect-to-https"
    compress = true
    allowed_methods = ["GET", "HEAD"]
    cached_methods = ["GET", "HEAD"]
    target_origin_id = "myS3Origin"

    forwarded_values {
      query_string = false
      cookies {
        forward = "none"
      }
    }
  }

   logging_config {
     include_cookies = false
     bucket          = "mylogs.s3.amazonaws.com"
     prefix          = "logFolder"
   }


  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  viewer_certificate {
    cloudfront_default_certificate = true
  }
}

C’est un cloudFront basique contenant simplement l’id du web_acl et d’un s3. Le default cache behavior permet de transférer les requêtes http vers du https.

Les autres paramètres sont obligatoires et définis de base dans le starter terraform du cloudfront.

Et un s3 basique pour exemple :

resource "aws_s3_bucket" "bucketExample" {
  bucket = "my-tf-bucket-122113222"
  force_destroy = true 
  tags = {
    Name        = "My bucket"
    Environment = "Dev"
  }
}

Conclusion

Nous venons donc de déployer un CloudFront et son WAF qui monitore les requêtes en entrée. Avec cette architecture votre WAF protège de la plupart des problèmes de sécurité connus contenus dans le top 10 des problèmes de sécurité. En plus, seuls des gens un minimum de confiance ont dorénavant accès à votre cloudFront.

Votre CloudFront est prête à être utilisée alors allez la tester !