So for more than a month I've been working on figuring out how to finally get my security model in place.
It wasn't a complete waste, I'm quite sure I headed off a number of surprises that would have been more costly later. But...
Microsoft's Azure DocumentDB is a great offering. Super fast and super scalable, it's just what I thought we needed. One of the challenges is that it's a No-SQL database. Basically flat with no relational structure behind it. Once I got past that old relational thinking I thought I had it made.
The problem started when I tried to become more efficient, using one of DocumentDB's security offerings.
One of the basics of internet security is the concept of least privilege. That is, only let people access the absolute minimum they need to perform the functions which they're allowed to. Core belief.
Another really important aspect is to assume that whatever security information flows out to any client device is going to be compromised. Sucks, but that's what comes with internet.
Microsoft created an SDK to make working with DocumentDB easier, but it doesn't run on Xamarin (which means no to Android and iPhone). I was also told (incorrectly, I believe) it didn't run on Azure Mobile Apps, which is where the server for this app would be hosted. 0 for 2.
However, you can make simple REST (internet) calls to perform all the functions. You can do those from any language. So I proceeded to write my own interface to DocumentDB that would run on all platforms. And it took this newbie a really long time, but it worked. I was really excited to think I could even do this, let alone have it work.
Yes! Back in business. On to implementing it into the system...
For DocumentDB there's a master key which gives you full control (don't let that out in the wild!) and a set of read only master keys, which you could ostensibly hand out to people in certain architectures, allowing them to read all they want, but not change anything. You get two of each and you get to rotate them whenever you want, although that's really a lot of work.
And read only master keys are not really good if you're putting together a multi-tenant application. Multi-tenant means you have all kinds of different customer data all in the same collection. Clearly, you don't want to leak information from customer A to customer B. You can prevent that on the server side of your application by requiring all data access to be filtered out per customer. Easy enough.
But what if you want your client devices to access DocumentDB directly? That would be awesome - all that traffic would be off of your server and instead of four network hops to get data to the client, it'd only be two. Huge performance benefit.
Only problem is, publishing read only keys means someone will almost certainly get them and if you're not changing your keys frequently (which you won't in reality), you can expect some bad person will have them and then they'll have your data. And they won't have to go through your server to get it.
DocumentDB also has a feature called resource tokens. So instead of publishing your master or read only keys, you can give them a read only resource token instead. These only last an hour and then a user has to get another one from a server in the middle (run by you). The server would hand out the tokens but only if it knew the person getting them was authenticated and had the rights to what they wanted to see.
Resource keys are tied to a "resource" which would be a collection, or an individual document. And there's the rub. I don't want to create security tokens for every single thing in the database. That won't scale at all.
Well, DocumentDB also has the concept of "partition keys" which allows the storage to grow to nearly limitless size and throughput. Microsoft suggests that one way to achieve this scale in a multi-tenant application is to make the tenant (customer) id the partition key. This way traffic and storage a divided up and response times can stay fast while the app gets more and more popular.
So what I needed was to create a database permission for each user that limited their view to their own tenant id. Basically that would allow me to create a resource token allowing read only access, but only to one tenant's data.
So assuming that some nefarious person signed up for the app and created an account, instead of getting a resource token giving them read access to everyone's data, the only resource tokens that would flow out to him/her would allow access only to their own data. Voila.
Now DocumentDB documentation says that when you can't have two permissions to the same resource for the same user, but I thought maybe, just maybe that applied to partitions, since if you have a partitioned collection, you must supply the partition key when you create the permission. I thought, incorrectly as I discovered today, that the permissions are for the entire collection.
So my choices are:
- Every tenant (customer) gets their own collection.
- I partition the app and hand out resource tokens and cross my fingers.
- I hand out read only master keys and move out of the country.
- I force all traffic through the server and hand out zero keys.
And the results are:
- Too expensive
- Risky at best.
- Even riskier.
- What did Holmes say, when you've eliminated everything else? "Shit", I'm sure.
But Arthur didn't put that in the book.