A Software Defined Firewall for Heroku
May 7, 2015 | DevOps | Kevin Gilpin
Traditional IT environments are protected with perimeter software like the good ol’ VPN. In the cloud setting, the one, all-encompassing VPN is replaced with (or supplemented by) a more granular set of interlocking “security groups” (to use AWS parlance).
In the world of PaaS, containers and microservices, security groups themselves are obsolete. Why? Because application containers are distributed across a near-uniform cluster of servers or virtual machines. The topology of container placement onto host machines is constantly shifting and changing. In the older world, application code could be segregated from the database servers by a network boundary. In a PaaS architecture, these containers can (and likely will) run side-by-side on the same host OS.
Yet, network security can’t simply be abandoned as a relic of a bygone age. At Conjur, our customers are hitting this problem head-on as they move rapidly towards PaaS and Docker. They’re asking us for a “software-defined firewall”: a perimeter-like barrier with granularity as fine as individual containers. And to meet their continuous delivery objectives, it needs to deliver a convenient workflow and user experience for developers, operations, security, and compliance teams.
As an example of how we approach this problem, we’re now providing a Heroku buildpack which can be used to add a software-defined firewall in front of any Heroku application or web service. It’s available at https://github.com/conjurinc/heroku-buildpack-conjur, and it works like this (full README available in Github).
First, make sure you are on the Heroku cedar-14 stack
Then configure buildpacks to combine your base buildpack (e.g. Ruby or Node.js) with the Conjur buildpack.
Next, configure your application to listen to /tmp/nginx.socket for new connections, and to touch /tmp/app-initialized when it’s ready to receive traffic.
Finally, configure the application with the id of a Conjur resource; inbound requests will need to have specific privileges on this resource in order to be passed through.
When your app starts up, it’s now running behind a gatekeeper which is selectively filtering inbound traffic according to Conjur RBAC and privilege grants.
From a technical standpoint, here’s how the system works.
The client authenticates with Conjur, and obtains a bearer token. To eliminate latency, tokens can be prefetched so that a token is already available when the client needs it.
a. The icon with 4 circles represents the token
b. The client can be a person, or code (e.g. another service)
The client sends an outbound HTTPS request, with the token on the Authorization header.
The gatekeeper receives the inbound request. The gatekeeper constructs a request to the Conjur authorization service, with the client’s Authorization token on this request.
a. The brick wall represents the gatekeeper
Conjur receives the authorization request, and verifies the token to obtain the client identity. Then, Conjur checks whether the client identity has the necessary privilege on the gatekeeper resource.
Conjur responds with an HTTP status code indicating if the request should be allowed.
The gatekeeper caches this response, so that the authorization cost does not have to be repeated for each client request.
If Conjur allows the request, then it is submitted to the protected Heroku application or service. The request is processed, and the response goes back to the client.
Let’s see the gatekeeper in action. Conjur’s release-bot is a webservice which deploys Conjur artifacts to destinations like RubyGems, NPM, and Heroku. release-bot runs in Heroku, and access to it is controlled by the Gatekeeper buildpack. It has a simple REST interface:
POST /npm/releases release to NPM
POST /rubygems/releases release to RubyGems
DELETE /rubygems/releases/:name yank a Ruby gem
POST /heroku/releases push a Heroku app
A Conjur webservice resource called
production/release-bot-2.0 is used to protect access to the release-bot. In accordance with the Gatekeeper,
POST methods require create privilege on the resource, and the
DELETE method requires update privilege. The webservice resource, along with the client roles, are defined in this policy file. The policy file is loaded using Conjur tools, once for each environment (development, stage, production).
The Jenkins server is enabled to perform code pushes. This is a simple process:
The Jenkins layer is granted the role
production/release-bot-2.0/publisher. This gives Jenkins hosts (both master and slaves) permission to read and create releases.
Jenkins jobs are created to perform releases; for example release-rubygems. This job operates by authenticating with Conjur (using the Jenkins host credentials), then POST-ing to the release-bot
A Jenkins promotion step is created on appropriate projects, such as the Conjur CLI for Ruby, which invokes the release-rubygems job
As a result, there’s a clean separation of duties:
Jenkins performs build and test, and creates artifacts
release-bot deploys artifacts into production
In addition, the Conjur development team has permission to invoke release-bot to perform any necessary manual administration of releases; for example, yanking a Gem, or re-invoking a push which has failed for some reason.
Neither Jenkins nor the Conjur development team have direct access to the deployment credentials, such as the RubyGems and Heroku API keys; yet can easily and securely perform the release functions.
At Conjur, we are working hard with our customers and the broader DevOps community to learn how to make tools and processes that give developers, operations, and security teams the power that they need to move fast and make decisions, without sacrificing security or transparency. Here’s how the Conjur gatekeeper feels to each member of the DevOps + continuous delivery team.
For developers, the gatekeeper enables them to remove HTTP(S) authentication from their application. This is great for development, because development servers can be built and tested against each other with security disabled. Also, because each app has its own built-in gatekeeper, there’s no single point of failure “global authentication server” which might limit the scalability or flexibility of the deployed application.
Security people get a uniform traffic authorization architecture that applies across the entire system. They know that each application will be protected in the same way, and they don’t need to vet the authentication and authorization systems that would otherwise be built into many different projects.
It’s also a nice solution for operations, because the build machine is going to generate application images which, although they don’t have built-in security, can be deployed to production without any modification.
Compliance people gain the assurance that communication between services is individually authorized. The system is no longer a big blob of services communicating freely between each other; it’s a defined, controlled set of interactions, each of which are independently authorized and audited. Furthermore, because Conjur provides role-based access control and permissions reporting, they can determine at any time which people and code have specific privileged access.