/* ---------- * veil_template * * Template for bringing Veil2 security to your database * * Copyright (c) 2020 Marc Munro * Author: Marc Munro * License: GPL V3 * * ---------- */ -- STEP 0 -- Define your database objects. These will intially be entirely -- independent of Veil2 unless you choose to use some of the Veil2 -- tables, such as roles, directly. -- STEP 1: Install Veil2 -- If you haven't installed the extension on your server, and have the -- pgxn client installed: -- $ pgxn install veil2 -- -- Install the Veil2 extension in your database. We use cascade to -- ensure that dependencies are also installed. create extension if not exists veil2 cascade; -- STEP 2: -- Define scopes -- 2.1 Create new scopes by inserting records into veil2.scope_types. -- /* insert into veil2.scope_types (scope_type_id, scope_type_name, description) values (,,), (,,), (,,); */ -- 2.2 Define your role_mapping context. If you intend to map all -- roles in global_context then you don't need to do anything. If you -- are not sure about this, just skip it. If you need it, it will -- become obvious later /* update veil2.system_parameters set parameter_value = where parameter_name = 'mapping context target scope type'; */ -- STEP 3: -- Authentication stuff. -- Create new authentication types if you need them. -- 3.1 Create new authentication methods. /* insert into veil2.authentication_types (shortname, enabled, description, authent_fn, supplemental_fn) values (,,,,), (,,,,), (,,,,); create or replace function veil2.authenticate_xxxxxx() accessor_id integer, token text) returns bool as $$ $$ language 'whatever' security definer stable leakproof; -- 3.2 Associate accessors and authentication contexts -- This associates each accessor with an authentication context. -- Typically each authentication context allows their own set of -- usernames, so that Bob at ProtectedCorp is a different person from -- Bob at SecuredCorp. /* create or replace view veil2.my_accessor_contexts ( accessor_id, context_type_id, context_id ) as select ,, from some-of-my-tables-and-views; */ -- 3.3 get_accessor() -- Create a my_get_accessor() function that will take a username and -- context, and return an accessor_id /* create or replace function veil2.my_get_accessor( username in text, context_type_id in integer, context_id in integer) returns integer as $$ $$ language 'whatever' security definer stable leakproof; */ -- 3.4 Session Management Customizations -- You may need to extend the set of Veil2 session management -- functions, or provide mechanisms to invoke them from SQL if your -- application is not flexible enough to use the built-in mechanisms -- as they stand. What this will look like will depend on what you -- are trying to achieve. It may be additional functions for managing -- extra round-trips between the user and the database for some -- imagined exotic authentication system, or it may be the tables and -- triggers so that authentication can be performed solely using -- simple insert and select statements. /* ?? */ -- STEP 4: -- Link Veil2 accessors to your database's users. -- 4.1 Create a mapping table -- Note that you may have multiple users tables and so you may need -- multiple FK fields back to your database. If so, you may want to -- add a check constraint to ensure that only 1 of them is not null. /* create table veil2.accessor_users_map ( accessor_id integer not null, . . . ); alter table veil2.accessor_users_map add constraint accessor_users__pk primary key (accessor_id); alter table veil2.accessor_users_map add constraint accessor__accessor_fk foreign key (accessor_id) references veil2.accessors(accessor_id); -- You should probably index the other FK columns create index accessor__user_idx on veil2.accessor_users_map(??); -- And you must create FKs alter table veil2.accessor_users_map add constraint accessor__user_fk foreign key (??) references ??.??(??); */ -- 4.2 Copy Existing User Records -- /* -- We will probably want to create new accessor_ids using a sequence... create sequence veil2.accessor_id_seq; -- Do this in plpgsql as we need to insert into 2 target tables for -- each source record. do $$ declare rec record; begin for rec in select , nextval('veil2.accessor_id_seq') as accessor_id from loop insert into veil2.accessors (accessor_id, username) values (rec.accessor_id, rec.); insert into veil2.accessor_users_map (accessor_id, ) values (rec.accessor_id, rec.); end loop; end; $$ language plpgsql; */ -- 4.3 Copy existing authentication records -- This is going to be tricky as it is unlikely that the -- authentication tokens in your database are going to match those -- exepected by Veil2. See the Veil2 documentation for Step 4 for -- more information. /* insert into veil2.authentication_details (accessor_id, authentication_type, authent_token) select a.accessor_id, 'bcrypt', u. from u inner join veil2.accessors a on a. = u.; */ -- 4.4 Create Referential Integrity Triggers -- On insert and on delete triggers are needed as a minimum. If -- updates are allowed to change the user id fields, then we will also -- need to deal with updates. Far better though to simply have an -- on-update trigger that fails if such an attempt is made. /* create or replace function () returns trigger as $$ declare _accessor_id integer; begin -- A new record has been created. Create the -- appropriate records in veil2 _accesor_id := nextval('veil2.accessor_id_seq'); insert into veil2.accessors (accessor_id, username) values (_accessor_id, new.); insert into veil2.accessor_users_map (accessor_id, ) values (_accessor_id, rec.); -- Note that you may also need to deal with authentication details -- here if the current token is created in the table. return new; end; $$ language plpgsql security definer volatile; create trigger _bi_trg before insert on for each row execute procedure (); create or replace function () returns trigger as $$ declare _accessor_id integer; begin delete from veil2.accessor_users_map where = old. returning accessor_id into _accessor_id; delete from veil2.authentication_details where accessor_id = _accessor_id; delete from veil2.accessor_roles where accessor_id = _accessor_id; delete from veil2.accessor_privileges_cache where accessor_id = _accessor_id; delete from veil2.accessors where accessor_id = _accessor_id; return old; end; $$ language plpgsql security definer volatile; create trigger _bd_trg before delete on for each row execute procedure (); -- Also do something for updates. See comments above */ -- 4.5 Authentication Token Handling Triggers -- Ideally, we will stop recording authentication tokens in the old -- database tables and instead record them in -- veil2.authentication_details. /* -- This assumes that the authentication details in our protected -- database are not stored in the same table as the basic user data. -- If it is then this functionaloty must be handled in the referential -- integrity triggers - see above. create or replace function () returns trigger as $$ begin insert into veil2.authentication_details (accessor_id, authentication_type, authent_token) select m.accessor_id, 'whatever', from accessor_users_map where = new.; -- Hide the authentication token from the old table. new. := 'xxxxxxxxxxx'; return new; end; $$ language plpgsql security definer volatile; -- Check how we are doing. select * from veil2.implementation_status(); create trigger _bi_trg before insert on for each row execute procedure (); create or replace function () returns trigger as $$ begin update veil2.authentication_details set authent_token = where accessor_id = ( select accessor_id from accessor_users_map where = new.); -- Hide the authentication token from the old table. new. := 'xxxxxxxxxxx'; return new; end; $$ language plpgsql security definer volatile; create trigger _bu_trg before update on for each row execute procedure (); -- Deletion trigger should not be needed, as deleting the user will -- generally do the job. However, YMMV. */ -- STEP 5: Link your scopes -- 5.1 Extend the veil2.scopes table to link to the tables that define -- your scopes. /* -- Extend veil2.scopes using inheritence create table veil2.scope_links ( , ) inherits (veil2.scopes); -- Define Keys for the extended table - these do not get created -- automatically. alter table veil2.scope_links add constraint scope_link__pk primary key(scope_type_id, scope_id); alter table veil2.scope_links add constraint scope_link__type_fk foreign key(scope_type_id) references veil2.scope_types; -- Define FKs for each alter table veil2.scope_links add constraint scope_link___fk foreign key () references () on update cascade on delete cascade; -- You may also want to define a check constraint to ensure that only -- one of the columns is null. We might also check that -- the scope_type_id (an inherited column) is apprpriate for the -- that is not null. */ -- 5.2 Create on-insert triggers to handle new scopes /* -- Add a trigger for *each* scope type. create or replace function () returns trigger as $$ begin insert into veil2.scope_types (scope_type_id, scope_id, ) values (, , ; return new; end; $$ language plpgsql security definer volatile; create trigger _bi_trg before insert on for each row execute procedure (); */ -- 5.3 Create on update triggers /* -- Add a trigger for *each* scope type. create or replace function () returns trigger as $$ begin if new. != old. then raise exception 'NOCANDO: You cannot change the PK field of '; end if; return new; end; $$ language plpgsql security definer volatile; create trigger _bu_trg before update on for each row execute procedure (); */ -- 5.4 Copy existing scope data into the links table. /* -- Copy existing scopes for *each* scope type. insert into veil2.scope_links (scope_type_id, scope_id, ) select , , from where ; */ -- 5.5 Update the all_accessor_roles view /* create or replace view veil2.my_all_accessor_roles ( accessor_id, role_id, context_type_id, context_id ) as select accessor_id, role_id, context_type_id, context_id from veil2.accessor_roles union all select , role_id, , from union all select , role_id, , from ; */ -- 5.6 Create triggers on updates to accessor roles -- There is no need to do this on the veil2.accessor_roles table as -- this is already done. /* -- For each table providing data to my_all_accessor_roles... create trigger _biud_trg before insert or update or delete on for each row execute procedure veil2.clear_accessor_privs_cache_entry(); -- You should probably ensure that truncation of the above table does -- not happen. If, for some reason you need to allow it, create a -- trigger for truncation that calls veil2.clear_accessor_privs_cache(); */ -- STEP 6: Define Scope Hierarchy /* -- 6.1 -- Define the my_superior_scopes view create or replace view veil2.my_superior_scopes ( scope_type_id, scope_id, superior_scope_type_id, superior_scope_id ) as select , , from where union all select , , from where union all . . .'; -- 6.2 -- Refresh matviews if scope hierarchy changes -- Create triggers on modification of the scope hierarchy create trigger privileges__aiud after insert or update or delete or truncate on for each statement execute procedure veil2.refresh_scopes_matviews(); -- Alternatively, if you need your trigger function to determine -- whether a change is significant enough to warrant clearing the -- matviews, you can call the function veil2.refresh_all_matviews() -- from your custom trigger function. */ -- STEP 7: -- Define privileges. Note that priv_ids below 20 are reserved for Veil2 -- objects /* insert into veil2.privileges (privilege_id, privilege_name, promotion_scope_type_id, description) values (20, , , (21, , , (22, , , . . .; */ -- STEP 8: -- Define/integrate roles -- 8.1 Integrate veil2 roles with your system's equivalent, if it has -- one. /* -- We will assume that your existing objects are going to use veil2's -- roles. If not, you will have to figure all of this out for -- yourself. -- For each protected table that needs to reference veil2.roles: alter table add constraint __role_fk foreign key (role_id) references veil2.roles(role_id); */ -- 8.2 Create new roles and mappings -- /* insert into veil2.roles (role_id, role_type_id, role_name, implicit, immutable, description) values (,,,,,), (,,,,,), ...; insert into veil2.role_privileges (role_id, privilege_id) values (,) (,), ...; insert into veil2.role_roles (primary_role_id, assigned_role_id, context_type_id, context_id) values (,,,), (,,,), ...; */ -- STEP 9: -- Secure Tables /* -- For each table alter table
enable row level security; -- For each operation (insert, update, select, delete) that you need -- to secure: create policy
_select on
for select using veil2.i_have_xxxx(,??); revoke all on
from public; grant on table_name to ; */ -- STEP 10: -- Secure Views /* -- For each view create or replace view view_name as select .... where veil2.i_have_xxxx(,??); -- Create appropriate instead-of triggers if the view is to be -- updatable. */ -- Check how we are doing. select * from veil2.implementation_status(); create trigger _bi_trg before insert on for each row execute procedure (); -- Step 11: -- Assign roles to accessors -- ...