| 3 | | Забелешка: наменето за PostgreSQL '''project''' шемата (v04). Во v04: |
| 4 | | security_alerts.id е INTEGER IDENTITY (не се внесува рачно), computer_id е INTEGER, |
| 5 | | timestamp е TIMESTAMP, resolved е BOOLEAN. Затоа INSERT-ите не внесуваат id, |
| 6 | | користат NOW() (не текст) и false (не 'false'). |
| | 3 | Забелешка: наменето за PostgreSQL project шемата (v04). Во v04: security_alerts.id е INTEGER IDENTITY (не се внесува рачно), computer_id е INTEGER, timestamp е TIMESTAMP, resolved е BOOLEAN. Затоа INSERT-ите не внесуваат id, користат NOW() (не текст) и false (не 'false'). Редоследот на имплементацијата во секој концепт е: Triggers, Stored procedures/functions, Views, Custom domains (само оние што важат за концептот). |
| 25 | | INSERT INTO security_alerts( |
| 26 | | computer_id, alert_type, severity, |
| 27 | | description, timestamp, resolved |
| 28 | | ) |
| 29 | | VALUES ( |
| 30 | | NEW.computer_id, |
| 31 | | 'High CPU Usage', |
| 32 | | 'HIGH', |
| 33 | | 'CPU usage exceeded 90%', |
| 34 | | NOW(), |
| 35 | | false |
| 36 | | ); |
| | 21 | INSERT INTO security_alerts(computer_id, alert_type, severity, description, timestamp, resolved) |
| | 22 | VALUES (NEW.computer_id, 'High CPU Usage', 'HIGH', 'CPU usage exceeded 90%', NOW(), false); |
| 58 | | INSERT INTO security_alerts( |
| 59 | | computer_id, alert_type, severity, |
| 60 | | description, timestamp, resolved |
| 61 | | ) |
| 62 | | VALUES ( |
| 63 | | NEW.computer_id, |
| 64 | | 'High RAM Usage', |
| 65 | | 'HIGH', |
| 66 | | 'RAM usage exceeded 90%', |
| 67 | | NOW(), |
| 68 | | false |
| 69 | | ); |
| | 42 | INSERT INTO security_alerts(computer_id, alert_type, severity, description, timestamp, resolved) |
| | 43 | VALUES (NEW.computer_id, 'High RAM Usage', 'HIGH', 'RAM usage exceeded 90%', NOW(), false); |
| 90 | | INSERT INTO security_alerts( |
| 91 | | computer_id, alert_type, severity, description, timestamp, resolved |
| 92 | | ) |
| 93 | | VALUES ( |
| 94 | | NEW.computer_id, 'High Disk Usage', 'MEDIUM', |
| 95 | | 'Disk usage exceeded 90%', NOW(), false |
| 96 | | ); |
| | 63 | INSERT INTO security_alerts(computer_id, alert_type, severity, description, timestamp, resolved) |
| | 64 | VALUES (NEW.computer_id, 'High Disk Usage', 'MEDIUM', 'Disk usage exceeded 90%', NOW(), false); |
| 105 | | FOR EACH ROW |
| 106 | | EXECUTE FUNCTION create_disk_alert(); |
| 107 | | }}} |
| 108 | | |
| 109 | | Тригер (BEFORE UPDATE) кој автоматски го ажурира `updated_at` во `env_settings` |
| 110 | | при секоја промена на поставките: |
| 111 | | |
| 112 | | {{{ |
| 113 | | CREATE OR REPLACE FUNCTION set_env_settings_updated_at() |
| 114 | | RETURNS TRIGGER AS |
| 115 | | $$ |
| 116 | | BEGIN |
| 117 | | NEW.updated_at := NOW(); |
| 118 | | RETURN NEW; |
| 119 | | END; |
| 120 | | $$ LANGUAGE plpgsql; |
| 121 | | |
| 122 | | DROP TRIGGER IF EXISTS env_settings_updated_trigger ON env_settings; |
| 123 | | CREATE TRIGGER env_settings_updated_trigger |
| 124 | | BEFORE UPDATE ON env_settings |
| 125 | | FOR EACH ROW |
| 126 | | EXECUTE FUNCTION set_env_settings_updated_at(); |
| 127 | | }}} |
| 128 | | |
| 129 | | Тригер (напреден) кој при нова проверка на достапност со недостапен сервис |
| 130 | | (`service_availability.is_available = false`) автоматски генерира безбедносен |
| 131 | | аларм, поврзувајќи го сервисот со неговиот компјутер: |
| | 73 | FOR EACH ROW EXECUTE FUNCTION create_disk_alert(); |
| | 74 | }}} |
| | 75 | |
| | 76 | Тригер кој при нова проверка на достапност со недостапен сервис автоматски генерира аларм, поврзувајќи го сервисот со неговиот компјутер: |
| 142 | | SELECT computer_id, service_name |
| 143 | | INTO v_computer_id, v_service |
| 144 | | FROM network_services |
| 145 | | WHERE id = NEW.service_id; |
| 146 | | |
| 147 | | INSERT INTO security_alerts( |
| 148 | | computer_id, alert_type, severity, description, timestamp, resolved |
| 149 | | ) |
| 150 | | VALUES ( |
| 151 | | v_computer_id, 'Service Unavailable', 'MEDIUM', |
| 152 | | 'Network service ' || COALESCE(v_service, '?') || ' is unavailable', |
| 153 | | NOW(), false |
| 154 | | ); |
| | 87 | SELECT computer_id, service_name INTO v_computer_id, v_service |
| | 88 | FROM network_services WHERE id = NEW.service_id; |
| | 89 | INSERT INTO security_alerts(computer_id, alert_type, severity, description, timestamp, resolved) |
| | 90 | VALUES (v_computer_id, 'Service Unavailable', 'MEDIUM', |
| | 91 | 'Network service ' || COALESCE(v_service, '?') || ' is unavailable', NOW(), false); |
| 163 | | FOR EACH ROW |
| 164 | | EXECUTE FUNCTION create_service_down_alert(); |
| 165 | | }}} |
| 166 | | |
| 167 | | Тригер кој при нов перформансен запис го освежува `last_seen` на компјутерот |
| 168 | | (машината се смета за активна штом испраќа податоци): |
| 169 | | |
| 170 | | {{{ |
| 171 | | CREATE OR REPLACE FUNCTION touch_computer_last_seen() |
| 172 | | RETURNS TRIGGER AS |
| 173 | | $$ |
| 174 | | BEGIN |
| 175 | | UPDATE computers |
| 176 | | SET last_seen = COALESCE(NEW.timestamp, NOW()) |
| 177 | | WHERE id = NEW.computer_id; |
| 178 | | RETURN NEW; |
| 179 | | END; |
| 180 | | $$ LANGUAGE plpgsql; |
| 181 | | |
| 182 | | DROP TRIGGER IF EXISTS touch_last_seen_trigger ON computer_history; |
| 183 | | CREATE TRIGGER touch_last_seen_trigger |
| 184 | | AFTER INSERT ON computer_history |
| 185 | | FOR EACH ROW |
| 186 | | EXECUTE FUNCTION touch_computer_last_seen(); |
| 187 | | }}} |
| 188 | | |
| 189 | | ==== Stored Procedures/Functions ==== |
| 190 | | |
| 191 | | Процедура за детекција на сомнителна активност врз основа на Sysmon настани. |
| 192 | | Ако еден компјутер генерирал повеќе од 50 Sysmon настани во последниот час, автоматски се внесува безбедносно предупредување. Може да се извршува периодично (на пр. преку `pg_cron`): |
| | 100 | FOR EACH ROW EXECUTE FUNCTION create_service_down_alert(); |
| | 101 | }}} |
| | 102 | |
| | 103 | ==== Stored procedures/functions ==== |
| | 104 | |
| | 105 | Процедура за детекција на сомнителна активност: ако компјутер генерирал повеќе од 50 Sysmon настани во последниот час, се внесува безбедносно предупредување: |
| 199 | | INSERT INTO security_alerts( |
| 200 | | computer_id, alert_type, severity, |
| 201 | | description, timestamp, resolved |
| 202 | | ) |
| 203 | | SELECT |
| 204 | | computer_id, |
| 205 | | 'Suspicious Activity', |
| 206 | | 'HIGH', |
| 207 | | 'More than 50 Sysmon events in the last hour', |
| 208 | | NOW(), |
| 209 | | false |
| | 112 | INSERT INTO security_alerts(computer_id, alert_type, severity, description, timestamp, resolved) |
| | 113 | SELECT computer_id, 'Suspicious Activity', 'HIGH', |
| | 114 | 'More than 50 Sysmon events in the last hour', NOW(), false |
| 233 | | ---- |
| 234 | | |
| 235 | | == Автоматско одржување и чистење на историски податоци == |
| 236 | | |
| 237 | | Со текот на времето, историските табели (`sysmon_events`, `computer_history`, `computer_processes_history`, `network_connections_history`) акумулираат голем број записи кои ги успоруваат пребарувањата и го зголемуваат дисковниот простор. Потребен е механизам за автоматско бришење застарени записи. |
| 238 | | |
| 239 | | === Implementation === |
| 240 | | |
| 241 | | ==== Stored Procedures/Functions ==== |
| | 135 | ==== Custom domains ==== |
| | 136 | |
| | 137 | Домен за тежината на алармите (ограничен сет вредности) и домен за процентот на искористеност (0..100), кои се користат за severity во security_alerts и за метриките во computer_history: |
| | 138 | |
| | 139 | {{{ |
| | 140 | CREATE DOMAIN severity_level AS TEXT |
| | 141 | CHECK (lower(VALUE) IN ('low','medium','high','critical')); |
| | 142 | |
| | 143 | CREATE DOMAIN usage_percent AS DOUBLE PRECISION |
| | 144 | CHECK (VALUE >= 0 AND VALUE <= 100); |
| | 145 | }}} |
| | 146 | |
| | 147 | ---- |
| | 148 | |
| | 149 | == Автоматско одржување на конзистентност на податоците == |
| | 150 | |
| | 151 | Одредени изведени/контролни полиња треба автоматски да се одржуваат конзистентни без апликацискиот слој да мора експлицитно да ги ажурира: полето updated_at во env_settings мора да се освежи при секоја промена на поставките, а last_seen на компјутер мора да се освежи штом машината испрати нови податоци. |
| | 152 | |
| | 153 | '''Implementation:''' |
| | 154 | |
| | 155 | ==== Triggers ==== |
| | 156 | |
| | 157 | Тригер (BEFORE UPDATE) кој автоматски го ажурира updated_at во env_settings: |
| | 158 | |
| | 159 | {{{ |
| | 160 | CREATE OR REPLACE FUNCTION set_env_settings_updated_at() |
| | 161 | RETURNS TRIGGER AS |
| | 162 | $$ |
| | 163 | BEGIN |
| | 164 | NEW.updated_at := NOW(); |
| | 165 | RETURN NEW; |
| | 166 | END; |
| | 167 | $$ LANGUAGE plpgsql; |
| | 168 | |
| | 169 | DROP TRIGGER IF EXISTS env_settings_updated_trigger ON env_settings; |
| | 170 | CREATE TRIGGER env_settings_updated_trigger |
| | 171 | BEFORE UPDATE ON env_settings |
| | 172 | FOR EACH ROW EXECUTE FUNCTION set_env_settings_updated_at(); |
| | 173 | }}} |
| | 174 | |
| | 175 | Тригер кој при нов перформансен запис го освежува last_seen на компјутерот: |
| | 176 | |
| | 177 | {{{ |
| | 178 | CREATE OR REPLACE FUNCTION touch_computer_last_seen() |
| | 179 | RETURNS TRIGGER AS |
| | 180 | $$ |
| | 181 | BEGIN |
| | 182 | UPDATE computers SET last_seen = COALESCE(NEW.timestamp, NOW()) |
| | 183 | WHERE id = NEW.computer_id; |
| | 184 | RETURN NEW; |
| | 185 | END; |
| | 186 | $$ LANGUAGE plpgsql; |
| | 187 | |
| | 188 | DROP TRIGGER IF EXISTS touch_last_seen_trigger ON computer_history; |
| | 189 | CREATE TRIGGER touch_last_seen_trigger |
| | 190 | AFTER INSERT ON computer_history |
| | 191 | FOR EACH ROW EXECUTE FUNCTION touch_computer_last_seen(); |
| | 192 | }}} |
| | 193 | |
| | 194 | ---- |
| | 195 | |
| | 196 | == Автоматско чистење на историски податоци (background jobs) == |
| | 197 | |
| | 198 | Историските табели (sysmon_events, computer_history, computer_processes_history, network_connections_history) акумулираат голем број записи кои ги успоруваат пребарувањата и го зголемуваат дисковниот простор. Потребен е механизам за автоматско бришење застарени записи преку закажани позадински задачи. |
| | 199 | |
| | 200 | '''Implementation:''' |
| | 201 | |
| | 202 | ==== Stored procedures/functions ==== |
| 276 | | DELETE FROM computer_processes_history |
| 277 | | WHERE timestamp < NOW() - INTERVAL '90 days'; |
| 278 | | |
| 279 | | DELETE FROM network_connections_history |
| 280 | | WHERE timestamp < NOW() - INTERVAL '90 days'; |
| 281 | | END; |
| 282 | | $$; |
| 283 | | }}} |
| 284 | | |
| 285 | | Процедурите може да се закажат за периодично извршување преку `pg_cron`: |
| 286 | | |
| 287 | | {{{ |
| 288 | | -- Извршување секој ден во раните утрински часови |
| | 235 | DELETE FROM computer_processes_history WHERE timestamp < NOW() - INTERVAL '90 days'; |
| | 236 | DELETE FROM network_connections_history WHERE timestamp < NOW() - INTERVAL '90 days'; |
| | 237 | END; |
| | 238 | $$; |
| | 239 | }}} |
| | 240 | |
| | 241 | Background jobs: процедурите се закажуваат за периодично извршување преку pg_cron: |
| | 242 | |
| | 243 | {{{ |
| | 341 | |
| | 342 | ---- |
| | 343 | |
| | 344 | == Валидација на влезни податоци преку сопствени домени == |
| | 345 | |
| | 346 | Одреден дел од колоните бараат построги ограничувања на вредностите кои се повторуваат низ повеќе табели: портите мора да се во опсег 1..65535, мрежниот протокол е TCP/UDP, а е-поштата мора да има валиден формат. Наместо истите CHECK ограничувања да се повторуваат на секоја колона, тие се централизирани во сопствени домени и повторно се употребуваат насекаде во шемата. |
| | 347 | |
| | 348 | '''Implementation:''' |
| | 349 | |
| | 350 | ==== Custom domains ==== |
| | 351 | |
| | 352 | {{{ |
| | 353 | -- Порта: дозволен опсег 1..65535 |
| | 354 | CREATE DOMAIN port_number AS INTEGER |
| | 355 | CHECK (VALUE BETWEEN 1 AND 65535); |
| | 356 | |
| | 357 | -- Мрежен протокол |
| | 358 | CREATE DOMAIN protocol_type AS TEXT |
| | 359 | CHECK (upper(VALUE) IN ('TCP','UDP')); |
| | 360 | |
| | 361 | -- Е-пошта (основна проверка на формат) |
| | 362 | CREATE DOMAIN email_address AS TEXT |
| | 363 | CHECK (VALUE ~ '^[^@[:space:]]+@[^@[:space:]]+\.[^@[:space:]]+$'); |
| | 364 | }}} |
| | 365 | |
| | 366 | Примена на домените врз колоните во шемата: |
| | 367 | |
| | 368 | {{{ |
| | 369 | -- tenants.owner_email, users.email -> email_address |
| | 370 | -- network_services.port -> port_number |
| | 371 | -- network_services.protocol -> protocol_type |
| | 372 | }}} |
| | 373 | |
| | 374 | Пример на употреба при креирање табела (доменот се пишува наместо базниот тип): |
| | 375 | |
| | 376 | {{{ |
| | 377 | CREATE TABLE network_services ( |
| | 378 | id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, |
| | 379 | computer_id INTEGER NOT NULL, |
| | 380 | service_name TEXT, |
| | 381 | port port_number NOT NULL, |
| | 382 | protocol protocol_type NOT NULL, |
| | 383 | status TEXT, |
| | 384 | last_checked TIMESTAMP, |
| | 385 | CONSTRAINT fk_network_services_computer FOREIGN KEY (computer_id) REFERENCES computers(id) ON DELETE CASCADE |
| | 386 | ); |
| | 387 | }}} |
| | 388 | |
| | 389 | Примена на веќе постоечка колона (важи ако податоците го задоволуваат ограничувањето): |
| | 390 | |
| | 391 | {{{ |
| | 392 | ALTER TABLE network_services ALTER COLUMN port TYPE port_number; |
| | 393 | ALTER TABLE network_services ALTER COLUMN protocol TYPE protocol_type; |
| | 394 | }}} |