Exploring The <literal>Veil2</literal> Demos 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 Sofware 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 <literal>veil2_demo</literal> Extension 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. Installation 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 Allowing Access For The Demo User 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 Security Contexts The demo provides 3 relational contexts: corp context; org context; project context Corp Context 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. Org Context 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. Project Context Projects have their own contexts. Assignment to a project uses a different mechanism from the more explicit role assignments made in the other contexts. The Organizational Hierarchy 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. Exploring It You can install the demo to a database that already had Veil2 objects installed by running the file demo/demo.sql against it. To create a clean veil2-enabled database you could use make. The following command, run from the Veil2 root directory would create a new database called vpd with the demo installed: $ make db; psql -d vpd -f demo/demo.sql The setup script 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. Users 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 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 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 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 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 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 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: roles 10, 11, 12 and privileges 20, 25, 26 and 27 in project scope for project 1; role 0 and privileges 0 and 20 in global scope; privilege 27 in org scope for org 1050; role 2 and privileges 10, 13, 21 and 26 in personal scope for accessor 1140 (Simon); roles 13, 14, 16 (with no privileges) in org scope for Secured Corp. 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. The <literal>veil2_minimal_demo</literal> Extension This aims to be a more complete but less extensive demonstration of Veil2's abilities. You are encouraged to explore this on your own. Installation 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 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