Application gateways, an example in Clojure

Last Nov, 30th I got together with the Barcelona Software Craftmanship Meetup and enjoyed a brilliant presentation by Viktor Farcic about how bleeding edge, scalable architectures work these days. There was a lot of Docker, Continous Delivery, Configuration Management Systems, Service Discovery and so on and so forth. Among all these pieces of technology, at some point in the session, Application Gateways were mentioned. Some doubts arose and I was kindly asked if I could add some information about them to the meetup page. I started to look through my bookmarks but I didn’t find something as illustrative as I would have wanted to so I thought that I could just create a small one and share my thoughts on it.

Application Gateways

An Application Gateway, Layer 7 firewall, Application-Level Gateway or, in its more specific form, Web Application Firewall is a element sitting between a service and its clients in such a way that:

  • It represents the elements providing the service (the servers).
  • It is able to fully understand the protocols in place between them and the clients.

While a classical firewall focuses on the lower half layers of the stack (network, transport), Application Gateways are able to detect and filter high-level operations. An FTP Gateway protecting an FTP server, for instance, could be able to prevent access to specific directories or file-types despite the server has no capability to block these requests and would happily serve them, if they reach it. These days of web-platforms, off-the-shelf Web Application Firewalls are able to protect our environments from XSS, SQL injections and other common attachs by fully understanding the HTTP conversations in place. Custom ones, very frequently built as processes on top of general purpose operating systems by the same companies providing a service that needs to be safeguarded, are able to achieve even higher degrees of integration. Real-life scenarios are:

  • Request filtering: blocking unauthorized requests based on complex criteria such as time of day, location, target object, etc.
  • Request transformation: mapping between REST operations, versions, etc.
  • Request optimization: routing, caching, etc.
  • API Management: rate limiting, charging, …

An example in Clojure

At the most basic level, an Application Gateway for a web environment is implemented as a server that:

  1. Receives HTTP requests.
  2. Decodes them including any relevant payload such as JSON or XML-encoded bodies.
  3. Decides what to do next based on this information. In some cases, the original request will be forwarded to the real server to be processed.
  4. Returns the response to the client.

Coding a small version of this process in Clojure is really simple. Web application libraries like ring or http-kit give us almost everything we need to create a toy to play with in less than an hour. You can see an example here, where a sample web-application and a gateway to protect it have been included.

The code

The README.md file should be pretty explanatory on how to the run the example. The more important points in the code are:

Web-app definition

Using compojure and ring, we create a sample webapp we intend to protect. In this case, it will return a JSON object with an uppercase version of a word received as a part of the request URI: http://localhost:3000/uppercase/word.

(defroutes webapp
  (GET "/uppercase/:text" [text] 
       (do
         (debug "Received request for" text)
         (-> (r/response (json/write-str {:status :ok :text (s/upper-case text)}))
             (r/content-type "application/json"))))
  (route/not-found
    (do 
      (debug "Unknown route")
      (-> (r/response (json/write-str {:status :err :reason :op-not-found}))
          (r/content-type "application/json")))))

Application Gateway definition

The gateway is also a web application and, as such, we have coded it using ring. In the snippet below, we can see how the routes are exactly the same as in the original web app even though that does not need to be the case:

;; The Application Gateway
(defroutes appgw
  (GET "/uppercase/:text" [text] 
       (process-request text))

  (route/not-found 
    (-> (r/response "NOT FOUND"))))

Request processing:

The request is received by the Appliation Gateway which, if the word is not blocked (request filtering) and the word is not cached (optimization), forwards it to the original application server. Returned result is passed back to the client:

(defn process-request
  "The heart of the Application Gateway. Checks if word is not _forbidden_ or
  _cached_ and if is not the case, it forwards the request to the orignal 
  server."
  [word]
  (debug "Received request for word" word)
  (cond
    (forbidden? word) (forbidden-response word)
    (cached? word) (cached-response word)
    :else (forward-request-and-cache! word (env :forward-port))))

Conclusion

An Application Gateway is just a device that represents an application server in front of its clients and that can block, transform or optimize the requests they make. We can find them in a number or formats, ranging from completely generic, off-the-shelf systems able to protect us from common threats (Palo Alto Networks, CISCO ACE, …) to custom platforms designed with very specific purposes in mind. Even though they can be very complex systems, the core ideas behind them are straight forward and small version of such elements can be coded without too much effort.