145 lines
5.7 KiB
SQL
145 lines
5.7 KiB
SQL
-- 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'))
|
|
);
|