Security in Scala: Using Object Capabilities
I presented Security in Scala at Scaladays NYC last Wednesday, and showed off ocaps, a small library of macros and utility classes for working with object capabilities.
Here are the slides and video. I also have an example project play-ocaps-demo that shows object capabilities being used with Play and Akka.
The presentation is mostly about why you want to use capabilities (and refinement types, and dynamic sealing, and membranes): in addition revocation and expiration, capabilities let you turn any security operation into a filter chain where you can add tracking, auditing and logging.
Capabilities has long been a fascination of mine – I remember it was one of my first questions to Adriaan Moors and James Iry about it when I first joined Typesafe. I still didn't really understand it though, because if capabilities are just object references, then why all the fuss?
Five years later, I think I get it. I also get why it's so hard to teach capabilities. There are several traps involved in capabilities, where the normal frame of reference doesn't apply.
For example, in object oriented programming, you create a type, and then if you want a reference to an object with that type, then you just instantiate it. How is getting a reference to a class an impediment?
Or, if you're using dependency injection, then you have access to everything that's bound to the DI framework or passed in through constructor. Again, getting a reference is no problem. That's literally what DI frameworks do.
Whereas the whole point of capabilities is that getting a capability is difficult. You can't get at the object under normal circumstances – there's one way in, and if you don't jump through the right hoops, you're not getting the golden ticket. And yet… authorization of capabilities is typically not shown in the literature. You can track it down to something called a Gatekeeper, but most papers are more interested what you can do with a capability rather than how you get it. Even though how you get it (and how tightly you control access to it) is pretty much what distinguishes a capability from an object reference in the first place.
And then finally, part of the problem is that if you discuss capabilities, you're in the unfortunate position of showing a connected access graph, rather than the truly important security property of capabilities, which is that you don't have access to them, and they are well and truly isolated. Most of your code doesn't have a reference to a capability. It's tough to show the absence of something in code. (Maybe show a Venn diagram with no intersection?)
The end result of this is that most of the work was not actually writing code. Most of the work was writing the documentation. This is the end result: a guide to object capabilities, which tackles capabilities in the context of software engineering design. The inspiration for this came from Joe Duffy's post on Objects as Secure Capabilities in Midori and Scott Wlaschin's posts on Capability Based Security in F#: I wanted to do something equivalent for Scala, and be able to demonstrate Scala's hybrid OO/FP model as a heterotic strength.
Writing good documentation is harder than it looks. Go too fast, and you lose people. Go too slow, and people lose interest. The introduction has to start with the basics of "here is a reference" and it only really starts picking up steam with the next section, Constructing Capabilities.
There are still sections I haven't written, such as "How do you build domain level capabilities out of low-level ones" and "How do you get capabilities working with DDD/aggregate roots" and so on. The examples section goes into patterns a bit more, and specifically the Repository Composition shows off some fancy cats functor work.
I'm particularly proud of the Access
pattern and the PermeableMembrane, as from what I can tell, they are new and original in the object capabilities field. There are other path-dependent monads around, notably managedt, and of course PermeableMembrane is a different use case than "uncooperative revocation" use case that Membrane is intended for – in that case, you probably want a ByteBuddy "general interceptor".
The biggest potential I see with capabilities is that they are a natural fit with Akka Actors and streams, especially when used with dynamic sealing. You can send a sealed message containing a capability anywhere you want, and only the object with the unsealer will be able to work with it! Capabilities are also a natural fit for functional programming, given that they are generally SAM, as long as you wrap an effect around them so you don't have to deal with revocation as a side-effect.
There's so much more than can be done with capabilities, and my belief is that they lead to better design overall, although I don't have concrete proof of this. The enforced abstraction and encapsulation provided by capabilities, together with the ability to mix in cross cutting concerns like logging and auditing transparently centralizes things nicely, and the ability to hand out "one-time only" capabilities and capabilities on an expiring timer prevents TOCTOU and confused deputy attacks.
Thanks for reading this, and I hope you like ocaps.