Current section

Files

Jump to
livebook lib livebook zta tailscale.ex
Raw

lib/livebook/zta/tailscale.ex

defmodule Livebook.ZTA.Tailscale do
@behaviour Livebook.ZTA
require Logger
@impl true
def child_spec(opts) do
%{id: __MODULE__, start: {__MODULE__, :start_link, [opts]}}
end
def start_link(opts) do
name = Keyword.fetch!(opts, :name)
address = Keyword.fetch!(opts, :identity_key)
if not String.starts_with?(address, "http") and
not File.exists?(address) do
Logger.error("Tailscale socket does not exist: #{inspect(address)}")
raise "invalid Tailscale ZTA configuration"
end
Livebook.ZTA.put(name, address)
:ignore
end
@impl true
def authenticate(name, conn, _opts) do
remote_ip = to_string(:inet_parse.ntoa(conn.remote_ip))
tailscale_address = Livebook.ZTA.get(name)
user = authenticate_ip(remote_ip, tailscale_address)
{conn, user}
end
defp authenticate_ip(remote_ip, address) do
{url, options} =
if String.starts_with?(address, "http") do
uri = URI.parse(address)
options =
if uri.userinfo do
# Req does not handle userinfo as part of the URL
[auth: "Basic #{Base.encode64(uri.userinfo)}"]
else
[]
end
url = to_string(%{uri | userinfo: nil, path: "/localapi/v0/whois?addr=#{remote_ip}:1"})
{url, options}
else
{
"http://local-tailscaled.sock/localapi/v0/whois?addr=#{remote_ip}:1",
[
unix_socket: address,
# Req or Finch do not pass on the host from the URL when using a unix socket,
# so we set the host header explicitly
headers: [host: "local-tailscaled.sock"]
]
}
end
with {:ok, response} <- Req.get(url, options),
200 <- response.status,
%{"UserProfile" => user} <- response.body do
%{
id: to_string(user["ID"]),
name: user["DisplayName"],
email: user["LoginName"]
}
else
_ -> nil
end
end
end