mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-08-28 13:24:09 +00:00
feat(config, docs): add JWT configuration options for MQTT authentication
- Introduced `jwt_ttl_seconds` and `jwt_renewal_interval` settings in `config.ini.example` for global JWT management, allowing for better control over token expiration and renewal intervals. - Updated documentation in `packet-capture.md` to clarify the usage of global and per-broker JWT settings, enhancing user understanding of authentication configurations. - Refactored `PacketCaptureService` to incorporate new JWT settings, ensuring consistent handling of token lifetimes and renewal processes.
This commit is contained in:
+9
-1
@@ -1605,6 +1605,9 @@ mqtt1_topic_packets = meshcore/{IATA}/{PUBLIC_KEY}/packets
|
||||
mqtt1_websocket_path = /mqtt
|
||||
mqtt1_client_id =
|
||||
mqtt1_upload_packet_types =
|
||||
# Optional per-broker JWT (inherit globals if omitted):
|
||||
# mqtt1_jwt_ttl_seconds = 3600 # JWT exp claim: iat + this many seconds
|
||||
# mqtt1_jwt_renewal_interval = 1800 # Refresh password this often; use < ttl; 0 = no renewal loop
|
||||
|
||||
# MQTT Broker 2 - Let's Mesh Analyzer (EU)
|
||||
mqtt2_enabled = true
|
||||
@@ -1627,7 +1630,12 @@ stats_in_status_enabled = true
|
||||
# Stats refresh interval (seconds)
|
||||
stats_refresh_interval = 300
|
||||
|
||||
# JWT renewal interval (seconds, 0 = disabled)
|
||||
# JWT TTL in seconds: exp − iat in the MQTT auth token (default 86400 = 24 hours)
|
||||
jwt_ttl_seconds = 86400
|
||||
|
||||
# JWT renewal interval (seconds): default cadence for proactive token refresh per broker.
|
||||
# 0 here makes the default broker renewal interval 0 (no renewal task unless a broker sets mqttN_jwt_renewal_interval > 0).
|
||||
# Tokens are still created at MQTT connect and on reconnect.
|
||||
jwt_renewal_interval = 86400
|
||||
|
||||
# Health check interval (seconds, 0 = disabled)
|
||||
|
||||
+15
-2
@@ -129,12 +129,25 @@ Placeholders:
|
||||
- `{PUBLIC_KEY}` - Device public key (uppercase)
|
||||
- `{public_key}` - Device public key (lowercase)
|
||||
|
||||
### Status Publishing
|
||||
### Status Publishing and MQTT auth (JWT)
|
||||
|
||||
Two separate settings:
|
||||
|
||||
- **`jwt_ttl_seconds`** (global) / **`mqttN_jwt_ttl_seconds`** (per broker): lifetime of the JWT in the `exp` claim (`exp = iat + ttl`). Use this when the broker enforces a maximum token lifetime (e.g. 60 minutes → `3600`).
|
||||
- **`jwt_renewal_interval`** (global) / **`mqttN_jwt_renewal_interval`** (per broker): how often the bot refreshes the MQTT password for that broker. Set **less than** the TTL (e.g. TTL 3600s and renewal every 1800s) so the connection does not outlive the token.
|
||||
|
||||
Per-broker keys override the global values for that broker only. Omit them to inherit globals.
|
||||
|
||||
```ini
|
||||
stats_in_status_enabled = true # Include device stats in status
|
||||
stats_refresh_interval = 300 # Publish status every 5 minutes
|
||||
jwt_renewal_interval = 86400 # Renew JWT every 24 hours
|
||||
|
||||
jwt_ttl_seconds = 86400 # Default JWT exp − iat (24 hours) for all brokers unless overridden
|
||||
jwt_renewal_interval = 43200 # Default proactive refresh cadence (12 hours); 0 = no renewal task
|
||||
|
||||
# Example on a broker that requires 60-minute tokens and refresh halfway through:
|
||||
# mqtt1_jwt_ttl_seconds = 3600
|
||||
# mqtt1_jwt_renewal_interval = 1800
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,126 @@
|
||||
"""PacketCapture MQTT broker JWT renewal interval and TTL parsing."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import configparser
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from modules.service_plugins.packet_capture_service import PacketCaptureService
|
||||
|
||||
|
||||
def _bot_from_ini(ini: str) -> MagicMock:
|
||||
cp = configparser.ConfigParser()
|
||||
cp.read_string(ini.strip())
|
||||
bot = MagicMock()
|
||||
bot.config = cp
|
||||
return bot
|
||||
|
||||
|
||||
def test_parse_mqtt_brokers_defaults_renewal_and_ttl():
|
||||
bot = _bot_from_ini(
|
||||
"""
|
||||
[PacketCapture]
|
||||
enabled = false
|
||||
mqtt1_server = broker.example
|
||||
"""
|
||||
)
|
||||
svc = object.__new__(PacketCaptureService)
|
||||
svc.bot = bot
|
||||
brokers = PacketCaptureService._parse_mqtt_brokers(svc, bot.config)
|
||||
assert len(brokers) == 1
|
||||
assert brokers[0]["jwt_renewal_interval"] == 43200
|
||||
assert brokers[0]["jwt_ttl_seconds"] == 86400
|
||||
|
||||
|
||||
def test_parse_mqtt_brokers_global_overrides():
|
||||
bot = _bot_from_ini(
|
||||
"""
|
||||
[PacketCapture]
|
||||
enabled = false
|
||||
jwt_renewal_interval = 7200
|
||||
jwt_ttl_seconds = 3600
|
||||
mqtt1_server = a.example
|
||||
mqtt2_server = b.example
|
||||
"""
|
||||
)
|
||||
svc = object.__new__(PacketCaptureService)
|
||||
svc.bot = bot
|
||||
brokers = PacketCaptureService._parse_mqtt_brokers(svc, bot.config)
|
||||
assert len(brokers) == 2
|
||||
for b in brokers:
|
||||
assert b["jwt_renewal_interval"] == 7200
|
||||
assert b["jwt_ttl_seconds"] == 3600
|
||||
|
||||
|
||||
def test_parse_mqtt_brokers_per_broker_overrides():
|
||||
bot = _bot_from_ini(
|
||||
"""
|
||||
[PacketCapture]
|
||||
enabled = false
|
||||
jwt_renewal_interval = 7200
|
||||
jwt_ttl_seconds = 86400
|
||||
mqtt1_server = short-ttl.example
|
||||
mqtt1_jwt_ttl_seconds = 3600
|
||||
mqtt1_jwt_renewal_interval = 1800
|
||||
mqtt2_server = inherit.example
|
||||
"""
|
||||
)
|
||||
svc = object.__new__(PacketCaptureService)
|
||||
svc.bot = bot
|
||||
brokers = PacketCaptureService._parse_mqtt_brokers(svc, bot.config)
|
||||
assert len(brokers) == 2
|
||||
assert brokers[0]["host"] == "short-ttl.example"
|
||||
assert brokers[0]["jwt_ttl_seconds"] == 3600
|
||||
assert brokers[0]["jwt_renewal_interval"] == 1800
|
||||
assert brokers[1]["jwt_ttl_seconds"] == 86400
|
||||
assert brokers[1]["jwt_renewal_interval"] == 7200
|
||||
|
||||
|
||||
def test_parse_mqtt_brokers_renewal_zero_override():
|
||||
bot = _bot_from_ini(
|
||||
"""
|
||||
[PacketCapture]
|
||||
enabled = false
|
||||
jwt_renewal_interval = 3600
|
||||
mqtt1_server = no-renew.example
|
||||
mqtt1_jwt_renewal_interval = 0
|
||||
"""
|
||||
)
|
||||
svc = object.__new__(PacketCaptureService)
|
||||
svc.bot = bot
|
||||
brokers = PacketCaptureService._parse_mqtt_brokers(svc, bot.config)
|
||||
assert brokers[0]["jwt_renewal_interval"] == 0
|
||||
|
||||
|
||||
def test_auth_token_iat_exp_clamps_non_positive_ttl():
|
||||
svc = object.__new__(PacketCaptureService)
|
||||
svc.jwt_ttl_seconds = 7200
|
||||
iat, exp = PacketCaptureService._auth_token_iat_exp(svc, {"jwt_ttl_seconds": 0})
|
||||
assert exp - iat == 86400
|
||||
assert exp == iat + 86400
|
||||
|
||||
iat2, exp2 = PacketCaptureService._auth_token_iat_exp(svc, {"jwt_ttl_seconds": -10})
|
||||
assert exp2 - iat2 == 86400
|
||||
|
||||
|
||||
def test_auth_token_iat_exp_uses_broker_then_global():
|
||||
svc = object.__new__(PacketCaptureService)
|
||||
svc.jwt_ttl_seconds = 999
|
||||
iat, exp = PacketCaptureService._auth_token_iat_exp(svc, {"jwt_ttl_seconds": 120})
|
||||
assert exp - iat == 120
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"ttl,phrase",
|
||||
[
|
||||
(3600, "1 hour"),
|
||||
(7200, "2 hours"),
|
||||
(60, "1 minute"),
|
||||
(180, "3 minutes"),
|
||||
(90, "90s"),
|
||||
],
|
||||
)
|
||||
def test_jwt_ttl_log_phrase(ttl: int, phrase: str):
|
||||
assert PacketCaptureService._jwt_ttl_log_phrase(ttl) == phrase
|
||||
Reference in New Issue
Block a user