What is Veil2 And What Does It Provide?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:
this document, especially the setup sections starting
here;comments in the database objects;comments in the Veil2 creation
scripts;the (commented) Veil2 demos.
You should familiarize yourself with at least this document and
the demos before starting out on
your implementation.
How It Works - An OverviewVeil2 works by:
ensuring the database knows which user is connected;
providing a set of contextual privileges to each user;
providing a fast means of testing a user's privileges;
individually securing each accessible relation using
privilege tests.
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.
Knowing The UserVeil2 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.
Contextual Privileges Per User
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.
Fast Privilege Testing
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:
performance;
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.
security.
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.
Individually Securing Each Relation
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.
Refactoring Your Systems To Use Veil2
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.
Web Session Management
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.
Integration of Role HandlingVeil2 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.
Integration of Security Contexts
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.
Integration of Users
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.
Performance Denormalizations
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.
Authentication
You may need to implement a new authentication system for
users. Or the built-in bcrypt
implementation may be enough.