From c06d0f4e79edfe0a5ec650b2d2a8c1a441ed3226 Mon Sep 17 00:00:00 2001 From: Franck Cuny Date: Sun, 12 Mar 2023 18:23:05 -0700 Subject: a CLI to update records for fcuny.xyz I want a simple solution to update the records for fcuny.xyz. The host that serves these records is a tailscale node, so I want to also query tailscale to get the IP for that host instead of hard coding the value. Some other information are hard coded, like the name of the project in GCP, etc. --- ts.go | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 ts.go (limited to 'ts.go') diff --git a/ts.go b/ts.go new file mode 100644 index 0000000..94c4b5a --- /dev/null +++ b/ts.go @@ -0,0 +1,82 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "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, APIKey, deviceName string) (*device, error) { + 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, APIKey, device string) ([]string, []string, error) { + ts_device, err := getTsDevice(ctx, APIKey, 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 +} -- cgit v1.2.3