8000 fix(logs): shut down TLS before closing syslog connections by adeelahmad · Pull Request #725 · adeelahmad/lense · GitHub
[go: up one dir, main page]

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog.d/syslog-tls-shutdown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- Syslog over TLS now sends a bounded TLS shutdown before closing its connection, avoiding a reset that could discard the final log message with TLS 1.3 collectors. Certificate verification is unchanged; a non-responsive collector can delay shutdown by at most half a second.
8 changes: 8 additions & 0 deletions fastapi_backend/app/domain/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,13 @@ def send(self, msg):

def close(self):
if self.sock is not None:
# TLS 1.3 may still have session tickets in flight. Closing without
# close_notify can reset the connection before the peer reads our logs.
# Bound the shutdown so an unresponsive collector cannot stall a worker.
if isinstance(self.sock, ssl.SSLSocket):
with contextlib.suppress(OSError):
self.sock.settimeout(0.5)
self.sock = self.sock.unwrap()
with contextlib.suppress(OSError):
self.sock.close()
self.sock = None
Expand Down Expand Up @@ -876,3 +883,4 @@ def read(cfg, limit=200, min_level=None, q=None, process=None, logger=None, befo
continue
found.sort(key=lambda d: d["ts"], reverse=True)
return found[:limit]

52 changes: 52 additions & 0 deletions fastapi_backend/tests/api/test_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,55 @@ def test_client_certificates_for_syslog_over_tls_and_https(client, db, cfg, pki)
assert _Http.got[-1][0] == "/ingest"
finally:
web.shutdown()


def test_syslog_tls_close_notifies_peer_and_closes_transport(monkeypatch):
class Raw:
closed = False

def close(self):
self.closed = True

raw = Raw()

class TLS:
timeout = None

def settimeout(self, value):
self.timeout = value

def unwrap(self):
return raw

monkeypatch.setattr(logs.ssl, "SSLSocket", TLS)
sock = TLS()
sender = logs.Sender("localhost", 6514, "tls")
sender.sock = sock
sender.close()
assert sock.timeout == 0.5
assert raw.closed
assert sender.sock is None


def test_syslog_tls_close_is_bounded_and_cleans_up_on_timeout(monkeypatch):
class TLS:
closed = False
timeout = None

def settimeout(self, value):
self.timeout = value

def unwrap(self):
raise TimeoutError("the peer did not close")

def close(self):
self.closed = True

monkeypatch.setattr(logs.ssl, "SSLSocket", TLS)
sock = TLS()
sender = logs.Sender("localhost", 6514, "tls")
sender.sock = sock
sender.close()
assert sock.timeout == 0.5
assert sock.closed
assert sender.sock is None
Loading
0