feat(ft546): enforce shadow database isolation
This commit is contained in:
77
sql/shadow/V001__ft546_shadow_read_views.sql
Normal file
77
sql/shadow/V001__ft546_shadow_read_views.sql
Normal file
@@ -0,0 +1,77 @@
|
||||
:setvar ShadowSourceDatabase "FT546-wcs"
|
||||
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
GO
|
||||
|
||||
IF DB_ID() <> DB_ID(N'$(ShadowSourceDatabase)')
|
||||
THROW 51000, 'Run the shadow read-view script only in ShadowSourceDatabase.', 1;
|
||||
GO
|
||||
|
||||
IF SCHEMA_ID(N'shadow_ro') IS NULL
|
||||
EXEC(N'CREATE SCHEMA shadow_ro AUTHORIZATION dbo');
|
||||
GO
|
||||
|
||||
CREATE OR ALTER VIEW shadow_ro.v_wcs_srgv_queue
|
||||
AS
|
||||
SELECT guid, task_no, task_type, priority, cargo, issued, path_info,
|
||||
execution_status, cargo_pick_site, cargo_place_site, start_site,
|
||||
end_site, the_floor, trun_path, up_down_site, create_date,
|
||||
start_date, finish_date, agv_id, default02, default03, default04,
|
||||
default05, default06, default07, default08
|
||||
FROM dbo.wcs_srgv_queue;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER VIEW shadow_ro.v_wcs_srgv_queue_finish
|
||||
AS
|
||||
SELECT guid, task_no, task_type, priority, cargo, issued, path_info,
|
||||
execution_status, cargo_pick_site, cargo_place_site, start_site,
|
||||
end_site, the_floor, trun_path, up_down_site, create_date,
|
||||
start_date, finish_date, agv_id, default02, default03, default04,
|
||||
default05, default06, default07, default08
|
||||
FROM dbo.wcs_srgv_queue_finish;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER VIEW shadow_ro.v_device_srgv
|
||||
AS
|
||||
SELECT guid, device_id, device_name, type, set_floor, power_time,
|
||||
last_time, agv_status, task_no, src_site, tar_site, curr_site,
|
||||
task_type, battery, work_mode, alarm, status, finish, charge_flag,
|
||||
work_phase, err_msg, ssj_request, change_layer, cargo_flag,
|
||||
manual_charge, path, allow_send, curr_floor, offline_site, locked,
|
||||
spare1, spare2, spare3, spare4, spare5
|
||||
FROM dbo.device_srgv;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER VIEW shadow_ro.v_wcs_srgv_map
|
||||
AS
|
||||
SELECT guid, x, y, z, axy, value, ux, uy, uz, uxy, space_name, remark,
|
||||
cargo_space_id, the_row, the_column, the_layer, site_id, plc_row,
|
||||
plc_column, plc_layer, warehouse, space_area
|
||||
FROM dbo.wcs_srgv_map;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER VIEW shadow_ro.v_bas_cargo_space
|
||||
AS
|
||||
SELECT cargo_space_id, warehouse_id, roadway_id, cargo_area_id, the_type,
|
||||
the_type_name, cargo_space_name, the_platoon, the_column, the_floor,
|
||||
status, locked, tray_code, cargo_hight, iss_double, group_coding_no,
|
||||
group_coding, cargo_space_logo, this_cargo_area, wms_cargo_space_id
|
||||
FROM dbo.bas_cargo_space;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER VIEW shadow_ro.v_tsj_execution
|
||||
AS
|
||||
SELECT guid, device_id, device_name, task_no, src_row, src_column,
|
||||
src_layer, tar_row, tar_column, tar_layer, src_site, tar_site,
|
||||
send_flag, send_type, task_type, create_date, start_date,
|
||||
finish_date, spare1, spare2, spare3, rwlx
|
||||
FROM dbo.tsj_device_execution_queue;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER VIEW shadow_ro.v_current_database_clock
|
||||
AS
|
||||
SELECT SYSUTCDATETIME() AS database_utc_time,
|
||||
DB_NAME() AS database_name,
|
||||
@@SERVERNAME AS server_name;
|
||||
GO
|
||||
192
sql/shadow/V002__ft546_shadow_read_login.sql
Normal file
192
sql/shadow/V002__ft546_shadow_read_login.sql
Normal file
@@ -0,0 +1,192 @@
|
||||
:setvar ShadowSourceLogin "ft546_shadow_ro"
|
||||
:setvar ShadowSourceUser "ft546_shadow_ro"
|
||||
:setvar ShadowSourcePassword "REPLACE_AT_DEPLOY_TIME"
|
||||
:setvar ShadowSourceDatabase "FT546-wcs"
|
||||
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
IF N'$(ShadowSourcePassword)' = N'REPLACE_AT_DEPLOY_TIME'
|
||||
THROW 51000, 'Supply ShadowSourcePassword through the approved secret channel.', 1;
|
||||
|
||||
IF DB_ID() <> DB_ID(N'$(ShadowSourceDatabase)')
|
||||
THROW 51001, 'Run the shadow source-login script only in ShadowSourceDatabase.', 1;
|
||||
|
||||
DECLARE @sourceLoginCreated bit = 0;
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.server_principals
|
||||
WHERE name = N'$(ShadowSourceLogin)')
|
||||
BEGIN
|
||||
DECLARE @createLogin nvarchar(max) =
|
||||
N'CREATE LOGIN ' + QUOTENAME(N'$(ShadowSourceLogin)')
|
||||
+ N' WITH PASSWORD = N'''
|
||||
+ REPLACE(N'$(ShadowSourcePassword)', N'''', N'''''')
|
||||
+ N''', CHECK_POLICY = ON, CHECK_EXPIRATION = ON';
|
||||
EXEC sys.sp_executesql @createLogin;
|
||||
SET @sourceLoginCreated = 1;
|
||||
END;
|
||||
|
||||
BEGIN TRY
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.server_role_members rm
|
||||
JOIN sys.server_principals role_principal
|
||||
ON role_principal.principal_id = rm.role_principal_id
|
||||
JOIN sys.server_principals login_principal
|
||||
ON login_principal.principal_id = rm.member_principal_id
|
||||
WHERE login_principal.name = N'$(ShadowSourceLogin)'
|
||||
)
|
||||
THROW 51002, 'The shadow source login must not belong to a server role.', 1;
|
||||
|
||||
DECLARE @serverContextSet bit = 0;
|
||||
BEGIN TRY
|
||||
EXECUTE AS LOGIN = N'$(ShadowSourceLogin)';
|
||||
SET @serverContextSet = 1;
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.fn_my_permissions(NULL, N'SERVER')
|
||||
WHERE permission_name NOT IN (N'CONNECT SQL', N'VIEW ANY DATABASE')
|
||||
)
|
||||
THROW 51007, 'The shadow source login retains forbidden server permissions.', 1;
|
||||
REVERT;
|
||||
SET @serverContextSet = 0;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
IF @serverContextSet = 1
|
||||
REVERT;
|
||||
THROW;
|
||||
END CATCH;
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.database_principals
|
||||
WHERE name = N'$(ShadowSourceUser)')
|
||||
BEGIN
|
||||
DECLARE @createUser nvarchar(max) =
|
||||
N'CREATE USER ' + QUOTENAME(N'$(ShadowSourceUser)')
|
||||
+ N' FOR LOGIN ' + QUOTENAME(N'$(ShadowSourceLogin)');
|
||||
EXEC sys.sp_executesql @createUser;
|
||||
END
|
||||
ELSE IF SUSER_SID(N'$(ShadowSourceLogin)') <> USER_SID(N'$(ShadowSourceUser)')
|
||||
THROW 51003, 'The existing shadow source user maps to a different login.', 1;
|
||||
|
||||
DECLARE @principal nvarchar(258) = QUOTENAME(N'$(ShadowSourceUser)');
|
||||
DECLARE @permissions nvarchar(max) =
|
||||
N'GRANT CONNECT TO ' + @principal + N';'
|
||||
+ N'REVOKE CONTROL FROM ' + @principal + N';'
|
||||
+ N'REVOKE SELECT FROM ' + @principal + N';'
|
||||
+ N'GRANT SELECT ON SCHEMA::shadow_ro TO ' + @principal + N';'
|
||||
+ N'DENY INSERT, UPDATE, DELETE, EXECUTE, ALTER, TAKE OWNERSHIP '
|
||||
+ N'TO ' + @principal + N';'
|
||||
+ N'DENY CONTROL ON OBJECT::dbo.wcs_srgv_queue TO ' + @principal + N';'
|
||||
+ N'DENY CONTROL ON OBJECT::dbo.wcs_srgv_queue_finish TO ' + @principal + N';'
|
||||
+ N'DENY CONTROL ON OBJECT::dbo.device_srgv TO ' + @principal + N';'
|
||||
+ N'DENY CONTROL ON OBJECT::dbo.wcs_srgv_map TO ' + @principal + N';'
|
||||
+ N'DENY CONTROL ON OBJECT::dbo.bas_cargo_space TO ' + @principal + N';'
|
||||
+ N'DENY CONTROL ON OBJECT::dbo.tsj_device_execution_queue TO '
|
||||
+ @principal + N';'
|
||||
+ N'DENY SELECT ON OBJECT::dbo.wcs_srgv_queue TO ' + @principal + N';'
|
||||
+ N'DENY SELECT ON OBJECT::dbo.wcs_srgv_queue_finish TO ' + @principal + N';'
|
||||
+ N'DENY SELECT ON OBJECT::dbo.device_srgv TO ' + @principal + N';'
|
||||
+ N'DENY SELECT ON OBJECT::dbo.wcs_srgv_map TO ' + @principal + N';'
|
||||
+ N'DENY SELECT ON OBJECT::dbo.bas_cargo_space TO ' + @principal + N';'
|
||||
+ N'DENY SELECT ON OBJECT::dbo.tsj_device_execution_queue TO '
|
||||
+ @principal + N';';
|
||||
EXEC sys.sp_executesql @permissions;
|
||||
|
||||
DECLARE @role sysname;
|
||||
DECLARE forbidden_roles CURSOR LOCAL FAST_FORWARD FOR
|
||||
SELECT role_principal.name
|
||||
FROM sys.database_principals role_principal
|
||||
JOIN sys.database_role_members rm
|
||||
ON rm.role_principal_id = role_principal.principal_id
|
||||
JOIN sys.database_principals member_principal
|
||||
ON member_principal.principal_id = rm.member_principal_id
|
||||
WHERE role_principal.type = 'R'
|
||||
AND member_principal.name = N'$(ShadowSourceUser)';
|
||||
|
||||
OPEN forbidden_roles;
|
||||
FETCH NEXT FROM forbidden_roles INTO @role;
|
||||
WHILE @@FETCH_STATUS = 0
|
||||
BEGIN
|
||||
IF IS_ROLEMEMBER(@role, N'$(ShadowSourceUser)') = 1
|
||||
BEGIN
|
||||
DECLARE @dropRole nvarchar(max) = N'ALTER ROLE '
|
||||
+ QUOTENAME(@role) + N' DROP MEMBER ' + @principal;
|
||||
EXEC sys.sp_executesql @dropRole;
|
||||
END;
|
||||
FETCH NEXT FROM forbidden_roles INTO @role;
|
||||
END;
|
||||
CLOSE forbidden_roles;
|
||||
DEALLOCATE forbidden_roles;
|
||||
|
||||
DECLARE @databaseContextSet bit = 0;
|
||||
BEGIN TRY
|
||||
EXECUTE AS USER = N'$(ShadowSourceUser)';
|
||||
SET @databaseContextSet = 1;
|
||||
IF HAS_PERMS_BY_NAME(DB_NAME(), N'DATABASE', N'SELECT') <> 0
|
||||
OR HAS_PERMS_BY_NAME(DB_NAME(), N'DATABASE', N'INSERT') <> 0
|
||||
OR HAS_PERMS_BY_NAME(DB_NAME(), N'DATABASE', N'UPDATE') <> 0
|
||||
OR HAS_PERMS_BY_NAME(DB_NAME(), N'DATABASE', N'DELETE') <> 0
|
||||
OR HAS_PERMS_BY_NAME(DB_NAME(), N'DATABASE', N'EXECUTE') <> 0
|
||||
OR HAS_PERMS_BY_NAME(DB_NAME(), N'DATABASE', N'ALTER') <> 0
|
||||
OR HAS_PERMS_BY_NAME(DB_NAME(), N'DATABASE', N'CONTROL') <> 0
|
||||
OR HAS_PERMS_BY_NAME(DB_NAME(), N'DATABASE', N'TAKE OWNERSHIP') <> 0
|
||||
THROW 51004, 'The shadow source login retains forbidden database permissions.', 1;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.schemas s
|
||||
CROSS APPLY sys.fn_my_permissions(QUOTENAME(s.name), N'SCHEMA') p
|
||||
WHERE s.name NOT IN (N'sys', N'INFORMATION_SCHEMA')
|
||||
AND NOT (s.name = N'shadow_ro' AND p.permission_name = N'SELECT')
|
||||
)
|
||||
THROW 51005, 'The shadow source login retains forbidden schema permissions.', 1;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.objects o
|
||||
JOIN sys.schemas s ON s.schema_id = o.schema_id
|
||||
CROSS APPLY sys.fn_my_permissions(
|
||||
QUOTENAME(s.name) + N'.' + QUOTENAME(o.name), N'OBJECT') p
|
||||
WHERE o.is_ms_shipped = 0
|
||||
AND NOT (
|
||||
s.name = N'shadow_ro'
|
||||
AND o.name IN (
|
||||
N'v_wcs_srgv_queue', N'v_wcs_srgv_queue_finish',
|
||||
N'v_device_srgv', N'v_wcs_srgv_map',
|
||||
N'v_bas_cargo_space', N'v_tsj_execution',
|
||||
N'v_current_database_clock')
|
||||
AND p.permission_name = N'SELECT'
|
||||
)
|
||||
)
|
||||
THROW 51006, 'The shadow source login retains forbidden object permissions.', 1;
|
||||
|
||||
SELECT TOP (0) * FROM shadow_ro.v_wcs_srgv_queue;
|
||||
SELECT TOP (0) * FROM shadow_ro.v_wcs_srgv_queue_finish;
|
||||
SELECT TOP (0) * FROM shadow_ro.v_device_srgv;
|
||||
SELECT TOP (0) * FROM shadow_ro.v_wcs_srgv_map;
|
||||
SELECT TOP (0) * FROM shadow_ro.v_bas_cargo_space;
|
||||
SELECT TOP (0) * FROM shadow_ro.v_tsj_execution;
|
||||
SELECT TOP (0) * FROM shadow_ro.v_current_database_clock;
|
||||
REVERT;
|
||||
SET @databaseContextSet = 0;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
IF @databaseContextSet = 1
|
||||
REVERT;
|
||||
THROW;
|
||||
END CATCH;
|
||||
|
||||
COMMIT TRANSACTION;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
IF XACT_STATE() <> 0
|
||||
ROLLBACK TRANSACTION;
|
||||
IF @sourceLoginCreated = 1
|
||||
BEGIN
|
||||
DECLARE @dropSourceLogin nvarchar(max) =
|
||||
N'DROP LOGIN ' + QUOTENAME(N'$(ShadowSourceLogin)');
|
||||
EXEC sys.sp_executesql @dropSourceLogin;
|
||||
END;
|
||||
THROW;
|
||||
END CATCH;
|
||||
312
sql/shadow/V003__ft546_shadow_evidence.sql
Normal file
312
sql/shadow/V003__ft546_shadow_evidence.sql
Normal file
@@ -0,0 +1,312 @@
|
||||
:setvar ShadowSourceDatabase "FT546-wcs"
|
||||
:setvar ShadowEvidenceLogin "ft546_shadow_evidence"
|
||||
:setvar ShadowEvidenceUser "ft546_shadow_evidence"
|
||||
:setvar ShadowEvidencePassword "REPLACE_AT_DEPLOY_TIME"
|
||||
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
IF DB_ID() = DB_ID(N'$(ShadowSourceDatabase)')
|
||||
THROW 51001, 'Evidence objects must be installed in a separate database.', 1;
|
||||
|
||||
IF N'$(ShadowEvidencePassword)' = N'REPLACE_AT_DEPLOY_TIME'
|
||||
THROW 51002, 'Supply ShadowEvidencePassword through the approved secret channel.', 1;
|
||||
|
||||
DECLARE @evidenceLoginCreated bit = 0;
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.server_principals
|
||||
WHERE name = N'$(ShadowEvidenceLogin)')
|
||||
BEGIN
|
||||
DECLARE @createEvidenceLogin nvarchar(max) =
|
||||
N'CREATE LOGIN ' + QUOTENAME(N'$(ShadowEvidenceLogin)')
|
||||
+ N' WITH PASSWORD = N'''
|
||||
+ REPLACE(N'$(ShadowEvidencePassword)', N'''', N'''''')
|
||||
+ N''', CHECK_POLICY = ON, CHECK_EXPIRATION = ON';
|
||||
EXEC sys.sp_executesql @createEvidenceLogin;
|
||||
SET @evidenceLoginCreated = 1;
|
||||
END;
|
||||
|
||||
BEGIN TRY
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.server_role_members rm
|
||||
JOIN sys.server_principals login_principal
|
||||
ON login_principal.principal_id = rm.member_principal_id
|
||||
WHERE login_principal.name = N'$(ShadowEvidenceLogin)'
|
||||
)
|
||||
THROW 51003, 'The shadow evidence login must not belong to a server role.', 1;
|
||||
|
||||
DECLARE @serverContextSet bit = 0;
|
||||
BEGIN TRY
|
||||
EXECUTE AS LOGIN = N'$(ShadowEvidenceLogin)';
|
||||
SET @serverContextSet = 1;
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM sys.fn_my_permissions(NULL, N'SERVER')
|
||||
WHERE permission_name NOT IN (N'CONNECT SQL', N'VIEW ANY DATABASE')
|
||||
)
|
||||
THROW 51009, 'The shadow evidence login retains forbidden server permissions.', 1;
|
||||
REVERT;
|
||||
SET @serverContextSet = 0;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
IF @serverContextSet = 1
|
||||
REVERT;
|
||||
THROW;
|
||||
END CATCH;
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.database_principals
|
||||
WHERE name = N'$(ShadowEvidenceUser)')
|
||||
BEGIN
|
||||
DECLARE @createEvidenceUser nvarchar(max) =
|
||||
N'CREATE USER ' + QUOTENAME(N'$(ShadowEvidenceUser)')
|
||||
+ N' FOR LOGIN ' + QUOTENAME(N'$(ShadowEvidenceLogin)');
|
||||
EXEC sys.sp_executesql @createEvidenceUser;
|
||||
END
|
||||
ELSE IF SUSER_SID(N'$(ShadowEvidenceLogin)')
|
||||
<> USER_SID(N'$(ShadowEvidenceUser)')
|
||||
THROW 51004, 'The existing shadow evidence user maps to a different login.', 1;
|
||||
|
||||
DECLARE @evidencePrincipal nvarchar(258) =
|
||||
QUOTENAME(N'$(ShadowEvidenceUser)');
|
||||
DECLARE @evidenceRole sysname;
|
||||
DECLARE evidence_roles CURSOR LOCAL FAST_FORWARD FOR
|
||||
SELECT role_principal.name
|
||||
FROM sys.database_principals role_principal
|
||||
JOIN sys.database_role_members rm
|
||||
ON rm.role_principal_id = role_principal.principal_id
|
||||
JOIN sys.database_principals member_principal
|
||||
ON member_principal.principal_id = rm.member_principal_id
|
||||
WHERE role_principal.type = 'R'
|
||||
AND member_principal.name = N'$(ShadowEvidenceUser)';
|
||||
|
||||
OPEN evidence_roles;
|
||||
FETCH NEXT FROM evidence_roles INTO @evidenceRole;
|
||||
WHILE @@FETCH_STATUS = 0
|
||||
BEGIN
|
||||
DECLARE @dropEvidenceRole nvarchar(max) = N'ALTER ROLE '
|
||||
+ QUOTENAME(@evidenceRole) + N' DROP MEMBER ' + @evidencePrincipal;
|
||||
EXEC sys.sp_executesql @dropEvidenceRole;
|
||||
FETCH NEXT FROM evidence_roles INTO @evidenceRole;
|
||||
END;
|
||||
CLOSE evidence_roles;
|
||||
DEALLOCATE evidence_roles;
|
||||
|
||||
DECLARE @revokeEvidenceDatabase nvarchar(max) =
|
||||
N'REVOKE CONTROL, ALTER, TAKE OWNERSHIP, SELECT, INSERT, UPDATE, DELETE, '
|
||||
+ N'EXECUTE FROM ' + @evidencePrincipal + N';';
|
||||
EXEC sys.sp_executesql @revokeEvidenceDatabase;
|
||||
|
||||
IF OBJECT_ID(N'dbo.shadow_run', N'U') IS NULL
|
||||
CREATE TABLE dbo.shadow_run (
|
||||
run_id uniqueidentifier NOT NULL PRIMARY KEY,
|
||||
started_at datetime2(3) NOT NULL,
|
||||
artifact_sha256 char(64) NOT NULL,
|
||||
configuration_sha256 char(64) NOT NULL,
|
||||
source_server nvarchar(256) NOT NULL,
|
||||
source_database sysname NOT NULL,
|
||||
source_login sysname NOT NULL,
|
||||
status varchar(32) NOT NULL
|
||||
);
|
||||
|
||||
IF OBJECT_ID(N'dbo.shadow_cycle', N'U') IS NULL
|
||||
CREATE TABLE dbo.shadow_cycle (
|
||||
cycle_id uniqueidentifier NOT NULL PRIMARY KEY,
|
||||
run_id uniqueidentifier NOT NULL,
|
||||
input_batch_hash char(64) NOT NULL,
|
||||
parent_task_id nvarchar(128) NOT NULL,
|
||||
phase varchar(64) NOT NULL,
|
||||
route_version bigint NOT NULL,
|
||||
status varchar(64) NOT NULL,
|
||||
reason_code nvarchar(512) NULL,
|
||||
database_captured_at datetime2(3) NULL,
|
||||
completed_at datetime2(3) NOT NULL,
|
||||
evidence_hash char(64) NOT NULL,
|
||||
CONSTRAINT fk_shadow_cycle_run FOREIGN KEY (run_id)
|
||||
REFERENCES dbo.shadow_run(run_id),
|
||||
CONSTRAINT uq_shadow_cycle_identity UNIQUE
|
||||
(run_id, input_batch_hash, parent_task_id, phase, route_version)
|
||||
);
|
||||
|
||||
IF OBJECT_ID(N'dbo.shadow_input_batch', N'U') IS NULL
|
||||
CREATE TABLE dbo.shadow_input_batch (
|
||||
cycle_id uniqueidentifier NOT NULL PRIMARY KEY,
|
||||
content_hash char(64) NOT NULL,
|
||||
captured_json nvarchar(max) NOT NULL,
|
||||
CONSTRAINT fk_shadow_input_cycle FOREIGN KEY (cycle_id)
|
||||
REFERENCES dbo.shadow_cycle(cycle_id)
|
||||
);
|
||||
|
||||
IF OBJECT_ID(N'dbo.shadow_legacy_queue', N'U') IS NULL
|
||||
CREATE TABLE dbo.shadow_legacy_queue (
|
||||
cycle_id uniqueidentifier NOT NULL PRIMARY KEY,
|
||||
vehicle_id nvarchar(64) NULL,
|
||||
path_info nvarchar(max) NULL,
|
||||
result_status varchar(64) NOT NULL,
|
||||
result_json nvarchar(max) NOT NULL,
|
||||
content_hash char(64) NOT NULL,
|
||||
CONSTRAINT fk_shadow_legacy_cycle FOREIGN KEY (cycle_id)
|
||||
REFERENCES dbo.shadow_cycle(cycle_id)
|
||||
);
|
||||
|
||||
IF OBJECT_ID(N'dbo.shadow_decision', N'U') IS NULL
|
||||
CREATE TABLE dbo.shadow_decision (
|
||||
cycle_id uniqueidentifier NOT NULL PRIMARY KEY,
|
||||
decision varchar(64) NOT NULL,
|
||||
vehicle_id nvarchar(64) NULL,
|
||||
reason_code nvarchar(512) NULL,
|
||||
decision_json nvarchar(max) NOT NULL,
|
||||
content_hash char(64) NOT NULL,
|
||||
CONSTRAINT fk_shadow_decision_cycle FOREIGN KEY (cycle_id)
|
||||
REFERENCES dbo.shadow_cycle(cycle_id)
|
||||
);
|
||||
|
||||
IF OBJECT_ID(N'dbo.shadow_encoded_write', N'U') IS NULL
|
||||
CREATE TABLE dbo.shadow_encoded_write (
|
||||
cycle_id uniqueidentifier NOT NULL,
|
||||
write_sequence int NOT NULL,
|
||||
register_address varchar(16) NOT NULL,
|
||||
data_type varchar(16) NOT NULL,
|
||||
values_json nvarchar(max) NOT NULL,
|
||||
trigger_value bigint NULL,
|
||||
CONSTRAINT pk_shadow_encoded_write PRIMARY KEY
|
||||
(cycle_id, write_sequence),
|
||||
CONSTRAINT fk_shadow_write_cycle FOREIGN KEY (cycle_id)
|
||||
REFERENCES dbo.shadow_cycle(cycle_id)
|
||||
);
|
||||
|
||||
IF OBJECT_ID(N'dbo.shadow_difference', N'U') IS NULL
|
||||
CREATE TABLE dbo.shadow_difference (
|
||||
difference_id bigint IDENTITY(1, 1) NOT NULL PRIMARY KEY,
|
||||
cycle_id uniqueidentifier NOT NULL,
|
||||
category varchar(64) NOT NULL,
|
||||
reason_code nvarchar(512) NOT NULL,
|
||||
expected_json nvarchar(max) NULL,
|
||||
actual_json nvarchar(max) NULL,
|
||||
CONSTRAINT fk_shadow_difference_cycle FOREIGN KEY (cycle_id)
|
||||
REFERENCES dbo.shadow_cycle(cycle_id)
|
||||
);
|
||||
|
||||
IF OBJECT_ID(N'dbo.shadow_traffic_state', N'U') IS NULL
|
||||
CREATE TABLE dbo.shadow_traffic_state (
|
||||
state_key nvarchar(256) NOT NULL PRIMARY KEY,
|
||||
snapshot_version bigint NOT NULL,
|
||||
state_json nvarchar(max) NOT NULL,
|
||||
updated_at datetime2(3) NOT NULL
|
||||
);
|
||||
|
||||
IF OBJECT_ID(N'dbo.shadow_hold_history', N'U') IS NULL
|
||||
CREATE TABLE dbo.shadow_hold_history (
|
||||
parent_task_id nvarchar(128) NOT NULL,
|
||||
vehicle_id nvarchar(64) NOT NULL,
|
||||
resource_key nvarchar(256) NOT NULL,
|
||||
reason_code nvarchar(512) NOT NULL,
|
||||
consecutive_count int NOT NULL,
|
||||
updated_at datetime2(3) NOT NULL,
|
||||
CONSTRAINT pk_shadow_hold_history PRIMARY KEY
|
||||
(parent_task_id, vehicle_id, resource_key, reason_code)
|
||||
);
|
||||
|
||||
IF OBJECT_ID(N'dbo.shadow_startup_check', N'U') IS NULL
|
||||
CREATE TABLE dbo.shadow_startup_check (
|
||||
check_id bigint IDENTITY(1, 1) NOT NULL PRIMARY KEY,
|
||||
check_name varchar(128) NOT NULL,
|
||||
status varchar(32) NOT NULL,
|
||||
checked_at datetime2(3) NOT NULL
|
||||
);
|
||||
|
||||
DECLARE @grantEvidence nvarchar(max) = N'GRANT CONNECT TO '
|
||||
+ @evidencePrincipal + N';';
|
||||
DECLARE @evidenceTable sysname;
|
||||
DECLARE evidence_tables CURSOR LOCAL FAST_FORWARD FOR
|
||||
SELECT name
|
||||
FROM sys.tables
|
||||
WHERE schema_id = SCHEMA_ID(N'dbo')
|
||||
AND name IN (N'shadow_run', N'shadow_cycle', N'shadow_input_batch',
|
||||
N'shadow_legacy_queue', N'shadow_decision',
|
||||
N'shadow_encoded_write', N'shadow_difference',
|
||||
N'shadow_traffic_state', N'shadow_hold_history',
|
||||
N'shadow_startup_check');
|
||||
OPEN evidence_tables;
|
||||
FETCH NEXT FROM evidence_tables INTO @evidenceTable;
|
||||
WHILE @@FETCH_STATUS = 0
|
||||
BEGIN
|
||||
SET @grantEvidence += N'GRANT SELECT, INSERT, UPDATE, DELETE ON OBJECT::dbo.'
|
||||
+ QUOTENAME(@evidenceTable) + N' TO ' + @evidencePrincipal + N';';
|
||||
FETCH NEXT FROM evidence_tables INTO @evidenceTable;
|
||||
END;
|
||||
CLOSE evidence_tables;
|
||||
DEALLOCATE evidence_tables;
|
||||
EXEC sys.sp_executesql @grantEvidence;
|
||||
|
||||
DECLARE @databaseContextSet bit = 0;
|
||||
BEGIN TRY
|
||||
EXECUTE AS USER = N'$(ShadowEvidenceUser)';
|
||||
SET @databaseContextSet = 1;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.database_role_members rm
|
||||
JOIN sys.database_principals member_principal
|
||||
ON member_principal.principal_id = rm.member_principal_id
|
||||
WHERE member_principal.name = USER_NAME()
|
||||
)
|
||||
THROW 51005, 'The shadow evidence login retains a database role.', 1;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM sys.fn_my_permissions(NULL, N'DATABASE')
|
||||
WHERE permission_name <> N'CONNECT'
|
||||
)
|
||||
THROW 51006, 'The shadow evidence login retains forbidden database permissions.', 1;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.schemas s
|
||||
CROSS APPLY sys.fn_my_permissions(QUOTENAME(s.name), N'SCHEMA') p
|
||||
WHERE s.name NOT IN (N'sys', N'INFORMATION_SCHEMA')
|
||||
)
|
||||
THROW 51007, 'The shadow evidence login retains forbidden schema permissions.', 1;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.objects o
|
||||
JOIN sys.schemas s ON s.schema_id = o.schema_id
|
||||
CROSS APPLY sys.fn_my_permissions(
|
||||
QUOTENAME(s.name) + N'.' + QUOTENAME(o.name), N'OBJECT') p
|
||||
WHERE o.is_ms_shipped = 0
|
||||
AND NOT (
|
||||
s.name = N'dbo'
|
||||
AND o.name IN (
|
||||
N'shadow_run', N'shadow_cycle', N'shadow_input_batch',
|
||||
N'shadow_legacy_queue', N'shadow_decision',
|
||||
N'shadow_encoded_write', N'shadow_difference',
|
||||
N'shadow_traffic_state', N'shadow_hold_history',
|
||||
N'shadow_startup_check')
|
||||
AND p.permission_name IN (
|
||||
N'SELECT', N'INSERT', N'UPDATE', N'DELETE')
|
||||
)
|
||||
)
|
||||
THROW 51008, 'The shadow evidence login retains forbidden object permissions.', 1;
|
||||
|
||||
REVERT;
|
||||
SET @databaseContextSet = 0;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
IF @databaseContextSet = 1
|
||||
REVERT;
|
||||
THROW;
|
||||
END CATCH;
|
||||
|
||||
COMMIT TRANSACTION;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
IF XACT_STATE() <> 0
|
||||
ROLLBACK TRANSACTION;
|
||||
IF @evidenceLoginCreated = 1
|
||||
BEGIN
|
||||
DECLARE @dropEvidenceLogin nvarchar(max) =
|
||||
N'DROP LOGIN ' + QUOTENAME(N'$(ShadowEvidenceLogin)');
|
||||
EXEC sys.sp_executesql @dropEvidenceLogin;
|
||||
END;
|
||||
THROW;
|
||||
END CATCH;
|
||||
137
sql/shadow/verify_ft546_shadow_permissions.sql
Normal file
137
sql/shadow/verify_ft546_shadow_permissions.sql
Normal file
@@ -0,0 +1,137 @@
|
||||
:setvar ShadowSourceLogin "ft546_shadow_ro"
|
||||
:setvar ShadowSourceDatabase "FT546-wcs"
|
||||
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
IF DB_ID() <> DB_ID(N'$(ShadowSourceDatabase)')
|
||||
THROW 51010, 'Run source permission verification only in ShadowSourceDatabase.', 1;
|
||||
|
||||
IF SUSER_ID(N'$(ShadowSourceLogin)') IS NULL
|
||||
THROW 51011, 'ShadowSourceLogin does not exist.', 1;
|
||||
|
||||
DECLARE @checks TABLE (
|
||||
check_name nvarchar(512) NOT NULL,
|
||||
expected_value int NOT NULL,
|
||||
actual_value int NULL
|
||||
);
|
||||
|
||||
INSERT INTO @checks (check_name, expected_value, actual_value)
|
||||
SELECT N'server_role_membership_count', 0, COUNT(*)
|
||||
FROM sys.server_role_members rm
|
||||
JOIN sys.server_principals member_principal
|
||||
ON member_principal.principal_id = rm.member_principal_id
|
||||
WHERE member_principal.name = N'$(ShadowSourceLogin)';
|
||||
|
||||
BEGIN TRY
|
||||
EXECUTE AS LOGIN = N'$(ShadowSourceLogin)';
|
||||
|
||||
DECLARE @objects TABLE (
|
||||
object_name nvarchar(512) NOT NULL,
|
||||
expected_select int NOT NULL,
|
||||
is_base_table bit NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO @objects (object_name, expected_select, is_base_table)
|
||||
VALUES
|
||||
(N'shadow_ro.v_wcs_srgv_queue', 1, 0),
|
||||
(N'shadow_ro.v_wcs_srgv_queue_finish', 1, 0),
|
||||
(N'shadow_ro.v_device_srgv', 1, 0),
|
||||
(N'shadow_ro.v_wcs_srgv_map', 1, 0),
|
||||
(N'shadow_ro.v_bas_cargo_space', 1, 0),
|
||||
(N'shadow_ro.v_tsj_execution', 1, 0),
|
||||
(N'shadow_ro.v_current_database_clock', 1, 0),
|
||||
(N'dbo.wcs_srgv_queue', 0, 1),
|
||||
(N'dbo.wcs_srgv_queue_finish', 0, 1),
|
||||
(N'dbo.device_srgv', 0, 1),
|
||||
(N'dbo.wcs_srgv_map', 0, 1),
|
||||
(N'dbo.bas_cargo_space', 0, 1),
|
||||
(N'dbo.tsj_device_execution_queue', 0, 1);
|
||||
|
||||
INSERT INTO @checks (check_name, expected_value, actual_value)
|
||||
SELECT N'select:' + object_name, expected_select,
|
||||
HAS_PERMS_BY_NAME(object_name, N'OBJECT', N'SELECT')
|
||||
FROM @objects;
|
||||
|
||||
INSERT INTO @checks (check_name, expected_value, actual_value)
|
||||
SELECT LOWER(permission_name) + N':' + object_name, 0,
|
||||
HAS_PERMS_BY_NAME(object_name, N'OBJECT', permission_name)
|
||||
FROM @objects
|
||||
CROSS JOIN (VALUES (N'INSERT'), (N'UPDATE'), (N'DELETE'),
|
||||
(N'ALTER'), (N'CONTROL')) AS operations(permission_name)
|
||||
WHERE is_base_table = 1;
|
||||
|
||||
INSERT INTO @checks (check_name, expected_value, actual_value)
|
||||
SELECT N'database_permission:' + permission_name, 0,
|
||||
HAS_PERMS_BY_NAME(DB_NAME(), N'DATABASE', permission_name)
|
||||
FROM (VALUES (N'SELECT'), (N'INSERT'), (N'UPDATE'), (N'DELETE'),
|
||||
(N'EXECUTE'), (N'ALTER'), (N'CONTROL'),
|
||||
(N'TAKE OWNERSHIP')) AS permissions(permission_name);
|
||||
|
||||
INSERT INTO @checks (check_name, expected_value, actual_value)
|
||||
SELECT N'database_role:' + role_name, 0, IS_MEMBER(role_name)
|
||||
FROM (VALUES (N'db_owner'), (N'db_datareader'),
|
||||
(N'db_datawriter'), (N'db_ddladmin'),
|
||||
(N'db_securityadmin'), (N'db_accessadmin'))
|
||||
AS roles(role_name);
|
||||
|
||||
INSERT INTO @checks (check_name, expected_value, actual_value)
|
||||
SELECT N'forbidden_effective_database_permission_count', 0, COUNT(*)
|
||||
FROM sys.fn_my_permissions(NULL, N'DATABASE')
|
||||
WHERE permission_name IN (N'SELECT', N'INSERT', N'UPDATE', N'DELETE',
|
||||
N'EXECUTE', N'ALTER', N'CONTROL',
|
||||
N'TAKE OWNERSHIP');
|
||||
|
||||
INSERT INTO @checks (check_name, expected_value, actual_value)
|
||||
SELECT N'forbidden_effective_server_permission_count', 0, COUNT(*)
|
||||
FROM sys.fn_my_permissions(NULL, N'SERVER')
|
||||
WHERE permission_name NOT IN (N'CONNECT SQL', N'VIEW ANY DATABASE');
|
||||
|
||||
INSERT INTO @checks (check_name, expected_value, actual_value)
|
||||
SELECT N'forbidden_effective_schema_permission_count', 0, COUNT(*)
|
||||
FROM sys.schemas s
|
||||
CROSS APPLY sys.fn_my_permissions(QUOTENAME(s.name), N'SCHEMA') p
|
||||
WHERE s.name NOT IN (N'sys', N'INFORMATION_SCHEMA')
|
||||
AND NOT (s.name = N'shadow_ro' AND p.permission_name = N'SELECT');
|
||||
|
||||
INSERT INTO @checks (check_name, expected_value, actual_value)
|
||||
SELECT N'forbidden_effective_object_permission_count', 0, COUNT(*)
|
||||
FROM sys.objects o
|
||||
JOIN sys.schemas s ON s.schema_id = o.schema_id
|
||||
CROSS APPLY sys.fn_my_permissions(
|
||||
QUOTENAME(s.name) + N'.' + QUOTENAME(o.name), N'OBJECT') p
|
||||
WHERE o.is_ms_shipped = 0
|
||||
AND NOT (
|
||||
s.name = N'shadow_ro'
|
||||
AND o.name IN (N'v_wcs_srgv_queue', N'v_wcs_srgv_queue_finish',
|
||||
N'v_device_srgv', N'v_wcs_srgv_map',
|
||||
N'v_bas_cargo_space', N'v_tsj_execution',
|
||||
N'v_current_database_clock')
|
||||
AND p.permission_name = N'SELECT'
|
||||
);
|
||||
|
||||
SELECT TOP (0) * FROM shadow_ro.v_wcs_srgv_queue;
|
||||
SELECT TOP (0) * FROM shadow_ro.v_wcs_srgv_queue_finish;
|
||||
SELECT TOP (0) * FROM shadow_ro.v_device_srgv;
|
||||
SELECT TOP (0) * FROM shadow_ro.v_wcs_srgv_map;
|
||||
SELECT TOP (0) * FROM shadow_ro.v_bas_cargo_space;
|
||||
SELECT TOP (0) * FROM shadow_ro.v_tsj_execution;
|
||||
SELECT TOP (0) * FROM shadow_ro.v_current_database_clock;
|
||||
|
||||
REVERT;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
IF ORIGINAL_LOGIN() <> SUSER_SNAME()
|
||||
REVERT;
|
||||
THROW;
|
||||
END CATCH;
|
||||
|
||||
SELECT check_name, expected_value, actual_value,
|
||||
CASE WHEN actual_value = expected_value THEN N'PASS' ELSE N'FAIL' END
|
||||
AS status
|
||||
FROM @checks
|
||||
ORDER BY check_name;
|
||||
|
||||
IF EXISTS (SELECT 1 FROM @checks
|
||||
WHERE actual_value IS NULL OR actual_value <> expected_value)
|
||||
THROW 51012, 'FT546 shadow source permission verification failed.', 1;
|
||||
Reference in New Issue
Block a user