Libmacaroons
Macaroons are flexible authorization credentials that support decentralized delegation, attenuation, and verification.
Install / Use
/learn @rescrv/LibmacaroonsREADME
Macaroons are Better Than Cookies!
This library provides an implementation of macaroons[1], which are flexible authorization tokens that work great in distributed systems. Like cookies, macaroons are bearer tokens that enable applications to ascertain whether their holders' actions are authorized. But macaroons are better than cookies!
Why Macaroons?
Macaroons are great for authorization because they're similar enough to cookies to be immediately usable by developers, but they include several features not present in cookies or other token-based authorization schemes. In particular:
-
Delegation with Contextual Caveats (i.e., confinement of the usage context):
Macaroons support delegation. Give your macaroon to another user, and they can act on your behalf, with the same authority. Cookies permit delegation as well, but the remaining features of macaroons make it much more safe and practical to pass around macaroons than cookies. In particular, macaroons can limit when, where, and by whom the delegated authority can be exercised (e.g., within one minute, from a machine that holds a certain key, or by a certain logged-in user), by using attenuation and third-party caveats. -
Attenuation: Macaroons enable users to add caveats to the macaroon that attenuate how, when, and where it may be used. Unlike cookies, macaroons permit their holder to attenuate them before delegating. Whereas cookies and authorization tokens enable an application to get access to all of your data and to perform actions on your behalf with your full privileges, macaroons enable you to restrict what they can do. Those questionable startups that "just want the address book, we swear it," become a whole lot more secure when the target application supports macaroons, because macaroons enable you to add caveats that restrict what the application can do.
-
Proof-Carrying: Macaroons are efficient, because they carry their own proof of authorization---cryptographically secured, of course. A macaroon's caveats are constructed using chained HMAC functions, which makes it really easy to add a caveat, but impossible to remove a caveat. When you attenuate a macaroon and give it to another application, there is no way to strip the caveats from the macaroon. It's easy for the entity that created a macaroon to verify the embedded proof, but others cannot.
-
Third-Party Caveats: Macaroons allow caveats to specify predicates that are enforced by third parties. A macaroon with a third-party caveat will only be authorized when the third party certifies that the caveat is satisfied. This enables loosely coupled distributed systems to work together to authorize requests. For example, a data store can provide macaroons that are authorized if and only if the application's authentication service says that the user is authenticated. The user obtains a proof that it is authenticated from the authentication service, and presents this proof alongside the original macaroon to the storage service. The storage service can verify that the user is indeed authenticated, without knowing anything about the authentication service's implementation---in a standard implementation, the storage service can authorize the request without even communicating with the authentication service.
-
Simple Verification: Macaroons eliminate complexity in the authorization code of your application. Instead of hand-coding complex conditionals in each routine that deals with authorization, and hoping that this logic is globally consistent, you construct a general verifier for macaroons. This verifier knows how to soundly check the proofs embedded within macaroons to see if they do indeed authorize access.
-
Decoupled Authorization Logic: Macaroons separate the policy of your application (who can access what, when), from the mechanism (the code that actually upholds this policy). Because of the way the verifier is constructed, it is agnostic to the actual underlying policies it is enforcing. It simply observes the policy (in the form of an embedded proof) and certifies that the proof is correct. The policy itself is specified when macaroons are created, attenuated, and shared. You can easily audit this code within your application, and ensure that it is upheld everywhere.
The rest of this document walks through the specifics of macaroons and see just how easy authorization can be. So pour a fresh cup of espresso to enjoy alongside your macaroons and read on!
Installing Macaroons
This library makes it easy to get started with using macaroons in your service. To use the library you must first install it. You'll need to somehow install libbsd[2]. It's packaged in some Linux distributions, and can be installed from source on most *NIX platforms. Once you have libbsd installed, installing macaroons is straight forward. If you're installing from git you'll need to install autoconf, automake, libtool, and autoconf-archive and execute the first command. if you're installing from a release tarball, you should skip the first command and start with configure.
$ autoreconf -i # only when installing from Git
$ ./configure --enable-python-bindings
$ make
# make install
This will install macaroons onto your system and give you both the C and Python interfaces to libmacaroons. In the rest of this document, we'll show examples using the Python interface, but the code could easily be translated into C.
Creating Your First Macaroon
Imagine that you ran a bank, and were looking to use macaroons as the basis of your authorization logic. Assuming you already installed libmacaroons, you can create a macaroon like this:
>>> import macaroons
>>> secret = 'this is our super secret key; only we should know it'
>>> public = 'we used our secret key'
>>> location = 'http://mybank/'
>>> M = macaroons.create(location, secret, public)
We've created our first macaroon!
You can see here that it took three pieces of information to create a macaroon. We start with a secret. Here, we just have English text, but in reality we would want to use something more random and less predictable (see below for a suggestion on how to generate one). The public portion tells us which secret we used to create the macaroon, but doesn't give anyone else a clue as to the contents of the secret. Anyone in possession of the macaroon can see the public portion:
>>> M.identifier
'we used our secret key'
This public portion, known as the macaroon's identifier, can be anything that enables us to remember our secret. In this example, we know that the string 'we used our secret key' always refers to this secret. We could just as easily keep a database mapping public macaroon identities to private secrets, or encrypt the public portion using a key known only to us. The only requirement here is that our application be able to remember the secret given the public portion, and that no one else is able to guess the secret.
Our macaroon includes some additional information that enables us to add caveats and figure out where the macaroon should be used. The macaroon's location gives a hint to the user about where the macaroon is accepted for authorization. This hint is a free-form string maintained to help applications figure out where to use macaroons; the libmacaroons library (and by extension, the Python bindings) do not ascribe any meaning to this location.
Each macaroon also has a signature that is the key used to add caveats and verify the macaroon. The signature is computed by the macaroons library, and is unique to each macaroon. Applications should never need to directly work with the signature of the macaroon. Any entity in possession of the macaroon's signature should be assumed to possess the macaroon itself.
Both of these pieces of information are publicly accessible:
>>> M.location
'http://mybank/'
>>> M.signature
'e3d9e02908526c4c0039ae15114115d97fdd68bf2ba379b342aaf0f617d0552f'
We can share this macaroon with others by serializing it. The serialized form is pure-ASCII, and is safe for inclusion in secure email, a standard HTTPS cookie, or a URL. We can get the serialized form with:
>>> M.serialize(format=1)
'MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMjZpZGVudGlmaWVyIHdlIHVzZWQgb3VyIHNlY3JldCBrZXkKMDAyZnNpZ25hdHVyZSDj2eApCFJsTAA5rhURQRXZf91ovyujebNCqvD2F9BVLwo'
Of course, this serialized form can be displayed in a more human-readable form for easy debugging:
>>> print M.inspect()
location http://mybank/
identifier we used our secret key
signature e3d9e02908526c4c0039ae15114115d97fdd68bf2ba379b342aaf0f617d0552f
Adding Caveats
At this point, we have an unconstrained macaroon that authorizes everything within our bank. In practice, such a macaroon is dangerous, because very few people should hold such power. Let's add a caveat to our macaroon that restricts it to just the account number 3735928559.
>>> M = M.add_first_party_caveat('account = 3735928559')
This new macaroon includes the same identifier and location that our old macaroon from our initial macaroon, but includes the additional caveat that restricts the bank account. The signature of this new macaroon is different, and incorporates the new caveat we've just added. An entity in possession of this new macaroon cannot simply remove our new caveat to construct the old macaroon:
>>> print M.inspect()
location http://mybank/
identifier we used our secret key
cid account = 3735928559
signature 1efe4763f290dbce0c1d08477367e11f4eee456a64933cf662d79772dbb82128
Of course, we can add a few more caveats, and the macaroon's signature will change with each of the
