tailscale / tailscale

The easiest, most secure way to use WireGuard and 2FA.
https://tailscale.com
BSD 3-Clause "New" or "Revised" License
18.49k stars 1.42k forks source link

FR: Connection lifecycle hooks #6367

Open iamalryz opened 1 year ago

iamalryz commented 1 year ago

What are you trying to do?

Hello! First of all, thanks for such a good VPN solution.

Our team is faced with a situation when we need to do some actions after the tailscale up establishes a connection. In particular, after connection establishment hosts in one of our networks receive a route that should not be on these hosts.

At the same time, this route should be propagated on almost all other hosts of our network. I spent some time to find a feature that can exclude route propagation for particular IPs, IP ranges, or subnets. And it seems like Tailscale has no such feature.

Therefore, after the connection establishment we need to remove the unneeded route from the route table manually:

> sudo ip ro del table 52 192.168.0.0/16 

It's pretty inconvenient to do it manually every time. Moreover, if host will be rebooted we can't do that, because we lose access to the host due to this route will be present on the host after its restart.

As a workaround, we can to wrap the tailscale binary into bash script that passes CLI args transparently to the binary and if up command was called, then it will perform route cleanup.

But it's pretty inconvenient too.

How should we solve this?

It seems like this problem could be fixed by introducing such feature as hooks. A hook can be a simple command or script, that should be executed at a particular point of the connection lifecycle.

Hooks can be of several types:

Lifecycle scheme:

           pre-up              post-up            pre-down          post-down
              |                   |                  |                  |
              v                   v                  v                  v
tailscale up --- (establishment) --- tailscale down --- (interruption) --->

In addition to solving this particular problem, this feature should bring a lot of flexibility to customize end users 'scenarios.

Possilbe CLI for the hooks:

  1. Inline hooks:
    • tailscale up|down --pre-hook='{hook comand or set of commands}'
    • tailscale up|down --post-hook='{hook comand or set of commands}'
  2. Hooks from script file/directory:
    • tailscale up|down --pre-hook-path='{path to script or directory containing scripts}'
    • tailscale up|down --post-hook-path='{path to script or directory containing scripts}'

What is the impact of not solving this?

No response

Anything else?

No response

DentonGentry commented 1 year ago

I understand the points about hooks where scripts can be run at various points in establishing the VPN connection, but if I may: the root problem being solved is "one of our networks receive a route that should not be on these hosts."

Is this because you do want some of the routes being advertised, so --accept-routes=false isn't a reasonable solution?

iamalryz commented 1 year ago

Is this because you do want some of the routes being advertised, so --accept-routes=false isn't a reasonable solution?

Yes. After the connection establishment we need to propagate routes except one of them.

iamalryz commented 1 year ago

By the way, I wrote a little wrapper:

> cat /usr/bin/tailscale
#!/bin/bash

eval /usr/bin/tailscale.bin "$@"

if [ $1 = "up" ]; then
  ROUTE_TABLE_NAME="52"
  ROUTE_TO_DELETE="192.168.0.0/16"

  echo "Tailscale route table after connection establishment:"
  ip ro show table "${ROUTE_TABLE_NAME}"

  echo -e "\nDeleting route ${ROUTE_TO_DELETE} from table ${ROUTE_TABLE_NAME}"
  ip ro del table "${ROUTE_TABLE_NAME}" "${ROUTE_TO_DELETE}"  

  echo -e "\nTailscale route table after cleanup:"
  ip ro show table "${ROUTE_TABLE_NAME}"
fi

It works fine, but I missed one important thing: it seems like, in fact, connection being established by tailscaled daemon, and tailscale is just daemon control utility. As a result, this wrapper will work only if being stared from CLI. After host boot the tailscaled will establish the connection independently from this wrapper script.

Therefore, as a workaround I also need to path post start section of tailscaled systemd unit.

I think lifecycle hooks seem like good solution for this problem.

iamalryz commented 1 year ago

A little update: to make things work I added an ExecStartPost section to /usr/lib/systemd/system/tailscaled.service:

ExecStartPost=bash -c 'sleep 3 && ip ro del table 52 192.168.0.0/16'

But it's pretty ugly hack.

iamalryz commented 1 year ago

Also, I realized that routes can be propagated at the lifetime of an already established connection. So pre- and post-hooks are not enough to solve this problem completely. I think that there can be introduced one more hook that will be fired after configuration receiving.

iamalryz commented 1 year ago

Also, this particular problem can be solved with more simple feature like excluding specific routes while applying received configuration. CLI call can be like: tailscale up --accept-routes --exclude-routes=<comma-separated routes list>.

iamalryz commented 1 year ago

Hi guys! Is there any info about this FR?

DentonGentry commented 1 year ago

To set expectations: I do not expect to provide hooks to run third party code or scripts at decision points. I do not believe we could maintain such a feature without freezing the current implementation of much of the connection management handling in tailscaled.

I do expect to provide more ways to control whether to use local LAN connections instead of routes advertised from the tailnet.

iamalryz commented 1 year ago

@DentonGentry thank you for your feedback.

I do not expect to provide hooks to run third party code or scripts at decision points.

It's pretty reasonable, but what about such a feature like simple CLI option like --exclude-routes as I offered in comment 1321531745?

iamalryz commented 1 year ago

I closed this issue accidentally with previous comment. So I reopened it.

knyar commented 1 year ago

If you need to ignore a route for 192.168.0.0/16 coming from Tailscale, perhaps a reasonable workaround might be to add two shorter-prefix static routes (192.168.0.0/17 and 192.168.128.0/17) to your network configuration.