second commit

This commit is contained in:
2026-08-19 22:33:19 +02:00
parent 411812e954
commit 199f306993
107 changed files with 5984 additions and 0 deletions

View 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'))
);