mirror of https://github.com/coder/coder.git
chore: switch to new wgtunnel via tunnelsdk (#6489)
This commit is contained in:
parent
e85a17b0c8
commit
5460ab4ba6
|
@ -85,6 +85,7 @@ import (
|
|||
"github.com/coder/coder/provisionersdk"
|
||||
sdkproto "github.com/coder/coder/provisionersdk/proto"
|
||||
"github.com/coder/coder/tailnet"
|
||||
"github.com/coder/wgtunnel/tunnelsdk"
|
||||
)
|
||||
|
||||
// ReadGitAuthProvidersFromEnv is provided for compatibility purposes with the
|
||||
|
@ -538,34 +539,25 @@ flags, and YAML configuration. The precedence is as follows:
|
|||
return xerrors.Errorf("configure http client: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
ctxTunnel, closeTunnel = context.WithCancel(ctx)
|
||||
tunnel *devtunnel.Tunnel
|
||||
tunnelErr <-chan error
|
||||
)
|
||||
defer closeTunnel()
|
||||
|
||||
// If the access URL is empty, we attempt to run a reverse-proxy
|
||||
// tunnel to make the initial setup really simple.
|
||||
var (
|
||||
tunnel *tunnelsdk.Tunnel
|
||||
tunnelDone <-chan struct{} = make(chan struct{}, 1)
|
||||
)
|
||||
if cfg.AccessURL.String() == "" {
|
||||
cmd.Printf("Opening tunnel so workspaces can connect to your deployment. For production scenarios, specify an external access URL\n")
|
||||
tunnel, tunnelErr, err = devtunnel.New(ctxTunnel, logger.Named("devtunnel"))
|
||||
tunnel, err = devtunnel.New(ctx, logger.Named("devtunnel"), cfg.WgtunnelHost.String())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create tunnel: %w", err)
|
||||
}
|
||||
err = cfg.AccessURL.Set(tunnel.URL)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("set access url: %w", err)
|
||||
}
|
||||
defer tunnel.Close()
|
||||
tunnelDone = tunnel.Wait()
|
||||
cfg.AccessURL = clibase.URL(*tunnel.URL)
|
||||
|
||||
if cfg.WildcardAccessURL.String() == "" {
|
||||
u, err := parseURL(tunnel.URL)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parse tunnel url: %w", err)
|
||||
}
|
||||
|
||||
// Suffixed wildcard access URL.
|
||||
u, err = url.Parse(fmt.Sprintf("*--%s", u.Hostname()))
|
||||
u, err := url.Parse(fmt.Sprintf("*--%s", tunnel.URL.Hostname()))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parse wildcard url: %w", err)
|
||||
}
|
||||
|
@ -1090,10 +1082,8 @@ flags, and YAML configuration. The precedence is as follows:
|
|||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Bold.Render(
|
||||
"Interrupt caught, gracefully exiting. Use ctrl+\\ to force quit",
|
||||
))
|
||||
case exitErr = <-tunnelErr:
|
||||
if exitErr == nil {
|
||||
exitErr = xerrors.New("dev tunnel closed unexpectedly")
|
||||
}
|
||||
case <-tunnelDone:
|
||||
exitErr = xerrors.New("dev tunnel closed unexpectedly")
|
||||
case exitErr = <-errCh:
|
||||
}
|
||||
if exitErr != nil && !xerrors.Is(exitErr, context.Canceled) {
|
||||
|
@ -1162,8 +1152,8 @@ flags, and YAML configuration. The precedence is as follows:
|
|||
// Close tunnel after we no longer have in-flight connections.
|
||||
if tunnel != nil {
|
||||
cmd.Println("Waiting for tunnel to close...")
|
||||
closeTunnel()
|
||||
<-tunnelErr
|
||||
_ = tunnel.Close()
|
||||
<-tunnel.Wait()
|
||||
cmd.Println("Done waiting for tunnel")
|
||||
}
|
||||
|
||||
|
@ -1241,22 +1231,6 @@ flags, and YAML configuration. The precedence is as follows:
|
|||
return root
|
||||
}
|
||||
|
||||
// parseURL parses a string into a URL.
|
||||
func parseURL(u string) (*url.URL, error) {
|
||||
hasScheme := strings.HasPrefix(u, "http:") || strings.HasPrefix(u, "https:")
|
||||
|
||||
if !hasScheme {
|
||||
return nil, xerrors.Errorf("URL %q must have a scheme of either http or https", u)
|
||||
}
|
||||
|
||||
parsed, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
// isLocalURL returns true if the hostname of the provided URL appears to
|
||||
// resolve to a loopback address.
|
||||
func isLocalURL(ctx context.Context, u *url.URL) (bool, error) {
|
||||
|
|
|
@ -6720,6 +6720,9 @@ const docTemplate = `{
|
|||
"verbose": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"wgtunnel_host": {
|
||||
"type": "string"
|
||||
},
|
||||
"wildcard_access_url": {
|
||||
"$ref": "#/definitions/clibase.URL"
|
||||
},
|
||||
|
|
|
@ -6012,6 +6012,9 @@
|
|||
"verbose": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"wgtunnel_host": {
|
||||
"type": "string"
|
||||
},
|
||||
"wildcard_access_url": {
|
||||
"$ref": "#/definitions/clibase.URL"
|
||||
},
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/go-ping/ping"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/cryptorand"
|
||||
)
|
||||
|
@ -19,13 +20,11 @@ type Region struct {
|
|||
}
|
||||
|
||||
type Node struct {
|
||||
ID int `json:"id"`
|
||||
RegionID int `json:"region_id"`
|
||||
HostnameHTTPS string `json:"hostname_https"`
|
||||
HostnameWireguard string `json:"hostname_wireguard"`
|
||||
WireguardPort uint16 `json:"wireguard_port"`
|
||||
ID int `json:"id"`
|
||||
RegionID int `json:"region_id"`
|
||||
HostnameHTTPS string `json:"hostname_https"`
|
||||
|
||||
AvgLatency time.Duration `json:"avg_latency"`
|
||||
AvgLatency time.Duration `json:"-"`
|
||||
}
|
||||
|
||||
var Regions = []Region{
|
||||
|
@ -34,28 +33,53 @@ var Regions = []Region{
|
|||
LocationName: "US East Pittsburgh",
|
||||
Nodes: []Node{
|
||||
{
|
||||
ID: 1,
|
||||
RegionID: 0,
|
||||
HostnameHTTPS: "pit-1.try.coder.app",
|
||||
HostnameWireguard: "pit-1.try.coder.app",
|
||||
WireguardPort: 55551,
|
||||
ID: 1,
|
||||
RegionID: 0,
|
||||
HostnameHTTPS: "pit-1.try.coder.app",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func FindClosestNode() (Node, error) {
|
||||
// Nodes returns a list of nodes to use for the tunnel. It will pick a random
|
||||
// node from each region.
|
||||
//
|
||||
// If a customNode is provided, it will be returned as the only node with ID
|
||||
// 9999.
|
||||
func Nodes(customTunnelHost string) ([]Node, error) {
|
||||
nodes := []Node{}
|
||||
|
||||
if customTunnelHost != "" {
|
||||
return []Node{
|
||||
{
|
||||
ID: 9999,
|
||||
RegionID: 9999,
|
||||
HostnameHTTPS: customTunnelHost,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
for _, region := range Regions {
|
||||
// Pick a random node from each region.
|
||||
i, err := cryptorand.Intn(len(region.Nodes))
|
||||
if err != nil {
|
||||
return Node{}, err
|
||||
return []Node{}, err
|
||||
}
|
||||
nodes = append(nodes, region.Nodes[i])
|
||||
}
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// FindClosestNode pings each node and returns the one with the lowest latency.
|
||||
func FindClosestNode(nodes []Node) (Node, error) {
|
||||
if len(nodes) == 0 {
|
||||
return Node{}, xerrors.New("no wgtunnel nodes")
|
||||
}
|
||||
|
||||
// Copy the nodes so we don't mutate the original.
|
||||
nodes = append([]Node{}, nodes...)
|
||||
|
||||
var (
|
||||
nodesMu sync.Mutex
|
||||
eg = errgroup.Group{}
|
||||
|
|
|
@ -1,134 +1,52 @@
|
|||
package devtunnel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"golang.org/x/xerrors"
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/cryptorand"
|
||||
"github.com/coder/wgtunnel/tunnelsdk"
|
||||
)
|
||||
|
||||
type Tunnel struct {
|
||||
URL string
|
||||
Listener net.Listener
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Version int `json:"version"`
|
||||
PrivateKey device.NoisePrivateKey `json:"private_key"`
|
||||
PublicKey device.NoisePublicKey `json:"public_key"`
|
||||
Version tunnelsdk.TunnelVersion `json:"version"`
|
||||
PrivateKey device.NoisePrivateKey `json:"private_key"`
|
||||
PublicKey device.NoisePublicKey `json:"public_key"`
|
||||
|
||||
Tunnel Node `json:"tunnel"`
|
||||
|
||||
// Used in testing. Normally this is nil, indicating to use DefaultClient.
|
||||
HTTPClient *http.Client `json:"-"`
|
||||
}
|
||||
type configExt struct {
|
||||
Version int `json:"-"`
|
||||
PrivateKey device.NoisePrivateKey `json:"-"`
|
||||
PublicKey device.NoisePublicKey `json:"public_key"`
|
||||
|
||||
Tunnel Node `json:"-"`
|
||||
|
||||
// Used in testing. Normally this is nil, indicating to use DefaultClient.
|
||||
HTTPClient *http.Client `json:"-"`
|
||||
}
|
||||
|
||||
// NewWithConfig calls New with the given config. For documentation, see New.
|
||||
func NewWithConfig(ctx context.Context, logger slog.Logger, cfg Config) (*Tunnel, <-chan error, error) {
|
||||
server, routineEnd, err := startUpdateRoutine(ctx, logger, cfg)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("start update routine: %w", err)
|
||||
func NewWithConfig(ctx context.Context, logger slog.Logger, cfg Config) (*tunnelsdk.Tunnel, error) {
|
||||
u := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: cfg.Tunnel.HostnameHTTPS,
|
||||
}
|
||||
|
||||
tun, tnet, err := netstack.CreateNetTUN(
|
||||
[]netip.Addr{server.ClientIP},
|
||||
[]netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})},
|
||||
1280,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("create net TUN: %w", err)
|
||||
c := tunnelsdk.New(u)
|
||||
if cfg.HTTPClient != nil {
|
||||
c.HTTPClient = cfg.HTTPClient
|
||||
}
|
||||
|
||||
wgip, err := net.ResolveIPAddr("ip", cfg.Tunnel.HostnameWireguard)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("resolve endpoint: %w", err)
|
||||
}
|
||||
// In IPv6, we need to enclose the address to in [] before passing to wireguard's endpoint key, like
|
||||
// [2001:abcd::1]:8888. We'll use netip.AddrPort to correctly handle this.
|
||||
wgAddr, err := netip.ParseAddr(wgip.String())
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("parse address: %w", err)
|
||||
}
|
||||
wgEndpoint := netip.AddrPortFrom(wgAddr, cfg.Tunnel.WireguardPort)
|
||||
|
||||
dlog := &device.Logger{
|
||||
Verbosef: slog.Stdlib(ctx, logger, slog.LevelDebug).Printf,
|
||||
Errorf: slog.Stdlib(ctx, logger, slog.LevelError).Printf,
|
||||
}
|
||||
dev := device.NewDevice(tun, conn.NewDefaultBind(), dlog)
|
||||
err = dev.IpcSet(fmt.Sprintf(`private_key=%s
|
||||
public_key=%s
|
||||
endpoint=%s
|
||||
persistent_keepalive_interval=21
|
||||
allowed_ip=%s/128`,
|
||||
hex.EncodeToString(cfg.PrivateKey[:]),
|
||||
server.ServerPublicKey,
|
||||
wgEndpoint.String(),
|
||||
server.ServerIP.String(),
|
||||
))
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("configure wireguard ipc: %w", err)
|
||||
}
|
||||
|
||||
err = dev.Up()
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("wireguard device up: %w", err)
|
||||
}
|
||||
|
||||
wgListen, err := tnet.ListenTCP(&net.TCPAddr{Port: 8090})
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("wireguard device listen: %w", err)
|
||||
}
|
||||
|
||||
ch := make(chan error, 1)
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
_ = wgListen.Close()
|
||||
// We need to remove peers before closing to avoid a race condition between dev.Close() and the peer
|
||||
// goroutines which results in segfault.
|
||||
dev.RemoveAllPeers()
|
||||
dev.Close()
|
||||
<-routineEnd
|
||||
close(ch)
|
||||
|
||||
case <-dev.Wait():
|
||||
close(ch)
|
||||
}
|
||||
}()
|
||||
|
||||
return &Tunnel{
|
||||
URL: fmt.Sprintf("https://%s", server.Hostname),
|
||||
Listener: wgListen,
|
||||
}, ch, nil
|
||||
return c.LaunchTunnel(ctx, tunnelsdk.TunnelConfig{
|
||||
Log: logger,
|
||||
Version: cfg.Version,
|
||||
PrivateKey: tunnelsdk.FromNoisePrivateKey(cfg.PrivateKey),
|
||||
})
|
||||
}
|
||||
|
||||
// New creates a tunnel with a public URL and returns a listener for incoming
|
||||
|
@ -136,82 +54,18 @@ allowed_ip=%s/128`,
|
|||
// Tunnel configuration is cached in the user's config directory. Successive
|
||||
// calls to New will always use the same URL. If multiple public URLs in
|
||||
// parallel are required, use NewWithConfig.
|
||||
func New(ctx context.Context, logger slog.Logger) (*Tunnel, <-chan error, error) {
|
||||
cfg, err := readOrGenerateConfig()
|
||||
//
|
||||
// This uses https://github.com/coder/wgtunnel as the server and client
|
||||
// implementation.
|
||||
func New(ctx context.Context, logger slog.Logger, customTunnelHost string) (*tunnelsdk.Tunnel, error) {
|
||||
cfg, err := readOrGenerateConfig(customTunnelHost)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("read or generate config: %w", err)
|
||||
return nil, xerrors.Errorf("read or generate config: %w", err)
|
||||
}
|
||||
|
||||
return NewWithConfig(ctx, logger, cfg)
|
||||
}
|
||||
|
||||
func startUpdateRoutine(ctx context.Context, logger slog.Logger, cfg Config) (ServerResponse, <-chan struct{}, error) {
|
||||
// Ensure we send the first config before spawning in the background.
|
||||
res, err := sendConfigToServer(ctx, cfg)
|
||||
if err != nil {
|
||||
return ServerResponse{}, nil, xerrors.Errorf("send config to server: %w", err)
|
||||
}
|
||||
|
||||
endCh := make(chan struct{})
|
||||
go func() {
|
||||
defer close(endCh)
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
case <-ticker.C:
|
||||
}
|
||||
|
||||
_, err := sendConfigToServer(ctx, cfg)
|
||||
if err != nil {
|
||||
logger.Debug(ctx, "send tunnel config to server", slog.Error(err))
|
||||
}
|
||||
}
|
||||
}()
|
||||
return res, endCh, nil
|
||||
}
|
||||
|
||||
type ServerResponse struct {
|
||||
Hostname string `json:"hostname"`
|
||||
ServerIP netip.Addr `json:"server_ip"`
|
||||
ServerPublicKey string `json:"server_public_key"` // hex
|
||||
ClientIP netip.Addr `json:"client_ip"`
|
||||
}
|
||||
|
||||
func sendConfigToServer(ctx context.Context, cfg Config) (ServerResponse, error) {
|
||||
raw, err := json.Marshal(configExt(cfg))
|
||||
if err != nil {
|
||||
return ServerResponse{}, xerrors.Errorf("marshal config: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", "https://"+cfg.Tunnel.HostnameHTTPS+"/tun", bytes.NewReader(raw))
|
||||
if err != nil {
|
||||
return ServerResponse{}, xerrors.Errorf("new request: %w", err)
|
||||
}
|
||||
|
||||
client := http.DefaultClient
|
||||
if cfg.HTTPClient != nil {
|
||||
client = cfg.HTTPClient
|
||||
}
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return ServerResponse{}, xerrors.Errorf("do request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
var resp ServerResponse
|
||||
err = json.NewDecoder(res.Body).Decode(&resp)
|
||||
if err != nil {
|
||||
return ServerResponse{}, xerrors.Errorf("decode response: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func cfgPath() (string, error) {
|
||||
cfgDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
|
@ -227,7 +81,7 @@ func cfgPath() (string, error) {
|
|||
return filepath.Join(cfgDir, "devtunnel"), nil
|
||||
}
|
||||
|
||||
func readOrGenerateConfig() (Config, error) {
|
||||
func readOrGenerateConfig(customTunnelHost string) (Config, error) {
|
||||
cfgFi, err := cfgPath()
|
||||
if err != nil {
|
||||
return Config{}, xerrors.Errorf("get config path: %w", err)
|
||||
|
@ -236,7 +90,7 @@ func readOrGenerateConfig() (Config, error) {
|
|||
fi, err := os.ReadFile(cfgFi)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
cfg, err := GenerateConfig()
|
||||
cfg, err := GenerateConfig(customTunnelHost)
|
||||
if err != nil {
|
||||
return Config{}, xerrors.Errorf("generate config: %w", err)
|
||||
}
|
||||
|
@ -264,7 +118,7 @@ func readOrGenerateConfig() (Config, error) {
|
|||
_, _ = fmt.Println(cliui.Styles.Error.Render("Upgrading you to the new version now. You will need to rebuild running workspaces."))
|
||||
_, _ = fmt.Println()
|
||||
|
||||
cfg, err := GenerateConfig()
|
||||
cfg, err := GenerateConfig(customTunnelHost)
|
||||
if err != nil {
|
||||
return Config{}, xerrors.Errorf("generate config: %w", err)
|
||||
}
|
||||
|
@ -280,20 +134,29 @@ func readOrGenerateConfig() (Config, error) {
|
|||
return cfg, nil
|
||||
}
|
||||
|
||||
func GenerateConfig() (Config, error) {
|
||||
priv, err := wgtypes.GeneratePrivateKey()
|
||||
func GenerateConfig(customTunnelHost string) (Config, error) {
|
||||
priv, err := tunnelsdk.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return Config{}, xerrors.Errorf("generate private key: %w", err)
|
||||
}
|
||||
pub := priv.PublicKey()
|
||||
privNoisePublicKey, err := priv.NoisePrivateKey()
|
||||
if err != nil {
|
||||
return Config{}, xerrors.Errorf("generate noise private key: %w", err)
|
||||
}
|
||||
pubNoisePublicKey := priv.NoisePublicKey()
|
||||
|
||||
spin := spinner.New(spinner.CharSets[39], 350*time.Millisecond)
|
||||
spin.Suffix = " Finding the closest tunnel region..."
|
||||
spin.Start()
|
||||
|
||||
node, err := FindClosestNode()
|
||||
nodes, err := Nodes(customTunnelHost)
|
||||
if err != nil {
|
||||
// If we fail to find the closest node, default to US East.
|
||||
return Config{}, xerrors.Errorf("get nodes: %w", err)
|
||||
}
|
||||
node, err := FindClosestNode(nodes)
|
||||
if err != nil {
|
||||
// If we fail to find the closest node, default to a random node from
|
||||
// the first region.
|
||||
region := Regions[0]
|
||||
n, _ := cryptorand.Intn(len(region.Nodes))
|
||||
node = region.Nodes[n]
|
||||
|
@ -302,16 +165,21 @@ func GenerateConfig() (Config, error) {
|
|||
_, _ = fmt.Println("Defaulting to", Regions[0].LocationName)
|
||||
}
|
||||
|
||||
locationName := "Unknown"
|
||||
if node.RegionID < len(Regions) {
|
||||
locationName = Regions[node.RegionID].LocationName
|
||||
}
|
||||
|
||||
spin.Stop()
|
||||
_, _ = fmt.Printf("Using tunnel in %s with latency %s.\n",
|
||||
cliui.Styles.Keyword.Render(Regions[node.RegionID].LocationName),
|
||||
cliui.Styles.Keyword.Render(locationName),
|
||||
cliui.Styles.Code.Render(node.AvgLatency.String()),
|
||||
)
|
||||
|
||||
return Config{
|
||||
Version: 1,
|
||||
PrivateKey: device.NoisePrivateKey(priv),
|
||||
PublicKey: device.NoisePublicKey(pub),
|
||||
Version: tunnelsdk.TunnelVersion2,
|
||||
PrivateKey: privNoisePublicKey,
|
||||
PublicKey: pubNoisePublicKey,
|
||||
Tunnel: node,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -2,14 +2,16 @@ package devtunnel_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base32"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -18,26 +20,12 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
"cdr.dev/slog/sloggers/slogtest"
|
||||
"github.com/coder/coder/coderd/devtunnel"
|
||||
"github.com/coder/coder/testutil"
|
||||
)
|
||||
|
||||
const (
|
||||
ipByte1 = 0xfc
|
||||
ipByte2 = 0xca
|
||||
wgPort = 48732
|
||||
)
|
||||
|
||||
var (
|
||||
serverIP = netip.AddrFrom16([16]byte{ipByte1, ipByte2, 15: 0x1})
|
||||
dnsIP = netip.AddrFrom4([4]byte{1, 1, 1, 1})
|
||||
clientIP = netip.AddrFrom16([16]byte{ipByte1, ipByte2, 15: 0x2})
|
||||
"github.com/coder/wgtunnel/tunneld"
|
||||
"github.com/coder/wgtunnel/tunnelsdk"
|
||||
)
|
||||
|
||||
// The tunnel leaks a few goroutines that aren't impactful to production scenarios.
|
||||
|
@ -45,194 +33,236 @@ var (
|
|||
// goleak.VerifyTestMain(m)
|
||||
// }
|
||||
|
||||
// TestTunnel cannot run in parallel because we hardcode the UDP port used by the wireguard server.
|
||||
// nolint: paralleltest
|
||||
func TestTunnel(t *testing.T) {
|
||||
ctx, cancelTun := context.WithCancel(context.Background())
|
||||
defer cancelTun()
|
||||
t.Parallel()
|
||||
|
||||
server := http.Server{
|
||||
ReadHeaderTimeout: time.Minute,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Log("got request for", r.URL)
|
||||
// Going to use something _slightly_ exotic so that we can't accidentally get some
|
||||
// default behavior creating a false positive on the test
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}),
|
||||
BaseContext: func(_ net.Listener) context.Context {
|
||||
return ctx
|
||||
cases := []struct {
|
||||
name string
|
||||
version tunnelsdk.TunnelVersion
|
||||
}{
|
||||
{
|
||||
name: "V1",
|
||||
version: tunnelsdk.TunnelVersion1,
|
||||
},
|
||||
{
|
||||
name: "V2",
|
||||
version: tunnelsdk.TunnelVersion2,
|
||||
},
|
||||
}
|
||||
|
||||
fTunServer := newFakeTunnelServer(t)
|
||||
cfg := fTunServer.config()
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
|
||||
tun, errCh, err := devtunnel.NewWithConfig(ctx, slogtest.Make(t, nil).Leveled(slog.LevelDebug), cfg)
|
||||
require.NoError(t, err)
|
||||
t.Log(tun.URL)
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
go func() {
|
||||
err := server.Serve(tun.Listener)
|
||||
assert.Equal(t, http.ErrServerClosed, err)
|
||||
}()
|
||||
defer func() { _ = server.Close() }()
|
||||
defer func() { tun.Listener.Close() }()
|
||||
ctx, cancelTun := context.WithCancel(context.Background())
|
||||
defer cancelTun()
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
res, err := fTunServer.requestHTTP()
|
||||
if !assert.NoError(t, err) {
|
||||
return false
|
||||
}
|
||||
defer res.Body.Close()
|
||||
_, _ = io.Copy(io.Discard, res.Body)
|
||||
server := http.Server{
|
||||
ReadHeaderTimeout: time.Minute,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Log("got request for", r.URL)
|
||||
// Going to use something _slightly_ exotic so that we can't
|
||||
// accidentally get some default behavior creating a false
|
||||
// positive on the test
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}),
|
||||
BaseContext: func(_ net.Listener) context.Context {
|
||||
return ctx
|
||||
},
|
||||
}
|
||||
|
||||
return res.StatusCode == http.StatusAccepted
|
||||
}, testutil.WaitShort, testutil.IntervalSlow)
|
||||
tunServer := newTunnelServer(t)
|
||||
cfg := tunServer.config(t, c.version)
|
||||
|
||||
assert.NoError(t, server.Close())
|
||||
cancelTun()
|
||||
tun, err := devtunnel.NewWithConfig(ctx, slogtest.Make(t, nil).Leveled(slog.LevelDebug), cfg)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, tun.OtherURLs, 1)
|
||||
t.Log(tun.URL, tun.OtherURLs[0])
|
||||
|
||||
select {
|
||||
case <-errCh:
|
||||
case <-time.After(testutil.WaitLong):
|
||||
t.Errorf("tunnel did not close after %s", testutil.WaitLong)
|
||||
hostSplit := strings.SplitN(tun.URL.Host, ".", 2)
|
||||
require.Len(t, hostSplit, 2)
|
||||
require.Equal(t, hostSplit[1], tunServer.api.BaseURL.Host)
|
||||
|
||||
// Verify the hostname using the same logic as the tunnel server.
|
||||
ip1, urls := tunServer.api.WireguardPublicKeyToIPAndURLs(cfg.PublicKey, c.version)
|
||||
require.Len(t, urls, 2)
|
||||
require.Equal(t, urls[0].String(), tun.URL.String())
|
||||
require.Equal(t, urls[1].String(), tun.OtherURLs[0].String())
|
||||
|
||||
ip2, err := tunServer.api.HostnameToWireguardIP(hostSplit[0])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ip1, ip2)
|
||||
|
||||
// Manually verify the hostname.
|
||||
switch c.version {
|
||||
case tunnelsdk.TunnelVersion1:
|
||||
// The subdomain should be a 32 character hex string.
|
||||
require.Len(t, hostSplit[0], 32)
|
||||
_, err := hex.DecodeString(hostSplit[0])
|
||||
require.NoError(t, err)
|
||||
case tunnelsdk.TunnelVersion2:
|
||||
// The subdomain should be a base32 encoded string containing
|
||||
// 16 bytes once decoded.
|
||||
dec, err := base32.HexEncoding.WithPadding(base32.NoPadding).DecodeString(strings.ToUpper(hostSplit[0]))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, dec, 8)
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := server.Serve(tun.Listener)
|
||||
assert.Equal(t, http.ErrServerClosed, err)
|
||||
}()
|
||||
defer func() { _ = server.Close() }()
|
||||
defer func() { tun.Listener.Close() }()
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, tun.URL.String(), nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return false
|
||||
}
|
||||
res, err := tunServer.requestTunnel(tun, req)
|
||||
if !assert.NoError(t, err) {
|
||||
return false
|
||||
}
|
||||
defer res.Body.Close()
|
||||
_, _ = io.Copy(io.Discard, res.Body)
|
||||
|
||||
return res.StatusCode == http.StatusAccepted
|
||||
}, testutil.WaitShort, testutil.IntervalSlow)
|
||||
|
||||
assert.NoError(t, server.Close())
|
||||
cancelTun()
|
||||
|
||||
select {
|
||||
case <-tun.Wait():
|
||||
case <-time.After(testutil.WaitLong):
|
||||
t.Errorf("tunnel did not close after %s", testutil.WaitLong)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// fakeTunnelServer is a fake version of the real dev tunnel server. It fakes 2 client interactions
|
||||
// that we want to test:
|
||||
// 1. Responding to a POST /tun from the client
|
||||
// 2. Sending an HTTP request down the wireguard connection
|
||||
//
|
||||
// Note that for 2, we don't implement a full proxy that accepts arbitrary requests, we just send
|
||||
// a test request over the Wireguard tunnel to make sure that we can listen. The proxy behavior is
|
||||
// outside of the scope of the dev tunnel client, which is what we are testing here.
|
||||
type fakeTunnelServer struct {
|
||||
t *testing.T
|
||||
pub device.NoisePublicKey
|
||||
priv device.NoisePrivateKey
|
||||
tnet *netstack.Net
|
||||
device *device.Device
|
||||
clients int
|
||||
server *httptest.Server
|
||||
}
|
||||
|
||||
func newFakeTunnelServer(t *testing.T) *fakeTunnelServer {
|
||||
func freeUDPPort(t *testing.T) uint16 {
|
||||
t.Helper()
|
||||
|
||||
priv, err := wgtypes.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
privBytes := [32]byte(priv)
|
||||
pub := priv.PublicKey()
|
||||
pubBytes := [32]byte(pub)
|
||||
tun, tnet, err := netstack.CreateNetTUN(
|
||||
[]netip.Addr{serverIP},
|
||||
[]netip.Addr{dnsIP},
|
||||
1280,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
slogger := slogtest.Make(t, nil).Leveled(slog.LevelDebug).Named("server")
|
||||
logger := &device.Logger{
|
||||
Verbosef: slog.Stdlib(ctx, slogger, slog.LevelDebug).Printf,
|
||||
Errorf: slog.Stdlib(ctx, slogger, slog.LevelError).Printf,
|
||||
}
|
||||
dev := device.NewDevice(tun, conn.NewDefaultBind(), logger)
|
||||
t.Cleanup(func() {
|
||||
dev.RemoveAllPeers()
|
||||
dev.Close()
|
||||
slogger.Debug(ctx, "dev.Close()")
|
||||
l, err := net.ListenUDP("udp", &net.UDPAddr{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Port: 0,
|
||||
})
|
||||
err = dev.IpcSet(fmt.Sprintf(`private_key=%s
|
||||
listen_port=%d`,
|
||||
hex.EncodeToString(privBytes[:]),
|
||||
wgPort,
|
||||
))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, err, "listen on random UDP port")
|
||||
|
||||
err = dev.Up()
|
||||
require.NoError(t, err)
|
||||
_, port, err := net.SplitHostPort(l.LocalAddr().String())
|
||||
require.NoError(t, err, "split host port")
|
||||
|
||||
server := newFakeTunnelHTTPSServer(t, pubBytes)
|
||||
portUint, err := strconv.ParseUint(port, 10, 16)
|
||||
require.NoError(t, err, "parse port")
|
||||
|
||||
return &fakeTunnelServer{
|
||||
t: t,
|
||||
pub: device.NoisePublicKey(pub),
|
||||
priv: device.NoisePrivateKey(priv),
|
||||
tnet: tnet,
|
||||
device: dev,
|
||||
server: server,
|
||||
}
|
||||
// This is prone to races, but since we have to tell wireguard to create the
|
||||
// listener and can't pass in a net.Listener, we have to do this.
|
||||
err = l.Close()
|
||||
require.NoError(t, err, "close UDP listener")
|
||||
|
||||
return uint16(portUint)
|
||||
}
|
||||
|
||||
func newFakeTunnelHTTPSServer(t *testing.T, pubBytes [32]byte) *httptest.Server {
|
||||
handler := http.NewServeMux()
|
||||
handler.HandleFunc("/tun", func(writer http.ResponseWriter, request *http.Request) {
|
||||
assert.Equal(t, "POST", request.Method)
|
||||
type tunnelServer struct {
|
||||
api *tunneld.API
|
||||
|
||||
resp := devtunnel.ServerResponse{
|
||||
Hostname: fmt.Sprintf("[%s]", serverIP.String()),
|
||||
ServerIP: serverIP,
|
||||
ServerPublicKey: hex.EncodeToString(pubBytes[:]),
|
||||
ClientIP: clientIP,
|
||||
server *httptest.Server
|
||||
}
|
||||
|
||||
func newTunnelServer(t *testing.T) *tunnelServer {
|
||||
var handler http.Handler
|
||||
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if handler != nil {
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
b, err := json.Marshal(&resp)
|
||||
assert.NoError(t, err)
|
||||
writer.WriteHeader(200)
|
||||
_, err = writer.Write(b)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
server := httptest.NewTLSServer(handler)
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
baseURLParsed, err := url.Parse(srv.URL)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "https", baseURLParsed.Scheme)
|
||||
baseURLParsed.Host = net.JoinHostPort("tunnel.coder.com", baseURLParsed.Port())
|
||||
|
||||
wireguardPort := freeUDPPort(t)
|
||||
|
||||
key, err := tunnelsdk.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
options := &tunneld.Options{
|
||||
BaseURL: baseURLParsed,
|
||||
WireguardEndpoint: fmt.Sprintf("127.0.0.1:%d", wireguardPort),
|
||||
WireguardPort: wireguardPort,
|
||||
WireguardKey: key,
|
||||
WireguardMTU: tunneld.DefaultWireguardMTU,
|
||||
WireguardServerIP: tunneld.DefaultWireguardServerIP,
|
||||
WireguardNetworkPrefix: tunneld.DefaultWireguardNetworkPrefix,
|
||||
}
|
||||
|
||||
td, err := tunneld.New(options)
|
||||
require.NoError(t, err)
|
||||
handler = td.Router()
|
||||
t.Cleanup(func() {
|
||||
server.Close()
|
||||
_ = td.Close()
|
||||
})
|
||||
return server
|
||||
}
|
||||
|
||||
func (f *fakeTunnelServer) config() devtunnel.Config {
|
||||
priv, err := wgtypes.GeneratePrivateKey()
|
||||
require.NoError(f.t, err)
|
||||
pub := priv.PublicKey()
|
||||
f.clients++
|
||||
assert.Equal(f.t, 1, f.clients) // only allow one client as we hardcode the address
|
||||
|
||||
err = f.device.IpcSet(fmt.Sprintf(`public_key=%x
|
||||
allowed_ip=%s/128`,
|
||||
pub[:],
|
||||
clientIP.String(),
|
||||
))
|
||||
require.NoError(f.t, err)
|
||||
return devtunnel.Config{
|
||||
Version: 1,
|
||||
PrivateKey: device.NoisePrivateKey(priv),
|
||||
PublicKey: device.NoisePublicKey(pub),
|
||||
Tunnel: devtunnel.Node{
|
||||
HostnameHTTPS: strings.TrimPrefix(f.server.URL, "https://"),
|
||||
HostnameWireguard: "localhost",
|
||||
WireguardPort: wgPort,
|
||||
},
|
||||
HTTPClient: f.server.Client(),
|
||||
return &tunnelServer{
|
||||
api: td,
|
||||
server: srv,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fakeTunnelServer) requestHTTP() (*http.Response, error) {
|
||||
func (s *tunnelServer) client() *http.Client {
|
||||
transport := &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
f.t.Log("Dial", network, addr)
|
||||
nc, err := f.tnet.DialContextTCPAddrPort(ctx, netip.AddrPortFrom(clientIP, 8090))
|
||||
assert.NoError(f.t, err)
|
||||
return nc, err
|
||||
return (&net.Dialer{}).DialContext(ctx, "tcp", s.server.Listener.Addr().String())
|
||||
},
|
||||
TLSClientConfig: &tls.Config{
|
||||
//nolint:gosec
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
client := &http.Client{
|
||||
return &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: testutil.WaitLong,
|
||||
}
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, fmt.Sprintf("http://[%s]:8090", clientIP), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
func (s *tunnelServer) config(t *testing.T, version tunnelsdk.TunnelVersion) devtunnel.Config {
|
||||
priv, err := tunnelsdk.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
privNoise, err := priv.NoisePrivateKey()
|
||||
require.NoError(t, err)
|
||||
pubNoise := priv.NoisePublicKey()
|
||||
|
||||
if version == 0 {
|
||||
version = tunnelsdk.TunnelVersionLatest
|
||||
}
|
||||
|
||||
return devtunnel.Config{
|
||||
Version: version,
|
||||
PrivateKey: privNoise,
|
||||
PublicKey: pubNoise,
|
||||
Tunnel: devtunnel.Node{
|
||||
RegionID: 0,
|
||||
ID: 1,
|
||||
HostnameHTTPS: s.api.BaseURL.Host,
|
||||
},
|
||||
HTTPClient: s.client(),
|
||||
}
|
||||
}
|
||||
|
||||
// requestTunnel performs the given request against the tunnel. The Host header
|
||||
// will be set to the tunnel's hostname.
|
||||
func (s *tunnelServer) requestTunnel(tunnel *tunnelsdk.Tunnel, req *http.Request) (*http.Response, error) {
|
||||
req.URL.Scheme = "https"
|
||||
req.URL.Host = tunnel.URL.Host
|
||||
req.Host = tunnel.URL.Host
|
||||
return s.client().Do(req)
|
||||
}
|
||||
|
|
|
@ -161,6 +161,7 @@ type DeploymentValues struct {
|
|||
Support SupportConfig `json:"support,omitempty" typescript:",notnull"`
|
||||
GitAuthProviders clibase.Struct[[]GitAuthConfig] `json:"git_auth,omitempty" typescript:",notnull"`
|
||||
SSHConfig SSHConfig `json:"config_ssh,omitempty" typescript:",notnull"`
|
||||
WgtunnelHost clibase.String `json:"wgtunnel_host,omitempty" typescript:",notnull"`
|
||||
|
||||
Config clibase.String `json:"config,omitempty" typescript:",notnull"`
|
||||
WriteConfig clibase.Bool `json:"write_config,omitempty" typescript:",notnull"`
|
||||
|
@ -1363,6 +1364,16 @@ Write out the current server configuration to the path specified by --config.`,
|
|||
Value: &c.GitAuthProviders,
|
||||
Hidden: true,
|
||||
},
|
||||
{
|
||||
Name: "Custom wgtunnel Host",
|
||||
Description: `Hostname of HTTPS server that runs https://github.com/coder/wgtunnel. By default, this will pick the best available wgtunnel server hosted by Coder. e.g. "tunnel.example.com".`,
|
||||
Flag: "wg-tunnel-host",
|
||||
Env: "WGTUNNEL_HOST",
|
||||
YAML: "wgtunnelHost",
|
||||
Value: &c.WgtunnelHost,
|
||||
Default: "", // empty string means pick best server
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -341,6 +341,7 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \
|
|||
},
|
||||
"update_check": true,
|
||||
"verbose": true,
|
||||
"wgtunnel_host": "string",
|
||||
"wildcard_access_url": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
|
|
|
@ -1873,6 +1873,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
|||
},
|
||||
"update_check": true,
|
||||
"verbose": true,
|
||||
"wgtunnel_host": "string",
|
||||
"wildcard_access_url": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
|
@ -2218,6 +2219,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
|||
},
|
||||
"update_check": true,
|
||||
"verbose": true,
|
||||
"wgtunnel_host": "string",
|
||||
"wildcard_access_url": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
|
@ -2284,6 +2286,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
|||
| `trace` | [codersdk.TraceConfig](#codersdktraceconfig) | false | | |
|
||||
| `update_check` | boolean | false | | |
|
||||
| `verbose` | boolean | false | | |
|
||||
| `wgtunnel_host` | string | false | | |
|
||||
| `wildcard_access_url` | [clibase.URL](#clibaseurl) | false | | |
|
||||
| `write_config` | boolean | false | | |
|
||||
|
||||
|
|
39
go.mod
39
go.mod
|
@ -56,7 +56,7 @@ replace github.com/imulab/go-scim/pkg/v2 => github.com/coder/go-scim/pkg/v2 v2.0
|
|||
|
||||
require (
|
||||
cdr.dev/slog v1.4.2-0.20230228204227-60d22dceaf04
|
||||
cloud.google.com/go/compute/metadata v0.2.1
|
||||
cloud.google.com/go/compute/metadata v0.2.3
|
||||
github.com/AlecAivazis/survey/v2 v2.3.5
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/adrg/xdg v0.4.0
|
||||
|
@ -76,11 +76,12 @@ require (
|
|||
github.com/coder/flog v1.0.0
|
||||
github.com/coder/retry v1.3.1-0.20230210155434-e90a2e1e091d
|
||||
github.com/coder/terraform-provider-coder v0.6.20
|
||||
github.com/coder/wgtunnel v0.1.5
|
||||
github.com/coreos/go-oidc/v3 v3.4.0
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||
github.com/creack/pty v1.1.18
|
||||
github.com/elastic/go-sysinfo v1.9.0
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/fatih/color v1.14.1
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/fatih/structtag v1.2.0
|
||||
github.com/fergusstrange/embedded-postgres v1.16.0
|
||||
|
@ -89,7 +90,7 @@ require (
|
|||
github.com/gliderlabs/ssh v0.3.4
|
||||
github.com/go-chi/chi v1.5.4
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
github.com/go-chi/httprate v0.7.0
|
||||
github.com/go-chi/httprate v0.7.1
|
||||
github.com/go-chi/render v1.0.1
|
||||
github.com/go-logr/logr v1.2.3
|
||||
github.com/go-ping/ping v1.1.0
|
||||
|
@ -156,18 +157,17 @@ require (
|
|||
golang.org/x/crypto v0.6.0
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db
|
||||
golang.org/x/mod v0.8.0
|
||||
golang.org/x/oauth2 v0.3.0
|
||||
golang.org/x/oauth2 v0.5.0
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/sys v0.5.0
|
||||
golang.org/x/term v0.5.0
|
||||
golang.org/x/text v0.7.0
|
||||
golang.org/x/tools v0.6.0
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230207233929-ebbd4a433088
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b
|
||||
google.golang.org/api v0.103.0
|
||||
google.golang.org/grpc v1.52.3
|
||||
google.golang.org/protobuf v1.28.1
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675
|
||||
google.golang.org/api v0.108.0
|
||||
google.golang.org/grpc v1.53.0
|
||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
@ -179,8 +179,10 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/logging v1.6.1 // indirect
|
||||
github.com/dgraph-io/badger/v3 v3.2103.5 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/golang/glog v1.0.0 // indirect
|
||||
github.com/google/flatbuffers v23.1.21+incompatible // indirect
|
||||
github.com/h2non/filetype v1.1.3 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
|
@ -190,10 +192,11 @@ require (
|
|||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.12.1 // indirect
|
||||
cloud.google.com/go/compute v1.18.0 // indirect
|
||||
cloud.google.com/go/longrunning v0.3.0 // indirect
|
||||
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
|
@ -221,7 +224,7 @@ require (
|
|||
github.com/containerd/continuity v0.3.0 // indirect
|
||||
github.com/coreos/go-iptables v0.6.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.7.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.8.1 // indirect
|
||||
github.com/docker/cli v20.10.17+incompatible // indirect
|
||||
github.com/docker/docker v20.10.17+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
|
@ -244,13 +247,13 @@ require (
|
|||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect
|
||||
|
@ -278,11 +281,11 @@ require (
|
|||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mdlayher/genetlink v1.2.0 // indirect
|
||||
github.com/mdlayher/netlink v1.6.0 // indirect
|
||||
github.com/mdlayher/netlink v1.6.2 // indirect
|
||||
github.com/mdlayher/sdnotify v1.0.0 // indirect
|
||||
github.com/mdlayher/socket v0.2.3 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
|
@ -346,11 +349,11 @@ require (
|
|||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||
go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
inet.af/peercred v0.0.0-20210906144145-0893ea02156a // indirect
|
||||
|
|
74
go.sum
74
go.sum
|
@ -50,15 +50,17 @@ cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6m
|
|||
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
|
||||
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
|
||||
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
|
||||
cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0=
|
||||
cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
|
||||
cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48=
|
||||
cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
|
||||
cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY=
|
||||
cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU=
|
||||
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
|
||||
cloud.google.com/go/logging v1.6.1 h1:ZBsZK+JG+oCDT+vaxwqF2egKNRjz8soXiS6Xv79benI=
|
||||
cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw=
|
||||
cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs=
|
||||
cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
|
@ -380,6 +382,8 @@ github.com/coder/tailscale v1.1.1-0.20230321171725-fed359a0cafa h1:EjRGgTz7BUECm
|
|||
github.com/coder/tailscale v1.1.1-0.20230321171725-fed359a0cafa/go.mod h1:jpg+77g19FpXL43U1VoIqoSg1K/Vh5CVxycGldQ8KhA=
|
||||
github.com/coder/terraform-provider-coder v0.6.20 h1:bVyITX9JlbnGzKzTj0qi/JziUCGqD2DiN3cXaWyDcxE=
|
||||
github.com/coder/terraform-provider-coder v0.6.20/go.mod h1:UIfU3bYNeSzJJvHyJ30tEKjD6Z9utloI+HUM/7n94CY=
|
||||
github.com/coder/wgtunnel v0.1.5 h1:WP3sCj/3iJ34eKvpMQEp1oJHvm24RYh0NHbj1kfUKfs=
|
||||
github.com/coder/wgtunnel v0.1.5/go.mod h1:bokoUrHnUFY4lu9KOeSYiIcHTI2MO1KwqumU4DPDyJI=
|
||||
github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
|
||||
github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
|
||||
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
|
||||
|
@ -542,8 +546,8 @@ github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8
|
|||
github.com/dhui/dktest v0.3.10 h1:0frpeeoM9pHouHjhLeZDuDTJ0PqjDTrycaHaMmkJAo8=
|
||||
github.com/dhui/dktest v0.3.10/go.mod h1:h5Enh0nG3Qbo9WjNFRrwmKUaePEBhXMOygbz3Ww7Sz0=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0=
|
||||
github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M=
|
||||
|
@ -603,8 +607,9 @@ github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:Pjfxu
|
|||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
|
||||
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||
|
@ -653,8 +658,8 @@ github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs=
|
|||
github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg=
|
||||
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/httprate v0.7.0 h1:8W0dF7Xa2Duz2p8ncGaehIphrxQGNlOtoGY0+NRRfjQ=
|
||||
github.com/go-chi/httprate v0.7.0/go.mod h1:6GOYBSwnpra4CQfAKXu8sQZg+nZ0M1g9QnyFvxrAB8A=
|
||||
github.com/go-chi/httprate v0.7.1 h1:d5kXARdms2PREQfU4pHvq44S6hJ1hPu4OXLeBKmCKWs=
|
||||
github.com/go-chi/httprate v0.7.1/go.mod h1:6GOYBSwnpra4CQfAKXu8sQZg+nZ0M1g9QnyFvxrAB8A=
|
||||
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||
github.com/go-critic/go-critic v0.6.1/go.mod h1:SdNCfU0yF3UBjtaZGw6586/WocupMOJuiqgom5DsQxM=
|
||||
|
@ -872,8 +877,9 @@ github.com/golangci/revgrep v0.0.0-20210930125155-c22e5001d4f2/go.mod h1:LK+zW4M
|
|||
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs=
|
||||
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
|
@ -938,8 +944,8 @@ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
|||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
|
@ -947,6 +953,7 @@ github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0
|
|||
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
|
||||
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
|
||||
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
|
||||
github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=
|
||||
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
||||
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
|
||||
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
|
||||
|
@ -993,8 +1000,9 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t
|
|||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.1 h1:I6ITHEanAwjB0FvaxmGm8pKqmCLR7QIe05ZmO4QAXMw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.1/go.mod h1:gYC+WX4YJFarA2ie73G2epzt7TBWpo9pzcBnK1g0MSw=
|
||||
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
||||
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||
|
@ -1326,8 +1334,9 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
|
|||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
|
@ -1375,8 +1384,9 @@ github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE
|
|||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
||||
github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0=
|
||||
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
|
||||
github.com/mdlayher/netlink v1.6.2 h1:D2zGSkvYsJ6NreeED3JiVTu1lj2sIYATqSaZlhPzUgQ=
|
||||
github.com/mdlayher/netlink v1.6.2/go.mod h1:O1HXX2sIWSMJ3Qn1BYZk1yZM+7iMki/uYGGiwGyq/iU=
|
||||
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c=
|
||||
|
@ -2219,6 +2229,7 @@ golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug
|
|||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
|
@ -2246,8 +2257,8 @@ golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j
|
|||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
|
||||
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
|
||||
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
|
||||
golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
|
||||
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -2261,6 +2272,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -2453,8 +2465,8 @@ golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxb
|
|||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@ -2589,10 +2601,10 @@ golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3j
|
|||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230207233929-ebbd4a433088 h1:AterY3udavhM90Dum0CUGAD5ZuYahpmBuYCPT3Pwe3A=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230207233929-ebbd4a433088/go.mod h1:whfbyDBt09xhCYQWtO2+3UVjlaq6/9hDZrjg2ZE6SyA=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b h1:9JncmKXcUwE918my+H6xmjBdhK2jM/UTUNXxhRG1BAk=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b/go.mod h1:yp4gl6zOlnDGOZeWeDfMwQcsdOIQnMdhuPx9mwwWBL4=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675 h1:/J/RVnr7ng4fWPRH3xa4WtBJ1Jp+Auu4YNLmGiPv5QU=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675/go.mod h1:whfbyDBt09xhCYQWtO2+3UVjlaq6/9hDZrjg2ZE6SyA=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde h1:ybF7AMzIUikL9x4LgwEmzhXtzRpKNqngme1VGDWz+Nk=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
|
@ -2644,8 +2656,8 @@ google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69
|
|||
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
|
||||
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
|
||||
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
|
||||
google.golang.org/api v0.103.0 h1:9yuVqlu2JCvcLg9p8S3fcFLZij8EPSyvODIY1rkMizQ=
|
||||
google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=
|
||||
google.golang.org/api v0.108.0 h1:WVBc/faN0DkKtR43Q/7+tPny9ZoLZdIiAyG5Q9vFClg=
|
||||
google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
|
||||
google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
|
@ -2753,8 +2765,8 @@ google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP
|
|||
google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 h1:a2S6M0+660BgMNl++4JPlcAO/CjkqYItDEZwkoDQK7c=
|
||||
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
|
||||
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 h1:znp6mq/drrY+6khTAlJUDNFFcDGV2ENLYKpMq8SyCds=
|
||||
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
|
||||
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
|
@ -2794,8 +2806,8 @@ google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11
|
|||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.52.3 h1:pf7sOysg4LdgBqduXveGKrcEwbStiK2rtfghdzlUYDQ=
|
||||
google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
|
||||
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
|
||||
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
@ -2811,8 +2823,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
|||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d h1:qp0AnQCvRCMlu9jBjtdbTaaEmThIgZOrbVyDEOcmKhQ=
|
||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
@ -362,6 +362,7 @@ export interface DeploymentValues {
|
|||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type
|
||||
readonly git_auth?: any
|
||||
readonly config_ssh?: SSHConfig
|
||||
readonly wgtunnel_host?: string
|
||||
readonly config?: string
|
||||
readonly write_config?: boolean
|
||||
// Named type "github.com/coder/coder/cli/clibase.HostPort" unknown, using "any"
|
||||
|
|
Loading…
Reference in New Issue