First some background.
The LedgerSMB project is an open source financial accounting software project that runs on PostgreSQL. We implement a very large amount of business logic in user defined functions, which act as the main mapping tool between program object methods and database behavior. Currently we use database users as authentication users, partly by choice (this allows centralized security logic, so that other tools can be written and re-use permissions given to users), and partly by necessity (after we forked from SQL-Ledger, there weren't many options for retrofitting security onto that codebase).
This buys us access to a reasonable number of single signon options that PostgreSQL has access to, from LDAP to Kerberos 5. We can even use PAM where passwords are concerned. It also allows us to re-use permissions when integrating with other applications, or allowing other client interfaces. For a financial accounting application this seems like a net win.
There are obvious costs involved. For the web application, we are very limited to the types of http auth that can be supported. DIGEST for example is entirely out. BASIC works, and we could implement KRB5 easily enough (I plan on having this supported and working out of the box for 1.4). Very strong authentication measures cannot be properly managed on this directly although we could probably shim them in if necessary (for example BASIC + client-side SSL cert with a cn matching the user name and a specific root ca).
At the same time we've run into a fair amount of criticism mostly from the development crowd and more occasionally from dba's who tell me that the application should be the security barrier, not the database. My view is still that a smaller security perimeter is generally better, that the re-use of business logic and security logic go together, and that it strikes me as dangerous to re-use business logic without re-using security logic on the same level of the program.
Am I missing any major tradeoffs here? Are there gotchas that I am not considering?
Best Answer
I think you're conflating authentication and authorization.
I completely agree that keeping the security model in the DB is wise, especially as LedgerSMB is designed with access from multiple clients in mind. Unless you plan on going 3-tier with a middleware layer it makes perfect sense to have users as database roles, especially for something like an accounting app.
This does not mean you have to authenticate users against the database using a PostgreSQL-supported authentication method. Your database users, roles and grants can be used for authorization only if you like.
Here's how it works for a web ui for example:
jane
connects to web ui server and authenticates using whatever method is desired, say HTTPS X.509 client certificate handshake and DIGEST auth. The server now has a connection from a user it accepts is reallyjane
.The server connects to PostgreSQL using a fixed username/password (or Kerberos or whatever you like), authenticating its self to the db server as the user
webui
. The db server trustswebui
to authenticate its users sowebui
has been given appropriateGRANT
s (see below).On that connection the server uses
SET ROLE jane;
to assume the authorization level of the userjane
. UntilRESET ROLE;
or anotherSET ROLE
is run, the connection is operating with the same access rights asjane
andSELECT current_user()
etc will reportjane
.The server maintains the association between the database connection on which it has
SET ROLE
tojane
and the web session for userjane
, not allowing that PostgreSQL connection to be used by other connections with other users without a newSET ROLE
inbetween.You are now authenticating outside the server, but maintaining authorisation in the server. Pg needs to know what users exist, but doesn't need passwords or authentication methods for them.
See:
SET SESSION AUTHORIZATION
SET ROLE
GRANT
Details
The webui server controls the queries run, and it's not going to let
jane
run raw SQL (I hope!) sojane
can'tRESET ROLE; SET ROLE special_admin_user;
via the web ui. For added safety I'd add a statement filter to the server that rejectedSET ROLE
andRESET ROLE
unless the connection was in or entering a pool of unassigned connections.You're still free to use direct authentication to Pg in other clients; you can mix and match freely. You just have to
GRANT
thewebui
user the rights toSET ROLE
to users who can log in via the web and then give those users any normalCONNECT
rights, passwords, etc you want. If you want to make them web-only,REVOKE
theirCONNECT
rights on the database (and frompublic
).To make such an authentication/authorisation split easy I have a special role
assume_any_user
that IGRANT
every newly created user to. I thenGRANT assume_any_user
to the real username used by things like a trusted web front-end, giving them the rights to become any user they like.It's important to make
assume_any_user
aNOINHERIT
role, so thewebui
user or whatever has no privilges by its self and can only act on the database once it'sSET ROLE
to a real user. Under no circumstances shouldwebui
be a superuser or DB owner.If you're connection pooling, you can use
SET LOCAL ROLE
to set role only within a transaction, so you can return connections to the pool afterCOMMIT
orROLLBACK
. Beware thatRESET ROLE
still works, so it still isn't safe to let the client run whatever SQL they want.SET SESSION AUTHORIZATION
is the related but stronger version of this command. It doesn't require role membership, but it's a superuser only command. You don't want your web ui connecting as a superuser. It can be reversed withRESET SESSION AUTHORIZATION
,SET SESSION AUTHORIZATION DEFAULT
orSET SESSION AUTHORIZATION theusername
to regain superuser rights so it isn't a privilege-dropping security barrier either.A command that worked like
SET SESSION AUTHORIZATION
but was irreversible and would work if you were a role member but not superuser would be great. At this point there isn't one, but you can still separate authentication and authorisation pretty well if you're careful.Example and explanation
Now connect as
webui
. Note that you can't do anything totest_table
but you canSET ROLE
tojane
and then you can accesstest_table
:Note that
webui
canSET ROLE
tojim
, even when alreadySET ROLE
d tojane
and even thoughjane
hasn't beenGRANT
ed the right to assume the rolejim
.SET ROLE
sets your effective user ID, but it doesn't remove your ability toSET ROLE
to other roles, that's a property of the role you connected as, not your current effective role. Consequently you must carefully control access to theSET ROLE
andRESET ROLE
commands. There is, AFAIK, no way to permanentlySET ROLE
for a connection, truly becoming the target user, though it'd certainly be nice to have.Compare:
to:
This means that
SET ROLE
isn't exactly the same as logging in as a given role, something you must keep in mind.webui
can'tSET ROLE
todbowner
since it hasn't beenGRANT
ed that right:so by its self it's pretty powerless, it can only assume the rights of other users and only when those users have web access enabled.