-
Notifications
You must be signed in to change notification settings - Fork 441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Tailscale ZTA module #2207
Add Tailscale ZTA module #2207
Conversation
5784ab7
to
6bb5c47
Compare
I don't know how to test the unix socket unfortunately |
6bb5c47
to
9b8420f
Compare
Hi @hkrutzer, while reviewing your PR, I noticed potential improvements in the ZTA implementations: e37b2ce Can you please align your PR accordingly? @wojtekmach, any suggestions on how to mock the socket for tests? |
lib/livebook/zta/tailscale.ex
Outdated
options = | ||
if uri.userinfo do | ||
# Req does not handle userinfo | ||
[headers: [authorization: "Basic #{Base.encode64(uri.userinfo)}"]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may be able to use the basic: uri.userinfo
option. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that a Req option? I can't find it in the Req codebase
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, it might be auth: {:basic, uri.user_info}
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
neither is supported. We can do auth: {user, pass}. In hindsight {:basic, userinfo} is not a bad idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed it to [auth: "Basic #{Base.encode64(uri.userinfo)}"]
which saves a few characters, but I think that's the best we can do right now :)
9b8420f
to
b0386f3
Compare
Btw, please remember to sign the CLA once it is ready to merge (if you agree with it, of course). :) |
Regarding testing unix socket, I think the best option is to open a real socket. I'd install Bandit for tests and: defmodule TestPlug do
end
@tag :tmp_dir
test "it works", %{tmp_dir: tmp_dir} do
socket = "#{tmp_dir}/bandit.sock"
start_supervised!({Bandit, plug: Plug, ip: {:local, socket}})
Req.get(unix_socket: socket, ...)
end alternatively we can do a low level test server. I haven't changed the following snippet to use UNIX socket but it's easy, need to update the test "it works" do
%{url: url} =
TestSocket.serve(fn socket ->
assert {:ok, "GET / HTTP/1.1\r\n" <> _} = :gen_tcp.recv(socket, 0)
body = "hello"
data = """
HTTP/1.1 200 OK
content-length: #{byte_size(body)}
#{body}
"""
:ok = :gen_tcp.send(socket, data)
end)
end
defmodule TestSocket do
def serve(fun) do
{:ok, listen_socket} = :gen_tcp.listen(0, mode: :binary, active: false)
{:ok, port} = :inet.port(listen_socket)
pid = ExUnit.Callbacks.start_supervised!({Task, fn -> accept(listen_socket, fun) end})
%{pid: pid, url: "http://localhost:#{port}"}
end
defp accept(listen_socket, fun) do
case :gen_tcp.accept(listen_socket) do
{:ok, socket} ->
fun.(socket)
:ok = :gen_tcp.close(socket)
{:error, :closed} ->
:ok
end
accept(listen_socket, fun)
end
end
|
b0386f3
to
fbfccf4
Compare
Cheers @wojtekmach went with the first option.
I hope I've done that :)
I have signed it but it wasn't obvious to me that there was one. If I knew beforehand I might not have built this, perhaps you can add it to the readme? |
Uffizzi Preview |
LGTM but CI is unhappy with setting up the unix port. Could it be a permission issue? |
I just tried on my Mac and it seems to be a path (length) issue? I get the EINVAL error there as well unless I use |
Ah yeah I've seen it recently, Erlang doesn't like a long socket path. This is a workaround that worked for me: (tmp_dir is always absolute) - socket = "#{tmp_dir}/bandit.sock")
+ socket = Path.relative_to_cwd("#{tmp_dir}/bandit.sock") |
fbfccf4
to
9a77b53
Compare
lib/livebook/zta/tailscale.ex
Outdated
To do both of these things, run | ||
|
||
```bash | ||
LIVEBOOK_IP=$(tailscale ip | head -1 | tr -d '\n') LIVEBOOK_IDENTITY_PROVIDER=tailscale:/var/run/tailscale/tailscaled.sock livebook server |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
per:
% tailscale ip --help
USAGE
ip [-1] [-4] [-6] [peer hostname or ip address]
Show Tailscale IP addresses for peer. Peer defaults to the current machine.
FLAGS
--1, --1=false
only print one IP address (default false)
--4, --4=false
only print IPv4 address (default false)
--6, --6=false
only print IPv6 address (default false)
we could pass --1
or --4
to get just one IP.
addr_info=$(lsof -n -a -c IPNExtension -F | sed -n 's/.*sameuserproof-\([[:digit:]]*-.*\).*/\1/p') | ||
port=$(echo "$addr_info" | cut -d '-' -f 1) | ||
pass=$(echo "$addr_info" | cut -d '-' -f 2) | ||
LIVEBOOK_IP=$(exec $(ps -xo comm | grep MacOS/Tailscale$) ip | head -1 | tr -d '\n') LIVEBOOK_IDENTITY_PROVIDER=tailscale:http://:[email protected]:$port livebook server |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LIVEBOOK_IP=$(exec ...)
in my local testing (against Mac App Store-installed tailscale) it was enough to set LIVEBOOK_IP
to $(tailscale ip --1)
so maybe we show that here too?
As an aside, @josevalim: would it be possible to just set LIVEBOOK_IDENTITY_PROVIDER=tailscale
and do the rest in Elixir e.g. when the app boots? I think it would be a much better UI if users don't have to copy paste a bunch of shell commands.
Similarly, perhaps we don't need to require users to set LIVEBOOK_IP
but can do that ourselves internally.
That being said I think it is totally fine to start with something really simple if verbose and improve this as more people use the feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's update the examples to use tailscale ip --1
but require those env vars to be set. I don't want to rely on lsof
for macos and the setting of LIVEBOOK_IP
happens at a very different place which I am not sure it is worth coupling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in my local testing (against Mac App Store-installed tailscale) it was enough to set LIVEBOOK_IP to $(tailscale ip --1) so maybe we show that here too?
It works if you have a Tailscale alias in your shell, I got this command here because it (should) work on all types of MacOS installs without requiring an alias to be set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good point, the CLI doesn’t get installed by default. Fine by me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or in our instructions we do the alias. I think that’s better. And if we can somehow get the port and password out of the cli with less code that’s even better. But no strong opinion.
|
||
{url, options} | ||
else | ||
# Assume address not starting with http is a Unix socket |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if the socket path is incorrect (see my comment above), the error is pretty bad:
[error] retry: got exception, will retry in 1000ms, 3 attempts left
[error] ** (Mint.TransportError) no such file or directory
[error] retry: got exception, will retry in 2000ms, 2 attempts left
[error] ** (Mint.TransportError) no such file or directory
[error] retry: got exception, will retry in 4000ms, 1 attempt left
[error] ** (Mint.TransportError) no such file or directory
** (Mint.TransportError) no such file or directory
(req 0.3.8) lib/req.ex:372: Req.get!/2
iex:3: (file)
my suggestion is to handle this particular scenario early on, perhaps something like this:
# Assume address not starting with http is a Unix socket | |
unless File.exists?(address) do | |
raise "socket doesn't exist: #{inspect(address)}" | |
end | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added!
I was using Tailscale and Livebook with this branch on two computers and accessed Livebook started in one computer from another (using Tailscale IP). I showed up as one user, the Tailscale-authenticated one, instead of two since there were two browser sessions so to speak. Is that expected? In any case @hkrutzer this is pretty awesome, thank you! |
9a77b53
to
d18f4f7
Compare
I am not sure what happens for the other identity providers, and how Livebook determines user uniqueness, I guess based on the ID field? As you are the same user it doesn't sound strange to me at least. |
RE multiple users, just to be clear I was asking in general about ZTA feature not necessarily about this one. Btw, I found out we can do:
and so we can parse this. Extracting information out of curl invocation doesn't sound super reliable but the the lsof, even though is actually what happens under the hood, looks pretty scary. Just to be clear, I think what we have right now is great but trying to figure out if we can make it easier for people. |
Each user would go through authentication provided by the proxy. In this case, it seems we are authenticating the machine Livebook is on, not the user accessing the page, right? If so, we need to document it. @wojtekmach I think asking users to install the |
It seems I was wrong. Tailscale is authenticating the user indeed. :) So it all looks good to me. Unless you create another tailscale account, it will be the same user. |
Co-authored-by: Wojtek Mach <[email protected]>
💚 💙 💜 💛 ❤️ |
@josevalim the app is the CLI, in other words they even tell us to:
so the users don't need to do anything fancy, just do the alias or a symlink. I think it's fine to do the alias in our setup instructions. (app being cli is giving me ideas but that's for another day) |
Ah, PRs to use the app alias then are very welcome :) |
Indeed the app also serves as CLI, and I can't find a page in the Tailscale docs that explains how to add it. It seems it's only documented in the Github issue I linked elsewhere. The way to add the alias is different for every method of installation (there are 3). So we could mention a user needs to have that alias, or link to that issue that explains how to add it, or replace the |
It seems Windows tests don't/didn't run on this PR. I don't think we can skip tests based on the platform? |
Good catch. I pushed an exclude in a later commit. |
No description provided.