mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-10-23 22:32:29 +00:00
fix(packet_capture): default waev.app brokers to a JWT TTL they accept (#248)
waev.app rejects a token whose exp is more than an hour past its iat, so the project-wide 24-hour default could never authenticate there — the operator saw only a bare auth failure with no hint that the lifetime was the problem, while the same public key worked from meshcoretomqtt. waev.app hosts now resolve to a 3600s TTL with renewal at 3500s when no value was configured for them, per broker or globally; a configured value still wins, and the substitution is logged. Matching is the apex and its subdomains only, so a lookalike host like waev.app.example.com does not qualify.
This commit is contained in:
1 parent
c573755324
commit
4312d8a39c
2 files changed
+115
-1
No files matched your search
@@ -76,10 +76,23 @@ NEIGHBORS_ATTEMPT_STATE_KEY = "packet_capture.last_neighbors_attempt"
|
||||
# instead, which is longer.
|
||||
NEIGHBORS_RETRY_BACKOFF_SECONDS = 300.0
|
||||
|
||||
# waev.app rejects a JWT whose exp is more than an hour past its iat, so the
|
||||
# project-wide 24-hour default cannot authenticate there (#248). These are the
|
||||
# values its brokers accept, applied only when nothing else was configured;
|
||||
# renewal is set just under the TTL so a token is replaced before it expires.
|
||||
WAEV_JWT_TTL_SECONDS = 3600
|
||||
WAEV_JWT_RENEWAL_INTERVAL = 3500
|
||||
|
||||
# Sentinel meaning "no IATA configured" (documented as invalid in config.ini.example).
|
||||
DEFAULT_IATA = "XYZ"
|
||||
|
||||
|
||||
def _is_waev_host(host: str) -> bool:
|
||||
"""Whether ``host`` is a waev.app broker (the apex or any subdomain)."""
|
||||
normalized = (host or "").strip().lower().rstrip(".")
|
||||
return normalized == "waev.app" or normalized.endswith(".waev.app")
|
||||
|
||||
|
||||
def _decode_key_str(key_str: str) -> Optional[bytes]:
|
||||
"""Decode a 16-byte channel key from a hex (32 chars) or base64 string."""
|
||||
key_str = key_str.strip()
|
||||
@@ -689,6 +702,8 @@ class PacketCaptureService(BaseServicePlugin):
|
||||
|
||||
global_jwt_renewal = config.getint("PacketCapture", "jwt_renewal_interval", fallback=43200)
|
||||
global_jwt_ttl = config.getint("PacketCapture", "jwt_ttl_seconds", fallback=86400)
|
||||
global_renewal_set = config.has_option("PacketCapture", "jwt_renewal_interval")
|
||||
global_ttl_set = config.has_option("PacketCapture", "jwt_ttl_seconds")
|
||||
|
||||
# Parse multiple brokers (mqtt1_*, mqtt2_*, etc.)
|
||||
broker_num = 1
|
||||
@@ -712,21 +727,42 @@ class PacketCaptureService(BaseServicePlugin):
|
||||
if not upload_packet_types:
|
||||
upload_packet_types = None
|
||||
|
||||
host = config.get("PacketCapture", server_key, fallback="localhost")
|
||||
# waev.app refuses a token whose lifetime runs past an hour, so the
|
||||
# 24-hour default never authenticates there and the operator only sees
|
||||
# a bare auth failure (#248). Give those brokers a lifetime they accept
|
||||
# unless a value was chosen for them, per broker or globally.
|
||||
waev_default = _is_waev_host(host)
|
||||
|
||||
renew_key = f"mqtt{broker_num}_jwt_renewal_interval"
|
||||
if config.has_option("PacketCapture", renew_key):
|
||||
jwt_renewal_interval = config.getint("PacketCapture", renew_key)
|
||||
elif waev_default and not global_renewal_set:
|
||||
jwt_renewal_interval = WAEV_JWT_RENEWAL_INTERVAL
|
||||
else:
|
||||
jwt_renewal_interval = global_jwt_renewal
|
||||
|
||||
ttl_key = f"mqtt{broker_num}_jwt_ttl_seconds"
|
||||
if config.has_option("PacketCapture", ttl_key):
|
||||
jwt_ttl_seconds = config.getint("PacketCapture", ttl_key)
|
||||
elif waev_default and not global_ttl_set:
|
||||
jwt_ttl_seconds = WAEV_JWT_TTL_SECONDS
|
||||
else:
|
||||
jwt_ttl_seconds = global_jwt_ttl
|
||||
|
||||
if waev_default and (
|
||||
jwt_ttl_seconds != global_jwt_ttl or jwt_renewal_interval != global_jwt_renewal
|
||||
):
|
||||
self.logger.info(
|
||||
"MQTT broker %d (%s) is a waev.app host; using a %ds JWT TTL and %ds "
|
||||
"renewal instead of the defaults, which it rejects. Set "
|
||||
"mqtt%d_jwt_ttl_seconds to override.",
|
||||
broker_num, host, jwt_ttl_seconds, jwt_renewal_interval, broker_num,
|
||||
)
|
||||
|
||||
broker = {
|
||||
"enabled": True,
|
||||
"host": config.get("PacketCapture", server_key, fallback="localhost"),
|
||||
"host": host,
|
||||
"port": config.getint("PacketCapture", f"mqtt{broker_num}_port", fallback=1883),
|
||||
"username": config.get("PacketCapture", f"mqtt{broker_num}_username", fallback=None),
|
||||
"password": config.get("PacketCapture", f"mqtt{broker_num}_password", fallback=None),
|
||||
|
||||
@@ -124,3 +124,81 @@ def test_auth_token_iat_exp_uses_broker_then_global():
|
||||
)
|
||||
def test_jwt_ttl_log_phrase(ttl: int, phrase: str):
|
||||
assert PacketCaptureService._jwt_ttl_log_phrase(ttl) == phrase
|
||||
|
||||
|
||||
def _waev_service(ini: str):
|
||||
bot = _bot_from_ini(ini)
|
||||
svc = object.__new__(PacketCaptureService)
|
||||
svc.bot = bot
|
||||
svc.logger = MagicMock()
|
||||
return svc, PacketCaptureService._parse_mqtt_brokers(svc, bot.config)
|
||||
|
||||
|
||||
def test_waev_broker_defaults_to_a_ttl_the_broker_accepts():
|
||||
"""waev.app rejects the 24-hour default, leaving the bot unable to auth (#248)."""
|
||||
_, brokers = _waev_service(
|
||||
"""
|
||||
[PacketCapture]
|
||||
enabled = false
|
||||
mqtt1_server = mqtt-a.waev.app
|
||||
mqtt2_server = broker.example
|
||||
"""
|
||||
)
|
||||
assert brokers[0]["jwt_ttl_seconds"] == 3600
|
||||
assert brokers[0]["jwt_renewal_interval"] == 3500
|
||||
# Only waev hosts move; every other broker keeps the project default.
|
||||
assert brokers[1]["jwt_ttl_seconds"] == 86400
|
||||
assert brokers[1]["jwt_renewal_interval"] == 43200
|
||||
|
||||
|
||||
@pytest.mark.parametrize("host", ["waev.app", "WAEV.APP", "mqtt-b.waev.app"])
|
||||
def test_waev_default_matches_apex_and_subdomains(host: str):
|
||||
_, brokers = _waev_service(
|
||||
f"""
|
||||
[PacketCapture]
|
||||
enabled = false
|
||||
mqtt1_server = {host}
|
||||
"""
|
||||
)
|
||||
assert brokers[0]["jwt_ttl_seconds"] == 3600
|
||||
|
||||
|
||||
@pytest.mark.parametrize("host", ["notwaev.app", "waev.app.example.com"])
|
||||
def test_waev_default_does_not_match_lookalike_hosts(host: str):
|
||||
_, brokers = _waev_service(
|
||||
f"""
|
||||
[PacketCapture]
|
||||
enabled = false
|
||||
mqtt1_server = {host}
|
||||
"""
|
||||
)
|
||||
assert brokers[0]["jwt_ttl_seconds"] == 86400
|
||||
|
||||
|
||||
def test_explicit_per_broker_ttl_beats_the_waev_default():
|
||||
_, brokers = _waev_service(
|
||||
"""
|
||||
[PacketCapture]
|
||||
enabled = false
|
||||
mqtt1_server = mqtt-a.waev.app
|
||||
mqtt1_jwt_ttl_seconds = 1800
|
||||
mqtt1_jwt_renewal_interval = 900
|
||||
"""
|
||||
)
|
||||
assert brokers[0]["jwt_ttl_seconds"] == 1800
|
||||
assert brokers[0]["jwt_renewal_interval"] == 900
|
||||
|
||||
|
||||
def test_explicit_global_ttl_beats_the_waev_default():
|
||||
"""An operator who set a global value meant it, even for waev."""
|
||||
_, brokers = _waev_service(
|
||||
"""
|
||||
[PacketCapture]
|
||||
enabled = false
|
||||
jwt_ttl_seconds = 7200
|
||||
jwt_renewal_interval = 3600
|
||||
mqtt1_server = mqtt-a.waev.app
|
||||
"""
|
||||
)
|
||||
assert brokers[0]["jwt_ttl_seconds"] == 7200
|
||||
assert brokers[0]["jwt_renewal_interval"] == 3600
|
||||
Reference in new issue
Block a user