| name | fabric-vpn |
| description | Set up Tailscale mesh VPN and SSH tunnels for secure access to FABRIC nodes |
| allowed-tools | ["Read","Grep","Glob","Write","Edit","Bash"] |
Instructions
When invoked, generate code for VPN and tunneling on FABRIC. Two main capabilities:
- Tailscale — Zero-trust mesh VPN for direct access to FABRIC nodes from your laptop
- SSH Tunnels — Port forwarding through FABRIC's bastion host for accessing services
Help the user choose the right approach and generate complete setup code.
Patterns
Tailscale Mesh VPN
from fabrictestbed_extensions.fablib.fablib import FablibManager
fablib = FablibManager()
slice = fablib.new_slice(name="tailscale-experiment")
node1 = slice.add_node(name="node1", site="TACC", cores=4, ram=16, disk=50)
node1.add_fabnet()
node2 = slice.add_node(name="node2", site="UCSD", cores=4, ram=16, disk=50)
node2.add_fabnet()
slice.submit()
ts_key = "tskey-auth-XXXXXXXX"
for node in slice.get_nodes():
node.execute(
f"curl -fsSL https://tailscale.com/install.sh | sh && "
f"sudo tailscale up --authkey {ts_key}"
)
for node in slice.get_nodes():
stdout, _ = node.execute("tailscale status")
print(f"{node.get_name()}:\n{stdout}")
node1 = slice.get_node(name="node1")
node1.execute(
"docker run -d --name filebrowser -p 8989:8989 "
f"-v /home/{node1.get_username()}:/data "
"-e FB_ROOT=/data -e FB_PORT=8989 -e FB_NOAUTH=noauth "
"filebrowser/filebrowser"
)
for node in slice.get_nodes():
node.execute("sudo tailscale logout")
slice.delete()
SSH Tunnels for Service Access
slice = fablib.new_slice(name="tunnel-experiment")
node = slice.add_node(
name="service-node",
site=fablib.get_random_site(),
cores=4,
ram=16,
disk=100,
image="docker_rocky_8",
)
node.add_post_boot_upload_directory('node_tools', '.')
node.add_post_boot_execute('node_tools/enable_docker.sh {{ _self_.image }}')
slice.submit()
node = slice.get_node(name="service-node")
node.execute(
"docker run -d --name filebrowser -p 127.0.0.1:5555:5555 "
f"-v /home/{node.get_username()}/data:/data "
"-e FB_BASEURL=/filebrowser -e FB_ROOT=/data "
"-e FB_PORT=5555 -e FB_NOAUTH=noauth "
"filebrowser/filebrowser"
)
fablib.create_ssh_tunnel_config(overwrite=True)
import os
local_port = "5555"
target_host = f"{node.get_username()}@{node.get_management_ip()}"
key_file = os.path.basename(fablib.get_default_slice_public_key_file())[:-4]
print(
f"ssh -L 127.0.0.1:{local_port}:127.0.0.1:{local_port} "
f"-i {key_file} -F ssh_config {target_host}"
)
print(f"\nThen browse: http://127.0.0.1:{local_port}/filebrowser")
SSH Tunnel to Jupyter/Grafana
node = slice.get_node(name="monitor-node")
mgmt_ip = node.get_management_ip()
username = node.get_username()
service_port = "3000"
print(f"Run on your laptop:")
print(
f"ssh -L 127.0.0.1:{service_port}:127.0.0.1:{service_port} "
f"-i <slice_key> -F ssh_config {username}@{mgmt_ip}"
)
print(f"Then browse: http://127.0.0.1:{service_port}")
Multiple Tunnels
ports = {"Grafana": 3000, "Prometheus": 9090, "App": 8080}
tunnel_flags = " ".join(
f"-L 127.0.0.1:{port}:127.0.0.1:{port}" for port in ports.values()
)
print(f"ssh {tunnel_flags} -i <key> -F ssh_config {username}@{mgmt_ip}")
for name, port in ports.items():
print(f" {name}: http://127.0.0.1:{port}")
Important Notes
- Tailscale auth key: Generate at https://login.tailscale.com/admin/settings/keys
- Tailscale logout: Always run
tailscale logout before deleting slices to clean up devices
- SSH tunnels: Use
fablib.create_ssh_tunnel_config() for bastion-aware SSH config
- Bind to localhost: Always bind services to
127.0.0.1 for security, not 0.0.0.0
- ssh_config: Generated config handles FABRIC bastion host automatically