Egor Chernobai 1 lună în urmă
părinte
comite
d026c579e6

BIN
Untitled.png


+ 89 - 0
data-1779533596459.csv

@@ -0,0 +1,89 @@
+Òàáëèöà;Òèï;Ïîëå;Ïîÿñíåíèå;;;;
+active_agents_view;uuid;id;Óíèêàëüíûé èäåíòèôèêàòîð àãåíòà;;;;
+active_agents_view;text;hostname;Èìÿ èëè ñåòåâîå èìÿ àãåíòà;;;;
+active_agents_view;agent_status;status;Òåêóùèé ñòàòóñ àãåíòà;;;;
+active_agents_view;timestamptz;last_seen;Âðåìÿ ïîñëåäíåé àêòèâíîñòè àãåíòà;;;;
+active_agents_view;timestamptz;registered_at;Äàòà ðåãèñòðàöèè àãåíòà;;;;
+active_agents_view;int8;total_commands;Îáùåå êîëè÷åñòâî êîìàíä àãåíòà;;;;
+active_agents_view;int8;completed_commands;Êîëè÷åñòâî óñïåøíî çàâåðø¸ííûõ êîìàíä;;;;
+active_agents_view;int8;pending_commands;Êîëè÷åñòâî îæèäàþùèõ êîìàíä;;;;
+agent_group_members;uuid;agent_id;Èäåíòèôèêàòîð àãåíòà;;;;
+agent_group_members;uuid;group_id;Èäåíòèôèêàòîð ãðóïïû àãåíòîâ;;;;
+agent_groups;uuid;id;Óíèêàëüíûé èäåíòèôèêàòîð ãðóïïû;;;;
+agent_groups;text;name;Íàçâàíèå ãðóïïû àãåíòîâ;;;;
+agent_groups;text;description;Îïèñàíèå ãðóïïû;;;;
+agent_groups;timestamptz;created_at;Äàòà ñîçäàíèÿ ãðóïïû;;;;
+agent_heartbeats;int8;id;Óíèêàëüíûé èäåíòèôèêàòîð heartbeat-çàïèñè;;;;
+agent_heartbeats;uuid;agent_id;Èäåíòèôèêàòîð àãåíòà;;;;
+agent_heartbeats;timestamptz;received_at;Âðåìÿ ïîëó÷åíèÿ heartbeat;;;;
+agent_heartbeats;jsonb;metadata;Äîïîëíèòåëüíûå äàííûå î ñîñòîÿíèè àãåíòà;;;;
+agents;uuid;id;Óíèêàëüíûé èäåíòèôèêàòîð àãåíòà;;;;
+agents;text;hostname;Èìÿ óñòðîéñòâà èëè õîñòà àãåíòà;;;;
+agents;text;token_hash;Õýø òîêåíà àóòåíòèôèêàöèè àãåíòà;;;;
+agents;text;cert_fingerprint;Îòïå÷àòîê ñåðòèôèêàòà àãåíòà;;;;
+agents;jsonb;os_info;Èíôîðìàöèÿ îá îïåðàöèîííîé ñèñòåìå;;;;
+agents;agent_status;status;Òåêóùåå ñîñòîÿíèå àãåíòà;;;;
+agents;bool;is_active;Ïðèçíàê àêòèâíîñòè àãåíòà;;;;
+agents;timestamptz;last_seen;Âðåìÿ ïîñëåäíåãî ïîäêëþ÷åíèÿ;;;;
+agents;timestamptz;registered_at;Äàòà ðåãèñòðàöèè àãåíòà;;;;
+agents;timestamptz;updated_at;Äàòà ïîñëåäíåãî îáíîâëåíèÿ çàïèñè;;;;
+audit_logs;uuid;id;Óíèêàëüíûé èäåíòèôèêàòîð çàïèñè àóäèòà;;;;
+audit_logs;uuid;actor_id;Èäåíòèôèêàòîð ïîëüçîâàòåëÿ èëè àãåíòà;;;;
+audit_logs;actor_type_enum;actor_type;Òèï ñóáúåêòà äåéñòâèÿ;;;;
+audit_logs;text;action;Âûïîëíåííîå äåéñòâèå;;;;
+audit_logs;text;target;Îáúåêò äåéñòâèÿ;;;;
+audit_logs;jsonb;details;Äîïîëíèòåëüíûå ñâåäåíèÿ î äåéñòâèè;;;;
+audit_logs;inet;ip_address;IP-àäðåñ èñòî÷íèêà äåéñòâèÿ;;;;
+audit_logs;timestamptz;created_at;Âðåìÿ ñîçäàíèÿ çàïèñè àóäèòà;;;;
+audit_logs_view;uuid;id;Óíèêàëüíûé èäåíòèôèêàòîð çàïèñè àóäèòà;;;;
+audit_logs_view;text;action;Âûïîëíåííîå äåéñòâèå;;;;
+audit_logs_view;text;target;Îáúåêò äåéñòâèÿ;;;;
+audit_logs_view;timestamptz;created_at;Âðåìÿ âûïîëíåíèÿ äåéñòâèÿ;;;;
+audit_logs_view;inet;ip_address;IP-àäðåñ èñòî÷íèêà;;;;
+audit_logs_view;text;actor_username;Èìÿ ïîëüçîâàòåëÿ, âûïîëíèâøåãî äåéñòâèå;;;;
+audit_logs_view;text;agent_hostname;Èìÿ àãåíòà, ñâÿçàííîãî ñ äåéñòâèåì;;;;
+command_types;int2;id;Óíèêàëüíûé èäåíòèôèêàòîð òèïà êîìàíäû;;;;
+command_types;text;name;Íàçâàíèå òèïà êîìàíäû;;;;
+commands;uuid;id;Óíèêàëüíûé èäåíòèôèêàòîð êîìàíäû;;;;
+commands;uuid;agent_id;Èäåíòèôèêàòîð àãåíòà;;;;
+commands;uuid;issued_by;Èäåíòèôèêàòîð ïîëüçîâàòåëÿ, îòïðàâèâøåãî êîìàíäó;;;;
+commands;int2;command_type_id;Èäåíòèôèêàòîð òèïà êîìàíäû;;;;
+commands;text;command;Òåêñò âûïîëíÿåìîé êîìàíäû;;;;
+commands;command_status;status;Ñòàòóñ âûïîëíåíèÿ êîìàíäû;;;;
+commands;text;output;Ðåçóëüòàò âûïîëíåíèÿ êîìàíäû;;;;
+commands;int4;exit_code;Êîä çàâåðøåíèÿ êîìàíäû;;;;
+commands;timestamptz;created_at;Âðåìÿ ñîçäàíèÿ êîìàíäû;;;;
+commands;timestamptz;completed_at;Âðåìÿ çàâåðøåíèÿ âûïîëíåíèÿ;;;;
+commands;timestamptz;updated_at;Âðåìÿ ïîñëåäíåãî îáíîâëåíèÿ êîìàíäû;;;;
+file_transfers;uuid;id;Óíèêàëüíûé èäåíòèôèêàòîð ïåðåäà÷è ôàéëà;;;;
+file_transfers;uuid;agent_id;Èäåíòèôèêàòîð àãåíòà;;;;
+file_transfers;uuid;initiated_by;Èäåíòèôèêàòîð ïîëüçîâàòåëÿ, èíèöèèðîâàâøåãî ïåðåäà÷ó;;;;
+file_transfers;uuid;command_id;Èäåíòèôèêàòîð ñâÿçàííîé êîìàíäû;;;;
+file_transfers;transfer_direction;direction;Íàïðàâëåíèå ïåðåäà÷è ôàéëà;;;;
+file_transfers;text;filename;Èìÿ ïåðåäàâàåìîãî ôàéëà;;;;
+file_transfers;bpchar;sha256;SHA-256 õýø ôàéëà;;;;
+file_transfers;int8;size_bytes;Ðàçìåð ôàéëà â áàéòàõ;;;;
+file_transfers;command_status;status;Ñòàòóñ ïåðåäà÷è ôàéëà;;;;
+file_transfers;timestamptz;created_at;Âðåìÿ ñîçäàíèÿ çàïèñè ïåðåäà÷è;;;;
+file_transfers;timestamptz;updated_at;Âðåìÿ îáíîâëåíèÿ çàïèñè ïåðåäà÷è;;;;
+recent_commands_view;uuid;id;Óíèêàëüíûé èäåíòèôèêàòîð êîìàíäû;;;;
+recent_commands_view;text;command;Òåêñò êîìàíäû;;;;
+recent_commands_view;command_status;status;Ñòàòóñ âûïîëíåíèÿ êîìàíäû;;;;
+recent_commands_view;timestamptz;created_at;Âðåìÿ ñîçäàíèÿ êîìàíäû;;;;
+recent_commands_view;timestamptz;completed_at;Âðåìÿ çàâåðøåíèÿ êîìàíäû;;;;
+recent_commands_view;text;agent_hostname;Èìÿ àãåíòà;;;;
+recent_commands_view;text;issued_by;Èìÿ ïîëüçîâàòåëÿ, îòïðàâèâøåãî êîìàíäó;;;;
+user_sessions;uuid;id;Óíèêàëüíûé èäåíòèôèêàòîð ñåññèè;;;;
+user_sessions;uuid;user_id;Èäåíòèôèêàòîð ïîëüçîâàòåëÿ;;;;
+user_sessions;text;refresh_token_hash;Õýø refresh-òîêåíà;;;;
+user_sessions;text;user_agent;Èíôîðìàöèÿ î êëèåíòñêîì ïðèëîæåíèè;;;;
+user_sessions;inet;ip_address;IP-àäðåñ ïîëüçîâàòåëÿ;;;;
+user_sessions;timestamptz;expires_at;Âðåìÿ îêîí÷àíèÿ äåéñòâèÿ ñåññèè;;;;
+user_sessions;timestamptz;created_at;Âðåìÿ ñîçäàíèÿ ñåññèè;;;;
+users;uuid;id;Óíèêàëüíûé èäåíòèôèêàòîð ïîëüçîâàòåëÿ;;;;
+users;text;username;Èìÿ ïîëüçîâàòåëÿ;;;;
+users;text;password_hash;Õýø ïàðîëÿ ïîëüçîâàòåëÿ;;;;
+users;user_role;role;Ðîëü ïîëüçîâàòåëÿ â ñèñòåìå;;;;
+users;bool;is_active;Ïðèçíàê àêòèâíîñòè ïîëüçîâàòåëÿ;;;;
+users;timestamptz;created_at;Äàòà ñîçäàíèÿ ïîëüçîâàòåëÿ;;;;
+users;timestamptz;updated_at;Äàòà ïîñëåäíåãî îáíîâëåíèÿ ïîëüçîâàòåëÿ;;;;

+ 437 - 0
insert.sql

@@ -0,0 +1,437 @@
+-- =========================================================
+-- TEST DATA FOR AGENT MANAGEMENT SYSTEM
+-- PostgreSQL
+-- =========================================================
+
+-- =========================================================
+-- 100 USERS
+-- =========================================================
+
+INSERT INTO users (
+    username,
+    password_hash,
+    role
+)
+SELECT
+    'user_' || gs,
+
+    md5(random()::text),
+
+    (
+        ARRAY[
+            'admin',
+            'operator',
+            'viewer'
+        ]
+    )[floor(random() * 3 + 1)::int]::user_role
+
+FROM generate_series(1, 100) gs;
+
+-- =========================================================
+-- 50 AGENTS
+-- =========================================================
+
+INSERT INTO agents (
+    hostname,
+    token_hash,
+    cert_fingerprint,
+    os_info,
+    status,
+    last_seen
+)
+SELECT
+    'agent-' || gs,
+
+    md5(random()::text),
+
+    md5(random()::text),
+
+    jsonb_build_object(
+        'os',
+        (
+            ARRAY[
+                'Windows 11',
+                'Ubuntu 22.04',
+                'Debian 12',
+                'CentOS 9',
+                'Windows Server 2022'
+            ]
+        )[floor(random() * 5 + 1)::int],
+
+        'ram_gb',
+        floor(random() * 64 + 4),
+
+        'cpu_cores',
+        floor(random() * 16 + 1)
+    ),
+
+    (
+        ARRAY[
+            'online',
+            'offline',
+            'busy',
+            'error'
+        ]
+    )[floor(random() * 4 + 1)::int]::agent_status,
+
+    NOW() - (random() * interval '10 days')
+
+FROM generate_series(1, 50) gs;
+
+-- =========================================================
+-- 200 COMMANDS (OPERATIONS HISTORY)
+-- =========================================================
+
+INSERT INTO commands (
+    agent_id,
+    issued_by,
+    command_type_id,
+    command,
+    status,
+    output,
+    exit_code,
+    created_at,
+    completed_at
+)
+SELECT
+    random_agent_id,
+    random_user_id,
+    random_command_type_id,
+
+    (
+        ARRAY[
+            'whoami',
+            'hostname',
+            'ipconfig',
+            'ifconfig',
+            'ls -la',
+            'df -h',
+            'uptime',
+            'systemctl status nginx',
+            'docker ps',
+            'cat /etc/os-release'
+        ]
+    )[floor(random() * 10 + 1)::int],
+
+    random_status,
+
+    (
+        ARRAY[
+            'Command executed successfully',
+            'Permission denied',
+            'Host unreachable',
+            'Completed with warnings',
+            'Execution timeout',
+            'No output'
+        ]
+    )[floor(random() * 6 + 1)::int],
+
+    floor(random() * 10)::int,
+
+    created_time,
+
+    CASE
+        WHEN random_status IN ('completed', 'failed', 'cancelled')
+        THEN created_time + (random() * interval '5 hours')
+        ELSE NULL
+    END
+
+FROM (
+    SELECT
+        (
+            SELECT id
+            FROM agents
+            ORDER BY random()
+            LIMIT 1
+        ) AS random_agent_id,
+
+        (
+            SELECT id
+            FROM users
+            ORDER BY random()
+            LIMIT 1
+        ) AS random_user_id,
+
+        (
+            SELECT id
+            FROM command_types
+            ORDER BY random()
+            LIMIT 1
+        ) AS random_command_type_id,
+
+        (
+            ARRAY[
+                'pending',
+                'running',
+                'completed',
+                'failed',
+                'cancelled'
+            ]
+        )[floor(random() * 5 + 1)::int]::command_status AS random_status,
+
+        NOW() - (random() * interval '30 days') AS created_time
+
+    FROM generate_series(1, 200)
+) t;
+
+-- =========================================================
+-- 80 FILE TRANSFERS
+-- =========================================================
+
+INSERT INTO file_transfers (
+    agent_id,
+    initiated_by,
+    command_id,
+    direction,
+    filename,
+    sha256,
+    size_bytes,
+    status,
+    created_at
+)
+SELECT
+    (
+        SELECT id
+        FROM agents
+        ORDER BY random()
+        LIMIT 1
+    ),
+
+    (
+        SELECT id
+        FROM users
+        ORDER BY random()
+        LIMIT 1
+    ),
+
+    (
+        SELECT id
+        FROM commands
+        ORDER BY random()
+        LIMIT 1
+    ),
+
+    (
+        ARRAY[
+            'upload',
+            'download'
+        ]
+    )[floor(random() * 2 + 1)::int]::transfer_direction,
+
+    (
+        ARRAY[
+            'backup.zip',
+            'logs.tar.gz',
+            'report.pdf',
+            'config.json',
+            'database.dump',
+            'image.iso',
+            'script.sh'
+        ]
+    )[floor(random() * 7 + 1)::int],
+
+    md5(random()::text) || md5(random()::text),
+
+    floor(random() * 1000000000),
+
+    (
+        ARRAY[
+            'pending',
+            'running',
+            'completed',
+            'failed'
+        ]
+    )[floor(random() * 4 + 1)::int]::command_status,
+
+    NOW() - (random() * interval '30 days')
+
+FROM generate_series(1, 80);
+
+-- =========================================================
+-- 150 AUDIT LOGS
+-- =========================================================
+
+INSERT INTO audit_logs (
+    actor_id,
+    actor_type,
+    action,
+    target,
+    details,
+    ip_address,
+    created_at
+)
+SELECT
+    (
+        SELECT id
+        FROM users
+        ORDER BY random()
+        LIMIT 1
+    ),
+
+    'user'::actor_type_enum,
+
+    (
+        ARRAY[
+            'login',
+            'logout',
+            'command_created',
+            'agent_updated',
+            'file_uploaded',
+            'settings_changed',
+            'password_changed'
+        ]
+    )[floor(random() * 7 + 1)::int],
+
+    (
+        ARRAY[
+            'agent',
+            'command',
+            'system',
+            'user',
+            'file_transfer'
+        ]
+    )[floor(random() * 5 + 1)::int],
+
+    jsonb_build_object(
+        'result',
+        (
+            ARRAY[
+                'success',
+                'failed',
+                'warning'
+            ]
+        )[floor(random() * 3 + 1)::int],
+
+        'session_id',
+        md5(random()::text)
+    ),
+
+    (
+        floor(random() * 255)::int || '.' ||
+        floor(random() * 255)::int || '.' ||
+        floor(random() * 255)::int || '.' ||
+        floor(random() * 255)::int
+    )::inet,
+
+    NOW() - (random() * interval '30 days')
+
+FROM generate_series(1, 150);
+
+-- =========================================================
+-- 100 HEARTBEATS
+-- =========================================================
+
+INSERT INTO agent_heartbeats (
+    agent_id,
+    received_at,
+    metadata
+)
+SELECT
+    (
+        SELECT id
+        FROM agents
+        ORDER BY random()
+        LIMIT 1
+    ),
+
+    NOW() - (random() * interval '30 days'),
+
+    jsonb_build_object(
+        'cpu_usage',
+        floor(random() * 100),
+
+        'ram_usage',
+        floor(random() * 100),
+
+        'disk_usage',
+        floor(random() * 100)
+    )
+
+FROM generate_series(1, 100);
+
+-- =========================================================
+-- 50 USER SESSIONS
+-- =========================================================
+
+INSERT INTO user_sessions (
+    user_id,
+    refresh_token_hash,
+    user_agent,
+    ip_address,
+    expires_at,
+    created_at
+)
+SELECT
+    (
+        SELECT id
+        FROM users
+        ORDER BY random()
+        LIMIT 1
+    ),
+
+    md5(random()::text),
+
+    (
+        ARRAY[
+            'Chrome',
+            'Firefox',
+            'Edge',
+            'Safari',
+            'Postman'
+        ]
+    )[floor(random() * 5 + 1)::int],
+
+    (
+        floor(random() * 255)::int || '.' ||
+        floor(random() * 255)::int || '.' ||
+        floor(random() * 255)::int || '.' ||
+        floor(random() * 255)::int
+    )::inet,
+
+    NOW() + (random() * interval '30 days'),
+
+    NOW() - (random() * interval '30 days')
+
+FROM generate_series(1, 50);
+
+-- =========================================================
+-- AGENT GROUPS
+-- =========================================================
+
+INSERT INTO agent_groups (
+    name,
+    description
+)
+VALUES
+('Production', 'Production infrastructure'),
+('Testing', 'Testing infrastructure'),
+('Development', 'Development infrastructure'),
+('Monitoring', 'Monitoring servers'),
+('Backup', 'Backup infrastructure');
+
+-- =========================================================
+-- AGENT GROUP MEMBERS
+-- =========================================================
+
+INSERT INTO agent_group_members (
+    agent_id,
+    group_id
+)
+SELECT DISTINCT
+    (
+        SELECT id
+        FROM agents
+        ORDER BY random()
+        LIMIT 1
+    ),
+
+    (
+        SELECT id
+        FROM agent_groups
+        ORDER BY random()
+        LIMIT 1
+    )
+
+FROM generate_series(1, 100)
+
+ON CONFLICT DO NOTHING;

BIN
lab11.docx


+ 612 - 0
main.sql

@@ -0,0 +1,612 @@
+CREATE EXTENSION IF NOT EXISTS pgcrypto;
+
+CREATE TYPE user_role AS ENUM (
+    'admin',
+    'operator',
+    'viewer'
+);
+
+CREATE TYPE agent_status AS ENUM (
+    'online',
+    'offline',
+    'busy',
+    'error'
+);
+
+CREATE TYPE command_status AS ENUM (
+    'pending',
+    'running',
+    'completed',
+    'failed',
+    'cancelled'
+);
+
+CREATE TYPE transfer_direction AS ENUM (
+    'upload',
+    'download'
+);
+
+CREATE TYPE actor_type_enum AS ENUM (
+    'user',
+    'agent',
+    'system'
+);
+
+-- USERS
+
+CREATE TABLE users (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+
+    username TEXT NOT NULL UNIQUE,
+    password_hash TEXT NOT NULL,
+
+    role user_role NOT NULL DEFAULT 'viewer',
+
+    is_active BOOLEAN NOT NULL DEFAULT TRUE,
+
+    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_users_role
+ON users(role);
+
+CREATE INDEX idx_users_active
+ON users(is_active);
+
+-- AGENTS
+
+CREATE TABLE agents (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+
+    hostname TEXT NOT NULL UNIQUE,
+
+    token_hash TEXT NOT NULL,
+
+    cert_fingerprint TEXT,
+
+    os_info JSONB,
+
+    status agent_status NOT NULL DEFAULT 'offline',
+
+    is_active BOOLEAN NOT NULL DEFAULT TRUE,
+
+    last_seen TIMESTAMPTZ,
+
+    registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+
+    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_agents_status
+ON agents(status);
+
+CREATE INDEX idx_agents_last_seen
+ON agents(last_seen);
+
+CREATE INDEX idx_agents_active
+ON agents(is_active);
+
+CREATE INDEX idx_agents_os_info_gin
+ON agents
+USING GIN(os_info);
+
+-- COMMAND TYPES
+
+CREATE TABLE command_types (
+    id SMALLSERIAL PRIMARY KEY,
+    name TEXT NOT NULL UNIQUE
+);
+
+INSERT INTO command_types(name)
+VALUES
+('shell'),
+('powershell'),
+('upload'),
+('download'),
+('system'),
+('custom');
+
+-- COMMANDS
+
+CREATE TABLE commands (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+
+    agent_id UUID NOT NULL
+        REFERENCES agents(id)
+        ON DELETE CASCADE,
+
+    issued_by UUID
+        REFERENCES users(id)
+        ON DELETE SET NULL,
+
+    command_type_id SMALLINT
+        REFERENCES command_types(id),
+
+    command TEXT NOT NULL,
+
+    status command_status NOT NULL DEFAULT 'pending',
+
+    output TEXT,
+
+    exit_code INTEGER,
+
+    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+
+    completed_at TIMESTAMPTZ,
+
+    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+
+    CHECK (
+        completed_at IS NULL
+        OR completed_at >= created_at
+    ),
+
+    CHECK (
+        exit_code IS NULL
+        OR exit_code BETWEEN -255 AND 255
+    )
+);
+
+CREATE INDEX idx_commands_agent
+ON commands(agent_id);
+
+CREATE INDEX idx_commands_issued_by
+ON commands(issued_by);
+
+CREATE INDEX idx_commands_status
+ON commands(status);
+
+CREATE INDEX idx_commands_created_at
+ON commands(created_at DESC);
+
+CREATE INDEX idx_commands_pending
+ON commands(status)
+WHERE status = 'pending';
+
+CREATE INDEX idx_commands_running
+ON commands(status)
+WHERE status = 'running';
+
+-- FILE TRANSFERS
+
+CREATE TABLE file_transfers (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+
+    agent_id UUID
+        REFERENCES agents(id)
+        ON DELETE CASCADE,
+
+    initiated_by UUID
+        REFERENCES users(id)
+        ON DELETE SET NULL,
+
+    command_id UUID
+        REFERENCES commands(id)
+        ON DELETE SET NULL,
+
+    direction transfer_direction NOT NULL,
+
+    filename TEXT NOT NULL,
+
+    sha256 CHAR(64),
+
+    size_bytes BIGINT,
+
+    status command_status NOT NULL DEFAULT 'pending',
+
+    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+
+    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+
+    CHECK (
+        size_bytes IS NULL
+        OR size_bytes >= 0
+    )
+);
+
+CREATE INDEX idx_file_transfers_agent
+ON file_transfers(agent_id);
+
+CREATE INDEX idx_file_transfers_status
+ON file_transfers(status);
+
+CREATE INDEX idx_file_transfers_sha256
+ON file_transfers(sha256);
+
+CREATE INDEX idx_file_transfers_created
+ON file_transfers(created_at DESC);
+
+-- AUDIT LOGS
+
+CREATE TABLE audit_logs (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+
+    actor_id UUID,
+
+    actor_type actor_type_enum NOT NULL,
+
+    action TEXT NOT NULL,
+
+    target TEXT,
+
+    details JSONB,
+
+    ip_address INET,
+
+    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_audit_actor
+ON audit_logs(actor_id);
+
+CREATE INDEX idx_audit_action
+ON audit_logs(action);
+
+CREATE INDEX idx_audit_created
+ON audit_logs(created_at DESC);
+
+CREATE INDEX idx_audit_details_gin
+ON audit_logs
+USING GIN(details);
+
+-- AGENT HEARTBEATS
+
+CREATE TABLE agent_heartbeats (
+    id BIGSERIAL PRIMARY KEY,
+
+    agent_id UUID NOT NULL
+        REFERENCES agents(id)
+        ON DELETE CASCADE,
+
+    received_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+
+    metadata JSONB
+);
+
+CREATE INDEX idx_heartbeats_agent
+ON agent_heartbeats(agent_id);
+
+CREATE INDEX idx_heartbeats_received
+ON agent_heartbeats(received_at DESC);
+
+-- USER SESSIONS
+
+CREATE TABLE user_sessions (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+
+    user_id UUID NOT NULL
+        REFERENCES users(id)
+        ON DELETE CASCADE,
+
+    refresh_token_hash TEXT NOT NULL,
+
+    user_agent TEXT,
+
+    ip_address INET,
+
+    expires_at TIMESTAMPTZ NOT NULL,
+
+    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_sessions_user
+ON user_sessions(user_id);
+
+CREATE INDEX idx_sessions_expires
+ON user_sessions(expires_at);
+
+-- AGENT GROUPS
+
+CREATE TABLE agent_groups (
+    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+
+    name TEXT NOT NULL UNIQUE,
+
+    description TEXT,
+
+    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE TABLE agent_group_members (
+    agent_id UUID NOT NULL
+        REFERENCES agents(id)
+        ON DELETE CASCADE,
+
+    group_id UUID NOT NULL
+        REFERENCES agent_groups(id)
+        ON DELETE CASCADE,
+
+    PRIMARY KEY (agent_id, group_id)
+);
+
+-- UPDATE updated_at FUNCTION
+
+CREATE OR REPLACE FUNCTION update_updated_at_column()
+RETURNS TRIGGER AS
+$$
+BEGIN
+    NEW.updated_at = NOW();
+    RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+-- updated_at TRIGGERS
+
+CREATE TRIGGER trg_users_updated_at
+BEFORE UPDATE ON users
+FOR EACH ROW
+EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER trg_agents_updated_at
+BEFORE UPDATE ON agents
+FOR EACH ROW
+EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER trg_commands_updated_at
+BEFORE UPDATE ON commands
+FOR EACH ROW
+EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER trg_file_transfers_updated_at
+BEFORE UPDATE ON file_transfers
+FOR EACH ROW
+EXECUTE FUNCTION update_updated_at_column();
+
+-- COMMAND COMPLETION TRIGGER
+
+CREATE OR REPLACE FUNCTION update_agent_after_command()
+RETURNS TRIGGER AS
+$$
+BEGIN
+    IF NEW.status = 'completed'
+       AND OLD.status IS DISTINCT FROM 'completed'
+    THEN
+        UPDATE agents
+        SET
+            last_seen = NOW(),
+            status = 'online'
+        WHERE id = NEW.agent_id;
+    END IF;
+
+    RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER trg_update_agent_after_command
+AFTER UPDATE ON commands
+FOR EACH ROW
+EXECUTE FUNCTION update_agent_after_command();
+
+-- AUDIT LOG COMMAND CREATION
+
+CREATE OR REPLACE FUNCTION log_command_creation()
+RETURNS TRIGGER AS
+$$
+BEGIN
+    INSERT INTO audit_logs (
+        actor_id,
+        actor_type,
+        action,
+        target,
+        details
+    )
+    VALUES (
+        NEW.issued_by,
+        'user',
+        'command_created',
+        NEW.id::TEXT,
+        jsonb_build_object(
+            'command_length', length(NEW.command),
+            'status', NEW.status
+        )
+    );
+
+    RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER trg_log_command_creation
+AFTER INSERT ON commands
+FOR EACH ROW
+EXECUTE FUNCTION log_command_creation();
+
+-- AGENT STATUS CHANGE LOGGING
+
+CREATE OR REPLACE FUNCTION log_agent_status_change()
+RETURNS TRIGGER AS
+$$
+BEGIN
+    IF OLD.status IS DISTINCT FROM NEW.status THEN
+
+        INSERT INTO audit_logs (
+            actor_id,
+            actor_type,
+            action,
+            target,
+            details
+        )
+        VALUES (
+            NEW.id,
+            'agent',
+            'status_changed',
+            NEW.hostname,
+            jsonb_build_object(
+                'old_status', OLD.status,
+                'new_status', NEW.status
+            )
+        );
+
+    END IF;
+
+    RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER trg_log_agent_status_change
+AFTER UPDATE ON agents
+FOR EACH ROW
+EXECUTE FUNCTION log_agent_status_change();
+
+
+-- IMMUTABLE AUDIT LOGS
+
+CREATE OR REPLACE FUNCTION prevent_audit_log_modification()
+RETURNS TRIGGER AS
+$$
+BEGIN
+    RAISE EXCEPTION 'audit_logs table is immutable';
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER trg_prevent_audit_update
+BEFORE UPDATE ON audit_logs
+FOR EACH ROW
+EXECUTE FUNCTION prevent_audit_log_modification();
+
+CREATE TRIGGER trg_prevent_audit_delete
+BEFORE DELETE ON audit_logs
+FOR EACH ROW
+EXECUTE FUNCTION prevent_audit_log_modification();
+
+-- VIEWS
+
+CREATE VIEW active_agents_view AS
+SELECT
+    a.id,
+    a.hostname,
+    a.status,
+    a.last_seen,
+    a.registered_at,
+
+    COUNT(c.id) AS total_commands,
+
+    COUNT(*) FILTER (
+        WHERE c.status = 'completed'
+    ) AS completed_commands,
+
+    COUNT(*) FILTER (
+        WHERE c.status = 'pending'
+    ) AS pending_commands
+
+FROM agents a
+LEFT JOIN commands c
+ON a.id = c.agent_id
+
+WHERE a.is_active = TRUE
+
+GROUP BY
+    a.id,
+    a.hostname,
+    a.status,
+    a.last_seen,
+    a.registered_at;
+
+
+-- RECENT COMMANDS VIEW
+
+CREATE VIEW recent_commands_view AS
+SELECT
+    c.id,
+    c.command,
+    c.status,
+    c.created_at,
+    c.completed_at,
+
+    a.hostname AS agent_hostname,
+
+    u.username AS issued_by
+
+FROM commands c
+
+JOIN agents a
+ON c.agent_id = a.id
+
+LEFT JOIN users u
+ON c.issued_by = u.id;
+
+-- AUDIT LOGS VIEW
+
+CREATE VIEW audit_logs_view AS
+SELECT
+    al.id,
+    al.action,
+    al.target,
+    al.created_at,
+    al.ip_address,
+
+    u.username AS actor_username,
+
+    a.hostname AS agent_hostname
+
+FROM audit_logs al
+
+LEFT JOIN users u
+ON al.actor_id = u.id
+AND al.actor_type = 'user'
+
+LEFT JOIN agents a
+ON al.actor_id = a.id
+AND al.actor_type = 'agent';
+
+-- CLEANUP
+
+CREATE OR REPLACE PROCEDURE cleanup_old_commands(
+    days_to_keep INTEGER
+)
+LANGUAGE plpgsql
+AS
+$$
+DECLARE
+    deleted_rows INTEGER;
+BEGIN
+
+    DELETE FROM commands
+    WHERE status = 'completed'
+    AND completed_at < NOW() - (days_to_keep || ' days')::INTERVAL;
+
+    GET DIAGNOSTICS deleted_rows = ROW_COUNT;
+
+    INSERT INTO audit_logs (
+        actor_type,
+        action,
+        target,
+        details
+    )
+    VALUES (
+        'system',
+        'cleanup_completed_commands',
+        'commands',
+        jsonb_build_object(
+            'days_to_keep', days_to_keep,
+            'deleted_rows', deleted_rows
+        )
+    );
+
+END;
+$$;
+
+-- SYSTEM STATS 
+
+CREATE OR REPLACE FUNCTION get_system_stats()
+RETURNS TABLE (
+    total_users BIGINT,
+    total_agents BIGINT,
+    online_agents BIGINT,
+    pending_commands BIGINT
+)
+LANGUAGE plpgsql
+AS
+$$
+BEGIN
+
+    RETURN QUERY
+    SELECT
+        (SELECT COUNT(*) FROM users WHERE is_active = TRUE),
+        (SELECT COUNT(*) FROM agents),
+        (SELECT COUNT(*) FROM agents WHERE status = 'online'),
+        (SELECT COUNT(*) FROM commands WHERE status = 'pending');
+
+END;
+$$;
+

+ 124 - 0
murkup.txt

@@ -0,0 +1,124 @@
+users {
+  id uuid pk
+  username text unique
+  password_hash text
+  role user_role
+  is_active boolean
+  created_at timestamptz
+  updated_at timestamptz
+
+  @index idx_users_role (role)
+  @index idx_users_active (is_active)
+}
+
+agents {
+  id uuid pk
+  hostname text unique
+  token_hash text
+  cert_fingerprint text
+  os_info jsonb
+  status enum
+  is_active boolean
+  last_seen timestamptz
+  registered_at timestamptz
+  updated_at timestamptz
+
+  @index idx_agents_status (status)
+  @index idx_agents_last_seen (last_seen)
+  @index idx_agents_active (is_active)
+  @index idx_agents_os_info_gin (os_info)
+}
+
+command_types {
+  id smallserial pk
+  name text unique
+}
+
+commands {
+  id uuid pk
+  agent_id uuid > agents.id
+  issued_by uuid > users.id
+  command_type_id smallint > command_types.id
+  command text
+  status enum
+  output text
+  exit_code integer
+  created_at timestamptz
+  completed_at timestamptz
+  updated_at timestamptz
+
+  @index idx_commands_agent (agent_id)
+  @index idx_commands_issued_by (issued_by)
+  @index idx_commands_status (status)
+  @index idx_commands_created_at (created_at)
+}
+
+file_transfers {
+  id uuid pk
+  agent_id uuid > agents.id
+  initiated_by uuid > users.id
+  command_id uuid > commands.id
+  direction transfer_direction
+  filename text
+  sha256 char(64)
+  size_bytes bigint
+  status enum
+  created_at timestamptz
+  updated_at timestamptz
+
+  @index idx_file_transfers_agent (agent_id)
+  @index idx_file_transfers_status (status)
+  @index idx_file_transfers_sha256 (sha256)
+  @index idx_file_transfers_created (created_at)
+}
+
+audit_logs {
+  id uuid pk
+  actor_id uuid
+  actor_type enum
+  action text
+  target text
+  details jsonb
+  ip_address inet
+  created_at timestamptz
+
+  @index idx_audit_actor (actor_id)
+  @index idx_audit_action (action)
+  @index idx_audit_created (created_at)
+  @index idx_audit_details_gin (details)
+}
+
+agent_heartbeats {
+  id bigserial pk
+  agent_id uuid > agents.id
+  received_at timestamptz
+  metadata jsonb
+
+  @index idx_heartbeats_agent (agent_id)
+  @index idx_heartbeats_received (received_at)
+}
+
+user_sessions {
+  id uuid pk
+  user_id uuid > users.id
+  refresh_token_hash text
+  user_agent text
+  ip_address inet
+  expires_at timestamptz
+  created_at timestamptz
+
+  @index idx_sessions_user (user_id)
+  @index idx_sessions_expires (expires_at)
+}
+
+agent_groups {
+  id uuid pk
+  name text unique
+  description text
+  created_at timestamptz
+}
+
+agent_group_members {
+  agent_id uuid pk > agents.id
+  group_id uuid pk > agent_groups.id
+}

BIN
photo_2026-05-23_14-34-53 (2).jpg


BIN
ЛР11. Работа с PostgresPro.docx


BIN
ЛР11.docx


BIN
Пример спецификации для приложения базы данных.docx


BIN
Топология.png