gateway-domain: the backend needs tag:notplants-test-server
The tailnet ACL only permits the gateway to open connections to nodes tagged tag:notplants-test-server. Map an untagged node and the gateway accepts the mapping and then never connects — no error anywhere, and it presents as a broken gateway rather than as a missing tag on your own box. Nothing said so, and it is not discoverable from the failure. The skill now leads with the requirement and the one-liner to check it, and names it as the first thing to look at when traffic does not flow. The tool also warns when the node it is about to map does not carry the tag. It cannot check a backend given explicitly on the command line — it only sees its own tags — so that case stays documented rather than enforced. Found by mapping this orchestrator box (tag:orchestrator, tag:server) as a smoke test: the mapping was written and looked entirely healthy.
This commit is contained in:
@@ -27,6 +27,7 @@ This script reads it from there itself. You should not need to handle the value.
|
||||
"""
|
||||
import argparse
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
@@ -96,6 +97,11 @@ _ERR = re.compile(r'<p style="color:red">(.*?)</p>', re.S)
|
||||
# /var/lib/tunnel-gateway/tunnel_map.conf can clear. Refuse to create one.
|
||||
_BACKEND = re.compile(r"^(\d{1,3}(?:\.\d{1,3}){3})(?::(\d{1,5}))?$")
|
||||
|
||||
# The tailnet ACL only lets the gateway open connections to nodes carrying this tag.
|
||||
# A mapping to an untagged node is accepted by the gateway and then simply never
|
||||
# connects, which looks like a gateway fault and is not one.
|
||||
REQUIRED_TAG = "tag:notplants-test-server"
|
||||
|
||||
|
||||
def _validate_backend(backend):
|
||||
m = _BACKEND.match(backend)
|
||||
@@ -125,12 +131,36 @@ def _parse_domains(body):
|
||||
return re.findall(r"<td>\s*(.*?)\s*</td>\s*<td>\s*(.*?)\s*</td>", section, re.S)
|
||||
|
||||
|
||||
def _self_tags(exe):
|
||||
"""This node's tailnet tags, or None if they cannot be determined."""
|
||||
try:
|
||||
out = subprocess.run([exe, "status", "--json"], capture_output=True, text=True, timeout=15)
|
||||
return json.loads(out.stdout).get("Self", {}).get("Tags") or []
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _warn_untagged(exe):
|
||||
tags = _self_tags(exe)
|
||||
if tags is None:
|
||||
return
|
||||
if REQUIRED_TAG not in tags:
|
||||
print(
|
||||
f"warning: this node is not tagged {REQUIRED_TAG} (tags: {', '.join(tags) or 'none'}).\n"
|
||||
" The gateway will accept the mapping but the tailnet ACL will not let it\n"
|
||||
" reach this box, so no traffic will flow. Add the tag in the Tailscale\n"
|
||||
" admin, or map a backend that already has it.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
|
||||
def _tailscale_ip():
|
||||
exe = shutil.which("tailscale") or "/run/current-system/sw/bin/tailscale"
|
||||
try:
|
||||
out = subprocess.run([exe, "ip", "-4"], capture_output=True, text=True, timeout=15)
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
sys.exit(f"could not run tailscale to detect this box's IP ({e}); pass the backend explicitly")
|
||||
_warn_untagged(exe)
|
||||
ip = out.stdout.strip().splitlines()[0].strip() if out.stdout.strip() else ""
|
||||
if not ip:
|
||||
sys.exit(
|
||||
|
||||
Reference in New Issue
Block a user