feat: add per-listener SOCKS5 server authentication (RFC 1929)
Per-listener username/password auth via `auth:` config key. When set, clients must negotiate method 0x02 and pass RFC 1929 subnegotiation; no-auth (0x00) is rejected to prevent downgrade. Listeners without `auth` keep current no-auth behavior. Includes auth_failures metric, API integration (/status auth flag, /config auth_users count without exposing passwords), config parsing with YAML int coercion, integration tests (success, failure, method rejection, no-auth unchanged), and documentation updates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -151,6 +151,77 @@ class TestHandleStatus:
|
||||
assert body["listeners"][1]["latency"] is None
|
||||
|
||||
|
||||
class TestHandleStatusAuth:
|
||||
"""Test auth flag in /status listener entries."""
|
||||
|
||||
def test_auth_flag_present(self):
|
||||
config = Config(
|
||||
listeners=[
|
||||
ListenerConfig(
|
||||
listen_host="0.0.0.0", listen_port=1080,
|
||||
auth={"alice": "s3cret", "bob": "hunter2"},
|
||||
),
|
||||
],
|
||||
)
|
||||
ctx = _make_ctx(config=config)
|
||||
_, body = _handle_status(ctx)
|
||||
assert body["listeners"][0]["auth"] is True
|
||||
|
||||
def test_auth_flag_absent_when_empty(self):
|
||||
config = Config(
|
||||
listeners=[
|
||||
ListenerConfig(listen_host="0.0.0.0", listen_port=1080),
|
||||
],
|
||||
)
|
||||
ctx = _make_ctx(config=config)
|
||||
_, body = _handle_status(ctx)
|
||||
assert "auth" not in body["listeners"][0]
|
||||
|
||||
|
||||
class TestHandleConfigAuth:
|
||||
"""Test auth_users in /config listener entries."""
|
||||
|
||||
def test_auth_users_count(self):
|
||||
config = Config(
|
||||
listeners=[
|
||||
ListenerConfig(
|
||||
listen_host="0.0.0.0", listen_port=1080,
|
||||
auth={"alice": "s3cret", "bob": "hunter2"},
|
||||
),
|
||||
],
|
||||
)
|
||||
ctx = _make_ctx(config=config)
|
||||
_, body = _handle_config(ctx)
|
||||
assert body["listeners"][0]["auth_users"] == 2
|
||||
|
||||
def test_auth_users_absent_when_empty(self):
|
||||
config = Config(
|
||||
listeners=[
|
||||
ListenerConfig(listen_host="0.0.0.0", listen_port=1080),
|
||||
],
|
||||
)
|
||||
ctx = _make_ctx(config=config)
|
||||
_, body = _handle_config(ctx)
|
||||
assert "auth_users" not in body["listeners"][0]
|
||||
|
||||
def test_passwords_not_exposed(self):
|
||||
config = Config(
|
||||
listeners=[
|
||||
ListenerConfig(
|
||||
listen_host="0.0.0.0", listen_port=1080,
|
||||
auth={"alice": "s3cret"},
|
||||
),
|
||||
],
|
||||
)
|
||||
ctx = _make_ctx(config=config)
|
||||
_, body = _handle_config(ctx)
|
||||
listener = body["listeners"][0]
|
||||
# only count, never passwords
|
||||
assert "auth_users" in listener
|
||||
assert "auth" not in listener
|
||||
assert "s3cret" not in str(body)
|
||||
|
||||
|
||||
class TestHandleStatusPools:
|
||||
"""Test GET /status with multiple named pools."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user