If it's a useful named resource and people accidentally delete it e.g. with TF, they can't get it back. This doesn't affect TF's ability to manage it, just delete.
4006 lines
131 KiB
Go
4006 lines
131 KiB
Go
// Code generated by "make migrations"; DO NOT EDIT.
|
|
package migrations
|
|
|
|
var postgresMigrations = map[string]*fakeFile{
|
|
"migrations": {
|
|
name: "migrations",
|
|
},
|
|
"migrations/01_domain_types.down.sql": {
|
|
name: "01_domain_types.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop domain wt_timestamp;
|
|
drop domain wt_public_id;
|
|
drop domain wt_private_id;
|
|
drop domain wt_scope_id;
|
|
drop domain wt_user_id;
|
|
drop domain wt_version;
|
|
|
|
drop function default_create_time;
|
|
drop function update_time_column;
|
|
drop function update_version_column;
|
|
drop function immutable_columns;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/01_domain_types.up.sql": {
|
|
name: "01_domain_types.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
create domain wt_public_id as text
|
|
check(
|
|
length(trim(value)) > 10
|
|
);
|
|
comment on domain wt_public_id is
|
|
'Random ID generated with github.com/hashicorp/vault/sdk/helper/base62';
|
|
|
|
create domain wt_private_id as text
|
|
not null
|
|
check(
|
|
length(trim(value)) > 10
|
|
);
|
|
comment on domain wt_private_id is
|
|
'Random ID generated with github.com/hashicorp/vault/sdk/helper/base62';
|
|
|
|
create domain wt_scope_id as text
|
|
check(
|
|
length(trim(value)) > 10 or value = 'global'
|
|
);
|
|
comment on domain wt_scope_id is
|
|
'"global" or random ID generated with github.com/hashicorp/vault/sdk/helper/base62';
|
|
|
|
create domain wt_user_id as text
|
|
not null
|
|
check(
|
|
length(trim(value)) > 10 or value = 'u_anon' or value = 'u_auth' or value = 'u_recovery'
|
|
);
|
|
comment on domain wt_scope_id is
|
|
'"u_anon", "u_auth", or random ID generated with github.com/hashicorp/vault/sdk/helper/base62';
|
|
|
|
create domain wt_role_id as text
|
|
not null
|
|
check(
|
|
length(trim(value)) > 10 or value = 'r_default'
|
|
);
|
|
comment on domain wt_scope_id is
|
|
'"r_default", or random ID generated with github.com/hashicorp/vault/sdk/helper/base62';
|
|
|
|
create domain wt_timestamp as
|
|
timestamp with time zone
|
|
default current_timestamp;
|
|
comment on domain wt_timestamp is
|
|
'Standard timestamp for all create_time and update_time columns';
|
|
|
|
create or replace function
|
|
update_time_column()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if row(new.*) is distinct from row(old.*) then
|
|
new.update_time = now();
|
|
return new;
|
|
else
|
|
return old;
|
|
end if;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
comment on function
|
|
update_time_column()
|
|
is
|
|
'function used in before update triggers to properly set update_time columns';
|
|
|
|
create or replace function
|
|
default_create_time()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if new.create_time is distinct from now() then
|
|
raise warning 'create_time cannot be set to %', new.create_time;
|
|
new.create_time = now();
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
comment on function
|
|
default_create_time()
|
|
is
|
|
'function used in before insert triggers to set create_time column to now';
|
|
|
|
|
|
create domain wt_version as bigint
|
|
default 1
|
|
not null
|
|
check(
|
|
value > 0
|
|
);
|
|
comment on domain wt_version is
|
|
'standard column for row version';
|
|
|
|
-- update_version_column() will increment the version column whenever row data
|
|
-- is updated and should only be used in an update after trigger. This function
|
|
-- will overwrite any explicit updates to the version column. The function
|
|
-- accepts an optional parameter of 'private_id' for the tables primary key.
|
|
create or replace function
|
|
update_version_column()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if pg_trigger_depth() = 1 then
|
|
if row(new.*) is distinct from row(old.*) then
|
|
if tg_nargs = 0 then
|
|
execute format('update %I set version = $1 where public_id = $2', tg_relid::regclass) using old.version+1, new.public_id;
|
|
new.version = old.version + 1;
|
|
return new;
|
|
end if;
|
|
if tg_argv[0] = 'private_id' then
|
|
execute format('update %I set version = $1 where private_id = $2', tg_relid::regclass) using old.version+1, new.private_id;
|
|
new.version = old.version + 1;
|
|
return new;
|
|
end if;
|
|
end if;
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
comment on function
|
|
update_version_column()
|
|
is
|
|
'function used in after update triggers to properly set version columns';
|
|
|
|
-- immutable_columns() will make the column names immutable which are passed as
|
|
-- parameters when the trigger is created. It raises error code 23601 which is a
|
|
-- class 23 integrity constraint violation: immutable column
|
|
create or replace function
|
|
immutable_columns()
|
|
returns trigger
|
|
as $$
|
|
declare
|
|
col_name text;
|
|
new_value text;
|
|
old_value text;
|
|
begin
|
|
foreach col_name in array tg_argv loop
|
|
execute format('SELECT $1.%I', col_name) into new_value using new;
|
|
execute format('SELECT $1.%I', col_name) into old_value using old;
|
|
if new_value is distinct from old_value then
|
|
raise exception 'immutable column: %.%', tg_table_name, col_name using
|
|
errcode = '23601',
|
|
schema = tg_table_schema,
|
|
table = tg_table_name,
|
|
column = col_name;
|
|
end if;
|
|
end loop;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
comment on function
|
|
immutable_columns()
|
|
is
|
|
'function used in before update triggers to make columns immutable';
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/02_oplog.down.sql": {
|
|
name: "02_oplog.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop table oplog_metadata cascade;
|
|
drop table oplog_ticket cascade;
|
|
drop table oplog_entry cascade;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/02_oplog.up.sql": {
|
|
name: "02_oplog.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
-- TODO (jimlambrt 7/2020) remove update_time
|
|
create table if not exists oplog_entry (
|
|
id bigint generated always as identity primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
version text not null,
|
|
aggregate_name text not null,
|
|
"data" bytea not null
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on oplog_entry
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on oplog_entry
|
|
for each row execute procedure default_create_time();
|
|
|
|
-- oplog_entry is immutable.
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on oplog_entry
|
|
for each row execute procedure immutable_columns('id','update_time','create_time','version','aggregate_name', 'data');
|
|
|
|
create table if not exists oplog_ticket (
|
|
id bigint generated always as identity primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
"name" text not null unique,
|
|
"version" bigint not null
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on oplog_ticket
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on oplog_ticket
|
|
for each row execute procedure default_create_time();
|
|
|
|
-- oplog_ticket: only allow updates to: version and update_time
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on oplog_ticket
|
|
for each row execute procedure immutable_columns('id','create_time','name');
|
|
|
|
-- TODO (jimlambrt 7/2020) remove update_time
|
|
create table if not exists oplog_metadata (
|
|
id bigint generated always as identity primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
entry_id bigint not null references oplog_entry(id) on delete cascade on update cascade,
|
|
"key" text not null,
|
|
value text null
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on oplog_metadata
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on oplog_metadata
|
|
for each row execute procedure default_create_time();
|
|
|
|
-- oplog_metadata is immutable
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on oplog_metadata
|
|
for each row execute procedure immutable_columns('id','create_time','update_time','entry_id','key','value');
|
|
|
|
create index if not exists idx_oplog_metatadata_key on oplog_metadata(key);
|
|
|
|
create index if not exists idx_oplog_metatadata_value on oplog_metadata(value);
|
|
|
|
insert into oplog_ticket (name, version)
|
|
values
|
|
('auth_token', 1),
|
|
('default', 1),
|
|
('iam_scope', 1),
|
|
('iam_user', 1),
|
|
('iam_group', 1),
|
|
('iam_group_member', 1),
|
|
('iam_role', 1),
|
|
('iam_role_grant', 1),
|
|
('iam_group_role', 1),
|
|
('iam_user_role', 1),
|
|
('db_test_user', 1),
|
|
('db_test_car', 1),
|
|
('db_test_rental', 1),
|
|
('db_test_scooter', 1),
|
|
('auth_account', 1),
|
|
('iam_principal_role', 1);
|
|
|
|
|
|
commit;
|
|
|
|
|
|
`),
|
|
},
|
|
"migrations/03_db.down.sql": {
|
|
name: "03_db.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop table db_test_rental cascade;
|
|
drop table db_test_car cascade;
|
|
drop table db_test_user cascade;
|
|
drop table db_test_scooter cascade;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/03_db.up.sql": {
|
|
name: "03_db.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
-- create test tables used in the unit tests for the internal/db package
|
|
-- these tables (db_test_user, db_test_car, db_test_rental, db_test_scooter) are
|
|
-- not part of the boundary domain model... they are simply used for testing
|
|
-- the internal/db package
|
|
create table if not exists db_test_user (
|
|
id bigint generated always as identity primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
public_id text not null unique,
|
|
name text unique,
|
|
phone_number text,
|
|
email text,
|
|
version wt_version
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on db_test_user
|
|
for each row execute procedure update_time_column();
|
|
|
|
-- define the immutable fields for db_test_user
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on db_test_user
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on db_test_user
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
update_version_column
|
|
after update on db_test_user
|
|
for each row execute procedure update_version_column();
|
|
|
|
create table if not exists db_test_car (
|
|
id bigint generated always as identity primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
public_id text not null unique,
|
|
name text unique,
|
|
model text,
|
|
mpg smallint
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on db_test_car
|
|
for each row execute procedure update_time_column();
|
|
|
|
-- define the immutable fields for db_test_car
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on db_test_car
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on db_test_car
|
|
for each row execute procedure default_create_time();
|
|
|
|
create table if not exists db_test_rental (
|
|
id bigint generated always as identity primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
public_id text not null unique,
|
|
name text unique,
|
|
user_id bigint not null references db_test_user(id),
|
|
car_id bigint not null references db_test_car(id)
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on db_test_rental
|
|
for each row execute procedure update_time_column();
|
|
|
|
-- define the immutable fields for db_test_rental
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on db_test_rental
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on db_test_rental
|
|
for each row execute procedure default_create_time();
|
|
|
|
|
|
create table if not exists db_test_scooter (
|
|
id bigint generated always as identity primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
private_id text not null unique,
|
|
name text unique,
|
|
model text,
|
|
mpg smallint
|
|
);
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on db_test_scooter
|
|
for each row execute procedure update_time_column();
|
|
|
|
|
|
-- define the immutable fields for db_test_scooter
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on db_test_scooter
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on db_test_scooter
|
|
for each row execute procedure default_create_time();
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/06_iam.down.sql": {
|
|
name: "06_iam.down.sql",
|
|
bytes: []byte(`
|
|
BEGIN;
|
|
|
|
drop table iam_group cascade;
|
|
drop table iam_user cascade;
|
|
drop table iam_scope_project cascade;
|
|
drop table iam_scope_org cascade;
|
|
drop table iam_scope_global cascade;
|
|
drop table iam_scope cascade;
|
|
drop table iam_scope_type_enm cascade;
|
|
drop table iam_role cascade;
|
|
drop view iam_principal_role cascade;
|
|
drop table iam_group_role cascade;
|
|
drop table iam_user_role cascade;
|
|
drop table iam_group_member_user cascade;
|
|
drop view iam_group_member cascade;
|
|
drop table iam_role_grant cascade;
|
|
|
|
drop function iam_sub_names cascade;
|
|
drop function iam_immutable_scope_type_func cascade;
|
|
drop function iam_sub_scopes_func cascade;
|
|
drop function iam_immutable_role_principal cascade;
|
|
drop function iam_user_role_scope_check cascade;
|
|
drop function iam_group_role_scope_check cascade;
|
|
drop function iam_group_member_scope_check cascade;
|
|
drop function iam_immutable_group_member cascade;
|
|
drop function get_scoped_member_id cascade;
|
|
drop function grant_scope_id_valid cascade;
|
|
drop function disallow_global_scope_deletion cascade;
|
|
drop function user_scope_id_valid cascade;
|
|
drop function iam_immutable_role_grant cascade;
|
|
drop function disallow_iam_predefined_user_deletion cascade;
|
|
drop function recovery_user_not_allowed cascade;
|
|
|
|
COMMIT;
|
|
|
|
`),
|
|
},
|
|
"migrations/06_iam.up.sql": {
|
|
name: "06_iam.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
create table iam_scope_type_enm (
|
|
string text not null primary key check(string in ('unknown', 'global', 'org', 'project'))
|
|
);
|
|
|
|
insert into iam_scope_type_enm (string)
|
|
values
|
|
('unknown'),
|
|
('global'),
|
|
('org'),
|
|
('project');
|
|
|
|
-- define the immutable fields of iam_scope_type_enm
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on iam_scope_type_enm
|
|
for each row execute procedure immutable_columns('string');
|
|
|
|
create table iam_scope (
|
|
public_id wt_scope_id primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
name text,
|
|
type text not null references iam_scope_type_enm(string) check(
|
|
(
|
|
type = 'global'
|
|
and parent_id is null
|
|
)
|
|
or (
|
|
type = 'org'
|
|
and parent_id = 'global'
|
|
)
|
|
or (
|
|
type = 'project'
|
|
and parent_id is not null
|
|
and parent_id != 'global'
|
|
)
|
|
),
|
|
description text,
|
|
parent_id text references iam_scope(public_id) on delete cascade on update cascade,
|
|
|
|
-- version allows optimistic locking of the role when modifying the role
|
|
-- itself and when modifying dependent items like principal roles.
|
|
version wt_version
|
|
);
|
|
|
|
create table iam_scope_global (
|
|
scope_id wt_scope_id primary key
|
|
references iam_scope(public_id)
|
|
on delete cascade
|
|
on update cascade
|
|
check(
|
|
scope_id = 'global'
|
|
),
|
|
name text unique
|
|
);
|
|
|
|
create table iam_scope_org (
|
|
scope_id wt_scope_id primary key
|
|
references iam_scope(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
parent_id wt_scope_id
|
|
not null
|
|
references iam_scope_global(scope_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
name text,
|
|
unique (parent_id, name)
|
|
);
|
|
|
|
create table iam_scope_project (
|
|
scope_id wt_scope_id
|
|
not null
|
|
references iam_scope(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
parent_id wt_public_id not null references iam_scope_org(scope_id) on delete cascade on update cascade,
|
|
name text,
|
|
unique(parent_id, name),
|
|
unique(scope_id),
|
|
primary key(scope_id, parent_id)
|
|
);
|
|
|
|
create or replace function
|
|
iam_sub_scopes_func()
|
|
returns trigger
|
|
as $$
|
|
declare parent_type int;
|
|
begin
|
|
if new.type = 'global' then
|
|
insert into iam_scope_global (scope_id, name)
|
|
values
|
|
(new.public_id, new.name);
|
|
return new;
|
|
end if;
|
|
if new.type = 'org' then
|
|
insert into iam_scope_org (scope_id, parent_id, name)
|
|
values
|
|
(new.public_id, new.parent_id, new.name);
|
|
return new;
|
|
end if;
|
|
if new.type = 'project' then
|
|
insert into iam_scope_project (scope_id, parent_id, name)
|
|
values
|
|
(new.public_id, new.parent_id, new.name);
|
|
return new;
|
|
end if;
|
|
raise exception 'unknown scope type';
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger
|
|
iam_scope_insert
|
|
after
|
|
insert on iam_scope
|
|
for each row execute procedure iam_sub_scopes_func();
|
|
|
|
create or replace function
|
|
disallow_global_scope_deletion()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if old.type = 'global' then
|
|
raise exception 'deletion of global scope not allowed';
|
|
end if;
|
|
return old;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger
|
|
iam_scope_disallow_global_deletion
|
|
before
|
|
delete on iam_scope
|
|
for each row execute procedure disallow_global_scope_deletion();
|
|
|
|
create trigger
|
|
update_time_column
|
|
before update on iam_scope
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on iam_scope
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
update_version_column
|
|
after update on iam_scope
|
|
for each row execute procedure update_version_column();
|
|
|
|
-- define the immutable fields for iam_scope
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on iam_scope
|
|
for each row execute procedure immutable_columns('public_id', 'create_time', 'type', 'parent_id');
|
|
|
|
-- define the immutable fields of iam_scope_global
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on iam_scope_global
|
|
for each row execute procedure immutable_columns('scope_id');
|
|
|
|
-- define the immutable fields of iam_scope_org
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on iam_scope_org
|
|
for each row execute procedure immutable_columns('scope_id');
|
|
|
|
-- define the immutable fields of iam_scope_project
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on iam_scope_project
|
|
for each row execute procedure immutable_columns('scope_id');
|
|
|
|
|
|
-- iam_sub_names will allow us to enforce the different name constraints for
|
|
-- orgs and projects via a before update trigger on the iam_scope
|
|
-- table.
|
|
create or replace function
|
|
iam_sub_names()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if new.name != old.name then
|
|
if new.type = 'global' then
|
|
update iam_scope_global set name = new.name where scope_id = old.public_id;
|
|
return new;
|
|
end if;
|
|
if new.type = 'org' then
|
|
update iam_scope_org set name = new.name where scope_id = old.public_id;
|
|
return new;
|
|
end if;
|
|
if new.type = 'project' then
|
|
update iam_scope_project set name = new.name where scope_id = old.public_id;
|
|
return new;
|
|
end if;
|
|
raise exception 'unknown scope type';
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger
|
|
iam_sub_names
|
|
before
|
|
update on iam_scope
|
|
for each row execute procedure iam_sub_names();
|
|
|
|
insert into iam_scope (public_id, name, type, description)
|
|
values ('global', 'global', 'global', 'Global Scope');
|
|
|
|
|
|
create table iam_user (
|
|
public_id wt_user_id
|
|
primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
name text,
|
|
description text,
|
|
scope_id wt_scope_id
|
|
not null
|
|
references iam_scope(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
unique(name, scope_id),
|
|
version wt_version,
|
|
|
|
-- The order of columns is important for performance. See:
|
|
-- https://dba.stackexchange.com/questions/58970/enforcing-constraints-two-tables-away/58972#58972
|
|
-- https://dba.stackexchange.com/questions/27481/is-a-composite-index-also-good-for-queries-on-the-first-field
|
|
unique(scope_id, public_id)
|
|
);
|
|
|
|
create or replace function
|
|
user_scope_id_valid()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
perform from iam_scope where public_id = new.scope_id and type in ('global', 'org');
|
|
if not found then
|
|
raise exception 'invalid scope type for user creation';
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create or replace function
|
|
grant_scope_id_valid()
|
|
returns trigger
|
|
as $$
|
|
declare parent_scope_id text;
|
|
declare role_scope_type text;
|
|
begin
|
|
-- There is a not-null constraint so ensure that if the value passed in is
|
|
-- empty we simply set to the scope ID
|
|
if new.grant_scope_id = '' or new.grant_scope_id is null then
|
|
new.grant_scope_id = new.scope_id;
|
|
end if;
|
|
-- If the scopes match, it's allowed
|
|
if new.grant_scope_id = new.scope_id then
|
|
return new;
|
|
end if;
|
|
-- Fetch the type of scope
|
|
select isc.type from iam_scope isc where isc.public_id = new.scope_id into role_scope_type;
|
|
-- Always allowed
|
|
if role_scope_type = 'global' then
|
|
return new;
|
|
end if;
|
|
-- Never allowed; the case where it's set to the same scope ID as the project
|
|
-- itself is covered above
|
|
if role_scope_type = 'project' then
|
|
raise exception 'invalid to set grant_scope_id to non-same scope_id when role scope type is project';
|
|
end if;
|
|
if role_scope_type = 'org' then
|
|
-- Look up the parent scope ID for the scope ID given
|
|
select isc.parent_id from iam_scope isc where isc.public_id = new.grant_scope_id into parent_scope_id;
|
|
-- Allow iff the grant scope ID's parent matches the role's scope ID; that
|
|
-- is, match if the role belongs to a direct child scope of this
|
|
-- org
|
|
if parent_scope_id = new.scope_id then
|
|
return new;
|
|
end if;
|
|
raise exception 'grant_scope_id is not a child project of the role scope';
|
|
end if;
|
|
raise exception 'unknown scope type';
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create or replace function
|
|
disallow_iam_predefined_user_deletion()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if old.public_id = 'u_anon' then
|
|
raise exception 'deletion of anonymous user not allowed';
|
|
end if;
|
|
if old.public_id = 'u_auth' then
|
|
raise exception 'deletion of authenticated user not allowed';
|
|
end if;
|
|
if old.public_id = 'u_recovery' then
|
|
raise exception 'deletion of recovery user not allowed';
|
|
end if;
|
|
return old;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger
|
|
update_version_column
|
|
after update on iam_user
|
|
for each row execute procedure update_version_column();
|
|
|
|
create trigger
|
|
ensure_user_scope_id_valid
|
|
before
|
|
insert or update on iam_user
|
|
for each row execute procedure user_scope_id_valid();
|
|
|
|
create trigger
|
|
update_time_column
|
|
before update on iam_user
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on iam_user
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
iam_user_disallow_predefined_user_deletion
|
|
before
|
|
delete on iam_user
|
|
for each row execute procedure disallow_iam_predefined_user_deletion();
|
|
|
|
-- TODO: Do we want to disallow changing the name or description?
|
|
insert into iam_user (public_id, name, description, scope_id)
|
|
values ('u_anon', 'anonymous', 'The anonymous user matches any request, whether authenticated or not', 'global');
|
|
|
|
insert into iam_user (public_id, name, description, scope_id)
|
|
values ('u_auth', 'authenticated', 'The authenticated user matches any user that has a valid token', 'global');
|
|
|
|
insert into iam_user (public_id, name, description, scope_id)
|
|
values ('u_recovery', 'recovery', 'The recovery user is used for any request that was performed with the recovery KMS workflow', 'global');
|
|
|
|
-- define the immutable fields for iam_user
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on iam_user
|
|
for each row execute procedure immutable_columns('public_id', 'create_time', 'scope_id');
|
|
|
|
create table iam_role (
|
|
public_id wt_role_id primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
name text,
|
|
description text,
|
|
scope_id wt_scope_id
|
|
not null
|
|
references iam_scope(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
grant_scope_id wt_scope_id
|
|
not null
|
|
references iam_scope(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
unique(name, scope_id),
|
|
version wt_version,
|
|
|
|
-- add unique index so a composite fk can be declared.
|
|
unique(scope_id, public_id)
|
|
);
|
|
|
|
-- Grants are immutable, which is enforced via the trigger below
|
|
create table iam_role_grant (
|
|
create_time wt_timestamp,
|
|
role_id wt_role_id -- pk
|
|
references iam_role(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
canonical_grant text -- pk
|
|
check(
|
|
length(trim(canonical_grant)) > 0
|
|
),
|
|
raw_grant text not null
|
|
check(
|
|
length(trim(raw_grant)) > 0
|
|
),
|
|
primary key(role_id, canonical_grant)
|
|
);
|
|
|
|
-- iam_immutable_role_grant() ensures that grants assigned to roles are immutable.
|
|
create or replace function
|
|
iam_immutable_role_grant()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
raise exception 'role grants are immutable';
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger immutable_role_grant
|
|
before
|
|
update on iam_role_grant
|
|
for each row execute procedure iam_immutable_role_grant();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on iam_role_grant
|
|
for each row execute procedure default_create_time();
|
|
|
|
create or replace function
|
|
disallow_r_default_deletion()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if old.public_id = 'r_default' then
|
|
raise exception 'deletion of r_default not allowed';
|
|
end if;
|
|
return old;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger
|
|
iam_role_disallow_global_deletion
|
|
before
|
|
delete on iam_role
|
|
for each row execute procedure disallow_r_default_deletion();
|
|
|
|
create trigger
|
|
update_version_column
|
|
after update on iam_role
|
|
for each row execute procedure update_version_column();
|
|
|
|
create trigger
|
|
update_time_column
|
|
before update on iam_role
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on iam_role
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
ensure_grant_scope_id_valid
|
|
before
|
|
insert or update on iam_role
|
|
for each row execute procedure grant_scope_id_valid();
|
|
|
|
-- define the immutable fields for iam_role (started trigger name with "a_" so
|
|
-- it will run first)
|
|
create trigger
|
|
a_immutable_columns
|
|
before
|
|
update on iam_role
|
|
for each row execute procedure immutable_columns('public_id', 'create_time', 'scope_id');
|
|
|
|
create or replace function
|
|
recovery_user_not_allowed()
|
|
returns trigger
|
|
as $$
|
|
declare
|
|
new_value text;
|
|
begin
|
|
execute format('SELECT $1.%I', tg_argv[0]) into new_value using new;
|
|
if new_value = 'u_recovery' then
|
|
raise exception '"u_recovery" not allowed here"';
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create table iam_group (
|
|
public_id wt_public_id
|
|
primary key,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
name text,
|
|
description text,
|
|
scope_id wt_scope_id
|
|
not null
|
|
references iam_scope(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
unique(name, scope_id),
|
|
-- version allows optimistic locking of the group when modifying the group
|
|
-- itself and when modifying dependent items like group members.
|
|
version wt_version,
|
|
|
|
-- add unique index so a composite fk can be declared.
|
|
unique(scope_id, public_id)
|
|
);
|
|
|
|
create trigger
|
|
update_version_column
|
|
after update on iam_group
|
|
for each row execute procedure update_version_column();
|
|
|
|
create trigger
|
|
update_time_column
|
|
before update on iam_group
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on iam_group
|
|
for each row execute procedure default_create_time();
|
|
|
|
-- define the immutable fields for iam_group
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on iam_group
|
|
for each row execute procedure immutable_columns('public_id', 'create_time', 'scope_id');
|
|
|
|
-- iam_user_role contains roles that have been assigned to users. Users can be
|
|
-- from any scope. The rows in this table must be immutable after insert, which
|
|
-- will be ensured with a before update trigger using
|
|
-- iam_immutable_role_principal().
|
|
create table iam_user_role (
|
|
create_time wt_timestamp,
|
|
role_id wt_role_id
|
|
references iam_role(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
principal_id wt_user_id
|
|
references iam_user(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
primary key (role_id, principal_id)
|
|
);
|
|
|
|
-- iam_group_role contains roles that have been assigned to groups.
|
|
-- Groups can be from any scope. The rows in this table must be immutable after
|
|
-- insert, which will be ensured with a before update trigger using
|
|
-- iam_immutable_role_principal().
|
|
create table iam_group_role (
|
|
create_time wt_timestamp,
|
|
role_id wt_role_id
|
|
references iam_role(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
principal_id wt_public_id
|
|
references iam_group(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
primary key (role_id, principal_id)
|
|
);
|
|
|
|
-- get_scoped_principal_id is used by the iam_principle_role view as a convient
|
|
-- way to create <scope_id>:<principal_id> to reference principals from
|
|
-- other scopes than the role's scope.
|
|
create or replace function get_scoped_principal_id(role_scope text, principal_scope text, principal_id text) returns text
|
|
as $$
|
|
begin
|
|
if role_scope = principal_scope then
|
|
return principal_id;
|
|
end if;
|
|
return principal_scope || ':' || principal_id;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
insert into iam_role (public_id, name, description, scope_id)
|
|
values('r_default', 'default', 'Default role created on first instantiation of Boundary. It is meant to provide enough permissions for users to successfully authenticate via various client types.', 'global');
|
|
insert into iam_role_grant (role_id, canonical_grant, raw_grant)
|
|
values
|
|
('r_default', 'type=scope;actions=list', 'type=scope;actions=list'),
|
|
('r_default', 'type=auth-method;actions=authenticate,list', 'type=auth-method;actions=authenticate,list');
|
|
insert into iam_user_role (role_id, principal_id)
|
|
values
|
|
('r_default', 'u_anon'),
|
|
('r_default', 'u_auth');
|
|
|
|
-- iam_principle_role provides a consolidated view all principal roles assigned
|
|
-- (user and group roles).
|
|
create view iam_principal_role as
|
|
select
|
|
ur.create_time,
|
|
ur.principal_id,
|
|
ur.role_id,
|
|
u.scope_id as principal_scope_id,
|
|
r.scope_id as role_scope_id,
|
|
get_scoped_principal_id(r.scope_id, u.scope_id, ur.principal_id) as scoped_principal_id,
|
|
'user' as type
|
|
from
|
|
iam_user_role ur,
|
|
iam_role r,
|
|
iam_user u
|
|
where
|
|
ur.role_id = r.public_id and
|
|
u.public_id = ur.principal_id
|
|
union
|
|
select
|
|
gr.create_time,
|
|
gr.principal_id,
|
|
gr.role_id,
|
|
g.scope_id as principal_scope_id,
|
|
r.scope_id as role_scope_id,
|
|
get_scoped_principal_id(r.scope_id, g.scope_id, gr.principal_id) as scoped_principal_id,
|
|
'group' as type
|
|
from
|
|
iam_group_role gr,
|
|
iam_role r,
|
|
iam_group g
|
|
where
|
|
gr.role_id = r.public_id and
|
|
g.public_id = gr.principal_id;
|
|
|
|
-- iam_immutable_role_principal() ensures that roles assigned to principals are immutable.
|
|
create or replace function
|
|
iam_immutable_role_principal()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
raise exception 'roles are immutable';
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger immutable_role_principal
|
|
before
|
|
update on iam_user_role
|
|
for each row execute procedure iam_immutable_role_principal();
|
|
|
|
create trigger
|
|
recovery_user_not_allowed_user_role
|
|
before
|
|
insert on iam_user_role
|
|
for each row execute procedure recovery_user_not_allowed('principal_id');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on iam_user_role
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger immutable_role_principal
|
|
before
|
|
update on iam_group_role
|
|
for each row execute procedure iam_immutable_role_principal();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on iam_group_role
|
|
for each row execute procedure default_create_time();
|
|
|
|
-- iam_group_member_user is an association table that represents groups with
|
|
-- associated users.
|
|
create table iam_group_member_user (
|
|
create_time wt_timestamp,
|
|
group_id wt_public_id references iam_group(public_id) on delete cascade on update cascade,
|
|
member_id wt_user_id references iam_user(public_id) on delete cascade on update cascade,
|
|
primary key (group_id, member_id)
|
|
);
|
|
|
|
-- iam_immutable_group_member() ensures that group members are immutable.
|
|
create or replace function
|
|
iam_immutable_group_member()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
raise exception 'group members are immutable';
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on iam_group_member_user
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger iam_immutable_group_member
|
|
before
|
|
update on iam_group_member_user
|
|
for each row execute procedure iam_immutable_group_member();
|
|
|
|
create trigger
|
|
recovery_user_not_allowed_group_member
|
|
before
|
|
insert on iam_group_member_user
|
|
for each row execute procedure recovery_user_not_allowed('member_id');
|
|
|
|
-- get_scoped_principal_id is used by the iam_group_member view as a convient
|
|
-- way to create <scope_id>:<member_id> to reference members from
|
|
-- other scopes than the group's scope.
|
|
create or replace function get_scoped_member_id(group_scope text, member_scope text, member_id text) returns text
|
|
as $$
|
|
begin
|
|
if group_scope = member_scope then
|
|
return member_id;
|
|
end if;
|
|
return member_scope || ':' || member_id;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
-- iam_group_member provides a consolidated view of group members.
|
|
create view iam_group_member as
|
|
select
|
|
gm.create_time,
|
|
gm.group_id,
|
|
gm.member_id,
|
|
u.scope_id as member_scope_id,
|
|
g.scope_id as group_scope_id,
|
|
get_scoped_member_id(g.scope_id, u.scope_id, gm.member_id) as scoped_member_id,
|
|
'user' as type
|
|
from
|
|
iam_group_member_user gm,
|
|
iam_user u,
|
|
iam_group g
|
|
where
|
|
gm.member_id = u.public_id and
|
|
gm.group_id = g.public_id;
|
|
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/07_auth.down.sql": {
|
|
name: "07_auth.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop function insert_auth_account_subtype;
|
|
drop function insert_auth_method_subtype;
|
|
|
|
drop table auth_account cascade;
|
|
drop table auth_method cascade;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/07_auth.up.sql": {
|
|
name: "07_auth.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
/*
|
|
|
|
┌────────────────┐ ┌────────────────┐
|
|
│ iam_scope │ │ auth_method │
|
|
├────────────────┤ ├────────────────┤
|
|
│ public_id (pk) │ ╱│ public_id (pk) │
|
|
│ │┼┼───────────○─│ scope_id (fk) │
|
|
│ │ ╲│ │
|
|
└────────────────┘ └────────────────┘
|
|
┼ ┼
|
|
┼ ┼
|
|
│ │
|
|
│ │ ▲fk1
|
|
│ │
|
|
○ ○
|
|
╱│╲ ╱│╲
|
|
┌────────────────┐ ┌──────────────────────────┐
|
|
│ iam_user │ │ auth_account │
|
|
├────────────────┤ ├──────────────────────────┤
|
|
│ public_id (pk) │ │ public_id (pk) │
|
|
│ scope_id (fk) │ ◀fk2 │ scope_id (fk1,fk2) │
|
|
│ │┼○──────○┼│ auth_method_id (fk1) │
|
|
│ │ │ iam_user_id (fk2) │
|
|
└────────────────┘ └──────────────────────────┘
|
|
|
|
An iam_scope can have 0 to many iam_users.
|
|
An iam_scope can have 0 to many auth_methods.
|
|
|
|
An iam_user belongs to 1 iam_scope.
|
|
An auth_method belongs to 1 iam_scope.
|
|
|
|
An iam_user can have 0 or 1 auth_account.
|
|
An auth_account belongs to 0 or 1 iam_user.
|
|
|
|
An auth_method can have 0 to many auth_accounts.
|
|
An auth_account belongs to 1 auth_account.
|
|
|
|
An auth_account can only be associated with an iam_user in the same scope of
|
|
the auth_account's auth_method. Including scope_id in fk1 and fk2 ensures this
|
|
restriction is not violated.
|
|
|
|
Design influenced by:
|
|
https://dba.stackexchange.com/questions/58970/enforcing-constraints-two-tables-away/58972#58972
|
|
|
|
*/
|
|
|
|
-- base table for auth methods
|
|
create table auth_method (
|
|
public_id wt_public_id
|
|
primary key,
|
|
scope_id wt_scope_id
|
|
not null
|
|
references iam_scope(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
|
|
-- The order of columns is important for performance. See:
|
|
-- https://dba.stackexchange.com/questions/58970/enforcing-constraints-two-tables-away/58972#58972
|
|
-- https://dba.stackexchange.com/questions/27481/is-a-composite-index-also-good-for-queries-on-the-first-field
|
|
unique(scope_id, public_id)
|
|
);
|
|
|
|
|
|
-- base table for auth accounts
|
|
create table auth_account (
|
|
public_id wt_public_id
|
|
primary key,
|
|
auth_method_id wt_public_id
|
|
not null,
|
|
scope_id wt_scope_id
|
|
not null,
|
|
iam_user_id wt_public_id,
|
|
-- including scope_id in fk1 and fk2 ensures the scope_id of the owning
|
|
-- auth_method and the scope_id of the owning iam_user are the same
|
|
foreign key (scope_id, auth_method_id) -- fk1
|
|
references auth_method (scope_id, public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
foreign key (scope_id, iam_user_id) -- fk2
|
|
references iam_user (scope_id, public_id)
|
|
on delete set null
|
|
on update cascade,
|
|
unique(scope_id, auth_method_id, public_id)
|
|
);
|
|
|
|
create or replace function
|
|
insert_auth_method_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
insert into auth_method
|
|
(public_id, scope_id)
|
|
values
|
|
(new.public_id, new.scope_id);
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create or replace function
|
|
insert_auth_account_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
|
|
select auth_method.scope_id
|
|
into new.scope_id
|
|
from auth_method
|
|
where auth_method.public_id = new.auth_method_id;
|
|
|
|
insert into auth_account
|
|
(public_id, auth_method_id, scope_id)
|
|
values
|
|
(new.public_id, new.auth_method_id, new.scope_id);
|
|
|
|
return new;
|
|
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/08_servers.down.sql": {
|
|
name: "08_servers.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop table server;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/08_servers.up.sql": {
|
|
name: "08_servers.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
-- For now at least the IDs will be the same as the name, because this allows us
|
|
-- to not have to persist some generated ID to worker and controller nodes.
|
|
-- Eventually we may want them to diverge, so we have both here for now.
|
|
|
|
create table server (
|
|
private_id text,
|
|
type text,
|
|
name text not null unique
|
|
check(length(trim(name)) > 0),
|
|
description text,
|
|
address text,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
primary key (private_id, type)
|
|
);
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on server
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on server
|
|
for each row execute procedure default_create_time();
|
|
|
|
create table recovery_nonces (
|
|
nonce text
|
|
primary key,
|
|
create_time wt_timestamp
|
|
);
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on recovery_nonces
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on recovery_nonces
|
|
for each row execute procedure immutable_columns('nonce', 'create_time');
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/11_auth_token.down.sql": {
|
|
name: "11_auth_token.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop view auth_token_account cascade;
|
|
drop table auth_token cascade;
|
|
|
|
drop function update_last_access_time cascade;
|
|
drop function immutable_auth_token_columns cascade;
|
|
drop function expire_time_not_older_than_token cascade;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/11_auth_token.up.sql": {
|
|
name: "11_auth_token.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
-- an auth token belongs to 1 and only 1 auth account
|
|
-- an auth account can have 0 to many auth tokens
|
|
create table auth_token (
|
|
public_id wt_public_id primary key,
|
|
token bytea not null unique,
|
|
-- TODO: Make key_id a foreign key once we have DEKs
|
|
key_id text not null
|
|
check(length(trim(key_id)) > 0),
|
|
auth_account_id wt_public_id not null
|
|
references auth_account(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
-- This column is not updated every time this auth token is accessed.
|
|
-- It is updated after X minutes from the last time it was updated on
|
|
-- a per row basis.
|
|
approximate_last_access_time wt_timestamp
|
|
check(
|
|
approximate_last_access_time <= expiration_time
|
|
),
|
|
expiration_time wt_timestamp
|
|
check(
|
|
create_time <= expiration_time
|
|
)
|
|
);
|
|
|
|
create view auth_token_account as
|
|
select at.public_id,
|
|
at.token,
|
|
at.auth_account_id,
|
|
at.create_time,
|
|
at.update_time,
|
|
at.approximate_last_access_time,
|
|
at.expiration_time,
|
|
aa.scope_id,
|
|
aa.iam_user_id,
|
|
aa.auth_method_id
|
|
from auth_token as at
|
|
inner join auth_account as aa
|
|
on at.auth_account_id = aa.public_id;
|
|
|
|
create or replace function
|
|
update_last_access_time()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if new.approximate_last_access_time is distinct from old.approximate_last_access_time then
|
|
new.approximate_last_access_time = now();
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
comment on function
|
|
update_last_access_time()
|
|
is
|
|
'function used in before update triggers to properly set last_access_time columns';
|
|
|
|
create or replace function
|
|
immutable_auth_token_columns()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if new.auth_account_id is distinct from old.auth_account_id then
|
|
raise exception 'auth_account_id is read-only';
|
|
end if;
|
|
if new.token is distinct from old.token then
|
|
raise exception 'token is read-only';
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
comment on function
|
|
immutable_auth_token_columns()
|
|
is
|
|
'function used in before update triggers to make specific columns immutable';
|
|
|
|
-- This allows the expiration to be calculated on the server side and still hold the constraint that
|
|
-- the expiration time cant be before the creation time of the auth token.
|
|
create or replace function
|
|
expire_time_not_older_than_token()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if new.expiration_time < new.create_time then
|
|
new.expiration_time = new.create_time;
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
comment on function
|
|
expire_time_not_older_than_token()
|
|
is
|
|
'function used in before insert triggers to ensure expiration time is not older than create time';
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before insert on auth_token
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
expire_time_not_older_than_token
|
|
before insert on auth_token
|
|
for each row execute procedure expire_time_not_older_than_token();
|
|
|
|
create trigger
|
|
update_time_column
|
|
before update on auth_token
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
update_last_access_time
|
|
before update on auth_token
|
|
for each row execute procedure update_last_access_time();
|
|
|
|
|
|
create trigger
|
|
immutable_auth_token_columns
|
|
before update on auth_token
|
|
for each row execute procedure immutable_auth_token_columns();
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on auth_token
|
|
for each row execute procedure immutable_columns('public_id', 'auth_account_id', 'create_time');
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/12_auth_password.down.sql": {
|
|
name: "12_auth_password.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop table auth_password_credential;
|
|
drop table auth_password_conf cascade;
|
|
drop table if exists auth_password_account;
|
|
drop table if exists auth_password_method;
|
|
|
|
drop function insert_auth_password_credential_subtype;
|
|
drop function insert_auth_password_conf_subtype;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/12_auth_password.up.sql": {
|
|
name: "12_auth_password.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
/*
|
|
|
|
┌────────────────┐ ┌──────────────────────┐ ┌────────────────────────────┐
|
|
│ auth_method │ │ auth_password_method │ │ auth_password_conf │
|
|
├────────────────┤ ├──────────────────────┤ ├────────────────────────────┤
|
|
│ public_id (pk) │ │ public_id (pk,fk) │ ╱│ private_id (pk,fk) │
|
|
│ scope_id (fk) │┼┼─────────────○┼│ scope_id (fk) │┼┼─────────○─│ password_method_id (fk) │
|
|
│ │ │ ... │ ╲│ │
|
|
└────────────────┘ └──────────────────────┘ └────────────────────────────┘
|
|
┼ ┼ ┼
|
|
┼ ┼ ┼
|
|
│ │ │
|
|
│ ▲fk1 │ ▲fk1 │ ▲fk1
|
|
│ │ │
|
|
○ ○ ○
|
|
╱│╲ ╱│╲ ╱│╲
|
|
┌──────────────────────────┐ ┌──────────────────────────┐ ┌───────────────────────────────┐
|
|
│ auth_account │ │ auth_password_account │ │ auth_password_credential │
|
|
├──────────────────────────┤ ├──────────────────────────┤ ├───────────────────────────────┤
|
|
│ public_id (pk) │ │ public_id (pk,fk2) │ │ private_id (pk) │
|
|
│ scope_id (fk1,fk2) │ ◀fk2 │ scope_id (fk1,fk2) │ ◀fk2 │ password_method_id (fk1,fk2) │
|
|
│ auth_method_id (fk1) │┼┼──────○┼│ auth_method_id (fk1,fk2) │┼┼──────○┼│ password_conf_id (fk1) │
|
|
│ iam_user_id (fk2) │ │ ... │ │ password_account_id (fk2) │
|
|
└──────────────────────────┘ └──────────────────────────┘ └───────────────────────────────┘
|
|
|
|
An auth_method is a base type. An auth_password_method is an auth_method
|
|
subtype. For every row in auth_password_method there is one row in auth_method
|
|
with the same public_id and scope_id.
|
|
|
|
Similarly, an auth_account is a base type. An auth_password_account is an
|
|
auth_account subtype. For every row in auth_password_account there is one row
|
|
in auth_account with the same public_id, scope_id, and auth_method_id.
|
|
|
|
Both auth_password_conf and auth_password_credential are base types. Each
|
|
password key derivation function will require a auth_password_conf and
|
|
auth_password_credential table.
|
|
|
|
An auth_method can have 0 or 1 auth_password_method.
|
|
An auth_account can have 0 or 1 auth_password_account.
|
|
|
|
An auth_password_method belongs to 1 auth_method.
|
|
An auth_password_method can have 0 to many auth_password_accounts.
|
|
An auth_password_method can have 0 to many auth_password_confs.
|
|
|
|
An auth_password_account belongs to 1 auth_account.
|
|
An auth_password_account belongs to 1 auth_password_method.
|
|
An auth_password_account can have 0 or 1 auth_password_credential.
|
|
|
|
An auth_password_conf belongs to 1 auth_password_method.
|
|
An auth_password_conf can have 0 to many auth_password_credentials.
|
|
|
|
An auth_password_credential belongs to 1 auth_password_account.
|
|
An auth_password_credential belongs to 1 auth_password_conf.
|
|
|
|
*/
|
|
|
|
create table auth_password_method (
|
|
public_id wt_public_id
|
|
primary key,
|
|
scope_id wt_scope_id
|
|
not null,
|
|
password_conf_id wt_private_id, -- FK to auth_password_conf added below
|
|
name text,
|
|
description text,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
min_login_name_length int
|
|
not null
|
|
default 3,
|
|
min_password_length int
|
|
not null
|
|
default 8,
|
|
version wt_version,
|
|
foreign key (scope_id, public_id)
|
|
references auth_method (scope_id, public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
unique(scope_id, name),
|
|
unique(scope_id, public_id)
|
|
);
|
|
|
|
create trigger
|
|
update_version_column
|
|
after update on auth_password_method
|
|
for each row execute procedure update_version_column();
|
|
|
|
create trigger
|
|
insert_auth_method_subtype
|
|
before insert on auth_password_method
|
|
for each row execute procedure insert_auth_method_subtype();
|
|
|
|
create table auth_password_account (
|
|
public_id wt_public_id
|
|
primary key,
|
|
auth_method_id wt_public_id
|
|
not null,
|
|
-- NOTE(mgaffney): The scope_id type is not wt_scope_id because the domain
|
|
-- check is executed before the insert trigger which retrieves the scope_id
|
|
-- causing an insert to fail.
|
|
scope_id text not null,
|
|
name text,
|
|
description text,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
login_name text not null
|
|
check(
|
|
lower(trim(login_name)) = login_name
|
|
and
|
|
length(login_name) > 0
|
|
),
|
|
version wt_version,
|
|
foreign key (scope_id, auth_method_id)
|
|
references auth_password_method (scope_id, public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
foreign key (scope_id, auth_method_id, public_id)
|
|
references auth_account (scope_id, auth_method_id, public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
unique(auth_method_id, name),
|
|
unique(auth_method_id, login_name),
|
|
unique(auth_method_id, public_id)
|
|
);
|
|
|
|
create trigger
|
|
update_version_column
|
|
after update on auth_password_account
|
|
for each row execute procedure update_version_column();
|
|
|
|
create trigger
|
|
insert_auth_account_subtype
|
|
before insert on auth_password_account
|
|
for each row execute procedure insert_auth_account_subtype();
|
|
|
|
create table auth_password_conf (
|
|
private_id wt_private_id
|
|
primary key,
|
|
password_method_id wt_public_id
|
|
not null
|
|
references auth_password_method (public_id)
|
|
on delete cascade
|
|
on update cascade
|
|
deferrable initially deferred,
|
|
unique(password_method_id, private_id)
|
|
);
|
|
|
|
alter table auth_password_method
|
|
add constraint current_conf_fkey
|
|
foreign key (public_id, password_conf_id)
|
|
references auth_password_conf (password_method_id, private_id)
|
|
on delete cascade
|
|
on update cascade
|
|
deferrable initially deferred;
|
|
|
|
-- insert_auth_password_conf_subtype() is a trigger function for subtypes of
|
|
-- auth_password_conf
|
|
create or replace function
|
|
insert_auth_password_conf_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
insert into auth_password_conf
|
|
(private_id, password_method_id)
|
|
values
|
|
(new.private_id, new.password_method_id);
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create table auth_password_credential (
|
|
private_id wt_private_id
|
|
primary key,
|
|
password_account_id wt_public_id
|
|
not null
|
|
unique,
|
|
password_conf_id wt_private_id,
|
|
password_method_id wt_public_id
|
|
not null,
|
|
foreign key (password_method_id, password_conf_id)
|
|
references auth_password_conf (password_method_id, private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
foreign key (password_method_id, password_account_id)
|
|
references auth_password_account (auth_method_id, public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
unique(password_method_id, password_conf_id, password_account_id)
|
|
);
|
|
|
|
-- insert_auth_password_credential_subtype() is a trigger function for
|
|
-- subtypes of auth_password_credential
|
|
create or replace function
|
|
insert_auth_password_credential_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
|
|
select auth_password_account.auth_method_id
|
|
into new.password_method_id
|
|
from auth_password_account
|
|
where auth_password_account.public_id = new.password_account_id;
|
|
|
|
insert into auth_password_credential
|
|
(private_id, password_account_id, password_conf_id, password_method_id)
|
|
values
|
|
(new.private_id, new.password_account_id, new.password_conf_id, new.password_method_id);
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
-- update_auth_password_credential_subtype() is an after update trigger
|
|
-- function for subtypes of auth_password_credential
|
|
create or replace function
|
|
update_auth_password_credential_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
/*
|
|
The configuration id of a credential is updated when a credential is
|
|
rehashed during authentication.
|
|
*/
|
|
if new.password_conf_id is distinct from old.password_conf_id then
|
|
update auth_password_credential
|
|
set password_conf_id = new.password_conf_id
|
|
where private_id = new.private_id;
|
|
end if;
|
|
return null; -- result is ignored since this is an after trigger
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
-- delete_auth_password_credential_subtype() is an after delete trigger
|
|
-- function for subtypes of auth_password_credential
|
|
create or replace function
|
|
delete_auth_password_credential_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
delete
|
|
from auth_password_credential
|
|
where private_id = old.private_id;
|
|
return null; -- result is ignored since this is an after trigger
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
--
|
|
-- triggers for time columns
|
|
--
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on auth_password_method
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on auth_password_method
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on auth_password_method
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on auth_password_account
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on auth_password_account
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on auth_password_account
|
|
for each row execute procedure default_create_time();
|
|
|
|
-- The tickets for oplog are the subtypes not the base types because no updates
|
|
-- are done to any values in the base types.
|
|
insert into oplog_ticket
|
|
(name, version)
|
|
values
|
|
('auth_password_method', 1),
|
|
('auth_password_account', 1),
|
|
('auth_password_credential', 1);
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/13_auth_password_argon.down.sql": {
|
|
name: "13_auth_password_argon.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop table auth_password_argon2_cred;
|
|
drop table auth_password_argon2_conf;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/13_auth_password_argon.up.sql": {
|
|
name: "13_auth_password_argon.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
create table auth_password_argon2_conf (
|
|
private_id wt_private_id primary key
|
|
references auth_password_conf (private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
password_method_id wt_public_id not null,
|
|
create_time wt_timestamp,
|
|
iterations int not null default 3
|
|
check(iterations > 0),
|
|
memory int not null default 65536
|
|
check(memory > 0),
|
|
threads int not null default 1
|
|
check(threads > 0),
|
|
-- salt_length unit is bytes
|
|
salt_length int not null default 32
|
|
-- minimum of 16 bytes (128 bits)
|
|
check(salt_length >= 16),
|
|
-- key_length unit is bytes
|
|
key_length int not null default 32
|
|
-- minimum of 16 bytes (128 bits)
|
|
check(key_length >= 16),
|
|
unique(password_method_id, iterations, memory, threads, salt_length, key_length),
|
|
unique (password_method_id, private_id),
|
|
foreign key (password_method_id, private_id)
|
|
references auth_password_conf (password_method_id, private_id)
|
|
on delete cascade
|
|
on update cascade
|
|
deferrable initially deferred
|
|
);
|
|
|
|
create or replace function
|
|
read_only_auth_password_argon2_conf()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
raise exception 'auth_password_argon2_conf is read-only';
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger
|
|
read_only_auth_password_argon2_conf
|
|
before
|
|
update on auth_password_argon2_conf
|
|
for each row execute procedure read_only_auth_password_argon2_conf();
|
|
|
|
create trigger
|
|
insert_auth_password_conf_subtype
|
|
before insert on auth_password_argon2_conf
|
|
for each row execute procedure insert_auth_password_conf_subtype();
|
|
|
|
create table auth_password_argon2_cred (
|
|
private_id wt_private_id primary key
|
|
references auth_password_credential (private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
password_account_id wt_public_id not null,
|
|
password_conf_id wt_private_id,
|
|
-- NOTE(mgaffney): The password_method_id type is not wt_public_id because
|
|
-- the domain check is executed before the insert trigger which retrieves
|
|
-- the password_method_id causing an insert to fail.
|
|
password_method_id text not null,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
salt bytea not null -- cannot be changed unless derived_key is changed too
|
|
check(length(salt) > 0),
|
|
derived_key bytea not null
|
|
check(length(derived_key) > 0),
|
|
-- TODO: Make key_id a foreign key once we have DEKs
|
|
key_id text not null
|
|
check(length(trim(key_id)) > 0),
|
|
foreign key (password_method_id, password_conf_id)
|
|
references auth_password_argon2_conf (password_method_id, private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
foreign key (password_method_id, password_conf_id, password_account_id)
|
|
references auth_password_credential (password_method_id, password_conf_id, password_account_id)
|
|
on delete cascade
|
|
on update cascade
|
|
deferrable initially deferred
|
|
);
|
|
|
|
create trigger
|
|
insert_auth_password_credential_subtype
|
|
before insert on auth_password_argon2_cred
|
|
for each row execute procedure insert_auth_password_credential_subtype();
|
|
|
|
create trigger
|
|
update_auth_password_credential_subtype
|
|
after update on auth_password_argon2_cred
|
|
for each row execute procedure update_auth_password_credential_subtype();
|
|
|
|
create trigger
|
|
delete_auth_password_credential_subtype
|
|
after delete on auth_password_argon2_cred
|
|
for each row execute procedure delete_auth_password_credential_subtype();
|
|
|
|
--
|
|
-- triggers for time columns
|
|
--
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on auth_password_argon2_conf
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on auth_password_argon2_conf
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
update_time_column
|
|
before
|
|
update on auth_password_argon2_cred
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on auth_password_argon2_cred
|
|
for each row execute procedure immutable_columns('create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on auth_password_argon2_cred
|
|
for each row execute procedure default_create_time();
|
|
|
|
-- The tickets for oplog are the subtypes not the base types because no updates
|
|
-- are done to any values in the base types.
|
|
insert into oplog_ticket
|
|
(name, version)
|
|
values
|
|
('auth_password_argon2_conf', 1),
|
|
('auth_password_argon2_cred', 1);
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/14_auth_password_views.down.sql": {
|
|
name: "14_auth_password_views.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop view auth_password_current_conf;
|
|
drop view auth_password_conf_union;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/14_auth_password_views.up.sql": {
|
|
name: "14_auth_password_views.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
-- auth_password_conf_union is a union of the configuration settings
|
|
-- of all supported key derivation functions.
|
|
-- It will be updated as new key derivation functions are supported.
|
|
create or replace view auth_password_conf_union as
|
|
-- Do not change the order of the columns when adding new configurations.
|
|
-- Union with new tables appending new columns as needed.
|
|
select c.password_method_id, c.private_id as password_conf_id, c.private_id,
|
|
'argon2' as conf_type,
|
|
c.iterations, c.memory, c.threads, c.salt_length, c.key_length
|
|
from auth_password_argon2_conf c;
|
|
|
|
-- auth_password_current_conf provides a view of the current password
|
|
-- configuration for each password auth method.
|
|
-- The view will be updated as new key derivation functions are supported
|
|
-- but the query to create the view should not need to be updated.
|
|
create or replace view auth_password_current_conf as
|
|
-- Rerun this query whenever auth_password_conf_union is updated.
|
|
select pm.min_login_name_length, pm.min_password_length, c.*
|
|
from auth_password_method pm
|
|
inner join auth_password_conf_union c
|
|
on pm.password_conf_id = c.password_conf_id;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/20_host.down.sql": {
|
|
name: "20_host.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop table host_set;
|
|
drop table host;
|
|
drop table host_catalog;
|
|
|
|
drop function insert_host_set_subtype;
|
|
drop function insert_host_subtype;
|
|
drop function insert_host_catalog_subtype;
|
|
|
|
delete
|
|
from oplog_ticket
|
|
where name in (
|
|
'host_catalog',
|
|
'host',
|
|
'host_set'
|
|
);
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/20_host.up.sql": {
|
|
name: "20_host.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
/*
|
|
|
|
┌─────────────────┐
|
|
│ host │
|
|
├─────────────────┤
|
|
│ public_id (pk) │
|
|
│ catalog_id (fk) │
|
|
│ │
|
|
└─────────────────┘
|
|
╲│╱
|
|
○
|
|
│
|
|
┼
|
|
┼
|
|
┌─────────────────┐ ┌─────────────────┐
|
|
│ iam_scope │ │ host_catalog │
|
|
├─────────────────┤ ├─────────────────┤
|
|
│ public_id (pk) │ ╱│ public_id (pk) │
|
|
│ │┼┼──────○─│ scope_id (fk) │
|
|
│ │ ╲│ │
|
|
└─────────────────┘ └─────────────────┘
|
|
┼
|
|
┼
|
|
│
|
|
○
|
|
╱│╲
|
|
┌─────────────────┐
|
|
│ host_set │
|
|
├─────────────────┤
|
|
│ public_id (pk) │
|
|
│ catalog_id (fk) │
|
|
│ │
|
|
└─────────────────┘
|
|
|
|
*/
|
|
|
|
-- host_catalog
|
|
create table host_catalog (
|
|
public_id wt_public_id
|
|
primary key,
|
|
scope_id wt_scope_id
|
|
not null
|
|
references iam_scope (public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
|
|
-- The order of columns is important for performance. See:
|
|
-- https://dba.stackexchange.com/questions/58970/enforcing-constraints-two-tables-away/58972#58972
|
|
-- https://dba.stackexchange.com/questions/27481/is-a-composite-index-also-good-for-queries-on-the-first-field
|
|
unique(scope_id, public_id)
|
|
);
|
|
|
|
create trigger immutable_columns before update on host_catalog
|
|
for each row execute procedure immutable_columns('public_id', 'scope_id');
|
|
|
|
-- insert_host_catalog_subtype() is a before insert trigger
|
|
-- function for subtypes of host_catalog
|
|
create or replace function insert_host_catalog_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
insert into host_catalog
|
|
(public_id, scope_id)
|
|
values
|
|
(new.public_id, new.scope_id);
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
-- delete_host_catalog_subtype() is an after delete trigger
|
|
-- function for subtypes of host_catalog
|
|
create or replace function delete_host_catalog_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
delete from host_catalog
|
|
where public_id = old.public_id;
|
|
return null; -- result is ignored since this is an after trigger
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
-- host
|
|
create table host (
|
|
public_id wt_public_id primary key,
|
|
catalog_id wt_public_id not null
|
|
references host_catalog (public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
unique(catalog_id, public_id)
|
|
);
|
|
|
|
create trigger immutable_columns before update on host
|
|
for each row execute procedure immutable_columns('public_id', 'catalog_id');
|
|
|
|
-- insert_host_subtype() is a before insert trigger
|
|
-- function for subtypes of host
|
|
create or replace function insert_host_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
insert into host
|
|
(public_id, catalog_id)
|
|
values
|
|
(new.public_id, new.catalog_id);
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
-- delete_host_subtype() is an after delete trigger
|
|
-- function for subtypes of host
|
|
create or replace function delete_host_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
delete from host
|
|
where public_id = old.public_id;
|
|
return null; -- result is ignored since this is an after trigger
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
-- host_set
|
|
create table host_set (
|
|
public_id wt_public_id primary key,
|
|
catalog_id wt_public_id not null
|
|
references host_catalog (public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
unique(catalog_id, public_id)
|
|
);
|
|
|
|
create trigger immutable_columns before update on host_set
|
|
for each row execute procedure immutable_columns('public_id', 'catalog_id');
|
|
|
|
-- insert_host_set_subtype() is a before insert trigger
|
|
-- function for subtypes of host_set
|
|
create or replace function insert_host_set_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
insert into host_set
|
|
(public_id, catalog_id)
|
|
values
|
|
(new.public_id, new.catalog_id);
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
-- delete_host_set_subtype() is an after delete trigger
|
|
-- function for subtypes of host_set
|
|
create or replace function delete_host_set_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
delete from host_set
|
|
where public_id = old.public_id;
|
|
return null; -- result is ignored since this is an after trigger
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
insert into oplog_ticket (name, version)
|
|
values
|
|
('host_catalog', 1),
|
|
('host', 1),
|
|
('host_set', 1);
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/22_static_host.down.sql": {
|
|
name: "22_static_host.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop table static_host_set_member cascade;
|
|
drop table static_host_set cascade;
|
|
drop table static_host cascade;
|
|
drop table static_host_catalog cascade;
|
|
|
|
delete
|
|
from oplog_ticket
|
|
where name in (
|
|
'static_host_catalog',
|
|
'static_host',
|
|
'static_host_set',
|
|
'static_host_set_member'
|
|
);
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/22_static_host.up.sql": {
|
|
name: "22_static_host.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
/*
|
|
|
|
┌─────────────────┐ ┌─────────────────────┐
|
|
│ host │ │ static_host │
|
|
├─────────────────┤ ├─────────────────────┤
|
|
│ public_id (pk) │ │ public_id (pk) │
|
|
│ catalog_id (fk) │┼┼──────○┼│ catalog_id (fk) │┼┼─────────────────────┐
|
|
│ │ │ address │ ◀fk1 │
|
|
└─────────────────┘ └─────────────────────┘ │
|
|
╲│╱ ╲│╱ │
|
|
○ ○ │
|
|
│ │ │
|
|
┼ ┼ ○
|
|
┼ ┼ ╱│╲
|
|
┌─────────────────┐ ┌─────────────────────┐ ┌────────────────────────┐
|
|
│ host_catalog │ │ static_host_catalog │ │ static_host_set_member │
|
|
├─────────────────┤ ├─────────────────────┤ ├────────────────────────┤
|
|
│ public_id (pk) │ │ public_id (pk) │ │ host_id (pk,fk1) │
|
|
│ scope_id (fk) │┼┼──────○┼│ scope_id (fk) │ │ set_id (pk,fk2) │
|
|
│ │ │ │ │ catalog_id (fk1,fk2) │
|
|
└─────────────────┘ └─────────────────────┘ └────────────────────────┘
|
|
┼ ┼ ╲│╱
|
|
┼ ┼ ○
|
|
│ │ │
|
|
○ ○ │
|
|
╱│╲ ╱│╲ │
|
|
┌─────────────────┐ ┌─────────────────────┐ │
|
|
│ host_set │ │ static_host_set │ │
|
|
├─────────────────┤ ├─────────────────────┤ │
|
|
│ public_id (pk) │ │ public_id (pk) │ ◀fk2 │
|
|
│ catalog_id (fk) │┼┼──────○┼│ catalog_id (fk) │┼┼─────────────────────┘
|
|
│ │ │ │
|
|
└─────────────────┘ └─────────────────────┘
|
|
|
|
*/
|
|
|
|
create table static_host_catalog (
|
|
public_id wt_public_id
|
|
primary key,
|
|
scope_id wt_scope_id
|
|
not null
|
|
references iam_scope (public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
name text,
|
|
description text,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
version wt_version,
|
|
foreign key (scope_id, public_id)
|
|
references host_catalog (scope_id, public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
unique(scope_id, name)
|
|
);
|
|
|
|
create trigger update_version_column after update on static_host_catalog
|
|
for each row execute procedure update_version_column();
|
|
|
|
create trigger update_time_column before update on static_host_catalog
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger default_create_time_column before insert on static_host_catalog
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger immutable_columns before update on static_host_catalog
|
|
for each row execute procedure immutable_columns('public_id', 'scope_id','create_time');
|
|
|
|
create trigger insert_host_catalog_subtype before insert on static_host_catalog
|
|
for each row execute procedure insert_host_catalog_subtype();
|
|
|
|
create trigger delete_host_catalog_subtype after delete on static_host_catalog
|
|
for each row execute procedure delete_host_catalog_subtype();
|
|
|
|
create table static_host (
|
|
public_id wt_public_id primary key,
|
|
catalog_id wt_public_id not null
|
|
references static_host_catalog (public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
name text,
|
|
description text,
|
|
address text not null
|
|
check(
|
|
length(trim(address)) > 2
|
|
and
|
|
length(trim(address)) < 256
|
|
),
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
version wt_version,
|
|
unique(catalog_id, name),
|
|
|
|
foreign key (catalog_id, public_id)
|
|
references host (catalog_id, public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
|
|
-- The order of columns is important for performance. See:
|
|
-- https://dba.stackexchange.com/questions/58970/enforcing-constraints-two-tables-away/58972#58972
|
|
-- https://dba.stackexchange.com/questions/27481/is-a-composite-index-also-good-for-queries-on-the-first-field
|
|
unique(catalog_id, public_id)
|
|
);
|
|
|
|
create trigger update_version_column after update on static_host
|
|
for each row execute procedure update_version_column();
|
|
|
|
create trigger update_time_column before update on static_host
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger default_create_time_column before insert on static_host
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger immutable_columns before update on static_host
|
|
for each row execute procedure immutable_columns('public_id', 'catalog_id','create_time');
|
|
|
|
create trigger insert_host_subtype before insert on static_host
|
|
for each row execute procedure insert_host_subtype();
|
|
|
|
create trigger delete_host_subtype after delete on static_host
|
|
for each row execute procedure delete_host_subtype();
|
|
|
|
create table static_host_set (
|
|
public_id wt_public_id primary key,
|
|
catalog_id wt_public_id not null
|
|
references static_host_catalog (public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
name text,
|
|
description text,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
version wt_version,
|
|
unique(catalog_id, name),
|
|
foreign key (catalog_id, public_id)
|
|
references host_set (catalog_id, public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
unique(catalog_id, public_id)
|
|
);
|
|
|
|
create trigger update_version_column after update on static_host_set
|
|
for each row execute procedure update_version_column();
|
|
|
|
create trigger update_time_column before update on static_host_set
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger default_create_time_column before insert on static_host_set
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger immutable_columns before update on static_host_set
|
|
for each row execute procedure immutable_columns('public_id', 'catalog_id','create_time');
|
|
|
|
create trigger insert_host_set_subtype before insert on static_host_set
|
|
for each row execute procedure insert_host_set_subtype();
|
|
|
|
create trigger delete_host_set_subtype after delete on static_host_set
|
|
for each row execute procedure delete_host_set_subtype();
|
|
|
|
create table static_host_set_member (
|
|
host_id wt_public_id not null,
|
|
set_id wt_public_id not null,
|
|
catalog_id wt_public_id not null,
|
|
primary key(host_id, set_id),
|
|
foreign key (catalog_id, host_id) -- fk1
|
|
references static_host (catalog_id, public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
foreign key (catalog_id, set_id) -- fk2
|
|
references static_host_set (catalog_id, public_id)
|
|
on delete cascade
|
|
on update cascade
|
|
);
|
|
|
|
create trigger immutable_columns before update on static_host_set_member
|
|
for each row execute procedure immutable_columns('host_id', 'set_id', 'catalog_id');
|
|
|
|
create or replace function insert_static_host_set_member()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
select static_host_set.catalog_id
|
|
into new.catalog_id
|
|
from static_host_set
|
|
where static_host_set.public_id = new.set_id;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger insert_static_host_set_member before insert on static_host_set_member
|
|
for each row execute procedure insert_static_host_set_member();
|
|
|
|
insert into oplog_ticket (name, version)
|
|
values
|
|
('static_host_catalog', 1),
|
|
('static_host', 1),
|
|
('static_host_set', 1),
|
|
('static_host_set_member', 1);
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/30_keys.down.sql": {
|
|
name: "30_keys.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop function kms_version_column cascade;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/30_keys.up.sql": {
|
|
name: "30_keys.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
-- kms_version_column() will increment the version column whenever row data
|
|
-- is inserted and should only be used in an before insert trigger. This
|
|
-- function will overwrite any explicit values to the version column.
|
|
create or replace function
|
|
kms_version_column()
|
|
returns trigger
|
|
as $$
|
|
declare
|
|
_key_id text;
|
|
_max bigint;
|
|
begin
|
|
execute format('SELECT $1.%I', tg_argv[0]) into _key_id using new;
|
|
execute format('select max(version) + 1 from %I where %I = $1', tg_relid::regclass, tg_argv[0]) using _key_id into _max;
|
|
if _max is null then
|
|
_max = 1;
|
|
end if;
|
|
new.version = _max;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
comment on function
|
|
kms_version_column()
|
|
is
|
|
'function used in before insert triggers to properly set version columns for kms_* tables with a version column';
|
|
|
|
commit;
|
|
`),
|
|
},
|
|
"migrations/31_keys.down.sql": {
|
|
name: "31_keys.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop table kms_root_key cascade;
|
|
drop table kms_root_key_version cascade;
|
|
drop table kms_database_key cascade;
|
|
drop table kms_database_key_version cascade;
|
|
drop table kms_oplog_key cascade;
|
|
drop table kms_oplog_key_version cascade;
|
|
drop table kms_session_key cascade;
|
|
drop table kms_session_key_version cascade;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/31_keys.up.sql": {
|
|
name: "31_keys.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
/*
|
|
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
├────────────────────────────────────────────────────────────────────────────────────────────┐ ○
|
|
├────────────────────────────────────────────────────────────────┐ ○ ┼
|
|
├────────────────────────────────────┐ ○ ┼ ┌────────────────────────┐
|
|
│ ○ ┼ ┌────────────────────────┐ │ kms_token_key │
|
|
┼ ┼ ┌────────────────────────┐ │ kms_session_key │ ├────────────────────────┤
|
|
┌────────────────────────┐ ┌────────────────────────┐ │ kms_oplog_key │ ├────────────────────────┤ │private_id │
|
|
│ kms_root_key │ │ kms_database_key │ ├────────────────────────┤ │private_id │ │root_key_id │
|
|
├────────────────────────┤ ├────────────────────────┤ │private_id │ │root_key_id │ │ │
|
|
│private_id │ │private_id │ │root_key_id │ │ │ │ │
|
|
│scope_id │ │root_key_id │ │ │ │ │ │ │
|
|
│ │ │ │ │ │ │ │ └────────────────────────┘
|
|
└────────────────────────┘ └────────────────────────┘ └────────────────────────┘ └────────────────────────┘ ┼
|
|
┼ ┼ ┼ ┼ │
|
|
│ │ │ │ │
|
|
│ │ │ │ │
|
|
│ │ │ │ │
|
|
│ │ │ │ │
|
|
┼ ┼ ┼ ┼ │
|
|
╱│╲ ╱│╲ ╱│╲ ╱│╲ ┼
|
|
┌────────────────────────┐ ┌────────────────────────┐ ┌────────────────────────┐ ┌────────────────────────┐ ╱│╲
|
|
│ kms_root_key_version │ │kms_database_key_version│ │ kms_oplog_key_version │ │kms_session_key_version │ ┌────────────────────────┐
|
|
├────────────────────────┤ ├────────────────────────┤ ├────────────────────────┤ ├────────────────────────┤ │ kms_token_key_version │
|
|
│private_id │ │private_id │ │private_id │ │private_id │ ├────────────────────────┤
|
|
│root_key_id │ │database_key_id │ │oplog_key_id │ │session_key_id │ │private_id │
|
|
│key │ │root_key_id │ │root_key_id │ │root_key_id │ │token_key_id │
|
|
│version │ │key │ │key │ │key │ │root_key_id │
|
|
│ │ │version │ │version │ │version │ │key │
|
|
└────────────────────────┘ └────────────────────────┘ │ │ │ │ │version │
|
|
┼ ┼ └────────────────────────┘ │ │ │ │
|
|
│ ○ ┼ └────────────────────────┘ │ │
|
|
├────────────────────────────────────┘ ○ ┼ └────────────────────────┘
|
|
├────────────────────────────────────────────────────────────────┘ ○ ┼
|
|
├────────────────────────────────────────────────────────────────────────────────────────────┘ ○
|
|
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
|
|
create table kms_root_key (
|
|
private_id wt_private_id primary key,
|
|
scope_id wt_scope_id not null unique -- there can only be one root key for a scope.
|
|
references iam_scope(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
create_time wt_timestamp
|
|
);
|
|
|
|
-- define the immutable fields for kms_root_key (all of them)
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on kms_root_key
|
|
for each row execute procedure immutable_columns('private_id', 'scope_id', 'create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on kms_root_key
|
|
for each row execute procedure default_create_time();
|
|
|
|
create table kms_root_key_version (
|
|
private_id wt_private_id primary key,
|
|
root_key_id wt_private_id not null
|
|
references kms_root_key(private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
version wt_version,
|
|
key bytea not null,
|
|
create_time wt_timestamp,
|
|
unique(root_key_id, version)
|
|
);
|
|
|
|
-- define the immutable fields for kms_root_key_version (all of them)
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on kms_root_key_version
|
|
for each row execute procedure immutable_columns('private_id', 'root_key_id', 'version', 'key', 'create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on kms_root_key_version
|
|
for each row execute procedure default_create_time();
|
|
|
|
|
|
create trigger
|
|
kms_version_column
|
|
before insert on kms_root_key_version
|
|
for each row execute procedure kms_version_column('root_key_id');
|
|
|
|
create table kms_database_key (
|
|
private_id wt_private_id primary key,
|
|
root_key_id wt_private_id not null unique -- there can be only one database dek per root key
|
|
references kms_root_key(private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
create_time wt_timestamp
|
|
);
|
|
|
|
-- define the immutable fields for kms_database_key (all of them)
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on kms_database_key
|
|
for each row execute procedure immutable_columns('private_id', 'root_key_id', 'create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on kms_database_key
|
|
for each row execute procedure default_create_time();
|
|
|
|
create table kms_database_key_version (
|
|
private_id wt_private_id primary key,
|
|
database_key_id wt_private_id not null
|
|
references kms_database_key(private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
root_key_version_id wt_private_id not null
|
|
references kms_root_key_version(private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
version wt_version,
|
|
key bytea not null,
|
|
create_time wt_timestamp,
|
|
unique(database_key_id, version)
|
|
);
|
|
|
|
-- define the immutable fields for kms_database_key_version (all of them)
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on kms_database_key_version
|
|
for each row execute procedure immutable_columns('private_id', 'database_key_id', 'root_key_version_id', 'version', 'key', 'create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on kms_database_key_version
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
kms_version_column
|
|
before insert on kms_database_key_version
|
|
for each row execute procedure kms_version_column('database_key_id');
|
|
|
|
create table kms_oplog_key (
|
|
private_id wt_private_id primary key,
|
|
root_key_id wt_private_id not null unique -- there can be only one oplog dek per root key
|
|
references kms_root_key(private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
create_time wt_timestamp
|
|
);
|
|
|
|
-- define the immutable fields for kms_oplog_key (all of them)
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on kms_oplog_key
|
|
for each row execute procedure immutable_columns('private_id', 'root_key_id', 'create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on kms_oplog_key
|
|
for each row execute procedure default_create_time();
|
|
|
|
create table kms_oplog_key_version (
|
|
private_id wt_private_id primary key,
|
|
oplog_key_id wt_private_id not null
|
|
references kms_oplog_key(private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
root_key_version_id wt_private_id not null
|
|
references kms_root_key_version(private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
version wt_version,
|
|
key bytea not null,
|
|
create_time wt_timestamp,
|
|
unique(oplog_key_id, version)
|
|
);
|
|
|
|
-- define the immutable fields for kms_oplog_key_version (all of them)
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on kms_oplog_key_version
|
|
for each row execute procedure immutable_columns('private_id', 'oplog_key_id', 'root_key_version_id', 'version', 'key', 'create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on kms_oplog_key_version
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
kms_version_column
|
|
before insert on kms_oplog_key_version
|
|
for each row execute procedure kms_version_column('oplog_key_id');
|
|
|
|
create table kms_session_key (
|
|
private_id wt_private_id primary key,
|
|
root_key_id wt_private_id not null unique -- there can be only one session dek per root key
|
|
references kms_root_key(private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
create_time wt_timestamp
|
|
);
|
|
|
|
-- define the immutable fields for kms_oplog_key (all of them)
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on kms_session_key
|
|
for each row execute procedure immutable_columns('private_id', 'root_key_id', 'create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on kms_session_key
|
|
for each row execute procedure default_create_time();
|
|
|
|
create table kms_session_key_version (
|
|
private_id wt_private_id primary key,
|
|
session_key_id wt_private_id not null
|
|
references kms_session_key(private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
root_key_version_id wt_private_id not null
|
|
references kms_root_key_version(private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
version wt_version,
|
|
key bytea not null,
|
|
create_time wt_timestamp,
|
|
unique(session_key_id, version)
|
|
);
|
|
|
|
|
|
-- define the immutable fields for kms_session_key_version (all of them)
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on kms_session_key_version
|
|
for each row execute procedure immutable_columns('private_id', 'session_key_id', 'root_key_version_id', 'version', 'key', 'create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on kms_session_key_version
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
kms_version_column
|
|
before insert on kms_session_key_version
|
|
for each row execute procedure kms_version_column('session_key_id');
|
|
|
|
create table kms_token_key (
|
|
private_id wt_private_id primary key,
|
|
root_key_id wt_private_id not null unique -- there can be only one token dek per root key
|
|
references kms_root_key(private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
create_time wt_timestamp
|
|
);
|
|
|
|
-- define the immutable fields for kms_token_key (all of them)
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on kms_token_key
|
|
for each row execute procedure immutable_columns('private_id', 'root_key_id', 'create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on kms_token_key
|
|
for each row execute procedure default_create_time();
|
|
|
|
create table kms_token_key_version (
|
|
private_id wt_private_id primary key,
|
|
token_key_id wt_private_id not null
|
|
references kms_token_key(private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
root_key_version_id wt_private_id not null
|
|
references kms_root_key_version(private_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
version wt_version,
|
|
key bytea not null,
|
|
create_time wt_timestamp,
|
|
unique(token_key_id, version)
|
|
);
|
|
|
|
-- define the immutable fields for kms_token_key_version (all of them)
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on kms_token_key_version
|
|
for each row execute procedure immutable_columns('private_id', 'token_key_id', 'root_key_version_id', 'version', 'key', 'create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on kms_token_key_version
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
kms_version_column
|
|
before insert on kms_token_key_version
|
|
for each row execute procedure kms_version_column('token_key_id');
|
|
|
|
insert into oplog_ticket
|
|
(name, version)
|
|
values
|
|
('kms_root_key', 1),
|
|
('kms_root_key_version', 1);
|
|
|
|
commit;
|
|
`),
|
|
},
|
|
"migrations/40_targets.down.sql": {
|
|
name: "40_targets.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop function insert_target_subtype;
|
|
drop function delete_target_subtype;
|
|
drop function target_scope_valid;
|
|
drop function target_host_set_scope_valid
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/40_targets.up.sql": {
|
|
name: "40_targets.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
|
|
-- insert_target_subtype() is a before insert trigger
|
|
-- function for subtypes of target
|
|
create or replace function
|
|
insert_target_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
insert into target
|
|
(public_id, scope_id)
|
|
values
|
|
(new.public_id, new.scope_id);
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
-- delete_target_subtype() is an after delete trigger
|
|
-- function for subtypes of host
|
|
create or replace function delete_target_subtype()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
delete from target
|
|
where
|
|
public_id = old.public_id;
|
|
return null; -- result is ignored since this is an after trigger
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
-- target_scope_valid() is a before insert trigger function for target
|
|
create or replace function
|
|
target_scope_valid()
|
|
returns trigger
|
|
as $$
|
|
declare scope_type text;
|
|
begin
|
|
-- Fetch the type of scope
|
|
select isc.type from iam_scope isc where isc.public_id = new.scope_id into scope_type;
|
|
if scope_type = 'project' then
|
|
return new;
|
|
end if;
|
|
raise exception 'invalid target scope type % (must be project)', scope_type;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
-- target_host_set_scope_valid() is a before insert trigger function for target_host_set
|
|
create or replace function
|
|
target_host_set_scope_valid()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
perform from
|
|
host_catalog hc,
|
|
host_set hs,
|
|
target t,
|
|
iam_scope s
|
|
where
|
|
hc.public_id = hs.catalog_id and
|
|
hc.scope_id = t.scope_id and
|
|
t.public_id = new.target_id;
|
|
if not found then
|
|
raise exception 'target scope and host set scope are not equal';
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
commit;
|
|
`),
|
|
},
|
|
"migrations/41_targets.down.sql": {
|
|
name: "41_targets.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop table target cascade;
|
|
drop table target_host_set cascade;
|
|
drop table target_tcp;
|
|
drop view target_all_subtypes;
|
|
drop view target_host_set_catalog;
|
|
|
|
|
|
delete
|
|
from oplog_ticket
|
|
where name in (
|
|
'target_tcp'
|
|
);
|
|
|
|
commit;
|
|
`),
|
|
},
|
|
"migrations/41_targets.up.sql": {
|
|
name: "41_targets.up.sql",
|
|
bytes: []byte(`
|
|
/*
|
|
┌─────────────────┐
|
|
┌─────────────────┐ │ target_tcp │
|
|
│ target │ ├─────────────────┤
|
|
├─────────────────┤ │public_id │
|
|
│public_id │┼─────────────○┼│scope_id │
|
|
│scope_id │ │default_port │
|
|
│ │ │name (not null) │
|
|
└─────────────────┘ │description │
|
|
┼ └─────────────────┘
|
|
│
|
|
○
|
|
╱│╲
|
|
┌─────────────────┐
|
|
│ target_host_set │
|
|
├─────────────────┤
|
|
│target_id │
|
|
│host_set_id │
|
|
│ │
|
|
└─────────────────┘
|
|
╲│╱
|
|
○
|
|
│
|
|
│
|
|
┼
|
|
┌─────────────────┐
|
|
│ host_set │
|
|
├─────────────────┤
|
|
│public_id │
|
|
│catalog_id │
|
|
│ │
|
|
└─────────────────┘
|
|
|
|
*/
|
|
|
|
begin;
|
|
|
|
create table target (
|
|
public_id wt_public_id primary key,
|
|
scope_id wt_scope_id not null
|
|
references iam_scope(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
create_time wt_timestamp
|
|
);
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on target
|
|
for each row execute procedure immutable_columns('public_id', 'scope_id', 'create_time');
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on target
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
target_scope_valid
|
|
before insert on target
|
|
for each row execute procedure target_scope_valid();
|
|
|
|
|
|
create table target_host_set(
|
|
target_id wt_public_id
|
|
references target(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
host_set_id wt_public_id
|
|
references host_set(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
primary key(target_id, host_set_id),
|
|
create_time wt_timestamp
|
|
);
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on target_host_set
|
|
for each row execute procedure immutable_columns('target_id', 'host_set_id', 'create_time');
|
|
|
|
create trigger
|
|
target_host_set_scope_valid
|
|
before
|
|
insert on target_host_set
|
|
for each row execute procedure target_host_set_scope_valid();
|
|
|
|
create table target_tcp (
|
|
public_id wt_public_id primary key
|
|
references target(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
scope_id wt_scope_id not null
|
|
references iam_scope(public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
name text not null, -- name is not optional for a target subtype
|
|
description text,
|
|
default_port int, -- default_port can be null
|
|
-- max duration of the session in seconds.
|
|
-- default is 8 hours
|
|
session_max_seconds int not null default 28800
|
|
check(session_max_seconds > 0),
|
|
-- limit on number of session connections allowed. -1 equals no limit
|
|
session_connection_limit int not null default 1
|
|
check(session_connection_limit > 0 or session_connection_limit = -1),
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
version wt_version,
|
|
unique(scope_id, name) -- name must be unique within a scope
|
|
);
|
|
|
|
create trigger
|
|
insert_target_subtype
|
|
before insert on target_tcp
|
|
for each row execute procedure insert_target_subtype();
|
|
|
|
create trigger
|
|
delete_target_subtype
|
|
after delete on target_tcp
|
|
for each row execute procedure delete_target_subtype();
|
|
|
|
-- define the immutable fields for target
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on target_tcp
|
|
for each row execute procedure immutable_columns('public_id', 'scope_id', 'create_time');
|
|
|
|
create trigger
|
|
update_version_column
|
|
after update on target_tcp
|
|
for each row execute procedure update_version_column();
|
|
|
|
create trigger
|
|
update_time_column
|
|
before update on target_tcp
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on target_tcp
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
target_scope_valid
|
|
before insert on target_tcp
|
|
for each row execute procedure target_scope_valid();
|
|
|
|
|
|
-- target_all_subtypes is a union of all target subtypes
|
|
create view target_all_subtypes
|
|
as
|
|
select
|
|
public_id,
|
|
scope_id,
|
|
name,
|
|
description,
|
|
default_port,
|
|
session_max_seconds,
|
|
session_connection_limit,
|
|
version,
|
|
create_time,
|
|
update_time,
|
|
'tcp' as type
|
|
from target_tcp;
|
|
|
|
create view target_set
|
|
as
|
|
select
|
|
hs.public_id,
|
|
hs.catalog_id,
|
|
ths.target_id
|
|
from
|
|
target_host_set ths,
|
|
host_set hs
|
|
where
|
|
hs.public_id = ths.host_set_id;
|
|
|
|
insert into oplog_ticket
|
|
(name, version)
|
|
values
|
|
('target_tcp', 1);
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/50_session.down.sql": {
|
|
name: "50_session.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop table session_state;
|
|
drop table session_state_enm;
|
|
drop table session;
|
|
drop table session_termination_reason_enm;
|
|
drop function insert_session_state;
|
|
drop function insert_new_session_state;
|
|
drop function insert_session;
|
|
drop function update_session_state_on_termination_reason;
|
|
drop function insert_session_state;
|
|
|
|
|
|
delete
|
|
from oplog_ticket
|
|
where name in (
|
|
'session'
|
|
);
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/50_session.up.sql": {
|
|
name: "50_session.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
/*
|
|
|
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
│ iam_scope_org │ │ iam_user │ │ auth_token │
|
|
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
|
|
│ public_id (pk) │ │ public_id (pk) │ │ public_id (pk) │
|
|
│ │ │ │ │ │
|
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
▲fk7 ┼ ▲fk1 ┼ ▲fk6 ┼
|
|
┼ ┼ ┼
|
|
├─────────────────────────────────┴─────────────────────┘
|
|
│
|
|
○
|
|
╱│╲
|
|
┌──────────────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
│ session │╲ fk3▶ │ server │ │ target │
|
|
├──────────────────────────┤─○──────○┼├─────────────────┤ ├─────────────────┤
|
|
│ public_id (pk) │╱ │ private_id (pk) │ │ public_id (pk) │
|
|
│ user_id (fk1) │ │ type (pk) │ │ │
|
|
│ host_id (fk2) │ └─────────────────┘ └─────────────────┘
|
|
│ server_id (fk3) │ ▲fk4 ┼
|
|
│ server_type (fk3) │╲ ┼
|
|
│ target_id (fk4) │─○─────────────────┬─────────────────────┤
|
|
│ host_set_id (fk5) │╱ ┼ ┼
|
|
│ auth_token_id (fk6) │ ▼fk5 ┼ ▼fk2 ┼
|
|
│ scope_id (fk7) │ ┌─────────────────┐ ┌─────────────────┐
|
|
│ termination_reason (fk8) │ │ host_set │ │ host │
|
|
└──────────────────────────┘ ├─────────────────┤ ├─────────────────┤
|
|
▲fk1 ┼ ╲│╱ │ public_id (pk) │ │ public_id (pk) │
|
|
┼ ○ │ │ │ │
|
|
│ │ └─────────────────┘ └─────────────────┘
|
|
│ │
|
|
└─┐ │
|
|
│ │ ┌───────────────────────────────┐
|
|
│ │ │session_termination_reason_enm │
|
|
│ │ fk8▶ ├───────────────────────────────┤
|
|
┼ └──────────○┼│ name │
|
|
╱│╲ └───────────────────────────────┘
|
|
┌──────────────────────────────────────────┐
|
|
│ session_state │
|
|
├──────────────────────────────────────────┤┼○┐
|
|
│ session_id (pk,fk1,fk2,unq1,unq2) │ │◀fk2
|
|
│ state (fk3) │ │
|
|
│ previous_end_time (fk2,unq1) │┼○┘
|
|
│ start_time (pk) │
|
|
│ end_time (unq2) │
|
|
└──────────────────────────────────────────┘
|
|
╲│╱
|
|
○
|
|
│
|
|
│
|
|
┼
|
|
▼fk3 ┼
|
|
┌───────────────────────────────┐
|
|
│ session_state_enm │
|
|
├───────────────────────────────┤
|
|
│ name │
|
|
└───────────────────────────────┘
|
|
|
|
*/
|
|
|
|
create table session_termination_reason_enm (
|
|
name text primary key
|
|
check (
|
|
name in (
|
|
'unknown',
|
|
'timed out',
|
|
'closed by end-user',
|
|
'terminated',
|
|
'network error',
|
|
'system error'
|
|
)
|
|
)
|
|
);
|
|
|
|
insert into session_termination_reason_enm (name)
|
|
values
|
|
('unknown'),
|
|
('timed out'),
|
|
('closed by end-user'),
|
|
('terminated'),
|
|
('network error'),
|
|
('system error');
|
|
|
|
create table session (
|
|
public_id wt_public_id primary key,
|
|
-- the user of the session
|
|
user_id text -- fk1
|
|
-- not using the wt_user_id domain type because it is marked 'not null'
|
|
references iam_user (public_id)
|
|
on delete set null
|
|
on update cascade,
|
|
-- the host the user is connected to via this session
|
|
host_id wt_public_id -- fk2
|
|
references host (public_id)
|
|
on delete set null
|
|
on update cascade,
|
|
-- the worker proxying the connection between the user and the host
|
|
server_id text, -- fk3
|
|
server_type text,-- fk3
|
|
foreign key (server_id, server_type)
|
|
references server (private_id, type)
|
|
on delete set null
|
|
on update cascade,
|
|
-- the target the host was chosen from and the user was authorized to
|
|
-- connect to
|
|
target_id wt_public_id -- fk4
|
|
references target (public_id)
|
|
on delete set null
|
|
on update cascade,
|
|
-- the host set the host was chosen from and the user was authorized to
|
|
-- connect to via the target
|
|
host_set_id wt_public_id -- fk5
|
|
references host_set (public_id)
|
|
on delete set null
|
|
on update cascade,
|
|
-- the auth token of the user when this session was created
|
|
auth_token_id wt_public_id -- fk6
|
|
references auth_token (public_id)
|
|
on delete set null
|
|
on update cascade,
|
|
-- the project which owns this session
|
|
scope_id wt_scope_id -- fk7
|
|
references iam_scope_project (scope_id)
|
|
on delete set null
|
|
on update cascade,
|
|
-- Certificate to use when connecting (or if using custom certs, to
|
|
-- serve as the "login"). Raw DER bytes.
|
|
certificate bytea not null,
|
|
-- after this time the connection will be expired, e.g. forcefully terminated
|
|
expiration_time wt_timestamp, -- maybe null
|
|
-- limit on number of session connections allowed. default of 0 equals no limit
|
|
connection_limit int not null default 1
|
|
check(connection_limit > 0 or connection_limit = -1),
|
|
-- trust of first use token
|
|
tofu_token bytea, -- will be null when session is first created
|
|
-- the reason this session ended (null until terminated)
|
|
-- TODO: Make key_id a foreign key once we have DEKs
|
|
key_id text, -- will be null on insert
|
|
-- references kms_database_key_version(private_id)
|
|
-- on delete restrict
|
|
-- on update cascade,
|
|
termination_reason text -- fk8
|
|
references session_termination_reason_enm (name)
|
|
on delete restrict
|
|
on update cascade,
|
|
version wt_version,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp,
|
|
endpoint text -- not part of the warehouse, used to send info to the worker
|
|
);
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on session
|
|
for each row execute procedure immutable_columns('public_id', 'certificate', 'expiration_time', 'connection_limit', 'create_time', 'endpoint');
|
|
|
|
create trigger
|
|
update_version_column
|
|
after update on session
|
|
for each row execute procedure update_version_column();
|
|
|
|
create trigger
|
|
update_time_column
|
|
before update on session
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on session
|
|
for each row execute procedure default_create_time();
|
|
|
|
create or replace function
|
|
insert_session()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
case
|
|
when new.user_id is null then
|
|
raise exception 'user_id is null';
|
|
when new.host_id is null then
|
|
raise exception 'host_id is null';
|
|
when new.target_id is null then
|
|
raise exception 'target_id is null';
|
|
when new.host_set_id is null then
|
|
raise exception 'host_set_id is null';
|
|
when new.auth_token_id is null then
|
|
raise exception 'auth_token_id is null';
|
|
when new.scope_id is null then
|
|
raise exception 'scope_id is null';
|
|
when new.endpoint is null then
|
|
raise exception 'endpoint is null';
|
|
else
|
|
end case;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger
|
|
insert_session
|
|
before insert on session
|
|
for each row execute procedure insert_session();
|
|
|
|
create or replace function
|
|
insert_new_session_state()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
insert into session_state (session_id, state)
|
|
values
|
|
(new.public_id, 'pending');
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger
|
|
insert_new_session_state
|
|
after insert on session
|
|
for each row execute procedure insert_new_session_state();
|
|
|
|
-- update_connection_state_on_closed_reason() is used in an update insert trigger on the
|
|
-- session_connection table. it will valiadate that all the session's
|
|
-- connections are closed, and then insert a state of "closed" in
|
|
-- session_connection_state for the closed session connection.
|
|
create or replace function
|
|
update_session_state_on_termination_reason()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if new.termination_reason is not null then
|
|
-- check to see if there are any open connections.
|
|
perform from
|
|
session_connection sc,
|
|
session_connection_state scs
|
|
where
|
|
sc.public_id = scs.connection_id and
|
|
scs.state != 'closed' and
|
|
sc.session_id = new.public_id;
|
|
if found then
|
|
raise 'session %s has existing open connections', new.public_id;
|
|
end if;
|
|
|
|
-- check to see if there's a terminated state already, before inserting a
|
|
-- new one.
|
|
perform from
|
|
session_state ss
|
|
where
|
|
ss.session_id = new.public_id and
|
|
ss.state = 'terminated';
|
|
if found then
|
|
return new;
|
|
end if;
|
|
|
|
insert into session_state (session_id, state)
|
|
values
|
|
(new.public_id, 'terminated');
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
|
|
create trigger
|
|
update_session_state_on_termination_reason
|
|
after update of termination_reason on session
|
|
for each row execute procedure update_session_state_on_termination_reason();
|
|
|
|
|
|
create table session_state_enm (
|
|
name text primary key
|
|
check (
|
|
name in ('pending', 'active', 'canceling', 'terminated')
|
|
)
|
|
);
|
|
|
|
insert into session_state_enm (name)
|
|
values
|
|
('pending'),
|
|
('active'),
|
|
('canceling'),
|
|
('terminated');
|
|
|
|
/*
|
|
|
|
|
|
┌────────────────┐
|
|
start │ │
|
|
. │ Canceling │
|
|
(●) ┌────▶│ │─────┐
|
|
' │ │ │ │
|
|
│ │ └────────────────┘ │
|
|
│ │ │
|
|
▼ │ ▼
|
|
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
|
|
│ │ │ │ │ │
|
|
│ Pending │ │ Active │ │ Terminated │
|
|
│ │──────────▶│ │──────────▶│ │
|
|
│ │ │ │ │ │
|
|
└────────────────┘ └────────────────┘ └────────────────┘
|
|
│ │
|
|
│ │
|
|
│ │
|
|
│ │
|
|
└──────────────────────▶ ◉ End ◀────────────────────────┘
|
|
|
|
*/
|
|
|
|
-- Design influenced by:
|
|
-- Joe Celko's SQL for Smarties: Advanced SQL Programming, 5th edition
|
|
-- Chapter 12, p270
|
|
create table session_state (
|
|
session_id wt_public_id not null -- fk1, fk2
|
|
references session (public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
state text not null -- fk3
|
|
references session_state_enm(name)
|
|
on delete restrict
|
|
on update cascade,
|
|
previous_end_time timestamp with time zone, -- fk2 -- null means first state
|
|
start_time timestamp with time zone default current_timestamp not null,
|
|
constraint previous_end_time_and_start_time_in_sequence
|
|
check (previous_end_time <= start_time),
|
|
end_time timestamp with time zone, -- null means unfinished current state
|
|
constraint start_and_end_times_in_sequence
|
|
check (start_time <= end_time),
|
|
constraint end_times_in_sequence
|
|
check (previous_end_time <> end_time),
|
|
primary key (session_id, start_time),
|
|
unique (session_id, previous_end_time), -- null means first state
|
|
unique (session_id, end_time), -- one null current state
|
|
foreign key (session_id, previous_end_time) -- self-reference
|
|
references session_state (session_id, end_time)
|
|
);
|
|
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on session_state
|
|
for each row execute procedure immutable_columns('session_id', 'start_time', 'previous_end_time');
|
|
|
|
create or replace function
|
|
insert_session_state()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
|
|
update session_state
|
|
set end_time = now()
|
|
where session_id = new.session_id
|
|
and end_time is null;
|
|
|
|
if not found then
|
|
new.previous_end_time = null;
|
|
new.start_time = now();
|
|
new.end_time = null;
|
|
return new;
|
|
end if;
|
|
|
|
new.previous_end_time = now();
|
|
new.start_time = now();
|
|
new.end_time = null;
|
|
return new;
|
|
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
|
|
create trigger insert_session_state before insert on session_state
|
|
for each row execute procedure insert_session_state();
|
|
|
|
create view session_with_state as
|
|
select
|
|
s.public_id,
|
|
s.user_id,
|
|
s.host_id,
|
|
s.server_id,
|
|
s.server_type,
|
|
s.target_id,
|
|
s.host_set_id,
|
|
s.auth_token_id,
|
|
s.scope_id,
|
|
s.certificate,
|
|
s.expiration_time,
|
|
s.connection_limit,
|
|
s.tofu_token,
|
|
s.key_id,
|
|
s.termination_reason,
|
|
s.version,
|
|
s.create_time,
|
|
s.update_time,
|
|
s.endpoint,
|
|
ss.state,
|
|
ss.previous_end_time,
|
|
ss.start_time,
|
|
ss.end_time
|
|
from
|
|
session s,
|
|
session_state ss
|
|
where
|
|
s.public_id = ss.session_id;
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/51_connection.down.sql": {
|
|
name: "51_connection.down.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
drop table session_connection_state;
|
|
drop table session_connection_state_enm;
|
|
drop table session_connection;
|
|
drop table session_connection_closed_reason_enm;
|
|
drop function insert_session_connection_state;
|
|
drop function insert_new_connection_state;
|
|
drop function update_connection_state_on_closed_reason;
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
"migrations/51_connection.up.sql": {
|
|
name: "51_connection.up.sql",
|
|
bytes: []byte(`
|
|
begin;
|
|
|
|
/*
|
|
|
|
┌────────────────┐
|
|
│ session │
|
|
├────────────────┤
|
|
│ public_id (pk) │
|
|
│ │
|
|
│ │
|
|
└────────────────┘
|
|
▲fk1 ┼
|
|
┼
|
|
│
|
|
│
|
|
┼
|
|
╱│╲ ┌──────────────────────────────────────┐
|
|
┌───────────────────────┐ │ session_connection_closed_reason_enm │
|
|
│ session_connection │╲ fk2▶ ├──────────────────────────────────────┤
|
|
├───────────────────────┤─○───○┼│ name │
|
|
│ public_id (pk) │╱ └──────────────────────────────────────┘
|
|
│ session_id (fk1) │
|
|
│ closed_reason (fk2) │
|
|
└───────────────────────┘
|
|
▲fk1 ┼
|
|
┼
|
|
│
|
|
│
|
|
┼
|
|
╱│╲
|
|
┌──────────────────────────────────────────┐
|
|
│ session_connection_state │
|
|
├──────────────────────────────────────────┤┼○┐
|
|
│ connection_id (pk,fk1,fk2,unq1,unq2) │ │◀fk2
|
|
│ state (fk3) │ │
|
|
│ previous_end_time (fk2,unq1) │┼○┘
|
|
│ start_time (pk) │
|
|
│ end_time (unq2) │
|
|
└──────────────────────────────────────────┘
|
|
╲│╱
|
|
○
|
|
│
|
|
│
|
|
┼
|
|
▼fk3 ┼
|
|
┌───────────────────────────────┐
|
|
│ session_connection_state_enm │
|
|
├───────────────────────────────┤
|
|
│ name │
|
|
└───────────────────────────────┘
|
|
|
|
*/
|
|
|
|
create table session_connection_closed_reason_enm (
|
|
name text primary key
|
|
check (
|
|
name in (
|
|
'unknown',
|
|
'timed out',
|
|
'closed by end-user',
|
|
'canceled',
|
|
'network error',
|
|
'system error'
|
|
)
|
|
)
|
|
);
|
|
|
|
insert into session_connection_closed_reason_enm (name)
|
|
values
|
|
('unknown'),
|
|
('timed out'),
|
|
('closed by end-user'),
|
|
('canceled'),
|
|
('network error'),
|
|
('system error');
|
|
|
|
-- A session connection is one connection proxied by a worker from a client to
|
|
-- a endpoint for a session. The client initiates the connection to the worker
|
|
-- and the worker initiates the connection to the endpoint.
|
|
-- A session can have zero or more session connections.
|
|
create table session_connection (
|
|
public_id wt_public_id primary key,
|
|
session_id wt_public_id not null
|
|
references session (public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
-- the client_tcp_address is the network address of the client which initiated
|
|
-- the connection to a worker
|
|
client_tcp_address inet, -- maybe null on insert
|
|
-- the client_tcp_port is the network port at the address of the client the
|
|
-- worker proxied a connection for the user
|
|
client_tcp_port integer -- maybe null on insert
|
|
check(
|
|
client_tcp_port > 0
|
|
and
|
|
client_tcp_port <= 65535
|
|
),
|
|
-- the endpoint_tcp_address is the network address of the endpoint which the
|
|
-- worker initiated the connection to, for the user
|
|
endpoint_tcp_address inet, -- maybe be null on insert
|
|
-- the endpoint_tcp_port is the network port at the address of the endpoint the
|
|
-- worker proxied a connection to, for the user
|
|
endpoint_tcp_port integer -- maybe null on insert
|
|
check(
|
|
endpoint_tcp_port > 0
|
|
and
|
|
endpoint_tcp_port <= 65535
|
|
),
|
|
-- the total number of bytes received by the worker from the client and sent
|
|
-- to the endpoint for this connection
|
|
bytes_up bigint -- can be null
|
|
check (
|
|
bytes_up is null
|
|
or
|
|
bytes_up >= 0
|
|
),
|
|
-- the total number of bytes received by the worker from the endpoint and sent
|
|
-- to the client for this connection
|
|
bytes_down bigint -- can be null
|
|
check (
|
|
bytes_down is null
|
|
or
|
|
bytes_down >= 0
|
|
),
|
|
closed_reason text
|
|
references session_connection_closed_reason_enm (name)
|
|
on delete restrict
|
|
on update cascade,
|
|
version wt_version,
|
|
create_time wt_timestamp,
|
|
update_time wt_timestamp
|
|
);
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on session_connection
|
|
for each row execute procedure immutable_columns('public_id', 'session_id', 'create_time');
|
|
|
|
create trigger
|
|
update_version_column
|
|
after update on session_connection
|
|
for each row execute procedure update_version_column();
|
|
|
|
create trigger
|
|
update_time_column
|
|
before update on session_connection
|
|
for each row execute procedure update_time_column();
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on session_connection
|
|
for each row execute procedure default_create_time();
|
|
|
|
-- insert_new_connection_state() is used in an after insert trigger on the
|
|
-- session_connection table. it will insert a state of "authorized" in
|
|
-- session_connection_state for the new session connection.
|
|
create or replace function
|
|
insert_new_connection_state()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
insert into session_connection_state (connection_id, state)
|
|
values
|
|
(new.public_id, 'authorized');
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger
|
|
insert_new_connection_state
|
|
after insert on session_connection
|
|
for each row execute procedure insert_new_connection_state();
|
|
|
|
-- update_connection_state_on_closed_reason() is used in an update insert trigger on the
|
|
-- session_connection table. it will insert a state of "closed" in
|
|
-- session_connection_state for the closed session connection.
|
|
create or replace function
|
|
update_connection_state_on_closed_reason()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if new.closed_reason is not null then
|
|
-- check to see if there's a closed state already, before inserting a
|
|
-- new one.
|
|
perform from
|
|
session_connection_state cs
|
|
where
|
|
cs.connection_id = new.public_id and
|
|
cs.state = 'closed';
|
|
if not found then
|
|
insert into session_connection_state (connection_id, state)
|
|
values
|
|
(new.public_id, 'closed');
|
|
end if;
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger
|
|
update_connection_state_on_closed_reason
|
|
after update of closed_reason on session_connection
|
|
for each row execute procedure update_connection_state_on_closed_reason();
|
|
|
|
create table session_connection_state_enm (
|
|
name text primary key
|
|
check (
|
|
name in ('authorized', 'connected', 'closed')
|
|
)
|
|
);
|
|
|
|
insert into session_connection_state_enm (name)
|
|
values
|
|
('authorized'),
|
|
('connected'),
|
|
('closed');
|
|
|
|
|
|
create table session_connection_state (
|
|
connection_id wt_public_id not null
|
|
references session_connection (public_id)
|
|
on delete cascade
|
|
on update cascade,
|
|
state text not null
|
|
references session_connection_state_enm(name)
|
|
on delete restrict
|
|
on update cascade,
|
|
previous_end_time timestamp with time zone, -- fk2 -- null means first state
|
|
start_time timestamp with time zone default current_timestamp not null,
|
|
constraint previous_end_time_and_start_time_in_sequence
|
|
check (previous_end_time <= start_time),
|
|
end_time timestamp with time zone, -- null means unfinished current state
|
|
constraint start_and_end_times_in_sequence
|
|
check (start_time <= end_time),
|
|
constraint end_times_in_sequence
|
|
check (previous_end_time <> end_time),
|
|
primary key (connection_id, start_time),
|
|
unique (connection_id, previous_end_time), -- null means first state
|
|
unique (connection_id, end_time), -- one null current state
|
|
foreign key (connection_id, previous_end_time) -- self-reference
|
|
references session_connection_state (connection_id, end_time)
|
|
);
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on session_connection_state
|
|
for each row execute procedure immutable_columns('connection_id', 'state', 'start_time');
|
|
|
|
create or replace function
|
|
insert_session_connection_state()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
|
|
update session_connection_state
|
|
set end_time = now()
|
|
where connection_id = new.connection_id
|
|
and end_time is null;
|
|
|
|
if not found then
|
|
new.previous_end_time = null;
|
|
new.start_time = now();
|
|
new.end_time = null;
|
|
return new;
|
|
end if;
|
|
|
|
new.previous_end_time = now();
|
|
new.start_time = now();
|
|
new.end_time = null;
|
|
return new;
|
|
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create trigger insert_session_connection_state before insert on session_connection_state
|
|
for each row execute procedure insert_session_connection_state();
|
|
|
|
commit;
|
|
|
|
`),
|
|
},
|
|
}
|