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 }