As the web became more popular web applications became more complex. When the idea of adding a scripting functionality within a web-browser was conceived the security model assumed certain things about what "secure" interaction between the browser and the web server looked like; this led to the "Same Origin Policy" (SOP). The SOP allowed browsers to decide when a script within a browser was trying to do something the policy did not allow, namely using browser scripting in the context of one page (aka the "origin") to interact with resources originating from a completely different site that the browser perceived as being from a different "origin". The goal is to prevent exploitation of "Cross Site Scripting" (XSS) vulnerabilities, allowing a browser to detect when script code is doing something that violates the SOP, such as making XMLHttpRequest() calls within a page originating from origin "A" to a resource originating from origin "B".

As the web continued to grow in popularity, web content became more abundant, and "mashups" began to appear, and some web sites even derived 100% of their content from other sites. In order to get around SOP restrictions, mashup sites could perform the fetching of content on their own servers, and serve the mashup content to the clients from one single origin they controlled. This method put additional bandwidth and storage demands on the mashup site operators, and the same limitations were faced by any other web application developers wanting to use this same technique to include external data sources in their applications. An updated browser security policy would be needed for efficient mixing of content from different remote sites within the same application. Browsers enforce the SOP, and although web servers can enforce restrictions on requests based on their header values (such as allowing or blocking requests based on the HTTP "Referer" header) web servers didn't originally have any way to indicate to a browser whether or not information it provided was expected to be used in other applications or not.

In response to the demands of web application developers, the W3C developed CORS: Cross Origin Resource Sharing. CORS provides the means by which browsers can communicate their application origin to servers and request a resource's origin access policy information from servers, and gives servers a way to indicate access policy to browsers and a framework to determine the validity of a cross-origin request. The following list of browsers have CORS support built in:

  • Chrome 3+
  • Firefox 3.5+
  • Opera 12+
  • Safari 4+
  • Internet Explorer 8+

More details on CORS support can be found here.

 

cors_flow

 

Scripting a Solution

The strategy we are going to employ for scripting CORS enforcement in the LineRate scripting environment is to create a "CorsService" object prototype as part of a module, and then require the module in a script where we can apply the CORS enforcement on a per-virtual server basis.  Since not all applications will be identical, one of the parameters to the CorsService object's constructor is a configuration object with the following convention:

{
  < Absolute URI where CORS is enforced >:
  {
    "origins": [ < List of origins allowed to query the parent URI > ],
    "allowCredentials": < Boolean indicating whether Cookies are allowed to this URI >,
    "allowHeaders": [ < List of headers to include as "allowed" > ],
    "methods": [ < List of allowed HTTP request methods > ]
  },
  < Another URI to enforce if we want... >:
  {
    ...
  },
  "maxage": < integer value indicating the validity of information received in a "preflight response" >
}

When we create the new "inline script" at the LineRate CLI we use the above convention to create the configuration of our CorsService object. The following command creates the inline script which will protect the URI "/api/v1.0/json" , allowing access from two different origins, accessible by the GET and HEAD methods, allowing a special header, and disallowing the Cookie header:

Script Logic

We take advantage of the NPM async module's waterfall functionality when implementing our CORS enforcement. We chose the "waterfall" method over other methods (e.g. serial, compose, etc) because of the waterfall method's default function, giving us a kind of error handling mechanism that simplifies how we need to write our functions implementing the CORS protocol. The high level logic is that we compose small functions that accomplish each discrete step in the CORS protocol handling process, and any time we encounter a condition that prevents us from providing a valid CORS response, we need to fall through to the default function. Our default function is a no-op when we provide a valid CORS response, otherwise we either bypass CORS or reject the request. In CORS, rejecting a request amounts to a 200 OK response with no CORS headers or body, resulting in the browser emitting an error event to the script function requesting the resource (e.g. XMLHttpRequest() would need to register a listener for the error event to detect this condition) At a more detailed level the async waterfall function list actions are: Check the request against the CorsService configuration, and verify whether we enforce the URI or not and if we do enforce, do we have a valid Origin? Check to see if this is a "preflight" request according to the CORS standard. If this is a "preflight" process the response as such, otherwise proxy the request to the real-server and add necessary response headers. DEFAULT ACTION: proxy the request and pipe back the response when the request URI is not enforced, else reject the request when the request is deemed to be an invalid CORS request.

And here's the final script (NOTE: this example may require modification to work properly in your environment. Feel free to ask any questions about it on F5's Dev Central boards!)

Remember: you can always try this code out with a free (as in forever) tier license for LineRate by visiting linerate.f5.com/try.