From 3a64fb3d588b606aa7a15a6fb3d4e3b8b8e685c9 Mon Sep 17 00:00:00 2001 From: Michael Gaffney Date: Tue, 16 May 2023 14:17:53 -0400 Subject: [PATCH] feat(sql): Add history tables for static credentials This adds history tables for `credential_static_store`, `credential_static_json_credential`, `credential_static_username_password_credential`, and `credential_static_ssh_private_key_credential`. This also adds a base table for the static credential history tables. --- .../70/13_static_credential_history.up.sql | 180 ++++++++++++++++++ .../db/sqltest/initdb.d/01_colors_persona.sql | 56 +++++- .../tests/history/history_table_tests.sql | 13 +- .../tests/history/static_credentials.sql | 38 ++++ .../kms/kms_data_key_destruction_job.sql | 2 +- 5 files changed, 274 insertions(+), 15 deletions(-) create mode 100644 internal/db/schema/migrations/oss/postgres/70/13_static_credential_history.up.sql create mode 100644 internal/db/sqltest/tests/history/static_credentials.sql diff --git a/internal/db/schema/migrations/oss/postgres/70/13_static_credential_history.up.sql b/internal/db/schema/migrations/oss/postgres/70/13_static_credential_history.up.sql new file mode 100644 index 0000000000..2d0a25b260 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/70/13_static_credential_history.up.sql @@ -0,0 +1,180 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: MPL-2.0 + +begin; + + create table credential_static_history_base ( + history_id wt_url_safe_id primary key + ); + comment on table credential_static_history_base is + 'credential_static_history_base is a base history table ' + 'for credential_static history tables.'; + + create function insert_credential_static_history_subtype() returns trigger + as $$ + begin + insert into credential_static_history_base + (history_id) + values + (new.history_id); + return new; + end; + $$ language plpgsql; + comment on function insert_credential_static_history_subtype is + 'insert_credential_static_history_subtype is a before insert trigger ' + 'function for subtypes of credential_static_history_base.'; + + create function delete_credential_static_history_subtype() returns trigger + as $$ + begin + delete + from credential_static_history_base + where history_id = old.history_id; + return null; -- result is ignored since this is an after trigger + end; + $$ language plpgsql; + comment on function delete_credential_static_history_subtype is + 'delete_credential_static_history_subtype() is an after delete trigger ' + 'function for subtypes of credential_static_history_base.'; + + create table credential_static_json_credential_hst ( + public_id wt_public_id not null, + name wt_name, + description wt_description, + project_id wt_public_id not null, + store_id wt_public_id not null, + object_hmac bytea not null, + history_id wt_url_safe_id default wt_url_safe_id() primary key + constraint credential_static_history_base_fkey + references credential_static_history_base (history_id) + on delete cascade + on update cascade, + valid_range tstzrange not null default tstzrange(current_timestamp, null), + constraint credential_static_json_credential_hst_valid_range_excl + exclude using gist (public_id with =, valid_range with &&) + ); + comment on table credential_static_json_credential_hst is + 'credential_static_json_credential_hst is a history table where each row contains the values from a row ' + 'in the credential_static_json_credential table during the time range in the valid_range column.'; + + create trigger insert_credential_static_history_subtype before insert on credential_static_json_credential_hst + for each row execute function insert_credential_static_history_subtype(); + create trigger delete_credential_static_history_subtype after delete on credential_static_json_credential_hst + for each row execute function delete_credential_static_history_subtype(); + + create trigger hst_on_insert after insert on credential_static_json_credential + for each row execute function hst_on_insert(); + create trigger hst_on_update after update on credential_static_json_credential + for each row execute function hst_on_update(); + create trigger hst_on_delete after delete on credential_static_json_credential + for each row execute function hst_on_delete(); + + insert into credential_static_json_credential_hst + (public_id, name, description, project_id, store_id, object_hmac) + select public_id, name, description, project_id, store_id, object_hmac + from credential_static_json_credential; + + create table credential_static_username_password_credential_hst ( + public_id wt_public_id not null, + name wt_name, + description wt_description, + project_id wt_public_id not null, + store_id wt_public_id not null, + username text not null, + password_hmac bytea not null, + history_id wt_url_safe_id default wt_url_safe_id() primary key + constraint credential_static_history_base_fkey + references credential_static_history_base (history_id) + on delete cascade + on update cascade, + valid_range tstzrange not null default tstzrange(current_timestamp, null), + constraint credential_static_user_password_credential_hst_valid_range_excl + exclude using gist (public_id with =, valid_range with &&) + ); + comment on table credential_static_username_password_credential_hst is + 'credential_static_username_password_credential_hst is a history table where each row contains the values from a row ' + 'in the credential_static_username_password_credential table during the time range in the valid_range column.'; + + create trigger insert_credential_static_history_subtype before insert on credential_static_username_password_credential_hst + for each row execute function insert_credential_static_history_subtype(); + create trigger delete_credential_static_history_subtype after delete on credential_static_username_password_credential_hst + for each row execute function delete_credential_static_history_subtype(); + + create trigger hst_on_insert after insert on credential_static_username_password_credential + for each row execute function hst_on_insert(); + create trigger hst_on_update after update on credential_static_username_password_credential + for each row execute function hst_on_update(); + create trigger hst_on_delete after delete on credential_static_username_password_credential + for each row execute function hst_on_delete(); + + insert into credential_static_username_password_credential_hst + (public_id, name, description, project_id, store_id, username, password_hmac) + select public_id, name, description, project_id, store_id, username, password_hmac + from credential_static_username_password_credential; + + create table credential_static_ssh_private_key_credential_hst ( + public_id wt_public_id not null, + name wt_name, + description wt_description, + project_id wt_public_id not null, + store_id wt_public_id not null, + username text not null, + private_key_hmac bytea not null, + private_key_passphrase_hmac bytea, + history_id wt_url_safe_id default wt_url_safe_id() primary key + constraint credential_static_history_base_fkey + references credential_static_history_base (history_id) + on delete cascade + on update cascade, + valid_range tstzrange not null default tstzrange(current_timestamp, null), + constraint credential_static_ssh_priv_key_credential_hst_valid_range_excl + exclude using gist (public_id with =, valid_range with &&) + ); + comment on table credential_static_ssh_private_key_credential_hst is + 'credential_static_ssh_private_key_credential_hst is a history table where each row contains the values from a row ' + 'in the credential_static_ssh_private_key_credential table during the time range in the valid_range column.'; + + create trigger insert_credential_static_history_subtype before insert on credential_static_ssh_private_key_credential_hst + for each row execute function insert_credential_static_history_subtype(); + create trigger delete_credential_static_history_subtype after delete on credential_static_ssh_private_key_credential_hst + for each row execute function delete_credential_static_history_subtype(); + + create trigger hst_on_insert after insert on credential_static_ssh_private_key_credential + for each row execute function hst_on_insert(); + create trigger hst_on_update after update on credential_static_ssh_private_key_credential + for each row execute function hst_on_update(); + create trigger hst_on_delete after delete on credential_static_ssh_private_key_credential + for each row execute function hst_on_delete(); + + insert into credential_static_ssh_private_key_credential_hst + (public_id, name, description, project_id, store_id, username, private_key_hmac, private_key_passphrase_hmac) + select public_id, name, description, project_id, store_id, username, private_key_hmac, private_key_passphrase_hmac + from credential_static_ssh_private_key_credential; + + create table credential_static_store_hst ( + public_id wt_public_id not null, + name wt_name, + description wt_description, + project_id wt_scope_id not null, + history_id wt_url_safe_id default wt_url_safe_id() primary key, + valid_range tstzrange not null default tstzrange(current_timestamp, null), + constraint credential_static_store_hst_valid_range_excl + exclude using gist (public_id with =, valid_range with &&) + ); + comment on table credential_static_store_hst is + 'credential_static_store_hst is a history table where each row contains the values from a row ' + 'in the credential_static_store table during the time range in the valid_range column.'; + + create trigger hst_on_insert after insert on credential_static_store + for each row execute function hst_on_insert(); + create trigger hst_on_update after update on credential_static_store + for each row execute function hst_on_update(); + create trigger hst_on_delete after delete on credential_static_store + for each row execute function hst_on_delete(); + + insert into credential_static_store_hst + (public_id, name, description, project_id) + select public_id, name, description, project_id + from credential_static_store; + +commit; diff --git a/internal/db/sqltest/initdb.d/01_colors_persona.sql b/internal/db/sqltest/initdb.d/01_colors_persona.sql index c5e33a41c1..e0c3039152 100644 --- a/internal/db/sqltest/initdb.d/01_colors_persona.sql +++ b/internal/db/sqltest/initdb.d/01_colors_persona.sql @@ -53,7 +53,7 @@ begin; insert into kms_data_key_version (private_id, data_key_id, root_key_version_id, key) values - ('kdkv_______colors', 'kdk_______colors', 'krkv_______colors', '_______color2'::bytea); + ('kdkv__colors', 'kdk_______colors', 'krkv_______colors', '_______color2'::bytea); insert into iam_group (scope_id, public_id, name) @@ -156,11 +156,11 @@ begin; insert into auth_token (key_id, auth_account_id, public_id, token) values - ('kdkv_______colors', 'apa____clare', 'tok____clare', 'tok____clare'::bytea), - ('kdkv_______colors', 'apa____cindy', 'tok____cindy', 'tok____cindy'::bytea), - ('kdkv_______colors', 'apa____ciara', 'tok____ciara', 'tok____ciara'::bytea), - ('kdkv_______colors', 'apa____carly', 'tok____carly', 'tok____carly'::bytea), - ('kdkv_______colors', 'apa_____cora', 'tok_____cora', 'tok_____cora'::bytea); + ('kdkv__colors', 'apa____clare', 'tok____clare', 'tok____clare'::bytea), + ('kdkv__colors', 'apa____cindy', 'tok____cindy', 'tok____cindy'::bytea), + ('kdkv__colors', 'apa____ciara', 'tok____ciara', 'tok____ciara'::bytea), + ('kdkv__colors', 'apa____carly', 'tok____carly', 'tok____carly'::bytea), + ('kdkv__colors', 'apa_____cora', 'tok_____cora', 'tok_____cora'::bytea); insert into static_host (catalog_id, public_id, address) @@ -184,7 +184,7 @@ begin; ('c___cr-sthcl', 'h_____cr__07', '7.red.color'), ('c___cr-sthcl', 'h_____cr__08', '8.red.color'), ('c___cr-sthcl', 'h_____cr__09', '9.red.color'), - + ('c___cg-sthcl', 'h_____cg__01', '1.green.color'), ('c___cg-sthcl', 'h_____cg__02', '2.green.color'), ('c___cg-sthcl', 'h_____cg__03', '3.green.color'), @@ -217,7 +217,7 @@ begin; ('h_____cr__07', '7.red.color'), ('h_____cr__08', '8.red.color'), ('h_____cr__09', '9.red.color'), - + ('h_____cg__01', '1.green.color'), ('h_____cg__02', '2.green.color'), ('h_____cg__03', '3.green.color'), @@ -250,7 +250,7 @@ begin; ('h_____cr__07', '77.77.77.77'), ('h_____cr__08', '2001:4860:4860::8888'), ('h_____cr__09', '99.99.99.99'), - + ('h_____cg__01', '111.111.111.111'), ('h_____cg__02', '3001:5860:5860::3333'), ('h_____cg__03', '112.112.112.112'), @@ -350,7 +350,7 @@ begin; (scope_id, public_id, name) values ('global', 'plg____sb-plg', 'Storage Bucket Plugin'); - + insert into plugin_storage_supported (public_id) values @@ -406,6 +406,42 @@ begin; values ('vs_______cvs1', 'vl______cvl', 'color vault library', 'None', '/secrets', 'GET'); + insert into credential_static_store + (project_id, public_id, name, description) + values + ('p____bcolors', 'css__bcolors', 'Blue Static Credential Store', 'Static Credential Store for the Blue project'), + ('p____rcolors', 'css__rcolors', 'Red Static Credential Store', 'Static Credential Store for the Red project'), + ('p____gcolors', 'css__gcolors', 'Green Static Credential Store', 'Static Credential Store for the Green project'); + + insert into credential_static_json_credential + (key_id, project_id, store_id, public_id, name, object_encrypted, object_hmac) + values + ('kdkv__colors', 'p____bcolors', 'css__bcolors', 'csj__bcolors', 'Blue json cred', 'bjson-enc'::bytea, 'bjson-hmac'::bytea), + ('kdkv__colors', 'p____rcolors', 'css__rcolors', 'csj__rcolors', 'Red json cred', 'rjson-enc'::bytea, 'rjson-hmac'::bytea), + ('kdkv__colors', 'p____gcolors', 'css__gcolors', 'csj__gcolors', 'Green json cred', 'gjson-enc'::bytea, 'gjson-hmac'::bytea); + + insert into credential_static_username_password_credential + (key_id, project_id, store_id, public_id, name, username, password_encrypted, password_hmac) + values + ('kdkv__colors', 'p____bcolors', 'css__bcolors', 'csu__bcolors', 'Blue username password cred', 'buser', 'bpasswd-enc'::bytea, 'bpasswd-hmac'::bytea), + ('kdkv__colors', 'p____rcolors', 'css__rcolors', 'csu__rcolors', 'Red username password cred', 'ruser', 'rpasswd-enc'::bytea, 'rpasswd-hmac'::bytea), + ('kdkv__colors', 'p____gcolors', 'css__gcolors', 'csu__gcolors', 'Green username password cred', 'guser', 'gpasswd-enc'::bytea, 'gpasswd-hmac'::bytea); + + insert into credential_static_ssh_private_key_credential + (key_id, project_id, store_id, public_id, name, username, private_key_encrypted, private_key_hmac) + values + ('kdkv__colors', 'p____bcolors', 'css__bcolors', 'cspk_bcolors', 'Blue username password cred', 'buser', 'bprivkey-enc'::bytea, 'bprivkey-hmac'::bytea), + ('kdkv__colors', 'p____rcolors', 'css__rcolors', 'cspk_rcolors', 'Red username password cred', 'ruser', 'rprivkey-enc'::bytea, 'rprivkey-hmac'::bytea), + ('kdkv__colors', 'p____gcolors', 'css__gcolors', 'cspk_gcolors', 'Green username password cred', 'guser', 'gprivkey-enc'::bytea, 'gprivkey-hmac'::bytea); + + insert into target_static_credential + (project_id, target_id, credential_static_id, credential_purpose) + values + ('p____bcolors', 't_________cb', 'csj__bcolors', 'brokered'), + ('p____bcolors', 'tssh______cb', 'csj__bcolors', 'injected_application'), + ('p____gcolors', 'tssh______cg', 'csj__gcolors', 'brokered'), + ('p____gcolors', 'tssh______cg', 'cspk_gcolors', 'injected_application'); + insert into target_credential_library (project_id, target_id, credential_library_id, credential_purpose) values diff --git a/internal/db/sqltest/tests/history/history_table_tests.sql b/internal/db/sqltest/tests/history/history_table_tests.sql index 0c921f3743..e681b73e18 100644 --- a/internal/db/sqltest/tests/history/history_table_tests.sql +++ b/internal/db/sqltest/tests/history/history_table_tests.sql @@ -69,8 +69,9 @@ begin; begin execute format('select results_eq( ' ' ''select count(*) from %I'', ' - ' ''select count(*) from %I'') ', - op_table(history_table_name), history_table_name) + ' ''select count(*) from %I'', ' + ' ''%I failed has_expected_row_count'') ', + op_table(history_table_name), history_table_name, history_table_name) into result; return result; end; @@ -88,7 +89,7 @@ begin; from get_columns(history_table_name); select into _q1 format('select %s from %s', _cols, history_table_name); select into _q2 format('select %s from %s', _cols, op_table(history_table_name)); - return results_eq(_q1, _q2); + return results_eq(_q1, _q2, history_table_name || ' failed has_expected_content'); end; $$ language plpgsql; @@ -149,7 +150,11 @@ begin; -- tests for an exclusion index create function has_exclusion_index(history_table_name name) returns text as $$ - select has_index(history_table_name, history_table_name || '_valid_range_excl', array['public_id', 'valid_range']); + select case when length(history_table_name || '_valid_range_excl') > 63 + then hasnt_index(history_table_name, history_table_name || '_valid_range_excl', 'Index name to long: ' || history_table_name || '_valid_range_excl') + else collect_tap( + has_index(history_table_name, history_table_name || '_valid_range_excl', array['public_id', 'valid_range']) + ) end; $$ language sql; -- tests to verify certain columns are not in the history table diff --git a/internal/db/sqltest/tests/history/static_credentials.sql b/internal/db/sqltest/tests/history/static_credentials.sql new file mode 100644 index 0000000000..6d1cd80e2c --- /dev/null +++ b/internal/db/sqltest/tests/history/static_credentials.sql @@ -0,0 +1,38 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: MPL-2.0 + +begin; + select plan(16); + + -- Verify the trigger functions exist and are declared properly + select has_function('insert_credential_static_history_subtype'); + select volatility_is('insert_credential_static_history_subtype', 'volatile'); + select isnt_strict('insert_credential_static_history_subtype'); + + select has_function('delete_credential_static_history_subtype'); + select volatility_is('delete_credential_static_history_subtype', 'volatile'); + select isnt_strict('delete_credential_static_history_subtype'); + + select has_trigger('credential_static_json_credential_hst', 'insert_credential_static_history_subtype'); + select has_trigger('credential_static_json_credential_hst', 'delete_credential_static_history_subtype'); + select fk_ok('credential_static_json_credential_hst', 'history_id', 'credential_static_history_base' , 'history_id'); + + select has_trigger('credential_static_username_password_credential_hst', 'insert_credential_static_history_subtype'); + select has_trigger('credential_static_username_password_credential_hst', 'delete_credential_static_history_subtype'); + select fk_ok('credential_static_username_password_credential_hst', 'history_id', 'credential_static_history_base' , 'history_id'); + + select has_trigger('credential_static_ssh_private_key_credential_hst', 'insert_credential_static_history_subtype'); + select has_trigger('credential_static_ssh_private_key_credential_hst', 'delete_credential_static_history_subtype'); + select fk_ok('credential_static_ssh_private_key_credential_hst', 'history_id', 'credential_static_history_base' , 'history_id'); + + select results_eq( + 'select ' + '(select count(*) from credential_static_json_credential_hst) + ' + '(select count(*) from credential_static_username_password_credential_hst) + ' + '(select count(*) from credential_static_ssh_private_key_credential_hst)', + 'select count(*) from credential_static_history_base' + ); + + select * from finish(); +rollback; + diff --git a/internal/db/sqltest/tests/kms/kms_data_key_destruction_job.sql b/internal/db/sqltest/tests/kms/kms_data_key_destruction_job.sql index d3615a3e47..43d8fb4b6b 100644 --- a/internal/db/sqltest/tests/kms/kms_data_key_destruction_job.sql +++ b/internal/db/sqltest/tests/kms/kms_data_key_destruction_job.sql @@ -26,7 +26,7 @@ begin; insert into kms_data_key_version_destruction_job (key_id) values - ('kdkv_______colors'); + ('kdkv__colors'); select lives_ok('insert_new_key_id', 'insert of valid key_id in kms_data_key_version_destruction_job failed'); -- Should fail when inserting an unknown table_name