Chapter 5. 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:

You should familiarize yourself with at least this document and the demos before starting out on your implementation.

5.1. How It Works - An Overview

Veil2 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.

5.1.1. Knowing The User

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.

5.1.2. 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.

5.1.3. 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:

  1. 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.

  2. 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.

5.1.4. 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.

5.2. 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.

5.2.1. 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.

5.2.2. Integration of Role Handling

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.

5.2.3. 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.

5.2.4. 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.

5.2.5. 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.

5.2.6. Authentication

You may need to implement a new authentication system for users. Or the built-in bcrypt implementation may be enough.