Adopt nanoDNS-style INI config for the guide gateway
Replace the gateway's env-var DNS policy with docker/nanodns.ini, modelled on https://github.com/drakmor/nanoDNS#config: [general] (redirect_url, allowed_clients), [upstream] (ordered resolver list + timeout_ms), [overrides] (mask = IPv4 spoof) and [exceptions] (masks forwarded upstream). Masks use shell-style wildcards and match case-insensitively; resolution order is exceptions -> overrides -> NXDOMAIN. The unmatched-name behaviour is deliberately inverted from upstream nanoDNS: names matching no rule return NXDOMAIN (default-deny) instead of being forwarded, so PSN, updates and telemetry stay blocked. The source-client allowlist now lives in [general] allowed_clients. The INI is baked into the image; CONFIG_FILE can point at a mounted file for per-host overrides. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
@@ -22,6 +22,8 @@ before including them in an image.
|
|||||||
- `compose.yaml`: hardened local deployment.
|
- `compose.yaml`: hardened local deployment.
|
||||||
- `docker/server.py`: static HTTP server and security headers.
|
- `docker/server.py`: static HTTP server and security headers.
|
||||||
- `docker/guide_gateway.py`: restricted DNS plus HTTP/HTTPS User Guide redirect.
|
- `docker/guide_gateway.py`: restricted DNS plus HTTP/HTTPS User Guide redirect.
|
||||||
|
- `docker/nanodns.ini`: nanoDNS-style DNS policy (overrides, exceptions, upstreams,
|
||||||
|
allowed client VLANs) read by `guide_gateway.py`; default-deny for unmatched names.
|
||||||
- `payloads.sha256`: approved payload digests for the pinned submodule commit.
|
- `payloads.sha256`: approved payload digests for the pinned submodule commit.
|
||||||
- `docs/PS5_SETUP.md`: user and operator guide.
|
- `docs/PS5_SETUP.md`: user and operator guide.
|
||||||
- `docs/HANDOFF.md`: current infrastructure state and durable session handoff.
|
- `docs/HANDOFF.md`: current infrastructure state and durable session handoff.
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ COPY app/offsets /srv/public/offsets
|
|||||||
COPY app/payloads /srv/public/payloads
|
COPY app/payloads /srv/public/payloads
|
||||||
COPY docker/server.py /srv/server.py
|
COPY docker/server.py /srv/server.py
|
||||||
COPY docker/guide_gateway.py /srv/guide_gateway.py
|
COPY docker/guide_gateway.py /srv/guide_gateway.py
|
||||||
|
COPY docker/nanodns.ini /srv/nanodns.ini
|
||||||
COPY --from=certificates /certificates /srv/certificates
|
COPY --from=certificates /certificates /srv/certificates
|
||||||
|
|
||||||
RUN cd /srv/public && sha256sum -c /tmp/payloads.sha256 \
|
RUN cd /srv/public && sha256sum -c /tmp/payloads.sha256 \
|
||||||
|
|||||||
+3
-6
@@ -17,12 +17,9 @@ services:
|
|||||||
ps5-guide-gateway:
|
ps5-guide-gateway:
|
||||||
image: relapse-exploit:local
|
image: relapse-exploit:local
|
||||||
command: ["python", "/srv/guide_gateway.py"]
|
command: ["python", "/srv/guide_gateway.py"]
|
||||||
environment:
|
# DNS policy (overrides, exceptions, upstreams, allowed client VLANs) lives in
|
||||||
GATEWAY_IP: "${GATEWAY_IP:-192.168.20.30}"
|
# docker/nanodns.ini, baked into the image. Set CONFIG_FILE to point at a
|
||||||
REDIRECT_URL: "${GUIDE_REDIRECT_URL:-http://192.168.20.30:8000/}"
|
# different mounted file for per-host customisation.
|
||||||
FORWARDED_DOMAINS: "${FORWARDED_DOMAINS:-homebrew.page,itsplk.github.io,store.spectrumlibrary.online,api.github.com,github.com,raw.githubusercontent.com,release-assets.githubusercontent.com,prosperopatches.com,cdn.prosperopatches.com,image.api.playstation.com,gmedia.playstation.com}"
|
|
||||||
UPSTREAM_DNS: "${UPSTREAM_DNS:-1.1.1.1}"
|
|
||||||
ALLOWED_CLIENTS: "${ALLOWED_CLIENTS:-192.168.40.0/28,192.168.20.0/24,192.168.10.0/27}"
|
|
||||||
ports:
|
ports:
|
||||||
- "${GATEWAY_BIND_IP:-192.168.20.30}:53:8053/udp"
|
- "${GATEWAY_BIND_IP:-192.168.20.30}:53:8053/udp"
|
||||||
- "${GATEWAY_BIND_IP:-192.168.20.30}:53:8053/tcp"
|
- "${GATEWAY_BIND_IP:-192.168.20.30}:53:8053/tcp"
|
||||||
|
|||||||
+123
-34
@@ -1,3 +1,5 @@
|
|||||||
|
import configparser
|
||||||
|
import fnmatch
|
||||||
import http.server
|
import http.server
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import os
|
import os
|
||||||
@@ -9,38 +11,96 @@ import struct
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_FILE = os.environ.get("CONFIG_FILE", "/srv/nanodns.ini")
|
||||||
DNS_PORT = int(os.environ.get("DNS_PORT", "8053"))
|
DNS_PORT = int(os.environ.get("DNS_PORT", "8053"))
|
||||||
HTTP_PORT = int(os.environ.get("HTTP_PORT", "8081"))
|
HTTP_PORT = int(os.environ.get("HTTP_PORT", "8081"))
|
||||||
HTTPS_PORT = int(os.environ.get("HTTPS_PORT", "8443"))
|
HTTPS_PORT = int(os.environ.get("HTTPS_PORT", "8443"))
|
||||||
GATEWAY_IP = ipaddress.IPv4Address(os.environ.get("GATEWAY_IP", "192.168.20.30"))
|
|
||||||
REDIRECT_URL = os.environ.get("REDIRECT_URL", f"http://{GATEWAY_IP}:8000/")
|
|
||||||
TARGET_DOMAIN = "manuals.playstation.net"
|
|
||||||
# Exact names forwarded to the upstream resolver (homebrew store: catalog + downloads).
|
|
||||||
FORWARDED_DOMAINS = frozenset(
|
|
||||||
name.strip().lower()
|
|
||||||
for name in os.environ.get(
|
|
||||||
"FORWARDED_DOMAINS",
|
|
||||||
"homebrew.page,itsplk.github.io,store.spectrumlibrary.online,api.github.com,github.com,raw.githubusercontent.com,release-assets.githubusercontent.com,prosperopatches.com,cdn.prosperopatches.com,image.api.playstation.com,gmedia.playstation.com",
|
|
||||||
).split(",")
|
|
||||||
if name.strip()
|
|
||||||
)
|
|
||||||
UPSTREAM_DNS = str(ipaddress.ip_address(os.environ.get("UPSTREAM_DNS", "1.1.1.1")))
|
|
||||||
UPSTREAM_TIMEOUT = 3.0
|
|
||||||
# Source networks allowed to query the DNS service. Defaults to the console VLAN
|
|
||||||
# plus the server and trusted-management VLANs. Accepts a comma-separated mix of
|
|
||||||
# individual addresses and CIDR ranges; empty disables the filter and serves
|
|
||||||
# every client.
|
|
||||||
ALLOWED_CLIENTS = tuple(
|
|
||||||
ipaddress.ip_network(entry.strip(), strict=False)
|
|
||||||
for entry in os.environ.get(
|
|
||||||
"ALLOWED_CLIENTS",
|
|
||||||
"192.168.40.0/28,192.168.20.0/24,192.168.10.0/27",
|
|
||||||
).split(",")
|
|
||||||
if entry.strip()
|
|
||||||
)
|
|
||||||
CERTIFICATE = "/srv/certificates/manuals.crt"
|
CERTIFICATE = "/srv/certificates/manuals.crt"
|
||||||
PRIVATE_KEY = "/srv/certificates/manuals.key"
|
PRIVATE_KEY = "/srv/certificates/manuals.key"
|
||||||
|
|
||||||
|
# Built-in defaults, used when the config file (or a given key) is absent. They
|
||||||
|
# mirror the reviewed production policy: the User Guide domain is spoofed to the
|
||||||
|
# local gateway, the reviewed store/update hosts are forwarded upstream, and
|
||||||
|
# every other name returns NXDOMAIN (default-deny).
|
||||||
|
DEFAULT_REDIRECT_URL = "http://192.168.20.30:8000/"
|
||||||
|
DEFAULT_ALLOWED_CLIENTS = "192.168.40.0/28, 192.168.20.0/24, 192.168.10.0/27"
|
||||||
|
DEFAULT_UPSTREAMS = "1.1.1.1"
|
||||||
|
DEFAULT_TIMEOUT_MS = "3000"
|
||||||
|
DEFAULT_OVERRIDES = (("manuals.playstation.net", "192.168.20.30"),)
|
||||||
|
DEFAULT_EXCEPTIONS = (
|
||||||
|
"homebrew.page",
|
||||||
|
"itsplk.github.io",
|
||||||
|
"store.spectrumlibrary.online",
|
||||||
|
"api.github.com",
|
||||||
|
"github.com",
|
||||||
|
"raw.githubusercontent.com",
|
||||||
|
"release-assets.githubusercontent.com",
|
||||||
|
"prosperopatches.com",
|
||||||
|
"cdn.prosperopatches.com",
|
||||||
|
"image.api.playstation.com",
|
||||||
|
"gmedia.playstation.com",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _split(value):
|
||||||
|
return [item for item in value.replace(",", " ").split() if item]
|
||||||
|
|
||||||
|
|
||||||
|
def load_config(path):
|
||||||
|
parser = configparser.ConfigParser(
|
||||||
|
allow_no_value=True,
|
||||||
|
inline_comment_prefixes=("#", ";"),
|
||||||
|
)
|
||||||
|
# Keep mask/address case verbatim; query names are lowercased before lookup.
|
||||||
|
parser.optionxform = str
|
||||||
|
parser.read(path)
|
||||||
|
|
||||||
|
general = parser["general"] if parser.has_section("general") else {}
|
||||||
|
redirect_url = general.get("redirect_url") or DEFAULT_REDIRECT_URL
|
||||||
|
allowed_raw = general.get("allowed_clients") or DEFAULT_ALLOWED_CLIENTS
|
||||||
|
allowed_clients = tuple(
|
||||||
|
ipaddress.ip_network(entry, strict=False) for entry in _split(allowed_raw)
|
||||||
|
)
|
||||||
|
|
||||||
|
upstream = parser["upstream"] if parser.has_section("upstream") else {}
|
||||||
|
servers_raw = (
|
||||||
|
upstream.get("server") or upstream.get("servers") or DEFAULT_UPSTREAMS
|
||||||
|
)
|
||||||
|
upstreams = tuple(
|
||||||
|
str(ipaddress.ip_address(server)) for server in _split(servers_raw)
|
||||||
|
)
|
||||||
|
timeout = max(0.1, int(upstream.get("timeout_ms") or DEFAULT_TIMEOUT_MS) / 1000.0)
|
||||||
|
|
||||||
|
override_items = (
|
||||||
|
parser.items("overrides")
|
||||||
|
if parser.has_section("overrides")
|
||||||
|
else DEFAULT_OVERRIDES
|
||||||
|
)
|
||||||
|
overrides = tuple(
|
||||||
|
(mask.lower(), ipaddress.IPv4Address(address))
|
||||||
|
for mask, address in override_items
|
||||||
|
if address is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
exception_items = (
|
||||||
|
parser.items("exceptions")
|
||||||
|
if parser.has_section("exceptions")
|
||||||
|
else ((mask, None) for mask in DEFAULT_EXCEPTIONS)
|
||||||
|
)
|
||||||
|
exceptions = tuple(mask.lower() for mask, _ in exception_items)
|
||||||
|
|
||||||
|
return redirect_url, allowed_clients, upstreams, timeout, overrides, exceptions
|
||||||
|
|
||||||
|
|
||||||
|
(
|
||||||
|
REDIRECT_URL,
|
||||||
|
ALLOWED_CLIENTS,
|
||||||
|
UPSTREAMS,
|
||||||
|
UPSTREAM_TIMEOUT,
|
||||||
|
OVERRIDES,
|
||||||
|
EXCEPTIONS,
|
||||||
|
) = load_config(CONFIG_FILE)
|
||||||
|
|
||||||
|
|
||||||
def client_allowed(address):
|
def client_allowed(address):
|
||||||
if not ALLOWED_CLIENTS:
|
if not ALLOWED_CLIENTS:
|
||||||
@@ -52,6 +112,17 @@ def client_allowed(address):
|
|||||||
return any(client in network for network in ALLOWED_CLIENTS)
|
return any(client in network for network in ALLOWED_CLIENTS)
|
||||||
|
|
||||||
|
|
||||||
|
def match_any(name, masks):
|
||||||
|
return any(fnmatch.fnmatchcase(name, mask) for mask in masks)
|
||||||
|
|
||||||
|
|
||||||
|
def override_for(name):
|
||||||
|
for mask, address in OVERRIDES:
|
||||||
|
if fnmatch.fnmatchcase(name, mask):
|
||||||
|
return address
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def parse_question(packet):
|
def parse_question(packet):
|
||||||
if len(packet) < 17:
|
if len(packet) < 17:
|
||||||
raise ValueError("short DNS query")
|
raise ValueError("short DNS query")
|
||||||
@@ -104,12 +175,12 @@ def receive_exactly(connection, wanted):
|
|||||||
return b"".join(chunks)
|
return b"".join(chunks)
|
||||||
|
|
||||||
|
|
||||||
def forward_query(packet, use_tcp=False):
|
def _forward_one(packet, server, use_tcp):
|
||||||
family = socket.AF_INET6 if ":" in UPSTREAM_DNS else socket.AF_INET
|
family = socket.AF_INET6 if ":" in server else socket.AF_INET
|
||||||
socket_type = socket.SOCK_STREAM if use_tcp else socket.SOCK_DGRAM
|
socket_type = socket.SOCK_STREAM if use_tcp else socket.SOCK_DGRAM
|
||||||
with socket.socket(family, socket_type) as upstream:
|
with socket.socket(family, socket_type) as upstream:
|
||||||
upstream.settimeout(UPSTREAM_TIMEOUT)
|
upstream.settimeout(UPSTREAM_TIMEOUT)
|
||||||
upstream.connect((UPSTREAM_DNS, 53))
|
upstream.connect((server, 53))
|
||||||
if use_tcp:
|
if use_tcp:
|
||||||
upstream.sendall(struct.pack("!H", len(packet)) + packet)
|
upstream.sendall(struct.pack("!H", len(packet)) + packet)
|
||||||
response_length = struct.unpack("!H", receive_exactly(upstream, 2))[0]
|
response_length = struct.unpack("!H", receive_exactly(upstream, 2))[0]
|
||||||
@@ -123,6 +194,16 @@ def forward_query(packet, use_tcp=False):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def forward_query(packet, use_tcp=False):
|
||||||
|
last_error = OSError("no upstream resolver configured")
|
||||||
|
for server in UPSTREAMS:
|
||||||
|
try:
|
||||||
|
return _forward_one(packet, server, use_tcp)
|
||||||
|
except (OSError, ValueError) as error:
|
||||||
|
last_error = error
|
||||||
|
raise last_error
|
||||||
|
|
||||||
|
|
||||||
def dns_response(packet, use_tcp=False):
|
def dns_response(packet, use_tcp=False):
|
||||||
if len(packet) < 12:
|
if len(packet) < 12:
|
||||||
return b""
|
return b""
|
||||||
@@ -137,21 +218,25 @@ def dns_response(packet, use_tcp=False):
|
|||||||
return struct.pack("!HHHHHH", transaction_id, response_flags | 1, 0, 0, 0, 0)
|
return struct.pack("!HHHHHH", transaction_id, response_flags | 1, 0, 0, 0, 0)
|
||||||
|
|
||||||
question = packet[12:question_end]
|
question = packet[12:question_end]
|
||||||
if name in FORWARDED_DOMAINS:
|
|
||||||
|
# Exceptions bypass overrides and are forwarded upstream (nanoDNS semantics).
|
||||||
|
if match_any(name, EXCEPTIONS):
|
||||||
try:
|
try:
|
||||||
return forward_query(packet, use_tcp=use_tcp)
|
return forward_query(packet, use_tcp=use_tcp)
|
||||||
except (OSError, ValueError) as error:
|
except (OSError, ValueError) as error:
|
||||||
print(f"DNS upstream failure for {name}: {error}", flush=True)
|
print(f"DNS upstream failure for {name}: {error}", flush=True)
|
||||||
return error_response(packet, 2) # SERVFAIL
|
return error_response(packet, 2) # SERVFAIL
|
||||||
|
|
||||||
if name != TARGET_DOMAIN:
|
address = override_for(name)
|
||||||
|
if address is None:
|
||||||
|
# Default-deny: a name matching no rule returns NXDOMAIN.
|
||||||
return struct.pack("!HHHHHH", transaction_id, response_flags | 3, 1, 0, 0, 0) + question
|
return struct.pack("!HHHHHH", transaction_id, response_flags | 3, 1, 0, 0, 0) + question
|
||||||
|
|
||||||
if qclass == 1 and qtype in (1, 255):
|
if qclass == 1 and qtype in (1, 255):
|
||||||
answer = (
|
answer = (
|
||||||
b"\xc0\x0c"
|
b"\xc0\x0c"
|
||||||
+ struct.pack("!HHIH", 1, 1, 60, 4)
|
+ struct.pack("!HHIH", 1, 1, 60, 4)
|
||||||
+ GATEWAY_IP.packed
|
+ address.packed
|
||||||
)
|
)
|
||||||
return struct.pack("!HHHHHH", transaction_id, response_flags, 1, 1, 0, 0) + question + answer
|
return struct.pack("!HHHHHH", transaction_id, response_flags, 1, 1, 0, 0) + question + answer
|
||||||
|
|
||||||
@@ -274,9 +359,13 @@ if __name__ == "__main__":
|
|||||||
serve(http, "http-redirect")
|
serve(http, "http-redirect")
|
||||||
serve(https, "https-redirect")
|
serve(https, "https-redirect")
|
||||||
|
|
||||||
|
overrides_summary = (
|
||||||
|
", ".join(f"{mask}->{address}" for mask, address in OVERRIDES) or "none"
|
||||||
|
)
|
||||||
print(
|
print(
|
||||||
f"PS5 guide gateway: {TARGET_DOMAIN} -> {GATEWAY_IP}; redirect -> {REDIRECT_URL}; "
|
"PS5 guide gateway (nanoDNS-style, default-deny): "
|
||||||
f"forward {','.join(sorted(FORWARDED_DOMAINS))} -> {UPSTREAM_DNS}; "
|
f"redirect -> {REDIRECT_URL}; overrides {overrides_summary}; "
|
||||||
|
f"forward {','.join(EXCEPTIONS) or 'none'} -> {','.join(UPSTREAMS) or 'none'}; "
|
||||||
f"clients {','.join(str(network) for network in ALLOWED_CLIENTS) or 'any'}",
|
f"clients {','.join(str(network) for network in ALLOWED_CLIENTS) or 'any'}",
|
||||||
flush=True,
|
flush=True,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# PS5 User Guide DNS gateway -- nanoDNS-style policy.
|
||||||
|
#
|
||||||
|
# Config model adapted from https://github.com/drakmor/nanoDNS#config, but the
|
||||||
|
# unmatched-name behaviour is deliberately inverted to default-DENY.
|
||||||
|
#
|
||||||
|
# Resolution order for each query:
|
||||||
|
# 1. the source address must fall inside [general] allowed_clients, otherwise
|
||||||
|
# the query is dropped and logged as "denied";
|
||||||
|
# 2. a name matching an [exceptions] mask is forwarded to [upstream];
|
||||||
|
# 3. otherwise a name matching an [overrides] mask is answered with its IPv4;
|
||||||
|
# 4. otherwise NXDOMAIN. Unlike upstream nanoDNS, names that match no rule are
|
||||||
|
# NOT forwarded, which keeps PSN, updates and telemetry blocked by default.
|
||||||
|
#
|
||||||
|
# Masks use shell-style wildcards: * ? [abc] [a-z] [!0-9]
|
||||||
|
# Names are matched case-insensitively.
|
||||||
|
|
||||||
|
[general]
|
||||||
|
redirect_url = http://192.168.20.30:8000/
|
||||||
|
# Source networks allowed to query this resolver (comma/space/newline separated).
|
||||||
|
# Consoles VLAN /28, server VLAN /24, trusted management VLAN /27.
|
||||||
|
allowed_clients = 192.168.40.0/28, 192.168.20.0/24, 192.168.10.0/27
|
||||||
|
|
||||||
|
[upstream]
|
||||||
|
# Resolvers tried in order; the first valid answer wins. Several may be listed,
|
||||||
|
# separated by commas, spaces or newlines. Must be IP addresses, not hostnames.
|
||||||
|
server = 1.1.1.1
|
||||||
|
timeout_ms = 3000
|
||||||
|
|
||||||
|
[overrides]
|
||||||
|
# mask = IPv4 -- matched names are answered with this address (A records).
|
||||||
|
manuals.playstation.net = 192.168.20.30
|
||||||
|
|
||||||
|
[exceptions]
|
||||||
|
# One mask per line (no address) -- matched names are forwarded upstream. These
|
||||||
|
# are the reviewed homebrew-store, PS5 Payload Manager, Spectrum Library and
|
||||||
|
# BlackBox endpoints. Everything not listed here or in [overrides] is NXDOMAIN.
|
||||||
|
homebrew.page
|
||||||
|
itsplk.github.io
|
||||||
|
store.spectrumlibrary.online
|
||||||
|
api.github.com
|
||||||
|
github.com
|
||||||
|
raw.githubusercontent.com
|
||||||
|
release-assets.githubusercontent.com
|
||||||
|
prosperopatches.com
|
||||||
|
cdn.prosperopatches.com
|
||||||
|
image.api.playstation.com
|
||||||
|
gmedia.playstation.com
|
||||||
+35
-19
@@ -182,26 +182,42 @@ the local container. The local gateway was therefore added. Configure the PS5 wi
|
|||||||
primary DNS `192.168.20.30` and secondary DNS `0.0.0.0`, then restart the console and
|
primary DNS `192.168.20.30` and secondary DNS `0.0.0.0`, then restart the console and
|
||||||
accept the self-signed certificate warning in the User Guide.
|
accept the self-signed certificate warning in the User Guide.
|
||||||
|
|
||||||
The gateway provides allowlisted recursive DNS forwarding only for `homebrew.page`,
|
The gateway's DNS policy follows the nanoDNS config model
|
||||||
`itsplk.github.io`, `store.spectrumlibrary.online`, `api.github.com`, `github.com`
|
(<https://github.com/drakmor/nanoDNS#config>), with the unmatched-name behaviour
|
||||||
`raw.githubusercontent.com`, `release-assets.githubusercontent.com`,
|
deliberately inverted to default-deny. The policy lives in `docker/nanodns.ini`,
|
||||||
`prosperopatches.com`, `cdn.prosperopatches.com`, `image.api.playstation.com` and
|
baked into the image, with these sections:
|
||||||
`gmedia.playstation.com`. Only those names and
|
|
||||||
`manuals.playstation.net` resolve; all other names receive NXDOMAIN. This permits
|
|
||||||
the reviewed fixed endpoints of the homebrew store, PS5 Payload Manager, Spectrum
|
|
||||||
Library and BlackBox while continuing to block PSN and update resolution. The PS5
|
|
||||||
sends DNS only to `net-utils01`; that host must be allowed to reach the configured
|
|
||||||
upstream resolver on UDP/TCP 53.
|
|
||||||
|
|
||||||
The gateway additionally restricts which source clients it answers. `ALLOWED_CLIENTS`
|
- `[general]` — `redirect_url` and `allowed_clients`.
|
||||||
defaults to the console VLAN `192.168.40.0/28`, the server VLAN `192.168.20.0/24`,
|
- `[upstream]` — one or more resolver IPs (`server`, comma/space/newline
|
||||||
and the trusted management VLAN `192.168.10.0/27`; queries from any other source
|
separated, tried in order) and a `timeout_ms` budget.
|
||||||
address are dropped and logged as `denied`, on both UDP and TCP. The server and
|
- `[overrides]` — `mask = IPv4`; matched names are answered with that A record.
|
||||||
trusted VLANs are included so operators can test and administer the resolver; the
|
`manuals.playstation.net` maps to `192.168.20.30`.
|
||||||
consoles themselves only need the `/28`. The value accepts a comma-separated mix of
|
- `[exceptions]` — one mask per line; matched names are forwarded upstream. These
|
||||||
individual addresses and CIDR ranges, and an empty value disables the filter. This
|
are the reviewed homebrew-store, PS5 Payload Manager, Spectrum Library and
|
||||||
keeps the service off the rest of the LAN and prevents arbitrary hosts from using it
|
BlackBox endpoints (`homebrew.page`, `itsplk.github.io`,
|
||||||
as an open resolver for the allowlisted names.
|
`store.spectrumlibrary.online`, `api.github.com`, `github.com`,
|
||||||
|
`raw.githubusercontent.com`, `release-assets.githubusercontent.com`,
|
||||||
|
`prosperopatches.com`, `cdn.prosperopatches.com`, `image.api.playstation.com`,
|
||||||
|
`gmedia.playstation.com`).
|
||||||
|
|
||||||
|
Masks use shell-style wildcards (`*`, `?`, `[abc]`, `[a-z]`, `[!0-9]`) and match
|
||||||
|
case-insensitively. Resolution order per query is: exceptions (forward) →
|
||||||
|
overrides (spoof) → NXDOMAIN. Unlike upstream nanoDNS, a name matching no rule is
|
||||||
|
**not** forwarded; it returns NXDOMAIN, which keeps PSN, updates and telemetry
|
||||||
|
blocked by default. The PS5 sends DNS only to `net-utils01`; that host must be
|
||||||
|
allowed to reach the configured upstream resolver on UDP/TCP 53.
|
||||||
|
|
||||||
|
The gateway additionally restricts which source clients it answers, via
|
||||||
|
`[general] allowed_clients`. It defaults to the console VLAN `192.168.40.0/28`,
|
||||||
|
the server VLAN `192.168.20.0/24`, and the trusted management VLAN
|
||||||
|
`192.168.10.0/27`; queries from any other source address are dropped and logged
|
||||||
|
as `denied`, on both UDP and TCP. The server and trusted VLANs are included so
|
||||||
|
operators can test and administer the resolver; the consoles themselves only need
|
||||||
|
the `/28`. The value accepts a comma/space/newline mix of individual addresses and
|
||||||
|
CIDR ranges, and an empty value disables the filter. This keeps the service off the
|
||||||
|
rest of the LAN and prevents arbitrary hosts from using it as an open resolver for
|
||||||
|
the allowlisted names. Editing the policy requires an image rebuild, or pointing
|
||||||
|
`CONFIG_FILE` at a mounted file.
|
||||||
|
|
||||||
The Payload Manager review used upstream commit
|
The Payload Manager review used upstream commit
|
||||||
`e4e72dc9d831dd09ee8fabc222ad1e7f5139050e` and the official catalog current on
|
`e4e72dc9d831dd09ee8fabc222ad1e7f5139050e` and the official catalog current on
|
||||||
|
|||||||
+26
-13
@@ -154,6 +154,17 @@ O Compose inclui um DNS controlado no `net-utils01` que:
|
|||||||
- apresente um gateway HTTPS local e redirecione o browser para o exploit em
|
- apresente um gateway HTTPS local e redirecione o browser para o exploit em
|
||||||
`http://192.168.20.30:8000/`.
|
`http://192.168.20.30:8000/`.
|
||||||
|
|
||||||
|
A política de DNS segue o modelo de configuração do nanoDNS
|
||||||
|
(<https://github.com/drakmor/nanoDNS#config>), mas com o comportamento para nomes
|
||||||
|
não correspondidos invertido para **default-deny** (NXDOMAIN em vez de reencaminhar).
|
||||||
|
Vive no ficheiro `docker/nanodns.ini`, incluído na imagem, com as secções
|
||||||
|
`[general]` (`redirect_url`, `allowed_clients`), `[upstream]` (`server`,
|
||||||
|
`timeout_ms`), `[overrides]` (`máscara = IPv4`, faz spoof) e `[exceptions]` (uma
|
||||||
|
máscara por linha, reencaminha para o upstream). As máscaras aceitam wildcards
|
||||||
|
shell (`*`, `?`, `[abc]`, `[a-z]`, `[!0-9]`) e a ordem de resolução é
|
||||||
|
exceptions → overrides → NXDOMAIN. Para alterar a política, editar o INI e
|
||||||
|
reconstruir a imagem (ou apontar `CONFIG_FILE` para um ficheiro montado).
|
||||||
|
|
||||||
Nesse cenário, a PS5 usaria:
|
Nesse cenário, a PS5 usaria:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -171,20 +182,22 @@ definir `GATEWAY_BIND_IP`, `GATEWAY_IP` e `GUIDE_REDIRECT_URL` antes de executar
|
|||||||
Compose. Também é possível bloquear o acesso WAN da PS5 no router, mantendo apenas
|
Compose. Também é possível bloquear o acesso WAN da PS5 no router, mantendo apenas
|
||||||
a comunicação LAN; esta continua a ser uma camada adicional mais forte do que DNS.
|
a comunicação LAN; esta continua a ser uma camada adicional mais forte do que DNS.
|
||||||
|
|
||||||
O resolvedor upstream predefinido é `1.1.1.1`. Pode ser substituído por um endereço
|
O resolvedor upstream predefinido é `1.1.1.1`, definido em `[upstream] server` no
|
||||||
DNS controlado na rede através de `UPSTREAM_DNS`; o valor deve ser um endereço IP,
|
`docker/nanodns.ini`. Podem listar-se vários (separados por vírgulas/espaços/linhas),
|
||||||
não um hostname. A firewall deve permitir UDP e TCP `53` de `net-utils01` para esse
|
tentados por ordem; os valores devem ser endereços IP, não hostnames. A firewall deve
|
||||||
resolvedor. A consola continua a precisar apenas de UDP/TCP `53` para
|
permitir UDP e TCP `53` de `net-utils01` para esse resolvedor. A consola continua a
|
||||||
`192.168.20.30`; não deve consultar diretamente um DNS público.
|
precisar apenas de UDP/TCP `53` para `192.168.20.30`; não deve consultar diretamente
|
||||||
|
um DNS público.
|
||||||
|
|
||||||
O gateway só responde a consultas DNS vindas das redes listadas em `ALLOWED_CLIENTS`.
|
O gateway só responde a consultas DNS vindas das redes listadas em
|
||||||
Por defeito inclui a VLAN das consolas `192.168.40.0/28`, a VLAN de servers
|
`[general] allowed_clients`. Por defeito inclui a VLAN das consolas
|
||||||
`192.168.20.0/24` e a VLAN de gestão/confiança `192.168.10.0/27` (estas duas últimas
|
`192.168.40.0/28`, a VLAN de servers `192.168.20.0/24` e a VLAN de gestão/confiança
|
||||||
para o operador poder testar e administrar). O valor aceita uma lista separada por
|
`192.168.10.0/27` (estas duas últimas para o operador poder testar e administrar). O
|
||||||
vírgulas de endereços individuais e/ou intervalos CIDR — por exemplo,
|
valor aceita uma lista separada por vírgulas/espaços de endereços individuais e/ou
|
||||||
`192.168.40.13,192.168.40.14` para trancar apenas às duas PS5 autorizadas (PS5FAT e
|
intervalos CIDR — por exemplo, `192.168.40.13 192.168.40.14` para trancar apenas às
|
||||||
PS5PRO). Um valor vazio desativa o filtro e serve todos os clientes. Consultas de
|
duas PS5 autorizadas (PS5FAT e PS5PRO). Um valor vazio desativa o filtro e serve todos
|
||||||
origens não permitidas são descartadas e registadas como `denied`.
|
os clientes. Consultas de origens não permitidas são descartadas e registadas como
|
||||||
|
`denied`.
|
||||||
|
|
||||||
O acesso WAN da consola pode ficar limitado a TCP `443` para os domínios
|
O acesso WAN da consola pode ficar limitado a TCP `443` para os domínios
|
||||||
permitidos. Não são necessárias regras WAN de entrada. Se faltarem ícones, consultar
|
permitidos. Não são necessárias regras WAN de entrada. Se faltarem ícones, consultar
|
||||||
|
|||||||
Reference in New Issue
Block a user