You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/db/migrations/postgres.gen.go

3921 lines
129 KiB

// 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 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
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,
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
-- 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
);
create trigger
immutable_columns
before
update on session
for each row execute procedure immutable_columns('public_id', 'certificate', 'expiration_time', 'create_time');
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';
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
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;
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();
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',
'cancelled',
'network error',
'system error'
)
)
);
insert into session_connection_closed_reason_enm (name)
values
('unknown'),
('timed out'),
('closed by end-user'),
('cancelled'),
('network error'),
('system error');
-- A session connection is one connection proxied by a worker from a client to
-- a backend for a session. The client initiates the connection to the worker
-- and the worker initiates the connection to the backend.
-- 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 not null,
-- 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 not null
check(
client_tcp_port > 0
and
client_tcp_port <= 65535
),
-- the backend_tcp_address is the network address of the backend which the
-- worker initiated the connection to, for the user
backend_tcp_address inet not null,
-- the backend_tcp_port is the network port at the address of the backend the
-- worker proxied a connection to, for the user
backend_tcp_port integer not null
check(
backend_tcp_port > 0
and
backend_tcp_port <= 65535
),
-- the total number of bytes received by the worker from the client and sent
-- to the backend 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 backend 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', 'client_tcp_address', 'client_tcp_port', 'backend_tcp_address', 'backend_tcp_port', '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 "connected" 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, 'connected');
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
insert into session_connection_state (connection_id, state)
values
(new.public_id, 'closed');
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 ('connected', 'closed')
)
);
insert into session_connection_state_enm (name)
values
('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;
`),
},
}