When RDP_ENABLE_NLA=true (service_cfg.nla=true on the topology side), confirm PROTOCOL_HYBRID on the X.224 Connection Confirm, upgrade the socket to TLS using a self-signed cert generated at first start by the entrypoint, then drive a tiny CredSSP loop: - Read inbound TSRequest DER (bounded to MAX_TSREQUEST_LEN). - Scan for the NTLMSSP signature, dispatch on message type: Type 1 -> respond with a hand-built TSRequest carrying our Type 2 challenge. Type 3 -> parse_type3() and emit auth_attempt with the universal credential SD shape (secret_kind = ntlmssp_v2). - Hand-built DER: no pyasn1 dependency. Also folds in a small fix-up to commit 1: SMB SERVER_CHALLENGE was hardcoded to 0x11..0x88 across the fleet, which would let a scanner fingerprint every DECNET decky by its NTLM challenge. Both SMB and RDP now derive the 8-byte challenge from instance_seed.random_bytes(8, "ntlm_challenge"), giving each decky a deterministic-but-distinct value. SMB Dockerfile gets the instance_seed.py copy too (was synced into the build context but not COPYed into the image). - decnet/services/rdp.py: optional service_cfg.nla bool flips RDP_ENABLE_NLA in the compose env. - decnet/templates/rdp/Dockerfile + entrypoint.sh: openssl install + per-decky cert generation gated on RDP_ENABLE_NLA. - 9 NLA unit tests cover the DER reader/builder, _handle_nla round- trip with Type 1 / Type 3, oversized-DER rejection, and per- NODE_NAME challenge divergence. - DEBT.md: DEBT-040 closed; full TS_INFO_PACKET capture documented as a follow-up if attacker telemetry justifies it.
32 lines
1.1 KiB
Python
32 lines
1.1 KiB
Python
from pathlib import Path
|
|
from decnet.services.base import BaseService
|
|
|
|
TEMPLATES_DIR = Path(__file__).parent.parent / "templates" / "rdp"
|
|
|
|
|
|
class RDPService(BaseService):
|
|
name = "rdp"
|
|
ports = [3389]
|
|
default_image = "build"
|
|
|
|
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
|
|
fragment: dict = {
|
|
"build": {"context": str(TEMPLATES_DIR)},
|
|
"container_name": f"{decky_name}-rdp",
|
|
"restart": "unless-stopped",
|
|
"environment": {
|
|
"NODE_NAME": decky_name,
|
|
},
|
|
}
|
|
if log_target:
|
|
fragment["environment"]["LOG_TARGET"] = log_target
|
|
# Opt into the CredSSP / NLA capture path. Off by default — basic
|
|
# X.224 cookie capture is sufficient for most attacker traffic and
|
|
# avoids the openssl cert-gen overhead at container start.
|
|
if service_cfg and service_cfg.get("nla"):
|
|
fragment["environment"]["RDP_ENABLE_NLA"] = "true"
|
|
return fragment
|
|
|
|
def dockerfile_context(self) -> Path | None:
|
|
return TEMPLATES_DIR
|