berth updates
This commit is contained in:
@@ -162,6 +162,9 @@ PY
|
||||
echo "bindings — services reached over the overlay must bind a reachable address"
|
||||
local checked=0
|
||||
while IFS=$'\x1f' read -r name host up kind raw placement peer port lhost; do
|
||||
# Resolve placement first: a placed service's upstream is derived, not
|
||||
# literal, so reading `up` alone skips it and this invariant goes quiet.
|
||||
up="$(service_upstream "$up" "$placement" "$peer" "$port")" || true
|
||||
[ -z "$up" ] && continue
|
||||
local uhost="${up%%:*}" uport="${up##*:}"
|
||||
addr_in_subnet "$uhost" "$(overlay_get estate subnet)" 2>/dev/null || continue
|
||||
@@ -256,8 +259,16 @@ render() {
|
||||
# because the keys are what is missing. A roaming peer's endpoint is a home
|
||||
# address and has no stable value — dropped in the parser, not just unused.
|
||||
capture() {
|
||||
local write=""
|
||||
for a in "$@"; do [ "$a" = "--write" ] && write=1; done
|
||||
local write="" as_peer=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--write) write=1 ;;
|
||||
--as) shift; as_peer="${1:-}"
|
||||
[ -z "$as_peer" ] && { echo "--as needs a peer name" >&2; exit 1; } ;;
|
||||
*) echo "capture: unknown argument '$1'" >&2; exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
local input; input=$(cat)
|
||||
if [ -z "$input" ]; then
|
||||
@@ -278,8 +289,8 @@ capture() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
WRITE="$write" INPUT="$input" python3 - "$ESTATE_FILE" <<'PYCAP'
|
||||
import collections, json, os, re, sys
|
||||
WRITE="$write" AS_PEER="$as_peer" INPUT="$input" python3 - "$ESTATE_FILE" <<'PYCAP'
|
||||
import collections, ipaddress, json, os, re, sys
|
||||
|
||||
text = os.environ["INPUT"]
|
||||
write = os.environ.get("WRITE") == "1"
|
||||
@@ -311,13 +322,28 @@ ov = d["vpn"]["overlays"]["estate"]
|
||||
|
||||
# address -> peer name, from what the estate already declares
|
||||
by_addr = {p["address"]: n for n, p in ov["peers"].items() if p.get("address")}
|
||||
# this machine's own wg address, so the interface block lands on the right peer
|
||||
me = None
|
||||
for n, p in ov["peers"].items():
|
||||
if p.get("address") and os.popen(
|
||||
by_key = {p["public_key"]: n for n, p in ov["peers"].items() if p.get("public_key")}
|
||||
subnet = ipaddress.ip_network(ov["subnet"]) if ov.get("subnet") else None
|
||||
hubs = [n for n, p in ov["peers"].items() if p.get("role") == "hub"]
|
||||
|
||||
# Whose interface block is this? `--as` names it explicitly, and that is the only
|
||||
# thing that works for output captured over ssh: the addresses on THIS machine
|
||||
# say nothing about the machine the output came from.
|
||||
as_peer = os.environ.get("AS_PEER") or ""
|
||||
if as_peer:
|
||||
if as_peer not in ov["peers"]:
|
||||
print("no peer named %r in this overlay. known: %s"
|
||||
% (as_peer, ", ".join(ov["peers"])))
|
||||
raise SystemExit(1)
|
||||
me = as_peer
|
||||
else:
|
||||
me = None
|
||||
local = os.popen(
|
||||
"ip -4 -o addr show 2>/dev/null | awk '{split($4,a,\"/\"); print a[1]}'"
|
||||
).read().split().count(p["address"]):
|
||||
me = n
|
||||
).read().split()
|
||||
for n, p in ov["peers"].items():
|
||||
if p.get("address") and p["address"] in local:
|
||||
me = n
|
||||
|
||||
changes = []
|
||||
conflicts = []
|
||||
@@ -339,13 +365,33 @@ def setf(peer, field, val, why=""):
|
||||
if me and iface.get("public_key"):
|
||||
setf(me, "public_key", iface["public_key"], "(this machine's interface)")
|
||||
|
||||
def match(pr):
|
||||
# 1. The public key IS the identity. Use it whenever the estate knows it.
|
||||
n = by_key.get(pr["public_key"])
|
||||
if n:
|
||||
return n
|
||||
nets = [a.strip() for a in pr.get("allowed_ips", "").split(",") if a.strip()]
|
||||
# 2. An allowed-ip that is a declared peer address — the ordinary spoke case.
|
||||
for a in nets:
|
||||
if a.split("/")[0] in by_addr:
|
||||
return by_addr[a.split("/")[0]]
|
||||
# 3. A peer routing the WHOLE overlay is the hub seen from a spoke. Its
|
||||
# allowed_ips is the subnet itself, so no single address ever matches it.
|
||||
if subnet and len(hubs) == 1:
|
||||
for a in nets:
|
||||
try:
|
||||
if ipaddress.ip_network(a, strict=False).supernet_of(subnet):
|
||||
return hubs[0]
|
||||
except ValueError:
|
||||
continue
|
||||
return None
|
||||
|
||||
for pr in peers:
|
||||
addrs = [a.split("/")[0] for a in pr.get("allowed_ips", "").split(",") if a.strip()]
|
||||
name = next((by_addr[a] for a in addrs if a in by_addr), None)
|
||||
name = match(pr)
|
||||
if not name:
|
||||
changes.append(("?", "UNMATCHED", None,
|
||||
"allowed_ips=%s key=%s" % (pr.get("allowed_ips"), pr["public_key"][:12] + "..."),
|
||||
"no estate peer has this address"))
|
||||
"no estate peer has this key, this address, or this route"))
|
||||
continue
|
||||
setf(name, "public_key", pr.get("public_key"))
|
||||
setf(name, "allowed_ips", pr.get("allowed_ips"))
|
||||
@@ -359,14 +405,21 @@ for pr in peers:
|
||||
else:
|
||||
setf(name, "endpoint", pr["endpoint"])
|
||||
|
||||
if iface.get("listen_port"):
|
||||
if me and iface.get("listen_port"):
|
||||
try:
|
||||
lp = int(iface["listen_port"])
|
||||
if ov.get("listen_port") != lp:
|
||||
changes.append(("(overlay)", "listen_port", ov.get("listen_port"), lp, ""))
|
||||
if write: ov["listen_port"] = lp
|
||||
except ValueError:
|
||||
pass
|
||||
lp = None
|
||||
if lp is not None:
|
||||
# A listen port belongs to the PEER, not to the overlay. A roaming peer's
|
||||
# is an ephemeral source port chosen by the kernel; writing it to the
|
||||
# overlay would rename the port the firewall rule is checked against.
|
||||
setf(me, "listen_port", lp)
|
||||
if ov["peers"][me].get("role") == "hub" and ov.get("listen_port") != lp:
|
||||
changes.append(("(overlay)", "listen_port", ov.get("listen_port"), lp,
|
||||
"the hub's port is the overlay's port"))
|
||||
if write:
|
||||
ov["listen_port"] = lp
|
||||
|
||||
if conflicts:
|
||||
print("REFUSING: the same field was reported twice with different values.\n")
|
||||
@@ -421,5 +474,5 @@ case "${1:-list}" in
|
||||
keygen) shift; keygen "${1:-}" ;;
|
||||
capture) shift; capture "$@" ;;
|
||||
up|down) v="$1"; shift; refuse "$v" "$@" ;;
|
||||
*) echo "usage: $0 [list|show <ov>|check|render <peer>|keygen <peer>|capture [--write]|up --yes|down --yes]" >&2; exit 1 ;;
|
||||
*) echo "usage: $0 [list|show <ov>|check|render <peer>|keygen <peer>|capture [--as <peer>] [--write]|up --yes|down --yes]" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user