mirror of https://github.com/tailscale/tailscale/
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
235 lines
6.8 KiB
Go
235 lines
6.8 KiB
Go
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
//go:build go1.19
|
|
|
|
package tailscale
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"tailscale.com/client/tailscale/apitype"
|
|
)
|
|
|
|
// DNSNameServers is returned when retrieving the list of nameservers.
|
|
// It is also the structure provided when setting nameservers.
|
|
type DNSNameServers struct {
|
|
DNS []string `json:"dns"` // DNS name servers
|
|
}
|
|
|
|
// DNSNameServersPostResponse is returned when setting the list of DNS nameservers.
|
|
//
|
|
// It includes the MagicDNS status since nameservers changes may affect MagicDNS.
|
|
type DNSNameServersPostResponse struct {
|
|
DNS []string `json:"dns"` // DNS name servers
|
|
MagicDNS bool `json:"magicDNS"` // whether MagicDNS is active for this tailnet (enabled + has fallback nameservers)
|
|
}
|
|
|
|
// DNSSearchpaths is the list of search paths for a given domain.
|
|
type DNSSearchPaths struct {
|
|
SearchPaths []string `json:"searchPaths"` // DNS search paths
|
|
}
|
|
|
|
// DNSPreferences is the preferences set for a given tailnet.
|
|
//
|
|
// It includes MagicDNS which can be turned on or off. To enable MagicDNS,
|
|
// there must be at least one nameserver. When all nameservers are removed,
|
|
// MagicDNS is disabled.
|
|
type DNSPreferences struct {
|
|
MagicDNS bool `json:"magicDNS"` // whether MagicDNS is active for this tailnet (enabled + has fallback nameservers)
|
|
}
|
|
|
|
func (c *Client) dnsGETRequest(ctx context.Context, endpoint string) ([]byte, error) {
|
|
path := fmt.Sprintf("%s/api/v2/tailnet/%s/dns/%s", c.baseURL(), c.tailnet, endpoint)
|
|
req, err := http.NewRequestWithContext(ctx, "GET", path, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b, resp, err := c.sendRequest(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If status code was not successful, return the error.
|
|
// TODO: Change the check for the StatusCode to include other 2XX success codes.
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, handleErrorResponse(b, resp)
|
|
}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
func (c *Client) dnsPOSTRequest(ctx context.Context, endpoint string, postData interface{}) ([]byte, error) {
|
|
path := fmt.Sprintf("%s/api/v2/tailnet/%s/dns/%s", c.baseURL(), c.tailnet, endpoint)
|
|
data, err := json.Marshal(&postData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "POST", path, bytes.NewBuffer(data))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b, resp, err := c.sendRequest(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If status code was not successful, return the error.
|
|
// TODO: Change the check for the StatusCode to include other 2XX success codes.
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, handleErrorResponse(b, resp)
|
|
}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
// DNSConfig retrieves the DNSConfig settings for a domain.
|
|
func (c *Client) DNSConfig(ctx context.Context) (cfg *apitype.DNSConfig, err error) {
|
|
// Format return errors to be descriptive.
|
|
defer func() {
|
|
if err != nil {
|
|
err = fmt.Errorf("tailscale.DNSConfig: %w", err)
|
|
}
|
|
}()
|
|
b, err := c.dnsGETRequest(ctx, "config")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var dnsResp apitype.DNSConfig
|
|
err = json.Unmarshal(b, &dnsResp)
|
|
return &dnsResp, err
|
|
}
|
|
|
|
func (c *Client) SetDNSConfig(ctx context.Context, cfg apitype.DNSConfig) (resp *apitype.DNSConfig, err error) {
|
|
// Format return errors to be descriptive.
|
|
defer func() {
|
|
if err != nil {
|
|
err = fmt.Errorf("tailscale.SetDNSConfig: %w", err)
|
|
}
|
|
}()
|
|
var dnsResp apitype.DNSConfig
|
|
b, err := c.dnsPOSTRequest(ctx, "config", cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = json.Unmarshal(b, &dnsResp)
|
|
return &dnsResp, err
|
|
}
|
|
|
|
// NameServers retrieves the list of nameservers set for a domain.
|
|
func (c *Client) NameServers(ctx context.Context) (nameservers []string, err error) {
|
|
// Format return errors to be descriptive.
|
|
defer func() {
|
|
if err != nil {
|
|
err = fmt.Errorf("tailscale.NameServers: %w", err)
|
|
}
|
|
}()
|
|
b, err := c.dnsGETRequest(ctx, "nameservers")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var dnsResp DNSNameServers
|
|
err = json.Unmarshal(b, &dnsResp)
|
|
return dnsResp.DNS, err
|
|
}
|
|
|
|
// SetNameServers sets the list of nameservers for a tailnet to the list provided
|
|
// by the user.
|
|
//
|
|
// It returns the new list of nameservers and the MagicDNS status in case it was
|
|
// affected by the change. For example, removing all nameservers will turn off
|
|
// MagicDNS.
|
|
func (c *Client) SetNameServers(ctx context.Context, nameservers []string) (dnsResp *DNSNameServersPostResponse, err error) {
|
|
defer func() {
|
|
if err != nil {
|
|
err = fmt.Errorf("tailscale.SetNameServers: %w", err)
|
|
}
|
|
}()
|
|
dnsReq := DNSNameServers{DNS: nameservers}
|
|
b, err := c.dnsPOSTRequest(ctx, "nameservers", dnsReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = json.Unmarshal(b, &dnsResp)
|
|
return dnsResp, err
|
|
}
|
|
|
|
// DNSPreferences retrieves the DNS preferences set for a tailnet.
|
|
//
|
|
// It returns the status of MagicDNS.
|
|
func (c *Client) DNSPreferences(ctx context.Context) (dnsResp *DNSPreferences, err error) {
|
|
// Format return errors to be descriptive.
|
|
defer func() {
|
|
if err != nil {
|
|
err = fmt.Errorf("tailscale.DNSPreferences: %w", err)
|
|
}
|
|
}()
|
|
b, err := c.dnsGETRequest(ctx, "preferences")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = json.Unmarshal(b, &dnsResp)
|
|
return dnsResp, err
|
|
}
|
|
|
|
// SetDNSPreferences sets the DNS preferences for a tailnet.
|
|
//
|
|
// MagicDNS can only be enabled when there is at least one nameserver provided.
|
|
// When all nameservers are removed, MagicDNS is disabled and will stay disabled,
|
|
// unless explicitly enabled by a user again.
|
|
func (c *Client) SetDNSPreferences(ctx context.Context, magicDNS bool) (dnsResp *DNSPreferences, err error) {
|
|
defer func() {
|
|
if err != nil {
|
|
err = fmt.Errorf("tailscale.SetDNSPreferences: %w", err)
|
|
}
|
|
}()
|
|
dnsReq := DNSPreferences{MagicDNS: magicDNS}
|
|
b, err := c.dnsPOSTRequest(ctx, "preferences", dnsReq)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = json.Unmarshal(b, &dnsResp)
|
|
return dnsResp, err
|
|
}
|
|
|
|
// SearchPaths retrieves the list of searchpaths set for a tailnet.
|
|
func (c *Client) SearchPaths(ctx context.Context) (searchpaths []string, err error) {
|
|
defer func() {
|
|
if err != nil {
|
|
err = fmt.Errorf("tailscale.SearchPaths: %w", err)
|
|
}
|
|
}()
|
|
b, err := c.dnsGETRequest(ctx, "searchpaths")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var dnsResp *DNSSearchPaths
|
|
err = json.Unmarshal(b, &dnsResp)
|
|
return dnsResp.SearchPaths, err
|
|
}
|
|
|
|
// SetSearchPaths sets the list of searchpaths for a tailnet.
|
|
func (c *Client) SetSearchPaths(ctx context.Context, searchpaths []string) (newSearchPaths []string, err error) {
|
|
defer func() {
|
|
if err != nil {
|
|
err = fmt.Errorf("tailscale.SetSearchPaths: %w", err)
|
|
}
|
|
}()
|
|
dnsReq := DNSSearchPaths{SearchPaths: searchpaths}
|
|
b, err := c.dnsPOSTRequest(ctx, "searchpaths", dnsReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var dnsResp DNSSearchPaths
|
|
err = json.Unmarshal(b, &dnsResp)
|
|
return dnsResp.SearchPaths, err
|
|
}
|