feat(storage): Add target storage_bucket relation

This commit is contained in:
Louis Ruch authored and Timothy Messier committed 2023-05-31 12:18:15 +00:00
1 parent 328b039138
commit 7263d08bc5
12 files changed
+487 -99

No files matched your search

@@ -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,
@@ -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
@@ -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
@@ -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;
+1 -1
View File
@@ -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
@@ -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
@@ -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;
@@ -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
@@ -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
@@ -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;
@@ -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;
@@ -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;