Gregg's personal blog
BrowserID versus DDOS
This article is the third in a three-part series on implementing the RDFa Test Suite. The first article discussed the use of Sinatra, Backbone.js and Bootstrap.js in creating the test harness. The second article discussed the use of JSON-LD. In this article, we focus on our use of BrowserID in responding to a Distributed Denial of Service Attack (DDOS).
RDFa Test Suite
Working on the updated RDFa Test Suite has really been a lot of fun. It was a great opportunity to explore new Web application technologies, such as Bootstrap.js and Backbone.js. The test suite is a single-page web application which uses a Sinatra based service to run individual test cases.
The site was becoming stable, and we were starting to flesh out more test cases for odd corner cases, when the site started to not respond. Manu Sporny, who’s company Digital Bazaar is kindly donating hosting for the web site, noticed that there were a number of Ruby processes that were consuming all available Ruby workers, and causing new requests to block. The service is fairly resource intensive, as it must invoke an external processor and run a SPARQL query over the results for each test. It seemed as if the site was being hammered by a large number of overzealous search crawlers! Naturally, we put a robots.txt in place, expecting that conforming search engines would detect the site’s crawl preferences and back off, but that didn’t happen. Upon further examination of the server logs, we noted requests were streaming in from all over the world! Clearly, we were under attack. (Who might wish ill of the RDFa development effort? Who knows, but most likely this was just an anonymous, and not specifically malicious attack).
My first thought was to make use of a secret api token, configured into the server and the web app, but that didn’t really do the trick either; it seemed that modern day malware actually just executes the JavaScript, so it picks up the API key naturally!
BrowserID to the Rescue!
Okay, how about authentication? It’s typically a pain, and we were reluctant to put up barriers in front of people who might want to test their own processors or see how listed processors perform. The two current contenders are WebID and BrowserID.
WebID has the laudable goal of combining personally maintained profile
information with SSL certificates (it was previously known as FOAF+SSL).
Basically, it’s a mechanism to allow users to use a profile page as
their identity. This could come off of their blog, Facebook, Twitter or
other social networking site. By configuring an SSL certificate into the
browser and pointing to their profile page, a service can determine that
the profile page actually belongs to the user. (There’s much more to it,
you can read more in the WebID
Spec). A key advantage here
is that the service now has access to all of the self-asserted information
the user want’s to provide about themselves as defined in their profile
page, such as foaf:name, foaf:knows, and so forth. The chief downside
is that the common source of existing user identities in the world haven’t
bought into this, and there’s a competing solution that offers similar
benefits.
BrowserID is a Mozilla initiative to enable people with e-mail addresses to use those e-mails to login to websites, kind of like OpenID - only more secure. Basically, as I understand it, a service wanting to support this would include the BrowserID JavaScript client code in the Web application and use a simple Sign In button that invokes this code. That sends a request off the the identity provider (IDP) to authenticate the user, which has probably already happened in the past and maintained in a cookie. The IDP then sends a response which invokes a callback. The client then does a call back to the service to complete the login passing whether or not the login was successful as well as the e-mail address that logged in.
The beauty is, using a tool such as the sinatra-browserid Ruby gem,
this becomes dirt simple! Basically, on the API side, put in a call to
authorized? to determine if the user is authorized. If not, either
direct them to a login screen, or in the case of the RDFa Test Suite,
place an informational message telling them why we need them to login, and
identify the BrowserID button at the top of the page.
In the principle entry-point to the test suite on the service side is
/test-suite/check-test/:version/:suite/:num. The only real change to
this method was to check for authorization before performing the test.
# Run a test
get '/test-suite/check-test/:version/:suite/:num' do
return [403, "Unauthorized access is not allowed"] unless authorized?
# Get the SPARQL query
source = File.open(File.expand_path("../tests/#{num}.sparql"))
# Do host-language specific modifications of the SPARQL query.
query = SPARQL.parse(source)
# Invoke the processor and retrieve results, parsed into an RDF graph
graph = RDF::Graph.load(params['rdfa-extractor'] + test_path(version, suite, num, format))
# Run the query
result = query.execute(graph)
# Return results as JSON
{:status => result}.to_json
end
In the banner, we add a little bit of Haml:
...
%div.navbar-text.pull-right
- if email
%p.email
Logged in as
%span.email
= email
%a{:href => '/test-suite/logout'}
(logout)
- else
= render_login_button
When the page is returned, the email variable is set if the user is
authorized, so they’ll see the email address if they’ve authenticated, and
a login button otherwise. The render_login_button has handled entirely
by sinatra-browserid; no muss, no fuss!
The only other thing to do is to not show the test cases in the test
suite, unless the user has authenticated, which we can tell because
$("span.email") won’t be empty. In our application.js, we use this to
either show the tests, or an explanation:
// If logged in, create primary test collection and view
if ($("span.email").length > 0) {
this.testList = new TestCollection([], {version: this.version});
this.testList.fetch();
this.testListView = new TestListView({model: this.testList});
} else {
this.unauthorizedView = new UnauthorizedView();
}
That’s pretty much all there is too it. The only complication I faced is that, when developing with shotgun, the session ID is changed with each invocation, so it wasn’t remembering the login. By fixing the session secret this problem went away. Total time from discovery of the problem to deployed solution: about 1 hour. Not too bad.
It’s important to note that the RDFa Test Suite is stateless, and we don’t really need any personal information; we don’t collect information anywhere, even in our logs. BrowserID basically becomes a gate keeper to help ward off abuse. It imposes a very low barrier of entry, so it doesn’t interfere with people using the site anyway they choose.
I do miss other user asserted information, such as the user’s name and
so-forth. OpenID, another single-signon initiative
that has lost momentum lately, provides a Simple Registration
Extension
add-on that allows users to assert simple information such as nickname,
mail, fullname and so forth. IMO, the right way to do this is with
something like FOAF or the schema.org Person class. Perhaps
BrowserID will provide something like this in the future.
If you liked this article you can add me to Twitter
Trackbacks for BrowserID versus DDOS
Powered by Typo – Thème Frédéric de Villamil | Photo Glenn
