connect fix 4

This commit is contained in:
2026-08-21 06:14:10 +02:00
parent bf2a02679d
commit 5e506c9921
17 changed files with 1266 additions and 162 deletions

View File

@ -811,13 +811,13 @@ async def get_host_detail(
if row is None:
raise HTTPException(status.HTTP_404_NOT_FOUND, "Host nicht gefunden")
keys_cursor = await conn.execute(
"SELECT sk.id, sk.label FROM host_ssh_key_map m JOIN ssh_keys sk ON sk.id = m.ssh_key_id "
"WHERE m.host_id = ?",
"SELECT sk.id, sk.label, sk.username FROM host_ssh_key_map m "
"JOIN ssh_keys sk ON sk.id = m.ssh_key_id WHERE m.host_id = ?",
(host_id,),
)
ssh_keys = [{"id": k[0], "label": k[1]} for k in await keys_cursor.fetchall()]
ssh_keys = [{"id": k[0], "label": k[1], "username": k[2]} for k in await keys_cursor.fetchall()]
rdp_row = await (await conn.execute(
"SELECT updated_at FROM rdp_credentials WHERE host_id = ?", (host_id,)
"SELECT updated_at, username, domain FROM rdp_credentials WHERE host_id = ?", (host_id,)
)).fetchone()
return {
"id": row[0], "hostname": row[1], "address": row[2], "protocol": row[3], "port": row[4],
@ -829,6 +829,10 @@ async def get_host_detail(
"ssh_keys": ssh_keys,
"rdp_credentials_set": rdp_row is not None,
"rdp_credentials_updated_at": rdp_row[0] if rdp_row else None,
# Benutzername/Domaene gehoeren seit Migration 0010 zu den
# Zugangsdaten; sie werden hier nur zur Anzeige mitgeliefert.
"rdp_credentials_username": rdp_row[1] if rdp_row else None,
"rdp_credentials_domain": rdp_row[2] if rdp_row else None,
}
@ -955,17 +959,28 @@ async def set_rdp_credentials(
conn = get_db()
if admin.is_any_admin:
await _assert_host_in_scope(conn, _scope(admin), host_id)
username = (payload.username or "").strip() or None
if username is None:
raise HTTPException(
status.HTTP_400_BAD_REQUEST,
"Bitte den Windows-Benutzernamen mit angeben -- er gehoert zu den "
"Zugangsdaten und wird fuer die Anmeldung am Ziel gebraucht.",
)
domain = (payload.domain or "").strip() or None
encrypted = encrypt_secret(payload.password.encode(), associated_data=b"rdp_password")
await conn.execute(
"INSERT INTO rdp_credentials (host_id, password_enc, updated_at) "
"VALUES (?, ?, strftime('%Y-%m-%dT%H:%M:%fZ','now')) "
"INSERT INTO rdp_credentials (host_id, password_enc, username, domain, updated_at) "
"VALUES (?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ','now')) "
"ON CONFLICT(host_id) DO UPDATE SET password_enc = excluded.password_enc, "
"username = excluded.username, domain = excluded.domain, "
"updated_at = excluded.updated_at",
(host_id, encrypted),
(host_id, encrypted, username, domain),
)
await write_audit_event(
conn, event_type="rdp_credentials_set", user_id=admin.id, client_ip=_client_ip(request),
details={"host_id": host_id},
# Benutzername/Domaene sind keine Geheimnisse (das Passwort schon) und
# gehoeren ins Audit-Log.
details={"host_id": host_id, "username": username, "domain": domain},
)
await conn.commit()
return {"status": "ok"}
@ -1011,18 +1026,20 @@ async def get_host_credentials(
if host_row is None:
raise HTTPException(status.HTTP_404_NOT_FOUND, "Host nicht gefunden")
keys_cursor = await conn.execute(
"SELECT sk.id, sk.label FROM host_ssh_key_map m JOIN ssh_keys sk ON sk.id = m.ssh_key_id "
"WHERE m.host_id = ?",
"SELECT sk.id, sk.label, sk.username FROM host_ssh_key_map m "
"JOIN ssh_keys sk ON sk.id = m.ssh_key_id WHERE m.host_id = ?",
(host_id,),
)
ssh_keys = [{"id": k[0], "label": k[1]} for k in await keys_cursor.fetchall()]
ssh_keys = [{"id": k[0], "label": k[1], "username": k[2]} for k in await keys_cursor.fetchall()]
rdp_row = await (await conn.execute(
"SELECT updated_at FROM rdp_credentials WHERE host_id = ?", (host_id,)
"SELECT updated_at, username, domain FROM rdp_credentials WHERE host_id = ?", (host_id,)
)).fetchone()
return {
"host_id": host_id, "ssh_keys": ssh_keys,
"rdp_credentials_set": rdp_row is not None,
"rdp_credentials_updated_at": rdp_row[0] if rdp_row else None,
"rdp_credentials_username": rdp_row[1] if rdp_row else None,
"rdp_credentials_domain": rdp_row[2] if rdp_row else None,
}
@ -1034,7 +1051,7 @@ async def list_rdp_credentials(admin: CurrentUser = Depends(require_admin_or_sco
scope = _scope(admin)
tenant_filter, params = scope.sql_filter("hg.tenant_id")
cursor = await conn.execute(
"SELECT h.id, h.hostname, h.address, hg.name, rc.updated_at "
"SELECT h.id, h.hostname, h.address, hg.name, rc.updated_at, rc.username, rc.domain "
"FROM hosts h JOIN host_groups hg ON hg.id = h.host_group_id "
"LEFT JOIN rdp_credentials rc ON rc.host_id = h.id "
f"WHERE h.protocol = 'rdp'{tenant_filter} ORDER BY h.hostname",
@ -1045,6 +1062,7 @@ async def list_rdp_credentials(admin: CurrentUser = Depends(require_admin_or_sco
{
"host_id": r[0], "hostname": r[1], "address": r[2], "host_group_name": r[3],
"credentials_set": r[4] is not None, "updated_at": r[4],
"username": r[5], "domain": r[6],
}
for r in rows
]
@ -1274,9 +1292,9 @@ async def create_ssh_key(
)
cursor = await conn.execute(
"INSERT INTO ssh_keys (label, owner_user_id, private_key_enc, public_key, key_type, "
"tenant_id, passphrase_enc) VALUES (?, ?, ?, ?, ?, ?, ?)",
"tenant_id, passphrase_enc, username) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
(payload.label, payload.owner_user_id, encrypted, payload.public_key, payload.key_type,
tenant_id, passphrase_enc),
tenant_id, passphrase_enc, (payload.username or "").strip() or None),
)
new_id = cursor.lastrowid
await write_audit_event(
@ -1284,6 +1302,9 @@ async def create_ssh_key(
details={
"id": new_id, "label": payload.label, "key_type": payload.key_type,
"has_passphrase": bool(payload.passphrase),
# Der Anmeldename ist kein Geheimnis und im Audit-Log ausdruecklich
# erwuenscht: er beantwortet "wer hat sich womit angemeldet".
"username": (payload.username or "").strip() or None,
},
)
await conn.commit()
@ -1297,7 +1318,7 @@ async def list_ssh_keys(admin: CurrentUser = Depends(require_admin_or_scope("ssh
tenant_filter, params = scope.sql_filter("sk.tenant_id")
cursor = await conn.execute(
"SELECT sk.id, sk.label, sk.owner_user_id, sk.key_type, sk.created_at, sk.rotated_at, "
"sk.expires_at, sk.tenant_id, t.name, (sk.passphrase_enc IS NOT NULL) "
"sk.expires_at, sk.tenant_id, t.name, (sk.passphrase_enc IS NOT NULL), sk.username "
"FROM ssh_keys sk JOIN tenants t ON t.id = sk.tenant_id "
f"WHERE 1=1{tenant_filter} ORDER BY sk.id",
params,
@ -1311,6 +1332,9 @@ async def list_ssh_keys(admin: CurrentUser = Depends(require_admin_or_scope("ssh
# Nur die Tatsache, NIE die Passphrase selbst -- kein Endpunkt
# dieser Anwendung gibt jemals Klartext-Geheimnisse zurueck.
"has_passphrase": bool(r[9]),
# Anmeldename des Zielsystems (Migration 0010). Kein Geheimnis --
# er wird angezeigt, damit erkennbar ist, welcher Zugang das ist.
"username": r[10],
}
for r in rows
]
@ -1371,6 +1395,10 @@ async def update_ssh_key(
fields.append("label = ?"); values.append(payload.label)
if "owner_user_id" in payload.model_fields_set:
fields.append("owner_user_id = ?"); values.append(payload.owner_user_id)
if "username" in payload.model_fields_set:
# Gleiche Semantik wie bei passphrase: Weglassen = unveraendert,
# explizites null/"" = entfernen.
fields.append("username = ?"); values.append((payload.username or "").strip() or None)
if rotating:
encrypted = encrypt_secret(payload.private_key_pem.encode(), associated_data=b"ssh_private_key")
fields += ["private_key_enc = ?", "public_key = ?", "key_type = ?",
@ -1396,6 +1424,7 @@ async def update_ssh_key(
"id": key_id, "rotated": rotating,
"passphrase_changed": bool(rotating or passphrase_given),
"has_passphrase": bool(passphrase),
"username_changed": "username" in payload.model_fields_set,
},
)
await conn.commit()