Add alias schema changes (#4230)

* Adding alias resource

* Update website/content/docs/install-boundary/system-requirements.mdx

---------

Co-authored-by: Irena Rindos <irenarindos@users.noreply.github.com>
pull/4470/head
Todd 2 years ago
parent df35f85971
commit 7e2038bfcc

@ -4,6 +4,11 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
## Next
### Added dependency
* postgres citext dependency added to enable aliases to be globally unique
in a case insensitive way.
## 0.15.1 (2024/02/28)
### Bug Fixes

@ -0,0 +1,11 @@
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
begin;
-- https://www.postgresql.org/docs/14/citext.html allows us to make
-- case-insensitive uniqueness constraints which is useful to us for
-- aliases.
create extension "citext";
commit;

@ -0,0 +1,46 @@
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
begin;
-- wt_alias defines a type for alias values
create domain wt_alias as citext
constraint wt_alias_too_short
check (length(trim(value)) > 0)
constraint wt_alias_no_suround_spaces
check (trim(value) = value);
comment on domain wt_alias is
'standard value column for an alias';
-- wt_target_alias defines a type for target alias values
create domain wt_target_alias as wt_alias
constraint wt_target_alias_too_long
check (length(trim(value)) < 254)
-- dns names consists of at least one label joined together by a "."
-- each label can consist of a-z 0-9 and "-" case insensitive
-- a label cannot start or end with a "-"
-- a label can be between 1 and 63 characters long
-- the final label in the dns name cannot be all numeric
-- see https://en.wikipedia.org/wiki/Domain_Name_System#Domain_name_syntax,_internationalization
--
-- Notes on the regex:
-- "^(?!-)[a-z0-9-]{0,62}[a-z0-9]" ensures that there is at least one label
-- * [a-z0-9-]{0,62} allows for the first 0-62 characters to be a-z 0-9 or "-"
-- * (?!-) is a look ahead to ensure the string does not start with a "-"
-- * [a-z0-9] at the end ensures that the string ends with a-z 0-9 which
-- enforces that the label is at least 1 character long which, when
-- combined with the previous regex, ensures that the label is between
-- 1 and 63 characters long
-- "(\.((?!-)[a-z0-9-]{0,62}[a-z0-9]))*$" is almost identical to the
-- previous section and allows for 0 or more additional labels, all of
-- which must start with a "."
-- The constraint that the final label is not all numeric is enforced by
-- the separate constraint wt_target_alias_tld_not_only_numeric
constraint wt_target_alias_value_shape
check (value ~* '^(?!-)[a-z0-9-]{0,62}[a-z0-9](\.((?!-)[a-z0-9-]{0,62}[a-z0-9]))*$')
constraint wt_target_alias_tld_not_only_numeric
check (substring(value from '[^.]*$') !~ '^[0-9]+$');
comment on domain wt_target_alias is
'standard value column for a target alias';
commit;

@ -0,0 +1,70 @@
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
begin;
create table alias (
public_id wt_public_id primary key,
scope_id wt_scope_id not null
constraint iam_scope_fkey
references iam_scope (public_id)
on delete cascade
on update cascade
constraint alias_must_be_in_global_scope
check(
scope_id = 'global'
),
value wt_alias not null
constraint alias_value_uq
unique,
constraint alias_scope_id_value_public_id_uq
unique(scope_id, value, public_id)
);
comment on table alias is
'alias is a base table for the alias type. '
'Each row is owned by a single scope and maps 1-to-1 to a row in one of the alias subtype tables.';
create trigger immutable_columns before update on alias
for each row execute procedure immutable_columns('public_id', 'scope_id');
-- insert_alias_subtype() is a before insert trigger
-- function for subtypes of alias
create function insert_alias_subtype() returns trigger
as $$
begin
insert into alias
(public_id, value, scope_id)
values
(new.public_id, new.value, new.scope_id);
return new;
end;
$$ language plpgsql;
comment on function insert_alias_subtype() is
'insert_alias_subtype() inserts a record into the base alias table when a corresponding record is inserted into the subtype table';
-- delete_alias_subtype() is an after delete trigger
-- function for subtypes of alias
create function delete_alias_subtype() returns trigger
as $$
begin
delete from alias
where
public_id = old.public_id;
return null;
end;
$$ language plpgsql;
comment on function delete_alias_subtype() is
'delete_alias_subtype() deletes the base alias record when the corresponding record is deleted from the subtype table';
create function update_alias_subtype() returns trigger
as $$
begin
update alias set value = new.value where public_id = new.public_id and new.value != value;
return new;
end;
$$ language plpgsql;
comment on function update_alias_subtype() is
'update_alias_subtype() updates the base table value column with the new value from the subtype table';
commit;

@ -0,0 +1,113 @@
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
begin;
create table alias_target (
public_id wt_public_id primary key,
name wt_name,
description wt_description,
scope_id wt_scope_id not null
constraint iam_scope_fkey
references iam_scope (public_id)
on delete cascade
on update cascade,
value wt_target_alias not null,
-- destination_id is used here instead of target_id because many subtyped
-- aliases may be coming in the future. Since Boundary's method for
-- updating these fields use update masks derived from the API resource and
-- there is a single API resource for each subtype with a field that is
-- generic enough to be used by all subtypes, this column name also needs to
-- be generic enough across subtypes.
destination_id wt_public_id
constraint target_fkey
references target (public_id)
on delete set null
on update cascade,
create_time wt_timestamp,
update_time wt_timestamp,
version wt_version,
host_id wt_public_id
constraint destination_id_set_when_host_id_is_set
check(
destination_id is not null
or
(
destination_id is null
and
host_id is null
)
),
constraint alias_target_scope_id_name_uq
unique(scope_id, name),
constraint alias_fkey
foreign key (scope_id, value, public_id)
references alias (scope_id, value, public_id)
on delete cascade
on update cascade
deferrable initially deferred
);
comment on table alias_target is
'alias_target is a subtype of alias. '
'Each row is owned by a single scope and maps 1-to-1 to a row in the alias table.';
create index alias_target_create_time_public_id_idx
on alias_target (create_time desc, public_id desc);
create index alias_target_update_time_public_id_idx
on alias_target (update_time desc, public_id desc);
create function delete_host_id_if_destination_id_is_null() returns trigger
as $$
begin
if new.destination_id is null then
new.host_id = null;
end if;
return new;
end;
$$ language plpgsql;
create trigger delete_host_id_if_destination_id_is_null before update on alias_target
for each row execute procedure delete_host_id_if_destination_id_is_null();
create trigger insert_alias_subtype before insert on alias_target
for each row execute procedure insert_alias_subtype();
create trigger update_alias_subtype after update on alias_target
for each row execute procedure update_alias_subtype();
create trigger delete_alias_subtype after delete on alias_target
for each row execute procedure delete_alias_subtype();
create trigger update_version_column after update on alias_target
for each row execute procedure update_version_column();
create trigger update_time_column before update on alias_target
for each row execute procedure update_time_column();
create trigger default_create_time_column before insert on alias_target
for each row execute procedure default_create_time();
create trigger immutable_columns before update on alias_target
for each row execute procedure immutable_columns('public_id', 'scope_id', 'create_time');
-- Alias delete tracking tables
create table alias_target_deleted (
public_id wt_public_id primary key,
delete_time wt_timestamp not null
);
comment on table alias_target_deleted is
'alias_target_deleted holds the ID and delete_time of every deleted target alias. '
'It is automatically trimmed of records older than 30 days by a job.';
create trigger insert_deleted_id after delete on alias_target
for each row execute procedure insert_deleted_id('alias_target_deleted');
create index alias_target_deleted_delete_time_idx on alias_target_deleted (delete_time);
insert into oplog_ticket (name, version)
values
('alias_target', 1);
commit;

@ -32,6 +32,7 @@ TESTS ?= tests/setup/*.sql \
tests/domain/*.sql \
tests/history/*.sql \
tests/recording/*.sql \
tests/alias/*.sql \
tests/auth/*/*.sql \
tests/purge/*.sql \
tests/pagination/*.sql \

@ -506,6 +506,16 @@ begin;
('p____gcolors', 'tssh______cg', 'cvl_______g1', 'brokered'),
('p____gcolors', 'tssh______cg', 'cvl__ssh__g1', 'injected_application');
insert into alias_target
(scope_id, public_id, value, destination_id)
values
('global', 'alt__t____cb', 'blue.tcp.target', 't_________cb'),
('global', 'alt__t____cr', 'red.tcp.target', 't_________cr'),
('global', 'alt__t____cg', 'green.tcp.target', 't_________cg'),
('global', 'alt__tssh_cb', 'blue.ssh.target', 'tssh______cb'),
('global', 'alt__tssh_cr', 'red.ssh.target', 'tssh______cr'),
('global', 'alt__tssh_cg', 'green.ssh.target', 'tssh______cg');
insert into session
(project_id, target_id, public_id, user_id, auth_token_id, certificate, endpoint)
values

@ -0,0 +1,76 @@
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
-- alias tests triggers:
-- insert_alias_subtype
-- update_alias_subtype
-- delete_alias_subtype
begin;
select plan(17);
select wtt_load('widgets', 'iam', 'kms', 'auth');
-- validate the setup data
select is(count(*), 1::bigint) from alias_target where public_id = 'alt__t____cb';
select is(count(*), 1::bigint) from alias_target where public_id = 'alt__t____cr';
select is(count(*), 1::bigint) from alias_target where public_id = 'alt__t____cg';
-- validate the insert triggers
prepare insert_target_alias as
insert into alias_target
(scope_id, public_id, value, destination_id)
values
('global', 'alt__t___2cb', 'second.blue.tcp.target', 't_________cb');
select lives_ok('insert_target_alias');
select is(count(*), 1::bigint) from alias_target where public_id = 'alt__t___2cb';
select is(count(*), 1::bigint) from alias where public_id = 'alt__t___2cb';
-- validate the update triggers
prepare update_target_alias as
update alias_target
set value = 'updated.red.tcp.target.updated'
where public_id = 'alt__t____cr';
select lives_ok('update_target_alias');
select is(count(*), 1::bigint) from alias_target where public_id = 'alt__t____cr' and value = 'updated.red.tcp.target.updated';
select is(count(*), 1::bigint) from alias where public_id = 'alt__t____cr' and value = 'updated.red.tcp.target.updated';
-- validate delete_host_id_if_destination_id_is_null
update alias_target
set host_id = 'hst_1234567890'
where
public_id = 'alt__t____cr'
or public_id = 'alt__tssh_cr';
select is(count(*), 2::bigint) from alias_target where host_id = 'hst_1234567890';
prepare delete_destination_target as
delete from target_ssh
where public_id = 'tssh______cr';
select lives_ok('delete_destination_target');
select is(count(*), 1::bigint) from alias_target where host_id = 'hst_1234567890';
prepare update_remove_destination_id as
update alias_target
set destination_id = null
where public_id = 'alt__t____cr';
select lives_ok('update_remove_destination_id');
select is(count(*), 0::bigint) from alias_target where host_id = 'hst_1234567890';
-- validate the delete triggers
prepare delete_target_alias as
delete
from alias_target
where public_id = 'alt__t____cg';
select lives_ok('delete_target_alias');
select is(count(*), 0::bigint) from alias_target where public_id = 'alt__t____cg';
select is(count(*), 0::bigint) from alias where public_id = 'alt__t____cg';
select * from finish();
rollback;

@ -0,0 +1,50 @@
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
begin;
select plan(11);
select wtt_load('widgets', 'iam', 'kms', 'auth');
select is(count(*), 1::bigint) from alias_target where public_id = 'alt__t____cb' and destination_id = 't_________cb';
-- validate destination id and host id can be updated
prepare update_target_alias_destination as
update alias_target
set (destination_id, host_id) = ('t_________cg', 'h_________cg')
where public_id = 'alt__t____cb';
select lives_ok('update_target_alias_destination');
select is(count(*), 1::bigint) from alias_target where value = 'blue.tcp.target';
prepare insert_case_insensitive_value_duplicate AS
insert into alias_target (public_id, scope_id, value)
values ('new_alias_for_tests', 'global', 'BLUE.TCP.TARGET');
select throws_like(
'insert_case_insensitive_value_duplicate',
'duplicate key value violates unique constraint "alias_value_uq"'
);
select is(count(*), 0::bigint) from alias_target where public_id = 'alt__t____cb' and destination_id = 't_________cb';
select is(count(*), 1::bigint) from alias_target where public_id = 'alt__t____cb' and destination_id = 't_________cg' and host_id = 'h_________cg';
-- validate deleting a target nulls out the destination id and host id
prepare update_target_alias as
delete from target_tcp
where public_id = 't_________cg';
select lives_ok('update_target_alias');
select is(count(*), 0::bigint) from alias_target where public_id = 'alt__t____cb' and destination_id = 't_________cb';
select is(count(*), 0::bigint) from alias_target where public_id = 'alt__t____cb' and destination_id = 't_________cg';
select is(count(*), 1::bigint) from alias_target where public_id = 'alt__t____cb' and destination_id is null and host_id is null;
-- validate a host id cant be set if the destination id is not set
prepare insert_destination_id_not_set_when_host_id_is_set as
insert into alias_target (public_id, scope_id, value, host_id)
values ('unset_destination_id', 'global', 'unset.destination.id', 'h_________cb');
select throws_like(
'insert_destination_id_not_set_when_host_id_is_set',
'new row for relation "alias_target" violates check constraint "destination_id_set_when_host_id_is_set"'
);
select * from finish();
rollback;

@ -0,0 +1,135 @@
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
-- controller_id tests:
-- validates the wt_controller_id domain
begin;
select plan(16);
select has_domain('wt_target_alias');
create table target_alias_testing (
v wt_target_alias
);
prepare empty_insert as insert into target_alias_testing (v) values ('');
select throws_like(
'empty_insert',
'%"wt_alias_too_short"',
'We should error for empty values'
);
prepare valid_inserts as insert into target_alias_testing (v) values
('a'),
('A'),
('192.168.1.9-9'),
('foo'),
('a.b.c'),
('A.B.C'),
('a-b-c'),
('a.9-9'),
('9things'),
('9-things'),
('hp--something.test.com'),
('test-for-long-name-which-is-almost-over-the-limit-of-characters'),
('TEST-FOR-LONG-NAME-WHICH-IS-ALMOST-OVER-THE-LIMIT-OF-CHARACTERS'),
('test-for-long-name-which-is-almost-over-the-limit-of-characters.another-label'),
('test.test-for-long-name-which-is-almost-over-the-limit-of-characters'),
('test-for-long-name-which-is-almost-over-the-limit-of-characters.test-for-long-name-which-is-almost-over-the-limit-of-characters'),
('9-things.9-things');
select lives_ok('valid_inserts');
prepare label_too_long as insert into target_alias_testing (v) values
('test-for-long-name-which-is-almost-over-the-limit-of-charactersX');
select throws_like(
'label_too_long',
'%"wt_target_alias_value_shape"'
);
prepare label_too_long_2 as insert into target_alias_testing (v) values
('a.test-for-long-name-which-is-almost-over-the-limit-of-charactersX');
select throws_like(
'label_too_long_2',
'%"wt_target_alias_value_shape"'
);
prepare label_too_long_3 as insert into target_alias_testing (v) values
('test-for-long-name-which-is-almost-over-the-limit-of-charactersX.a');
select throws_like(
'label_too_long_3',
'%"wt_target_alias_value_shape"'
);
prepare starting_with_hyphen as insert into target_alias_testing (v) values
('-test.com');
select throws_like(
'starting_with_hyphen',
'%"wt_target_alias_value_shape"'
);
prepare starting_with_hyphen2 as insert into target_alias_testing (v) values
('a.-test');
select throws_like(
'starting_with_hyphen2',
'%"wt_target_alias_value_shape"'
);
prepare ending_with_hyphen as insert into target_alias_testing (v) values
('test-.com');
select throws_like(
'ending_with_hyphen',
'%"wt_target_alias_value_shape"'
);
prepare ending_with_hyphen2 as insert into target_alias_testing (v) values
('a.test-');
select throws_like(
'ending_with_hyphen2',
'%"wt_target_alias_value_shape"'
);
prepare ending_with_hyphen3 as insert into target_alias_testing (v) values
('a.9-');
select throws_like(
'ending_with_hyphen3',
'%"wt_target_alias_value_shape"'
);
prepare empty_label as insert into target_alias_testing (v) values
('a..com');
select throws_like(
'empty_label',
'%"wt_target_alias_value_shape"'
);
prepare empty_label2 as insert into target_alias_testing (v) values
('.a.com');
select throws_like(
'empty_label2',
'%"wt_target_alias_value_shape"'
);
prepare empty_label3 as insert into target_alias_testing (v) values
('a.com.');
select throws_like(
'empty_label3',
'%"wt_target_alias_value_shape"'
);
prepare numeric_only_tld as insert into target_alias_testing (v) values
('a.123');
select throws_like(
'numeric_only_tld',
'%"wt_target_alias_tld_not_only_numeric"'
);
prepare numeric_only_tld2 as insert into target_alias_testing (v) values
('a.9');
select throws_like(
'numeric_only_tld2',
'%"wt_target_alias_tld_not_only_numeric"'
);
select * from finish();
rollback;

@ -0,0 +1,12 @@
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
begin;
select plan(2);
select has_index('alias_target', 'alias_target_create_time_public_id_idx', array['create_time', 'public_id']);
select has_index('alias_target', 'alias_target_update_time_public_id_idx', array['update_time', 'public_id']);
select * from finish();
rollback;

@ -154,7 +154,7 @@ When you initialize the database with the `boundary database init` command, the
### Required PostgreSQL modules
Boundary has a dependency on the PostgreSQL [pgcrypto](https://www.postgresql.org/docs/11/pgcrypto.html) module, which is one of the standard modules that is supplied with PostgreSQL.
Boundary has a dependency on the PostgreSQL [pgcrypto](https://www.postgresql.org/docs/11/pgcrypto.html) module and [citext](https://www.postgresql.org/docs/11/citext.html), which are part of the standard modules that are supplied with PostgreSQL.
Refer to the PostgreSQL documentation for more information.
## Load balancer recommendations

Loading…
Cancel
Save