From bcafe36f170d83f73ecff43c5b58358cb58821d9 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Wed, 29 Jul 2026 20:50:28 -0300 Subject: [PATCH] =?UTF-8?q?ci(stand):=20=D0=BF=D1=83=D0=B1=D0=BB=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D0=BF=D0=BE=D1=80=D1=82?= =?UTF-8?q?=D1=8B=20CI-=D1=81=D1=82=D0=B5=D0=BD=D0=B4=D0=B0=20=D1=82=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=BD=D0=B0=20loopback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docker-compose.batchv11.yml уже публиковал 5005/5006/6006 на 127.0.0.1, а CI-стенд оставался на 0.0.0.0. В rippled.cfg у каждой станзы стоит admin = 0.0.0.0, поэтому любой из этих портов выдаёт роль администратора - stop, connect, feature, validation_seed - всякому, кто до него дотянулся, и только 6007 спрашивает креды. Более широкая привязка не давала ничего: тесты ходят на localhost, а ledger-acceptor обращается к ноде по сети compose (http://xrpld:5006), а не через опубликованный порт. Хостовый файрвол тут не помощник - правила DNAT, которые ставит Docker, стоят раньше цепочек, которыми управляет ufw. Проверено на пересозданном стенде: ledger-acceptor отвечает {"result":{"ledger_current_index":10,"status":"success"}}, интеграция 219 пройдено / 40 пропущено (amendment-gated) / 0 падений. --- .ci-config/docker-compose.ci.yml | 18 ++++++++++++------ CHANGES.md | 2 +- DocFx/StandaloneNode-Guide.md | 2 ++ DocFx/StandaloneNode-Guide.ru.md | 2 ++ 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.ci-config/docker-compose.ci.yml b/.ci-config/docker-compose.ci.yml index 1c2da9d4..bf42c40a 100644 --- a/.ci-config/docker-compose.ci.yml +++ b/.ci-config/docker-compose.ci.yml @@ -4,12 +4,18 @@ services: container_name: rippled-service command: ["-a", "--start"] ports: - - "5005:5005" - - "5006:5006" - - "6006:6006" - # credential-protected admin ws port, see [port_ws_admin_auth] in rippled.cfg. - # Loopback-only: rippled.cfg carries these credentials in plain text, so the port - # must not be reachable from other hosts. Tests connect over localhost. + # Publish to loopback only, matching docker-compose.batchv11.yml. Every port below grants + # the admin role (rippled.cfg sets `admin = 0.0.0.0` on each stanza), and admin means + # `stop`, `connect`, `feature`, `validation_seed` — node control, not just reads. Only + # 6007 asks for credentials; the rest accept anyone who can reach them. + # Binding wider buys nothing: tests connect over localhost and the ledger-acceptor + # reaches the node through the compose network (http://xrpld:5006), not a published port. + # A host firewall is not a substitute — Docker's DNAT rules sit ahead of the chains ufw + # manages, so a published port stays reachable even with ufw enabled. + - "127.0.0.1:5005:5005" + - "127.0.0.1:5006:5006" + - "127.0.0.1:6006:6006" + # credential-protected admin ws port, see [port_ws_admin_auth] in rippled.cfg - "127.0.0.1:6007:6007" volumes: - ./rippled.cfg:/config/rippled.cfg:ro diff --git a/CHANGES.md b/CHANGES.md index cbebaa68..19dab341 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -36,7 +36,7 @@ * Review pass (PR [#68](https://github.com/StaticBit-io/XrplCSharp/pull/68)): * **`ValidateCheckCreate` now enforces `InvoiceID` as a Hash256**, not merely as a string. `sfInvoiceID` is a 256-bit hash and this same release added exactly that rule for `WalletLocator` in `AccountSet` and `SignerListSet`, so `CheckCreate` was the odd one out: a malformed value passed validation and only blew up later inside the codec, reporting an encoding error instead of a `ValidationException`. The exception message is unchanged (`CheckCreate: invalid InvoiceID`) - * the standalone stand's credential-protected ws port is bound to `127.0.0.1` instead of every interface — `rippled.cfg` carries those credentials in plain text, and the nightly stand already published it loopback-only + * **the CI stand publishes every port on loopback only**, matching the nightly stand. The review flagged the new credential-protected ws port (6007) for being bound to every interface, but that was the least exposed of the four: `rippled.cfg` sets `admin = 0.0.0.0` on every stanza, so 5005/5006/6006 hand the admin role — `stop`, `connect`, `feature`, `validation_seed`, i.e. node control — to anyone who can reach them, and 6007 is the only one that asks for credentials at all. Nothing needed the wider binding: tests connect over localhost and the ledger-acceptor reaches the node through the compose network. Worth doing even though the node is a throwaway genesis container, because a host firewall does not cover this — Docker's DNAT rules sit ahead of the chains `ufw` manages * `TestIConnectionStates` — both reconnect-exhaustion tests discarded the `Task.WhenAny` winner, so a run where the terminal event never arrived proceeded after the 30 s timeout and could still pass. They now assert the event task won, and that at least one reconnect was attempted * `TestIProtocolFieldSets` sets `Expiration` on the mint-time NFT offer but never checked it read back; asserted now, closing the last unverified field of the corrected `NFTokenMint` set * test-only tidying: the parse-floor literal is shared instead of duplicated (`RippledTransactionFormats.MinimumExpectedTransactions`), the common-field set both conformance surfaces subtract now comes from one helper (`RippledTransactionFormats.CommonFields`), and a redundant `Link` on the vendored fixture is dropped diff --git a/DocFx/StandaloneNode-Guide.md b/DocFx/StandaloneNode-Guide.md index 57fbf28d..65fffeff 100644 --- a/DocFx/StandaloneNode-Guide.md +++ b/DocFx/StandaloneNode-Guide.md @@ -43,6 +43,8 @@ docker compose -f .ci-config/docker-compose.batchv11.yml down | 6006 | WebSocket (admin) — integration tests connect here (`ws://localhost:6006`) | | 6007 | WebSocket with `admin_user`/`admin_password` — used only by `TestIAdminCredentials` | +All ports are published on **loopback only** (`127.0.0.1`): the stand is reachable from the same machine and not from the network. That is deliberate — every stanza in the config sets `admin = 0.0.0.0`, so each port hands the admin role (`stop`, `connect`, `feature`, `validation_seed`) to anyone who can reach it, and only 6007 asks for credentials. If you genuinely need to reach the node from another host, widen the binding in the compose file as a conscious decision, not to make something work. + Port 6007 (`[port_ws_admin_auth]`) exists to prove that admin commands over WS open up only when `ClientOptions.AdminUser`/`AdminPassword` are set. rippled carries those credentials **inside the request JSON** — it never checks a Basic header on the ws handshake (its `user`/`password` port settings apply to plain HTTP JSON-RPC only). No other test uses this port. ## Why you get `temDISABLED` diff --git a/DocFx/StandaloneNode-Guide.ru.md b/DocFx/StandaloneNode-Guide.ru.md index b9310f0f..c7182b48 100644 --- a/DocFx/StandaloneNode-Guide.ru.md +++ b/DocFx/StandaloneNode-Guide.ru.md @@ -43,6 +43,8 @@ docker compose -f .ci-config/docker-compose.batchv11.yml down | 6006 | WebSocket (admin) — интеграционные тесты подключаются сюда (`ws://localhost:6006`) | | 6007 | WebSocket с `admin_user`/`admin_password` — только для `TestIAdminCredentials` | +Все порты публикуются **только на loopback** (`127.0.0.1`), поэтому стенд доступен с той же машины и недоступен из сети. Так и задумано: каждая станза в конфиге стоит с `admin = 0.0.0.0`, то есть даёт роль администратора (`stop`, `connect`, `feature`, `validation_seed`) любому, кто дотянулся до порта, а креды спрашивает только 6007. Если нужно ходить на ноду с другой машины — правьте привязку в compose-файле осознанно, а не «чтобы заработало». + Порт 6007 (`[port_ws_admin_auth]`) существует, чтобы проверить, что admin-команды по WS открываются только при заданных `ClientOptions.AdminUser`/`AdminPassword`. rippled передаёт эти креды **внутри JSON** запроса — Basic-заголовок на ws-рукопожатии сама нода не проверяет (её `user`/`password` относятся только к HTTP JSON-RPC). Остальные тесты порт не используют. ## Почему возникает `temDISABLED`