Veil2
is a collection of database objects,
written in C and SQL, that provides an implementation of roles,
privileges, contexts, scopes and session management that can be
used to secure your database with relatively little custom
implementation work.
It links to your database through functions, triggers and foreign keys, and provides fast, tested mechanisms to identify, load and test a user's privileges in various scopes.
It is important to realize at this point that
Veil2
is not a complete application or a
product: it cannot be used stand-alone and can only be integrated
into your database by careful work on your part. You will need to
define the links between Veil2
and your
database schema, and you will need to provide
Veil2
with customized functions and views to
make this integration work.
To aid in this, Veil2
allows user-provided
views and functions to take precedence over the built-in
system-provided ones. This mechanism aims to provide maximum
flexibility while still allowing Veil2
to be
distributed as a PostgreSQL extension. This means that future
Veil2
bug-fixes and upgrades can be easily
applied to your database without breaking your customizations.
There is various documentation to help you with this:
Veil2
creation
scripts;Veil2
demos.
You should familiarize yourself with at least this document and the demos before starting out on your implementation.
Veil2
works by:
What this means is that when Alice tries to select the user record for Bob, Alice will only see that record if she has been assigned the necessary privilege to view Bob's user record in an appropriate scope. As each user's privilege assignments will be different, each user will see a different subset of data.
The following sections provide more detail on each of the above list items.
Veil2
provides session management functions
for both dedicated and
shared
database connections. It is up to you or your application to
ensure that the session protocols are followed. If they are
not, the user will have access to no data at all, or access to
data based on another user's access rights.
By calling the appropriate session management functions with
appropriate authentication tokens, a Veil2
session will be created. This causes session parameters to be
set up in secure temporary tables from which they can be
quickly retrieved. These session parameters include session
privileges with one record for each scope in which the user
has privileges.
The set of privileges in each scope is stored in a single bitmap. This is a space-efficient array of numbered bits, with each bit indicating the presence or absence of a privilege. Tests for the presence of a privilege in a bitmap are very fast.
At the start of each session, the user's privileges are determined based upon the roles that have been assigned to them and the contexts of those role assignments. This is a highly optimized process, using pre-built, cached, bitmaps and materialized views. These caches and materialized views are automatically updated as needed.
Each privilege assigned to a user acts within a scope that depends on the context in which it was assigned. Some scopes will allow all records in a table to be seen or manipulated, and some will allow large, small or smaller subsets of data to be seen.
As stated above, the set of privileges in each scope is stored
in a bitmap, and tests for the presence of a privilege in the
bitmap are very fast. The security rules for a relation will
typically be defined something like this (from the
veil2_demo
extension):
alter table demo.projects enable row level security; create policy projects__select on demo.projects for select using ( veil2.i_have_global_priv(25) or veil2.i_have_priv_in_scope(25, 3, corp_id) or veil2.i_have_priv_in_scope(25, 4, org_id) or veil2.i_have_priv_in_scope(25, 5, project_id));
Each of the test functions is checking whether the user has
the privilege, select projects
(privilege
25), to view the current record. Tests are made in global
scope, then corp scope (scope type 3) of the owning corp, then
org scope (scope type 4) of the owning org, and finally in
project scope (scope type 5) of the project itself. Each of
these tests causes a different bitmap in the users session
privileges to be checked.
Although having so many tests performed for each record returned might seem like a significant overhead, in fact it is very small compared with the cost of retrieving the record in the first place and will often be effectively unmeasurable.
Note that you should not rely solely on your VPD (Virtual
Private Database) implementation to limit the number of
records returned from queries to your users. Your application
should be constructing where
-clauses that
only return records that your user is entitled to see. That
is, your Veil2
implementation should act as
a final back-stop safety check and not as a (hidden) part of
your system's functionality.
There are 2 reasons for this:
Relying on Veil2
to filter unsuitable
records means that your where
-clauses
are essentially incomplete, which in turn means that the
RDBMS has not been given all of the information that it
needs in order to best optimize your queries.
Furthermore, if Veil2
is
filtering-out records from the result-set then we are
unnecessarily retrieving those records, and are having
to process them in order to determine their
unsuitability. This would be a large and unnecessary
overhead.
If our queries are running slower than they should
because Veil2
is having to filter-out
unsuitable records, it may be possible for an attacker
to use a timing attack to determine the existence of
records they are not entitled to see.
For the truly security conscious, you may want to modify the privilege testing functions so that attempts to view records to which you have no access, result in logs being recorded. Note though, that such logs would be essentially unusable and would become a significant overhead if significant numbers of queries required results to be filtered.
Every table and view is given its own individual security definition. For tables, they will be much as shown above but will include definitions for insert, update and delete as well as select.
Views are secured in a similar way, but with the privilege testing functions defined in the view itself. Sometimes using views can improve the performance of the privilege tests as they can be incorporated more deeply within the view, meaning that the tests do not have to be executed for every row used by the view's query.
Integrating your systems with Veil2
is not a
trivial task, but it is straightforward. Once you have
understood at least the core
concepts, you can simply follow the steps described in
the Setting Up A Veil2
Virtual Private Database - Overview section.
The following sections describe the major areas that you will
need to address in order to protect an existing system with
Veil2
. This is intended as an introduction
to the process solely in order to give you a feel for what is
required.
The Veil2
-protected database needs to know
which user is accessing it all times. It provides a
session-management protocol for use from web applications.
Your web application will have to provide handling of this
session management protocol. Generally this will be handled
as triggers or hooks into the database connection pool
management.
Veil2
has its own view of what roles should
do and what they should look like. If your own system uses
roles for its existing access control purposes, you will have
to somehow integrate your system's role requirements with
those of Veil2
. You essentially have two
choices:
Bring your roles to Veil2
.
Make your roles the source of Veil2
roles. Use triggers and Foreign Key (FK) constraints to
make and maintain copies of your roles within
Veil2
.
Refactor your system to only use
Veil2
roles.
The advantage of this is that you will end up with a cleaner system, with fewer moving parts. The downside is that your applications will probably require more refactoring.
For every type of security context/scope that you wish to provide,
you will need to provide a link from your existing tables back
to Veil2
. This will be handled with
triggers and FK-constraints. There are examples of how this
may be done in the demos.
All of your users must be associated with
Veil2
accessors (users who access your
database), and any existing credentials must be migrated.
Again this can be handled by triggers and FK-constraints, and
the demos provide examples.
You will be applying access controls to each relation in your system. Some of them may benefit from data denormalizations to improve the performance of the security tests. Typically you may want to add ownership columns to some of your tables so that ownership can be determined without the need for extra joins. If an access control function has to perform extra queries for each row returned, performance is likely to suffer.
You may need to implement a new authentication system for users. Or the built-in bcrypt implementation may be enough.