Repository navigation
Record a failed inbound TLS handshake on the HTTP intake listener, the API and MLLP under uvloop (BACKLOG #3302, #2613 item 3) - #2401
Conversation
|
QA line from the Builder of vault BACKLOG #3302 (posted verbatim by the Manager): The skill's first lines, verbatim: round 1, "I found 12 issues in this branch's diff against origin/main."; round 2 returned a bare JSON list of 12 findings with no opener. The five open round-2 findings are listed in the PR body under Known defects. |
|
Proposed vault banner for #3302, verbatim from the Builder's report (for the Lander; the ledger was not edited):
Proposed new row title: "a failed inbound TLS handshake on the DICOM C-STORE SCP leaves no record above DEBUG". The DICOM C-STORE SCP logs a failed inbound TLS handshake at DEBUG only, with no counter or event (docs/DICOM.md:88); it runs its own handshake via (Edited by the Manager: one ASVS cell id removed from the quoted banner; the vault copy of this banner may carry it.) |
| ctx = ssl.create_default_context() | ||
| with socket.create_connection(("127.0.0.1", port), timeout=5.0) as raw: | ||
| try: | ||
| with ctx.wrap_socket(raw, server_hostname="127.0.0.1"): |
|
Lander: HELD before enqueue. The engine merge queue squashes with COMMIT_MESSAGES, so this branch's commit bodies would land on main, and they pair an ASVS cell id with an open state (the vault presence-map rule). Please rewrite the branch history so no commit message carries that pairing, then post the new head with a QA line naming it. |
…e API and MLLP under uvloop (BACKLOG #3302, #2613 item 3) Squash of four Builder commits; the pre-squash head 9a24c35 is kept on branch b291/3302-pre-squash. Their messages follow, with one ASVS cell id removed. Record a failed inbound TLS handshake the event loop runs (BACKLOG #3302, #2613 item 3) Where the loop runs the TLS handshake (asyncio.start_server or loop.create_server with ssl=, uvloop, uvicorn), a failed one never reaches the application, and both loops log it at DEBUG only. So the HTTP intake listener and the API recorded none on any loop, and MLLP none on uvloop. New messagefoundry/tls_handshake_failures.py: - HandshakeFailures: the MLLP listener's counter and throttle (#2613), lifted out so all three surfaces share it. First failure at once, then one report per listener per 60 s with the count. - watch_loop_handshakes: sets the server context's sslobject_class to an SSLObject subclass whose do_handshake reports an ssl.SSLError other than SSLWantReadError/SSLSyscallError, then re-raises. Read in CPython 3.14 asyncio/sslproto.py and uvloop 0.23.0 sslproto.pyx: both build the object with ctx.wrap_bio and drive do_handshake, treating exactly that set as failure. No server or protocol class replaced. Wiring: MLLP installs the hook on the loop-run branch (uvloop) and keeps its own peer-bearing count where it upgrades itself; the HTTP listener emits tls_handshake_failed; the API logs a throttled WARNING, and serve logs the held count once uvicorn returns. Limits, recorded not forced: the hook knows no peer address, so these records name none; a handshake that only runs past its bound is not counted where the loop runs it. A wrong certificate, missing client certificate, downgrade or plaintext all raise and are counted. With [diagnostics].connection_events = false the MLLP and HTTP events are dropped as before (#3299); the in-process counters still count. The API line is a log line and does not depend on that switch. Proposed PR title: Record a failed inbound TLS handshake on the HTTP intake listener, the API and MLLP under uvloop (BACKLOG #3302, #2613 item 3) Proposed vault banner for #3302: CLOSING STEPS 1-3 BUILT (engine branch b291/3302-tls-handshake-record). A failed inbound TLS handshake is counted and recorded, throttled to one record per listener per minute, on the HTTP intake listener (tls_handshake_failed event), the API (WARNING line) and MLLP under uvloop (tls_handshake_failed event). Records the loop ran name no peer; a handshake past its bound is not counted there. Step 4, the vault re-read, is open vault record work. For #2613: open item 3 (the uvloop count) built on the same branch. The ledger was not edited. ----- Fix the handshake throttle's stale timer and the review's doc findings (BACKLOG #3302) code-review xhigh, round 1: - A report due now left an armed trailing timer in place. It could fire a second report inside the new window and lose its handle, so cancel() and flush() at stop() could not reach the next timer. _count now disarms it; the timer has its own callback. - The API line is logged by a plain emitter, at once, so a task that uvicorn's closing loop cancels can no longer lose it. - MLLP and HTTP stop() flush and cancel only while still stopping, so a stop() that resumes after a restart leaves the new run's failures be. - Docs: CONNECTIONS.md Table B said a failed MLLP handshake had no connection event; SECURITY.md said MLLP never runs its allow-list before a failed handshake, false on the stdlib loop (#1606); the changelog claimed every TLS listener (the DICOM SCP still logs at DEBUG only); the console filter comment named MLLP only. - The serve test restores the logger level it found. Proposed PR title: Record a failed inbound TLS handshake on the HTTP intake listener, the API and MLLP under uvloop (BACKLOG #3302, #2613 item 3) Proposed vault banner for #3302: CLOSING STEPS 1-3 BUILT (engine branch b291/3302-tls-handshake-record). A failed inbound TLS handshake is counted and recorded, throttled to one record per listener per minute, on the HTTP intake listener (tls_handshake_failed event), the API (WARNING line) and MLLP under uvloop (tls_handshake_failed event). Records the loop ran name no peer; a handshake past its bound is not counted there. Step 4, the vault re-read, is open vault record work. For #2613: open item 3 (the uvloop count) built on the same branch. The ledger was not edited. ----- Bound the recorder-fault log and tighten the handshake docs and tests (BACKLOG #3302) code-review xhigh, round 2 (the last): - A recorder that fails on every call logged a WARNING per failed handshake; now the first is a WARNING and later ones DEBUG. - The module's list of what the hook cannot see is now "at least" and names wrap_bio faults and wrap_socket contexts (the DICOM SCP). - The counters' docstrings say failures during stop() are not counted. - CONNECTIONS.md no longer says a uvloop handshake logs at DEBUG or frees a slot, and points at PHI.md row 7 instead of restating the throttle; SECURITY.md names build_api_ssl_context instead of a stale line anchor. - The timer test uses a 60 s window, and the API serve test polls the record's count instead of sleeping. Open notes are in the Builder's report for the PR body. Proposed PR title: Record a failed inbound TLS handshake on the HTTP intake listener, the API and MLLP under uvloop (BACKLOG #3302, #2613 item 3) Proposed vault banner for #3302: CLOSING STEPS 1-3 BUILT (engine branch b291/3302-tls-handshake-record). A failed inbound TLS handshake is counted and recorded, throttled to one record per listener per minute, on the HTTP intake listener (tls_handshake_failed event), the API (WARNING line) and MLLP under uvloop (tls_handshake_failed event). Records the loop ran name no peer; a handshake past its bound is not counted there. Step 4, the vault re-read, is open vault record work. For #2613: open item 3 (the uvloop count) built on the same branch. The ledger was not edited. ----- Stub the API context with a real SSLContext in the exposure tests (BACKLOG #3302) serve now watches the API context's handshakes, which sets its sslobject_class. The revocation-gate tests stubbed the context builder with a bare object(), so serve exited 1 there. They now get an empty server SSLContext; the gate, not context building, is still what they test. Found by the full suite on this branch. Proposed PR title: Record a failed inbound TLS handshake on the HTTP intake listener, the API and MLLP under uvloop (BACKLOG #3302, #2613 item 3) Proposed vault banner for #3302: CLOSING STEPS 1-3 BUILT (engine branch b291/3302-tls-handshake-record). A failed inbound TLS handshake is counted and recorded, throttled to one record per listener per minute, on the HTTP intake listener (tls_handshake_failed event), the API (WARNING line) and MLLP under uvloop (tls_handshake_failed event). Records the loop ran name no peer; a handshake past its bound is not counted there. Step 4, the vault re-read, is open vault record work. For #2613: open item 3 (the uvloop count) built on the same branch. The ledger was not edited. -----
9a24c35 to
26c6f6c
Compare
|
Branch rewritten by the Manager at the Lander's request: the merge queue squashes with commit bodies, and four commit messages paired an ASVS cell id with an open state.
QA line naming the new head: |
|
Lander: enqueuing at pinned head 26c6f6c. Review proof covers this head; open findings are filed in vault (pass U, vault PR 2506, merged). |
Batch 291 (security-asvs), item vault BACKLOG #3302, plus #2613 open item 3. Cut as its own PR because it adds a detection control.
Merge danger
Reversible: yes -- a revert of the squashed commit undoes all of it; nothing acts outside the commit.
Blast radius: the TLS server contexts of the HTTP intake listener, the API (
serve) and MLLP: each now installs anSSLObjectsubclass whosedo_handshakereports a failure and re-raises it unchanged. New modulemessagefoundry/tls_handshake_failures.py; MLLP's throttle extracted into it (net reduction inmllp.py);http_listener.py;__main__.pyserve wiring;scripts/security/crypto_inventory_check.py; docsPHI.md,SECURITY.md,CONNECTIONS.md; a web console comment;changelog.d/3302.security.md. No store, pipeline or API route change.Evidence
asyncio/sslproto.pyand uvloop 0.23.0sslproto.pyx).tests/test_tls_handshake_failures.pyfails a real handshake on each surface (plaintext, and a client that refuses the certificate) and reads the record; each has a completed-handshake control that records nothing. With the hook disabled, 5 tests go red. MLLP runs on the stdlib loop forced onto the uvloop path everywhere, and on real uvloop (skipped on Windows).Item 3302
Source branch:
b291/3302-tls-handshake-record, head9a24c35fb8a6242a5a4cf5e74fdb993271b9ba65, cut from064700f2d.tls_handshake_failedconnection event plus atls_handshake_failurescounter. API: a throttled WARNING throughApiHandshakeRecord, wired inserve; the held count is logged once uvicorn returns. Throttle: first failure at once, then one record per listener per 60 s with the count. Cause = exception class plus OpenSSL's reason mnemonic matched against[A-Z0-9_]{1,64}; no payload and no certificate contents._start_tlscount, which names the peer; a guard stops double counting.Limits, recorded rather than forced (no server or protocol class replaced):
do_handshake, so it is not counted where the loop runs the handshake. A wrong certificate, missing client certificate, downgrade and plaintext on a TLS port all raise and are counted.[diagnostics].connection_events = false(switch unchanged, #3299's subject): MLLP and HTTP events are dropped as before; the in-process counters still count; the API record is a log line and does not depend on the switch.wrap_socket, which this hook does not reach; proposed as a new row.Owner question (not answered by any seat)
The API logs a WARNING when a client refuses the engine's own auto-minted self-signed certificate (ADR 0172), e.g. a browser before the operator trusts it. Throttled to one line a minute, but a default install would see it routinely. Builder recommends keeping WARNING (the row asked for a WARNING line; the throttle bounds it), with a follow-up if the owner wants refusals of the generated pair at INFO.
Proposed vault banner and new row
Verbatim in the Builder's report to the Manager, carried to the Lander: CLOSING STEPS 1-3 BUILT; step 4 open as vault record work; #2613 open item 3 built on the same branch. Proposed new row: "a failed inbound TLS handshake on the DICOM C-STORE SCP leaves no record above DEBUG".
Known defects shipped (Builder's review round 2, open)
weakref.WeakMethodwould break it.stop()methods are judged unnecessary by the reviewer (the runner joins abandoned stops first); kept as harmless. The "no report outlives stop()" guard lives in each listener rather than in the helper.Checks
ruff check,ruff format --checkon every changed file; mypy strict--platform linux(messagefoundry, webconsole, toolkit, excluding tray) and--platform win32, clean; tests leg on the changed test files, clean; crypto-inventory gate OK.-n 8) ata86c7bd84, before the review fixes: 11 failed, 47973 passed. Three were the Builder's, fixed in later commits; seven passed on re-run at a later head;test_connscale_smoke::test_the_sweep_produces_one_record_per_mode_and_count(a load test) was never re-run and is judged unrelated but not verified.gates (ubuntu-latest)fortests/test_tls_handshake_failures.py::test_mllp_records_a_failed_handshake_on_uvloop(must pass there; on Windows it must show skipped, not failed);gates (windows-latest). The full suite was not re-run after the review fixes.Landing order: none. (Open PR 2378 also edits
mllp.py; this branch merges clean againstmainat58a9e32dc.)