diff options
author | Franck Cuny <franck@fcuny.net> | 2022-05-07 15:57:10 -0700 |
---|---|---|
committer | Franck Cuny <franck@fcuny.net> | 2022-05-07 15:57:10 -0700 |
commit | 0103bbc594e6b63f7d918b3e700fb7a6d69a901a (patch) | |
tree | 4224dbf9db47b50ad6edeabb0f1d0887d060b7ea /cmd/dnsupdate/ts.go | |
parent | repo: add support for direnv (diff) | |
download | world-0103bbc594e6b63f7d918b3e700fb7a6d69a901a.tar.gz |
cmd: add a command to update fcuny.xyz
I'm using fcuny.xyz as a domain to run a number of services on a host using the IP provided by Tailscale. Instead of manually updating the DNS configuration in the console every time I create a new subdomain, let's do this with a small program. The program query Tailscale API to get the IP address of the host `tahoe`, and then query the DNS API to see if anything is missing or is mis-configured. If it's the case, it will resolve the problems.
Diffstat (limited to '')
-rw-r--r-- | cmd/dnsupdate/ts.go | 89 |
1 files changed, 89 insertions, 0 deletions
diff --git a/cmd/dnsupdate/ts.go b/cmd/dnsupdate/ts.go new file mode 100644 index 0000000..4d3ebb3 --- /dev/null +++ b/cmd/dnsupdate/ts.go @@ -0,0 +1,89 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + + "inet.af/netaddr" +) + +type device struct { + Hostname string `json:"hostname"` + ID string `json:"id"` + Addresses []string `json:"addresses"` +} + +const ( + TS_NAME = "franck.cuny@gmail.com" + TS_API_DOMAIN = "api.tailscale.com" +) + +func getTsDevice(ctx context.Context, deviceName string) (*device, error) { + apiKey, found := os.LookupEnv("TS_API_KEY") + if !found { + return nil, errors.New("the environment variable TS_API_KEY is not set") + } + + url := fmt.Sprintf("https://%s/api/v2/tailnet/%s/devices", TS_API_DOMAIN, TS_NAME) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, err + } + + req.SetBasicAuth(apiKey, "") + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("non-ok status code %d returned from tailscale api: %s", resp.StatusCode, resp.Status) + } + var buf struct { + Devices []device `json:"devices"` + } + if err := json.NewDecoder(resp.Body).Decode(&buf); err != nil { + return nil, err + } + + for _, d := range buf.Devices { + if d.Hostname == deviceName { + return &d, nil + } + } + return nil, fmt.Errorf("could not find the tailscale device named %s", deviceName) +} + +// Get the Tailscale IPv4 and IPv6 addresses associated with the given device. +func getTsIpsDevice(ctx context.Context, device string) ([]string, []string, error) { + ts_device, err := getTsDevice(ctx, device) + if err != nil { + return nil, nil, fmt.Errorf("failed to get Tailscale device information: %v", err) + } + + var ( + tsIpV4Addresses = []string{} + tsIpV6Addresses = []string{} + ) + for _, ipString := range ts_device.Addresses { + // we convert the string to a netaddr.IP so we can check if + // it's an IP v4 or v6. We need to know what's the version in + // order to use it properly when creating/updating the + // record. Then we convert it back as a string, since this is + // what the DNS API expect. + ip := netaddr.MustParseIP(ipString) + if ip.Is4() { + tsIpV4Addresses = append(tsIpV4Addresses, ip.String()) + } else { + tsIpV6Addresses = append(tsIpV6Addresses, ip.String()) + } + } + + return tsIpV4Addresses, tsIpV6Addresses, nil +} |