Photo by Karolina Szczur on Unsplash

Fresh Baked Roles with Zend Permissions RBAC

As our Traackr application has grown, so has our need to restrict access to features or pieces of features. Recently, our search feature added new filters to use. However, some of these filters are only meant to be available to enterprise customers. This was not a new problem for us; our analytics feature added a new report type, one only enterprise customers should have.

Feature flags were our initial solution to both efforts. This worked well enough for our analytics use case. Our customer success team attaches an account to the feature flag, and the account can view the new report. Cracks started showing in this approach when applied to our search effort.

Let's say we added four new filters to search. That is four new feature flags to create, not a terrible burden from a coding standpoint. But our poor customer success team... They now have to go account by account and flip four new switches to make sure the account has access to the search filters granted to them by their subscription. Yuck...

Role-based Access Control to the Rescue!

During one of our hack weeks, I had tinkered with the Zend Permissions RBAC package without a solid use case in mind. It was just an interesting concept that crossed my path at the time. Now, it seemed a perfect use case for it was in front of me. Before we dive into details, we should first answer an important question.

What is role-based access control (RBAC)?

A standard access control list (ACL) focuses on objects and who can act on them. Your computer's file system is an example of an ACL. Using a Linux file system, each file and directory is an object. The ACL for a file contains three entities -- the owner of the object, the group associated with the object, and everyone else. A feature in our application would be an object, and the feature flag ended up being the ACL attached to it.

The statement ACL makes is "This object is associated with these people, and each person can access the object in specific ways". Phrased in the context of our application, "this feature can be accessed by the following accounts."

How does RBAC differ then? I'll let the package's documentation make the initial introduction:

RBAC differs from access control lists (ACL) by putting the emphasis on roles and their permissions rather than objects (resources).

RBAC makes the statement "These people are associated with these roles. These roles grant them these permissions." Again, in the context of our application, the statement becomes "This account is associated with this role. This role allows them access to these features." This concept has an immediate benefit for us. Accounts can be granted a specific role. As we add a new feature, we update the role to have new permissions for the feature, and every account is automatically able to use the feature. No longer do we need to edit accounts individually.

Baking up our roles

Let's start building this out! We first need to define our roles and the permissions associated with them. Here is a stripped down version of our class to manage that:

Our "Enterprise Subscription Package" starts with a parent role representing just the subscription for the account. It contains no feature specific permissions and would contain more general permissions instead -- number of users allowed in the account, number of concurrent logins, quotas, etc. A child "Search" role is added to the subscription role. Within the "Search" role we grant permission (search) to use the search feature and grant permission to use the topic (search.topics), brand (search.brands), and location (search.locations) filters. The dot notation is our own convention for role and permissions names. Names can be any string, but this gives us a visual hierarchy of the pieces of a feature.

May I?

When a customer logs into our site, we check the subscription attached to their account and attach the appropriate Role object to their session. Our search page must now check they can access the page, and if so, what filters they should see.

That smells good...

The sample checks above may not seem much different than what you would do with features flags. We are just replacing "are they on this feature flag" with "do they have this role and permission". The benefit here is in the ease of maintenance, especially for managing accounts and features. Using RBAC, a new feature is added to a role, that role attached to a subscription, and that role addition / subscription update is shipped with the feature. Every account tied to that subscription has immediate access to the feature. Our customer success team no longer has to take time to turn the feature on for an account.

RBAC has also made it easy to extend the system even further. As an example, we built an override and exclusion system on top of it. If a customer abuses the location search feature, we can shut it off just for them. Need an incentive for a customer to upgrade? We can override the permissions to allow them access to a feature for a short time.

We will be migrating our existing feature flagged based items to RBAC. Using it has also sparked ways to improve other areas of our code, particularly user based permissions. Our code and process has been much more manageable since we baked RBAC into it. But don't take my word; give the recipe a try for yourself.


Git Driven Release Management

Like many engineering teams, here at Traackr we like to streamline our workflow to spend as much time as possible building cool features. We also like to release early and often. And our product team likes being able to answer pesky questions like "has feature X been deployed?" or "there was a regression with feature Y, what release was that included in?". At times, these requirements can be a give and take. Using an issue tracker helps answer questions about when things were released, but navigating an application like Jira is a context switch. Plus, it's easy to click the wrong buttons. Releasing often means these context switches and mistakes happen frequently. Can we do better?

Some of our teams release more often than others.
Some of our teams release more often than others.

We think so, which led us to look at what we like about our tooling. One great thing about using Jira and Bitbucket is the way they seamlessly links commits to issues by including a ticket ID in your commit message. Because of this, we've standardized on a "<Ticket ID> <Message>" commit message format. We enforce this standard using a commit hook. This got us thinking... If we can link Jira tickets to commit messages, can we also map our git tags to Jira fix versions? It turns out the answer is yes!

Out of the box integration is nice, but sometimes you need to do it yourself.
Out of the box integration is nice, but sometimes you need to do it yourself.

The prototype

There were two main problems to solve. How do you identify a range of commits associated with a release? How do you interact with Jira to tag the issues with a new fix version? Enter release_fix_versioner.py.

At Traackr, we use a git-flow tagging and branching strategy; this is very convenient because for every release we already have a tag to reference. By providing our script with a new git tag, we can grab all commits between this tag and the previous tag to represent all of the commits to create a new release. With startTag and endTag, git has a handy command "git log --format=%s startTag...endTag" to print a list of commit messages in an easy to parse format. From there, it's a simple matter of parsing the commits to come up with a unique set of issue IDs. In order to support a variety of commit formats across different teams in our organization, the parsing is done using a regular expression with named groups to identify the key and message: "(?P<key>[\w]*-[\d]*)[ :-](?P<value>.*)".

Interacting with Jira turns out to be simple as well; the interface is extensive and does anything you could ask for. To start with, we decided to add some validation to each ticket prior to dealing with tags. We check things like making sure the work is finished by verifying the state equals "Done". This method is really all there is to it:

After creating two more similar methods to create a new fix version and add that fix version to each ticket, we've got ourselves a prototype:

Next Steps

Now that we have this script, we can start using it to simplify the manual issue management. If it works out, we'll include it as an automated step in our one button deploy job, and we'll never have to worry about Jira being out of date again. This could even be used to generate release notes for our public facing applications.

All the code for this hackweek project is available on github. Give it a try and let us know how it went!


Raft Visualization

Raft Visualization


Round-robin Cross Subdomain AJAX Requests

Well, that’s a mouthful! But what is it and what is it useful for? If you have done any web development in the last 10 years you have most likely used AJAX to make your web pages or, even more likely your web based app, more responsive and dynamic. You also probably know that most browsers limit the number of concurrent AJAX requests that can be made back to the origin server (i.e. the same server the page is served from). Usually that number of concurrent requests is 6 to 8 (sometimes a bit more) depending of the browser.

 

This might be fine for what you are trying to do, but if your page has a lot of dynamic components or if you are developing a single page web app, this might make the initial loading of your page, and all its content, a bit slower. After your first 6 (or 8) AJAX requests have fired, any subsequent AJAX query back to that same origin server will be queued and wait. Obviously your AJAX requests should be fairly fast, but even if your AJAX requests are just 50 milliseconds, any request after the initial 6 or 8 would have to wait 50 milliseconds when queued before executing. So in total a request could take 50 milliseconds (waiting) + 50 milliseconds executing = 100 milliseconds. This short waiting times add up pretty quickly.

 

The solution? Make these requests look like they are going to different origin servers so that the browser will execute them in parallel, even if they are, in fact, going to the same server. This will allow more AJAX requests to run in parallel, and therefore speed up your site/app. Here I will show you how to accomplish this using JQuery (but the same strategy can be used with other frameworks).

Cross subdomain AJAX requests with a single server

Basic idea

The basic idea is pretty simple. Say you are serving your pages from webapp.traackr.com. You will want to send some of the AJAX requests through webapp1.traackr.com, some through webapp2.traackr.com, some through webapp3.traackr.com …etc. You get the idea. And we will set it up so that all these hosts are, in fact, the same server.

What I will show you here is how to do this transparently so you don’t have to manually set up each AJAX request and decide where it should go. Our little trick will randomly round-robin through all the available hosts. This will work even if your application runs on a single server and the best part is that if you need to scale and upgrade to multiple redundant servers you will have little if anything to change.

 

Easy enough, right? Well of course, there is just one more little thing. When you start making AJAX requests to a host different than the original host, you open the door to security vulnerabilities. I won’t go into the specific details of the potential security issues, but suffice it to say that your browser won’t let you make these cross-domain (subdomain in our case) requests. Luckily, CORS comes to the rescue. CORS is a rule-based mechanism that allows for secure cross-domain requests. I will show you how to setup the proper CORS headers so everything works seamlessly.

The setup

First things first - you will have to create DNS entries for all these subdomain hosts. Because we are doing this with a single server, you need to create DNS entries for webapp1.traackr.com thru webapp3.traackr.com that all resolve to the same host as webapp.traackr.com (they can be AAA or CNAME records). All set? Moving on.

The code

We need to accomplish two things:

  1. (Almost) Randomly round-robin the AJAX request to the various hosts we have just defined
  2. Modify AJAX requests so they can work across subdomains with CORS

Round-robin your AJAX requests

Here we are going to leverage the excellent JQuery framework, so I’m assuming you are using JQuery for all your AJAX requests.

 

The following Javascript code should be executed as soon as your document is ready. This piece of code uses global AJAX settings in JQuery to hijack all AJAX requests made with JQuery. Once we identify an AJAX request going to the origin server, we rewrite it right before it hits the wire (beforeSend()).

 

Almost there. Now we need to make sure your server can handle these requests when they come via one of the aliases we defined.

Enable CORS for your AJAX requests

Before we look at the CORS headers needed for these cross-domain AJAX requests to work, you need to understand how cross-domain AJAX requests are different than regular AJAX requests. Because of CORS, your browser needs to ensure the target server will accept and respond to cross-domain AJAX requests. Your browser does this by issuing OPTIONS requests. They are called ‘preflighted’ requests. These requests are very similar to GET or POST requests except that the server is not required to send anything in the body of the response. The HTTP headers are the only thing that matters in these requests. This diagram illustrates the difference:

So you need to make sure your server returns the proper headers. Remember in the current set up webapp.traackr.com and webapp[1-3].traackr.com are the same server. There are many ways to do this. Here is one that leverages Apache .htaccess config:

Because we allow credentials to be passed in these cross-domain requests, the origin allowed header ('Access-Control-Allow-Origin’) must specify a full hostname. You can not use ’*’, this is a requirement of the CORS specification.

 

An important note here. Because these OPTIONS calls are made before each AJAX request, you want to make sure they are super fast (i.e. a few milliseconds). Since the only thing that matters are the headers (see above), I recommend you serve a static empty file for these requests. Here is another .htaccess config that will do the trick (make sure options.html exists and is empty):

And that is it! Pop open your JavaScript console and look your AJAX requests being routed through the various aliases and being executed in parallel.

Be warned

So what trade-offs are you are making? Remember this is engineering, there are always trade-offs! Well all of a sudden you might get twice (or more) as many requests hitting your server in parallel. Make sure it can handle the load.

What’s next?

There are probably a few things you can improve on. I will mention two we use at Traackr but will leave their details as an exercise for the reader:

  • You will probably want to avoid hardcoding your list of servers in the script.
  • Some corporate firewalls do not allow OPTIONS requests. This can cause this entire approach to fail. The script we use at Traackr will actually detect errors in OPTIONS requests and will fall back to regular AJAX requests.

Use a load balancer and let it scale

What can you do if/when you need to scale? One easy approach that doesn’t require you to change much is to put 2 or more servers behind a load balancer (maybe an Elastic Load Balancer on AWS). Then update your DNS so that all your webapp[1-3].traackr.com URL are now pointing to your load balancer.

 

What magic happens then? Browsers will make more parallel requests, just as we have described all along here. But now your load balancer will round robin each of these requests to the many servers you have running behind it. Magically you are spreading the load (and the love).

 

Thank you to the entire awesome Traackr engineering team for the help with this post. We are Traackr, the global and ultimate Influencer Management Platform. Everything you need to discover your influencers, manage key relationships, and measure their impact on your business. Check our blog. We are hiring.


The Why and How of Ansible and Docker

The Why and How of Ansible and Docker


How to Perform Fuzzy-Matching with Mongo Connector and Elastic Search

How to Perform Fuzzy-Matching with Mongo Connector and Elastic Search


Introducing Full Stack JS Amber - A boilerplate with Ember on the client and a Node Server API » { 100PercentJS }

Introducing Full Stack JS Amber - A boilerplate with Ember on the client and a Node Server API » { 100PercentJS }

Building a Recipe Search Site with Angular and Elasticsearch

Building a Recipe Search Site with Angular and Elasticsearch

Migrating databases with zero downtime

Migrating databases with zero downtime

Building a Message Queue using Redis in Go

Building a Message Queue using Redis in Go