summaryrefslogtreecommitdiff
path: root/docs/research/simple-ssh-scope.md
diff options
context:
space:
mode:
authorAndrew Guschin <guschin@altlinux.org>2026-10-30 01:59:28 +0400
committerAndrew Guschin <guschin@altlinux.org>2026-10-30 01:59:28 +0400
commitda2e0eef42d5d611b0125de17e467dbddf3d8d71 (patch)
treebdbbf6b7190157fc2325ccb7dd42cf6bb62cdcd1 /docs/research/simple-ssh-scope.md
parent170f05697812b2f8b29226b4aa07b1b8db38873e (diff)
Document subscription formats and ALT runtime integrationresearch
- Add the Roentgen domain glossary - Record research on 3x-ui subscriptions, runtime integration, and defaults
Diffstat (limited to 'docs/research/simple-ssh-scope.md')
-rw-r--r--docs/research/simple-ssh-scope.md97
1 files changed, 97 insertions, 0 deletions
diff --git a/docs/research/simple-ssh-scope.md b/docs/research/simple-ssh-scope.md
new file mode 100644
index 0000000..54b686d
--- /dev/null
+++ b/docs/research/simple-ssh-scope.md
@@ -0,0 +1,97 @@
+# Can SSH protection stay small and general?
+
+Researched 2026-10-30 for Roentgen's ALT Linux first release. This report proposes a support boundary; it does not record successful networking tests or an approved product decision.
+
+## Recommendation
+
+SSH invocation detection can stay small. Universal automatic SSH protection cannot. Recommend explicit, administrator-authorized local management TCP endpoints and one ordinary host routing arrangement. Use `SSH_CONNECTION` only to catch likely mistakes in the invoking CLI, never as authority to create a direct exception. Protect eligible replies on configured endpoints regardless of which CLI, user or SSH session caused activation. Do not discover listeners, enumerate login sessions, trace process ancestry or monitor SSH configuration changes.
+
+This gives a bounded general TCP reply mechanism that also works with custom SSH ports. It requires accepting a narrower promise: Roentgen preserves direct routing for eligible management replies under the supported arrangement. It cannot promise to detect every remote invocation or preserve every possible SSH access path. If that narrower promise is unacceptable, reconsider remote activation or require an operator-managed management path. Do not quietly expand this work into a discovery subsystem.
+
+The existing [ALT runtime report](alt-runtime-integration.md) proposes protecting current and future SSH sessions and seeding established flows. This report narrows that proposal. The latest spec's default direct RFC1918 destinations supersede that report's connected-LAN-only proposal. Keep persistent systemd services, the polkit-authorized D-Bus controller, enabled fail-closed behavior, normal service lifecycle and manual-only configuration selection. SSH exceptions do not select a configuration or authorize disabling protection.
+
+## Evidence and inspection boundary
+
+Read the uncommitted main-checkout runtime report and first-release spec at `/home/aguschin/Projects/roentgen`, plus research ticket 14. Those files are context, not independently verified upstream evidence. This prepared checkout has no glossary or ADR directory. Read-only RPM metadata here reports OpenSSH server/client `9.6p1-alt7`, iproute2 `6.18.0-alt1`, nftables `1.1.7-alt2` and systemd `262-alt1`. These versions establish installed packages, not effective SSH configuration, firewall behavior or management reachability.
+
+No host networking, service, polkit or package changes, authentication prompts or live connection tests were performed. No session addresses were collected. Sources below are official manuals and upstream documentation. Current OpenBSD manuals describe OpenSSH semantics; they are not proof of every ALT 9.6p1 implementation detail. Proposed rules and transition behavior require ALT validation before shipping.
+
+## What the small detector establishes
+
+OpenSSH documents `SSH_CONNECTION` as four space-separated fields: client IP, client port, server IP and server port. `SSH_TTY` is absent for sessions without a tty, so it is an unsuitable required signal. [OpenSSH environment manual](https://man.openbsd.org/ssh#ENVIRONMENT).
+
+The unprivileged CLI can parse the tuple into bounded typed IP addresses and ports, reject malformed present values and include an optional hint in its D-Bus request. This is little general parsing code. It should report "SSH connection hint present" rather than "remote session verified". Absence means unknown. A local user can set, clear or copy environment values; wrappers and long-lived shells can lose or retain stale values. Neither presence nor absence establishes comprehensive session discovery.
+
+The system service has its own environment. It must receive the hint explicitly; it must not look for the caller's connection in the daemon's environment. The bus authenticates the caller connection and supplies process credentials through `GetConnectionCredentials`. It does not authenticate arbitrary method arguments as facts about the caller's SSH transport. [D-Bus credentials specification](https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-get-connection-credentials).
+
+Consequently, polkit authorization for enable is not proof that a submitted tuple belongs to that caller. Finding a matching established TCP socket or conntrack entry would prove that a flow exists, not that this CLI descends from its authenticated SSH login. Stronger association needs additional trusted evidence and race handling. Reading process environments or walking sshd parents is outside this recommendation.
+
+Keep the daemon's authority in its persisted management policy. A hint may cause refusal or verify coverage by existing policy; it must not add an arbitrary client address, tuple or port. Creating or widening management policy requires the existing separate policy authorization. Even a correctly parsed invented hint then cannot widen egress. An optional exact-flow mode would instead need explicit authorization of that specific exception, rather than calling it trusted autodetection.
+
+## Separate the guarantees
+
+1. Detecting one likely SSH invocation needs only the parser above. It offers a best-effort usability check. A direct D-Bus client can omit the hint, so this is not a daemon-enforceable guarantee that all SSH invocations are refused when unsafe.
+2. Protecting one exact current TCP flow needs its family and both address/port pairs, correct direction, plus a usable reply route. Replies reverse the tuple and target the client's ephemeral port. An output destination-port-22 rule does not identify them.
+3. Protecting other current sessions needs either knowledge of those flows or a policy that classifies all eligible replies on configured endpoints. The invoking environment supplies neither a complete list nor future membership.
+4. Protecting new inbound sessions on custom ports needs an enduring endpoint policy before those sessions arrive. One ephemeral client tuple cannot cover reconnects. An endpoint exception preserves routing eligibility; it does not open a listener, change SSH authorization or override another firewall's deny rule.
+
+OpenSSH permits multiple `Port` and `ListenAddress` values. Command-line options and alternative config files affect effective configuration. `sshd -T` prints the configuration it was asked to evaluate; it is not a registry of all running or future SSH servers. [OpenSSH listener configuration](https://man.openbsd.org/sshd_config#ListenAddress), [OpenSSH daemon options](https://man.openbsd.org/sshd.8#DESCRIPTION).
+
+systemd may own a listening socket and pass it to a service, including inetd-style activation. Socket and service namespaces can differ. This is another reason that finding an sshd process is insufficient. [systemd socket activation and namespaces](https://www.freedesktop.org/software/systemd/man/latest/systemd.socket.html#Description).
+
+A port can also belong to a forwarder or another protocol. A remote forwarded port number need not equal the host-visible SSH port. The proposed policy uses the local endpoints seen by the host, and explicitly grants a TCP service exception. It does not attest that packets contain SSH.
+
+## Minimal proposed endpoint contract
+
+Support host-terminated TCP in the controller's host network namespace, ordinary conntrack zone, and a conventional direct route in the retained main table. Configure a small bounded set of local management addresses and ports, with an explicit family and optional peer CIDR restriction. A family-wide local-address wildcard must be a deliberate policy choice. Never assume port 22 or infer all listening ports. Never exempt all traffic to the remote client IP or all traffic with a matching source port.
+
+Use the conntrack original destination endpoint to classify inbound management connections, and exempt only their outgoing reply direction. nftables exposes original/reply tuples, direction, state and marks; route-type output chains support route relookup. Conntrack identity describes a flow, not the owning application. [Official nftables manual, conntrack expressions and chains](https://netfilter.org/projects/nftables/manpage.html).
+
+The smallest proposed implementation matches those conntrack fields directly in output. It does not need a userspace session list or pre-seeded per-session marks. Set a reserved masked packet mark for eligible replies before general TUN capture, then use a higher-priority policy rule to look up the main table. Recheck the management predicate in the output guard rather than granting egress on an unqualified mark alone. Reserve mark bits and rule priorities with the rest of the runtime. iproute2 documents mark-based rules, explicit priority ordering and table lookup. [Upstream ip-rule manual](https://raw.githubusercontent.com/iproute2/iproute2/main/man/man8/ip-rule.8.in).
+
+Existing sessions qualify only if conntrack already records the incoming connection in the correct direction. Installing a rule matching only new SYN packets misses old sessions. Output classification avoids that particular race but cannot manufacture trustworthy preexisting conntrack history. The kernel's `nf_conntrack_tcp_loose` setting controls pickup of established connections; it does not certify the original initiator after tracking began late. Do not toggle it as a workaround. [Kernel conntrack documentation](https://www.kernel.org/doc/html/latest/networking/nf_conntrack-sysctl.html).
+
+Require valid tracking before supported sessions start, such as product rules installed before new management connections. Treat sessions predating installation, untracked flows, missing entries and reversed original tuples as unsupported until reconnected through the installed policy. For a supplied current-session hint, require a corresponding established incoming conntrack entry before acceptance. Do not claim this verifies ownership. Other current sessions are covered only when they satisfy the same tracking and configured-endpoint conditions; no all-session audit is proposed.
+
+Future connections to the configured endpoints can qualify without process or session discovery. Persist this policy with enabled intent and load it with the early guard, before ordinary networking. Keep it available through controller/Xray restarts and ordinary systemd lifecycle operations. A controller stopped or crashed must not remove the kernel exception. Reboot creates new flows under the same endpoint policy, not preserved TCP sessions. Port/address changes require an explicit policy update before relying on the changed endpoint. Socket activation needs no special discovery if it produces the same supported host-visible TCP flow; cross-namespace, forwarded and NAT-transformed paths are outside the first validation boundary.
+
+## A tuple is not a saved reply route
+
+Connection tracking and packet marks do not by themselves save a gateway or interface. `ip route get` can query a route for explicit source, destination, mark and TCP ports without sending a packet. It establishes a current routing decision, not continued reachability. [Upstream ip-route manual](https://raw.githubusercontent.com/iproute2/iproute2/main/man/man8/ip-route.8.in).
+
+For one hinted flow, query the reply tuple before activation and compare it with the candidate main-table route. Compare source, output interface and next hop. Once bypass rules are prepared, verify the actual marked lookup before capture starts. A query with an unused mark before its rule exists cannot validate that rule. Refuse a detectable mismatch rather than silently assuming that "main" preserves the previous route. This works within the proposed simple topology because Roentgen leaves the direct main-table routes intact. It does not freeze those routes against DHCP, link loss or administrator changes.
+
+Drop automatic route preservation for arbitrary multihoming, source policy, VRFs, ECMP-dependent paths, network namespaces, host NAT and competing tunnels. Supporting them needs more route context, per-path policy and revalidation. Reverse-path filtering can depend on marks and asymmetric routing; do not globally relax it to rescue an unsupported case. [Kernel reverse-path filtering and source mark semantics](https://docs.kernel.org/networking/ip-sysctl.html#rp-filter).
+
+The supported guarantee is that Roentgen does not divert eligible replies into TUN or block them in its own guard under the validated arrangement. It cannot guarantee SSH availability through link failure, upstream filtering, route changes or another firewall. Preserve exact tuple scope for a one-session exception; exact historical route pinning is a separate, larger requirement.
+
+## Short activation algorithm
+
+This is proposed design, not an executable ruleset.
+
+1. The CLI parses an optional SSH hint. A malformed present value fails before mutation. Missing data remains unknown; no tty or login-session discovery fallback runs.
+2. The controller authorizes the actual D-Bus sender through polkit and validates the existing management policy and supported networking arrangement. The hint cannot modify that policy.
+3. When a hint exists, check endpoint coverage, the established incoming conntrack tuple and the direct reply route. Refuse on uncertainty before changing disabled intent or networking. Report the failed prerequisite. Missing hints must never produce a "local invocation verified" claim.
+4. Prepare owned management classification, bypass routing and guard exceptions before TUN capture can affect replies. Verify the marked reply lookup at this point. Apply the existing crash-safe enable ordering with the exception policy persisted alongside intent. nftables and routing updates are separate operations and require safe ordering and reconciliation.
+5. Keep the guard and configured reply exceptions through retries and ordinary service restarts. Reject policy edits that knowingly remove the hinted active path. An error while already enabled leaves protection armed; it must not automatically disable proxying or restore unrestricted direct traffic.
+
+Ordinary traffic remains fail-closed. RFC1918 destinations retain the user's default direct policy. Management exceptions do not create a generic established-connection accept. Endpoint port 53 would conflict with the earlier DNS-capture policy; reject that overlap in this narrow contract unless the human explicitly revisits precedence. IPv6 management needs its own validated policy; IPv4 protection is insufficient for an IPv6 SSH hint.
+
+## Alternatives and effort
+
+Explicit endpoint policy has bounded implementation components: a typed policy and hint parser, existing polkit policy authorization, a conntrack tuple lookup for the known hint, route queries and topology checks, generated reply classification and masked routing rules, persistence/reconciliation, and ALT integration tests. Bound the topology checks to a documented allowed routing arrangement and reject unfamiliar rules; do not attempt to interpret arbitrary host policy. This is moderate networking integration within the existing runtime, with little SSH-specific code. It is not merely an environment-variable check.
+
+One known session is narrower in traffic permissions but less useful for persistent services. An explicitly authorized exact tuple avoids listener discovery and protects only that connection. It still needs routing validation, stale-flow handling and removal tied to connection lifetime or bounded expiry. It cannot promise other sessions, a reconnect with a new client port, or access after reboot. Blindly persisting the tuple risks granting a later reused tuple the exception. Treat this as an alternative product contract, not an automatic fallback.
+
+An operator-managed exception is the smallest Roentgen feature. The administrator prepares both reply routing and firewall compatibility outside Roentgen and retains console recovery. Roentgen may check the known tuple, but it cannot certify all external policy. A firewall accept alone does not keep replies out of TUN. This transfers configuration and availability responsibility to the operator and drops product-managed SSH protection.
+
+Universal automatic protection has open-ended scope: identifying listeners and their launch configurations, associating callers with authenticated sessions, capturing all old flows, tracking later listeners and changes, and preserving unusual reply routes. It fails the user's condition that detection need little general code. No credible exact line-count estimate follows from these manuals.
+
+## Human decisions still required
+
+- Accept configured management endpoints and the conditional flow/routing guarantee, choose the one-session alternative, or make exceptions operator-managed. Endpoint policy is the recommendation, not prior approval.
+- Decide the missing-hint policy. Allowing activation offers only best-effort SSH refusal. Requiring an already configured management policy for every activation is stricter and does not depend on detecting SSH. A console-only recovery workflow is another choice. A client assertion that it is local is not proof. An enforceable universal remote/local distinction would reopen scope.
+- Choose the actual local ports, address/family and optional peer restrictions. Decide whether exceptions may cover all local addresses and whether other TCP services on those endpoints are an accepted consequence. Configure custom ports explicitly before use.
+- Accept the limited routing/conntrack boundary and reconnect requirement for sessions predating tracking, or reconsider automatic protection. Decide how policy changes are performed safely when the environment hint is absent.
+
+Before shipping, test in disposable ALT environments: old correctly tracked flows, late/missing tracking, multiple sessions, IPv4/IPv6 custom ports, absent/spoofed hints, outbound SSH remaining captured, first server reply during activation, reboot/reconnect, and ordinary controller/Xray stop/restart/failure. Verify tuple direction and physical egress by packet traces. Test route mismatch and competing firewall rejection. These are remaining validation tasks, not experiments performed for this report.