umbau 1.0

This commit is contained in:
2026-09-02 20:30:44 +02:00
parent afe6719f51
commit 5c95b21be7
77 changed files with 10733 additions and 1914 deletions

View File

@ -96,12 +96,33 @@ def _plain_key_pem() -> str:
).decode()
# Teil D Schritt 4 (Achse B): connect_to_host()/load_private_key_for_host()
# loesen Zugangsdaten seither benutzerabhaengig auf -- _make_db() seedet
# daher immer einen Benutzer (TEST_USER_ID) samt Gruppe, und bei with_key=True
# zusaetzlich eine group_ssh_key_grants-Freigabe dieser Gruppe fuer den
# angelegten Schluessel (ohne die wuerde resolve_credential_for_user_on_host()
# keinen Treffer finden, siehe app/rbac.py).
TEST_USER_ID = 1
TEST_USER_GROUP_ID = 1
def _make_db(tmp_path, *, host_username=None, key_username="l4u", fingerprint=FINGERPRINT,
host_key=PUBLIC_KEY, with_key=True) -> str:
path = str(tmp_path / "jumphost.sqlite3")
db = sqlite3.connect(path)
_apply_migrations(db)
db.execute("INSERT INTO host_groups (id, name, tenant_id) VALUES (1, 'gruppe', 1)")
db.execute(
"INSERT INTO users (id, username, password_hash) VALUES (?, 'p12-user', 'x')",
(TEST_USER_ID,),
)
db.execute(
"INSERT INTO user_groups (id, name) VALUES (?, 'p12-team')", (TEST_USER_GROUP_ID,),
)
db.execute(
"INSERT INTO user_group_members (user_group_id, user_id) VALUES (?, ?)",
(TEST_USER_GROUP_ID, TEST_USER_ID),
)
db.execute("INSERT INTO host_groups (id, name) VALUES (1, 'gruppe')")
db.execute(
"INSERT INTO hosts (id, host_group_id, hostname, address, protocol, port, os_type, "
"ssh_host_key_fingerprint, ssh_host_key, ssh_username) "
@ -110,11 +131,15 @@ def _make_db(tmp_path, *, host_username=None, key_username="l4u", fingerprint=FI
)
if with_key:
db.execute(
"INSERT INTO ssh_keys (id, label, private_key_enc, public_key, key_type, tenant_id, username) "
"VALUES (1, 'testkey', ?, 'ssh-ed25519 AAAA', 'ed25519', 1, ?)",
"INSERT INTO ssh_keys (id, label, private_key_enc, public_key, key_type, username) "
"VALUES (1, 'testkey', ?, 'ssh-ed25519 AAAA', 'ed25519', ?)",
(encrypt_secret(_plain_key_pem().encode(), associated_data=b"ssh_private_key"), key_username),
)
db.execute("INSERT INTO host_ssh_key_map (host_id, ssh_key_id) VALUES (1, 1)")
db.execute(
"INSERT INTO group_ssh_key_grants (user_group_id, ssh_key_id) VALUES (?, 1)",
(TEST_USER_GROUP_ID,),
)
db.commit()
db.close()
return path
@ -206,7 +231,7 @@ async def test_verbindung_ohne_hinterlegten_hostkey_wird_abgelehnt(tmp_path, mon
monkeypatch.setattr(asyncssh, "connect", _fake_connect)
conn = FakeConnection(_make_db(tmp_path, fingerprint=None, host_key=None))
with pytest.raises(HostKeyNotPinnedError):
await connect_to_host(conn, 1)
await connect_to_host(conn, 1, user_id=TEST_USER_ID)
async def test_abweichender_hostkey_bricht_vor_der_anmeldung_ab(tmp_path, monkeypatch):
@ -227,7 +252,7 @@ async def test_abweichender_hostkey_bricht_vor_der_anmeldung_ab(tmp_path, monkey
conn = FakeConnection(_make_db(tmp_path))
with pytest.raises(HostKeyMismatchError) as excinfo:
await connect_to_host(conn, 1)
await connect_to_host(conn, 1, user_id=TEST_USER_ID)
assert excinfo.value.expected == FINGERPRINT
assert versuche == []
@ -249,7 +274,7 @@ async def test_verbindung_nutzt_benutzernamen_der_zugangsdaten(tmp_path, monkeyp
monkeypatch.setattr(asyncssh, "get_server_host_key", _fake_get_host_key, raising=False)
conn = FakeConnection(_make_db(tmp_path, host_username="alterWertAmHost", key_username="l4u"))
await connect_to_host(conn, 1)
await connect_to_host(conn, 1, user_id=TEST_USER_ID)
# Der Name aus den Zugangsdaten gewinnt gegen den Altwert am Host.
assert aufrufe["username"] == "l4u"
@ -270,7 +295,7 @@ async def test_hostkey_wechsel_nach_der_pruefung_beendet_die_sitzung(tmp_path, m
conn = FakeConnection(_make_db(tmp_path))
with pytest.raises(HostKeyMismatchError):
await connect_to_host(conn, 1)
await connect_to_host(conn, 1, user_id=TEST_USER_ID)
assert verbindung.aborted is True
@ -290,7 +315,7 @@ async def test_altbestand_ohne_gespeicherten_hostkey_wird_nachgetragen(tmp_path,
monkeypatch.setattr(asyncssh, "get_server_host_key", _fake_get_host_key, raising=False)
conn = FakeConnection(_make_db(tmp_path, host_key=None))
await connect_to_host(conn, 1)
await connect_to_host(conn, 1, user_id=TEST_USER_ID)
(stored,) = conn.raw.execute("SELECT ssh_host_key FROM hosts WHERE id = 1").fetchone()
assert stored == PUBLIC_KEY
@ -323,9 +348,13 @@ def _rdp_host(**overrides):
def test_rdp_params_nehmen_benutzernamen_der_zugangsdaten():
# Bug-Fix (FORTSETZUNG_Teil_C.md Abschnitt 3 Punkt 2): build_rdp_params()
# verlangt inzwischen session_id als Pflicht-Keyword-Argument (echte
# Signaturerweiterung, app/rdp_proxy/guacd_client.py) -- fuer diese Tests
# ist der konkrete Wert irrelevant, ein beliebiger int reicht.
params = build_rdp_params(
_rdp_host(rdp_username="alt", rdp_domain="ALTEDOMAENE"),
"geheim", username="Administrator", domain="CONTOSO",
"geheim", username="Administrator", domain="CONTOSO", session_id=1,
)
assert params["username"] == "Administrator"
assert params["domain"] == "CONTOSO"
@ -333,20 +362,22 @@ def test_rdp_params_nehmen_benutzernamen_der_zugangsdaten():
def test_rdp_params_fallback_auf_altwert_am_host():
params = build_rdp_params(_rdp_host(rdp_username="alt", rdp_domain="D"), "geheim")
params = build_rdp_params(
_rdp_host(rdp_username="alt", rdp_domain="D"), "geheim", session_id=1,
)
assert (params["username"], params["domain"]) == ("alt", "D")
def test_rdp_params_ohne_benutzernamen_meldet_zugangsdaten():
with pytest.raises(GuacamoleProtocolError) as excinfo:
build_rdp_params(_rdp_host(), "geheim")
build_rdp_params(_rdp_host(), "geheim", session_id=1)
assert "Zugangsdaten" in str(excinfo.value)
def test_rdp_params_brauchen_die_hostspalte_nicht_mehr():
"""Ein Hostdatensatz ohne rdp_username ist kein Fehler mehr -- der Name
kommt jetzt von woanders."""
params = build_rdp_params(_rdp_host(), "geheim", username="svc")
params = build_rdp_params(_rdp_host(), "geheim", username="svc", session_id=1)
assert params["username"] == "svc"
@ -362,7 +393,7 @@ def _pre_0010_db(tmp_path) -> sqlite3.Connection:
def test_migration_uebernimmt_eindeutige_ssh_benutzernamen(tmp_path):
db = _pre_0010_db(tmp_path)
db.execute("INSERT INTO host_groups (id, name, tenant_id) VALUES (1, 'g', 1)")
db.execute("INSERT INTO host_groups (id, name) VALUES (1, 'g')")
for host_id in (1, 2):
db.execute(
"INSERT INTO hosts (id, host_group_id, hostname, address, protocol, port, os_type, ssh_username) "
@ -370,8 +401,8 @@ def test_migration_uebernimmt_eindeutige_ssh_benutzernamen(tmp_path):
(host_id,),
)
db.execute(
"INSERT INTO ssh_keys (id, label, private_key_enc, public_key, key_type, tenant_id) "
"VALUES (1, 'k', X'00', 'pub', 'ed25519', 1)"
"INSERT INTO ssh_keys (id, label, private_key_enc, public_key, key_type) "
"VALUES (1, 'k', X'00', 'pub', 'ed25519')"
)
db.execute("INSERT INTO host_ssh_key_map (host_id, ssh_key_id) VALUES (1, 1), (2, 1)")
db.commit()
@ -386,7 +417,7 @@ def test_migration_raet_nicht_bei_mehrdeutigen_benutzernamen(tmp_path):
jede automatische Wahl geraten -- also bleibt das Feld leer und der
Fallback greift weiter."""
db = _pre_0010_db(tmp_path)
db.execute("INSERT INTO host_groups (id, name, tenant_id) VALUES (1, 'g', 1)")
db.execute("INSERT INTO host_groups (id, name) VALUES (1, 'g')")
for host_id, name in ((1, "root"), (2, "l4u")):
db.execute(
"INSERT INTO hosts (id, host_group_id, hostname, address, protocol, port, os_type, ssh_username) "
@ -394,8 +425,8 @@ def test_migration_raet_nicht_bei_mehrdeutigen_benutzernamen(tmp_path):
(host_id, name),
)
db.execute(
"INSERT INTO ssh_keys (id, label, private_key_enc, public_key, key_type, tenant_id) "
"VALUES (1, 'k', X'00', 'pub', 'ed25519', 1)"
"INSERT INTO ssh_keys (id, label, private_key_enc, public_key, key_type) "
"VALUES (1, 'k', X'00', 'pub', 'ed25519')"
)
db.execute("INSERT INTO host_ssh_key_map (host_id, ssh_key_id) VALUES (1, 1), (2, 1)")
db.commit()
@ -407,7 +438,7 @@ def test_migration_raet_nicht_bei_mehrdeutigen_benutzernamen(tmp_path):
def test_migration_uebernimmt_rdp_benutzer_und_domaene(tmp_path):
db = _pre_0010_db(tmp_path)
db.execute("INSERT INTO host_groups (id, name, tenant_id) VALUES (1, 'g', 1)")
db.execute("INSERT INTO host_groups (id, name) VALUES (1, 'g')")
db.execute(
"INSERT INTO hosts (id, host_group_id, hostname, address, protocol, port, os_type, "
"rdp_username, rdp_domain) "