We just added endpoint discovery to Wishlist, our SSH host directory.
Wishlist
- ConvenientVery
Your SSH directory.
Wishlist can act as a bastion, presenting the user with a list of hosts they can SSH into from that host.
You can also run it locally, in which case it becomes a TUI (text-based
user interface) for your ~/.ssh/config
(or to a predefined list of hosts in
a YAML configuration file).
But there’s more to it: did you know you can discover hosts from several sources?
Endpoint Discovery
Currently, we support the following sources:
- DNS SRV records
- Zeroconf with mDNS
- Tailscale
Let’s see how it works, shall we?
Tailscale
We partnered with our friends at Tailscale to add endpoint discovery to Wishlist. Check out what they have to say about it here.
Tailscale is a VPN service that makes devices and applications you own securely accessible anywhere in the world. It uses the WireGuard protocol, and has apps for pretty much all platforms.
To discover your Tailscale-connected machines, you’ll need an API Access Token, and the name of your tailnet.
With that information in hand, run wishlist
with --tailscale.key
(or set $TAILSCALE_KEY
) and --tailscale.net
, for example:
wishlist --tailscale.net=charm --tailscale.key=ts-key-aaabbb...
And that’s it! Wishlist will discover all your tailnet’s machines on startup.
Note: We can’t get the open ports through the API, so all endpoints discovered will use the default SSH port (
22
). You can change that with hints.
It’s also worth mentioning that Tailscale’s API keys expire after 90 days (max). To avoid the trouble of having to change the key every couple of months, you can also use their beta OAuth Clients.
Create an app with devices:read
scope
here, and run with:
wishlist --tailscale.net=charm \
--tailscale.client.id=aaabbb... \
--tailscale.client.secret=tskey-client-aaaabbb...
This also gives Wishlist a more restricted access: only reading the device list, nothing else.
Zeroconf with mDNS
Zeroconf enables service discovery in a network without any operator intervention. It was originally adapted from AppleTalk, circa 1997, and is tracked by the RFC 6762.
If you run anything Apple in your network, chances are they are all already
available in .local
domains.
This happens because Apple devices employ
Bonjour (a Zeroconf implementation)
which is installed and configured by default.
On Linux, you can use either Avahi or SystemD for the same purpose.
For Avahi, installing and enabling the daemon (the package is usually named
avahi-daemon
) will make your host available in the network as hostname.local
.
To make the host available in Wishlist, though, you’ll need to expose a
_ssh._tcp
service.
You can do that by creating the file /etc/avahi/services/ssh.service
with the
following contents:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<service-group>
<name replace-wildcards="yes">%h</name>
<service>
<type>_ssh._tcp</type>
<port>22</port>
</service>
</service-group>
You can check if you have any available endpoints by running:
# on Linux:
avahi-browse --domain local _ssh._tcp
# on macOS:
dns-sd -B _ssh._tcp
All that being said, you can run wishlist
with --zeroconf.enabled
, and it’ll operate using sensible defaults:
wishlist --zeroconf.enabled
Run wishlist --help | grep zeroconf
to see all options.
DNS SRV
Service Records (SRV) are specified in DNS for defining the host name and port number of servers for specific services.
It is defined as:
_service._proto.name. ttl IN SRV priority weight port target
Wishlist will look for records with _ssh._tcp
as service
and proto
.
You can mimic what it’ll do with dig
, e.g. for the caarlos0.dev
domain:
dig SRV _ssh._tcp.caarlos0.dev +short
So, for instance, if I want to expose a SRV
record on port 2244
,
I would add an entry like this to my DNS:
_ssh._tcp.caarlos0.dev. 300 IN SRV 10 2 2244 192.168.1.123.
Once you’ve done that, you can run wishlist
setting --srv.domain
to
make it query the your name server and list the SRV records as endpoints.
For example:
wishlist --srv.domain=caarlos0.dev
Hints
You might be asking yourself: “What if I want to set some more advanced options in these endpoints?” That’s what hints are for.
Hints have a similar structure to the endpoints
setting, but they work
differently: if a hint doesn’t match a discovered endpoint, it won’t get
added to the final endpoint list, whereas regular endpoints
would.
It works by using a match
field (which can be a glob), and then tries
to match all discovered endpoints hostnames with it.
If it matches, the options in that hint will be set onto the final endpoint.
You can set port
(especially useful with Tailscale), link
, user
,
description
, and more.
Here’s an example showing all available fields:
hints:
- match: "*.local"
port: 23234
description: "A description of this endpoint.\nCan have multiple lines."
user: notme
remote_command: uptime -a
forward_agent: true
request_tty: true
connect_timeout: 10s
proxy_jump: user@host:22
link:
name: Optional link name
url: https://github.com/charmbracelet/wishlist
identity_files:
- ~/.ssh/id_ed25519
- ~/.ssh/charm_id_ed25519
set_env:
- FOO=bar
- BAR=baz
send_env:
- LC_*
- LANG
- SOME_ENV
You can see the full, commented out, configuration file here.
Once you have your configuration file, run wishlist
passing its path
to --config
.
Wishlist will then discover all the endpoints (through all options available),
and then, if there are any hints
, iterate through them and apply
them to the discovered nodes.
Finally, on server mode, you can specify a --endpoints.refresh.interval
,
so Wishlist will re-discover the nodes and also reload the configuration file,
re-applying the hints too.
We believe that those features will make your Wishlist-powered SSH directories easier to maintain and to use, and can’t wait to see what you’ll do with it.