diff --git a/internal/db/schema/migrations/oss/postgres/64/01_ssh_targets.up.sql b/internal/db/schema/migrations/oss/postgres/64/01_ssh_targets.up.sql index 9055b00f7..ee40098af 100644 --- a/internal/db/schema/migrations/oss/postgres/64/01_ssh_targets.up.sql +++ b/internal/db/schema/migrations/oss/postgres/64/01_ssh_targets.up.sql @@ -79,6 +79,7 @@ begin; drop view if exists whx_credential_dimension_source; drop view if exists target_all_subtypes; + -- Replaced in oss/70/08_targets.up.sql create view target_all_subtypes as select public_id, @@ -115,7 +116,7 @@ begin; from target_ssh; - -- replaces view from oss/60/03_wh_sessions.up.sql + -- Replaced in oss/70/08_targets.up.sql create view whx_host_dimension_source as with host_sources ( @@ -248,7 +249,7 @@ begin; -- The whx_credential_dimension_source view shows the current values in the -- operational tables of the credential dimension. - -- Replaces whx_credential_dimension_source defined in oss/63/03_wh_ssh_cert_library.up.sql + -- Replaced in oss/70/08_targets.up.sql create view whx_credential_dimension_source as with vault_generic_library as ( select vcl.public_id as public_id, diff --git a/internal/db/schema/migrations/oss/postgres/70/03_storage_bucket.up.sql b/internal/db/schema/migrations/oss/postgres/70/03_storage_bucket.up.sql index be8790cc8..441de3ac6 100644 --- a/internal/db/schema/migrations/oss/postgres/70/03_storage_bucket.up.sql +++ b/internal/db/schema/migrations/oss/postgres/70/03_storage_bucket.up.sql @@ -3,16 +3,6 @@ begin; - create table storage_bucket ( - public_id wt_public_id primary key, - scope_id wt_scope_id - constraint iam_scope_fkey - references iam_scope (public_id) - on delete restrict -- Scopes with storage buckets cannot be deleted - on update cascade - ); - comment on table storage_bucket is 'storage_bucket entries represent external storage buckets that boundary has access to.'; - create table plugin_storage_supported ( public_id wt_plugin_id primary key references plugin(public_id) @@ -30,10 +20,7 @@ begin; where name = 'aws'; create table storage_plugin_storage_bucket ( - public_id wt_public_id primary key - references storage_bucket(public_id) - on delete cascade - on update cascade, + public_id wt_public_id primary key, scope_id wt_scope_id not null constraint iam_scope_fkey references iam_scope(public_id) @@ -76,45 +63,6 @@ begin; create trigger immutable_columns before update on storage_plugin_storage_bucket for each row execute procedure immutable_columns('public_id', 'scope_id', 'create_time', 'bucket_name'); - -- insert_storage_bucket_subtype() is a trigger function for - -- subtypes of storage_bucket - create or replace function insert_storage_bucket_subtype() returns trigger - as $$ - begin - - insert into storage_bucket - (public_id, scope_id) - values - (new.public_id, new.scope_id); - - return new; - - end; - $$ language plpgsql; - comment on function insert_storage_bucket_subtype is - 'insert_storage_bucket_subtype is a trigger function for subtypes of storage_bucket that ensures values exist in the parent storage_bucket table.'; - - create trigger insert_storage_bucket_subtype before insert on storage_plugin_storage_bucket - for each row execute procedure insert_storage_bucket_subtype(); - - -- delete_storage_bucket_subtype() is a trigger function for - -- subtypes of storage_bucket - create or replace function delete_storage_bucket_subtype() returns trigger - as $$ - begin - - delete from storage_bucket - where public_id = old.public_id; - return null; -- result is ignored since this is an after trigger - - end; - $$ language plpgsql; - comment on function delete_storage_bucket_subtype is - 'delete_storage_bucket_subtype is a trigger function for subtypes of storage_bucket that removes the associated values in the storage_bucket table on delete.'; - - create trigger delete_storage_bucket_subtype after delete on storage_plugin_storage_bucket - for each row execute procedure delete_storage_bucket_subtype(); - -- storage_bucket_scope_id_valid() is a trigger function for -- subtypes of storage_bucket create or replace function storage_bucket_scope_id_valid() returns trigger diff --git a/internal/db/schema/migrations/oss/postgres/70/07_session_recording.up.sql b/internal/db/schema/migrations/oss/postgres/70/07_session_recording.up.sql index d8b812cc6..fa4fe985d 100644 --- a/internal/db/schema/migrations/oss/postgres/70/07_session_recording.up.sql +++ b/internal/db/schema/migrations/oss/postgres/70/07_session_recording.up.sql @@ -11,8 +11,8 @@ begin; create table recording_session ( public_id wt_public_id primary key, storage_bucket_id wt_public_id not null - constraint storage_bucket_fkey - references storage_bucket (public_id) + constraint storage_plugin_storage_bucket_fkey + references storage_plugin_storage_bucket (public_id) on delete restrict -- Storage buckets with session recordings cannot be deleted on update cascade, session_id wt_public_id null -- Can be null if associated session has been deleted diff --git a/internal/db/schema/migrations/oss/postgres/70/08_targets.up.sql b/internal/db/schema/migrations/oss/postgres/70/08_targets.up.sql new file mode 100644 index 000000000..e302078ae --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/70/08_targets.up.sql @@ -0,0 +1,348 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: MPL-2.0 + +begin; + + create function validate_target_storage_bucket() returns trigger + as $$ + declare storage_bucket_scope_id text; + begin + -- Ensure enable_session_recording is not true if no storage_bucket is associated + if new.enable_session_recording = true and new.storage_bucket_id is null then + raise exception 'session recording enabled without storage bucket'; + end if; + + -- If storage bucket is null no need to validate further + if new.storage_bucket_id is null then + return new; + end if; + + -- Look up the scope ID for the storage bucket + select sb.scope_id from storage_plugin_storage_bucket sb where sb.public_id = new.storage_bucket_id into strict storage_bucket_scope_id; + + -- Global storage bucket can be associated with any target + if storage_bucket_scope_id = 'global' then + return new; + end if; + + -- Validate that the target project id parents scope is the storage bucket scope id + perform from iam_scope_project where scope_id = new.project_id and parent_id = storage_bucket_scope_id; + if not found then + raise exception 'invalid scope type for target storage bucket association'; + end if; + return new; + end; + $$ language plpgsql; + comment on function validate_target_storage_bucket() is + 'validate_target_storage_bucket validates that the storage bucket associated with target_ssh, ' + 'is within the global scope or within the org scope that is the parent of the target_ssh projectId. ' + 'It also validates that enable_session_recording is only set if a valid storage_bucket_id is also set.'; + + create trigger validate_target_storage_bucket after insert or update on target_ssh + for each row execute procedure validate_target_storage_bucket(); + + alter table target_ssh + add column enable_session_recording bool not null default false, + add column storage_bucket_id wt_public_id, -- storage_bucket_id can be null + add constraint storage_plugin_storage_bucket_fkey foreign key (storage_bucket_id) + references storage_plugin_storage_bucket (public_id) + on delete set null + on update cascade; + + -- The whx_* views here depend on target_all_subtypes, so we need to drop + -- these first. + drop view whx_host_dimension_source; + drop view whx_credential_dimension_source; + drop view target_all_subtypes; + + -- replaces target_all_subtypes defined in oss/64/01_ssh_targets.up.sql + create view target_all_subtypes as + select + public_id, + project_id, + name, + description, + default_port, + session_max_seconds, + session_connection_limit, + version, + create_time, + update_time, + worker_filter, + egress_worker_filter, + ingress_worker_filter, + default_client_port, + null as storage_bucket_id, + false as enable_session_recording, + 'tcp' as type + from target_tcp + union + select + public_id, + project_id, + name, + description, + default_port, + session_max_seconds, + session_connection_limit, + version, + create_time, + update_time, + worker_filter, + egress_worker_filter, + ingress_worker_filter, + default_client_port, + storage_bucket_id, + enable_session_recording, + 'ssh' as type + from + target_ssh; + + -- replaces whx_host_dimension_source defined in oss/64/01_ssh_targets.up.sql + create view whx_host_dimension_source as + with + host_sources ( + host_id, host_type, host_name, host_description, + host_set_id, host_set_type, host_set_name, host_set_description, + host_catalog_id, host_catalog_type, host_catalog_name, host_catalog_description, + target_id, target_type, target_name, target_description, + target_default_port_number, target_session_max_seconds, target_session_connection_limit, + project_id, project_name, project_description, + organization_id, organization_name, organization_description + ) as ( + select -- id is the first column in the target view + h.public_id as host_id, + case when sh.public_id is not null then 'static host' + when ph.public_id is not null then 'plugin host' + else 'Unknown' end as host_type, + case when sh.public_id is not null then coalesce(sh.name, 'None') + when ph.public_id is not null then coalesce(ph.name, 'None') + else 'Unknown' end as host_name, + case when sh.public_id is not null then coalesce(sh.description, 'None') + when ph.public_id is not null then coalesce(ph.description, 'None') + else 'Unknown' end as host_description, + hs.public_id as host_set_id, + case when shs.public_id is not null then 'static host set' + when phs.public_id is not null then 'plugin host set' + else 'Unknown' end as host_set_type, + case + when shs.public_id is not null then coalesce(shs.name, 'None') + when phs.public_id is not null then coalesce(phs.name, 'None') + else 'None' + end as host_set_name, + case + when shs.public_id is not null then coalesce(shs.description, 'None') + when phs.public_id is not null then coalesce(phs.description, 'None') + else 'None' + end as host_set_description, + hc.public_id as host_catalog_id, + case when shc.public_id is not null then 'static host catalog' + when phc.public_id is not null then 'plugin host catalog' + else 'Unknown' end as host_catalog_type, + case + when shc.public_id is not null then coalesce(shc.name, 'None') + when phc.public_id is not null then coalesce(phc.name, 'None') + else 'None' + end as host_catalog_name, + case + when shc.public_id is not null then coalesce(shc.description, 'None') + when phc.public_id is not null then coalesce(phc.description, 'None') + else 'None' + end as host_catalog_description, + t.public_id as target_id, + case + when t.type = 'tcp' then 'tcp target' + when t.type = 'ssh' then 'ssh target' + else 'Unknown' + end as target_type, + coalesce(t.name, 'None') as target_name, + coalesce(t.description, 'None') as target_description, + coalesce(t.default_port, 0) as target_default_port_number, + t.session_max_seconds as target_session_max_seconds, + t.session_connection_limit as target_session_connection_limit, + p.public_id as project_id, + coalesce(p.name, 'None') as project_name, + coalesce(p.description, 'None') as project_description, + o.public_id as organization_id, + coalesce(o.name, 'None') as organization_name, + coalesce(o.description, 'None') as organization_description + from host as h + join host_catalog as hc on h.catalog_id = hc.public_id + join host_set as hs on h.catalog_id = hs.catalog_id + join target_host_set as ts on hs.public_id = ts.host_set_id + join target_all_subtypes as t on ts.target_id = t.public_id + join iam_scope as p on t.project_id = p.public_id and p.type = 'project' + join iam_scope as o on p.parent_id = o.public_id and o.type = 'org' + + left join static_host as sh on sh.public_id = h.public_id + left join host_plugin_host as ph on ph.public_id = h.public_id + left join static_host_catalog as shc on shc.public_id = hc.public_id + left join host_plugin_catalog as phc on phc.public_id = hc.public_id + left join static_host_set as shs on shs.public_id = hs.public_id + left join host_plugin_set as phs on phs.public_id = hs.public_id + ), + host_target_address ( + host_id, host_type, host_name, host_description, + host_set_id, host_set_type, host_set_name, host_set_description, + host_catalog_id, host_catalog_type, host_catalog_name, host_catalog_description, + target_id, target_type, target_name, target_description, + target_default_port_number, target_session_max_seconds, target_session_connection_limit, + project_id, project_name, project_description, + organization_id, organization_name, organization_description + ) as ( + select + 'Not Applicable' as host_id, + 'direct address' as host_type, + 'Not Applicable' as host_name, + 'Not Applicable' as host_description, + 'Not Applicable' as host_set_id, + 'Not Applicable' as host_set_type, + 'Not Applicable' as host_set_name, + 'Not Applicable' as host_set_description, + 'Not Applicable' as host_catalog_id, + 'Not Applicable' as host_catalog_type, + 'Not Applicable' as host_catalog_name, + 'Not Applicable' as host_catalog_description, + t.public_id as target_id, + case + when t.type = 'tcp' then 'tcp target' + when t.type = 'ssh' then 'ssh target' + else 'Unknown' + end as target_type, + coalesce(t.name, 'None') as target_name, + coalesce(t.description, 'None') as target_description, + coalesce(t.default_port, 0) as target_default_port_number, + t.session_max_seconds as target_session_max_seconds, + t.session_connection_limit as target_session_connection_limit, + p.public_id as project_id, + coalesce(p.name, 'None') as project_name, + coalesce(p.description, 'None') as project_description, + o.public_id as organization_id, + coalesce(o.name, 'None') as organization_name, + coalesce(o.description, 'None') as organization_description + from target_all_subtypes as t + right join target_address as ta on t.public_id = ta.target_id + left join iam_scope as p on p.public_id = t.project_id + left join iam_scope as o on o.public_id = p.parent_id + ) + select * from host_sources + union + select * from host_target_address; + + -- The whx_credential_dimension_source view shows the current values in the + -- operational tables of the credential dimension. + -- Replaces whx_credential_dimension_source defined in oss/64/01_ssh_targets.up.sql.sql + create view whx_credential_dimension_source as + with vault_generic_library as ( + select vcl.public_id as public_id, + 'vault generic credential library' as type, + coalesce(vcl.name, 'None') as name, + coalesce(vcl.description, 'None') as description, + vcl.vault_path as vault_path, + vcl.http_method as http_method, + case + when vcl.http_method = 'GET' then 'Not Applicable' + else coalesce(vcl.http_request_body::text, 'None') + end as http_request_body, + 'Not Applicable' as username, + 'Not Applicable' as key_type_and_bits + from credential_vault_library as vcl + ), + vault_ssh_cert_library as ( + select vsccl.public_id as public_id, + 'vault ssh certificate credential library' as type, + coalesce(vsccl.name, 'None') as name, + coalesce(vsccl.description, 'None') as description, + vsccl.vault_path as vault_path, + 'Not Applicable' as http_method, + 'Not Applicable' as http_request_body, + vsccl.username as username, + case + when vsccl.key_type = 'ed25519' then vsccl.key_type + else vsccl.key_type || '-' || vsccl.key_bits::text + end as key_type_and_bits + from credential_vault_ssh_cert_library as vsccl + ), + final as ( + select s.public_id as session_id, + scd.credential_purpose as credential_purpose, + cl.public_id as credential_library_id, + coalesce(vcl.type, vsccl.type) as credential_library_type, + coalesce(vcl.name, vsccl.name) as credential_library_name, + coalesce(vcl.description, vsccl.description) as credential_library_description, + coalesce(vcl.vault_path, vsccl.vault_path) as credential_library_vault_path, + coalesce(vcl.http_method, vsccl.http_method) as credential_library_vault_http_method, + coalesce(vcl.http_request_body, vsccl.http_request_body) as credential_library_vault_http_request_body, + coalesce(vcl.username, vsccl.username) as credential_library_username, + coalesce(vcl.key_type_and_bits, vsccl.key_type_and_bits) as credential_library_key_type_and_bits, + cs.public_id as credential_store_id, + case + when vcs is null then 'None' + else 'vault credential store' + end as credential_store_type, + coalesce(vcs.name, 'None') as credential_store_name, + coalesce(vcs.description, 'None') as credential_store_description, + coalesce(vcs.namespace, 'None') as credential_store_vault_namespace, + coalesce(vcs.vault_address, 'None') as credential_store_vault_address, + t.public_id as target_id, + case + when tt.type = 'tcp' then 'tcp target' + when tt.type = 'ssh' then 'ssh target' + else 'Unknown' + end as target_type, + coalesce(tt.name, 'None') as target_name, + coalesce(tt.description, 'None') as target_description, + coalesce(tt.default_port, 0) as target_default_port_number, + tt.session_max_seconds as target_session_max_seconds, + tt.session_connection_limit as target_session_connection_limit, + p.public_id as project_id, + coalesce(p.name, 'None') as project_name, + coalesce(p.description, 'None') as project_description, + o.public_id as organization_id, + coalesce(o.name, 'None') as organization_name, + coalesce(o.description, 'None') as organization_description + from session_credential_dynamic as scd + join session as s on scd.session_id = s.public_id + join credential_library as cl on scd.library_id = cl.public_id + join credential_store as cs on cl.store_id = cs.public_id + join target as t on s.target_id = t.public_id + join iam_scope as p on p.public_id = t.project_id and p.type = 'project' + join iam_scope as o on p.parent_id = o.public_id and o.type = 'org' + left join vault_generic_library as vcl on cl.public_id = vcl.public_id + left join vault_ssh_cert_library as vsccl on cl.public_id = vsccl.public_id + left join credential_vault_store as vcs on cs.public_id = vcs.public_id + left join target_all_subtypes as tt on t.public_id = tt.public_id + ) + select session_id, + credential_purpose, + credential_library_id, + credential_library_type, + credential_library_name, + credential_library_description, + credential_library_vault_path, + credential_library_vault_http_method, + credential_library_vault_http_request_body, + credential_library_username, + credential_library_key_type_and_bits, + credential_store_id, + credential_store_type, + credential_store_name, + credential_store_description, + credential_store_vault_namespace, + credential_store_vault_address, + target_id, + target_type, + target_name, + target_description, + target_default_port_number, + target_session_max_seconds, + target_session_connection_limit, + project_id, + project_name, + project_description, + organization_id, + organization_name, + organization_description + from final; + +commit; diff --git a/internal/db/sqltest/Makefile b/internal/db/sqltest/Makefile index 92fc43cd1..7b86092d2 100644 --- a/internal/db/sqltest/Makefile +++ b/internal/db/sqltest/Makefile @@ -28,7 +28,7 @@ TESTS ?= tests/setup/*.sql \ tests/controller/*.sql \ tests/hcp/*/*.sql \ tests/kms/*.sql \ - tests/plugin/*.sql \ + tests/storage/*.sql \ tests/history/*.sql \ tests/recording/*.sql diff --git a/internal/db/sqltest/initdb.d/01_colors_persona.sql b/internal/db/sqltest/initdb.d/01_colors_persona.sql index 9239f82c0..50f51c40b 100644 --- a/internal/db/sqltest/initdb.d/01_colors_persona.sql +++ b/internal/db/sqltest/initdb.d/01_colors_persona.sql @@ -286,6 +286,23 @@ begin; ('p____rcolors', 't_________cr', 'Red Color Target'), ('p____gcolors', 't_________cg', 'Green Color Target'); + insert into plugin + (scope_id, public_id, name) + values + ('global', 'plg____sb-plg', 'Storage Bucket Plugin'); + + insert into plugin_storage_supported + (public_id) + values + ('plg____sb-plg'); + + insert into storage_plugin_storage_bucket + (public_id, scope_id, plugin_id, bucket_name, worker_filter, secrets_hmac) + values + ('sb_________o','o_____colors', 'plg____sb-plg', 'test bucket name', 'test worker filter', '\xdeadbeef'), + ('sb_________g','global', 'plg____sb-plg', 'test bucket name', 'test worker filter', '\xdeadbeef'); + + insert into target_host_set (project_id, target_id, host_set_id) values diff --git a/internal/db/sqltest/tests/plugin/valid_storage_bucket_added.sql b/internal/db/sqltest/tests/plugin/valid_storage_bucket_added.sql deleted file mode 100644 index 76acabb7e..000000000 --- a/internal/db/sqltest/tests/plugin/valid_storage_bucket_added.sql +++ /dev/null @@ -1,31 +0,0 @@ --- Copyright (c) HashiCorp, Inc. --- SPDX-License-Identifier: MPL-2.0 - -begin; - select plan(4); - - -- Ensure no data has been added - select is(count(*), 0::bigint) from storage_bucket; - select is(count(*), 0::bigint) from storage_plugin_storage_bucket; - - -- Insert records into plugins storage_plugin_storage_bucket - insert into plugin - (scope_id, public_id, name) - values - ('global', 'plg____sb-plg', 'Storage Bucket Plugin'); - - insert into plugin_storage_supported - (public_id) - values - ('plg____sb-plg'); - - insert into storage_plugin_storage_bucket - (public_id, scope_id, plugin_id, bucket_name, worker_filter, secrets_hmac) - values - ('sb_________1','global', 'plg____sb-plg', 'test bucket name', 'test worker filter', '\xdeadbeef'); - - select is(count(*), 1::bigint) from storage_plugin_storage_bucket where public_id = 'sb_________1'; - select is(count(*), 1::bigint) from storage_bucket where public_id = 'sb_________1'; - - select * from finish(); -rollback; \ No newline at end of file diff --git a/internal/db/sqltest/tests/recording/recording_channel_ssh.sql b/internal/db/sqltest/tests/recording/recording_channel_ssh.sql index c15d4f62c..d1a7c6a8e 100644 --- a/internal/db/sqltest/tests/recording/recording_channel_ssh.sql +++ b/internal/db/sqltest/tests/recording/recording_channel_ssh.sql @@ -13,11 +13,10 @@ begin; select plan(9); select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'sessions'); - insert into storage_bucket (public_id, scope_id) values ('sb_123456789', 'global'); insert into recording_session (public_id, storage_bucket_id, session_id) values - ('sr_123456789', 'sb_123456789', 's1_____clare'); + ('sr_123456789', 'sb_________g', 's1_____clare'); insert into session_connection (public_id, session_id) values diff --git a/internal/db/sqltest/tests/recording/recording_connection.sql b/internal/db/sqltest/tests/recording/recording_connection.sql index cfd12063c..9b9cc0ef4 100644 --- a/internal/db/sqltest/tests/recording/recording_connection.sql +++ b/internal/db/sqltest/tests/recording/recording_connection.sql @@ -13,11 +13,10 @@ begin; select plan(10); select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'sessions'); - insert into storage_bucket (public_id, scope_id) values ('sb_123456789', 'global'); insert into recording_session (public_id, storage_bucket_id, session_id) values - ('sr_123456789', 'sb_123456789', 's1_____clare'); + ('sr_123456789', 'sb_________g', 's1_____clare'); insert into session_connection (public_id, session_id) values diff --git a/internal/db/sqltest/tests/recording/recording_session.sql b/internal/db/sqltest/tests/recording/recording_session.sql index 353e317c6..044a0eb3a 100644 --- a/internal/db/sqltest/tests/recording/recording_session.sql +++ b/internal/db/sqltest/tests/recording/recording_session.sql @@ -28,8 +28,6 @@ begin; select hst_fk_column('user_scope_hst_id', 'iam_scope_hst'); select hst_fk_column('user_hst_id', 'iam_user_hst'); - insert into storage_bucket (public_id, scope_id) values ('sb_123456789', 'global'); - -- test insert trigger can handle more than one row of history -- update the iam_scope of test user 's1_____clare' select is(count(*), 1::bigint) @@ -49,14 +47,14 @@ begin; insert into recording_session (public_id, storage_bucket_id, session_id) values - ('sr_123456789', 'sb_123456789', null); + ('sr_123456789', 'sb_________g', null); select throws_ok('insert_invalid_recording_session', null, null, 'insert invalid recording_session succeeded'); prepare insert_recording_session as insert into recording_session (public_id, storage_bucket_id, session_id) values - ('sr_123456789', 'sb_123456789', 's1_____clare'); + ('sr_123456789', 'sb_________g', 's1_____clare'); select lives_ok('insert_recording_session'); -- Try to set end_time before start_time @@ -84,8 +82,8 @@ begin; -- Deleting the storage bucket with active recordings should fail prepare delete_bucket as - delete from storage_bucket where public_id = 'sb_123456789'; - select throws_ok('delete_bucket', null, null, 'deleting a storage_bucket with recordings succeeded'); + delete from storage_plugin_storage_bucket where public_id = 'sb_________g'; + select throws_ok('delete_bucket', null, null, 'deleting a storage_plugin_storage_bucket with recordings succeeded'); select * from finish(); rollback; diff --git a/internal/db/sqltest/tests/storage/valid_storage_bucket_added.sql b/internal/db/sqltest/tests/storage/valid_storage_bucket_added.sql new file mode 100644 index 000000000..112258bcf --- /dev/null +++ b/internal/db/sqltest/tests/storage/valid_storage_bucket_added.sql @@ -0,0 +1,35 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: MPL-2.0 + +begin; + select plan(4); + + -- Ensure test data is correct + select is(count(*), 2::bigint) from storage_plugin_storage_bucket; + + -- insert global storage bucket + insert into storage_plugin_storage_bucket + (public_id, scope_id, plugin_id, bucket_name, worker_filter, secrets_hmac) + values + ('sb_________1','global', 'plg____sb-plg', 'test bucket name', 'test worker filter', '\xdeadbeef'); + + select is(count(*), 1::bigint) from storage_plugin_storage_bucket where public_id = 'sb_________1'; + + -- insert org storage bucket + insert into storage_plugin_storage_bucket + (public_id, scope_id, plugin_id, bucket_name, worker_filter, secrets_hmac) + values + ('sb_________2','o_____colors', 'plg____sb-plg', 'test bucket name', 'test worker filter', '\xdeadbeef'); + + select is(count(*), 1::bigint) from storage_plugin_storage_bucket where public_id = 'sb_________2'; + + -- Try to insert row with a project scope id + prepare invalid_storage_bucket as + insert into storage_plugin_storage_bucket + (public_id, scope_id, plugin_id, bucket_name, worker_filter, secrets_hmac) + values + ('sb_________3','p____bcolors', 'plg____sb-plg', 'test bucket name', 'test worker filter', '\xdeadbeef'); + select throws_ok('invalid_storage_bucket', null, null, 'insert invalid storage_bucket succeeded'); + + select * from finish(); +rollback; diff --git a/internal/db/sqltest/tests/target/target_storage_bucket.sql b/internal/db/sqltest/tests/target/target_storage_bucket.sql new file mode 100644 index 000000000..ef7527b43 --- /dev/null +++ b/internal/db/sqltest/tests/target/target_storage_bucket.sql @@ -0,0 +1,74 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: MPL-2.0 + +begin; + select plan(11); + select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets'); + + -- validate the setup data + select is(count(*), 2::bigint) from storage_plugin_storage_bucket; + + -- insert target with storage bucket as null with session recording enabled + prepare invalid_session_recording_insert as + insert into target_ssh + (project_id, public_id, enable_session_recording, storage_bucket_id, name) + values + ('p____swidget', 'tssh___small', true, null, 'Small Widget SSH Target'); + select throws_ok('invalid_session_recording_insert', null, null, 'insert of invalid session recording state succeeded'); + + -- insert target with storage bucket from an org not the parent of its project + prepare invalid_storage_bucket_insert as + insert into target_ssh + (project_id, public_id, enable_session_recording, storage_bucket_id, name) + values + ('p____swidget', 'tssh___small', false, 'sb_________o', 'Small Widget SSH Target'); + select throws_ok('invalid_storage_bucket_insert', null, null, 'insert of invalid storage bucket state succeeded'); + + -- insert targets with valid storage buckets + insert into target_ssh + (project_id, public_id, enable_session_recording, storage_bucket_id, name) + values + ('p____bcolors', 'tssh_______b', true, 'sb_________o', 'Blue Color SSH Target'), + ('p____rcolors', 'tssh_______r', true, 'sb_________g', 'Red Color SSH Target'), + ('p____bwidget', 'tssh_____big', false, null, 'Big Widget SSH Target'); + + select is(count(*), 1::bigint) from target_ssh where public_id = 'tssh_______b' and storage_bucket_id = 'sb_________o'; + select is(count(*), 1::bigint) from target_ssh where public_id = 'tssh_______r' and storage_bucket_id = 'sb_________g'; + select is(count(*), 1::bigint) from target_ssh where public_id = 'tssh_____big' and storage_bucket_id is null; + + -- update storage bucket to null without disabling session recording + prepare invalid_session_recording_update as + update target_ssh + set storage_bucket_id = null + where public_id = 'tssh_______b'; + select throws_ok('invalid_session_recording_enabled', null, null, 'update to invalid session recording state succeeded'); + + prepare valid_session_recording_update as + update target_ssh + set storage_bucket_id = null, enable_session_recording = false + where public_id = 'tssh_______b'; + select lives_ok('valid_session_recording_update', 'update to valid session recording state failed'); + + -- Update target with storage bucket from an org not the parent of its project + prepare invalid_storage_bucket_update as + update target_ssh + set storage_bucket_id = 'sb_________o' + where public_id = 'tssh_____big'; + select throws_ok('invalid_storage_bucket_update', null, null, 'update to invalid storage bucket state succeeded'); + + -- update storage bucket to global scope + prepare valid_storage_bucket_global_update as + update target_ssh + set storage_bucket_id = 'sb_________g' + where public_id = 'tssh_______r'; + select lives_ok('valid_storage_bucket_global_update', 'update to valid storage bucket state failed'); + + -- disable session recording without removing storage bucket + prepare valid_disable_session_recording as + update target_ssh + set enable_session_recording = false + where public_id = 'tssh_______r'; + select lives_ok('valid_disable_session_recording', 'update to valid storage bucket state failed'); + + select * from finish(); +rollback;