Add first risk assememnt implementation
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
1
pkg/coredata/migrations/20240421T032700Z.sql
Normal file
1
pkg/coredata/migrations/20240421T032700Z.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE peoples DROP COLUMN version;
|
||||
305
pkg/coredata/migrations/20250420T120000Z.sql
Normal file
305
pkg/coredata/migrations/20250420T120000Z.sql
Normal file
@@ -0,0 +1,305 @@
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
-- PostgreSQL implementation of the GID generation system
|
||||
|
||||
-- 1. First, create functions for TenantID generation
|
||||
CREATE OR REPLACE FUNCTION generate_machine_id()
|
||||
RETURNS bytea AS $func$
|
||||
DECLARE
|
||||
machine_id bytea;
|
||||
BEGIN
|
||||
-- Generate 3 random bytes for machine ID
|
||||
machine_id := decode(encode(gen_random_bytes(3), 'hex'), 'hex');
|
||||
RETURN machine_id;
|
||||
END;
|
||||
$func$ LANGUAGE plpgsql STABLE;
|
||||
|
||||
-- Store machine ID as a database-wide setting (run once)
|
||||
DO $block$
|
||||
BEGIN
|
||||
-- Check if the setting exists
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_settings WHERE name = 'app.machine_id') THEN
|
||||
-- Create custom parameter in postgresql.conf or via ALTER SYSTEM
|
||||
PERFORM set_config('app.machine_id', encode(generate_machine_id(), 'hex'), false);
|
||||
END IF;
|
||||
END $block$;
|
||||
|
||||
-- Counter for tenant ID generation (used atomically)
|
||||
CREATE SEQUENCE IF NOT EXISTS tenant_id_counter_seq;
|
||||
|
||||
-- Function to generate a TenantID
|
||||
CREATE OR REPLACE FUNCTION generate_tenant_id()
|
||||
RETURNS bytea AS $func$
|
||||
DECLARE
|
||||
id bytea;
|
||||
machine_id bytea;
|
||||
timestamp_bytes bytea;
|
||||
counter_bytes bytea;
|
||||
counter_val int;
|
||||
BEGIN
|
||||
-- 1. Get machine ID (3 bytes)
|
||||
machine_id := decode(current_setting('app.machine_id'), 'hex');
|
||||
|
||||
-- 2. Get timestamp bytes (3 bytes - Unix time in seconds)
|
||||
timestamp_bytes := substring(int8send(extract(epoch from now())::bigint) from 6 for 3);
|
||||
|
||||
-- 3. Get counter (2 bytes)
|
||||
counter_val := nextval('tenant_id_counter_seq') % 65536; -- 2^16
|
||||
counter_bytes := substring(int4send(counter_val) from 3 for 2);
|
||||
|
||||
-- 4. Combine all parts
|
||||
id := machine_id || timestamp_bytes || counter_bytes;
|
||||
|
||||
RETURN id;
|
||||
END;
|
||||
$func$ LANGUAGE plpgsql VOLATILE;
|
||||
|
||||
-- Function to convert TenantID to string
|
||||
CREATE OR REPLACE FUNCTION tenant_id_to_string(tenant_id bytea)
|
||||
RETURNS text AS $func$
|
||||
BEGIN
|
||||
-- Make sure to handle padding properly - remove trailing '=' characters
|
||||
RETURN rtrim(translate(encode(tenant_id, 'base64'), '+/', '-_'), '=');
|
||||
END;
|
||||
$func$ LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
-- Function to parse tenant ID from string
|
||||
CREATE OR REPLACE FUNCTION parse_tenant_id(encoded text)
|
||||
RETURNS bytea AS $func$
|
||||
DECLARE
|
||||
decoded bytea;
|
||||
padded_input text;
|
||||
padding_needed int;
|
||||
BEGIN
|
||||
-- Add proper padding for base64 decoding
|
||||
padding_needed := (4 - (length(encoded) % 4)) % 4;
|
||||
padded_input := encoded || repeat('=', padding_needed);
|
||||
|
||||
-- Replace URL-safe chars and decode
|
||||
BEGIN
|
||||
decoded := decode(translate(padded_input, '-_', '+/'), 'base64');
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
RAISE EXCEPTION 'Invalid base64 encoding in tenant ID';
|
||||
END;
|
||||
|
||||
-- Validate length
|
||||
IF octet_length(decoded) != 8 THEN
|
||||
RAISE EXCEPTION 'Invalid tenant ID length: got %, want 8', octet_length(decoded);
|
||||
END IF;
|
||||
|
||||
RETURN decoded;
|
||||
END;
|
||||
$func$ LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
-- 2. Now create functions for full GID generation
|
||||
|
||||
-- Function to generate a GID
|
||||
CREATE OR REPLACE FUNCTION generate_gid(tenant_id bytea, entity_type int)
|
||||
RETURNS bytea AS $func$
|
||||
DECLARE
|
||||
id bytea;
|
||||
timestamp_ms_bytes bytea;
|
||||
entity_type_bytes bytea;
|
||||
random_bytes bytea;
|
||||
BEGIN
|
||||
-- Validate tenant_id
|
||||
IF tenant_id IS NULL OR octet_length(tenant_id) != 8 THEN
|
||||
RAISE EXCEPTION 'Invalid tenant ID: must be 8 bytes';
|
||||
END IF;
|
||||
|
||||
-- 1. Start with tenant ID (8 bytes)
|
||||
id := tenant_id;
|
||||
|
||||
-- 2. Add entity type (2 bytes)
|
||||
entity_type_bytes := substring(int4send(entity_type) from 3 for 2);
|
||||
id := id || entity_type_bytes;
|
||||
|
||||
-- 3. Add timestamp in milliseconds (8 bytes)
|
||||
-- Extract milliseconds since epoch
|
||||
timestamp_ms_bytes := int8send(
|
||||
(extract(epoch from now()) * 1000)::bigint
|
||||
);
|
||||
id := id || timestamp_ms_bytes;
|
||||
|
||||
-- 4. Add random bytes for uniqueness (6 bytes)
|
||||
random_bytes := gen_random_bytes(6);
|
||||
id := id || random_bytes;
|
||||
|
||||
RETURN id;
|
||||
END;
|
||||
$func$ LANGUAGE plpgsql VOLATILE;
|
||||
|
||||
-- Note: removed the single-parameter overload - tenant_id must be explicitly provided
|
||||
|
||||
-- Function to convert GID to string
|
||||
CREATE OR REPLACE FUNCTION gid_to_string(gid bytea)
|
||||
RETURNS text AS $func$
|
||||
BEGIN
|
||||
-- Make sure to handle padding properly - remove trailing '=' characters
|
||||
RETURN rtrim(translate(encode(gid, 'base64'), '+/', '-_'), '=');
|
||||
END;
|
||||
$func$ LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
-- Function to parse GID from string
|
||||
CREATE OR REPLACE FUNCTION parse_gid(encoded text)
|
||||
RETURNS bytea AS $func$
|
||||
DECLARE
|
||||
decoded bytea;
|
||||
padded_input text;
|
||||
padding_needed int;
|
||||
BEGIN
|
||||
-- Add proper padding for base64 decoding
|
||||
padding_needed := (4 - (length(encoded) % 4)) % 4;
|
||||
padded_input := encoded || repeat('=', padding_needed);
|
||||
|
||||
-- Replace URL-safe chars and decode
|
||||
BEGIN
|
||||
decoded := decode(translate(padded_input, '-_', '+/'), 'base64');
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
RAISE EXCEPTION 'Invalid base64 encoding in GID';
|
||||
END;
|
||||
|
||||
-- Validate length
|
||||
IF octet_length(decoded) != 24 THEN
|
||||
RAISE EXCEPTION 'Invalid GID length: got %, want 24', octet_length(decoded);
|
||||
END IF;
|
||||
|
||||
RETURN decoded;
|
||||
END;
|
||||
$func$ LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
-- Extract tenant ID from GID
|
||||
CREATE OR REPLACE FUNCTION extract_tenant_id(gid bytea)
|
||||
RETURNS bytea AS $func$
|
||||
BEGIN
|
||||
RETURN substring(gid from 1 for 8);
|
||||
END;
|
||||
$func$ LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
-- Extract entity type from GID
|
||||
CREATE OR REPLACE FUNCTION extract_entity_type(gid bytea)
|
||||
RETURNS int AS $func$
|
||||
BEGIN
|
||||
RETURN get_byte(gid, 8) * 256 + get_byte(gid, 9);
|
||||
END;
|
||||
$func$ LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
-- Extract timestamp from GID
|
||||
CREATE OR REPLACE FUNCTION extract_timestamp(gid bytea)
|
||||
RETURNS timestamp AS $func$
|
||||
DECLARE
|
||||
ms bigint;
|
||||
BEGIN
|
||||
ms := (get_byte(gid, 10)::bigint << 56) |
|
||||
(get_byte(gid, 11)::bigint << 48) |
|
||||
(get_byte(gid, 12)::bigint << 40) |
|
||||
(get_byte(gid, 13)::bigint << 32) |
|
||||
(get_byte(gid, 14)::bigint << 24) |
|
||||
(get_byte(gid, 15)::bigint << 16) |
|
||||
(get_byte(gid, 16)::bigint << 8) |
|
||||
get_byte(gid, 17)::bigint;
|
||||
|
||||
RETURN to_timestamp(ms / 1000.0);
|
||||
END;
|
||||
$func$ LANGUAGE plpgsql IMMUTABLE;
|
||||
|
||||
-- Example usage:
|
||||
-- Generate a new GID for entity type 42 with a specific tenant ID
|
||||
-- SELECT gid_to_string(generate_gid(generate_tenant_id(), 42));
|
||||
|
||||
-- Parse a GID from string
|
||||
-- SELECT parse_gid('your-base64-encoded-gid-here');
|
||||
|
||||
-- Extract components
|
||||
-- SELECT
|
||||
-- gid_to_string(gid) as gid_string,
|
||||
-- tenant_id_to_string(extract_tenant_id(gid)) as tenant_id,
|
||||
-- extract_entity_type(gid) as entity_type,
|
||||
-- extract_timestamp(gid) as created_at
|
||||
-- FROM (SELECT generate_gid(generate_tenant_id(), 42) as gid) t;
|
||||
|
||||
CREATE OR REPLACE FUNCTION decode_base64_unpadded(input_text text)
|
||||
RETURNS bytea AS $$
|
||||
DECLARE
|
||||
padded_text text;
|
||||
mod_length integer;
|
||||
BEGIN
|
||||
-- Calculate how many padding characters we need to add
|
||||
mod_length := length(input_text) % 4;
|
||||
|
||||
-- Add the required padding
|
||||
IF mod_length = 0 THEN
|
||||
padded_text := input_text;
|
||||
ELSIF mod_length = 1 THEN
|
||||
-- Invalid base64 - length mod 4 can't be 1
|
||||
RAISE EXCEPTION 'Invalid base64 length';
|
||||
ELSIF mod_length = 2 THEN
|
||||
padded_text := input_text || '==';
|
||||
ELSIF mod_length = 3 THEN
|
||||
padded_text := input_text || '=';
|
||||
END IF;
|
||||
|
||||
-- Decode the padded base64
|
||||
RETURN decode(padded_text, 'base64');
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
||||
|
||||
CREATE TYPE data_sensitivity AS ENUM ('NONE', 'LOW', 'MEDIUM', 'HIGH', 'CRITICAL');
|
||||
CREATE TYPE business_impact AS ENUM ('LOW', 'MEDIUM', 'HIGH', 'CRITICAL');
|
||||
|
||||
CREATE TABLE risk_assessments (
|
||||
tenant_id TEXT NOT NULL,
|
||||
id TEXT PRIMARY KEY,
|
||||
vendor_id TEXT NOT NULL REFERENCES vendors(id) ON DELETE CASCADE,
|
||||
accessed_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
accessed_by TEXT NOT NULL REFERENCES peoples(id) ON DELETE SET NULL,
|
||||
approved_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
approved_by TEXT NOT NULL REFERENCES peoples(id) ON DELETE SET NULL,
|
||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
data_sensitivity data_sensitivity NOT NULL,
|
||||
business_impact business_impact NOT NULL,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE peoples ADD COLUMN user_id TEXT NOT NULL REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
UPDATE peoples p
|
||||
SET user_id = u.id
|
||||
FROM users u
|
||||
WHERE u.email_address = p.primary_email_address;
|
||||
|
||||
INSERT INTO peoples (
|
||||
tenant_id,
|
||||
id,
|
||||
organization_id,
|
||||
full_name,
|
||||
primary_email_address,
|
||||
kind,
|
||||
user_id,
|
||||
additional_email_addresses,
|
||||
created_at,
|
||||
updated_at,
|
||||
version
|
||||
)
|
||||
SELECT
|
||||
o.tenant_id,
|
||||
encode(generate_gid(decode_base64_unpadded(o.tenant_id), 8), 'base64'),
|
||||
o.id,
|
||||
u.fullname,
|
||||
u.email_address,
|
||||
'CONTRACTOR',
|
||||
u.id,
|
||||
ARRAY[]::TEXT[],
|
||||
NOW(),
|
||||
NOW(),
|
||||
1
|
||||
FROM users u
|
||||
JOIN users_organizations uo ON u.id = uo.user_id
|
||||
JOIN organizations o ON uo.organization_id = o.id
|
||||
LEFT JOIN peoples p ON u.email_address = p.primary_email_address
|
||||
WHERE p.id IS NULL;
|
||||
|
||||
3
pkg/coredata/migrations/20250420T120001Z.sql
Normal file
3
pkg/coredata/migrations/20250420T120001Z.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE vendors
|
||||
DROP COLUMN service_criticality,
|
||||
DROP COLUMN risk_tier;
|
||||
1
pkg/coredata/migrations/20250421T032000Z.sql
Normal file
1
pkg/coredata/migrations/20250421T032000Z.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE peoples ALTER COLUMN user_id DROP NOT NULL;
|
||||
Reference in New Issue
Block a user