account: harden Wolf unlock client IP
This commit is contained in:
parent
8c1e3a3887
commit
38aed252db
@ -9,10 +9,8 @@ from ..keycloak import require_auth, require_account_access
|
|||||||
|
|
||||||
|
|
||||||
def _client_ip() -> str:
|
def _client_ip() -> str:
|
||||||
for header in ("CF-Connecting-IP", "X-Forwarded-For", "X-Real-IP"):
|
# ProxyFix normalizes this from the trusted Traefik hop. Reading raw
|
||||||
value = request.headers.get(header, "").strip()
|
# forwarding headers here would let a browser request choose an unlock IP.
|
||||||
if value:
|
|
||||||
return value.split(",", 1)[0].strip()
|
|
||||||
return request.remote_addr or ""
|
return request.remote_addr or ""
|
||||||
|
|
||||||
|
|
||||||
@ -58,7 +56,7 @@ def register_account_wolf(app) -> None:
|
|||||||
if not ok:
|
if not ok:
|
||||||
return resp
|
return resp
|
||||||
payload = _json_payload()
|
payload = _json_payload()
|
||||||
payload["ip"] = payload.get("ip") or _client_ip()
|
payload["ip"] = _client_ip()
|
||||||
return ariadne_client.proxy("POST", "/api/game-stream/firewall/revoke", payload=payload)
|
return ariadne_client.proxy("POST", "/api/game-stream/firewall/revoke", payload=payload)
|
||||||
|
|
||||||
@app.route("/api/account/wolf/pairing/status", methods=["GET"])
|
@app.route("/api/account/wolf/pairing/status", methods=["GET"])
|
||||||
|
|||||||
@ -35,7 +35,11 @@ def make_client(monkeypatch, *, ariadne: DummyAriadne | None = None, account_ok:
|
|||||||
def test_wolf_status_proxies_source_ip(monkeypatch) -> None:
|
def test_wolf_status_proxies_source_ip(monkeypatch) -> None:
|
||||||
client, ariadne = make_client(monkeypatch)
|
client, ariadne = make_client(monkeypatch)
|
||||||
|
|
||||||
resp = client.get("/api/account/wolf", headers={"X-Forwarded-For": "1.2.3.4, 10.0.0.1"})
|
resp = client.get(
|
||||||
|
"/api/account/wolf",
|
||||||
|
environ_base={"REMOTE_ADDR": "1.2.3.4"},
|
||||||
|
headers={"X-Forwarded-For": "9.9.9.9, 10.0.0.1"},
|
||||||
|
)
|
||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert ariadne.calls == [("GET", "/api/game-stream/status", None, {"source_ip": "1.2.3.4"})]
|
assert ariadne.calls == [("GET", "/api/game-stream/status", None, {"source_ip": "1.2.3.4"})]
|
||||||
@ -44,7 +48,12 @@ def test_wolf_status_proxies_source_ip(monkeypatch) -> None:
|
|||||||
def test_wolf_unlock_uses_current_source_ip(monkeypatch) -> None:
|
def test_wolf_unlock_uses_current_source_ip(monkeypatch) -> None:
|
||||||
client, ariadne = make_client(monkeypatch)
|
client, ariadne = make_client(monkeypatch)
|
||||||
|
|
||||||
resp = client.post("/api/account/wolf/firewall/unlock", json={"ttl_seconds": 120}, headers={"X-Real-IP": "5.6.7.8"})
|
resp = client.post(
|
||||||
|
"/api/account/wolf/firewall/unlock",
|
||||||
|
json={"ttl_seconds": 120},
|
||||||
|
environ_base={"REMOTE_ADDR": "5.6.7.8"},
|
||||||
|
headers={"X-Real-IP": "9.9.9.9"},
|
||||||
|
)
|
||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert ariadne.calls == [("POST", "/api/game-stream/firewall/unlock", {"ttl_seconds": 120, "ip": "5.6.7.8"}, None)]
|
assert ariadne.calls == [("POST", "/api/game-stream/firewall/unlock", {"ttl_seconds": 120, "ip": "5.6.7.8"}, None)]
|
||||||
@ -53,8 +62,12 @@ def test_wolf_unlock_uses_current_source_ip(monkeypatch) -> None:
|
|||||||
def test_wolf_pairing_and_admin_actions_proxy(monkeypatch) -> None:
|
def test_wolf_pairing_and_admin_actions_proxy(monkeypatch) -> None:
|
||||||
client, ariadne = make_client(monkeypatch)
|
client, ariadne = make_client(monkeypatch)
|
||||||
|
|
||||||
client.get("/api/account/wolf/pairing/status", headers={"X-Forwarded-For": "1.2.3.4"})
|
client.get("/api/account/wolf/pairing/status", environ_base={"REMOTE_ADDR": "1.2.3.4"})
|
||||||
client.post("/api/account/wolf/pairing/submit-pin", json={"pair_secret": "secret", "pin": "1234"}, headers={"X-Forwarded-For": "1.2.3.4"})
|
client.post(
|
||||||
|
"/api/account/wolf/pairing/submit-pin",
|
||||||
|
json={"pair_secret": "secret", "pin": "1234"},
|
||||||
|
environ_base={"REMOTE_ADDR": "1.2.3.4"},
|
||||||
|
)
|
||||||
client.post("/api/account/wolf/game-mode/start", json={"game": "steam"})
|
client.post("/api/account/wolf/game-mode/start", json={"game": "steam"})
|
||||||
client.post("/api/account/wolf/game-mode/stop", json={"game": "steam"})
|
client.post("/api/account/wolf/game-mode/stop", json={"game": "steam"})
|
||||||
client.post("/api/account/wolf/admin/firewall/unlock", json={"ip": "8.8.8.8", "target_user": "olya"})
|
client.post("/api/account/wolf/admin/firewall/unlock", json={"ip": "8.8.8.8", "target_user": "olya"})
|
||||||
@ -68,6 +81,19 @@ def test_wolf_pairing_and_admin_actions_proxy(monkeypatch) -> None:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wolf_user_revoke_cannot_choose_arbitrary_ip(monkeypatch) -> None:
|
||||||
|
client, ariadne = make_client(monkeypatch)
|
||||||
|
|
||||||
|
resp = client.post(
|
||||||
|
"/api/account/wolf/firewall/revoke",
|
||||||
|
json={"ip": "9.9.9.9"},
|
||||||
|
environ_base={"REMOTE_ADDR": "1.2.3.4"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert ariadne.calls == [("POST", "/api/game-stream/firewall/revoke", {"ip": "1.2.3.4"}, None)]
|
||||||
|
|
||||||
|
|
||||||
def test_wolf_routes_require_account_and_ariadne(monkeypatch) -> None:
|
def test_wolf_routes_require_account_and_ariadne(monkeypatch) -> None:
|
||||||
client, _ariadne = make_client(monkeypatch, account_ok=False)
|
client, _ariadne = make_client(monkeypatch, account_ok=False)
|
||||||
assert client.get("/api/account/wolf").status_code == 403
|
assert client.get("/api/account/wolf").status_code == 403
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user