berth init
This commit is contained in:
125
berth/ctrl/lib/estate.sh
Normal file
125
berth/ctrl/lib/estate.sh
Normal file
@@ -0,0 +1,125 @@
|
||||
# Reading and projecting estate/<name>.json. Sourced, never executed.
|
||||
#
|
||||
# python3 rather than jq: berth's floor already includes python3, so it is a
|
||||
# dependency berth has rather than one it adds.
|
||||
|
||||
estate_get() {
|
||||
python3 -c '
|
||||
import json, sys
|
||||
d = json.load(open(sys.argv[1]))
|
||||
for k in sys.argv[2].split("."):
|
||||
if isinstance(d, list):
|
||||
try: k = int(k)
|
||||
except ValueError: sys.exit(0)
|
||||
try: d = d[k]
|
||||
except Exception: sys.exit(0)
|
||||
print("" if d is None else d if isinstance(d, str) else json.dumps(d))
|
||||
' "$ESTATE_FILE" "$1"
|
||||
}
|
||||
|
||||
# Services in scope for one target. A service names its targets; absent = all.
|
||||
# Fields are US-separated (0x1f), not tab: tab is IFS whitespace, so bash
|
||||
# collapses a run of them and an empty field would shift every later column.
|
||||
estate_services() {
|
||||
local target="${1:-$TARGET}"
|
||||
python3 -c '
|
||||
import json, sys
|
||||
d = json.load(open(sys.argv[1]))
|
||||
target = sys.argv[2]
|
||||
for s in d.get("services", []):
|
||||
tg = s.get("targets")
|
||||
if tg is not None and target not in tg:
|
||||
continue
|
||||
print("\x1f".join([
|
||||
s.get("name", ""),
|
||||
s.get("host", ""),
|
||||
str(s.get(target + "_upstream", s.get("upstream", "")) or ""),
|
||||
s.get("kind", "proxy"),
|
||||
"raw" if s.get("raw") else "",
|
||||
s.get("placement", "box"),
|
||||
s.get("peer", ""),
|
||||
str(s.get("port", "") or ""),
|
||||
s.get("local_host", s.get("host", "")),
|
||||
]))
|
||||
' "$ESTATE_FILE" "$target"
|
||||
}
|
||||
|
||||
# The SAN list the services need, derived — never a literal list.
|
||||
estate_sans() {
|
||||
python3 -c '
|
||||
import json, sys
|
||||
d = json.load(open(sys.argv[1]))
|
||||
domain = d["domain"]
|
||||
sans = [domain]
|
||||
depths = set()
|
||||
for s in d.get("services", []):
|
||||
h = s.get("host", "")
|
||||
if not h:
|
||||
continue
|
||||
# A wildcard matches exactly ONE label. "git" needs *.domain; "dlt.spr"
|
||||
# needs *.spr.domain. The parent of the leaf is what has to be covered.
|
||||
parent = h.split(".", 1)[1] if "." in h else ""
|
||||
depths.add(parent)
|
||||
for p in sorted(depths):
|
||||
sans.append("*." + (p + "." if p else "") + domain)
|
||||
for s in sans:
|
||||
print(s)
|
||||
' "$ESTATE_FILE"
|
||||
}
|
||||
|
||||
# Is <fqdn> covered by <san>? A wildcard matches exactly one label.
|
||||
san_covers() {
|
||||
local fqdn="$1" san="$2"
|
||||
[ "$fqdn" = "$san" ] && return 0
|
||||
case "$san" in
|
||||
\*.*)
|
||||
local suffix="${san#\*.}"
|
||||
# Must end in .suffix AND have exactly one extra label.
|
||||
case "$fqdn" in
|
||||
*".$suffix") [ "${fqdn%".$suffix"}" = "${fqdn%%.*}" ] && return 0 ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
# ── the overlay ────────────────────────────────────────────────────────────
|
||||
|
||||
# Every overlay name, one per line.
|
||||
overlay_names() {
|
||||
python3 -c '
|
||||
import json, sys
|
||||
d = json.load(open(sys.argv[1]))
|
||||
for n in d.get("vpn", {}).get("overlays", {}):
|
||||
print(n)
|
||||
' "$ESTATE_FILE"
|
||||
}
|
||||
|
||||
# Peers of one overlay, US-separated:
|
||||
# name, address, role, endpoint, public_key, allowed_ips, keepalive
|
||||
overlay_peers() {
|
||||
python3 -c '
|
||||
import json, sys
|
||||
d = json.load(open(sys.argv[1]))
|
||||
ov = d.get("vpn", {}).get("overlays", {}).get(sys.argv[2], {})
|
||||
for name, p in ov.get("peers", {}).items():
|
||||
print("\x1f".join(str(x) if x is not None else "" for x in [
|
||||
name, p.get("address"), p.get("role"), p.get("endpoint"),
|
||||
p.get("public_key"), p.get("allowed_ips"), p.get("keepalive"),
|
||||
]))
|
||||
' "$ESTATE_FILE" "$1"
|
||||
}
|
||||
|
||||
overlay_get() { estate_get "vpn.overlays.$1.$2"; }
|
||||
|
||||
# Is an address inside a CIDR? Pure python so there is no ipcalc dependency —
|
||||
# berth's floor already includes python3 because the IaC side needs it.
|
||||
addr_in_subnet() {
|
||||
python3 -c '
|
||||
import ipaddress, sys
|
||||
try:
|
||||
sys.exit(0 if ipaddress.ip_address(sys.argv[1]) in ipaddress.ip_network(sys.argv[2], strict=False) else 1)
|
||||
except ValueError:
|
||||
sys.exit(2)
|
||||
' "$1" "$2"
|
||||
}
|
||||
Reference in New Issue
Block a user