HTTP Forward Proxy (implemented with http-explicit HTTP Profile) filtering iRule - v1.0

Problem this snippet solves:

In case a forward proxy was implemented using the explicit proxy mode (Virtual Server with http-explicit HTTP Profile) this iRule and the two data groups which are used in it can be used to filter traffic based on source IP and destination host and port pairs.

How to use this snippet:

This iRule has to be attached to the Virtual Server which is using the http-explicit HTTP Profile.

Code :

## The main filtering logic is based on the following iRule thread from devcentral: https://devcentral.f5.com/s/questions/how-to-filter-client-access-based-on-source-ip-and-uri-48727
##
## What it does: it allows F5 administrators to allow specific source IP to reach specific destination host:port pairs. It must be used with a VS which have http-explicit profile applied
##
## How it works:
##  It uses two data groups:
##    1) forward_proxy_ip_services_map which should contain entries with keys equals to allowed source IPs and value equals the white space delimited list of services which are hosted on the specific IP
##    2) forward_proxy_destination_services_map which should contain entries with key equals to the destination hostname:port and value equals the white space delimited list of services which allowed to reach these destinations
##
## Example for the datagroups and the access:
##   forward_proxy_ip_services_map (type ip):
##      "10.0.0.1" := "service1 service3"
##      "10.0.0.2" := "service2"
##      "10.0.0.3" := "service2 service4"
##
##   forward_proxy_destination_services_map (type string):
##      "api.example.com:443" := "service1 service2"
##      "anotherapi.blablabla.com:80" := "service4"
##      "thirdone.bug.com:80" := "service3 service4"
##
##  This would allow the following proxy requests:
##      10.0.0.1 could reach api.example.com:443 (due to service1), thirdone.bug.com:80 (due to service3)
##      10.0.0.2 could reach api.example.com:443 (due to service2)
##      10.0.0.3 could reach all three destinations (due to service2 and service4)
##
## Please note that due to how http-explicit profile works, we have to handle the requests at the HTTP_PROXY_REQUEST event


when RULE_INIT {
  set static::DEBUG 1
}

when CLIENT_ACCEPTED {
    set hosted_services [class match -value [IP::client_addr] equals forward_proxy_ip_services_map]    
}


when HTTP_PROXY_REQUEST {
  if { $static::DEBUG } { log local0.info "HTTP::method: [HTTP::method] HTTP::request: [HTTP::request] HTTP::uri: [HTTP::uri]"}

  if { $hosted_services ne "" }{
    # the client IP has services in the forward_proxy_ip_services_map data group

    set port [getfield [HTTP::host] ":" 2]
    if {$port ne ""}{
      # port was defined in the Host header
      set host_with_port [string tolower [getfield [HTTP::host] ":" 1]]:$port

    } elseif {[HTTP::method] eq "CONNECT"} {
      # port was not defined in the Host header and the request is https
      set host_with_port [string tolower [getfield [HTTP::host] ":" 1]]:443

    } else {
      # port was not defined in the Host header and the request is http
      set host_with_port [string tolower [getfield [HTTP::host] ":" 1]]:80
    }
    if { $static::DEBUG } { log local0.info "Host and port: $host_with_port"}


    if { [set allowed_services [class match -value $host_with_port ends_with forward_proxy_destination_services_map]] ne "" }{
    # we have the destination in the forward_proxy_destination_services_map so allowed_services contain the list of services which have access to this destination
    
      foreach service $allowed_services {
        if { [lsearch -inline $hosted_services $service] ne "" }{
          # the client hosts one of the services which allowed to go into this destination
          if { $static::DEBUG } { log local0.info "Client [IP::client_addr] hosts $service service which is allowed to reach $host_with_port through the proxy"}
          return
        }
      }
      if { $static::DEBUG } { log local0.info "Unauthorized access to forward proxy: client [IP::client_addr] does not have access to $host_with_port through the proxy, sending 403 access denied"}

    } else {
      # the destination is not listed in the forward_proxy_destination_services_map
      if { $static::DEBUG } { log local0.info "Unauthorized access to forward proxy: $host_with_port is not defined as a possible destination, sending 403 access denied"}
    }

  } else {
    # the client IP is not included in the forward_proxy_ip_services_map data group or has an empty string as list of services (value of the key)
    if { $static::DEBUG } { log local0.info "Unauthorized access to forward proxy: client [IP::client_addr] does not have access to proxy"}
  }

  HTTP::respond 403 content "Access denied..." "Content-Type" "text/html"  

}

Tested this on version:

11.6
Published Jan 24, 2017
Version 1.0

Was this article helpful?

No CommentsBe the first to comment