second commit
This commit is contained in:
144
app/db/migrations/0001_initial.sql
Normal file
144
app/db/migrations/0001_initial.sql
Normal file
@ -0,0 +1,144 @@
|
||||
-- Initiales Schema. Siehe Konzeptdokument Kap. 5.
|
||||
-- Wird vom Migration-Runner (app/db.py) einmalig und idempotent angewendet.
|
||||
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
totp_secret_enc BLOB,
|
||||
totp_enrolled INTEGER NOT NULL DEFAULT 0,
|
||||
is_active INTEGER NOT NULL DEFAULT 1,
|
||||
is_admin INTEGER NOT NULL DEFAULT 0,
|
||||
failed_logins INTEGER NOT NULL DEFAULT 0,
|
||||
locked_until TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
||||
password_changed_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
||||
must_change_password INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS recovery_codes (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
code_hash TEXT NOT NULL,
|
||||
used_at TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_recovery_codes_user ON recovery_codes(user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS roles (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT UNIQUE NOT NULL
|
||||
);
|
||||
INSERT OR IGNORE INTO roles (id, name) VALUES
|
||||
(1, 'ssh_connect'),
|
||||
(2, 'rdp_connect'),
|
||||
(3, 'file_transfer'),
|
||||
(4, 'clipboard'),
|
||||
(5, 'session_recording_view'),
|
||||
(6, 'admin_hostgroup');
|
||||
|
||||
CREATE TABLE IF NOT EXISTS host_groups (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
description TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS hosts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
host_group_id INTEGER NOT NULL REFERENCES host_groups(id),
|
||||
hostname TEXT NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
protocol TEXT NOT NULL CHECK (protocol IN ('ssh','rdp')),
|
||||
port INTEGER NOT NULL,
|
||||
os_type TEXT NOT NULL CHECK (os_type IN ('linux','windows')),
|
||||
ssh_host_key_fingerprint TEXT,
|
||||
ssh_username TEXT,
|
||||
rdp_username TEXT,
|
||||
rdp_domain TEXT,
|
||||
rdp_require_nla INTEGER NOT NULL DEFAULT 1,
|
||||
clipboard_enabled INTEGER NOT NULL DEFAULT 1,
|
||||
file_transfer_enabled INTEGER NOT NULL DEFAULT 1,
|
||||
is_active INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_hosts_group ON hosts(host_group_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ssh_keys (
|
||||
id INTEGER PRIMARY KEY,
|
||||
label TEXT NOT NULL,
|
||||
owner_user_id INTEGER REFERENCES users(id),
|
||||
private_key_enc BLOB NOT NULL,
|
||||
public_key TEXT NOT NULL,
|
||||
key_type TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
||||
rotated_at TEXT,
|
||||
expires_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS host_ssh_key_map (
|
||||
host_id INTEGER NOT NULL REFERENCES hosts(id) ON DELETE CASCADE,
|
||||
ssh_key_id INTEGER NOT NULL REFERENCES ssh_keys(id),
|
||||
PRIMARY KEY (host_id, ssh_key_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_hostgroup_roles (
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
host_group_id INTEGER NOT NULL REFERENCES host_groups(id) ON DELETE CASCADE,
|
||||
role_id INTEGER NOT NULL REFERENCES roles(id),
|
||||
granted_by INTEGER REFERENCES users(id),
|
||||
granted_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
||||
expires_at TEXT,
|
||||
PRIMARY KEY (user_id, host_group_id, role_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_uhr_user ON user_hostgroup_roles(user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||
host_id INTEGER NOT NULL REFERENCES hosts(id),
|
||||
protocol TEXT NOT NULL,
|
||||
started_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
||||
ended_at TEXT,
|
||||
client_ip TEXT NOT NULL,
|
||||
recording_path TEXT,
|
||||
end_reason TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_host ON sessions(host_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS file_transfers (
|
||||
id INTEGER PRIMARY KEY,
|
||||
session_id INTEGER NOT NULL REFERENCES sessions(id),
|
||||
direction TEXT NOT NULL CHECK (direction IN ('upload','download')),
|
||||
filename TEXT NOT NULL,
|
||||
size_bytes INTEGER NOT NULL,
|
||||
sha256 TEXT NOT NULL,
|
||||
av_scan_result TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_ft_session ON file_transfers(session_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id INTEGER PRIMARY KEY,
|
||||
ts TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
||||
user_id INTEGER REFERENCES users(id),
|
||||
client_ip TEXT,
|
||||
event_type TEXT NOT NULL,
|
||||
details_json TEXT NOT NULL,
|
||||
prev_hash TEXT NOT NULL,
|
||||
entry_hash TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_ts ON audit_log(ts);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_user ON audit_log(user_id);
|
||||
|
||||
-- Audit-Log ist auf DB-Ebene append-only: UPDATE/DELETE werden hart verweigert.
|
||||
CREATE TRIGGER IF NOT EXISTS no_audit_update BEFORE UPDATE ON audit_log
|
||||
BEGIN SELECT RAISE(ABORT, 'audit_log ist append-only'); END;
|
||||
CREATE TRIGGER IF NOT EXISTS no_audit_delete BEFORE DELETE ON audit_log
|
||||
BEGIN SELECT RAISE(ABORT, 'audit_log ist append-only'); END;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
filename TEXT PRIMARY KEY,
|
||||
applied_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
||||
);
|
||||
Reference in New Issue
Block a user