Veil2
is supplied with 2 demo applications,
packaged as extensions. These are:
veil2_demo
;
This provides an extension that demonstrates multiple types of scopes emulating a Software As A Service implementation for multiple corporate clients. The demo is workable but incomplete as not all triggers, rules, etc are provided.
veil2_minimal_demo
;
This aims to be a more complete implementation than
veil2_demo
, but of a much simpler
system. It emulates a social media site that allows users
to make postings that are private, public, or for friends
only. Most access controls are based on the friend
relationships between users.
The Veil2
demo exists to demonstrate a Relational Security
System, show its capabilities, and provide an example of
Veil2
in use. It is a trivial, simple and
incomplete example but provides enough of a foundation to
explore and understand what Veil2
is capable
of.
You are encouraged to dissect, play with, and hack on, the demo. There is much that has not been implemented. Feel free to implement the missing parts.
The demo can be most easily installed as a package from within postgres:
vpd=# create extension veil2_demo cascade; CREATE EXTENSION vpd=#
As an alternative you can run the demo.sql
script which can be found as described here, eg
marc:veil2$ psql -d vpd -f /usr/share/postgresql/12/veil2/demo.sql
This installs the veil2_demo and runs a simple set of tests on
it. Note that in order to run the tests as supplied, the
postgres user demouser
must be given access
as described below
As a final option, if you have cloned the
Veil2
source from github you can use make:
marc:veil2$ make demo
The Veil2
test script
demo/demo_test.sql
accesses the test
database using demo user demouser
, which
it assumes is a trusted user (ie no password is needed).
Note that this is not a good security practice and is done
simply for simplicity and expediency.
If this doesn't work for you, you can create a modified
version of the test script to use a more secure
authentication method that conforms with your site's
policies and requirements. However if you want to simply
try the demo in a test or development environment where
security is not a concern, you can enable trust-based
authentication solely for the demouser
role, solely for your demo database.
To allow demouser
to access the demo
database without needing to provide authentication you will
need to add a “trust” authentication definition
to your pg_hba.conf
file. This line
should look something like this:
local whateverdbnameyoulike demouser trust
This defines that local (ie non-networked) connections to
the whateverdbnameyoulike
database by the
demouser
user are to be trusted. This
line must appear before any
local
definition for
all
databases or all
users.
The diagram above provides a logical view of the
Veil2_demo
database. The
privilege
, role
,
role_privilege
and
role_role
entities are from
Veil2
, while the rest are created
specifically for the application.
Parties
may be persons, corporations or
organizations, where organizations are subdivisions of
corporations. Every party belongs to both an organization and
a corporation.
All users are persons
. Their access rights
are determined by their party_roles
, their
project_assignments
and by their (implied)
personal_context
role (this relationship is
shown in the diagram with an asterisk). These tables and
relationships provide the entire
relational security model for the
veil2_demo
application.
Note that role_role
has a mapping
context
relationship back to
corporation
. This allows each corporation
to manage its own independent set of role
to role
mappings.
The demo provides 3 relational contexts:
The demo uses the parties
table to
identify organizations and persons. There is a tree
structured organizational hierarchy of organizations. The
top-level of each organizational tree is called a corp, or
corporation. The corp context applies to all data owned by
the corp, and by all organizations within it. Many tables
contain a corp_id
field as a data
denormalization to make privilege testing in corp context
easier and faster.
Within each corp there is a tree of organizational parts, or orgs. Privileges applied in org contexts apply in all subordinate orgs. Technically, there is little to distinguish between corp context and a top-level org context, but corp context provides faster privilege checks in this implementation.
See the above descriptions of corp and org contexts for an
introduction to this. Persons work for specific orgs in the
org hierarchy. The company they work for provides their login
context. There are comments in the
demo/demo.sql
file that should help it all
make sense. Take a look at them.
You can create the demo database by simply creating the
extension veil2_demo
.
The extension creates an unprivileged
demouser
(demo user) account, which you
should use to explore the demo. Using a privileged account
will bypass the security which would be pointless and
uninteresting.
This section describes a number of the users defined for the demo. These users are mostly assigned the same roles, but they each see different subsets of data. This should be a pretty convincing demonstration of the power of relational security contexts. Note that although the users are connecting in a number of different login contexts, their role mapping contexts are all the same (the global context).
The code that follows uses a shortcut single query authentication process. You should probably not do this in real life. The overhead of an extra database round-trip will, in most cases, be so small that it will not be noticeable, and the ability to subsequently use the two-step authentication protocol as a means to further improve your authentication security is something that you may not want to lose. For more on this see the authentication protocols appendix.
Alice is a global superuser. She works for Veil Corp (corp 1000). She authenticates using bcrypt. You can connect as Alice using this query:
vpd=# select * vpd-# from veil2.create_session('Alice', 'bcrypt', 4, 1000) c vpd-# cross join veil2.open_connection(c.session_id, 1, 'passwd1');
Because she has global superuser privilege, Alice can see all records.
To see which privileges she has, and how she has arrived at them, run the following query:
vpd=# select * vpd-# from veil2.privilege_assignments vpd-# where accessor_id = 1080;
Bob is a superuser in corp context for Secured Corp. He works for Secured Corp (corp 1010). He authenticates using plaintext. You can connect as Bob using this query:
vpd=# select * vpd-# from veil2.create_session('Bob', 'plaintext', 4, 1010) c vpd-# cross join veil2.open_connection(c.session_id, 1, 'passwd2') o1;
Because he has superuser privilege for Secured Corp, Bob can see all records associated with Secured Corp.
If you compare Bob's
privilege_assignments
with Alice's, you
will see that Bob has all of the same privileges that
Alice has, but assigned in different contexts. This is
why Bob sees fewer records than Alice.
Carol is a superuser in corp context for Protected Corp. She works for Protected Corp (corp 1020). She authenticates using plaintext. You can connect as Carol using this query:
vpd=# select * vpd-# from veil2.create_session('Carol', 'plaintext', 4, 1020) c vpd-# cross join veil2.open_connection(c.session_id, 1, 'passwd3') o1;
Unlike Bob, Carol can see no projects. This is because Protected Corp has no projects.
Eve is a superuser in corp context for both Secure Corp
and Protected Corp. She works for Veil Corp (corp 1000).
She authenticates using plaintext. She has the connect
role for Veil Corp and Secured Corp. This means she
authenticates within Veil Corp but also has the right to
connect to Secured Corp.
You can connect as Eve in Veil Corp using this query:
vpd=# select * vpd-# from veil2.create_session('Eve', 'plaintext', 4, 1000) c vpd-# cross join veil2.open_connection(c.session_id, 1, 'passwd4') o1;
Connected to Veil Corp, Eve can only see her own party record.
To connect Eve in the context of Secured Corp you cab do this:
vpd=# select * vpd-# from veil2.create_session('Eve', 'plaintext', 4, 1000, 4, 1010) c vpd-# cross join veil2.open_connection(c.session_id, 1, 'passwd4') o1;
Eve now sees all parties in corp 1010 (plus herself). She sees all projects and project assignments within corp 1010.
Eve cannot log in to Protected Corp, even though she has
the superuser
role because she has not
been given the connect
role in that
context. If you wish, you can verify this using:
vpd=# select * vpd-# from veil2.create_session('Eve', 'plaintext', 4, 1000, 4, 1020) c vpd-# cross join veil2.open_connection(c.session_id, 1, 'passwd4') o1;
If you give Eve the connect
role for
Protected Corp she will then be able to log in and see all
parties for Protected Corp:
vpd=# select * vpd-# from veil2.create_session('Eve', 'plaintext', 4, 1000, 4, 1020) c vpd-# cross join veil2.open_connection(c.session_id, 1, 'passwd4') o1; session_id | session_token | session_supplemental | success | errmsg ------------+----------------------------------------------+----------------------+---------+---------- 28 | v3AvfSi6KQ0h4zhR0Yoha3XZ6JT8WHgVRUqGQAC+4R8= | | f | AUTHFAIL (1 row) vpd=# select * from demo.parties; party_id | party_type_id | corp_id | org_id | party_name | password ----------+---------------+---------+--------+------------+---------- (0 rows) vpd=# insert vpd-# into veil2.accessor_roles vpd-# (accessor_id, role_id, context_type_id, context_id) vpd-# values (1110, 0, 4, 1020); INSERT 0 1 vpd=# select * from veil2.create_session('Eve', 'plaintext', 4, 1000, 4, 1020) c cross join veil2.open_connection(c.session_id, 1, 'passwd4') o1; session_id | session_token | session_supplemental | success | errmsg ------------+----------------------------------------------+----------------------+---------+-------- 29 | fA3tsbnh0nhC9j6KTXSKOEQCgiZNU7//McP7IcxZPVU= | | t | (1 row) vpd=# select * from demo.parties; party_id | party_type_id | corp_id | org_id | party_name | password ----------+---------------+---------+--------+----------------+-------------- 1020 | 2 | 1020 | 1020 | Protected Corp | 1100 | 1 | 1020 | 1020 | Carol | xxxxxxxxxxxx 1110 | 1 | 1000 | 1000 | Eve | xxxxxxxxxxxx 1180 | 2 | 1020 | 1020 | Department P | 1190 | 2 | 1020 | 1020 | Department P2 | 1200 | 1 | 1020 | 1020 | Paul | xxxxxxxxxxxx 1210 | 1 | 1020 | 1020 | Pippa | xxxxxxxxxxxx 1220 | 1 | 1020 | 1020 | Phil | xxxxxxxxxxxx 1230 | 1 | 1020 | 1020 | Pete | xxxxxxxxxxxx 1240 | 1 | 1020 | 1020 | Pam | xxxxxxxxxxxx (10 rows) vpd=#
What this shows is that Eve can see all users in both corps, but only for one corp at a time, depending on how she has logged in. Pretty cool, eh?
Sue is a superuser in org context for Department S. She works for Department S (org 1050). She authenticates using plaintext. You can connect as Sue using this query:
vpd=# select * from veil2.create_session('Sue', 'plaintext', 4, 1050) c cross join veil2.open_connection(c.session_id, 1, 'passwd5') o1;
Sue sees only those party, party_assignment and project
records for Department S. Of particular interest are the
project_assignment
records for which
Sue gets the appropriate privilege in a scope superior to
the context of the project itself. Try executing these 2
queries:
vpd=# select veil2.i_have_priv_in_scope(26, 5, 1); vpd=# select veil2.i_have_priv_in_superior_scope(26, 5, 1);
Simon is a project manager for project S.1. He works for Department S (org 1050). He authenticates using plaintext. You can connect as Simon using this query:
vpd=# select * vpd-# from veil2.create_session('Simon', 'plaintext', 4, 1050) c vpd-# cross join veil2.open_connection(c.session_id, 1, 'passwd7') o1;
Simon's only assigned roles, are
connect
, assigned in global context in
veil2.accessor_roles
, and
project manager
assigned in the context
of project 1 in
demo.project_assignments
.
This limits what Simon can see to data about project 1,
and data about himself through privileges assigned in
personal context. What is interesting, and cool, is that
Simon is able to see the demo.parties
record for the org that owns project 1:
vpd=# select * from demo.parties; party_id | party_type_id | corp_id | org_id | party_name | password ----------+---------------+---------+--------+--------------+-------------- 1050 | 2 | 1010 | 1030 | Department S | 1140 | 1 | 1010 | 1050 | Simon | xxxxxxxxxxxx (2 rows) vpd=>
His access to the record for Department S arises from the
project viewer
role, which has been
assigned the select orgs
privilege.
This privilege allows the privilege holder to select from
parties that are organizations (not persons), and when
assigned in a lesser context (like project context)
promotes to apply in org scope. Simon has the
project viewer
role indirectly as it
has been assigned to the project
manager
role.
Understanding how a user gains their privileges, and the contexts in which they apply, can be tricky. This is where the developer views, and general postgres expertise come in.
Let's begin by understanding what privileges could be
giving us access to demo.parties
. The
psql \d
command can be used to describe
a relation:
vpd=# \d demo.parties View "demo.parties" Column | Type | Collation | Nullable | Default ---------------+---------+-----------+----------+--------- party_id | integer | | | party_type_id | integer | | | corp_id | integer | | | org_id | integer | | | party_name | text | | | password | text | | |
Hmmm demo.parties
is a view, not a
table. It appears, on inspection using
\d+
, to not have any security
restrictions (calls to Veil2
privilege
testing functions) on it, so let's take a look at the
underlying table: demo.parties_tbl
:
vpd=# \d demo.parties_tbl Table "demo.parties_tbl" Column | Type | Collation | Nullable | Default ---------------+---------+-----------+----------+--------- party_id | integer | | not null | party_type_id | integer | | not null | corp_id | integer | | not null | org_id | integer | | not null | party_name | text | | not null | password | text | | | Indexes: "parties_tbl_pkey" PRIMARY KEY, btree (party_id) Foreign-key constraints: "parties_tbl_corp_id_fkey" FOREIGN KEY (corp_id) REFERENCES demo.parties_tbl(party_id) "parties_tbl_org_id_fkey" FOREIGN KEY (org_id) REFERENCES demo.parties_tbl(party_id) "parties_tbl_party_type_id_fkey" FOREIGN KEY (party_type_id) REFERENCES demo.party_types(party_type_id) Referenced by: TABLE "veil2.accessor_party_map" CONSTRAINT "accessor_party_map__party_fk" FOREIGN KEY (accessor_id) REFERENCES demo.parties_tbl(party_id) TABLE "demo.parties_tbl" CONSTRAINT "parties_tbl_corp_id_fkey" FOREIGN KEY (corp_id) REFERENCES demo.parties_tbl(party_id) TABLE "demo.parties_tbl" CONSTRAINT "parties_tbl_org_id_fkey" FOREIGN KEY (org_id) REFERENCES demo.parties_tbl(party_id) TABLE "demo.project_assignments" CONSTRAINT "project_assignments_party_id_fkey" FOREIGN KEY (party_id) REFERENCES demo.parties_tbl(party_id) TABLE "demo.projects" CONSTRAINT "projects_corp_id_fkey" FOREIGN KEY (corp_id) REFERENCES demo.parties_tbl(party_id) TABLE "demo.projects" CONSTRAINT "projects_org_id_fkey" FOREIGN KEY (org_id) REFERENCES demo.parties_tbl(party_id) TABLE "veil2.scope_links" CONSTRAINT "scope_link__party_fk" FOREIGN KEY (party_id) REFERENCES demo.parties_tbl(party_id) ON DELETE CASCADE Policies: POLICY "parties_tbl__select" FOR SELECT USING ((veil2.i_have_priv_in_scope_or_global(21, 3, corp_id) OR veil2.i_have_priv_in_scope(21, 4, org_id) OR veil2.i_have_priv_in_scope(21, 4, party_id) OR veil2.i_have_personal_priv(21, party_id) OR ((party_type_id = 2) AND veil2.i_have_priv_in_scope(27, 4, party_id)))) Triggers: parties_tbl_ait AFTER INSERT ON demo.parties_tbl FOR EACH ROW EXECUTE FUNCTION demo.parties_tbl_ai() parties_tbl_aut AFTER UPDATE ON demo.parties_tbl FOR EACH ROW WHEN (new.password <> old.password) EXECUTE FUNCTION demo.parties_tbl_au()
The part of interest to us, is the
POLICY
stuff. We see that we can be
given select access via privilege 21 in various scopes, or
27 in org scope for the party. If we look at the
privileges table we can see that these privileges are
select parties
and select
orgs
. Note that to view the contents of the
veil2.privileges
table you will need to
connect to a more privileged user as
demouser
has no select privilege on
that table.
So, does Simon have privileges 21 and/or 27? Let's try
running the privilege testing functions directly. We can
see party
records 1140 and 1050. It
seems likely that we can see record 1140 because its our
record (we are being Simon at this point). And maybe we
can see record 1050 because its the owner or project 1,
for which we have the project manager
role.
vpd=# select veil2.i_have_personal_priv(21, 1140); i_have_personal_priv ---------------------- t (1 row) vpd=# select veil2.i_have_priv_in_scope(27, 4, 1050); i_have_priv_in_scope ---------------------- t (1 row)
Well, that appears to explain it but it required a few assumptions about which scopes might apply. Let's try do it again, without those assumptions.
We can discover the full set of Simon's privileges by
selecting from session_privileges
.
However, this contains bitmaps, which are difficult to
read, so it is better to use the helper view
session_privileges_info
.
vpd=# select * from veil2.session_privileges_info; scope_type_id | scope_id | roles | privs ---------------+----------+------------+--------------- 3 | 1010 | {13,14,16} | {} 2 | 1140 | {2} | {10,13,21,26} 1 | 0 | {0} | {0,20} 5 | 1 | {10,11,12} | {20,25,26,27} 4 | 1050 | {} | {27} (5 rows)
This shows us that we have:
This tells us which privileges we have in which scopes, as well as which roles we have been assigned, directly or indirectly, in which contexts. What it doesn't tell us is which roles gave us which privileges, and whether roles were assigned directly or indirectly.
We can get more detailed information about privilege
assignments from the veil2.privilege_assignments
view:
vpd=# select distinct privilege_id as priv_id, ass_cntxt_type_id as cntxt_type, vpd-# ass_cntxt_id as cntxt_id, scope_type_id as scope_type, vpd-# scope_id, ass_role_id as role_id, vpd-# priv_bearing_role_id as priv_role_id, vpd-# role_id_mapping vpd-# from veil2.privilege_assignments vpd-# where accessor_id = 1140 and privilege_id in (21, 27); priv_id | cntxt_type | cntxt_id | scope_type | scope_id | role_id | priv_role_id | role_id_mapping ---------+------------+----------+------------+----------+---------+--------------+----------------- 21 | 2 | 1140 | 2 | 1140 | 2 | 2 | 2 27 | 5 | 1 | 4 | 1050 | 10 | 11 | 10->11 (2 rows)
This gives us pretty much the whole story. We get
privilege 21 in personal scope from role 2
(personal context
assigned in personal
context. And privilege 27 in org scope for org 1050, from
role 11, mapped from role 10 assigned to us in project
scope of project 1.
You are encouraged to become familiar with the developer views. There is more there than this quick tour shows, and they can provide invaluable information to help you debug and understand your security model.
This aims to be a more complete but less extensive demonstration
of Veil2
's abilities. You are encouraged to
explore this on your own.
The demo can be most easily installed as a package from within postgres:
vpd=# create extension veil2_minimal_demo cascade; CREATE EXTENSION vpd=#
As an alternative you can run the minimal_demo.sql
script which can be found as described here, eg
marc:veil2$ psql -d vpd -f /usr/share/postgresql/12/veil2/minimal_demo.sql
This installs the veil2_demo and runs a simple set of tests on
it. Note that in order to run the tests as supplied, the
postgres user demouser
must be given access
as described below
As a final option, if you have cloned the
Veil2
source from github you can use make:
marc:veil2$ make mindemo