From a783c91b2d29573083399fef1c7f2aa471565078 Mon Sep 17 00:00:00 2001 From: Michael Gaffney Date: Wed, 17 May 2023 14:49:49 -0400 Subject: [PATCH] feat(sql): Add dynamic credential hst tables to session recording This adds the `recording_dynamic_credential` join table and a trigger that runs as an after insert on the `recording_session` table. The join table joins the `recording_session`, `credential_vault_store_hst`, `credential_library_history_base`, and the `credential_purpose_enm` tables. --- .../70/16_recording_dynamic_credential.up.sql | 79 +++++++++++++++++++ .../db/sqltest/initdb.d/01_colors_persona.sql | 19 ++++- .../recording_dynamic_credentials.sql | 72 +++++++++++++++++ .../wh/session_credential_dynamic/insert.sql | 10 +-- .../insert_multiple.sql | 10 +-- 5 files changed, 178 insertions(+), 12 deletions(-) create mode 100644 internal/db/schema/migrations/oss/postgres/70/16_recording_dynamic_credential.up.sql create mode 100644 internal/db/sqltest/tests/recording/recording_dynamic_credentials.sql diff --git a/internal/db/schema/migrations/oss/postgres/70/16_recording_dynamic_credential.up.sql b/internal/db/schema/migrations/oss/postgres/70/16_recording_dynamic_credential.up.sql new file mode 100644 index 000000000..c72e38161 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/70/16_recording_dynamic_credential.up.sql @@ -0,0 +1,79 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: MPL-2.0 + +begin; + + create table recording_dynamic_credential ( + recording_id wt_public_id not null + constraint recording_session_fk + references recording_session (public_id) + on delete cascade + on update cascade, + credential_vault_store_hst_id wt_url_safe_id not null + constraint credential_vault_store_hst_fk + references credential_vault_store_hst (history_id) + on delete restrict -- History records with session recordings cannot be deleted + on update cascade, + credential_library_hst_id wt_url_safe_id not null + constraint credential_library_history_base_fk + references credential_library_history_base (history_id) + on delete restrict -- History records with session recordings cannot be deleted + on update cascade, + credential_purpose text not null + constraint credential_purpose_fkey + references credential_purpose_enm (name) + on delete restrict + on update cascade, + primary key(recording_id, credential_vault_store_hst_id, credential_library_hst_id, credential_purpose) + ); + comment on table recording_dynamic_credential is + 'recording_dynamic_credential table is a join table that associates a session recording with ' + 'a credential library, the credential library''s credential store, ' + 'and the purpose of the credential produced by the library ' + 'along with the values of those entities at the time of the recording. ' + 'These values are also stored in the BSR file.'; + + create function insert_recording_dynamic_credentials() returns trigger + as $$ + begin + with + session_recording(session_id, recording_id) as ( + select session_id, public_id + from recording_session + where session_id = new.session_id + ), + session_dynamic_creds(library_id, purpose, recording_id) as ( + select library_id, credential_purpose, recording_id + from session_credential_dynamic + join session_recording using (session_id) + ), + library_history(public_id, store_id, library_hst_id, valid_range) as ( + select public_id, store_id, history_id, valid_range + from credential_vault_library_hst + union + select public_id, store_id, history_id, valid_range + from credential_vault_ssh_cert_library_hst + ), + final(recording_id, library_id, store_id, library_hst_id, store_hst_id, cred_purpose) as ( + select sdc.recording_id, lib.public_id, lib.store_id, lib.library_hst_id, store_hst.history_id, sdc.purpose + from library_history as lib + join credential_vault_store_hst as store_hst on lib.store_id = store_hst.public_id + and store_hst.valid_range @> current_timestamp + join session_dynamic_creds as sdc on lib.public_id = sdc.library_id + where lib.public_id in (select library_id from session_dynamic_creds) + and lib.valid_range @> current_timestamp + ) + insert into recording_dynamic_credential + (recording_id, credential_vault_store_hst_id, credential_library_hst_id, credential_purpose) + select recording_id, store_hst_id, library_hst_id, cred_purpose + from final; + return new; + end; + $$ language plpgsql; + comment on function insert_recording_dynamic_credentials is + 'insert_recording_dynamic_credentials is an after insert trigger for the recording_session table.'; + + create trigger insert_recording_dynamic_credentials after insert on recording_session + for each row execute procedure insert_recording_dynamic_credentials(); + +commit; diff --git a/internal/db/sqltest/initdb.d/01_colors_persona.sql b/internal/db/sqltest/initdb.d/01_colors_persona.sql index 1b2359616..6e8b88673 100644 --- a/internal/db/sqltest/initdb.d/01_colors_persona.sql +++ b/internal/db/sqltest/initdb.d/01_colors_persona.sql @@ -440,6 +440,13 @@ begin; ('cvs__rcolors', 'cvl_______r1', 'red vault library', 'None', '/secrets', 'GET'), ('cvs__gcolors', 'cvl_______g1', 'green vault library', 'None', '/secrets', 'GET'); + insert into credential_vault_ssh_cert_library + (store_id, public_id, name, vault_path, username, key_type, key_bits) + values + ('cvs__bcolors', 'cvl__ssh__b1', 'blue vault ssh library', '/ssh/sign/blue', 'admin', 'ed25519', 0), + ('cvs__rcolors', 'cvl__ssh__r1', 'red vault ssh library', '/ssh/issue/red', 'webdev', 'ecdsa', 521), + ('cvs__gcolors', 'cvl__ssh__g1', 'green vault ssh library', '/ssh/issue/green', 'dba', 'rsa', 4096); + insert into credential_static_store (project_id, public_id, name, description) values @@ -480,8 +487,10 @@ begin; insert into target_credential_library (project_id, target_id, credential_library_id, credential_purpose) values - ('p____bcolors', 't_________cb', 'cvl_______b1', 'brokered'), - ('p____bcolors', 'tssh______cb', 'cvl_______b1', 'brokered'); + ('p____bcolors', 't_________cb', 'cvl_______b1', 'brokered'), + ('p____bcolors', 'tssh______cb', 'cvl__ssh__b1', 'brokered'), + ('p____gcolors', 'tssh______cg', 'cvl_______g1', 'brokered'), + ('p____gcolors', 'tssh______cg', 'cvl__ssh__g1', 'injected_application'); insert into session (project_id, target_id, public_id, user_id, auth_token_id, certificate, endpoint) @@ -511,6 +520,12 @@ begin; ('s2______cora', 'csj__gcolors', 'brokered'), -- tssh______cg ('s2______cora', 'cspk_gcolors', 'injected_application'); -- tssh______cg + insert into session_credential_dynamic + (session_id, library_id, credential_purpose) + values + ('s1______cora', 'cvl_______g1', 'brokered'), -- tssh______cg + ('s1______cora', 'cvl__ssh__g1', 'injected_application'); -- tssh______cg + insert into session_host_set_host (session_id, host_set_id, host_id) values diff --git a/internal/db/sqltest/tests/recording/recording_dynamic_credentials.sql b/internal/db/sqltest/tests/recording/recording_dynamic_credentials.sql new file mode 100644 index 000000000..a626d9923 --- /dev/null +++ b/internal/db/sqltest/tests/recording/recording_dynamic_credentials.sql @@ -0,0 +1,72 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: MPL-2.0 + +begin; + select plan(27); + + select has_table('recording_dynamic_credential'); + + -- tests a fk column referencing a history table + -- add 5 to the plan for each time this function is called + create function hst_fk_column(column_name name, pk_table name) returns text + as $$ + select * from collect_tap( + has_column('recording_dynamic_credential', column_name), + col_not_null('recording_dynamic_credential', column_name), + col_type_is('recording_dynamic_credential', column_name, 'wt_url_safe_id'), -- should be the same type as the operational table + col_hasnt_default('recording_dynamic_credential', column_name), + fk_ok('recording_dynamic_credential', column_name, pk_table, 'history_id') + ); + $$ language sql; + + select hst_fk_column('credential_vault_store_hst_id', 'credential_vault_store_hst'); + select hst_fk_column('credential_library_hst_id', 'credential_library_history_base'); + + select has_column('recording_dynamic_credential', 'recording_id'); + select col_not_null('recording_dynamic_credential', 'recording_id'); + select col_type_is('recording_dynamic_credential', 'recording_id', 'wt_public_id'); -- should be the same type as the operational table + select col_hasnt_default('recording_dynamic_credential', 'recording_id'); + select fk_ok('recording_dynamic_credential', 'recording_id', 'recording_session', 'public_id'); + + select has_column('recording_dynamic_credential', 'credential_purpose'); + select col_not_null('recording_dynamic_credential', 'credential_purpose'); + select col_type_is('recording_dynamic_credential', 'credential_purpose', 'text'); -- should be the same type as the operational table + select col_hasnt_default('recording_dynamic_credential', 'credential_purpose'); + select fk_ok('recording_dynamic_credential', 'credential_purpose', 'credential_purpose_enm', 'name'); + + select col_is_pk('recording_dynamic_credential', + array['recording_id', 'credential_vault_store_hst_id', 'credential_library_hst_id', 'credential_purpose']); + + prepare get_target_creds as + select credential_library_id, credential_purpose + from target_credential_library + where target_id = 'tssh______cg'; + + prepare get_session_creds as + select library_id, credential_purpose + from session_credential_dynamic + join session on session_id = public_id + where target_id = 'tssh______cg' + and session_id = 's1______cora'; + + select results_eq('get_target_creds', 'get_session_creds'); + + select is(count(*), 2::bigint) + from target_credential_library + where target_id = 'tssh______cg'; + + select is(count(*), 2::bigint) + from session_credential_dynamic + where session_id = 's1______cora'; + + select is(count(*), 1::bigint) + from recording_session + where session_id = 's1______cora' + and public_id = 'sr1_____cora'; + + select is(count(*), 2::bigint) + from recording_dynamic_credential + where recording_id = 'sr1_____cora'; + + select * from finish(); +rollback; diff --git a/internal/db/sqltest/tests/wh/session_credential_dynamic/insert.sql b/internal/db/sqltest/tests/wh/session_credential_dynamic/insert.sql index 17e56e29b..d8a6ca576 100644 --- a/internal/db/sqltest/tests/wh/session_credential_dynamic/insert.sql +++ b/internal/db/sqltest/tests/wh/session_credential_dynamic/insert.sql @@ -6,10 +6,10 @@ begin; select plan(6); select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials'); - -- ensure no existing dimensions or bridge table rows + select is(count(*), 0::bigint) from wh_credential_dimension where organization_id = 'o_____widget'; - select is(count(*), 0::bigint) from wh_credential_group_membership where credential_group_key != 'no credentials' and credential_group_key != 'Unknown'; - select is(count(*), 0::bigint) from wh_credential_group where key != 'no credentials' and key != 'Unknown'; + select is(count(*), 2::bigint) from wh_credential_group_membership where credential_group_key != 'no credentials' and credential_group_key != 'Unknown'; + select is(count(*), 1::bigint) from wh_credential_group where key != 'no credentials' and key != 'Unknown'; --- insert single credential insert into session @@ -26,8 +26,8 @@ begin; ('s1____walter', 'vl______wvl1', null, 'brokered'); select is(count(*), 1::bigint) from wh_credential_dimension where organization_id = 'o_____widget'; - select is(count(*), 1::bigint) from wh_credential_group_membership where credential_group_key != 'no credentials' and credential_group_key != 'Unknown'; - select is(count(*), 1::bigint) from wh_credential_group where key != 'no credentials' and key != 'Unknown'; + select is(count(*), 3::bigint) from wh_credential_group_membership where credential_group_key != 'no credentials' and credential_group_key != 'Unknown'; + select is(count(*), 2::bigint) from wh_credential_group where key != 'no credentials' and key != 'Unknown'; select * from finish(); rollback; diff --git a/internal/db/sqltest/tests/wh/session_credential_dynamic/insert_multiple.sql b/internal/db/sqltest/tests/wh/session_credential_dynamic/insert_multiple.sql index 2443a6af2..dde20d6ab 100644 --- a/internal/db/sqltest/tests/wh/session_credential_dynamic/insert_multiple.sql +++ b/internal/db/sqltest/tests/wh/session_credential_dynamic/insert_multiple.sql @@ -6,10 +6,10 @@ begin; select plan(6); select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials'); - -- ensure no existing dimensions + select is(count(*), 0::bigint) from wh_credential_dimension where organization_id = 'o_____widget'; - select is(count(*), 0::bigint) from wh_credential_group_membership where credential_group_key != 'no credentials' and credential_group_key != 'Unknown'; - select is(count(*), 0::bigint) from wh_credential_group where key != 'no credentials' and key != 'Unknown'; + select is(count(*), 2::bigint) from wh_credential_group_membership where credential_group_key != 'no credentials' and credential_group_key != 'Unknown'; + select is(count(*), 1::bigint) from wh_credential_group where key != 'no credentials' and key != 'Unknown'; --- multiple single credentials insert into session @@ -29,8 +29,8 @@ begin; ('s1____walter', 'vl______wvl3', null, 'injected_application'); select is(count(*), 4::bigint) from wh_credential_dimension where organization_id = 'o_____widget'; - select is(count(*), 4::bigint) from wh_credential_group_membership where credential_group_key != 'no credentials' and credential_group_key != 'Unknown'; - select is(count(*), 1::bigint) from wh_credential_group where key != 'no credentials' and key != 'Unknown'; + select is(count(*), 6::bigint) from wh_credential_group_membership where credential_group_key != 'no credentials' and credential_group_key != 'Unknown'; + select is(count(*), 2::bigint) from wh_credential_group where key != 'no credentials' and key != 'Unknown'; select * from finish(); rollback;