Compare commits
1 Commits
tom/integr
...
bradfitz/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccdc41988c |
2
.github/workflows/cifuzz.yml
vendored
2
.github/workflows/cifuzz.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
dry-run: false
|
||||
language: go
|
||||
- name: Upload Crash
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
|
||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
4
.github/workflows/cross-darwin.yml
vendored
4
.github/workflows/cross-darwin.yml
vendored
@@ -17,13 +17,13 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2.1.5
|
||||
with:
|
||||
go-version: 1.18
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: macOS build cmd
|
||||
env:
|
||||
|
||||
4
.github/workflows/cross-freebsd.yml
vendored
4
.github/workflows/cross-freebsd.yml
vendored
@@ -17,13 +17,13 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2.1.5
|
||||
with:
|
||||
go-version: 1.18
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: FreeBSD build cmd
|
||||
env:
|
||||
|
||||
4
.github/workflows/cross-openbsd.yml
vendored
4
.github/workflows/cross-openbsd.yml
vendored
@@ -17,13 +17,13 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2.1.5
|
||||
with:
|
||||
go-version: 1.18
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: OpenBSD build cmd
|
||||
env:
|
||||
|
||||
4
.github/workflows/cross-windows.yml
vendored
4
.github/workflows/cross-windows.yml
vendored
@@ -17,13 +17,13 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2.1.5
|
||||
with:
|
||||
go-version: 1.18
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Windows build cmd
|
||||
env:
|
||||
|
||||
4
.github/workflows/depaware.yml
vendored
4
.github/workflows/depaware.yml
vendored
@@ -14,12 +14,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2.1.5
|
||||
with:
|
||||
go-version: 1.18
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: depaware tailscaled
|
||||
run: go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscaled
|
||||
|
||||
18
.github/workflows/go-generate-without-stringer.sh
vendored
Executable file
18
.github/workflows/go-generate-without-stringer.sh
vendored
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env sh
|
||||
#
|
||||
# This is a temporary hack to work around
|
||||
# https://github.com/golang/go/issues/51629 , wherein the stringer
|
||||
# generator doesn't work with generics.
|
||||
#
|
||||
# This script is the equivalent of `go generate ./...`, except that it
|
||||
# only runs generate on packages that don't try to use stringer.
|
||||
|
||||
set -e
|
||||
|
||||
find . -name '*.go' | xargs grep -l go:generate | xargs -n1 dirname | sort -u | while read dir; do
|
||||
if ! egrep "cmd/(stringer|cloner)" $dir/*.go; then
|
||||
set -x
|
||||
go generate -tags=hermetic $dir
|
||||
set +x
|
||||
fi
|
||||
done
|
||||
17
.github/workflows/go_generate.yml
vendored
17
.github/workflows/go_generate.yml
vendored
@@ -15,24 +15,23 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2.1.5
|
||||
with:
|
||||
go-version: 1.18
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: check 'go generate' is clean
|
||||
# The shell script invocation below is a temporary hack for
|
||||
# https://github.com/tailscale/tailscale/issues/4194. When
|
||||
# that issue is fixed, replace its invocation with:
|
||||
# go generate --tags=hermetic ./...
|
||||
run: |
|
||||
if [[ "${{github.ref}}" == release-branch/* ]]
|
||||
then
|
||||
pkgs=$(go list ./... | grep -v dnsfallback)
|
||||
else
|
||||
pkgs=$(go list ./... | grep -v dnsfallback)
|
||||
fi
|
||||
go generate $pkgs
|
||||
set -e
|
||||
./.github/workflows/go-generate-without-stringer.sh
|
||||
echo
|
||||
echo
|
||||
git diff --name-only --exit-code || (echo "The files above need updating. Please run 'go generate'."; exit 1)
|
||||
|
||||
4
.github/workflows/license.yml
vendored
4
.github/workflows/license.yml
vendored
@@ -14,12 +14,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2.1.5
|
||||
with:
|
||||
go-version: 1.18
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Run license checker
|
||||
run: ./scripts/check_license_headers.sh .
|
||||
|
||||
4
.github/workflows/linux-race.yml
vendored
4
.github/workflows/linux-race.yml
vendored
@@ -17,13 +17,13 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2.1.5
|
||||
with:
|
||||
go-version: 1.18
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Basic build
|
||||
run: go build ./cmd/...
|
||||
|
||||
4
.github/workflows/linux.yml
vendored
4
.github/workflows/linux.yml
vendored
@@ -17,13 +17,13 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2.1.5
|
||||
with:
|
||||
go-version: 1.18
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Basic build
|
||||
run: go build ./cmd/...
|
||||
|
||||
4
.github/workflows/linux32.yml
vendored
4
.github/workflows/linux32.yml
vendored
@@ -17,13 +17,13 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2.1.5
|
||||
with:
|
||||
go-version: 1.18
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Basic build
|
||||
run: GOARCH=386 go build ./cmd/...
|
||||
|
||||
4
.github/workflows/staticcheck.yml
vendored
4
.github/workflows/staticcheck.yml
vendored
@@ -14,12 +14,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2.1.5
|
||||
with:
|
||||
go-version: 1.18
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Run go vet
|
||||
run: go vet ./...
|
||||
|
||||
10
.github/workflows/vm.yml
vendored
10
.github/workflows/vm.yml
vendored
@@ -16,19 +16,15 @@ jobs:
|
||||
run: echo "GOPATH=$HOME/go" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2.1.5
|
||||
with:
|
||||
go-version: 1.18
|
||||
- name: Set up Nix
|
||||
uses: cachix/install-nix-action@v17
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-21.11
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Run VM tests
|
||||
run: go test ./tstest/integration/vms -v --timeout=20m --no-s3 --run-vm-tests --distro-regex '(nix|ubuntu)'
|
||||
run: go test ./tstest/integration/vms -v -no-s3 -run-vm-tests -run=TestRunUbuntu2004
|
||||
env:
|
||||
HOME: "/tmp"
|
||||
TMPDIR: "/tmp"
|
||||
|
||||
6
.github/workflows/windows-race.yml
vendored
6
.github/workflows/windows-race.yml
vendored
@@ -17,15 +17,15 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2.1.5
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Restore Cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
# Note: unlike some other setups, this is only grabbing the mod download
|
||||
# cache, rather than the whole mod directory, as the download cache
|
||||
|
||||
6
.github/workflows/windows.yml
vendored
6
.github/workflows/windows.yml
vendored
@@ -17,15 +17,15 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2.1.5
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Restore Cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
# Note: unlike some other setups, this is only grabbing the mod download
|
||||
# cache, rather than the whole mod directory, as the download cache
|
||||
|
||||
3
Makefile
3
Makefile
@@ -8,9 +8,6 @@ usage:
|
||||
vet:
|
||||
./tool/go vet ./...
|
||||
|
||||
tidy:
|
||||
./tool/go mod tidy -compat=1.17
|
||||
|
||||
updatedeps:
|
||||
./tool/go run github.com/tailscale/depaware --update tailscale.com/cmd/tailscaled
|
||||
./tool/go run github.com/tailscale/depaware --update tailscale.com/cmd/tailscale
|
||||
|
||||
4
api.md
4
api.md
@@ -636,9 +636,11 @@ POST /api/v2/tailnet/example.com/acl/validate
|
||||
curl 'https://api.tailscale.com/api/v2/tailnet/example.com/acl/validate' \
|
||||
-u "tskey-yourapikey123:" \
|
||||
--data-binary '
|
||||
{
|
||||
[
|
||||
{"User": "user1@example.com", "Allow": ["example-host-1:22"], "Deny": ["example-host-2:100"]}
|
||||
]'
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
@@ -32,7 +31,6 @@ import (
|
||||
"tailscale.com/client/tailscale/apitype"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/paths"
|
||||
"tailscale.com/safesocket"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -185,7 +183,6 @@ func send(ctx context.Context, method, path string, wantStatus int, body io.Read
|
||||
return nil, err
|
||||
}
|
||||
if res.StatusCode != wantStatus {
|
||||
err = fmt.Errorf("%v: %s", res.Status, bytes.TrimSpace(slurp))
|
||||
return nil, bestError(err, slurp)
|
||||
}
|
||||
return slurp, nil
|
||||
@@ -275,21 +272,6 @@ func status(ctx context.Context, queryString string) (*ipnstate.Status, error) {
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// IDToken is a request to get an OIDC ID token for an audience.
|
||||
// The token can be presented to any resource provider which offers OIDC
|
||||
// Federation.
|
||||
func IDToken(ctx context.Context, aud string) (*tailcfg.TokenResponse, error) {
|
||||
body, err := get200(ctx, "/localapi/v0/id-token?aud="+url.QueryEscape(aud))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr := new(tailcfg.TokenResponse)
|
||||
if err := json.Unmarshal(body, tr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
func WaitingFiles(ctx context.Context) ([]apitype.WaitingFile, error) {
|
||||
body, err := get200(ctx, "/localapi/v0/files/")
|
||||
if err != nil {
|
||||
@@ -436,60 +418,6 @@ func SetDNS(ctx context.Context, name, value string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// DialTCP connects to the host's port via Tailscale.
|
||||
//
|
||||
// The host may be a base DNS name (resolved from the netmap inside
|
||||
// tailscaled), a FQDN, or an IP address.
|
||||
//
|
||||
// The ctx is only used for the duration of the call, not the lifetime of the net.Conn.
|
||||
func DialTCP(ctx context.Context, host string, port uint16) (net.Conn, error) {
|
||||
connCh := make(chan net.Conn, 1)
|
||||
trace := httptrace.ClientTrace{
|
||||
GotConn: func(info httptrace.GotConnInfo) {
|
||||
connCh <- info.Conn
|
||||
},
|
||||
}
|
||||
ctx = httptrace.WithClientTrace(ctx, &trace)
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", "http://local-tailscaled.sock/localapi/v0/dial", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header = http.Header{
|
||||
"Upgrade": []string{"ts-dial"},
|
||||
"Connection": []string{"upgrade"},
|
||||
"Dial-Host": []string{host},
|
||||
"Dial-Port": []string{fmt.Sprint(port)},
|
||||
}
|
||||
res, err := DoLocalRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.StatusCode != http.StatusSwitchingProtocols {
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return nil, fmt.Errorf("unexpected HTTP response: %s, %s", res.Status, body)
|
||||
}
|
||||
// From here on, the underlying net.Conn is ours to use, but there
|
||||
// is still a read buffer attached to it within resp.Body. So, we
|
||||
// must direct I/O through resp.Body, but we can still use the
|
||||
// underlying net.Conn for stuff like deadlines.
|
||||
var switchedConn net.Conn
|
||||
select {
|
||||
case switchedConn = <-connCh:
|
||||
default:
|
||||
}
|
||||
if switchedConn == nil {
|
||||
res.Body.Close()
|
||||
return nil, fmt.Errorf("httptrace didn't provide a connection")
|
||||
}
|
||||
rwc, ok := res.Body.(io.ReadWriteCloser)
|
||||
if !ok {
|
||||
res.Body.Close()
|
||||
return nil, errors.New("http Transport did not provide a writable body")
|
||||
}
|
||||
return netutil.NewAltReadWriteCloserConn(rwc, switchedConn), nil
|
||||
}
|
||||
|
||||
// CurrentDERPMap returns the current DERPMap that is being used by the local tailscaled.
|
||||
// It is intended to be used with netcheck to see availability of DERPs.
|
||||
func CurrentDERPMap(ctx context.Context) (*tailcfg.DERPMap, error) {
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
"github.com/goreleaser/nfpm"
|
||||
_ "github.com/goreleaser/nfpm/deb"
|
||||
_ "github.com/goreleaser/nfpm/rpm"
|
||||
"github.com/pborman/getopt"
|
||||
)
|
||||
|
||||
// parseFiles parses a comma-separated list of colon-separated pairs
|
||||
@@ -44,21 +44,19 @@ func parseEmptyDirs(s string) []string {
|
||||
}
|
||||
|
||||
func main() {
|
||||
out := flag.String("out", "", "output file to write")
|
||||
name := flag.String("name", "tailscale", "package name")
|
||||
description := flag.String("description", "The easiest, most secure, cross platform way to use WireGuard + oauth2 + 2FA/SSO", "package description")
|
||||
goarch := flag.String("arch", "amd64", "GOARCH this package is for")
|
||||
pkgType := flag.String("type", "deb", "type of package to build (deb or rpm)")
|
||||
files := flag.String("files", "", "comma-separated list of files in src:dst form")
|
||||
configFiles := flag.String("configs", "", "like --files, but for files marked as user-editable config files")
|
||||
emptyDirs := flag.String("emptydirs", "", "comma-separated list of empty directories")
|
||||
version := flag.String("version", "0.0.0", "version of the package")
|
||||
postinst := flag.String("postinst", "", "debian postinst script path")
|
||||
prerm := flag.String("prerm", "", "debian prerm script path")
|
||||
postrm := flag.String("postrm", "", "debian postrm script path")
|
||||
replaces := flag.String("replaces", "", "package which this package replaces, if any")
|
||||
depends := flag.String("depends", "", "comma-separated list of packages this package depends on")
|
||||
flag.Parse()
|
||||
out := getopt.StringLong("out", 'o', "", "output file to write")
|
||||
goarch := getopt.StringLong("arch", 'a', "amd64", "GOARCH this package is for")
|
||||
pkgType := getopt.StringLong("type", 't', "deb", "type of package to build (deb or rpm)")
|
||||
files := getopt.StringLong("files", 'F', "", "comma-separated list of files in src:dst form")
|
||||
configFiles := getopt.StringLong("configs", 'C', "", "like --files, but for files marked as user-editable config files")
|
||||
emptyDirs := getopt.StringLong("emptydirs", 'E', "", "comma-separated list of empty directories")
|
||||
version := getopt.StringLong("version", 0, "0.0.0", "version of the package")
|
||||
postinst := getopt.StringLong("postinst", 0, "", "debian postinst script path")
|
||||
prerm := getopt.StringLong("prerm", 0, "", "debian prerm script path")
|
||||
postrm := getopt.StringLong("postrm", 0, "", "debian postrm script path")
|
||||
replaces := getopt.StringLong("replaces", 0, "", "package which this package replaces, if any")
|
||||
depends := getopt.StringLong("depends", 0, "", "comma-separated list of packages this package depends on")
|
||||
getopt.Parse()
|
||||
|
||||
filesMap, err := parseFiles(*files)
|
||||
if err != nil {
|
||||
@@ -70,12 +68,12 @@ func main() {
|
||||
}
|
||||
emptyDirList := parseEmptyDirs(*emptyDirs)
|
||||
info := nfpm.WithDefaults(&nfpm.Info{
|
||||
Name: *name,
|
||||
Name: "tailscale",
|
||||
Arch: *goarch,
|
||||
Platform: "linux",
|
||||
Version: *version,
|
||||
Maintainer: "Tailscale Inc <info@tailscale.com>",
|
||||
Description: *description,
|
||||
Description: "The easiest, most secure, cross platform way to use WireGuard + oauth2 + 2FA/SSO",
|
||||
Homepage: "https://www.tailscale.com",
|
||||
License: "MIT",
|
||||
Overridables: nfpm.Overridables{
|
||||
|
||||
4
cmd/nginx-auth/.gitignore
vendored
4
cmd/nginx-auth/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
nga.sock
|
||||
*.deb
|
||||
*.rpm
|
||||
tailscale.nginx-auth
|
||||
@@ -1,135 +0,0 @@
|
||||
# nginx-auth
|
||||
|
||||
This is a tool that allows users to use Tailscale Whois authentication with
|
||||
NGINX as a reverse proxy. This allows users that already have a bunch of
|
||||
services hosted on an internal NGINX server to point those domains to the
|
||||
Tailscale IP of the NGINX server and then seamlessly use Tailscale for
|
||||
authentication.
|
||||
|
||||
Many thanks to [@zrail](https://twitter.com/zrail/status/1511788463586222087) on
|
||||
Twitter for introducing the basic idea and offering some sample code. This
|
||||
program is based on that sample code with security enhancements. Namely:
|
||||
|
||||
* This listens over a UNIX socket instead of a TCP socket, to prevent
|
||||
leakage to the network
|
||||
* This uses systemd socket activation so that systemd owns the socket
|
||||
and can then lock down the service to the bare minimum required to do
|
||||
its job without having to worry about dropping permissions
|
||||
* This provides additional information in HTTP response headers that can
|
||||
be useful for integrating with various services
|
||||
|
||||
## Configuration
|
||||
|
||||
In order to protect a service with this tool, do the following in the respective
|
||||
`server` block:
|
||||
|
||||
Create an authentication location with the `internal` flag set:
|
||||
|
||||
```nginx
|
||||
location /auth {
|
||||
internal;
|
||||
|
||||
proxy_pass http://unix:/run/tailscale.nginx-auth.sock;
|
||||
proxy_pass_request_body off;
|
||||
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Remote-Addr $remote_addr;
|
||||
proxy_set_header Remote-Port $remote_port;
|
||||
proxy_set_header Original-URI $request_uri;
|
||||
}
|
||||
```
|
||||
|
||||
Then add the following to the `location /` block:
|
||||
|
||||
```
|
||||
auth_request /auth;
|
||||
auth_request_set $auth_user $upstream_http_tailscale_user;
|
||||
auth_request_set $auth_name $upstream_http_tailscale_name;
|
||||
auth_request_set $auth_login $upstream_http_tailscale_login;
|
||||
auth_request_set $auth_tailnet $upstream_http_tailscale_tailnet;
|
||||
auth_request_set $auth_profile_picture $upstream_http_tailscale_profile_picture;
|
||||
|
||||
proxy_set_header X-Webauth-User "$auth_user";
|
||||
proxy_set_header X-Webauth-Name "$auth_name";
|
||||
proxy_set_header X-Webauth-Login "$auth_login";
|
||||
proxy_set_header X-Webauth-Tailnet "$auth_tailnet";
|
||||
proxy_set_header X-Webauth-Profile-Picture "$auth_profile_picture";
|
||||
```
|
||||
|
||||
When this configuration is used with a Go HTTP handler such as this:
|
||||
|
||||
```go
|
||||
http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
|
||||
e := json.NewEncoder(w)
|
||||
e.SetIndent("", " ")
|
||||
e.Encode(r.Header)
|
||||
})
|
||||
```
|
||||
|
||||
You will get output like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"Accept": [
|
||||
"*/*"
|
||||
],
|
||||
"Connection": [
|
||||
"upgrade"
|
||||
],
|
||||
"User-Agent": [
|
||||
"curl/7.82.0"
|
||||
],
|
||||
"X-Webauth-Login": [
|
||||
"Xe"
|
||||
],
|
||||
"X-Webauth-Name": [
|
||||
"Xe Iaso"
|
||||
],
|
||||
"X-Webauth-Profile-Picture": [
|
||||
"https://avatars.githubusercontent.com/u/529003?v=4"
|
||||
],
|
||||
"X-Webauth-Tailnet": [
|
||||
"cetacean.org.github"
|
||||
]
|
||||
"X-Webauth-User": [
|
||||
"Xe@github"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Headers
|
||||
|
||||
The authentication service provides the following headers to decorate your
|
||||
proxied requests:
|
||||
|
||||
| Header | Example Value | Description |
|
||||
| :------ | :-------------- | :---------- |
|
||||
| `Tailscale-User` | `azurediamond@hunter2.net` | The Tailscale username the remote machine is logged in as in user@host form |
|
||||
| `Tailscale-Login` | `azurediamond` | The user portion of the Tailscale username the remote machine is logged in as |
|
||||
| `Tailscale-Name` | `Azure Diamond` | The "real name" of the Tailscale user the machine is logged in as |
|
||||
| `Tailscale-Profile-Picture` | `https://i.kym-cdn.com/photos/images/newsfeed/001/065/963/ae0.png` | The profile picture provided by the Identity Provider your tailnet uses |
|
||||
| `Tailscale-Tailnet` | `hunter2.net` | The tailnet name |
|
||||
|
||||
Most of the time you can set `X-Webauth-User` to the contents of the
|
||||
`Tailscale-User` header, but some services may not accept a username with an `@`
|
||||
symbol in it. If this is the case, set `X-Webauth-User` to the `Tailscale-Login`
|
||||
header.
|
||||
|
||||
The `Tailscale-Tailnet` header can help you identify which tailnet the session
|
||||
is coming from. If you are using node sharing, this can help you make sure that
|
||||
you aren't giving administrative access to people outside your tailnet. You will
|
||||
need to be sure to check this in your application code. If you use OpenResty,
|
||||
you may be able to do more complicated access controls than you can with NGINX
|
||||
alone.
|
||||
|
||||
## Building
|
||||
|
||||
Install `cmd/mkpkg`:
|
||||
|
||||
```
|
||||
cd .. && go install ./mkpkg
|
||||
```
|
||||
|
||||
Then run `./mkdeb.sh`. It will emit a `.deb` and `.rpm` package for amd64
|
||||
machines (Linux uname flag: `x86_64`). You can add these to your deployment
|
||||
methods as you see fit.
|
||||
@@ -1,23 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o tailscale.nginx-auth .
|
||||
|
||||
mkpkg \
|
||||
--out tailscale-nginx-auth-0.1.0-amd64.deb \
|
||||
--name=tailscale-nginx-auth \
|
||||
--version=0.1.0 \
|
||||
--type=deb\
|
||||
--arch=amd64 \
|
||||
--description="Tailscale NGINX authentication protocol handler" \
|
||||
--files=./tailscale.nginx-auth:/usr/sbin/tailscale.nginx-auth,./tailscale.nginx-auth.socket:/lib/systemd/system/tailscale.nginx-auth.socket,./tailscale.nginx-auth.service:/lib/systemd/system/tailscale.nginx-auth.service
|
||||
|
||||
mkpkg \
|
||||
--out tailscale-nginx-auth-0.1.0-amd64.rpm \
|
||||
--name=tailscale-nginx-auth \
|
||||
--version=0.1.0 \
|
||||
--type=rpm \
|
||||
--arch=amd64 \
|
||||
--description="Tailscale NGINX authentication protocol handler" \
|
||||
--files=./tailscale.nginx-auth:/usr/sbin/tailscale.nginx-auth,./tailscale.nginx-auth.socket:/lib/systemd/system/tailscale.nginx-auth.socket,./tailscale.nginx-auth.service:/lib/systemd/system/tailscale.nginx-auth.service
|
||||
@@ -1,120 +0,0 @@
|
||||
// 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 linux
|
||||
|
||||
// Command nginx-auth is a tool that allows users to use Tailscale Whois
|
||||
// authentication with NGINX as a reverse proxy. This allows users that
|
||||
// already have a bunch of services hosted on an internal NGINX server
|
||||
// to point those domains to the Tailscale IP of the NGINX server and
|
||||
// then seamlessly use Tailscale for authentication.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-systemd/activation"
|
||||
"tailscale.com/client/tailscale"
|
||||
)
|
||||
|
||||
var (
|
||||
sockPath = flag.String("sockpath", "", "the filesystem path for the unix socket this service exposes")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
remoteHost := r.Header.Get("Remote-Addr")
|
||||
remotePort := r.Header.Get("Remote-Port")
|
||||
if remoteHost == "" || remotePort == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
log.Println("set Remote-Addr to $remote_addr and Remote-Port to $remote_port in your nginx config")
|
||||
return
|
||||
}
|
||||
|
||||
remoteAddrStr := net.JoinHostPort(remoteHost, remotePort)
|
||||
remoteAddr, err := netip.ParseAddrPort(remoteAddrStr)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
log.Printf("remote address and port are not valid: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
info, err := tailscale.WhoIs(r.Context(), remoteAddr.String())
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
log.Printf("can't look up %s: %v", remoteAddr, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(info.Node.Tags) != 0 {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
log.Printf("node %s is tagged", info.Node.Hostinfo.Hostname())
|
||||
return
|
||||
}
|
||||
|
||||
_, tailnet, ok := strings.Cut(info.Node.Name, info.Node.ComputedName+".")
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
log.Printf("can't extract tailnet name from hostname %q", info.Node.Name)
|
||||
return
|
||||
}
|
||||
tailnet, _, ok = strings.Cut(tailnet, ".beta.tailscale.net")
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
log.Printf("can't extract tailnet name from hostname %q", info.Node.Name)
|
||||
return
|
||||
}
|
||||
|
||||
h := w.Header()
|
||||
h.Set("Tailscale-Login", strings.Split(info.UserProfile.LoginName, "@")[0])
|
||||
h.Set("Tailscale-User", info.UserProfile.LoginName)
|
||||
h.Set("Tailscale-Name", info.UserProfile.DisplayName)
|
||||
h.Set("Tailscale-Profile-Picture", info.UserProfile.ProfilePicURL)
|
||||
h.Set("Tailscale-Tailnet", tailnet)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
|
||||
if *sockPath != "" {
|
||||
_ = os.Remove(*sockPath) // ignore error, this file may not already exist
|
||||
ln, err := net.Listen("unix", *sockPath)
|
||||
if err != nil {
|
||||
log.Fatalf("can't listen on %s: %v", *sockPath, err)
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
log.Printf("listening on %s", *sockPath)
|
||||
log.Fatal(http.Serve(ln, mux))
|
||||
}
|
||||
|
||||
listeners, err := activation.Listeners()
|
||||
if err != nil {
|
||||
log.Fatalf("no sockets passed to this service with systemd: %v", err)
|
||||
}
|
||||
|
||||
// NOTE(Xe): normally you'd want to make a waitgroup here and then register
|
||||
// each listener with it. In this case I want this to blow up horribly if
|
||||
// any of the listeners stop working. systemd will restart it due to the
|
||||
// socket activation at play.
|
||||
//
|
||||
// TL;DR: Let it crash, it will come back
|
||||
for _, ln := range listeners {
|
||||
go func(ln net.Listener) {
|
||||
log.Printf("listening on %s", ln.Addr())
|
||||
log.Fatal(http.Serve(ln, mux))
|
||||
}(ln)
|
||||
}
|
||||
|
||||
for {
|
||||
select {}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
[Unit]
|
||||
Description=Tailscale NGINX Authentication service
|
||||
After=nginx.service
|
||||
Wants=nginx.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/sbin/tailscale.nginx-auth
|
||||
DynamicUser=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -1,9 +0,0 @@
|
||||
[Unit]
|
||||
Description=Tailscale NGINX Authentication socket
|
||||
PartOf=tailscale.nginx-auth.service
|
||||
|
||||
[Socket]
|
||||
ListenStream=/var/run/tailscale.nginx-auth.sock
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
@@ -1,152 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// proxy-to-grafana is a reverse proxy which identifies users based on their
|
||||
// originating Tailscale identity and maps them to corresponding Grafana
|
||||
// users, creating them if needed.
|
||||
//
|
||||
// It uses Grafana's AuthProxy feature:
|
||||
// https://grafana.com/docs/grafana/latest/auth/auth-proxy/
|
||||
//
|
||||
// Set the TS_AUTHKEY environment variable to have this server automatically
|
||||
// join your tailnet, or look for the logged auth link on first start.
|
||||
//
|
||||
// Use this Grafana configuration to enable the auth proxy:
|
||||
//
|
||||
// [auth.proxy]
|
||||
// enabled = true
|
||||
// header_name = X-WEBAUTH-USER
|
||||
// header_property = username
|
||||
// auto_sign_up = true
|
||||
// whitelist = 127.0.0.1
|
||||
// headers = Name:X-WEBAUTH-NAME
|
||||
// enable_login_token = true
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tsnet"
|
||||
)
|
||||
|
||||
var (
|
||||
hostname = flag.String("hostname", "", "Tailscale hostname to serve on, used as the base name for MagicDNS or subdomain in your domain alias for HTTPS.")
|
||||
backendAddr = flag.String("backend-addr", "", "Address of the Grafana server served over HTTP, in host:port format. Typically localhost:nnnn.")
|
||||
tailscaleDir = flag.String("state-dir", "./", "Alternate directory to use for Tailscale state storage. If empty, a default is used.")
|
||||
useHTTPS = flag.Bool("use-https", false, "Serve over HTTPS via your *.ts.net subdomain if enabled in Tailscale admin.")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if *hostname == "" || strings.Contains(*hostname, ".") {
|
||||
log.Fatal("missing or invalid --hostname")
|
||||
}
|
||||
if *backendAddr == "" {
|
||||
log.Fatal("missing --backend-addr")
|
||||
}
|
||||
ts := &tsnet.Server{
|
||||
Dir: *tailscaleDir,
|
||||
Hostname: *hostname,
|
||||
}
|
||||
|
||||
url, err := url.Parse(fmt.Sprintf("http://%s", *backendAddr))
|
||||
if err != nil {
|
||||
log.Fatalf("couldn't parse backend address: %v", err)
|
||||
}
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(url)
|
||||
originalDirector := proxy.Director
|
||||
proxy.Director = func(req *http.Request) {
|
||||
originalDirector(req)
|
||||
modifyRequest(req)
|
||||
}
|
||||
|
||||
var ln net.Listener
|
||||
if *useHTTPS {
|
||||
ln, err = ts.Listen("tcp", ":443")
|
||||
ln = tls.NewListener(ln, &tls.Config{
|
||||
GetCertificate: tailscale.GetCertificate,
|
||||
})
|
||||
|
||||
go func() {
|
||||
// wait for tailscale to start before trying to fetch cert names
|
||||
for i := 0; i < 60; i++ {
|
||||
st, err := tailscale.Status(context.Background())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("tailscale status: %v", st.BackendState)
|
||||
if st.BackendState == "Running" {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
l80, err := ts.Listen("tcp", ":80")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
name, ok := tailscale.ExpandSNIName(context.Background(), *hostname)
|
||||
if !ok {
|
||||
log.Fatalf("can't get hostname for https redirect")
|
||||
}
|
||||
if err := http.Serve(l80, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, fmt.Sprintf("https://%s", name), http.StatusMovedPermanently)
|
||||
})); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
ln, err = ts.Listen("tcp", ":80")
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("proxy-to-grafana running at %v, proxying to %v", ln.Addr(), *backendAddr)
|
||||
log.Fatal(http.Serve(ln, proxy))
|
||||
}
|
||||
|
||||
func modifyRequest(req *http.Request) {
|
||||
// with enable_login_token set to true, we get a cookie that handles
|
||||
// auth for paths that are not /login
|
||||
if req.URL.Path != "/login" {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := getTailscaleUser(req.Context(), req.RemoteAddr)
|
||||
if err != nil {
|
||||
log.Printf("error getting Tailscale user: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("X-Webauth-User", user.LoginName)
|
||||
req.Header.Set("X-Webauth-Name", user.DisplayName)
|
||||
}
|
||||
|
||||
func getTailscaleUser(ctx context.Context, ipPort string) (*tailcfg.UserProfile, error) {
|
||||
whois, err := tailscale.WhoIs(ctx, ipPort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to identify remote host: %w", err)
|
||||
}
|
||||
if len(whois.Node.Tags) != 0 {
|
||||
return nil, fmt.Errorf("tagged nodes are not users")
|
||||
}
|
||||
if whois.UserProfile == nil || whois.UserProfile.LoginName == "" {
|
||||
return nil, fmt.Errorf("failed to identify remote user")
|
||||
}
|
||||
|
||||
return whois.UserProfile, nil
|
||||
}
|
||||
231
cmd/tailscale/cli/admin.go
Normal file
231
cmd/tailscale/cli/admin.go
Normal file
@@ -0,0 +1,231 @@
|
||||
// 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.
|
||||
|
||||
// Admin commands.
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
"tailscale.com/client/tailscale"
|
||||
)
|
||||
|
||||
var adminCmd = &ffcli.Command{
|
||||
Name: "admin",
|
||||
Exec: runAdmin,
|
||||
LongHelp: `"tailscale admin" contains admin commands to manage a Tailscale network.`,
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("admin")
|
||||
fs.StringVar(&adminArgs.apiBase, "api-server", "https://api.tailscale.com", "which Tailscale server instance to use. Ignored when --token-file is empty.")
|
||||
fs.StringVar(&adminArgs.tokenFile, "token-file", "", "if non-empty, filename containing API token to use. If empty, authentication is done via the active Tailscale control plane connection.")
|
||||
fs.StringVar(&adminArgs.tailnet, "tailnet", "", "Tailnet to query or edit. Required if token-file is used. Must be blank if token-file is blank, in which case the tailnet used is the same as the active tailnet.")
|
||||
return fs
|
||||
})(),
|
||||
Subcommands: []*ffcli.Command{
|
||||
newTailnetACLGetCmd(),
|
||||
newTailnetDeviceListCmd(),
|
||||
newTailnetKeyListCmd(),
|
||||
},
|
||||
}
|
||||
|
||||
var adminArgs struct {
|
||||
tokenFile string
|
||||
tailnet string
|
||||
apiBase string
|
||||
}
|
||||
|
||||
func runAdmin(ctx context.Context, args []string) error {
|
||||
if len(args) > 0 {
|
||||
return errors.New("unknown command; see 'tailscale admin --help'")
|
||||
}
|
||||
return errors.New("see 'tailscale admin --help'")
|
||||
}
|
||||
|
||||
type adminClient struct {
|
||||
apiBase string // e.g. "https://api.tailscale.com"
|
||||
token string // non-empty if using token-based auth
|
||||
hc *http.Client
|
||||
tailnet string // always non-empty
|
||||
}
|
||||
|
||||
func getAdminHTTPClient() (*adminClient, error) {
|
||||
tokenFile := adminArgs.tokenFile
|
||||
tailnet := adminArgs.tailnet
|
||||
apiBase := adminArgs.apiBase
|
||||
if (tokenFile != "") != (tailnet != "") {
|
||||
return nil, errors.New("--token-file and --tailnet must both be blank or both be specified")
|
||||
}
|
||||
if tailnet == "" {
|
||||
st, err := tailscale.StatusWithoutPeers(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if st.BackendState != "Running" {
|
||||
return nil, fmt.Errorf("Tailscale must be running; currently in state %q", st.BackendState)
|
||||
}
|
||||
if st.CurrentTailnet == nil {
|
||||
return nil, fmt.Errorf("no CurrentTailnet in status")
|
||||
}
|
||||
tailnet = st.CurrentTailnet.Name
|
||||
// TODO(bradfitz): put apiBase in *ipnstate.TailnetStatus? update apiBase here?
|
||||
}
|
||||
ac := &adminClient{
|
||||
tailnet: tailnet,
|
||||
apiBase: apiBase,
|
||||
}
|
||||
|
||||
if tokenFile != "" {
|
||||
v, err := os.ReadFile(tokenFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token := strings.TrimSpace(string(v))
|
||||
if token == "" || strings.Contains(token, "\n") {
|
||||
return nil, fmt.Errorf("expect exactly 1 line in API token file %v", tokenFile)
|
||||
}
|
||||
ac.token = token
|
||||
ac.hc = http.DefaultClient
|
||||
} else {
|
||||
// Otherwise, proxy via the local tailscaled and use its identity.
|
||||
ac.hc = &http.Client{Transport: apiViaTailscaledTransport{}}
|
||||
ac.apiBase = "http://local-tailscaled.sock"
|
||||
}
|
||||
return ac, nil
|
||||
}
|
||||
|
||||
func newTailnetDeviceListCmd() *ffcli.Command {
|
||||
var fields string
|
||||
const sub = "tailnet-device-list"
|
||||
fs := newFlagSet(sub)
|
||||
fs.StringVar(&fields, "fields", "default", "comma-separated fields to include in response or 'default', 'all'")
|
||||
return &ffcli.Command{
|
||||
Name: sub,
|
||||
ShortHelp: "list devices",
|
||||
FlagSet: fs,
|
||||
Exec: func(ctx context.Context, args []string) error {
|
||||
ac, err := getAdminHTTPClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{"fields": []string{fields}}
|
||||
return writeResJSON(ac.hc.Get(ac.apiBase + "/api/v2/tailnet/" + ac.tailnet + "/devices?" + q.Encode()))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newTailnetKeyListCmd() *ffcli.Command {
|
||||
const sub = "tailnet-key-list"
|
||||
return &ffcli.Command{
|
||||
Name: sub,
|
||||
ShortHelp: "list keys or specific key (with keyID as argument)",
|
||||
Exec: func(ctx context.Context, args []string) error {
|
||||
var suf string
|
||||
if len(args) == 1 {
|
||||
suf = "/" + args[0]
|
||||
} else if len(args) > 1 {
|
||||
return errors.New("too many arguments")
|
||||
}
|
||||
ac, err := getAdminHTTPClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeResJSON(ac.hc.Get(ac.apiBase + "/api/v2/tailnet/" + ac.tailnet + "/keys" + suf))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newTailnetACLGetCmd() *ffcli.Command {
|
||||
var asJSON bool // true is JSON, false is HuJSON
|
||||
const sub = "tailnet-acl-get"
|
||||
fs := newFlagSet(sub)
|
||||
fs.BoolVar(&asJSON, "json", false, "if true, return ACL is JSON format. The default of false means to use the original HuJSON JSON superset form that allows comments and trailing commas.")
|
||||
return &ffcli.Command{
|
||||
Name: sub,
|
||||
ShortHelp: "list Tailnet ACL/config policy",
|
||||
FlagSet: fs,
|
||||
Exec: func(ctx context.Context, args []string) error {
|
||||
ac, err := getAdminHTTPClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("GET", ac.apiBase+"/api/v2/tailnet/"+ac.tailnet+"/acl", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if asJSON {
|
||||
req.Header.Set("Accept", "application/json")
|
||||
}
|
||||
res, err := ac.hc.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if asJSON {
|
||||
return writeResJSON(res, err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
return fmt.Errorf("%v: %s", res.Status, body)
|
||||
}
|
||||
all, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
buf.Write(all)
|
||||
ensureTrailingNewline(&buf)
|
||||
os.Stdout.Write(buf.Bytes())
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// apiViaTailscaledTransport is an http.RoundTripper that makes
|
||||
// Tailscale API HTTP requests via the localapi to tailscaled,
|
||||
// which then forwards them on over Noise.
|
||||
type apiViaTailscaledTransport struct{}
|
||||
|
||||
func (apiViaTailscaledTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
return tailscale.DoLocalRequest(r)
|
||||
}
|
||||
|
||||
func ensureTrailingNewline(buf *bytes.Buffer) {
|
||||
if buf.Len() > 0 && buf.Bytes()[buf.Len()-1] != '\n' {
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
func writeResJSON(res *http.Response, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
return fmt.Errorf("%v: %s", res.Status, body)
|
||||
}
|
||||
all, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := json.Indent(&buf, all, "", "\t"); err != nil {
|
||||
return err
|
||||
}
|
||||
ensureTrailingNewline(&buf)
|
||||
os.Stdout.Write(buf.Bytes())
|
||||
return nil
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/paths"
|
||||
"tailscale.com/safesocket"
|
||||
@@ -130,6 +129,18 @@ func Run(args []string) (err error) {
|
||||
if len(args) == 1 && (args[0] == "-V" || args[0] == "--version") {
|
||||
args = []string{"version"}
|
||||
}
|
||||
if runtime.GOOS == "linux" && distro.Get() == distro.Gokrazy &&
|
||||
os.Getenv("GOKRAZY_FIRST_START") == "1" {
|
||||
defer func() {
|
||||
// Exit with 125 otherwise the CLI binary is restarted
|
||||
// forever in a loop by the Gokrazy process supervisor.
|
||||
// See https://gokrazy.org/userguide/process-interface/
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
os.Exit(125)
|
||||
}()
|
||||
}
|
||||
|
||||
var warnOnce sync.Once
|
||||
tailscale.SetVersionMismatchHandler(func(clientVer, serverVer string) {
|
||||
@@ -159,13 +170,12 @@ change in the future.
|
||||
ipCmd,
|
||||
statusCmd,
|
||||
pingCmd,
|
||||
ncCmd,
|
||||
sshCmd,
|
||||
versionCmd,
|
||||
webCmd,
|
||||
fileCmd,
|
||||
bugReportCmd,
|
||||
certCmd,
|
||||
adminCmd,
|
||||
},
|
||||
FlagSet: rootfs,
|
||||
Exec: func(context.Context, []string) error { return flag.ErrHelp },
|
||||
@@ -174,9 +184,6 @@ change in the future.
|
||||
for _, c := range rootCmd.Subcommands {
|
||||
c.UsageFunc = usageFunc
|
||||
}
|
||||
if envknob.UseWIPCode() {
|
||||
rootCmd.Subcommands = append(rootCmd.Subcommands, idTokenCmd)
|
||||
}
|
||||
|
||||
// Don't advertise the debug command, but it exists.
|
||||
if strSliceContains(args, "debug") {
|
||||
|
||||
@@ -659,39 +659,6 @@ func TestPrefsFromUpArgs(t *testing.T) {
|
||||
NoSNAT: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "via_route_good",
|
||||
goos: "linux",
|
||||
args: upArgsT{
|
||||
advertiseRoutes: "fd7a:115c:a1e0:b1a::bb:10.0.0.0/112",
|
||||
netfilterMode: "off",
|
||||
},
|
||||
want: &ipn.Prefs{
|
||||
WantRunning: true,
|
||||
NoSNAT: true,
|
||||
AdvertiseRoutes: []netaddr.IPPrefix{
|
||||
netaddr.MustParseIPPrefix("fd7a:115c:a1e0:b1a::bb:10.0.0.0/112"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "via_route_short_prefix",
|
||||
goos: "linux",
|
||||
args: upArgsT{
|
||||
advertiseRoutes: "fd7a:115c:a1e0:b1a::/64",
|
||||
netfilterMode: "off",
|
||||
},
|
||||
wantErr: "fd7a:115c:a1e0:b1a::/64 4-in-6 prefix must be at least a /96",
|
||||
},
|
||||
{
|
||||
name: "via_route_short_reserved_siteid",
|
||||
goos: "linux",
|
||||
args: upArgsT{
|
||||
advertiseRoutes: "fd7a:115c:a1e0:b1a:1234:5678::/112",
|
||||
netfilterMode: "off",
|
||||
},
|
||||
wantErr: "route fd7a:115c:a1e0:b1a:1234:5678::/112 contains invalid site ID 12345678; must be 0xff or less",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -192,7 +192,7 @@ func runDebug(ctx context.Context, args []string) error {
|
||||
// to subcommands.
|
||||
return nil
|
||||
}
|
||||
return errors.New("see 'tailscale debug --help")
|
||||
return errors.New("see 'tailscale debug --help'")
|
||||
}
|
||||
|
||||
func runLocalCreds(ctx context.Context, args []string) error {
|
||||
|
||||
@@ -318,7 +318,6 @@ var fileGetCmd = &ffcli.Command{
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("get")
|
||||
fs.BoolVar(&getArgs.wait, "wait", false, "wait for a file to arrive if inbox is empty")
|
||||
fs.BoolVar(&getArgs.loop, "loop", false, "run get in a loop, receiving files as they come in")
|
||||
fs.BoolVar(&getArgs.verbose, "verbose", false, "verbose output")
|
||||
fs.Var(&getArgs.conflict, "conflict", `behavior when a conflicting (same-named) file already exists in the target directory.
|
||||
skip: skip conflicting files: leave them in the taildrop inbox and print an error. get any non-conflicting files
|
||||
@@ -330,7 +329,6 @@ var fileGetCmd = &ffcli.Command{
|
||||
|
||||
var getArgs = struct {
|
||||
wait bool
|
||||
loop bool
|
||||
verbose bool
|
||||
conflict onConflict
|
||||
}{conflict: skipOnExist}
|
||||
@@ -360,7 +358,7 @@ func openFileOrSubstitute(dir, base string, action onConflict) (*os.File, error)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to write; %w", err)
|
||||
case overwriteExisting:
|
||||
// remove the target file and create it anew so we don't fall for an
|
||||
// remove the target file and create it anew so we don't fall for an
|
||||
// attacker who symlinks a known target name to a file he wants changed.
|
||||
if err = os.Remove(targetFile); err != nil {
|
||||
return nil, fmt.Errorf("unable to remove target file: %w", err)
|
||||
@@ -389,71 +387,18 @@ func receiveFile(ctx context.Context, wf apitype.WaitingFile, dir string) (targe
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("opening inbox file %q: %w", wf.Name, err)
|
||||
}
|
||||
defer rc.Close()
|
||||
f, err := openFileOrSubstitute(dir, wf.Name, getArgs.conflict)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
_, err = io.Copy(f, rc)
|
||||
rc.Close()
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return "", 0, fmt.Errorf("failed to write %v: %v", f.Name(), err)
|
||||
}
|
||||
return f.Name(), size, f.Close()
|
||||
}
|
||||
|
||||
func runFileGetOneBatch(ctx context.Context, dir string) []error {
|
||||
var wfs []apitype.WaitingFile
|
||||
var err error
|
||||
var errs []error
|
||||
for len(errs) == 0 {
|
||||
wfs, err = tailscale.WaitingFiles(ctx)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("getting WaitingFiles: %w", err))
|
||||
break
|
||||
}
|
||||
if len(wfs) != 0 || !(getArgs.wait || getArgs.loop) {
|
||||
break
|
||||
}
|
||||
if getArgs.verbose {
|
||||
printf("waiting for file...")
|
||||
}
|
||||
if err := waitForFile(ctx); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
deleted := 0
|
||||
for i, wf := range wfs {
|
||||
if len(errs) > 100 {
|
||||
// Likely, everything is broken.
|
||||
// Don't try to receive any more files in this batch.
|
||||
errs = append(errs, fmt.Errorf("too many errors in runFileGetOneBatch(). %d files unexamined", len(wfs) - i))
|
||||
break
|
||||
}
|
||||
writtenFile, size, err := receiveFile(ctx, wf, dir)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
if getArgs.verbose {
|
||||
printf("wrote %v as %v (%d bytes)\n", wf.Name, writtenFile, size)
|
||||
}
|
||||
if err = tailscale.DeleteWaitingFile(ctx, wf.Name); err != nil {
|
||||
errs = append(errs, fmt.Errorf("deleting %q from inbox: %v", wf.Name, err))
|
||||
continue
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
if deleted == 0 && len(wfs) > 0 {
|
||||
// persistently stuck files are basically an error
|
||||
errs = append(errs, fmt.Errorf("moved %d/%d files", deleted, len(wfs)))
|
||||
} else if getArgs.verbose {
|
||||
printf("moved %d/%d files\n", deleted, len(wfs))
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func runFileGet(ctx context.Context, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return errors.New("usage: file get <target-directory>")
|
||||
@@ -468,28 +413,45 @@ func runFileGet(ctx context.Context, args []string) error {
|
||||
if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
|
||||
return fmt.Errorf("%q is not a directory", dir)
|
||||
}
|
||||
if getArgs.loop {
|
||||
for {
|
||||
errs := runFileGetOneBatch(ctx, dir)
|
||||
for _, err := range errs {
|
||||
outln(err)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
// It's possible whatever caused the error(s) (e.g. conflicting target file,
|
||||
// full disk, unwritable target directory) will re-occur if we try again so
|
||||
// let's back off and not busy loop on error.
|
||||
//
|
||||
// If we've been invoked as:
|
||||
// tailscale file get --conflict=skip ~/Downloads
|
||||
// then any file coming in named the same as one in ~/Downloads will always
|
||||
// appear as an "error" until the user clears it, but other incoming files
|
||||
// should be receivable when they arrive, so let's not wait too long to
|
||||
// check again.
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
||||
var wfs []apitype.WaitingFile
|
||||
var err error
|
||||
for {
|
||||
wfs, err = tailscale.WaitingFiles(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting WaitingFiles: %w", err)
|
||||
}
|
||||
if len(wfs) != 0 || !getArgs.wait {
|
||||
break
|
||||
}
|
||||
if getArgs.verbose {
|
||||
printf("waiting for file...")
|
||||
}
|
||||
if err := waitForFile(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
errs := runFileGetOneBatch(ctx, dir)
|
||||
|
||||
var errs []error
|
||||
deleted := 0
|
||||
for _, wf := range wfs {
|
||||
writtenFile, size, err := receiveFile(ctx, wf, dir)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
if getArgs.verbose {
|
||||
printf("wrote %v as %v (%d bytes)\n", wf.Name, writtenFile, size)
|
||||
}
|
||||
if err = tailscale.DeleteWaitingFile(ctx, wf.Name); err != nil {
|
||||
errs = append(errs, fmt.Errorf("deleting %q from inbox: %v", wf.Name, err))
|
||||
continue
|
||||
}
|
||||
deleted++
|
||||
}
|
||||
if getArgs.verbose {
|
||||
printf("moved %d/%d files\n", deleted, len(wfs))
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -527,10 +489,9 @@ func waitForFile(ctx context.Context) error {
|
||||
c, bc, pumpCtx, cancel := connect(ctx)
|
||||
defer cancel()
|
||||
fileWaiting := make(chan bool, 1)
|
||||
notifyError := make(chan error, 1)
|
||||
bc.SetNotifyCallback(func(n ipn.Notify) {
|
||||
if n.ErrMessage != nil {
|
||||
notifyError <- fmt.Errorf("Notify.ErrMessage: %v", *n.ErrMessage)
|
||||
fatalf("Notify.ErrMessage: %v\n", *n.ErrMessage)
|
||||
}
|
||||
if n.FilesWaiting != nil {
|
||||
select {
|
||||
@@ -547,7 +508,5 @@ func waitForFile(ctx context.Context) error {
|
||||
return pumpCtx.Err()
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case err := <-notifyError:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// 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.
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
"tailscale.com/client/tailscale"
|
||||
)
|
||||
|
||||
var idTokenCmd = &ffcli.Command{
|
||||
Name: "id-token",
|
||||
ShortUsage: "id-token <aud>",
|
||||
ShortHelp: "fetch an OIDC id-token for the Tailscale machine",
|
||||
Exec: runIDToken,
|
||||
}
|
||||
|
||||
func runIDToken(ctx context.Context, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return errors.New("usage: id-token <aud>")
|
||||
}
|
||||
|
||||
tr, err := tailscale.IDToken(ctx, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(tr.IDToken)
|
||||
return nil
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
// 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.
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
"tailscale.com/client/tailscale"
|
||||
)
|
||||
|
||||
var ncCmd = &ffcli.Command{
|
||||
Name: "nc",
|
||||
ShortUsage: "nc <hostname-or-IP> <port>",
|
||||
ShortHelp: "Connect to a port on a host, connected to stdin/stdout",
|
||||
Exec: runNC,
|
||||
}
|
||||
|
||||
func runNC(ctx context.Context, args []string) error {
|
||||
st, err := tailscale.Status(ctx)
|
||||
if err != nil {
|
||||
return fixTailscaledConnectError(err)
|
||||
}
|
||||
description, ok := isRunningOrStarting(st)
|
||||
if !ok {
|
||||
printf("%s\n", description)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(args) != 2 {
|
||||
return errors.New("usage: nc <hostname-or-IP> <port>")
|
||||
}
|
||||
|
||||
hostOrIP, portStr := args[0], args[1]
|
||||
port, err := strconv.ParseUint(portStr, 10, 16)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid port number %q", portStr)
|
||||
}
|
||||
|
||||
// TODO(bradfitz): also add UDP too, via flag?
|
||||
c, err := tailscale.DialTCP(ctx, hostOrIP, uint16(port))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Dial(%q, %v): %w", hostOrIP, port, err)
|
||||
}
|
||||
defer c.Close()
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := io.Copy(os.Stdout, c)
|
||||
errc <- err
|
||||
}()
|
||||
go func() {
|
||||
_, err := io.Copy(c, os.Stdin)
|
||||
errc <- err
|
||||
}()
|
||||
return <-errc
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
// 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.
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/alessio/shellescape"
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
)
|
||||
|
||||
var sshCmd = &ffcli.Command{
|
||||
Name: "ssh",
|
||||
ShortUsage: "ssh [user@]<host> [args...]",
|
||||
ShortHelp: "SSH to a Tailscale machine",
|
||||
Exec: runSSH,
|
||||
}
|
||||
|
||||
func runSSH(ctx context.Context, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("usage: ssh [user@]<host>")
|
||||
}
|
||||
arg, argRest := args[0], args[1:]
|
||||
username, host, ok := strings.Cut(arg, "@")
|
||||
if !ok {
|
||||
host = arg
|
||||
lu, err := user.Current()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
username = lu.Username
|
||||
}
|
||||
|
||||
st, err := tailscale.Status(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// hostForSSH is the hostname we'll tell OpenSSH we're
|
||||
// connecting to, so we have to maintain fewer entries in the
|
||||
// known_hosts files.
|
||||
hostForSSH := host
|
||||
if v, ok := nodeDNSNameFromArg(st, host); ok {
|
||||
hostForSSH = v
|
||||
}
|
||||
|
||||
ssh, err := exec.LookPath("ssh")
|
||||
if err != nil {
|
||||
// TODO(bradfitz): use Go's crypto/ssh client instead
|
||||
// of failing. But for now:
|
||||
return fmt.Errorf("no system 'ssh' command found: %w", err)
|
||||
}
|
||||
tailscaleBin, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
knownHostsFile, err := writeKnownHosts(st)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
argv := append([]string{
|
||||
ssh,
|
||||
|
||||
"-o", fmt.Sprintf("UserKnownHostsFile %s",
|
||||
shellescape.Quote(knownHostsFile),
|
||||
),
|
||||
"-o", fmt.Sprintf("ProxyCommand %s --socket=%s nc %%h %%p",
|
||||
shellescape.Quote(tailscaleBin),
|
||||
shellescape.Quote(rootArgs.socket),
|
||||
),
|
||||
|
||||
// Explicitly rebuild the user@host argument rather than
|
||||
// passing it through. In general, the use of OpenSSH's ssh
|
||||
// binary is a crutch for now. We don't want to be
|
||||
// Hyrum-locked into passing through all OpenSSH flags to the
|
||||
// OpenSSH client forever. We try to make our flags and args
|
||||
// be compatible, but only a subset. The "tailscale ssh"
|
||||
// command should be a simple and portable one. If they want
|
||||
// to use a different one, we'll later be making stock ssh
|
||||
// work well by default too. (doing things like automatically
|
||||
// setting known_hosts, etc)
|
||||
username + "@" + hostForSSH,
|
||||
}, argRest...)
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// Don't use syscall.Exec on Windows.
|
||||
cmd := exec.Command(ssh, argv[1:]...)
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
var ee *exec.ExitError
|
||||
err := cmd.Run()
|
||||
if errors.As(err, &ee) {
|
||||
os.Exit(ee.ExitCode())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if envknob.Bool("TS_DEBUG_SSH_EXEC") {
|
||||
log.Printf("Running: %q, %q ...", ssh, argv)
|
||||
}
|
||||
if err := syscall.Exec(ssh, argv, os.Environ()); err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New("unreachable")
|
||||
}
|
||||
|
||||
func writeKnownHosts(st *ipnstate.Status) (knownHostsFile string, err error) {
|
||||
confDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tsConfDir := filepath.Join(confDir, "tailscale")
|
||||
if err := os.MkdirAll(tsConfDir, 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
knownHostsFile = filepath.Join(tsConfDir, "ssh_known_hosts")
|
||||
want := genKnownHosts(st)
|
||||
if cur, err := os.ReadFile(knownHostsFile); err != nil || !bytes.Equal(cur, want) {
|
||||
if err := os.WriteFile(knownHostsFile, want, 0644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return knownHostsFile, nil
|
||||
}
|
||||
|
||||
func genKnownHosts(st *ipnstate.Status) []byte {
|
||||
var buf bytes.Buffer
|
||||
for _, k := range st.Peers() {
|
||||
ps := st.Peer[k]
|
||||
for _, hk := range ps.SSH_HostKeys {
|
||||
hostKey := strings.TrimSpace(hk)
|
||||
if strings.ContainsAny(hostKey, "\n\r") { // invalid
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&buf, "%s %s\n", ps.DNSName, hostKey)
|
||||
}
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// nodeDNSNameFromArg returns the PeerStatus.DNSName value from a peer
|
||||
// in st that matches the input arg which can be a base name, full
|
||||
// DNS name, or an IP.
|
||||
func nodeDNSNameFromArg(st *ipnstate.Status, arg string) (dnsName string, ok bool) {
|
||||
if arg == "" {
|
||||
return
|
||||
}
|
||||
argIP, _ := netaddr.ParseIP(arg)
|
||||
for _, ps := range st.Peer {
|
||||
dnsName = ps.DNSName
|
||||
if !argIP.IsZero() {
|
||||
for _, ip := range ps.TailscaleIPs {
|
||||
if ip == argIP {
|
||||
return dnsName, true
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(strings.TrimSuffix(arg, "."), strings.TrimSuffix(dnsName, ".")) {
|
||||
return dnsName, true
|
||||
}
|
||||
if base, _, ok := strings.Cut(ps.DNSName, "."); ok && strings.EqualFold(base, arg) {
|
||||
return dnsName, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -70,14 +69,7 @@ var statusArgs struct {
|
||||
}
|
||||
|
||||
func runStatus(ctx context.Context, args []string) error {
|
||||
if len(args) > 0 {
|
||||
return errors.New("unexpected non-flag arguments to 'tailscale status'")
|
||||
}
|
||||
getStatus := tailscale.Status
|
||||
if !statusArgs.peers {
|
||||
getStatus = tailscale.StatusWithoutPeers
|
||||
}
|
||||
st, err := getStatus(ctx)
|
||||
st, err := tailscale.Status(ctx)
|
||||
if err != nil {
|
||||
return fixTailscaledConnectError(err)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ package cli
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
@@ -28,7 +27,6 @@ import (
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/safesocket"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -203,26 +201,6 @@ var (
|
||||
ipv6default = netaddr.MustParseIPPrefix("::/0")
|
||||
)
|
||||
|
||||
func validateViaPrefix(ipp netaddr.IPPrefix) error {
|
||||
if !tsaddr.IsViaPrefix(ipp) {
|
||||
return fmt.Errorf("%v is not a 4-in-6 prefix", ipp)
|
||||
}
|
||||
if ipp.Bits() < (128 - 32) {
|
||||
return fmt.Errorf("%v 4-in-6 prefix must be at least a /%v", ipp, 128-32)
|
||||
}
|
||||
a := ipp.IP().As16()
|
||||
// The first 64 bits of a are the via prefix.
|
||||
// The next 32 bits are the "site ID".
|
||||
// The last 32 bits are the IPv4.
|
||||
// For now, we reserve the top 3 bytes of the site ID,
|
||||
// and only allow users to use site IDs 0-255.
|
||||
siteID := binary.BigEndian.Uint32(a[8:12])
|
||||
if siteID > 0xFF {
|
||||
return fmt.Errorf("route %v contains invalid site ID %08x; must be 0xff or less", ipp, siteID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func calcAdvertiseRoutes(advertiseRoutes string, advertiseDefaultRoute bool) ([]netaddr.IPPrefix, error) {
|
||||
routeMap := map[netaddr.IPPrefix]bool{}
|
||||
if advertiseRoutes != "" {
|
||||
@@ -236,11 +214,6 @@ func calcAdvertiseRoutes(advertiseRoutes string, advertiseDefaultRoute bool) ([]
|
||||
if ipp != ipp.Masked() {
|
||||
return nil, fmt.Errorf("%s has non-address bits set; expected %s", ipp, ipp.Masked())
|
||||
}
|
||||
if tsaddr.IsViaPrefix(ipp) {
|
||||
if err := validateViaPrefix(ipp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if ipp == ipv4default {
|
||||
default4 = true
|
||||
} else if ipp == ipv6default {
|
||||
|
||||
@@ -269,14 +269,15 @@ func synoTokenRedirect(w http.ResponseWriter, r *http.Request) bool {
|
||||
}
|
||||
// We need a SynoToken for authenticate.cgi.
|
||||
// So we tell the client to get one.
|
||||
_, _ = fmt.Fprint(w, synoTokenRedirectHTML)
|
||||
serverURL := r.URL.Scheme + "://" + r.URL.Host
|
||||
synoTokenRedirectHTML.Execute(w, serverURL)
|
||||
return true
|
||||
}
|
||||
|
||||
const synoTokenRedirectHTML = `<html><body>
|
||||
var synoTokenRedirectHTML = template.Must(template.New("redirect").Parse(`<html><body>
|
||||
Redirecting with session token...
|
||||
<script>
|
||||
var serverURL = window.location.protocol + "//" + window.location.host;
|
||||
var serverURL = {{ . }};
|
||||
var req = new XMLHttpRequest();
|
||||
req.overrideMimeType("application/json");
|
||||
req.open("GET", serverURL + "/webman/login.cgi", true);
|
||||
@@ -288,7 +289,7 @@ req.onload = function() {
|
||||
req.send(null);
|
||||
</script>
|
||||
</body></html>
|
||||
`
|
||||
`))
|
||||
|
||||
func webHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if authRedirect(w, r) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/depaware)
|
||||
|
||||
github.com/alessio/shellescape from tailscale.com/cmd/tailscale/cli
|
||||
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate+
|
||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
||||
@@ -57,7 +56,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/net/neterror from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/netknob from tailscale.com/net/netns
|
||||
tailscale.com/net/netns from tailscale.com/derp/derphttp+
|
||||
tailscale.com/net/netutil from tailscale.com/client/tailscale
|
||||
tailscale.com/net/packet from tailscale.com/wgengine/filter
|
||||
tailscale.com/net/portmapper from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/stun from tailscale.com/net/netcheck
|
||||
@@ -194,7 +192,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
os from crypto/rand+
|
||||
os/exec from github.com/toqueteos/webbrowser+
|
||||
os/signal from tailscale.com/cmd/tailscale/cli
|
||||
os/user from tailscale.com/util/groupmember+
|
||||
os/user from tailscale.com/util/groupmember
|
||||
path from html/template+
|
||||
path/filepath from crypto/x509+
|
||||
reflect from crypto/x509+
|
||||
|
||||
@@ -3,7 +3,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+
|
||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
||||
LD github.com/anmitsu/go-shlex from tailscale.com/tempfork/gliderlabs/ssh
|
||||
LD github.com/anmitsu/go-shlex from github.com/tailscale/ssh
|
||||
L github.com/aws/aws-sdk-go-v2 from github.com/aws/aws-sdk-go-v2/internal/ini
|
||||
L github.com/aws/aws-sdk-go-v2/aws from github.com/aws/aws-sdk-go-v2/aws/middleware+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/arn from tailscale.com/ipn/store/awsstore
|
||||
@@ -90,10 +90,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
|
||||
W github.com/pkg/errors from github.com/tailscale/certstore
|
||||
W 💣 github.com/tailscale/certstore from tailscale.com/control/controlclient
|
||||
LD github.com/tailscale/golang-x-crypto/chacha20 from github.com/tailscale/golang-x-crypto/ssh
|
||||
LD 💣 github.com/tailscale/golang-x-crypto/internal/subtle from github.com/tailscale/golang-x-crypto/chacha20
|
||||
LD github.com/tailscale/golang-x-crypto/ssh from tailscale.com/ipn/ipnlocal+
|
||||
LD github.com/tailscale/golang-x-crypto/ssh/internal/bcrypt_pbkdf from github.com/tailscale/golang-x-crypto/ssh
|
||||
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2+
|
||||
github.com/tailscale/goupnp/dcps/internetgateway2 from tailscale.com/net/portmapper
|
||||
github.com/tailscale/goupnp/httpu from github.com/tailscale/goupnp+
|
||||
@@ -101,6 +97,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
github.com/tailscale/goupnp/soap from github.com/tailscale/goupnp+
|
||||
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
|
||||
L 💣 github.com/tailscale/netlink from tailscale.com/wgengine/router
|
||||
LD github.com/tailscale/ssh from tailscale.com/ssh/tailssh
|
||||
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
|
||||
LD github.com/u-root/u-root/pkg/termios from tailscale.com/ssh/tailssh
|
||||
L github.com/u-root/uio/rand from github.com/insomniacslk/dhcp/dhcpv4
|
||||
@@ -130,8 +127,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
gvisor.dev/gvisor/pkg/linewriter from gvisor.dev/gvisor/pkg/log
|
||||
gvisor.dev/gvisor/pkg/log from gvisor.dev/gvisor/pkg/context+
|
||||
gvisor.dev/gvisor/pkg/rand from gvisor.dev/gvisor/pkg/tcpip/network/hash+
|
||||
gvisor.dev/gvisor/pkg/refs from gvisor.dev/gvisor/pkg/refsvfs2+
|
||||
gvisor.dev/gvisor/pkg/refsvfs2 from gvisor.dev/gvisor/pkg/tcpip/stack+
|
||||
gvisor.dev/gvisor/pkg/refs from gvisor.dev/gvisor/pkg/refsvfs2
|
||||
gvisor.dev/gvisor/pkg/refsvfs2 from gvisor.dev/gvisor/pkg/tcpip/stack
|
||||
💣 gvisor.dev/gvisor/pkg/sleep from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
|
||||
💣 gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/atomicbitops+
|
||||
gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state
|
||||
@@ -204,7 +201,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/logtail/filch from tailscale.com/logpolicy
|
||||
💣 tailscale.com/metrics from tailscale.com/derp+
|
||||
tailscale.com/net/dns from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/net/dns/publicdns from tailscale.com/net/dns/resolver
|
||||
tailscale.com/net/dns/resolvconffile from tailscale.com/net/dns+
|
||||
tailscale.com/net/dns/resolver from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/net/dnscache from tailscale.com/control/controlclient+
|
||||
@@ -234,7 +230,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
LD 💣 tailscale.com/ssh/tailssh from tailscale.com/wgengine/netstack
|
||||
tailscale.com/syncs from tailscale.com/control/controlknobs+
|
||||
tailscale.com/tailcfg from tailscale.com/client/tailscale+
|
||||
LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh
|
||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||
tailscale.com/tstime from tailscale.com/wgengine/magicsock
|
||||
💣 tailscale.com/tstime/mono from tailscale.com/net/tstun+
|
||||
@@ -286,19 +281,19 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
golang.org/x/crypto/acme from tailscale.com/ipn/localapi
|
||||
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
|
||||
golang.org/x/crypto/blake2s from golang.zx2c4.com/wireguard/device+
|
||||
LD golang.org/x/crypto/blowfish from golang.org/x/crypto/ssh/internal/bcrypt_pbkdf+
|
||||
LD golang.org/x/crypto/blowfish from golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
|
||||
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305+
|
||||
golang.org/x/crypto/chacha20poly1305 from crypto/tls+
|
||||
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
|
||||
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
|
||||
golang.org/x/crypto/curve25519 from crypto/tls+
|
||||
LD golang.org/x/crypto/ed25519 from golang.org/x/crypto/ssh+
|
||||
LD golang.org/x/crypto/ed25519 from golang.org/x/crypto/ssh
|
||||
golang.org/x/crypto/hkdf from crypto/tls+
|
||||
golang.org/x/crypto/nacl/box from tailscale.com/types/key
|
||||
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
||||
golang.org/x/crypto/poly1305 from golang.zx2c4.com/wireguard/device+
|
||||
golang.org/x/crypto/poly1305 from golang.zx2c4.com/wireguard/device
|
||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||
LD golang.org/x/crypto/ssh from tailscale.com/ssh/tailssh
|
||||
LD golang.org/x/crypto/ssh from github.com/tailscale/ssh+
|
||||
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
|
||||
golang.org/x/net/dns/dnsmessage from net+
|
||||
golang.org/x/net/http/httpguts from golang.org/x/net/http2+
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -129,6 +130,14 @@ var subCommands = map[string]*func([]string) error{
|
||||
}
|
||||
|
||||
func main() {
|
||||
// We aren't very performance sensitive, and the parts that are
|
||||
// performance sensitive (wireguard) try hard not to do any memory
|
||||
// allocations. So let's be aggressive about garbage collection,
|
||||
// unless the user specifically overrides it in the usual way.
|
||||
if _, ok := os.LookupEnv("GOGC"); !ok {
|
||||
debug.SetGCPercent(10)
|
||||
}
|
||||
|
||||
printVersion := false
|
||||
flag.IntVar(&args.verbose, "verbose", 0, "log verbosity level; 0 is default, 1 or higher are increasingly verbose")
|
||||
flag.BoolVar(&args.cleanup, "cleanup", false, "clean up system state and exit")
|
||||
@@ -172,6 +181,9 @@ func main() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if runtime.GOOS == "darwin" && runtime.Version() == "go1.18" {
|
||||
log.Fatalf("tailscaled is broken on macOS with go1.18 due to upstream bug https://github.com/golang/go/issues/51759; use 1.18.1+ or Tailscale's Go fork")
|
||||
}
|
||||
if runtime.GOOS == "darwin" && os.Getuid() != 0 && !strings.Contains(args.tunname, "userspace-networking") && !args.cleanup {
|
||||
log.SetFlags(0)
|
||||
log.Fatalf("tailscaled requires root; use sudo tailscaled (or use --tun=userspace-networking)")
|
||||
|
||||
@@ -31,11 +31,11 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/creack/pty"
|
||||
gossh "github.com/tailscale/golang-x-crypto/ssh"
|
||||
"github.com/tailscale/ssh"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/net/interfaces"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/tempfork/gliderlabs/ssh"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -52,11 +52,10 @@ type rxState struct {
|
||||
sync.Mutex
|
||||
cipher cipher.AEAD
|
||||
nonce nonce
|
||||
buf *maxMsgBuffer // or nil when reads exhausted
|
||||
n int // number of valid bytes in buf
|
||||
next int // offset of next undecrypted packet
|
||||
plaintext []byte // slice into buf of decrypted bytes
|
||||
hdrBuf [headerLen]byte // small buffer used when buf is nil
|
||||
buf [maxMessageSize]byte
|
||||
n int // number of valid bytes in buf
|
||||
next int // offset of next undecrypted packet
|
||||
plaintext []byte // slice into buf of decrypted bytes
|
||||
}
|
||||
|
||||
// txState is all the Conn state that Write uses.
|
||||
@@ -64,6 +63,7 @@ type txState struct {
|
||||
sync.Mutex
|
||||
cipher cipher.AEAD
|
||||
nonce nonce
|
||||
buf [maxMessageSize]byte
|
||||
err error // records the first partial write error for all future calls
|
||||
}
|
||||
|
||||
@@ -89,10 +89,6 @@ func (c *Conn) Peer() key.MachinePublic {
|
||||
// readNLocked reads into c.rx.buf until buf contains at least total
|
||||
// bytes. Returns a slice of the total bytes in rxBuf, or an
|
||||
// error if fewer than total bytes are available.
|
||||
//
|
||||
// It may be called with a nil c.rx.buf only if total == headerLen.
|
||||
//
|
||||
// On success, c.rx.buf will be non-nil.
|
||||
func (c *Conn) readNLocked(total int) ([]byte, error) {
|
||||
if total > maxMessageSize {
|
||||
return nil, errReadTooBig{total}
|
||||
@@ -101,26 +97,8 @@ func (c *Conn) readNLocked(total int) ([]byte, error) {
|
||||
if total <= c.rx.n {
|
||||
return c.rx.buf[:total], nil
|
||||
}
|
||||
var n int
|
||||
var err error
|
||||
if c.rx.buf == nil {
|
||||
if c.rx.n != 0 || total != headerLen {
|
||||
panic("unexpected")
|
||||
}
|
||||
// Optimization to reduce memory usage.
|
||||
// Most connections are blocked forever waiting for
|
||||
// a read, so we don't want c.rx.buf to be allocated until
|
||||
// we know there's data to read. Instead, when we're
|
||||
// waiting for data to arrive here, read into the
|
||||
// 3 byte hdrBuf:
|
||||
n, err = c.conn.Read(c.rx.hdrBuf[:])
|
||||
if n > 0 {
|
||||
c.rx.buf = getMaxMsgBuffer()
|
||||
copy(c.rx.buf[:], c.rx.hdrBuf[:n])
|
||||
}
|
||||
} else {
|
||||
n, err = c.conn.Read(c.rx.buf[c.rx.n:])
|
||||
}
|
||||
|
||||
n, err := c.conn.Read(c.rx.buf[c.rx.n:])
|
||||
c.rx.n += n
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -156,19 +134,19 @@ func (c *Conn) decryptLocked(msg []byte) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// encryptLocked encrypts plaintext into buf (including the
|
||||
// encryptLocked encrypts plaintext into c.tx.buf (including the
|
||||
// packet header) and returns a slice of the ciphertext, or an error
|
||||
// if the cipher is exhausted (i.e. can no longer be used safely).
|
||||
func (c *Conn) encryptLocked(plaintext []byte, buf *maxMsgBuffer) ([]byte, error) {
|
||||
func (c *Conn) encryptLocked(plaintext []byte) ([]byte, error) {
|
||||
if !c.tx.nonce.Valid() {
|
||||
// Received 2^64-1 messages on this cipher state. Connection
|
||||
// is no longer usable.
|
||||
return nil, errCipherExhausted{}
|
||||
}
|
||||
|
||||
buf[0] = msgTypeRecord
|
||||
binary.BigEndian.PutUint16(buf[1:headerLen], uint16(len(plaintext)+chp.Overhead))
|
||||
ret := c.tx.cipher.Seal(buf[:headerLen], c.tx.nonce[:], plaintext, nil)
|
||||
c.tx.buf[0] = msgTypeRecord
|
||||
binary.BigEndian.PutUint16(c.tx.buf[1:headerLen], uint16(len(plaintext)+chp.Overhead))
|
||||
ret := c.tx.cipher.Seal(c.tx.buf[:headerLen], c.tx.nonce[:], plaintext, nil)
|
||||
c.tx.nonce.Increment()
|
||||
|
||||
return ret, nil
|
||||
@@ -213,14 +191,6 @@ func (c *Conn) decryptOneLocked() error {
|
||||
c.rx.next = 0
|
||||
}
|
||||
|
||||
// Return our buffer to the pool if it's empty, lest we be
|
||||
// blocked in a long Read call, reading the 3 byte header. We
|
||||
// don't to keep that buffer unnecessarily alive.
|
||||
if c.rx.n == 0 && c.rx.next == 0 && c.rx.buf != nil {
|
||||
bufPool.Put(c.rx.buf)
|
||||
c.rx.buf = nil
|
||||
}
|
||||
|
||||
bs, err := c.readNLocked(headerLen)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -257,12 +227,6 @@ func (c *Conn) Read(bs []byte) (int, error) {
|
||||
}
|
||||
n := copy(bs, c.rx.plaintext)
|
||||
c.rx.plaintext = c.rx.plaintext[n:]
|
||||
|
||||
// Lose slice's underlying array pointer to unneeded memory so
|
||||
// GC can collect more.
|
||||
if len(c.rx.plaintext) == 0 {
|
||||
c.rx.plaintext = nil
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
@@ -293,9 +257,6 @@ func (c *Conn) Write(bs []byte) (n int, err error) {
|
||||
return 0, net.ErrClosed
|
||||
}
|
||||
|
||||
buf := getMaxMsgBuffer()
|
||||
defer bufPool.Put(buf)
|
||||
|
||||
var sent int
|
||||
for len(bs) > 0 {
|
||||
toSend := bs
|
||||
@@ -304,7 +265,7 @@ func (c *Conn) Write(bs []byte) (n int, err error) {
|
||||
}
|
||||
bs = bs[len(toSend):]
|
||||
|
||||
ciphertext, err := c.encryptLocked(toSend, buf)
|
||||
ciphertext, err := c.encryptLocked(toSend)
|
||||
if err != nil {
|
||||
return sent, err
|
||||
}
|
||||
@@ -394,16 +355,3 @@ func (n *nonce) Increment() {
|
||||
}
|
||||
binary.BigEndian.PutUint64(n[4:], 1+binary.BigEndian.Uint64(n[4:]))
|
||||
}
|
||||
|
||||
type maxMsgBuffer [maxMessageSize]byte
|
||||
|
||||
// bufPool holds the temporary buffers for Conn.Read & Write.
|
||||
var bufPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(maxMsgBuffer)
|
||||
},
|
||||
}
|
||||
|
||||
func getMaxMsgBuffer() *maxMsgBuffer {
|
||||
return bufPool.Get().(*maxMsgBuffer)
|
||||
}
|
||||
|
||||
@@ -13,12 +13,10 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"testing/iotest"
|
||||
"time"
|
||||
|
||||
chp "golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/net/nettest"
|
||||
@@ -26,8 +24,6 @@ import (
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
const testProtocolVersion = 1
|
||||
|
||||
func TestMessageSize(t *testing.T) {
|
||||
// This test is a regression guard against someone looking at
|
||||
// maxCiphertextSize, going "huh, we could be more efficient if it
|
||||
@@ -209,7 +205,7 @@ func TestConnStd(t *testing.T) {
|
||||
c2, err = Server(context.Background(), s2, controlKey, nil)
|
||||
serverErr <- err
|
||||
}()
|
||||
c1, err = Client(context.Background(), s1, machineKey, controlKey.Public(), testProtocolVersion)
|
||||
c1, err = Client(context.Background(), s1, machineKey, controlKey.Public())
|
||||
if err != nil {
|
||||
s1.Close()
|
||||
s2.Close()
|
||||
@@ -228,81 +224,6 @@ func TestConnStd(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// tests that the idle memory overhead of a Conn blocked in a read is
|
||||
// reasonable (under 2K). It was previously over 8KB with two 4KB
|
||||
// buffers for rx/tx. This make sure we don't regress. Hopefully it
|
||||
// doesn't turn into a flaky test. If so, const max can be adjusted,
|
||||
// or it can be deleted or reworked.
|
||||
func TestConnMemoryOverhead(t *testing.T) {
|
||||
num := 1000
|
||||
if testing.Short() {
|
||||
num = 100
|
||||
}
|
||||
ng0 := runtime.NumGoroutine()
|
||||
|
||||
runtime.GC()
|
||||
var ms0 runtime.MemStats
|
||||
runtime.ReadMemStats(&ms0)
|
||||
|
||||
var closers []io.Closer
|
||||
closeAll := func() {
|
||||
for _, c := range closers {
|
||||
c.Close()
|
||||
}
|
||||
closers = nil
|
||||
}
|
||||
defer closeAll()
|
||||
|
||||
for i := 0; i < num; i++ {
|
||||
client, server := pair(t)
|
||||
closers = append(closers, client, server)
|
||||
go func() {
|
||||
var buf [1]byte
|
||||
client.Read(buf[:])
|
||||
}()
|
||||
}
|
||||
|
||||
t0 := time.Now()
|
||||
deadline := t0.Add(3 * time.Second)
|
||||
var ngo int
|
||||
for time.Now().Before(deadline) {
|
||||
runtime.GC()
|
||||
ngo = runtime.NumGoroutine()
|
||||
if ngo >= num {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if ngo < num {
|
||||
t.Fatalf("only %v goroutines; expected %v+", ngo, num)
|
||||
}
|
||||
runtime.GC()
|
||||
var ms runtime.MemStats
|
||||
runtime.ReadMemStats(&ms)
|
||||
growthTotal := int64(ms.HeapAlloc) - int64(ms0.HeapAlloc)
|
||||
growthEach := float64(growthTotal) / float64(num)
|
||||
t.Logf("Alloced %v bytes, %.2f B/each", growthTotal, growthEach)
|
||||
const max = 2000
|
||||
if growthEach > max {
|
||||
t.Errorf("allocated more than expected; want max %v bytes/each", max)
|
||||
}
|
||||
|
||||
closeAll()
|
||||
|
||||
// And make sure our goroutines go away too.
|
||||
deadline = time.Now().Add(3 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
ngo = runtime.NumGoroutine()
|
||||
if ngo < ng0+num/10 {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if ngo >= ng0+num/10 {
|
||||
t.Errorf("goroutines didn't go back down; started at %v, now %v", ng0, ngo)
|
||||
}
|
||||
}
|
||||
|
||||
// mkConns creates synthetic Noise Conns wrapping the given net.Conns.
|
||||
// This function is for testing just the Conn transport logic without
|
||||
// having to muck about with Noise handshakes.
|
||||
@@ -402,7 +323,7 @@ func pairWithConns(t *testing.T, clientConn, serverConn net.Conn) (*Conn, *Conn)
|
||||
serverErr <- err
|
||||
}()
|
||||
|
||||
client, err := Client(context.Background(), clientConn, machineKey, controlKey.Public(), testProtocolVersion)
|
||||
client, err := Client(context.Background(), clientConn, machineKey, controlKey.Public())
|
||||
if err != nil {
|
||||
t.Fatalf("client connection failed: %v", err)
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ const (
|
||||
protocolName = "Noise_IK_25519_ChaChaPoly_BLAKE2s"
|
||||
// protocolVersion is the version of the control protocol that
|
||||
// Client will use when initiating a handshake.
|
||||
//protocolVersion uint16 = 1
|
||||
protocolVersion uint16 = 1
|
||||
// protocolVersionPrefix is the name portion of the protocol
|
||||
// name+version string that gets mixed into the handshake as a
|
||||
// prologue.
|
||||
@@ -66,7 +66,7 @@ type HandshakeContinuation func(context.Context, net.Conn) (*Conn, error)
|
||||
// protocol switching. By splitting the handshake into an initial
|
||||
// message and a continuation, we can embed the handshake initiation
|
||||
// into the HTTP protocol switching request and avoid a bit of delay.
|
||||
func ClientDeferred(machineKey key.MachinePrivate, controlKey key.MachinePublic, protocolVersion uint16) (initialHandshake []byte, continueHandshake HandshakeContinuation, err error) {
|
||||
func ClientDeferred(machineKey key.MachinePrivate, controlKey key.MachinePublic) (initialHandshake []byte, continueHandshake HandshakeContinuation, err error) {
|
||||
var s symmetricState
|
||||
s.Initialize()
|
||||
|
||||
@@ -78,7 +78,7 @@ func ClientDeferred(machineKey key.MachinePrivate, controlKey key.MachinePublic,
|
||||
s.MixHash(controlKey.UntypedBytes())
|
||||
|
||||
// -> e, es, s, ss
|
||||
init := mkInitiationMessage(protocolVersion)
|
||||
init := mkInitiationMessage()
|
||||
machineEphemeral := key.NewMachine()
|
||||
machineEphemeralPub := machineEphemeral.Public()
|
||||
copy(init.EphemeralPub(), machineEphemeralPub.UntypedBytes())
|
||||
@@ -96,7 +96,7 @@ func ClientDeferred(machineKey key.MachinePrivate, controlKey key.MachinePublic,
|
||||
s.EncryptAndHash(cipher, init.Tag(), nil) // empty message payload
|
||||
|
||||
cont := func(ctx context.Context, conn net.Conn) (*Conn, error) {
|
||||
return continueClientHandshake(ctx, conn, &s, machineKey, machineEphemeral, controlKey, protocolVersion)
|
||||
return continueClientHandshake(ctx, conn, &s, machineKey, machineEphemeral, controlKey)
|
||||
}
|
||||
return init[:], cont, nil
|
||||
}
|
||||
@@ -107,8 +107,8 @@ func ClientDeferred(machineKey key.MachinePrivate, controlKey key.MachinePublic,
|
||||
// This is a helper for when you don't need the fancy
|
||||
// continuation-style handshake, and just want to synchronously
|
||||
// upgrade a net.Conn to a secure transport.
|
||||
func Client(ctx context.Context, conn net.Conn, machineKey key.MachinePrivate, controlKey key.MachinePublic, protocolVersion uint16) (*Conn, error) {
|
||||
init, cont, err := ClientDeferred(machineKey, controlKey, protocolVersion)
|
||||
func Client(ctx context.Context, conn net.Conn, machineKey key.MachinePrivate, controlKey key.MachinePublic) (*Conn, error) {
|
||||
init, cont, err := ClientDeferred(machineKey, controlKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func Client(ctx context.Context, conn net.Conn, machineKey key.MachinePrivate, c
|
||||
return cont(ctx, conn)
|
||||
}
|
||||
|
||||
func continueClientHandshake(ctx context.Context, conn net.Conn, s *symmetricState, machineKey, machineEphemeral key.MachinePrivate, controlKey key.MachinePublic, protocolVersion uint16) (*Conn, error) {
|
||||
func continueClientHandshake(ctx context.Context, conn net.Conn, s *symmetricState, machineKey, machineEphemeral key.MachinePrivate, controlKey key.MachinePublic) (*Conn, error) {
|
||||
// No matter what, this function can only run once per s. Ensure
|
||||
// attempted reuse causes a panic.
|
||||
defer func() {
|
||||
@@ -239,12 +239,9 @@ func Server(ctx context.Context, conn net.Conn, controlKey key.MachinePrivate, o
|
||||
} else if _, err := io.ReadFull(conn, init.Header()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Just a rename to make it more obvious what the value is. In the
|
||||
// current implementation we don't need to block any protocol
|
||||
// versions at this layer, it's safe to let the handshake proceed
|
||||
// and then let the caller make decisions based on the agreed-upon
|
||||
// protocol version.
|
||||
clientVersion := init.Version()
|
||||
if init.Version() != protocolVersion {
|
||||
return nil, sendErr("unsupported protocol version")
|
||||
}
|
||||
if init.Type() != msgTypeInitiation {
|
||||
return nil, sendErr("unexpected handshake message type")
|
||||
}
|
||||
@@ -260,7 +257,7 @@ func Server(ctx context.Context, conn net.Conn, controlKey key.MachinePrivate, o
|
||||
|
||||
// prologue. Can only do this once we at least think the client is
|
||||
// handshaking using a supported version.
|
||||
s.MixHash(protocolVersionPrologue(clientVersion))
|
||||
s.MixHash(protocolVersionPrologue(protocolVersion))
|
||||
|
||||
// <- s
|
||||
// ...
|
||||
@@ -313,7 +310,7 @@ func Server(ctx context.Context, conn net.Conn, controlKey key.MachinePrivate, o
|
||||
|
||||
c := &Conn{
|
||||
conn: conn,
|
||||
version: clientVersion,
|
||||
version: protocolVersion,
|
||||
peer: machineKey,
|
||||
handshakeHash: s.h,
|
||||
tx: txState{
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestHandshake(t *testing.T) {
|
||||
serverErr <- err
|
||||
}()
|
||||
|
||||
client, err := Client(context.Background(), clientConn, clientKey, serverKey.Public(), testProtocolVersion)
|
||||
client, err := Client(context.Background(), clientConn, clientKey, serverKey.Public())
|
||||
if err != nil {
|
||||
t.Fatalf("client connection failed: %v", err)
|
||||
}
|
||||
@@ -42,8 +42,8 @@ func TestHandshake(t *testing.T) {
|
||||
t.Fatal("client and server disagree on handshake hash")
|
||||
}
|
||||
|
||||
if client.ProtocolVersion() != int(testProtocolVersion) {
|
||||
t.Fatalf("client reporting wrong protocol version %d, want %d", client.ProtocolVersion(), testProtocolVersion)
|
||||
if client.ProtocolVersion() != int(protocolVersion) {
|
||||
t.Fatalf("client reporting wrong protocol version %d, want %d", client.ProtocolVersion(), protocolVersion)
|
||||
}
|
||||
if client.ProtocolVersion() != server.ProtocolVersion() {
|
||||
t.Fatalf("peers disagree on protocol version, client=%d server=%d", client.ProtocolVersion(), server.ProtocolVersion())
|
||||
@@ -82,7 +82,7 @@ func TestNoReuse(t *testing.T) {
|
||||
serverErr <- err
|
||||
}()
|
||||
|
||||
client, err := Client(context.Background(), clientConn, clientKey, serverKey.Public(), testProtocolVersion)
|
||||
client, err := Client(context.Background(), clientConn, clientKey, serverKey.Public())
|
||||
if err != nil {
|
||||
t.Fatalf("client connection failed: %v", err)
|
||||
}
|
||||
@@ -181,7 +181,7 @@ func TestTampering(t *testing.T) {
|
||||
serverErr <- err
|
||||
}()
|
||||
|
||||
_, err := Client(context.Background(), clientConn, clientKey, serverKey.Public(), testProtocolVersion)
|
||||
_, err := Client(context.Background(), clientConn, clientKey, serverKey.Public())
|
||||
if err == nil {
|
||||
t.Fatal("client connection succeeded despite tampering")
|
||||
}
|
||||
@@ -204,7 +204,7 @@ func TestTampering(t *testing.T) {
|
||||
serverErr <- err
|
||||
}()
|
||||
|
||||
_, err := Client(context.Background(), clientConn, clientKey, serverKey.Public(), testProtocolVersion)
|
||||
_, err := Client(context.Background(), clientConn, clientKey, serverKey.Public())
|
||||
if err == nil {
|
||||
t.Fatal("client connection succeeded despite tampering")
|
||||
}
|
||||
@@ -231,7 +231,7 @@ func TestTampering(t *testing.T) {
|
||||
serverErr <- err
|
||||
}()
|
||||
|
||||
client, err := Client(context.Background(), clientConn, clientKey, serverKey.Public(), testProtocolVersion)
|
||||
client, err := Client(context.Background(), clientConn, clientKey, serverKey.Public())
|
||||
if err != nil {
|
||||
t.Fatalf("client handshake failed: %v", err)
|
||||
}
|
||||
@@ -281,7 +281,7 @@ func TestTampering(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
client, err := Client(context.Background(), clientConn, clientKey, serverKey.Public(), testProtocolVersion)
|
||||
client, err := Client(context.Background(), clientConn, clientKey, serverKey.Public())
|
||||
if err != nil {
|
||||
t.Fatalf("client handshake failed: %v", err)
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ func TestInteropServer(t *testing.T) {
|
||||
)
|
||||
|
||||
go func() {
|
||||
client, err := Client(context.Background(), s1, machineKey, controlKey.Public(), testProtocolVersion)
|
||||
client, err := Client(context.Background(), s1, machineKey, controlKey.Public())
|
||||
clientErr <- err
|
||||
if err != nil {
|
||||
return
|
||||
@@ -121,11 +121,11 @@ func noiseExplorerClient(conn net.Conn, controlKey key.MachinePublic, machineKey
|
||||
copy(mk.public_key[:], machineKey.Public().UntypedBytes())
|
||||
var peerKey [32]byte
|
||||
copy(peerKey[:], controlKey.UntypedBytes())
|
||||
session := InitSession(true, protocolVersionPrologue(testProtocolVersion), mk, peerKey)
|
||||
session := InitSession(true, protocolVersionPrologue(protocolVersion), mk, peerKey)
|
||||
|
||||
_, msg1 := SendMessage(&session, nil)
|
||||
var hdr [initiationHeaderLen]byte
|
||||
binary.BigEndian.PutUint16(hdr[:2], testProtocolVersion)
|
||||
binary.BigEndian.PutUint16(hdr[:2], protocolVersion)
|
||||
hdr[2] = msgTypeInitiation
|
||||
binary.BigEndian.PutUint16(hdr[3:5], 96)
|
||||
if _, err := conn.Write(hdr[:]); err != nil {
|
||||
@@ -193,7 +193,7 @@ func noiseExplorerServer(conn net.Conn, controlKey key.MachinePrivate, wantMachi
|
||||
var mk keypair
|
||||
copy(mk.private_key[:], controlKey.UntypedBytes())
|
||||
copy(mk.public_key[:], controlKey.Public().UntypedBytes())
|
||||
session := InitSession(false, protocolVersionPrologue(testProtocolVersion), mk, [32]byte{})
|
||||
session := InitSession(false, protocolVersionPrologue(protocolVersion), mk, [32]byte{})
|
||||
|
||||
var buf [1024]byte
|
||||
if _, err := io.ReadFull(conn, buf[:101]); err != nil {
|
||||
|
||||
@@ -39,9 +39,9 @@ const (
|
||||
// 16b: message tag (authenticates the whole message)
|
||||
type initiationMessage [101]byte
|
||||
|
||||
func mkInitiationMessage(protocolVersion uint16) initiationMessage {
|
||||
func mkInitiationMessage() initiationMessage {
|
||||
var ret initiationMessage
|
||||
binary.BigEndian.PutUint16(ret[:2], protocolVersion)
|
||||
binary.BigEndian.PutUint16(ret[:2], uint16(protocolVersion))
|
||||
ret[2] = msgTypeInitiation
|
||||
binary.BigEndian.PutUint16(ret[3:5], uint16(len(ret.Payload())))
|
||||
return ret
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -38,7 +39,6 @@ import (
|
||||
"tailscale.com/net/dnsfallback"
|
||||
"tailscale.com/net/interfaces"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/net/tlsdial"
|
||||
"tailscale.com/net/tshttpproxy"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -69,7 +69,6 @@ type Direct struct {
|
||||
keepSharerAndUserSplit bool
|
||||
skipIPForwardingCheck bool
|
||||
pinger Pinger
|
||||
popBrowser func(url string) // or nil
|
||||
|
||||
mu sync.Mutex // mutex guards the following fields
|
||||
serverKey key.MachinePublic // original ("legacy") nacl crypto_box-based public key
|
||||
@@ -101,10 +100,9 @@ type Options struct {
|
||||
NewDecompressor func() (Decompressor, error)
|
||||
KeepAlive bool
|
||||
Logf logger.Logf
|
||||
HTTPTestClient *http.Client // optional HTTP client to use (for tests only)
|
||||
DebugFlags []string // debug settings to send to control
|
||||
LinkMonitor *monitor.Mon // optional link monitor
|
||||
PopBrowserURL func(url string) // optional func to open browser
|
||||
HTTPTestClient *http.Client // optional HTTP client to use (for tests only)
|
||||
DebugFlags []string // debug settings to send to control
|
||||
LinkMonitor *monitor.Mon // optional link monitor
|
||||
|
||||
// KeepSharerAndUserSplit controls whether the client
|
||||
// understands Node.Sharer. If false, the Sharer is mapped to the User.
|
||||
@@ -200,7 +198,6 @@ func NewDirect(opts Options) (*Direct, error) {
|
||||
linkMon: opts.LinkMonitor,
|
||||
skipIPForwardingCheck: opts.SkipIPForwardingCheck,
|
||||
pinger: opts.Pinger,
|
||||
popBrowser: opts.PopBrowserURL,
|
||||
}
|
||||
if opts.Hostinfo == nil {
|
||||
c.SetHostinfo(hostinfo.New())
|
||||
@@ -852,15 +849,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
|
||||
metricMapResponsePings.Add(1)
|
||||
go answerPing(c.logf, c.httpc, pr)
|
||||
}
|
||||
if u := resp.PopBrowserURL; u != "" && u != sess.lastPopBrowserURL {
|
||||
sess.lastPopBrowserURL = u
|
||||
if c.popBrowser != nil {
|
||||
c.logf("netmap: control says to open URL %v; opening...", u)
|
||||
c.popBrowser(u)
|
||||
} else {
|
||||
c.logf("netmap: control says to open URL %v; no popBrowser func", u)
|
||||
}
|
||||
}
|
||||
|
||||
if resp.ControlTime != nil && !resp.ControlTime.IsZero() {
|
||||
c.logf.JSON(1, "controltime", resp.ControlTime.UTC())
|
||||
}
|
||||
@@ -869,7 +858,6 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
|
||||
} else {
|
||||
vlogf("netmap: got new map")
|
||||
}
|
||||
|
||||
select {
|
||||
case timeoutReset <- struct{}{}:
|
||||
vlogf("netmap: sent timer reset")
|
||||
@@ -1151,17 +1139,89 @@ func TrimWGConfig() opt.Bool {
|
||||
//
|
||||
// It should not return false positives.
|
||||
//
|
||||
// TODO(bradfitz): Change controlclient.Options.SkipIPForwardingCheck into a
|
||||
// func([]netaddr.IPPrefix) error signature instead.
|
||||
// TODO(bradfitz): merge this code into LocalBackend.CheckIPForwarding
|
||||
// and change controlclient.Options.SkipIPForwardingCheck into a
|
||||
// func([]netaddr.IPPrefix) error signature instead. Then we only have
|
||||
// one copy of this code.
|
||||
func ipForwardingBroken(routes []netaddr.IPPrefix, state *interfaces.State) bool {
|
||||
warn, err := netutil.CheckIPForwarding(routes, state)
|
||||
if err != nil {
|
||||
// Oh well, we tried. This is just for debugging.
|
||||
// We don't want false positives.
|
||||
// TODO: maybe we want a different warning for inability to check?
|
||||
if len(routes) == 0 {
|
||||
// Nothing to route, so no need to warn.
|
||||
return false
|
||||
}
|
||||
return warn != nil
|
||||
|
||||
if runtime.GOOS != "linux" {
|
||||
// We only do subnet routing on Linux for now.
|
||||
// It might work on darwin/macOS when building from source, so
|
||||
// don't return true for other OSes. We can OS-based warnings
|
||||
// already in the admin panel.
|
||||
return false
|
||||
}
|
||||
|
||||
localIPs := map[netaddr.IP]bool{}
|
||||
for _, addrs := range state.InterfaceIPs {
|
||||
for _, pfx := range addrs {
|
||||
localIPs[pfx.IP()] = true
|
||||
}
|
||||
}
|
||||
|
||||
v4Routes, v6Routes := false, false
|
||||
for _, r := range routes {
|
||||
// It's possible to advertise a route to one of the local
|
||||
// machine's local IPs. IP forwarding isn't required for this
|
||||
// to work, so we shouldn't warn for such exports.
|
||||
if r.IsSingleIP() && localIPs[r.IP()] {
|
||||
continue
|
||||
}
|
||||
if r.IP().Is4() {
|
||||
v4Routes = true
|
||||
} else {
|
||||
v6Routes = true
|
||||
}
|
||||
}
|
||||
|
||||
if v4Routes {
|
||||
out, err := ioutil.ReadFile("/proc/sys/net/ipv4/ip_forward")
|
||||
if err != nil {
|
||||
// Try another way.
|
||||
out, err = exec.Command("sysctl", "-n", "net.ipv4.ip_forward").Output()
|
||||
}
|
||||
if err != nil {
|
||||
// Oh well, we tried. This is just for debugging.
|
||||
// We don't want false positives.
|
||||
// TODO: maybe we want a different warning for inability to check?
|
||||
return false
|
||||
}
|
||||
if strings.TrimSpace(string(out)) == "0" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if v6Routes {
|
||||
// Note: you might be wondering why we check only the state of
|
||||
// conf.all.forwarding, rather than per-interface forwarding
|
||||
// configuration. According to kernel documentation, it seems
|
||||
// that to actually forward packets, you need to enable
|
||||
// forwarding globally, and the per-interface forwarding
|
||||
// setting only alters other things such as how router
|
||||
// advertisements are handled. The kernel itself warns that
|
||||
// enabling forwarding per-interface and not globally will
|
||||
// probably not work, so I feel okay calling those configs
|
||||
// broken until we have proof otherwise.
|
||||
out, err := ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/forwarding")
|
||||
if err != nil {
|
||||
out, err = exec.Command("sysctl", "-n", "net.ipv6.conf.all.forwarding").Output()
|
||||
}
|
||||
if err != nil {
|
||||
// Oh well, we tried. This is just for debugging.
|
||||
// We don't want false positives.
|
||||
// TODO: maybe we want a different warning for inability to check?
|
||||
return false
|
||||
}
|
||||
if strings.TrimSpace(string(out)) == "0" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isUniquePingRequest reports whether pr contains a new PingRequest.URL
|
||||
|
||||
@@ -44,7 +44,6 @@ type mapSession struct {
|
||||
previousPeers []*tailcfg.Node // for delta-purposes
|
||||
lastDomain string
|
||||
lastHealth []string
|
||||
lastPopBrowserURL string
|
||||
|
||||
// netMapBuilding is non-nil during a netmapForResponse call,
|
||||
// containing the value to be returned, once fully populated.
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -18,7 +17,6 @@ import (
|
||||
"golang.org/x/net/http2"
|
||||
"tailscale.com/control/controlbase"
|
||||
"tailscale.com/control/controlhttp"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/util/multierr"
|
||||
)
|
||||
@@ -148,12 +146,7 @@ func (nc *noiseClient) dial(_, _ string, _ *tls.Config) (net.Conn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if tailcfg.CurrentCapabilityVersion > math.MaxUint16 {
|
||||
// Panic, because a test should have started failing several
|
||||
// thousand version numbers before getting to this point.
|
||||
panic("capability version is too high to fit in the wire protocol")
|
||||
}
|
||||
conn, err := controlhttp.Dial(ctx, nc.serverHost, nc.privKey, nc.serverPubKey, uint16(tailcfg.CurrentCapabilityVersion))
|
||||
conn, err := controlhttp.Dial(ctx, nc.serverHost, nc.privKey, nc.serverPubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
// 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.
|
||||
|
||||
package controlclient
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
// maxAllowedNoiseVersion is the highest we expect the Tailscale
|
||||
// capability version to ever get. It's a value close to 2^16, but
|
||||
// with enough leeway that we get a very early warning that it's time
|
||||
// to rework the wire protocol to allow larger versions, while still
|
||||
// giving us headroom to bump this test and fix the build.
|
||||
//
|
||||
// Code elsewhere in the client will panic() if the tailcfg capability
|
||||
// version exceeds 16 bits, so take a failure of this test seriously.
|
||||
const maxAllowedNoiseVersion = math.MaxUint16 - 5000
|
||||
|
||||
func TestNoiseVersion(t *testing.T) {
|
||||
if tailcfg.CurrentCapabilityVersion > maxAllowedNoiseVersion {
|
||||
t.Fatalf("tailcfg.CurrentCapabilityVersion is %d, want <=%d", tailcfg.CurrentCapabilityVersion, maxAllowedNoiseVersion)
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,6 @@ import (
|
||||
"tailscale.com/net/dnscache"
|
||||
"tailscale.com/net/dnsfallback"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/net/tlsdial"
|
||||
"tailscale.com/net/tshttpproxy"
|
||||
"tailscale.com/types/key"
|
||||
@@ -65,7 +64,7 @@ const (
|
||||
//
|
||||
// The provided ctx is only used for the initial connection, until
|
||||
// Dial returns. It does not affect the connection once established.
|
||||
func Dial(ctx context.Context, addr string, machineKey key.MachinePrivate, controlKey key.MachinePublic, protocolVersion uint16) (*controlbase.Conn, error) {
|
||||
func Dial(ctx context.Context, addr string, machineKey key.MachinePrivate, controlKey key.MachinePublic) (*controlbase.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -77,7 +76,6 @@ func Dial(ctx context.Context, addr string, machineKey key.MachinePrivate, contr
|
||||
httpsPort: "443",
|
||||
machineKey: machineKey,
|
||||
controlKey: controlKey,
|
||||
version: protocolVersion,
|
||||
proxyFunc: tshttpproxy.ProxyFromEnvironment,
|
||||
}
|
||||
return a.dial()
|
||||
@@ -90,7 +88,6 @@ type dialParams struct {
|
||||
httpsPort string
|
||||
machineKey key.MachinePrivate
|
||||
controlKey key.MachinePublic
|
||||
version uint16
|
||||
proxyFunc func(*http.Request) (*url.URL, error) // or nil
|
||||
|
||||
// For tests only
|
||||
@@ -98,7 +95,7 @@ type dialParams struct {
|
||||
}
|
||||
|
||||
func (a *dialParams) dial() (*controlbase.Conn, error) {
|
||||
init, cont, err := controlbase.ClientDeferred(a.machineKey, a.controlKey, a.version)
|
||||
init, cont, err := controlbase.ClientDeferred(a.machineKey, a.controlKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -122,7 +119,7 @@ func (a *dialParams) dial() (*controlbase.Conn, error) {
|
||||
// being difficult and see if we can get through over HTTPS.
|
||||
u.Scheme = "https"
|
||||
u.Host = net.JoinHostPort(a.host, a.httpsPort)
|
||||
init, cont, err = controlbase.ClientDeferred(a.machineKey, a.controlKey, a.version)
|
||||
init, cont, err = controlbase.ClientDeferred(a.machineKey, a.controlKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -235,5 +232,22 @@ func (a *dialParams) tryURL(u *url.URL, init []byte) (net.Conn, error) {
|
||||
return nil, errors.New("http Transport did not provide a writable body")
|
||||
}
|
||||
|
||||
return netutil.NewAltReadWriteCloserConn(rwc, switchedConn), nil
|
||||
return &wrappedConn{switchedConn, rwc}, nil
|
||||
}
|
||||
|
||||
type wrappedConn struct {
|
||||
net.Conn
|
||||
rwc io.ReadWriteCloser
|
||||
}
|
||||
|
||||
func (w *wrappedConn) Read(bs []byte) (int, error) {
|
||||
return w.rwc.Read(bs)
|
||||
}
|
||||
|
||||
func (w *wrappedConn) Write(bs []byte) (int, error) {
|
||||
return w.rwc.Write(bs)
|
||||
}
|
||||
|
||||
func (w *wrappedConn) Close() error {
|
||||
return w.rwc.Close()
|
||||
}
|
||||
|
||||
@@ -104,7 +104,6 @@ func TestControlHTTP(t *testing.T) {
|
||||
func testControlHTTP(t *testing.T, proxy proxy) {
|
||||
client, server := key.NewMachine(), key.NewMachine()
|
||||
|
||||
const testProtocolVersion = 1
|
||||
sch := make(chan serverResult, 1)
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := AcceptHTTP(context.Background(), w, r, server)
|
||||
@@ -153,7 +152,6 @@ func testControlHTTP(t *testing.T, proxy proxy) {
|
||||
httpsPort: strconv.Itoa(httpsLn.Addr().(*net.TCPAddr).Port),
|
||||
machineKey: client,
|
||||
controlKey: server.Public(),
|
||||
version: testProtocolVersion,
|
||||
insecureTLS: true,
|
||||
}
|
||||
|
||||
|
||||
@@ -5,14 +5,15 @@
|
||||
package controlhttp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"tailscale.com/control/controlbase"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
@@ -61,7 +62,9 @@ func AcceptHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request, pri
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("flushing hijacked HTTP buffer: %w", err)
|
||||
}
|
||||
conn = netutil.NewDrainBufConn(conn, brw.Reader)
|
||||
if brw.Reader.Buffered() > 0 {
|
||||
conn = &drainBufConn{conn, brw.Reader}
|
||||
}
|
||||
|
||||
nc, err := controlbase.Server(ctx, conn, private, init)
|
||||
if err != nil {
|
||||
@@ -71,3 +74,22 @@ func AcceptHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request, pri
|
||||
|
||||
return nc, nil
|
||||
}
|
||||
|
||||
// drainBufConn is a net.Conn with an initial bunch of bytes in a
|
||||
// bufio.Reader. Read drains the bufio.Reader until empty, then passes
|
||||
// through subsequent reads to the Conn directly.
|
||||
type drainBufConn struct {
|
||||
net.Conn
|
||||
r *bufio.Reader
|
||||
}
|
||||
|
||||
func (b *drainBufConn) Read(bs []byte) (int, error) {
|
||||
if b.r == nil {
|
||||
return b.Conn.Read(bs)
|
||||
}
|
||||
n, err := b.r.Read(bs)
|
||||
if b.r.Buffered() == 0 {
|
||||
b.r = nil
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -391,18 +391,6 @@ func (s *Server) isClosed() bool {
|
||||
return s.closed
|
||||
}
|
||||
|
||||
// IsClientConnectedForTest reports whether the client with specified key is connected.
|
||||
// This is used in tests to verify that nodes are connected.
|
||||
func (s *Server) IsClientConnectedForTest(k key.NodePublic) bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
x, ok := s.clients[k]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return x.ActiveClient() != nil
|
||||
}
|
||||
|
||||
// Accept adds a new connection to the server and serves it.
|
||||
//
|
||||
// The provided bufio ReadWriter must be already connected to nc.
|
||||
|
||||
@@ -37,7 +37,7 @@ func noteEnv(k, v string) {
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if _, ok := set[k]; !ok {
|
||||
if _, ok := set[v]; !ok {
|
||||
list = append(list, k)
|
||||
}
|
||||
set[k] = v
|
||||
@@ -142,10 +142,3 @@ func LookupInt(envVar string) (v int, ok bool) {
|
||||
// UseWIPCode is whether TAILSCALE_USE_WIP_CODE is set to permit use
|
||||
// of Work-In-Progress code.
|
||||
func UseWIPCode() bool { return Bool("TAILSCALE_USE_WIP_CODE") }
|
||||
|
||||
// CanSSHD is whether the Tailscale SSH server is allowed to run.
|
||||
//
|
||||
// If disabled, the SSH server won't start (won't intercept port 22)
|
||||
// if already enabled and any attempt to re-enable it will result in
|
||||
// an error.
|
||||
func CanSSHD() bool { return !Bool("TS_DISABLE_SSH_SERVER") }
|
||||
|
||||
29
go.mod
29
go.mod
@@ -5,9 +5,7 @@ go 1.18
|
||||
require (
|
||||
filippo.io/mkcert v1.4.3
|
||||
github.com/akutz/memconn v0.1.0
|
||||
github.com/alessio/shellescape v1.4.1
|
||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
github.com/aws/aws-sdk-go-v2 v1.11.2
|
||||
github.com/aws/aws-sdk-go-v2/config v1.11.0
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.7.4
|
||||
@@ -25,40 +23,41 @@ require (
|
||||
github.com/goreleaser/nfpm v1.10.3
|
||||
github.com/iancoleman/strcase v0.2.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20211209223715-7d93572ebe8e
|
||||
github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20211203074127-fd9a11f42291
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.13.6
|
||||
github.com/mdlayher/genetlink v1.2.0
|
||||
github.com/mdlayher/netlink v1.6.0
|
||||
github.com/mdlayher/sdnotify v1.0.0
|
||||
github.com/mdlayher/sdnotify v0.0.0-20210228150836-ea3ec207d697
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/mitchellh/go-ps v1.0.0
|
||||
github.com/pborman/getopt v1.1.0
|
||||
github.com/peterbourgon/ff/v3 v3.1.2
|
||||
github.com/pkg/sftp v1.13.4
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d
|
||||
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502
|
||||
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20220330002111-62119522bbcf
|
||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05
|
||||
github.com/tailscale/hujson v0.0.0-20211105212140-3a0adc019d83
|
||||
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85
|
||||
github.com/tailscale/ssh v0.3.4-0.20220313013419-be8b7add4057
|
||||
github.com/tcnksm/go-httpstat v0.2.0
|
||||
github.com/toqueteos/webbrowser v1.2.0
|
||||
github.com/u-root/u-root v0.8.0
|
||||
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
|
||||
go4.org/mem v0.0.0-20210711025021-927187094b94
|
||||
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
|
||||
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f
|
||||
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
|
||||
golang.org/x/tools v0.1.11-0.20220413170336-afc6aad76eb1
|
||||
golang.org/x/tools v0.1.10
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220317000134-95b48cdb3961
|
||||
golang.zx2c4.com/wireguard/windows v0.4.10
|
||||
gvisor.dev/gvisor v0.0.0-20220407223209-21871174d445
|
||||
honnef.co/go/tools v0.4.0-0.dev.0.20220404092545-59d7a2877f83
|
||||
gvisor.dev/gvisor v0.0.0-20220126021142-d8aa030b2591
|
||||
honnef.co/go/tools v0.3.0-0.dev.0.20220306074811-23e1086441d2
|
||||
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6
|
||||
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
|
||||
inet.af/wf v0.0.0-20211204062712-86aaea0a7310
|
||||
@@ -69,7 +68,7 @@ require (
|
||||
4d63.com/gochecknoglobals v0.1.0 // indirect
|
||||
github.com/Antonboom/errname v0.1.5 // indirect
|
||||
github.com/Antonboom/nilnil v0.1.0 // indirect
|
||||
github.com/BurntSushi/toml v1.1.0 // indirect
|
||||
github.com/BurntSushi/toml v0.4.1 // indirect
|
||||
github.com/Djarvur/go-err113 v0.1.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
@@ -80,6 +79,7 @@ require (
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||
github.com/alexkohler/prealloc v1.0.0 // indirect
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||
github.com/ashanbrown/forbidigo v1.2.0 // indirect
|
||||
github.com/ashanbrown/makezero v0.0.0-20210520155254-b6261585ddde // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.0.0 // indirect
|
||||
@@ -105,7 +105,6 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/charithe/durationcheck v0.0.9 // indirect
|
||||
github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af // indirect
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect
|
||||
github.com/daixiang0/gci v0.2.9 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/denis-tingajkin/go-header v0.4.2 // indirect
|
||||
@@ -184,7 +183,7 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mbilski/exhaustivestruct v1.2.0 // indirect
|
||||
github.com/mdlayher/socket v0.2.3 // indirect
|
||||
github.com/mdlayher/socket v0.1.1 // indirect
|
||||
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect
|
||||
github.com/mgechev/revive v1.1.2 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
@@ -249,7 +248,7 @@ require (
|
||||
github.com/yeya24/promlinter v0.1.0 // indirect
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20220328175248-053ad81199eb // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
|
||||
279
go.sum
279
go.sum
@@ -1,6 +1,7 @@
|
||||
4d63.com/gochecknoglobals v0.0.0-20201008074935-acfc0b28355a/go.mod h1:wfdC5ZjKSPr7CybKEcgJhUOgeAQW1+7WcyK8OvUilfo=
|
||||
4d63.com/gochecknoglobals v0.1.0 h1:zeZSRqj5yCg28tCkIV/z/lWbwvNm5qnKVS15PI8nhD0=
|
||||
4d63.com/gochecknoglobals v0.1.0/go.mod h1:wfdC5ZjKSPr7CybKEcgJhUOgeAQW1+7WcyK8OvUilfo=
|
||||
bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM=
|
||||
bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
@@ -58,10 +59,16 @@ github.com/Antonboom/errname v0.1.5 h1:IM+A/gz0pDhKmlt5KSNTVAvfLMb+65RxavBXpRtCU
|
||||
github.com/Antonboom/errname v0.1.5/go.mod h1:DugbBstvPFQbv/5uLcRRzfrNqKE9tVdVCqWCLp6Cifo=
|
||||
github.com/Antonboom/nilnil v0.1.0 h1:DLDavmg0a6G/F4Lt9t7Enrbgb3Oph6LnDE6YVsmTt74=
|
||||
github.com/Antonboom/nilnil v0.1.0/go.mod h1:PhHLvRPSghY5Y7mX4TW+BHZQYo1A8flE5H20D3IPZBo=
|
||||
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
|
||||
@@ -81,16 +88,21 @@ github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZC
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/Microsoft/go-winio v0.4.15/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||
github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
|
||||
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us=
|
||||
github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 h1:XcF0cTDJeiuZ5NU8w7WUDge0HRwwNRmxj/GGk6KSA6g=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
|
||||
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
|
||||
@@ -104,8 +116,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
|
||||
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
|
||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
|
||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw=
|
||||
@@ -167,6 +177,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.11.1 h1:QKR7wy5e650q70PFKMfGF9sTo0rZ
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.11.1/go.mod h1:UV2N5HaPfdbDpkgkz4sRzWCvQswZjdO1FfqCWl0t7RA=
|
||||
github.com/aws/smithy-go v1.9.0 h1:c7FUdEqrQA1/UVKKCNDFQPNKGp4FQg3YW4Ck5SLTG58=
|
||||
github.com/aws/smithy-go v1.9.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||
github.com/bazelbuild/rules_go v0.27.0/go.mod h1:MC23Dc/wkXEyk3Wpq6lCqz0ZAYOZDw2DR5y3N1q2i7M=
|
||||
github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@@ -191,7 +203,10 @@ github.com/butuzov/ireturn v0.1.1 h1:QvrO2QF2+/Cx1WA/vETCIYBKtRjc30vesdoPUNo1EbY
|
||||
github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc=
|
||||
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e h1:hHg27A0RSSp2Om9lubZpiMgVbvn39bsUmW9U5h0twqc=
|
||||
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
|
||||
github.com/cenkalti/backoff v1.1.1-0.20190506075156-2146c9339422/go.mod h1:b6Nc7NRH5C4aCISLry0tLnTjcuTEvoiqcWDdsU0sOGM=
|
||||
github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
@@ -203,15 +218,38 @@ github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af/go.mod h1:Qjyv4H3/
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.8.1 h1:bLSSEbBLqGPXxls55pGr5qWZaTqcmfDJHhou7t254ao=
|
||||
github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk=
|
||||
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
|
||||
github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
||||
github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
||||
github.com/cilium/ebpf v0.7.0 h1:1k/q3ATgxSXRdrmPfH8d7YK0GfqVsEKZAX9dQZvs56k=
|
||||
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
|
||||
github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU=
|
||||
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||
github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
|
||||
github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.3.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/containerd/continuity v0.2.1/go.mod h1:wCYX+dRqZdImhGucXOqTQn05AhX6EUDaGEMUzTFFpLg=
|
||||
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
||||
github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4=
|
||||
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
|
||||
github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok=
|
||||
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
|
||||
github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
|
||||
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
|
||||
github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
@@ -223,8 +261,8 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||
github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
@@ -233,6 +271,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/daixiang0/gci v0.2.4/go.mod h1:+AV8KmHTGxxwp/pY84TLQfFKp2vuKXXJVzF3kD/hfR4=
|
||||
@@ -241,6 +280,7 @@ github.com/daixiang0/gci v0.2.9 h1:iwJvwQpBZmMg31w+QQ6jsyZ54KEATn6/nfARbBNW294=
|
||||
github.com/daixiang0/gci v0.2.9/go.mod h1:+4dZ7TISfSmqfAGv59ePaHfNzgGtIkHAhhdKggP1JAc=
|
||||
github.com/dave/jennifer v1.4.1 h1:XyqG6cn5RQsTj3qlWQTKlRGAyrTcsk1kUmWdZBzRjDw=
|
||||
github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpTVGlwA=
|
||||
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -250,9 +290,14 @@ github.com/denis-tingajkin/go-header v0.4.2 h1:jEeSF4sdv8/3cT/WY8AgDHUoItNSoEZ7q
|
||||
github.com/denis-tingajkin/go-header v0.4.2/go.mod h1:eLRHAVXzE5atsKAnNRDB90WHCFFnBUn4RN0nRcs1LJA=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -262,12 +307,14 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/esimonov/ifshort v1.0.3 h1:JD6x035opqGec5fZ0TLjXeROD2p5H7oLGn8MKfy9HTM=
|
||||
github.com/esimonov/ifshort v1.0.3/go.mod h1:yZqNJUrNn20K8Q9n2CrjTKYyVEmX209Hgu+M1LBpeZE=
|
||||
github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw=
|
||||
github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY=
|
||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
||||
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=
|
||||
@@ -277,6 +324,7 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF
|
||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
|
||||
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
@@ -286,6 +334,7 @@ github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5
|
||||
github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM=
|
||||
github.com/fzipp/gocyclo v0.3.1 h1:A9UeX3HJSXTBzvHzhqoYVuE0eAhe+aM8XBCCwsPMZOc=
|
||||
github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
@@ -294,6 +343,7 @@ github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
||||
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
|
||||
github.com/gliderlabs/ssh v0.1.2-0.20181113160402-cbabf5414432/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/gliderlabs/ssh v0.3.3 h1:mBQ8NiOgDkINJrZtoizkC3nDNYgSaWtxyem6S2XHBtA=
|
||||
github.com/gliderlabs/ssh v0.3.3/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914=
|
||||
@@ -321,10 +371,15 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
@@ -371,6 +426,7 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
|
||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
@@ -379,9 +435,12 @@ github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/gojuno/minimock/v3 v3.0.4/go.mod h1:HqeqnwV8mAABn3pO5hqF+RE7gjA0jsN8cbbSogoGrzI=
|
||||
github.com/gojuno/minimock/v3 v3.0.8/go.mod h1:TPKxc8tiB8O83YH2//pOzxvEjaI3TMhd6ev/GmlMiYA=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -401,6 +460,7 @@ github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -471,7 +531,13 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI=
|
||||
github.com/google/go-tpm v0.2.1-0.20200615092505-5d8a91de9ae3/go.mod h1:iVLWvrPp/bHeEkxTFi9WG6K9w0iy2yIszHwZGHPbzAw=
|
||||
github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845/go.mod h1:AVfHadzbdzHo54inR2x1v640jdi1YSi3NauM2DUsxk0=
|
||||
github.com/google/goexpect v0.0.0-20191001010744-5b6988669ffa/go.mod h1:qtE5aAEkt0vOSA84DBh8aJsz6riL8ONfqfULY7lBjqc=
|
||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
|
||||
github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 h1:CVuJwN34x4xM2aT4sIKhmeib40NeBPhRihNjQmpJsA4=
|
||||
github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
@@ -496,6 +562,7 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/rpmpack v0.0.0-20201206194719-59e495f2b7e1 h1:BRIy5qQZKSC/nthA5ueW547F73BV5hMoIoxhPfhxa3k=
|
||||
github.com/google/rpmpack v0.0.0-20201206194719-59e495f2b7e1/go.mod h1:+y9lKiqDhR4zkLl+V9h4q0rdyrYVsWWm6LLCQP33DIk=
|
||||
github.com/google/subcommands v1.0.2-0.20190508160503-636abe8753b8/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/trillian v1.3.11/go.mod h1:0tPraVHrSDkA3BO6vKX67zgLXs6SsOAbHEivX+9mPgw=
|
||||
github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -506,9 +573,12 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
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=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.4.0/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
|
||||
github.com/gookit/color v1.3.1/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ=
|
||||
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
|
||||
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
|
||||
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20210225214923-2e10b2664254/go.mod h1:M9mZEtGIsR1oDaZagNPNG9iq9n2HrhZ17dsXk73V3Lw=
|
||||
@@ -523,6 +593,7 @@ github.com/goreleaser/nfpm v1.10.3/go.mod h1:EEC7YD5wi+ol0MiAshpgPANBOkjXDl7wqTL
|
||||
github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
@@ -545,6 +616,7 @@ github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW
|
||||
github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M=
|
||||
github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY=
|
||||
github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
@@ -590,6 +662,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
|
||||
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||
github.com/hexdigest/gowrap v1.1.7/go.mod h1:Z+nBFUDLa01iaNM+/jzoOA1JJ7sm51rnYFauKFUB5fs=
|
||||
github.com/hexdigest/gowrap v1.1.8/go.mod h1:H/JiFmQMp//tedlV8qt2xBdGzmne6bpbaSuiHmygnMw=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
||||
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
||||
@@ -601,6 +675,7 @@ github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
@@ -608,8 +683,10 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20210817203519-d82598001386/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20211209223715-7d93572ebe8e h1:IQpunlq7T+NiJJMO7ODYV2YWBiv/KnObR3gofX0mWOo=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20211209223715-7d93572ebe8e/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
|
||||
github.com/intel-go/cpuid v0.0.0-20200819041909-2aa72927c3e2/go.mod h1:RmeVYf9XrPRbRc3XIx0gLYA8qOFvNoPOfaEZduRlEp4=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
@@ -633,6 +710,7 @@ github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhB
|
||||
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
|
||||
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/txtarfs v0.0.0-20210218200122-0702f000015a/go.mod h1:izVPOvVRsHiKkeGCT6tYBNWyDVuzj9wAaBb5R9qamfw=
|
||||
@@ -641,8 +719,15 @@ github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
|
||||
github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b h1:Yws7RV6kZr2O7PPdT+RkbSmmOponA8i/1DuGHe8BRsM=
|
||||
github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b/go.mod h1:TzDCVOZKUa79z6iXbbXqhtAflVgUKaFkZ21M5tK5tzY=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmKSdU4VGSiv1bMsdqNALI4RSvvjtz65tTMCnD05qLo=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20211203074127-fd9a11f42291 h1:0J2ntV09uHLUHC79Z3YKJX2EnfOKL2QkMuHabu4L8JM=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20211203074127-fd9a11f42291/go.mod h1:J7jazXS6RFR/oZT8XdfdD2KQ1bl56ukeE1qt4w8UQaI=
|
||||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
@@ -659,6 +744,7 @@ github.com/julz/importas v0.0.0-20210419104244-841f0c0fe66d/go.mod h1:oSFU2R4XK/
|
||||
github.com/julz/importas v0.0.0-20210922140945-27e0a5d4dee2 h1:3sSu9gZvOTazWE4B4wsND7ofCsn75BD8Iz1OCBUZISs=
|
||||
github.com/julz/importas v0.0.0-20210922140945-27e0a5d4dee2/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0=
|
||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||
github.com/kaey/framebuffer v0.0.0-20140402104929-7b385489a1ff/go.mod h1:tS4qtlcKqtt3tCIHUflVSqeP3CLH5Qtv2szX9X2SyhU=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
@@ -673,12 +759,14 @@ github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
|
||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.10.6/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -691,6 +779,8 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.4-0.20190131011033-7dc38fb350b1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
@@ -723,6 +813,7 @@ github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
|
||||
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/maratori/testpackage v1.0.1 h1:QtJ5ZjqapShm0w5DosRjg0PRlSdAdlx+W6cCKoALdbQ=
|
||||
github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU=
|
||||
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
|
||||
@@ -732,6 +823,7 @@ github.com/matoous/godox v0.0.0-20210227103229-6504466cf951/go.mod h1:1BELzlh859
|
||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
@@ -762,21 +854,33 @@ github.com/mbilski/exhaustivestruct v1.1.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aks
|
||||
github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo=
|
||||
github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc=
|
||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||
github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60/go.mod h1:aYbhishWc4Ai3I2U4Gaa2n3kHWSwzme6EsG/46HRQbE=
|
||||
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
|
||||
github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
|
||||
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
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.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
|
||||
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
|
||||
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
|
||||
github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqAS9cNgn6Q=
|
||||
github.com/mdlayher/netlink v1.4.2/go.mod h1:13VaingaArGUTUxFLf/iEovKxXji32JAtF858jZYEug=
|
||||
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/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=
|
||||
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
|
||||
github.com/mdlayher/sdnotify v0.0.0-20210228150836-ea3ec207d697 h1:PBb7ld5cQGfxHF2pKvb/ydtuPwdRaltGI4e0QSCuiNI=
|
||||
github.com/mdlayher/sdnotify v0.0.0-20210228150836-ea3ec207d697/go.mod h1:HtjVsQfsrBm1GDcDTUFn4ZXhftxTwO/hxrvEiRc61U4=
|
||||
github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc=
|
||||
github.com/mdlayher/socket v0.0.0-20211007213009-516dcbdf0267/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
|
||||
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
|
||||
github.com/mdlayher/socket v0.1.1 h1:q3uOGirUPfAV2MUoaC7BavjQ154J7+JOkTWyiV+intI=
|
||||
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
|
||||
github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM=
|
||||
github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY=
|
||||
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0=
|
||||
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg=
|
||||
github.com/mgechev/revive v1.1.2 h1:MiYA/o9M7REjvOF20QN43U8OtXDDHQFKLCtJnxLGLog=
|
||||
@@ -817,20 +921,24 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mohae/deepcopy v0.0.0-20170308212314-bb9b5e7adda9/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/moricho/tparallel v0.2.1 h1:95FytivzT6rYzdJLdtfn6m1bfFJylOJK41+lgv/EHf4=
|
||||
github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k=
|
||||
github.com/mozilla/scribe v0.0.0-20180711195314-fb71baf557c1/go.mod h1:FIczTrinKo8VaLxe6PWTPEXRXDIHz2QAwiaBaP5/4a8=
|
||||
github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||
github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo=
|
||||
github.com/mwitkow/go-proto-validators v0.2.0/go.mod h1:ZfA1hW+UH/2ZHOWvQ3HnQaU0DtnpXu850MZiy+YUgcc=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c=
|
||||
github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=
|
||||
github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=
|
||||
@@ -854,13 +962,17 @@ github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXW
|
||||
github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
@@ -869,7 +981,15 @@ github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v1.0.0-rc90/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20211123151946-c2389c3cb60a/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/orangecms/go-framebuffer v0.0.0-20200613202404-a0700d90c330/go.mod h1:3Myb/UszJY32F2G7yGkUtcW/ejHpjlGfYLim7cv2uKA=
|
||||
github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k=
|
||||
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
@@ -877,6 +997,9 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pborman/getopt v1.1.0 h1:eJ3aFZroQqq0bWmraivjQNt6Dmm5M0h2JcDW38/Azb0=
|
||||
github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk=
|
||||
github.com/pborman/getopt/v2 v2.1.0/go.mod h1:4NtW75ny4eBw9fO1bhtNdYTlZKYX5/tBLtsOpwKIKd0=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||
@@ -890,6 +1013,7 @@ github.com/peterbourgon/ff/v3 v3.1.2 h1:0GNhbRhO9yHA4CC27ymskOsuRpmX0YQxwxM9UPiP
|
||||
github.com/peterbourgon/ff/v3 v3.1.2/go.mod h1:XNJLY8EIl6MjMVjBS4F0+G0LYoAqs0DTa4rmHHukKDE=
|
||||
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d h1:CdDQnGF8Nq9ocOS/xlSptM1N3BbrA6/kmaep5ggwaIA=
|
||||
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw=
|
||||
github.com/pierrec/lz4/v4 v4.1.11/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
@@ -928,8 +1052,10 @@ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB8
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
@@ -952,6 +1078,8 @@ github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:r
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20200805063351-8f842688393c/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU=
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
|
||||
github.com/rck/unit v0.0.3/go.mod h1:jTOnzP4s1OjIP1vdxb4n76b23QPKS4EurYg7sYMr2DM=
|
||||
github.com/rekby/gpt v0.0.0-20200219180433-a930afbc6edc/go.mod h1:scrOqOnnHVKCHENvFw8k9ajCb88uqLQDA4BvuJNJ2ew=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
@@ -972,6 +1100,7 @@ github.com/ryancurrah/gomodguard v1.2.3/go.mod h1:rYbA/4Tg5c54mV1sv4sQTP5WOPBcoL
|
||||
github.com/ryanrolds/sqlclosecheck v0.3.0 h1:AZx+Bixh8zdUBxUA1NxbxVAS78vTPq4rCb8OUZI9xFw=
|
||||
github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/safchain/ethtool v0.0.0-20200218184317-f459e2d13664/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
||||
github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE=
|
||||
github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI=
|
||||
github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc=
|
||||
@@ -1015,6 +1144,7 @@ github.com/sourcegraph/go-diff v0.6.1 h1:hmA1LzxW0n1c3Q4YbrFgg4P99GSnebYa3x8gr0H
|
||||
github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
|
||||
@@ -1025,17 +1155,20 @@ github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
|
||||
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
||||
@@ -1048,6 +1181,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
|
||||
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
@@ -1061,20 +1195,23 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/sylvia7788/contextcheck v1.0.4 h1:MsiVqROAdr0efZc/fOCt0c235qm9XJqHtWwM+2h2B04=
|
||||
github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ=
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/tailscale/certstore v0.0.0-20210528134328-066c94b793d3 h1:fEubocuQkrlcuYeXelhYq/YcKvVVe1Ah7saQEtj98Mo=
|
||||
github.com/tailscale/certstore v0.0.0-20210528134328-066c94b793d3/go.mod h1:2P+hpOwd53e7JMX/L4f3VXkv1G+33ES6IWZSrkIeWNs=
|
||||
github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d h1:K3j02b5j2Iw1xoggN9B2DIEkhWGheqFOeDkdJdBrJI8=
|
||||
github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d/go.mod h1:2P+hpOwd53e7JMX/L4f3VXkv1G+33ES6IWZSrkIeWNs=
|
||||
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502 h1:34icjjmqJ2HPjrSuJYEkdZ+0ItmGQAQ75cRHIiftIyE=
|
||||
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
|
||||
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns=
|
||||
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ=
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20220330002111-62119522bbcf h1:+DSoknr7gaiW2LlViX6+ko8TBdxTLkvOBbIWQtYyMaE=
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20220330002111-62119522bbcf/go.mod h1:95n9fbUCixVSI4QXLEvdKJjnYK2eUlkTx9+QwLPXFKU=
|
||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=
|
||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
|
||||
github.com/tailscale/hujson v0.0.0-20211105212140-3a0adc019d83 h1:f7nwzdAHTUUOJjHZuDvLz9CEAlUM228amCRvwzlPvsA=
|
||||
github.com/tailscale/hujson v0.0.0-20211105212140-3a0adc019d83/go.mod h1:iTDXJsA6A2wNNjurgic2rk+is6uzU4U2NLm4T+edr6M=
|
||||
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk=
|
||||
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
|
||||
github.com/tailscale/ssh v0.3.4-0.20220313013419-be8b7add4057 h1:aN1DLGpS7j6LLQaM33w4Lo6Otvq8Rx60D2ciI/UC1KQ=
|
||||
github.com/tailscale/ssh v0.3.4-0.20220313013419-be8b7add4057/go.mod h1:LC21Rp6xYOAc7NEeMhYN0xTXUB74MZAN60GRPegZLkg=
|
||||
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0=
|
||||
github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8=
|
||||
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
|
||||
@@ -1099,20 +1236,26 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tomarrell/wrapcheck v0.0.0-20200807122107-df9e8bcb914d/go.mod h1:yiFB6fFoV7saXirUGfuK+cPtUh4NX/Hf5y2WC2lehu0=
|
||||
github.com/tomarrell/wrapcheck v0.0.0-20201130113247-1683564d9756 h1:zV5mu0ESwb+WnzqVaW2z1DdbAP0S46UtjY8DHQupQP4=
|
||||
github.com/tomarrell/wrapcheck v0.0.0-20201130113247-1683564d9756/go.mod h1:yiFB6fFoV7saXirUGfuK+cPtUh4NX/Hf5y2WC2lehu0=
|
||||
github.com/tomarrell/wrapcheck/v2 v2.4.0 h1:mU4H9KsqqPZUALOUbVOpjy8qNQbWLoLI9fV68/1tq30=
|
||||
github.com/tomarrell/wrapcheck/v2 v2.4.0/go.mod h1:68bQ/eJg55BROaRTbMjC7vuhL2OgfoG8bLp9ZyoBfyY=
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
|
||||
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa h1:RC4maTWLKKwb7p1cnoygsbKIgNlJqSYBeAFON3Ar8As=
|
||||
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.4.0 h1:1t0f8Uiaq+fqKteUR4N9Umr6E99R+lDnLnq7PwX2PPE=
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.4.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
|
||||
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
|
||||
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
|
||||
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
|
||||
github.com/twitchtv/twirp v5.8.0+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A=
|
||||
github.com/u-root/iscsinl v0.1.1-0.20210528121423-84c32645822a/go.mod h1:RWIgJWqm9/0gjBZ0Hl8iR6MVGzZ+yAda2uqqLmetE2I=
|
||||
github.com/u-root/u-root v0.8.0 h1:jqP7uPC2+0eRszYTrmdZ6UDyO1Dbuy0rpMo+BnPZ9cY=
|
||||
github.com/u-root/u-root v0.8.0/go.mod h1:But1FHzS4Ua4ywx6kZOaRzZTucUKIDKOPOLEKOckQ68=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||
github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7 h1:XMAtQHwKjWHIRwg+8Nj/rzUomQY1q6cM3ncA0wP8GU4=
|
||||
github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
@@ -1120,6 +1263,7 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
|
||||
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA=
|
||||
@@ -1128,6 +1272,7 @@ github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFO
|
||||
github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=
|
||||
github.com/uudashr/gocognit v1.0.5 h1:rrSex7oHr3/pPLQ0xoWq108XMU8s678FJcQ+aSfOHa4=
|
||||
github.com/uudashr/gocognit v1.0.5/go.mod h1:wgYz0mitoKOTysqxTDMOUXg+Jb5SvtihkfmugIZYpEA=
|
||||
@@ -1139,11 +1284,16 @@ github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/V
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE=
|
||||
github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 h1:8mhqcHPqTMhSPoslhGYihEgSfc77+7La1P6kiB6+9So=
|
||||
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vtolstov/go-ioctl v0.0.0-20151206205506-6be9cced4810/go.mod h1:dF0BBJ2YrV1+2eAIyEI+KeSidgA6HqoIP1u5XTlMq/o=
|
||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
||||
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
|
||||
@@ -1165,9 +1315,11 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/ziutek/telnet v0.0.0-20180329124119-c3b780dc415b/go.mod h1:IZpXDfkJ6tWD3PhBK5YzgQT+xJWh7OsdwiG8hA2MkO4=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c/go.mod h1:xCI7ZzBfRuGgBXyXO6yfWfDmlWd35khcWpUa4L0xI/k=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
@@ -1206,6 +1358,7 @@ golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -1213,8 +1366,10 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
@@ -1227,8 +1382,10 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 h1:S25/rfnfsMVgORT4/J61MJ7rdyseOZOyvLIrZEZ7s6s=
|
||||
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000 h1:SL+8VVnkqyshUSz5iNnXtrBQzvFF2SkROm6t5RczFAE=
|
||||
golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -1240,9 +1397,10 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 h1:FR+oGxGfbQu1d+jglI3rCkjAjUnhRSZcUxr+DqlDLNo=
|
||||
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220328175248-053ad81199eb h1:fP6C8Xutcp5AlakmT/SkQot0pMicROAsEX7OfNPuG10=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220328175248-053ad81199eb/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e h1:qyrTQ++p1afMkO4DPEeLGq/3oTsdlvdH4vqZUBWzUKM=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -1272,9 +1430,11 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1291,6 +1451,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -1300,6 +1461,7 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -1323,6 +1485,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
@@ -1336,10 +1500,14 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 h1:EN5+DfgmRMvRUrMGERW2gQl3Vc+Z7ZMnI/xdEpPSf0c=
|
||||
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1357,6 +1525,7 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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=
|
||||
@@ -1372,6 +1541,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 h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1381,6 +1551,7 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1393,8 +1564,10 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -1403,13 +1576,17 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200121082415-34d275377bf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -1431,18 +1608,24 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201109165425-215b40eba54c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -1466,24 +1649,33 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211002104244-808efd93c36d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f h1:8w7RhxzTVgUzw/AH/9mUV5q0vMgy40SQRursCcfmkCw=
|
||||
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 h1:A9i04dxx7Cribqbs8jf3FQLogkL/CV2YN7hj9KWJCkc=
|
||||
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1504,6 +1696,7 @@ golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxb
|
||||
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=
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -1521,6 +1714,7 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@@ -1612,8 +1806,10 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.8-0.20211102182255-bb4add04ddef/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.1.11-0.20220413170336-afc6aad76eb1 h1:Z3vE1sGlC7qiyFJkkDcZms8Y3+yV8+W7HmDSmuf71tM=
|
||||
golang.org/x/tools v0.1.11-0.20220413170336-afc6aad76eb1/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1622,6 +1818,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210905140043-2ef39d47540c/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20211116201604-de7c702ace45 h1:mEVhdMPTuebD9IUXOUB5Q2sjZpcmzkahHWd6DrGpLHA=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20211116201604-de7c702ace45/go.mod h1:evxZIqfCetExY5piKXGAxJYwvXWkps9zTCkWpkoGFxw=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220317000134-95b48cdb3961 h1:oIXcKhP1Ge6cRqdpQuldl0hf4mjIsNaXojabghlHuTs=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220317000134-95b48cdb3961/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
|
||||
golang.zx2c4.com/wireguard/windows v0.4.10 h1:HmjzJnb+G4NCdX+sfjsQlsxGPuYaThxRbZUZFLyR0/s=
|
||||
@@ -1679,6 +1877,7 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
@@ -1716,6 +1915,7 @@ google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxH
|
||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
|
||||
@@ -1724,6 +1924,7 @@ google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEc
|
||||
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
@@ -1750,6 +1951,7 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ
|
||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.42.0-dev.0.20211020220737-f00baa6c3c84/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
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=
|
||||
@@ -1777,6 +1979,7 @@ gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
@@ -1803,8 +2006,9 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20220407223209-21871174d445 h1:pLNQCtMzh4O6rdhoUeWHuutt4yMft+B9Cgw/bezWchE=
|
||||
gvisor.dev/gvisor v0.0.0-20220407223209-21871174d445/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
gvisor.dev/gvisor v0.0.0-20220126021142-d8aa030b2591 h1:acuXPUADpJMtawdLCUje9xKlQN/8utegCB/Hr/ZgEuY=
|
||||
gvisor.dev/gvisor v0.0.0-20220126021142-d8aa030b2591/go.mod h1:vmN0Pug/s8TJmpnt30DvrEfZ5vDl52psGLU04tFuK2U=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@@ -1814,8 +2018,10 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY=
|
||||
honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
|
||||
honnef.co/go/tools v0.4.0-0.dev.0.20220404092545-59d7a2877f83 h1:lZ9GIYaU+o5+X6ST702I/Ntyq9Y2oIMZ42rBQpem64A=
|
||||
honnef.co/go/tools v0.4.0-0.dev.0.20220404092545-59d7a2877f83/go.mod h1:vlRD9XErLMGT+mDuofSr0mMMquscM/1nQqtRSsh6m70=
|
||||
honnef.co/go/tools v0.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk=
|
||||
honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
|
||||
honnef.co/go/tools v0.3.0-0.dev.0.20220306074811-23e1086441d2 h1:utiSabORbG/JeX7MlmKMdmsjwom2+v8zmdb6SoBe4UY=
|
||||
honnef.co/go/tools v0.3.0-0.dev.0.20220306074811-23e1086441d2/go.mod h1:dZI0HmIvwDMW8owtLBJxTHoeX48yuF5p5pDy3y73jGU=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
@@ -1826,6 +2032,16 @@ inet.af/peercred v0.0.0-20210906144145-0893ea02156a h1:qdkS8Q5/i10xU2ArJMKYhVa1D
|
||||
inet.af/peercred v0.0.0-20210906144145-0893ea02156a/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU=
|
||||
inet.af/wf v0.0.0-20211204062712-86aaea0a7310 h1:0jKHTf+W75kYRyg5bto1UT+r18QmAz2u/5pAs/fx4zo=
|
||||
inet.af/wf v0.0.0-20211204062712-86aaea0a7310/go.mod h1:ViGMZRA6+RA318D7GCncrjv5gHUrPYrNDejjU12tikA=
|
||||
k8s.io/api v0.16.13/go.mod h1:QWu8UWSTiuQZMMeYjwLs6ILu5O74qKSJ0c+4vrchDxs=
|
||||
k8s.io/apimachinery v0.16.13/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ=
|
||||
k8s.io/apimachinery v0.16.14-rc.0/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ=
|
||||
k8s.io/client-go v0.16.13/go.mod h1:UKvVT4cajC2iN7DCjLgT0KVY/cbY6DGdUCyRiIfws5M=
|
||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
k8s.io/kube-openapi v0.0.0-20200410163147-594e756bea31/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||
mvdan.cc/gofumpt v0.0.0-20200802201014-ab5a8192947d/go.mod h1:bzrjFmaD6+xqohD3KYP0H2FEuxknnBmyyOxdhLdaIws=
|
||||
mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475/go.mod h1:E4LOcu9JQEtnYXtB1Y51drqh2Qr2Ngk9J3YrRCwcbd0=
|
||||
mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48=
|
||||
@@ -1841,10 +2057,13 @@ mvdan.cc/unparam v0.0.0-20211002134041-24922b6997ca h1:xzXXnoG5a3NUnKAcVMpE2cs3+
|
||||
mvdan.cc/unparam v0.0.0-20211002134041-24922b6997ca/go.mod h1:Mb96j26qXgU/+SOj6MSgC36X30UgAlRYaxckYuYyEmo=
|
||||
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
pack.ag/tftp v1.0.1-0.20181129014014-07909dfbde3c/go.mod h1:N1Pyo5YG+K90XHoR2vfLPhpRuE8ziqbgMn/r/SghZas=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
software.sslmate.com/src/go-pkcs12 v0.0.0-20180114231543-2291e8f0f237/go.mod h1:/xvNRWUqm0+/ZMiF4EX00vrSCMsE4/NHb+Pt3freEeQ=
|
||||
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 h1:SqYE5+A2qvRhErbsXFfUEUmpWEKxxRSMgGLkvRAFOV4=
|
||||
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78/go.mod h1:B7Wf0Ya4DHF9Yw+qfZuJijQYkWicqDa+79Ytmmq3Kjg=
|
||||
src.elv.sh v0.16.3/go.mod h1:WxJAMoN8uQcg1ZwRvtjmbYAo6uKeJ8F7b25etVZ743w=
|
||||
|
||||
@@ -1 +1 @@
|
||||
5ce3ec4d89c72f2a2b6f6f5089c950d7a6a33530
|
||||
68c97fb924bbcacd951dc01b292c679aaeff5802
|
||||
|
||||
@@ -107,9 +107,8 @@ func SetDeviceModel(model string) { deviceModelAtomic.Store(model) }
|
||||
func SetOSVersion(v string) { osVersionAtomic.Store(v) }
|
||||
|
||||
// SetPackage sets the packaging type for the app.
|
||||
//
|
||||
// As of 2022-03-25, this is used by Android ("nogoogle" for the
|
||||
// F-Droid build) and tsnet (set to "tsnet").
|
||||
// This is currently (2021-10-05) only used by Android,
|
||||
// set to "nogoogle" for the F-Droid build.
|
||||
func SetPackage(v string) { packagingType.Store(v) }
|
||||
|
||||
func deviceModel() string {
|
||||
|
||||
@@ -5,13 +5,16 @@
|
||||
package ipnlocal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -33,7 +36,6 @@ import (
|
||||
"tailscale.com/ipn/policy"
|
||||
"tailscale.com/net/dns"
|
||||
"tailscale.com/net/interfaces"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/paths"
|
||||
@@ -64,7 +66,6 @@ import (
|
||||
)
|
||||
|
||||
var controlDebugFlags = getControlDebugFlags()
|
||||
var canSSH = envknob.CanSSHD()
|
||||
|
||||
func getControlDebugFlags() []string {
|
||||
if e := envknob.String("TS_DEBUG_CONTROL_FLAGS"); e != "" {
|
||||
@@ -106,8 +107,7 @@ type LocalBackend struct {
|
||||
|
||||
filterHash deephash.Sum
|
||||
|
||||
filterAtomic atomic.Value // of *filter.Filter
|
||||
containsViaIPFuncAtomic atomic.Value // of func(netaddr.IP) bool
|
||||
filterAtomic atomic.Value // of *filter.Filter
|
||||
|
||||
// The mutex protects the following elements.
|
||||
mu sync.Mutex
|
||||
@@ -140,7 +140,6 @@ type LocalBackend struct {
|
||||
peerAPIListeners []*peerAPIListener
|
||||
loginFlags controlclient.LoginFlags
|
||||
incomingFiles map[*incomingFile]bool
|
||||
lastStatusTime time.Time // status.AsOf value of the last processed status update
|
||||
// directFileRoot, if non-empty, means to write received files
|
||||
// directly to this directory, without staging them in an
|
||||
// intermediate buffered directory for "pick-up" later. If
|
||||
@@ -473,7 +472,6 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
|
||||
ShareeNode: p.Hostinfo.ShareeNode(),
|
||||
ExitNode: p.StableID != "" && p.StableID == b.prefs.ExitNodeID,
|
||||
ExitNodeOption: exitNodeOption,
|
||||
SSH_HostKeys: p.Hostinfo.SSH_HostKeys().AsSlice(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -707,13 +705,6 @@ func (b *LocalBackend) setWgengineStatus(s *wgengine.Status, err error) {
|
||||
}
|
||||
|
||||
b.mu.Lock()
|
||||
if s.AsOf.Before(b.lastStatusTime) {
|
||||
// Don't process a status update that is older than the one we have
|
||||
// already processed. (corp#2579)
|
||||
b.mu.Unlock()
|
||||
return
|
||||
}
|
||||
b.lastStatusTime = s.AsOf
|
||||
es := b.parseWgStatusLocked(s)
|
||||
cc := b.cc
|
||||
b.engineStatus = es
|
||||
@@ -983,7 +974,6 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
DebugFlags: debugFlags,
|
||||
LinkMonitor: b.e.GetLinkMonitor(),
|
||||
Pinger: b.e,
|
||||
PopBrowserURL: b.tellClientToBrowseToURL,
|
||||
|
||||
// Don't warn about broken Linux IP forwarding when
|
||||
// netstack is being used.
|
||||
@@ -1381,18 +1371,12 @@ func (b *LocalBackend) popBrowserAuthNow() {
|
||||
|
||||
b.blockEngineUpdates(true)
|
||||
b.stopEngineAndWait()
|
||||
b.tellClientToBrowseToURL(url)
|
||||
b.send(ipn.Notify{BrowseToURL: &url})
|
||||
if b.State() == ipn.Running {
|
||||
b.enterState(ipn.Starting)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *LocalBackend) tellClientToBrowseToURL(url string) {
|
||||
if url != "" {
|
||||
b.send(ipn.Notify{BrowseToURL: &url})
|
||||
}
|
||||
}
|
||||
|
||||
// For testing lazy machine key generation.
|
||||
var panicOnMachineKeyGeneration = envknob.Bool("TS_DEBUG_PANIC_MACHINE_KEY")
|
||||
|
||||
@@ -1574,23 +1558,11 @@ func (b *LocalBackend) loadStateLocked(key ipn.StateKey, prefs *ipn.Prefs) (err
|
||||
|
||||
b.logf("using backend prefs for %q: %s", key, b.prefs.Pretty())
|
||||
|
||||
b.setAtomicValuesFromPrefs(b.prefs)
|
||||
b.sshAtomicBool.Set(b.prefs != nil && b.prefs.RunSSH)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setAtomicValuesFromPrefs populates sshAtomicBool and containsViaIPFuncAtomic
|
||||
// from the prefs p, which may be nil.
|
||||
func (b *LocalBackend) setAtomicValuesFromPrefs(p *ipn.Prefs) {
|
||||
b.sshAtomicBool.Set(p != nil && p.RunSSH && canSSH)
|
||||
|
||||
if p == nil {
|
||||
b.containsViaIPFuncAtomic.Store(tsaddr.NewContainsIPFunc(nil))
|
||||
} else {
|
||||
b.containsViaIPFuncAtomic.Store(tsaddr.NewContainsIPFunc(tsaddr.FilterPrefixesCopy(p.AdvertiseRoutes, tsaddr.IsViaPrefix)))
|
||||
}
|
||||
}
|
||||
|
||||
// State returns the backend state machine's current state.
|
||||
func (b *LocalBackend) State() ipn.State {
|
||||
b.mu.Lock()
|
||||
@@ -1725,11 +1697,6 @@ func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (*ipn.Prefs, error) {
|
||||
p0 := b.prefs.Clone()
|
||||
p1 := b.prefs.Clone()
|
||||
p1.ApplyEdits(mp)
|
||||
if p1.RunSSH && !canSSH {
|
||||
b.mu.Unlock()
|
||||
b.logf("EditPrefs requests SSH, but disabled by envknob; returning error")
|
||||
return nil, errors.New("Tailscale SSH server administratively disabled.")
|
||||
}
|
||||
if p1.Equals(p0) {
|
||||
b.mu.Unlock()
|
||||
return p1, nil
|
||||
@@ -1759,7 +1726,7 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) {
|
||||
netMap := b.netMap
|
||||
stateKey := b.stateKey
|
||||
|
||||
b.setAtomicValuesFromPrefs(newp)
|
||||
b.sshAtomicBool.Set(newp.RunSSH)
|
||||
|
||||
oldp := b.prefs
|
||||
newp.Persist = oldp.Persist // caller isn't allowed to override this
|
||||
@@ -1957,7 +1924,6 @@ func (b *LocalBackend) authReconfig() {
|
||||
nm := b.netMap
|
||||
hasPAC := b.prevIfState.HasPAC()
|
||||
disableSubnetsIfPAC := nm != nil && nm.Debug != nil && nm.Debug.DisableSubnetsIfPAC.EqualBool(true)
|
||||
oneCGNATRoute := nm != nil && nm.Debug != nil && nm.Debug.OneCGNATRoute.EqualBool(true)
|
||||
b.mu.Unlock()
|
||||
|
||||
if blocked {
|
||||
@@ -2002,7 +1968,7 @@ func (b *LocalBackend) authReconfig() {
|
||||
return
|
||||
}
|
||||
|
||||
rcfg := b.routerConfig(cfg, prefs, oneCGNATRoute)
|
||||
rcfg := b.routerConfig(cfg, prefs)
|
||||
dcfg := dnsConfigForNetmap(nm, prefs, b.logf, version.OS())
|
||||
|
||||
err = b.e.Reconfig(cfg, rcfg, dcfg, nm.Debug)
|
||||
@@ -2398,32 +2364,17 @@ func peerRoutes(peers []wgcfg.Peer, cgnatThreshold int) (routes []netaddr.IPPref
|
||||
} else {
|
||||
routes = append(routes, cgNATIPs...)
|
||||
}
|
||||
|
||||
sort.Slice(routes, func(i, j int) bool {
|
||||
return ipPrefixLess(routes[i], routes[j])
|
||||
})
|
||||
return routes
|
||||
}
|
||||
|
||||
func ipPrefixLess(ri, rj netaddr.IPPrefix) bool {
|
||||
if ri.IP() == rj.IP() {
|
||||
return ri.Bits() < rj.Bits()
|
||||
}
|
||||
return ri.IP().Less(rj.IP())
|
||||
}
|
||||
|
||||
// routerConfig produces a router.Config from a wireguard config and IPN prefs.
|
||||
func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs, oneCGNATRoute bool) *router.Config {
|
||||
singleRouteThreshold := 10_000
|
||||
if oneCGNATRoute {
|
||||
singleRouteThreshold = 1
|
||||
}
|
||||
func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router.Config {
|
||||
rs := &router.Config{
|
||||
LocalAddrs: unmapIPPrefixes(cfg.Addresses),
|
||||
SubnetRoutes: unmapIPPrefixes(prefs.AdvertiseRoutes),
|
||||
SNATSubnetRoutes: !prefs.NoSNAT,
|
||||
NetfilterMode: prefs.NetfilterMode,
|
||||
Routes: peerRoutes(cfg.Peers, singleRouteThreshold),
|
||||
Routes: peerRoutes(cfg.Peers, 10_000),
|
||||
}
|
||||
|
||||
if distro.Get() == distro.Synology {
|
||||
@@ -2505,7 +2456,7 @@ func (b *LocalBackend) applyPrefsToHostinfo(hi *tailcfg.Hostinfo, prefs *ipn.Pre
|
||||
hi.ShieldsUp = prefs.ShieldsUp
|
||||
|
||||
var sshHostKeys []string
|
||||
if prefs.RunSSH && canSSH {
|
||||
if prefs.RunSSH {
|
||||
// TODO(bradfitz): this is called with b.mu held. Not ideal.
|
||||
// If the filesystem gets wedged or something we could block for
|
||||
// a long time. But probably fine.
|
||||
@@ -2719,20 +2670,10 @@ func (b *LocalBackend) ResetForClientDisconnect() {
|
||||
b.authURL = ""
|
||||
b.authURLSticky = ""
|
||||
b.activeLogin = ""
|
||||
b.setAtomicValuesFromPrefs(nil)
|
||||
b.sshAtomicBool.Set(false)
|
||||
}
|
||||
|
||||
func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Get() && canSSH }
|
||||
|
||||
// ShouldHandleViaIP reports whether whether ip is an IPv6 address in the
|
||||
// Tailscale ULA's v6 "via" range embedding an IPv4 address to be forwarded to
|
||||
// by Tailscale.
|
||||
func (b *LocalBackend) ShouldHandleViaIP(ip netaddr.IP) bool {
|
||||
if f, ok := b.containsViaIPFuncAtomic.Load().(func(netaddr.IP) bool); ok {
|
||||
return f(ip)
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Get() }
|
||||
|
||||
// Logout tells the controlclient that we want to log out, and
|
||||
// transitions the local engine to the logged-out state without
|
||||
@@ -3076,17 +3017,105 @@ func nodeIP(n *tailcfg.Node, pred func(netaddr.IP) bool) netaddr.IP {
|
||||
return netaddr.IP{}
|
||||
}
|
||||
|
||||
func isBSD(s string) bool {
|
||||
return s == "dragonfly" || s == "freebsd" || s == "netbsd" || s == "openbsd"
|
||||
}
|
||||
|
||||
func (b *LocalBackend) CheckIPForwarding() error {
|
||||
if wgengine.IsNetstackRouter(b.e) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: let the caller pass in the ranges.
|
||||
warn, err := netutil.CheckIPForwarding(tsaddr.ExitRoutes(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
switch {
|
||||
case isBSD(runtime.GOOS):
|
||||
return fmt.Errorf("Subnet routing and exit nodes only work with additional manual configuration on %v, and is not currently officially supported.", runtime.GOOS)
|
||||
case runtime.GOOS == "linux":
|
||||
return checkIPForwardingLinux()
|
||||
default:
|
||||
// TODO: subnet routing and exit nodes probably don't work
|
||||
// correctly on non-linux, non-netstack OSes either. Warn
|
||||
// instead of being silent?
|
||||
return nil
|
||||
}
|
||||
return warn
|
||||
}
|
||||
|
||||
// checkIPForwardingLinux checks if IP forwarding is enabled correctly
|
||||
// for subnet routing and exit node functionality. Returns an error
|
||||
// describing configuration issues if the configuration is not
|
||||
// definitely good.
|
||||
func checkIPForwardingLinux() error {
|
||||
const kbLink = "\nSee https://tailscale.com/kb/1104/enable-ip-forwarding/"
|
||||
|
||||
disabled, err := disabledSysctls("net.ipv4.ip_forward", "net.ipv6.conf.all.forwarding")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Couldn't check system's IP forwarding configuration, subnet routing/exit nodes may not work: %w%s", err, kbLink)
|
||||
}
|
||||
|
||||
if len(disabled) == 0 {
|
||||
// IP forwarding is enabled systemwide, all is well.
|
||||
return nil
|
||||
}
|
||||
|
||||
// IP forwarding isn't enabled globally, but it might be enabled
|
||||
// on a per-interface basis. Check if it's on for all interfaces,
|
||||
// and warn appropriately if it's not.
|
||||
ifaces, err := interfaces.GetList()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Couldn't enumerate network interfaces, subnet routing/exit nodes may not work: %w%s", err, kbLink)
|
||||
}
|
||||
|
||||
var (
|
||||
warnings []string
|
||||
anyEnabled bool
|
||||
)
|
||||
for _, iface := range ifaces {
|
||||
if iface.Name == "lo" {
|
||||
continue
|
||||
}
|
||||
disabled, err = disabledSysctls(fmt.Sprintf("net.ipv4.conf.%s.forwarding", iface.Name), fmt.Sprintf("net.ipv6.conf.%s.forwarding", iface.Name))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Couldn't check system's IP forwarding configuration, subnet routing/exit nodes may not work: %w%s", err, kbLink)
|
||||
}
|
||||
if len(disabled) > 0 {
|
||||
warnings = append(warnings, fmt.Sprintf("Traffic received on %s won't be forwarded (%s disabled)", iface.Name, strings.Join(disabled, ", ")))
|
||||
} else {
|
||||
anyEnabled = true
|
||||
}
|
||||
}
|
||||
if !anyEnabled {
|
||||
// IP forwarding is compeltely disabled, just say that rather
|
||||
// than enumerate all the interfaces on the system.
|
||||
return fmt.Errorf("IP forwarding is disabled, subnet routing/exit nodes will not work.%s", kbLink)
|
||||
}
|
||||
if len(warnings) > 0 {
|
||||
// If partially enabled, enumerate the bits that won't work.
|
||||
return fmt.Errorf("%s\nSubnet routes and exit nodes may not work correctly.%s", strings.Join(warnings, "\n"), kbLink)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// disabledSysctls checks if the given sysctl keys are off, according
|
||||
// to strconv.ParseBool. Returns a list of keys that are disabled, or
|
||||
// err if something went wrong which prevented the lookups from
|
||||
// completing.
|
||||
func disabledSysctls(sysctls ...string) (disabled []string, err error) {
|
||||
for _, k := range sysctls {
|
||||
// TODO: on linux, we can get at these values via /proc/sys,
|
||||
// rather than fork subcommands that may not be installed.
|
||||
bs, err := exec.Command("sysctl", "-n", k).Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't check %s (%v)", k, err)
|
||||
}
|
||||
on, err := strconv.ParseBool(string(bytes.TrimSpace(bs)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't parse %s (%v)", k, err)
|
||||
}
|
||||
if !on {
|
||||
disabled = append(disabled, k)
|
||||
}
|
||||
}
|
||||
return disabled, nil
|
||||
}
|
||||
|
||||
// DERPMap returns the current DERPMap in use, or nil if not connected.
|
||||
@@ -3225,3 +3254,38 @@ func (b *LocalBackend) DoNoiseRequest(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
return cc.DoNoiseRequest(req)
|
||||
}
|
||||
|
||||
// ProxyAPIRequestOverNoise sends Tailscale API request r over the
|
||||
// Noise channel, authenticated as the current node+machine key, to
|
||||
// the control plane and copies its response back to w.
|
||||
func (b *LocalBackend) ProxyAPIRequestOverNoise(w http.ResponseWriter, r *http.Request) {
|
||||
var nodePub key.NodePublic
|
||||
b.mu.Lock()
|
||||
if nm := b.netMap; nm != nil {
|
||||
nodePub = nm.NodeKey
|
||||
}
|
||||
b.mu.Unlock()
|
||||
if nodePub.IsZero() {
|
||||
http.Error(w, "no node public key", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
outR := r.Clone(r.Context())
|
||||
outR.RequestURI = ""
|
||||
outR.URL.Scheme = "https"
|
||||
outR.URL.Host = "unused"
|
||||
|
||||
outR.SetBasicAuth(url.QueryEscape(nodePub.String()), "")
|
||||
res, err := b.DoNoiseRequest(outR)
|
||||
if err != nil {
|
||||
http.Error(w, "failed to make backend noise request: "+err.Error(), http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
for k, vv := range res.Header {
|
||||
for _, v := range vv {
|
||||
w.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(res.StatusCode)
|
||||
io.Copy(w, res.Body)
|
||||
}
|
||||
|
||||
@@ -302,31 +302,8 @@ func TestPeerRoutes(t *testing.T) {
|
||||
},
|
||||
},
|
||||
want: []netaddr.IPPrefix{
|
||||
pp("100.64.0.0/10"),
|
||||
pp("fd7a:115c:a1e0::/48"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "output-should-be-sorted",
|
||||
peers: []wgcfg.Peer{
|
||||
{
|
||||
AllowedIPs: []netaddr.IPPrefix{
|
||||
pp("100.64.0.2/32"),
|
||||
pp("10.0.0.0/16"),
|
||||
},
|
||||
},
|
||||
{
|
||||
AllowedIPs: []netaddr.IPPrefix{
|
||||
pp("100.64.0.1/32"),
|
||||
pp("10.0.0.0/8"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []netaddr.IPPrefix{
|
||||
pp("10.0.0.0/8"),
|
||||
pp("10.0.0.0/16"),
|
||||
pp("100.64.0.1/32"),
|
||||
pp("100.64.0.2/32"),
|
||||
pp("100.64.0.0/10"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/tailscale/golang-x-crypto/ssh"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"tailscale.com/envknob"
|
||||
)
|
||||
|
||||
|
||||
@@ -62,7 +62,6 @@ func (nt *notifyThrottler) put(n ipn.Notify) {
|
||||
// drain pulls the notifications out of the queue, asserting that there are
|
||||
// exactly count notifications that have been put so far.
|
||||
func (nt *notifyThrottler) drain(count int) []ipn.Notify {
|
||||
nt.t.Helper()
|
||||
nt.mu.Lock()
|
||||
ch := nt.ch
|
||||
nt.mu.Unlock()
|
||||
@@ -924,7 +923,7 @@ func TestStateMachine(t *testing.T) {
|
||||
}
|
||||
notifies.expect(1)
|
||||
// Fake a DERP connection.
|
||||
b.setWgengineStatus(&wgengine.Status{DERPs: 1, AsOf: time.Now()}, nil)
|
||||
b.setWgengineStatus(&wgengine.Status{DERPs: 1}, nil)
|
||||
{
|
||||
nn := notifies.drain(1)
|
||||
cc.assertCalls("unpause")
|
||||
@@ -1017,7 +1016,7 @@ func TestWGEngineStatusRace(t *testing.T) {
|
||||
if i == 0 {
|
||||
n = 1
|
||||
}
|
||||
b.setWgengineStatus(&wgengine.Status{AsOf: time.Now(), DERPs: n}, nil)
|
||||
b.setWgengineStatus(&wgengine.Status{DERPs: n}, nil)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
@@ -1049,6 +1049,10 @@ func (s *Server) localhostHandler(ci connIdentity) http.Handler {
|
||||
lah.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(r.URL.Path, "/api/") {
|
||||
s.b.ProxyAPIRequestOverNoise(w, r)
|
||||
return
|
||||
}
|
||||
if ci.NotWindows {
|
||||
io.WriteString(w, "<html><title>Tailscale</title><body><h1>Tailscale</h1>This is the local Tailscale daemon.")
|
||||
return
|
||||
|
||||
@@ -145,9 +145,6 @@ type PeerStatus struct {
|
||||
PeerAPIURL []string
|
||||
Capabilities []string `json:",omitempty"`
|
||||
|
||||
// SSH_HostKeys are the node's SSH host keys, if known.
|
||||
SSH_HostKeys []string `json:"sshHostKeys,omitempty"`
|
||||
|
||||
// ShareeNode indicates this node exists in the netmap because
|
||||
// it's owned by a shared-to user and that node might connect
|
||||
// to us. These nodes should be hidden by "tailscale status"
|
||||
@@ -287,9 +284,6 @@ func (sb *StatusBuilder) AddPeer(peer key.NodePublic, st *PeerStatus) {
|
||||
if v := st.OS; v != "" {
|
||||
e.OS = st.OS
|
||||
}
|
||||
if v := st.SSH_HostKeys; v != nil {
|
||||
e.SSH_HostKeys = v
|
||||
}
|
||||
if v := st.Addrs; v != nil {
|
||||
e.Addrs = v
|
||||
}
|
||||
@@ -353,7 +347,6 @@ func (st *Status) WriteHTML(w io.Writer) {
|
||||
f(`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Tailscale State</title>
|
||||
<style>
|
||||
body { font-family: monospace; }
|
||||
|
||||
@@ -6,14 +6,12 @@
|
||||
package localapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
@@ -28,7 +26,6 @@ import (
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/clientmetric"
|
||||
@@ -127,10 +124,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.serveDebug(w, r)
|
||||
case "/localapi/v0/set-expiry-sooner":
|
||||
h.serveSetExpirySooner(w, r)
|
||||
case "/localapi/v0/dial":
|
||||
h.serveDial(w, r)
|
||||
case "/localapi/v0/id-token":
|
||||
h.serveIDToken(w, r)
|
||||
case "/":
|
||||
io.WriteString(w, "tailscaled\n")
|
||||
default:
|
||||
@@ -138,50 +131,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// serveIDToken handles requests to get an OIDC ID token.
|
||||
func (h *Handler) serveIDToken(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "id-token access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
nm := h.b.NetMap()
|
||||
if nm == nil {
|
||||
http.Error(w, "no netmap", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
aud := strings.TrimSpace(r.FormValue("aud"))
|
||||
if len(aud) == 0 {
|
||||
http.Error(w, "no audience requested", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
req := &tailcfg.TokenRequest{
|
||||
CapVersion: tailcfg.CurrentCapabilityVersion,
|
||||
Audience: aud,
|
||||
NodeKey: nm.NodeKey,
|
||||
}
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
httpReq, err := http.NewRequest("POST", "https://unused/machine/id-token", bytes.NewReader(b))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
resp, err := h.b.DoNoiseRequest(httpReq)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
if _, err := io.Copy(w, resp.Body); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) serveBugReport(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitRead {
|
||||
http.Error(w, "bugreport access denied", http.StatusForbidden)
|
||||
@@ -593,63 +542,6 @@ func (h *Handler) serveSetExpirySooner(w http.ResponseWriter, r *http.Request) {
|
||||
io.WriteString(w, "done\n")
|
||||
}
|
||||
|
||||
func (h *Handler) serveDial(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, "POST required", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
const upgradeProto = "ts-dial"
|
||||
if !strings.Contains(r.Header.Get("Connection"), "upgrade") ||
|
||||
r.Header.Get("Upgrade") != upgradeProto {
|
||||
http.Error(w, "bad ts-dial upgrade", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
hostStr, portStr := r.Header.Get("Dial-Host"), r.Header.Get("Dial-Port")
|
||||
if hostStr == "" || portStr == "" {
|
||||
http.Error(w, "missing Dial-Host or Dial-Port header", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
hijacker, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
http.Error(w, "make request over HTTP/1", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
addr := net.JoinHostPort(hostStr, portStr)
|
||||
outConn, err := h.b.Dialer().UserDial(r.Context(), "tcp", addr)
|
||||
if err != nil {
|
||||
http.Error(w, "dial failure: "+err.Error(), http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
defer outConn.Close()
|
||||
|
||||
w.Header().Set("Upgrade", upgradeProto)
|
||||
w.Header().Set("Connection", "upgrade")
|
||||
w.WriteHeader(http.StatusSwitchingProtocols)
|
||||
|
||||
reqConn, brw, err := hijacker.Hijack()
|
||||
if err != nil {
|
||||
h.logf("localapi dial Hijack error: %v", err)
|
||||
return
|
||||
}
|
||||
defer reqConn.Close()
|
||||
if err := brw.Flush(); err != nil {
|
||||
return
|
||||
}
|
||||
reqConn = netutil.NewDrainBufConn(reqConn, brw.Reader)
|
||||
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := io.Copy(reqConn, outConn)
|
||||
errc <- err
|
||||
}()
|
||||
go func() {
|
||||
_, err := io.Copy(outConn, reqConn)
|
||||
errc <- err
|
||||
}()
|
||||
<-errc
|
||||
}
|
||||
|
||||
func defBool(a string, def bool) bool {
|
||||
if a == "" {
|
||||
return def
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"tailscale.com/net/dns/resolver"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/ipproto"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
@@ -205,12 +204,12 @@ func toIPPorts(ips []netaddr.IP) (ret []netaddr.IPPort) {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (m *Manager) EnqueuePacket(bs []byte, proto ipproto.Proto, from, to netaddr.IPPort) error {
|
||||
return m.resolver.EnqueuePacket(bs, proto, from, to)
|
||||
func (m *Manager) EnqueueRequest(bs []byte, from netaddr.IPPort) error {
|
||||
return m.resolver.EnqueueRequest(bs, from)
|
||||
}
|
||||
|
||||
func (m *Manager) NextPacket() ([]byte, error) {
|
||||
return m.resolver.NextPacket()
|
||||
func (m *Manager) NextResponse() ([]byte, netaddr.IPPort, error) {
|
||||
return m.resolver.NextResponse()
|
||||
}
|
||||
|
||||
func (m *Manager) Down() error {
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// Package publicdns contains mapping and helpers for working with
|
||||
// public DNS providers.
|
||||
package publicdns
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
var knownDoH = map[netaddr.IP]string{} // 8.8.8.8 => "https://..."
|
||||
var dohIPsOfBase = map[string][]netaddr.IP{}
|
||||
var populateOnce sync.Once
|
||||
|
||||
// KnownDoH returns a map of well-known public DNS IPs to their DoH URL.
|
||||
// The returned map should not be modified.
|
||||
func KnownDoH() map[netaddr.IP]string {
|
||||
populateOnce.Do(populate)
|
||||
return knownDoH
|
||||
}
|
||||
|
||||
// DoHIPsOfBase returns a map of DNS server IP addresses keyed
|
||||
// by their DoH URL. It is the inverse of KnownDoH.
|
||||
func DoHIPsOfBase() map[string][]netaddr.IP {
|
||||
populateOnce.Do(populate)
|
||||
return dohIPsOfBase
|
||||
}
|
||||
|
||||
// DoHV6 returns the first IPv6 DNS address from a given public DNS provider
|
||||
// if found, along with a boolean indicating success.
|
||||
func DoHV6(base string) (ip netaddr.IP, ok bool) {
|
||||
populateOnce.Do(populate)
|
||||
for _, ip := range dohIPsOfBase[base] {
|
||||
if ip.Is6() {
|
||||
return ip, true
|
||||
}
|
||||
}
|
||||
return ip, false
|
||||
}
|
||||
|
||||
// addDoH parses a given well-formed ip string into a netaddr.IP type and
|
||||
// adds it to both knownDoH and dohIPsOFBase maps.
|
||||
func addDoH(ipStr, base string) {
|
||||
ip := netaddr.MustParseIP(ipStr)
|
||||
knownDoH[ip] = base
|
||||
dohIPsOfBase[base] = append(dohIPsOfBase[base], ip)
|
||||
}
|
||||
|
||||
// populate is called once to initialize the knownDoH and dohIPsOfBase maps.
|
||||
func populate() {
|
||||
// Cloudflare
|
||||
addDoH("1.1.1.1", "https://cloudflare-dns.com/dns-query")
|
||||
addDoH("1.0.0.1", "https://cloudflare-dns.com/dns-query")
|
||||
addDoH("2606:4700:4700::1111", "https://cloudflare-dns.com/dns-query")
|
||||
addDoH("2606:4700:4700::1001", "https://cloudflare-dns.com/dns-query")
|
||||
|
||||
// Cloudflare -Malware
|
||||
addDoH("1.1.1.2", "https://security.cloudflare-dns.com/dns-query")
|
||||
addDoH("1.0.0.2", "https://security.cloudflare-dns.com/dns-query")
|
||||
addDoH("2606:4700:4700::1112", "https://security.cloudflare-dns.com/dns-query")
|
||||
addDoH("2606:4700:4700::1002", "https://security.cloudflare-dns.com/dns-query")
|
||||
|
||||
// Cloudflare -Malware -Adult
|
||||
addDoH("1.1.1.3", "https://family.cloudflare-dns.com/dns-query")
|
||||
addDoH("1.0.0.3", "https://family.cloudflare-dns.com/dns-query")
|
||||
addDoH("2606:4700:4700::1113", "https://family.cloudflare-dns.com/dns-query")
|
||||
addDoH("2606:4700:4700::1003", "https://family.cloudflare-dns.com/dns-query")
|
||||
|
||||
// Google
|
||||
addDoH("8.8.8.8", "https://dns.google/dns-query")
|
||||
addDoH("8.8.4.4", "https://dns.google/dns-query")
|
||||
addDoH("2001:4860:4860::8888", "https://dns.google/dns-query")
|
||||
addDoH("2001:4860:4860::8844", "https://dns.google/dns-query")
|
||||
|
||||
// OpenDNS
|
||||
// TODO(bradfitz): OpenDNS is unique amongst this current set in that
|
||||
// its DoH DNS names resolve to different IPs than its normal DNS
|
||||
// IPs. Support that later. For now we assume that they're the same.
|
||||
// addDoH("208.67.222.222", "https://doh.opendns.com/dns-query")
|
||||
// addDoH("208.67.220.220", "https://doh.opendns.com/dns-query")
|
||||
// addDoH("208.67.222.123", "https://doh.familyshield.opendns.com/dns-query")
|
||||
// addDoH("208.67.220.123", "https://doh.familyshield.opendns.com/dns-query")
|
||||
|
||||
// Quad9
|
||||
addDoH("9.9.9.9", "https://dns.quad9.net/dns-query")
|
||||
addDoH("149.112.112.112", "https://dns.quad9.net/dns-query")
|
||||
addDoH("2620:fe::fe", "https://dns.quad9.net/dns-query")
|
||||
addDoH("2620:fe::fe:9", "https://dns.quad9.net/dns-query")
|
||||
|
||||
// Quad9 -DNSSEC
|
||||
addDoH("9.9.9.10", "https://dns10.quad9.net/dns-query")
|
||||
addDoH("149.112.112.10", "https://dns10.quad9.net/dns-query")
|
||||
addDoH("2620:fe::10", "https://dns10.quad9.net/dns-query")
|
||||
addDoH("2620:fe::fe:10", "https://dns10.quad9.net/dns-query")
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// 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.
|
||||
|
||||
package publicdns
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
for baseKey, baseSet := range DoHIPsOfBase() {
|
||||
for _, addr := range baseSet {
|
||||
if KnownDoH()[addr] != baseKey {
|
||||
t.Errorf("Expected %v to map to %s, got %s", addr, baseKey, KnownDoH()[addr])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDohV6(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
firstIP netaddr.IP
|
||||
want bool
|
||||
}{
|
||||
{"https://cloudflare-dns.com/dns-query", netaddr.MustParseIP("2606:4700:4700::1111"), true},
|
||||
{"https://dns.google/dns-query", netaddr.MustParseIP("2001:4860:4860::8888"), true},
|
||||
{"bogus", netaddr.IP{}, false},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.in, func(t *testing.T) {
|
||||
ip, ok := DoHV6(test.in)
|
||||
if ok != test.want || ip != test.firstIP {
|
||||
t.Errorf("DohV6 got (%v: IPv6 %v) for %v, want (%v: IPv6 %v)", ip, ok, test.in, test.firstIP, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Copyright (c) 2021 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.
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"tailscale.com/net/dns/publicdns"
|
||||
)
|
||||
|
||||
var testDoH = flag.Bool("test-doh", false, "do real DoH tests against the network")
|
||||
@@ -41,7 +40,7 @@ func TestDoH(t *testing.T) {
|
||||
if !*testDoH {
|
||||
t.Skip("skipping manual test without --test-doh flag")
|
||||
}
|
||||
if len(publicdns.KnownDoH()) == 0 {
|
||||
if len(knownDoH) == 0 {
|
||||
t.Fatal("no known DoH")
|
||||
}
|
||||
|
||||
@@ -49,7 +48,7 @@ func TestDoH(t *testing.T) {
|
||||
dohSem: make(chan struct{}, 10),
|
||||
}
|
||||
|
||||
for ip := range publicdns.KnownDoH() {
|
||||
for ip := range knownDoH {
|
||||
t.Run(ip.String(), func(t *testing.T) {
|
||||
urlBase, c, ok := f.getKnownDoHClient(ip)
|
||||
if !ok {
|
||||
@@ -86,9 +85,9 @@ func TestDoH(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDoHV6Fallback(t *testing.T) {
|
||||
for ip, base := range publicdns.KnownDoH() {
|
||||
for ip, base := range knownDoH {
|
||||
if ip.Is4() {
|
||||
ip6, ok := publicdns.DoHV6(base)
|
||||
ip6, ok := dohV6(base)
|
||||
if !ok {
|
||||
t.Errorf("no v6 DoH known for %v", ip)
|
||||
} else if !ip6.Is6() {
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
dns "golang.org/x/net/dns/dnsmessage"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/hostinfo"
|
||||
"tailscale.com/net/dns/publicdns"
|
||||
"tailscale.com/net/neterror"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/tsdial"
|
||||
@@ -243,7 +242,7 @@ func resolversWithDelays(resolvers []dnstype.Resolver) []resolverAndDelay {
|
||||
rr := make([]resolverAndDelay, len(resolvers))
|
||||
for _, r := range resolvers {
|
||||
if ip, err := netaddr.ParseIP(r.Addr); err == nil {
|
||||
if host, ok := publicdns.KnownDoH()[ip]; ok {
|
||||
if host, ok := knownDoH[ip]; ok {
|
||||
total[hostAndFam{host, ip.BitLen()}]++
|
||||
}
|
||||
}
|
||||
@@ -253,7 +252,7 @@ func resolversWithDelays(resolvers []dnstype.Resolver) []resolverAndDelay {
|
||||
for i, r := range resolvers {
|
||||
var startDelay time.Duration
|
||||
if ip, err := netaddr.ParseIP(r.Addr); err == nil {
|
||||
if host, ok := publicdns.KnownDoH()[ip]; ok {
|
||||
if host, ok := knownDoH[ip]; ok {
|
||||
key4 := hostAndFam{host, 32}
|
||||
key6 := hostAndFam{host, 128}
|
||||
switch {
|
||||
@@ -333,7 +332,7 @@ func (f *forwarder) packetListener(ip netaddr.IP) (packetListener, error) {
|
||||
}
|
||||
|
||||
func (f *forwarder) getKnownDoHClient(ip netaddr.IP) (urlBase string, c *http.Client, ok bool) {
|
||||
urlBase, ok = publicdns.KnownDoH()[ip]
|
||||
urlBase, ok = knownDoH[ip]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -357,7 +356,7 @@ func (f *forwarder) getKnownDoHClient(ip netaddr.IP) (urlBase string, c *http.Cl
|
||||
c, err := nsDialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), "443"))
|
||||
// If v4 failed, try an equivalent v6 also in the time remaining.
|
||||
if err != nil && ctx.Err() == nil {
|
||||
if ip6, ok := publicdns.DoHV6(urlBase); ok && ip.Is4() {
|
||||
if ip6, ok := dohV6(urlBase); ok && ip.Is4() {
|
||||
if c6, err := nsDialer.DialContext(ctx, "tcp", net.JoinHostPort(ip6.String(), "443")); err == nil {
|
||||
return c6, nil
|
||||
}
|
||||
@@ -777,3 +776,69 @@ func (p *closePool) Close() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var knownDoH = map[netaddr.IP]string{} // 8.8.8.8 => "https://..."
|
||||
|
||||
var dohIPsOfBase = map[string][]netaddr.IP{}
|
||||
|
||||
func addDoH(ipStr, base string) {
|
||||
ip := netaddr.MustParseIP(ipStr)
|
||||
knownDoH[ip] = base
|
||||
dohIPsOfBase[base] = append(dohIPsOfBase[base], ip)
|
||||
}
|
||||
|
||||
func dohV6(base string) (ip netaddr.IP, ok bool) {
|
||||
for _, ip := range dohIPsOfBase[base] {
|
||||
if ip.Is6() {
|
||||
return ip, true
|
||||
}
|
||||
}
|
||||
return ip, false
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Cloudflare
|
||||
addDoH("1.1.1.1", "https://cloudflare-dns.com/dns-query")
|
||||
addDoH("1.0.0.1", "https://cloudflare-dns.com/dns-query")
|
||||
addDoH("2606:4700:4700::1111", "https://cloudflare-dns.com/dns-query")
|
||||
addDoH("2606:4700:4700::1001", "https://cloudflare-dns.com/dns-query")
|
||||
|
||||
// Cloudflare -Malware
|
||||
addDoH("1.1.1.2", "https://security.cloudflare-dns.com/dns-query")
|
||||
addDoH("1.0.0.2", "https://security.cloudflare-dns.com/dns-query")
|
||||
addDoH("2606:4700:4700::1112", "https://security.cloudflare-dns.com/dns-query")
|
||||
addDoH("2606:4700:4700::1002", "https://security.cloudflare-dns.com/dns-query")
|
||||
|
||||
// Cloudflare -Malware -Adult
|
||||
addDoH("1.1.1.3", "https://family.cloudflare-dns.com/dns-query")
|
||||
addDoH("1.0.0.3", "https://family.cloudflare-dns.com/dns-query")
|
||||
addDoH("2606:4700:4700::1113", "https://family.cloudflare-dns.com/dns-query")
|
||||
addDoH("2606:4700:4700::1003", "https://family.cloudflare-dns.com/dns-query")
|
||||
|
||||
// Google
|
||||
addDoH("8.8.8.8", "https://dns.google/dns-query")
|
||||
addDoH("8.8.4.4", "https://dns.google/dns-query")
|
||||
addDoH("2001:4860:4860::8888", "https://dns.google/dns-query")
|
||||
addDoH("2001:4860:4860::8844", "https://dns.google/dns-query")
|
||||
|
||||
// OpenDNS
|
||||
// TODO(bradfitz): OpenDNS is unique amongst this current set in that
|
||||
// its DoH DNS names resolve to different IPs than its normal DNS
|
||||
// IPs. Support that later. For now we assume that they're the same.
|
||||
// addDoH("208.67.222.222", "https://doh.opendns.com/dns-query")
|
||||
// addDoH("208.67.220.220", "https://doh.opendns.com/dns-query")
|
||||
// addDoH("208.67.222.123", "https://doh.familyshield.opendns.com/dns-query")
|
||||
// addDoH("208.67.220.123", "https://doh.familyshield.opendns.com/dns-query")
|
||||
|
||||
// Quad9
|
||||
addDoH("9.9.9.9", "https://dns.quad9.net/dns-query")
|
||||
addDoH("149.112.112.112", "https://dns.quad9.net/dns-query")
|
||||
addDoH("2620:fe::fe", "https://dns.quad9.net/dns-query")
|
||||
addDoH("2620:fe::fe:9", "https://dns.quad9.net/dns-query")
|
||||
|
||||
// Quad9 -DNSSEC
|
||||
addDoH("9.9.9.10", "https://dns10.quad9.net/dns-query")
|
||||
addDoH("149.112.112.10", "https://dns10.quad9.net/dns-query")
|
||||
addDoH("2620:fe::10", "https://dns10.quad9.net/dns-query")
|
||||
addDoH("2620:fe::fe:10", "https://dns10.quad9.net/dns-query")
|
||||
}
|
||||
|
||||
@@ -25,27 +25,15 @@ import (
|
||||
dns "golang.org/x/net/dns/dnsmessage"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/net/dns/resolvconffile"
|
||||
tspacket "tailscale.com/net/packet"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/net/tstun"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/ipproto"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
)
|
||||
|
||||
const dnsSymbolicFQDN = "magicdns.localhost-tailscale-daemon."
|
||||
|
||||
var (
|
||||
magicDNSIP = tsaddr.TailscaleServiceIP()
|
||||
magicDNSIPv6 = tsaddr.TailscaleServiceIPv6()
|
||||
)
|
||||
|
||||
const magicDNSPort = 53
|
||||
|
||||
// maxResponseBytes is the maximum size of a response from a Resolver. The
|
||||
// actual buffer size will be one larger than this so that we can detect
|
||||
// truncation in a platform-agnostic way.
|
||||
@@ -292,20 +280,10 @@ func (r *Resolver) Close() {
|
||||
r.forwarder.Close()
|
||||
}
|
||||
|
||||
// EnqueuePacket handles a packet to the magicDNS endpoint.
|
||||
// EnqueueRequest places the given DNS request in the resolver's queue.
|
||||
// It takes ownership of the payload and does not block.
|
||||
// If the queue is full, the request will be dropped and an error will be returned.
|
||||
func (r *Resolver) EnqueuePacket(bs []byte, proto ipproto.Proto, from, to netaddr.IPPort) error {
|
||||
if to.Port() != magicDNSPort || proto != ipproto.UDP {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.enqueueRequest(bs, proto, from, to)
|
||||
}
|
||||
|
||||
// enqueueRequest places the given DNS request in the resolver's queue.
|
||||
// If the queue is full, the request will be dropped and an error will be returned.
|
||||
func (r *Resolver) enqueueRequest(bs []byte, proto ipproto.Proto, from, to netaddr.IPPort) error {
|
||||
func (r *Resolver) EnqueueRequest(bs []byte, from netaddr.IPPort) error {
|
||||
metricDNSQueryLocal.Add(1)
|
||||
select {
|
||||
case <-r.closed:
|
||||
@@ -322,56 +300,9 @@ func (r *Resolver) enqueueRequest(bs []byte, proto ipproto.Proto, from, to netad
|
||||
return nil
|
||||
}
|
||||
|
||||
// NextPacket returns the next packet to service traffic for magicDNS. The returned
|
||||
// packet is prefixed with unused space consistent with the semantics of injection
|
||||
// into tstun.Wrapper.
|
||||
// NextResponse returns a DNS response to a previously enqueued request.
|
||||
// It blocks until a response is available and gives up ownership of the response payload.
|
||||
func (r *Resolver) NextPacket() (ipPacket []byte, err error) {
|
||||
bs, to, err := r.nextResponse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Unused space is needed further down the stack. To avoid extra
|
||||
// allocations/copying later on, we allocate such space here.
|
||||
const offset = tstun.PacketStartOffset
|
||||
|
||||
var buf []byte
|
||||
switch {
|
||||
case to.IP().Is4():
|
||||
h := tspacket.UDP4Header{
|
||||
IP4Header: tspacket.IP4Header{
|
||||
Src: magicDNSIP,
|
||||
Dst: to.IP(),
|
||||
},
|
||||
SrcPort: magicDNSPort,
|
||||
DstPort: to.Port(),
|
||||
}
|
||||
hlen := h.Len()
|
||||
buf = make([]byte, offset+hlen+len(bs))
|
||||
copy(buf[offset+hlen:], bs)
|
||||
h.Marshal(buf[offset:])
|
||||
case to.IP().Is6():
|
||||
h := tspacket.UDP6Header{
|
||||
IP6Header: tspacket.IP6Header{
|
||||
Src: magicDNSIPv6,
|
||||
Dst: to.IP(),
|
||||
},
|
||||
SrcPort: magicDNSPort,
|
||||
DstPort: to.Port(),
|
||||
}
|
||||
hlen := h.Len()
|
||||
buf = make([]byte, offset+hlen+len(bs))
|
||||
copy(buf[offset+hlen:], bs)
|
||||
h.Marshal(buf[offset:])
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// nextResponse returns a DNS response to a previously enqueued request.
|
||||
// It blocks until a response is available and gives up ownership of the response payload.
|
||||
func (r *Resolver) nextResponse() (packet []byte, to netaddr.IPPort, err error) {
|
||||
func (r *Resolver) NextResponse() (packet []byte, to netaddr.IPPort, err error) {
|
||||
select {
|
||||
case <-r.closed:
|
||||
return nil, netaddr.IPPort{}, ErrClosed
|
||||
@@ -622,18 +553,6 @@ func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netaddr.IP,
|
||||
return netaddr.IP{}, dns.RCodeNameError
|
||||
}
|
||||
|
||||
// We return a symbolic domain if someone does a reverse lookup on the
|
||||
// DNS endpoint. To round out this special case, we also do the inverse
|
||||
// (returning the endpoint IP if someone looks up the symbolic domain).
|
||||
if domain == dnsSymbolicFQDN {
|
||||
switch typ {
|
||||
case dns.TypeA:
|
||||
return tsaddr.TailscaleServiceIP(), dns.RCodeSuccess
|
||||
case dns.TypeAAAA:
|
||||
return tsaddr.TailscaleServiceIPv6(), dns.RCodeSuccess
|
||||
}
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
hosts := r.hostToIP
|
||||
localDomains := r.localDomains
|
||||
@@ -725,14 +644,6 @@ func (r *Resolver) resolveLocalReverse(name dnsname.FQDN) (dnsname.FQDN, dns.RCo
|
||||
return "", dns.RCodeRefused
|
||||
}
|
||||
|
||||
// If someone curiously does a reverse lookup on the DNS IP, we
|
||||
// return a domain that helps indicate that Tailscale is using
|
||||
// this IP for a special purpose and it is not a node on their
|
||||
// tailnet.
|
||||
if ip == tsaddr.TailscaleServiceIP() || ip == tsaddr.TailscaleServiceIPv6() {
|
||||
return dnsSymbolicFQDN, dns.RCodeSuccess
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
ret, ok := r.ipToHost[ip]
|
||||
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/ipproto"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
)
|
||||
@@ -38,8 +37,6 @@ var (
|
||||
|
||||
testipv4Arpa = dnsname.FQDN("4.3.2.1.in-addr.arpa.")
|
||||
testipv6Arpa = dnsname.FQDN("f.0.e.0.d.0.c.0.b.0.a.0.9.0.8.0.7.0.6.0.5.0.4.0.3.0.2.0.1.0.0.0.ip6.arpa.")
|
||||
|
||||
magicDNSv4Port = netaddr.MustParseIPPort("100.100.100.100:53")
|
||||
)
|
||||
|
||||
var dnsCfg = Config{
|
||||
@@ -234,10 +231,10 @@ func unpackResponse(payload []byte) (dnsResponse, error) {
|
||||
}
|
||||
|
||||
func syncRespond(r *Resolver, query []byte) ([]byte, error) {
|
||||
if err := r.enqueueRequest(query, ipproto.UDP, netaddr.IPPort{}, magicDNSv4Port); err != nil {
|
||||
return nil, fmt.Errorf("enqueueRequest: %w", err)
|
||||
if err := r.EnqueueRequest(query, netaddr.IPPort{}); err != nil {
|
||||
return nil, fmt.Errorf("EnqueueRequest: %w", err)
|
||||
}
|
||||
payload, _, err := r.nextResponse()
|
||||
payload, _, err := r.NextResponse()
|
||||
return payload, err
|
||||
}
|
||||
|
||||
@@ -347,7 +344,6 @@ func TestResolveLocal(t *testing.T) {
|
||||
{"mx-nxdomain", "test3.ipn.dev.", dns.TypeMX, netaddr.IP{}, dns.RCodeNameError},
|
||||
{"ns-nxdomain", "test3.ipn.dev.", dns.TypeNS, netaddr.IP{}, dns.RCodeNameError},
|
||||
{"onion-domain", "footest.onion.", dns.TypeA, netaddr.IP{}, dns.RCodeNameError},
|
||||
{"magicdns", dnsSymbolicFQDN, dns.TypeA, netaddr.MustParseIP("100.100.100.100"), dns.RCodeSuccess},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -381,7 +377,6 @@ func TestResolveLocalReverse(t *testing.T) {
|
||||
{"ipv4_nxdomain", dnsname.FQDN("5.3.2.1.in-addr.arpa."), "", dns.RCodeNameError},
|
||||
{"ipv6_nxdomain", dnsname.FQDN("0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.ip6.arpa."), "", dns.RCodeNameError},
|
||||
{"nxdomain", dnsname.FQDN("2.3.4.5.in-addr.arpa."), "", dns.RCodeRefused},
|
||||
{"magicdns", dnsname.FQDN("100.100.100.100.in-addr.arpa."), dnsSymbolicFQDN, dns.RCodeSuccess},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -730,14 +725,14 @@ func TestDelegateCollision(t *testing.T) {
|
||||
// packets will have the same dns txid.
|
||||
for _, p := range packets {
|
||||
payload := dnspacket(p.qname, p.qtype, noEdns)
|
||||
err := r.enqueueRequest(payload, ipproto.UDP, p.addr, magicDNSv4Port)
|
||||
err := r.EnqueueRequest(payload, p.addr)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Despite the txid collision, the answer(s) should still match the query.
|
||||
resp, addr, err := r.nextResponse()
|
||||
resp, addr, err := r.NextResponse()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ func (d *dialer) DialContext(ctx context.Context, network, address string) (retC
|
||||
defer func() {
|
||||
// On failure, consider that our DNS might be wrong and ask the DNS fallback mechanism for
|
||||
// some other IPs to try.
|
||||
if ret == nil || ctx.Err() != nil || d.dnsCache.LookupIPFallback == nil || dc.dnsWasTrustworthy() {
|
||||
if ret == nil || d.dnsCache.LookupIPFallback == nil || dc.dnsWasTrustworthy() {
|
||||
return
|
||||
}
|
||||
ips, err := d.dnsCache.LookupIPFallback(ctx, host)
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:generate go run update-dns-fallbacks.go
|
||||
|
||||
// Package dnsfallback contains a DNS fallback mechanism
|
||||
// for starting up Tailscale when the system DNS is broken or otherwise unavailable.
|
||||
package dnsfallback
|
||||
@@ -29,10 +27,6 @@ import (
|
||||
)
|
||||
|
||||
func Lookup(ctx context.Context, host string) ([]netaddr.IP, error) {
|
||||
if ip, err := netaddr.ParseIP(host); err == nil && !ip.IsZero() {
|
||||
return []netaddr.IP{ip}, nil
|
||||
}
|
||||
|
||||
type nameIP struct {
|
||||
dnsName string
|
||||
ip netaddr.IP
|
||||
|
||||
10
net/dnsfallback/generate.go
Normal file
10
net/dnsfallback/generate.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// 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 !hermetic
|
||||
// +build !hermetic
|
||||
|
||||
package dnsfallback
|
||||
|
||||
//go:generate go run update-dns-fallbacks.go
|
||||
@@ -1,221 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// Package netutil contains misc shared networking code & types.
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/net/interfaces"
|
||||
)
|
||||
|
||||
// protocolsRequiredForForwarding reports whether IPv4 and/or IPv6 protocols are
|
||||
// required to forward the specified routes.
|
||||
// The state param must be specified.
|
||||
func protocolsRequiredForForwarding(routes []netaddr.IPPrefix, state *interfaces.State) (v4, v6 bool) {
|
||||
if len(routes) == 0 {
|
||||
// Nothing to route, so no need to warn.
|
||||
return false, false
|
||||
}
|
||||
|
||||
localIPs := make(map[netaddr.IP]bool)
|
||||
for _, addrs := range state.InterfaceIPs {
|
||||
for _, pfx := range addrs {
|
||||
localIPs[pfx.IP()] = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range routes {
|
||||
// It's possible to advertise a route to one of the local
|
||||
// machine's local IPs. IP forwarding isn't required for this
|
||||
// to work, so we shouldn't warn for such exports.
|
||||
if r.IsSingleIP() && localIPs[r.IP()] {
|
||||
continue
|
||||
}
|
||||
if r.IP().Is4() {
|
||||
v4 = true
|
||||
} else {
|
||||
v6 = true
|
||||
}
|
||||
}
|
||||
return v4, v6
|
||||
}
|
||||
|
||||
// CheckIPForwarding reports whether IP forwarding is enabled correctly
|
||||
// for subnet routing and exit node functionality on any interface.
|
||||
// The state param can be nil, in which case interfaces.GetState is used.
|
||||
// The routes should only be advertised routes, and should not contain the
|
||||
// nodes Tailscale IPs.
|
||||
// It returns an error if it is unable to determine if IP forwarding is enabled.
|
||||
// It returns a warning describing configuration issues if IP forwarding is
|
||||
// non-functional or partly functional.
|
||||
func CheckIPForwarding(routes []netaddr.IPPrefix, state *interfaces.State) (warn, err error) {
|
||||
if runtime.GOOS != "linux" {
|
||||
switch runtime.GOOS {
|
||||
case "dragonfly", "freebsd", "netbsd", "openbsd":
|
||||
return fmt.Errorf("Subnet routing and exit nodes only work with additional manual configuration on %v, and is not currently officially supported.", runtime.GOOS), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
const kbLink = "\nSee https://tailscale.com/kb/1104/enable-ip-forwarding/"
|
||||
if state == nil {
|
||||
var err error
|
||||
state, err = interfaces.GetState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
wantV4, wantV6 := protocolsRequiredForForwarding(routes, state)
|
||||
if !wantV4 && !wantV6 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
v4e, err := ipForwardingEnabledLinux(ipv4, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't check system's IP forwarding configuration, subnet routing/exit nodes may not work: %w%s", err, kbLink)
|
||||
}
|
||||
v6e, err := ipForwardingEnabledLinux(ipv6, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't check system's IP forwarding configuration, subnet routing/exit nodes may not work: %w%s", err, kbLink)
|
||||
}
|
||||
|
||||
if v4e && v6e {
|
||||
// IP forwarding is enabled systemwide, all is well.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !wantV4 {
|
||||
if !v6e {
|
||||
return nil, fmt.Errorf("IPv6 forwarding is disabled, subnet routing/exit nodes may not work.%s", kbLink)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
// IP forwarding isn't enabled globally, but it might be enabled
|
||||
// on a per-interface basis. Check if it's on for all interfaces,
|
||||
// and warn appropriately if it's not.
|
||||
// Note: you might be wondering why we check only the state of
|
||||
// ipv6.conf.all.forwarding, rather than per-interface forwarding
|
||||
// configuration. According to kernel documentation, it seems
|
||||
// that to actually forward packets, you need to enable
|
||||
// forwarding globally, and the per-interface forwarding
|
||||
// setting only alters other things such as how router
|
||||
// advertisements are handled. The kernel itself warns that
|
||||
// enabling forwarding per-interface and not globally will
|
||||
// probably not work, so I feel okay calling those configs
|
||||
// broken until we have proof otherwise.
|
||||
var (
|
||||
anyEnabled bool
|
||||
warnings []string
|
||||
)
|
||||
if wantV6 && !v6e {
|
||||
warnings = append(warnings, "IPv6 forwarding is disabled.")
|
||||
}
|
||||
for _, iface := range state.Interface {
|
||||
if iface.Name == "lo" {
|
||||
continue
|
||||
}
|
||||
v4e, err := ipForwardingEnabledLinux(ipv4, iface.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't check system's IP forwarding configuration, subnet routing/exit nodes may not work: %w%s", err, kbLink)
|
||||
} else if !v4e {
|
||||
warnings = append(warnings, fmt.Sprintf("Traffic received on %s won't be forwarded (%s disabled)", iface.Name, ipForwardSysctlKey(dotFormat, ipv4, iface.Name)))
|
||||
} else {
|
||||
anyEnabled = true
|
||||
}
|
||||
}
|
||||
if !anyEnabled {
|
||||
// IP forwarding is completely disabled, just say that rather
|
||||
// than enumerate all the interfaces on the system.
|
||||
return fmt.Errorf("IP forwarding is disabled, subnet routing/exit nodes will not work.%s", kbLink), nil
|
||||
}
|
||||
if len(warnings) > 0 {
|
||||
// If partially enabled, enumerate the bits that won't work.
|
||||
return fmt.Errorf("%s\nSubnet routes and exit nodes may not work correctly.%s", strings.Join(warnings, "\n"), kbLink), nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ipForwardSysctlKey returns the sysctl key for the given protocol and iface.
|
||||
// When the dotFormat parameter is true the output is formatted as `net.ipv4.ip_forward`,
|
||||
// else it is `net/ipv4/ip_forward`
|
||||
func ipForwardSysctlKey(format sysctlFormat, p protocol, iface string) string {
|
||||
if iface == "" {
|
||||
if format == dotFormat {
|
||||
if p == ipv4 {
|
||||
return "net.ipv4.ip_forward"
|
||||
}
|
||||
return "net.ipv6.conf.all.forwarding"
|
||||
}
|
||||
if p == ipv4 {
|
||||
return "net/ipv4/ip_forward"
|
||||
}
|
||||
return "net/ipv6/conf/all/forwarding"
|
||||
}
|
||||
|
||||
var k string
|
||||
if p == ipv4 {
|
||||
k = "net/ipv4/conf/%s/forwarding"
|
||||
} else {
|
||||
k = "net/ipv6/conf/%s/forwarding"
|
||||
}
|
||||
if format == dotFormat {
|
||||
// Swap the delimiters.
|
||||
iface = strings.ReplaceAll(iface, ".", "/")
|
||||
k = strings.ReplaceAll(k, "/", ".")
|
||||
}
|
||||
return fmt.Sprintf(k, iface)
|
||||
}
|
||||
|
||||
type sysctlFormat int
|
||||
|
||||
const (
|
||||
dotFormat sysctlFormat = iota
|
||||
slashFormat
|
||||
)
|
||||
|
||||
type protocol int
|
||||
|
||||
const (
|
||||
ipv4 protocol = iota
|
||||
ipv6
|
||||
)
|
||||
|
||||
// ipForwardingEnabledLinux reports whether the IP Forwarding is enabled for the
|
||||
// given interface.
|
||||
// The iface param determines which interface to check against, "" means to check
|
||||
// global config.
|
||||
// It tries to lookup the value directly from `/proc/sys`, and fallsback to
|
||||
// using `sysctl` on failure.
|
||||
func ipForwardingEnabledLinux(p protocol, iface string) (bool, error) {
|
||||
k := ipForwardSysctlKey(slashFormat, p, iface)
|
||||
bs, err := os.ReadFile(filepath.Join("/proc/sys", k))
|
||||
if err != nil {
|
||||
// Fallback to using sysctl.
|
||||
// Sysctl accepts `/` as separator.
|
||||
bs, err = exec.Command("sysctl", "-n", k).Output()
|
||||
if err != nil {
|
||||
// But in case it doesn't.
|
||||
k := ipForwardSysctlKey(dotFormat, p, iface)
|
||||
bs, err = exec.Command("sysctl", "-n", k).Output()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("couldn't check %s (%v)", k, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
on, err := strconv.ParseBool(string(bytes.TrimSpace(bs)))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("couldn't parse %s (%v)", k, err)
|
||||
}
|
||||
return on, nil
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
@@ -64,55 +63,3 @@ type dummyAddr string
|
||||
|
||||
func (a dummyAddr) Network() string { return string(a) }
|
||||
func (a dummyAddr) String() string { return string(a) }
|
||||
|
||||
// NewDrainBufConn returns a net.Conn conditionally wrapping c,
|
||||
// prefixing any bytes that are in initialReadBuf, which may be nil.
|
||||
func NewDrainBufConn(c net.Conn, initialReadBuf *bufio.Reader) net.Conn {
|
||||
r := initialReadBuf
|
||||
if r != nil && r.Buffered() == 0 {
|
||||
r = nil
|
||||
}
|
||||
return &drainBufConn{c, r}
|
||||
}
|
||||
|
||||
// drainBufConn is a net.Conn with an initial bunch of bytes in a
|
||||
// bufio.Reader. Read drains the bufio.Reader until empty, then passes
|
||||
// through subsequent reads to the Conn directly.
|
||||
type drainBufConn struct {
|
||||
net.Conn
|
||||
r *bufio.Reader
|
||||
}
|
||||
|
||||
func (b *drainBufConn) Read(bs []byte) (int, error) {
|
||||
if b.r == nil {
|
||||
return b.Conn.Read(bs)
|
||||
}
|
||||
n, err := b.r.Read(bs)
|
||||
if b.r.Buffered() == 0 {
|
||||
b.r = nil
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NewAltReadWriteCloserConn returns a net.Conn that wraps rwc (for
|
||||
// Read, Write, and Close) and c (for all other methods).
|
||||
func NewAltReadWriteCloserConn(rwc io.ReadWriteCloser, c net.Conn) net.Conn {
|
||||
return wrappedConn{c, rwc}
|
||||
}
|
||||
|
||||
type wrappedConn struct {
|
||||
net.Conn
|
||||
rwc io.ReadWriteCloser
|
||||
}
|
||||
|
||||
func (w wrappedConn) Read(bs []byte) (int, error) {
|
||||
return w.rwc.Read(bs)
|
||||
}
|
||||
|
||||
func (w wrappedConn) Write(bs []byte) (int, error) {
|
||||
return w.rwc.Write(bs)
|
||||
}
|
||||
|
||||
func (w wrappedConn) Close() error {
|
||||
return w.rwc.Close()
|
||||
}
|
||||
|
||||
@@ -434,6 +434,40 @@ func (q *Parsed) IsEchoResponse() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveECNBits modifies p and its underlying memory buffer to remove
|
||||
// ECN bits, if any. It reports whether it did so.
|
||||
//
|
||||
// It currently only does the TCP flags.
|
||||
func (p *Parsed) RemoveECNBits() bool {
|
||||
if p.IPVersion == 0 {
|
||||
return false
|
||||
}
|
||||
if p.IPProto != ipproto.TCP {
|
||||
// TODO(bradfitz): handle non-TCP too? for now only trying to
|
||||
// fix the Issue 2642 problem.
|
||||
return false
|
||||
}
|
||||
if p.TCPFlags&TCPECNBits == 0 {
|
||||
// Nothing to do.
|
||||
return false
|
||||
}
|
||||
|
||||
// Clear flags.
|
||||
|
||||
// First in the parsed output.
|
||||
p.TCPFlags = p.TCPFlags & ^TCPECNBits
|
||||
|
||||
// Then in the underlying memory.
|
||||
tcp := p.Transport()
|
||||
old := binary.BigEndian.Uint16(tcp[12:14])
|
||||
tcp[13] = byte(p.TCPFlags)
|
||||
new := binary.BigEndian.Uint16(tcp[12:14])
|
||||
oldSum := binary.BigEndian.Uint16(tcp[16:18])
|
||||
newSum := ^checksumUpdate2ByteAlignedUint16(^oldSum, old, new)
|
||||
binary.BigEndian.PutUint16(tcp[16:18], newSum)
|
||||
return true
|
||||
}
|
||||
|
||||
func Hexdump(b []byte) string {
|
||||
out := new(strings.Builder)
|
||||
for i := 0; i < len(b); i += 16 {
|
||||
@@ -465,3 +499,26 @@ func Hexdump(b []byte) string {
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// From gVisor's unexported API:
|
||||
|
||||
// checksumUpdate2ByteAlignedUint16 updates a uint16 value in a calculated
|
||||
// checksum.
|
||||
//
|
||||
// The value MUST begin at a 2-byte boundary in the original buffer.
|
||||
func checksumUpdate2ByteAlignedUint16(xsum, old, new uint16) uint16 {
|
||||
// As per RFC 1071 page 4,
|
||||
//(4) Incremental Update
|
||||
//
|
||||
// ...
|
||||
//
|
||||
// To update the checksum, simply add the differences of the
|
||||
// sixteen bit integers that have been changed. To see why this
|
||||
// works, observe that every 16-bit integer has an additive inverse
|
||||
// and that addition is associative. From this it follows that
|
||||
// given the original value m, the new value m', and the old
|
||||
// checksum C, the new checksum C' is:
|
||||
//
|
||||
// C' = C + (-m) + m' = C + (m' - m)
|
||||
return checksumCombine(xsum, checksumCombine(new, ^old))
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ package packet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"inet.af/netaddr"
|
||||
@@ -561,3 +563,57 @@ func BenchmarkString(b *testing.B) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveECNBits(t *testing.T) {
|
||||
// withECNHex is a TCP SYN packet with ECN bits set in the TCP
|
||||
// header as captured by Wireshark on macOS against the
|
||||
// Tailscale interface. In this packet (because it's a SYN
|
||||
// control packet), the ECN bits are not set in the IP header.
|
||||
const withECNHex = `45 00 00 40 00 00 40 00
|
||||
40 06 0c 66 64 7b 65 28 64 7f 00 30 f1 ab 00 16
|
||||
5a 7a 63 e8 00 00 00 00 b0 c2 ff ff 97 76 00 00
|
||||
02 04 04 d8 01 03 03 06 01 01 08 0a 03 e1 bd 49
|
||||
00 00 00 00 04 02 00 00`
|
||||
|
||||
// Generated by hand-editing a pcap file in hexl-mode to set
|
||||
// the TCP flags to just SYN (0x02), then loading that pcap
|
||||
// file in wireshark to get the expected checksum value, then
|
||||
// putting that checksum value (0x9836) in the file.
|
||||
const wantStrippedHex = `45 00 00 40 00 00 40 00
|
||||
40 06 0c 66 64 7b 65 28 64 7f 00 30 f1 ab 00 16
|
||||
5a 7a 63 e8 00 00 00 00 b0 02 ff ff 98 36 00 00
|
||||
02 04 04 d8 01 03 03 06 01 01 08 0a 03 e1 bd 49
|
||||
00 00 00 00 04 02 00 00`
|
||||
|
||||
var p Parsed
|
||||
pktBuf := bytesOfHex(withECNHex)
|
||||
p.Decode(pktBuf)
|
||||
if want := TCPCWR | TCPECNEcho | TCPSyn; p.TCPFlags != want {
|
||||
t.Fatalf("pre flags = %v; want %v", p.TCPFlags, want)
|
||||
}
|
||||
|
||||
if !p.RemoveECNBits() {
|
||||
t.Fatal("didn't remove bits")
|
||||
}
|
||||
if want := TCPSyn; p.TCPFlags != want {
|
||||
t.Fatalf("post flags = %v; want %v", p.TCPFlags, want)
|
||||
}
|
||||
wantPkt := bytesOfHex(wantStrippedHex)
|
||||
if !bytes.Equal(pktBuf, wantPkt) {
|
||||
t.Fatalf("wrong result.\n got: % 2x\nwant: % 2x\n", pktBuf, wantPkt)
|
||||
}
|
||||
|
||||
if p.RemoveECNBits() {
|
||||
t.Fatal("unexpected true return value on second call")
|
||||
}
|
||||
}
|
||||
|
||||
var nonHex = regexp.MustCompile(`[^0-9a-fA-F]+`)
|
||||
|
||||
func bytesOfHex(s string) []byte {
|
||||
b, err := hex.DecodeString(nonHex.ReplaceAllString(s, ""))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ var (
|
||||
cgnatRange oncePrefix
|
||||
ulaRange oncePrefix
|
||||
tsUlaRange oncePrefix
|
||||
tsViaRange oncePrefix
|
||||
ula4To6Range oncePrefix
|
||||
ulaEph6Range oncePrefix
|
||||
serviceIPv6 oncePrefix
|
||||
@@ -73,14 +72,6 @@ func TailscaleULARange() netaddr.IPPrefix {
|
||||
return tsUlaRange.v
|
||||
}
|
||||
|
||||
// TailscaleViaRange returns the IPv6 Unique Local Address subset range
|
||||
// TailscaleULARange that's used for IPv4 tunneling via IPv6.
|
||||
func TailscaleViaRange() netaddr.IPPrefix {
|
||||
// Mnemonic: "b1a" sounds like "via".
|
||||
tsViaRange.Do(func() { mustPrefix(&tsViaRange.v, "fd7a:115c:a1e0:b1a::/64") })
|
||||
return tsViaRange.v
|
||||
}
|
||||
|
||||
// Tailscale4To6Range returns the subset of TailscaleULARange used for
|
||||
// auto-translated Tailscale ipv4 addresses.
|
||||
func Tailscale4To6Range() netaddr.IPPrefix {
|
||||
@@ -247,36 +238,3 @@ func AllIPv4() netaddr.IPPrefix { return allIPv4 }
|
||||
|
||||
// AllIPv6 returns ::/0.
|
||||
func AllIPv6() netaddr.IPPrefix { return allIPv6 }
|
||||
|
||||
// ExitRoutes returns a slice containing AllIPv4 and AllIPv6.
|
||||
func ExitRoutes() []netaddr.IPPrefix { return []netaddr.IPPrefix{allIPv4, allIPv6} }
|
||||
|
||||
// FilterPrefixes returns a new slice, not aliasing in, containing elements of
|
||||
// in that match f.
|
||||
func FilterPrefixesCopy(in []netaddr.IPPrefix, f func(netaddr.IPPrefix) bool) []netaddr.IPPrefix {
|
||||
var out []netaddr.IPPrefix
|
||||
for _, v := range in {
|
||||
if f(v) {
|
||||
out = append(out, v)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// IsViaPrefix reports whether p is a CIDR in the Tailscale "via" range.
|
||||
// See TailscaleViaRange.
|
||||
func IsViaPrefix(p netaddr.IPPrefix) bool {
|
||||
return TailscaleViaRange().Contains(p.IP())
|
||||
}
|
||||
|
||||
// UnmapVia returns the IPv4 address that corresponds to the provided Tailscale
|
||||
// "via" IPv4-in-IPv6 address.
|
||||
//
|
||||
// If ip is not a via address, it returns ip unchanged.
|
||||
func UnmapVia(ip netaddr.IP) netaddr.IP {
|
||||
if TailscaleViaRange().Contains(ip) {
|
||||
a := ip.As16()
|
||||
return netaddr.IPFrom4(*(*[4]byte)(a[12:16]))
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
@@ -88,19 +88,3 @@ func BenchmarkTailscaleServiceAddr(b *testing.B) {
|
||||
sinkIP = TailscaleServiceIP()
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmapVia(t *testing.T) {
|
||||
tests := []struct {
|
||||
ip string
|
||||
want string
|
||||
}{
|
||||
{"1.2.3.4", "1.2.3.4"}, // unchanged v4
|
||||
{"fd7a:115c:a1e0:b1a::bb:10.2.1.3", "10.2.1.3"},
|
||||
{"fd7a:115c:a1e0:b1b::bb:10.2.1.4", "fd7a:115c:a1e0:b1b:0:bb:a02:104"}, // "b1b",not "bia"
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := UnmapVia(netaddr.MustParseIP(tt.ip)).String(); got != tt.want {
|
||||
t.Errorf("for %q: got %q, want %q", tt.ip, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,14 +21,9 @@ import (
|
||||
// It must not be mutated once created.
|
||||
//
|
||||
// Example keys are "foo.domain.tld.beta.tailscale.net" and "foo",
|
||||
// both without trailing dots, and both always lowercase.
|
||||
// both without trailing dots.
|
||||
type dnsMap map[string]netaddr.IP
|
||||
|
||||
// canonMapKey canonicalizes its input s to be a dnsMap map key.
|
||||
func canonMapKey(s string) string {
|
||||
return strings.ToLower(strings.TrimSuffix(s, "."))
|
||||
}
|
||||
|
||||
func dnsMapFromNetworkMap(nm *netmap.NetworkMap) dnsMap {
|
||||
if nm == nil {
|
||||
return nil
|
||||
@@ -38,9 +33,9 @@ func dnsMapFromNetworkMap(nm *netmap.NetworkMap) dnsMap {
|
||||
have4 := false
|
||||
if nm.Name != "" && len(nm.Addresses) > 0 {
|
||||
ip := nm.Addresses[0].IP()
|
||||
ret[canonMapKey(nm.Name)] = ip
|
||||
ret[strings.TrimRight(nm.Name, ".")] = ip
|
||||
if dnsname.HasSuffix(nm.Name, suffix) {
|
||||
ret[canonMapKey(dnsname.TrimSuffix(nm.Name, suffix))] = ip
|
||||
ret[dnsname.TrimSuffix(nm.Name, suffix)] = ip
|
||||
}
|
||||
for _, a := range nm.Addresses {
|
||||
if a.IP().Is4() {
|
||||
@@ -57,9 +52,9 @@ func dnsMapFromNetworkMap(nm *netmap.NetworkMap) dnsMap {
|
||||
if ip.Is4() && !have4 {
|
||||
continue
|
||||
}
|
||||
ret[canonMapKey(p.Name)] = ip
|
||||
ret[strings.TrimRight(p.Name, ".")] = ip
|
||||
if dnsname.HasSuffix(p.Name, suffix) {
|
||||
ret[canonMapKey(dnsname.TrimSuffix(p.Name, suffix))] = ip
|
||||
ret[dnsname.TrimSuffix(p.Name, suffix)] = ip
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -72,7 +67,7 @@ func dnsMapFromNetworkMap(nm *netmap.NetworkMap) dnsMap {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ret[canonMapKey(rec.Name)] = ip
|
||||
ret[strings.TrimRight(rec.Name, ".")] = ip
|
||||
}
|
||||
return ret
|
||||
}
|
||||
@@ -111,7 +106,7 @@ func (m dnsMap) resolveMemory(ctx context.Context, network, addr string) (_ neta
|
||||
// Host is not an IP, so assume it's a DNS name.
|
||||
|
||||
// Try MagicDNS first, otherwise a real DNS lookup.
|
||||
ip := m[canonMapKey(host)]
|
||||
ip := m[host]
|
||||
if !ip.IsZero() {
|
||||
return netaddr.IPPortFrom(ip, port), nil
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"go4.org/mem"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/disco"
|
||||
"tailscale.com/net/packet"
|
||||
@@ -155,19 +154,12 @@ type Wrapper struct {
|
||||
disableTSMPRejected bool
|
||||
}
|
||||
|
||||
// tunReadResult is the result of a TUN read, or an injected result pretending to be a TUN read.
|
||||
// The data is not interpreted in the usual way for a Read method.
|
||||
// tunReadResult is the result of a TUN read: Some data and an error.
|
||||
// The byte slice is not interpreted in the usual way for a Read method.
|
||||
// See the comment in the middle of Wrap.Read.
|
||||
type tunReadResult struct {
|
||||
// Only one of err, packet or data should be set, and are read in that order
|
||||
// of precendence.
|
||||
err error
|
||||
packet *stack.PacketBuffer
|
||||
data []byte
|
||||
|
||||
// injected is set if the read result was generated internally, and contained packets should not
|
||||
// pass through filters.
|
||||
injected bool
|
||||
data []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func WrapTAP(logf logger.Logf, tdev tun.Device) *Wrapper {
|
||||
@@ -502,22 +494,15 @@ func (t *Wrapper) Read(buf []byte, offset int) (int, error) {
|
||||
}
|
||||
|
||||
metricPacketOut.Add(1)
|
||||
|
||||
var n int
|
||||
if res.packet != nil {
|
||||
n = copy(buf[offset:], res.packet.NetworkHeader().View())
|
||||
n += copy(buf[offset+n:], res.packet.TransportHeader().View())
|
||||
n += copy(buf[offset+n:], res.packet.Data().AsRange().AsView())
|
||||
|
||||
res.packet.DecRef()
|
||||
} else {
|
||||
n = copy(buf[offset:], res.data)
|
||||
|
||||
// t.buffer has a fixed location in memory.
|
||||
if &res.data[0] == &t.buffer[PacketStartOffset] {
|
||||
// We are done with t.buffer. Let poll re-use it.
|
||||
t.sendBufferConsumed()
|
||||
}
|
||||
pkt := res.data
|
||||
n := copy(buf[offset:], pkt)
|
||||
// t.buffer has a fixed location in memory.
|
||||
// If the packet is not from t.buffer, then it is an injected packet.
|
||||
// &pkt[0] can be used because empty packets do not reach t.outbound.
|
||||
isInjectedPacket := &pkt[0] != &t.buffer[PacketStartOffset]
|
||||
if !isInjectedPacket {
|
||||
// We are done with t.buffer. Let poll re-use it.
|
||||
t.sendBufferConsumed()
|
||||
}
|
||||
|
||||
p := parsedPacketPool.Get().(*packet.Parsed)
|
||||
@@ -531,7 +516,7 @@ func (t *Wrapper) Read(buf []byte, offset int) (int, error) {
|
||||
}
|
||||
|
||||
// Do not filter injected packets.
|
||||
if !res.injected && !t.disableFilter {
|
||||
if !isInjectedPacket && !t.disableFilter {
|
||||
response := t.filterOut(p)
|
||||
if response != filter.Accept {
|
||||
metricPacketOutDrop.Add(1)
|
||||
@@ -756,24 +741,7 @@ func (t *Wrapper) InjectOutbound(packet []byte) error {
|
||||
if len(packet) == 0 {
|
||||
return nil
|
||||
}
|
||||
t.sendOutbound(tunReadResult{data: packet, injected: true})
|
||||
return nil
|
||||
}
|
||||
|
||||
// InjectOutboundPacketBuffer logically behaves as InjectOutbound. It takes ownership of one
|
||||
// reference count on the packet, and the packet may be mutated. The packet refcount will be
|
||||
// decremented after the injected buffer has been read.
|
||||
func (t *Wrapper) InjectOutboundPacketBuffer(packet *stack.PacketBuffer) error {
|
||||
size := packet.Size()
|
||||
if size > MaxPacketSize {
|
||||
packet.DecRef()
|
||||
return errPacketTooBig
|
||||
}
|
||||
if size == 0 {
|
||||
packet.DecRef()
|
||||
return nil
|
||||
}
|
||||
t.sendOutbound(tunReadResult{packet: packet, injected: true})
|
||||
t.sendOutbound(tunReadResult{data: packet})
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@ const maxHTTPBody = 4 << 20 // MiB
|
||||
|
||||
// HTTP returns a Probe that healthchecks an HTTP URL.
|
||||
//
|
||||
// The ProbeFunc sends a GET request for url, expects an HTTP 200
|
||||
// The Probe sends a GET request for url, expects an HTTP 200
|
||||
// response, and verifies that want is present in the response
|
||||
// body. If the URL is HTTPS, the probe further checks that the TLS
|
||||
// certificate is good for at least the next 7 days.
|
||||
func HTTP(url, wantText string) ProbeFunc {
|
||||
func HTTP(url, wantText string) Probe {
|
||||
return func(ctx context.Context) error {
|
||||
return probeHTTP(ctx, url, []byte(wantText))
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func probeHTTP(ctx context.Context, url string, want []byte) error {
|
||||
return fmt.Errorf("fetching %q: status code %d, want 200", url, resp.StatusCode)
|
||||
}
|
||||
|
||||
bs, err := io.ReadAll(io.LimitReader(resp.Body, maxHTTPBody))
|
||||
bs, err := io.ReadAll(&io.LimitedReader{resp.Body, maxHTTPBody})
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading body of %q: %w", url, err)
|
||||
}
|
||||
|
||||
335
prober/prober.go
335
prober/prober.go
@@ -9,22 +9,19 @@ package prober
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"expvar"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tailscale.com/metrics"
|
||||
)
|
||||
|
||||
// ProbeFunc is a function that probes something and reports whether
|
||||
// the probe succeeded. The provided context's deadline must be obeyed
|
||||
// for correct probe scheduling.
|
||||
type ProbeFunc func(context.Context) error
|
||||
// Probe is a function that probes something and reports whether the
|
||||
// probe succeeded. The provided context must be used to ensure timely
|
||||
// cancellation and timeout behavior.
|
||||
type Probe func(context.Context) error
|
||||
|
||||
// a Prober manages a set of probes and keeps track of their results.
|
||||
type Prober struct {
|
||||
@@ -32,8 +29,35 @@ type Prober struct {
|
||||
now func() time.Time
|
||||
newTicker func(time.Duration) ticker
|
||||
|
||||
mu sync.Mutex // protects all following fields
|
||||
probes map[string]*Probe
|
||||
// lastStart is the time, in seconds since epoch, of the last time
|
||||
// each probe started a probe cycle.
|
||||
lastStart metrics.LabelMap
|
||||
// lastEnd is the time, in seconds since epoch, of the last time
|
||||
// each probe finished a probe cycle.
|
||||
lastEnd metrics.LabelMap
|
||||
// lastResult records whether probes succeeded. A successful probe
|
||||
// is recorded as 1, a failure as 0.
|
||||
lastResult metrics.LabelMap
|
||||
// lastLatency records how long the last probe cycle took for each
|
||||
// probe, in milliseconds.
|
||||
lastLatency metrics.LabelMap
|
||||
// probeInterval records the time in seconds between successive
|
||||
// runs of each probe.
|
||||
//
|
||||
// This is to help Prometheus figure out how long a probe should
|
||||
// be failing before it fires an alert for it. To avoid random
|
||||
// background noise, you want it to wait for more than 1
|
||||
// datapoint, but you also can't use a fixed interval because some
|
||||
// probes might run every few seconds, while e.g. TLS certificate
|
||||
// expiry might only run once a day.
|
||||
//
|
||||
// So, for each probe, the prober tells Prometheus how often it
|
||||
// runs, so that the alert can autotune itself to eliminate noise
|
||||
// without being excessively delayed.
|
||||
probeInterval metrics.LabelMap
|
||||
|
||||
mu sync.Mutex // protects all following fields
|
||||
activeProbeCh map[string]chan struct{}
|
||||
}
|
||||
|
||||
// New returns a new Prober.
|
||||
@@ -43,113 +67,84 @@ func New() *Prober {
|
||||
|
||||
func newForTest(now func() time.Time, newTicker func(time.Duration) ticker) *Prober {
|
||||
return &Prober{
|
||||
now: now,
|
||||
newTicker: newTicker,
|
||||
probes: map[string]*Probe{},
|
||||
now: now,
|
||||
newTicker: newTicker,
|
||||
lastStart: metrics.LabelMap{Label: "probe"},
|
||||
lastEnd: metrics.LabelMap{Label: "probe"},
|
||||
lastResult: metrics.LabelMap{Label: "probe"},
|
||||
lastLatency: metrics.LabelMap{Label: "probe"},
|
||||
probeInterval: metrics.LabelMap{Label: "probe"},
|
||||
activeProbeCh: map[string]chan struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// Expvar returns the metrics for running probes.
|
||||
func (p *Prober) Expvar() expvar.Var {
|
||||
return varExporter{p}
|
||||
func (p *Prober) Expvar() *metrics.Set {
|
||||
ret := new(metrics.Set)
|
||||
ret.Set("start_secs", &p.lastStart)
|
||||
ret.Set("end_secs", &p.lastEnd)
|
||||
ret.Set("result", &p.lastResult)
|
||||
ret.Set("latency_millis", &p.lastLatency)
|
||||
ret.Set("interval_secs", &p.probeInterval)
|
||||
return ret
|
||||
}
|
||||
|
||||
// Run executes fun every interval, and exports probe results under probeName.
|
||||
//
|
||||
// fun is given a context.Context that, if obeyed, ensures that fun
|
||||
// ends within interval. If fun disregards the context, it will not be
|
||||
// run again until it does finish, and metrics will reflect that the
|
||||
// probe function is stuck.
|
||||
//
|
||||
// Run returns a context.CancelFunc that stops the probe when
|
||||
// invoked. Probe shutdown and removal happens-before the CancelFunc
|
||||
// returns.
|
||||
//
|
||||
// Registering a probe under an already-registered name panics.
|
||||
func (p *Prober) Run(name string, interval time.Duration, labels map[string]string, fun ProbeFunc) *Probe {
|
||||
func (p *Prober) Run(name string, interval time.Duration, fun Probe) context.CancelFunc {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if _, ok := p.probes[name]; ok {
|
||||
panic(fmt.Sprintf("probe named %q already registered", name))
|
||||
}
|
||||
ticker := p.registerLocked(name, interval)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ticker := p.newTicker(interval)
|
||||
probe := &Probe{
|
||||
prober: p,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
stopped: make(chan struct{}),
|
||||
go p.probeLoop(ctx, name, interval, ticker, fun)
|
||||
|
||||
name: name,
|
||||
doProbe: fun,
|
||||
interval: interval,
|
||||
tick: ticker,
|
||||
labels: labels,
|
||||
return func() {
|
||||
p.mu.Lock()
|
||||
stopped := p.activeProbeCh[name]
|
||||
p.mu.Unlock()
|
||||
cancel()
|
||||
<-stopped
|
||||
}
|
||||
p.probes[name] = probe
|
||||
go probe.loop()
|
||||
return probe
|
||||
}
|
||||
|
||||
func (p *Prober) unregister(probe *Probe) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
name := probe.name
|
||||
delete(p.probes, name)
|
||||
}
|
||||
|
||||
// Reports the number of registered probes. For tests only.
|
||||
func (p *Prober) activeProbes() int {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return len(p.probes)
|
||||
}
|
||||
|
||||
// Probe is a probe that healthchecks something and updates Prometheus
|
||||
// metrics with the results.
|
||||
type Probe struct {
|
||||
prober *Prober
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc // run to initiate shutdown
|
||||
stopped chan struct{} // closed when shutdown is complete
|
||||
|
||||
name string
|
||||
doProbe ProbeFunc
|
||||
interval time.Duration
|
||||
tick ticker
|
||||
labels map[string]string
|
||||
|
||||
mu sync.Mutex
|
||||
start time.Time // last time doProbe started
|
||||
end time.Time // last time doProbe returned
|
||||
result bool // whether the last doProbe call succeeded
|
||||
}
|
||||
|
||||
// Close shuts down the Probe and unregisters it from its Prober.
|
||||
// It is safe to Run a new probe of the same name after Close returns.
|
||||
func (p *Probe) Close() error {
|
||||
p.cancel()
|
||||
<-p.stopped
|
||||
p.prober.unregister(p)
|
||||
return nil
|
||||
}
|
||||
|
||||
// probeLoop invokes runProbe on fun every interval. The first probe
|
||||
// is run after interval.
|
||||
func (p *Probe) loop() {
|
||||
defer close(p.stopped)
|
||||
func (p *Prober) probeLoop(ctx context.Context, name string, interval time.Duration, tick ticker, fun Probe) {
|
||||
defer func() {
|
||||
p.unregister(name)
|
||||
tick.Stop()
|
||||
}()
|
||||
|
||||
// Do a first probe right away, so that the prober immediately exports results for everything.
|
||||
p.run()
|
||||
p.runProbe(ctx, name, interval, fun)
|
||||
for {
|
||||
select {
|
||||
case <-p.tick.Chan():
|
||||
p.run()
|
||||
case <-p.ctx.Done():
|
||||
case <-tick.Chan():
|
||||
p.runProbe(ctx, name, interval, fun)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// run invokes fun and records the results.
|
||||
// runProbe invokes fun and records the results.
|
||||
//
|
||||
// fun is invoked with a timeout slightly less than interval, so that
|
||||
// the probe either succeeds or fails before the next cycle is
|
||||
// scheduled to start.
|
||||
func (p *Probe) run() {
|
||||
start := p.recordStart()
|
||||
func (p *Prober) runProbe(ctx context.Context, name string, interval time.Duration, fun Probe) {
|
||||
start := p.start(name)
|
||||
defer func() {
|
||||
// Prevent a panic within one probe function from killing the
|
||||
// entire prober, so that a single buggy probe doesn't destroy
|
||||
@@ -157,140 +152,70 @@ func (p *Probe) run() {
|
||||
// as a probe failure, so panicking probes will trigger an
|
||||
// alert for debugging.
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("probe %s panicked: %v", p.name, r)
|
||||
p.recordEnd(start, errors.New("panic"))
|
||||
log.Printf("probe %s panicked: %v", name, r)
|
||||
p.end(name, start, errors.New("panic"))
|
||||
}
|
||||
}()
|
||||
timeout := time.Duration(float64(p.interval) * 0.8)
|
||||
ctx, cancel := context.WithTimeout(p.ctx, timeout)
|
||||
timeout := time.Duration(float64(interval) * 0.8)
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
err := p.doProbe(ctx)
|
||||
p.recordEnd(start, err)
|
||||
err := fun(ctx)
|
||||
p.end(name, start, err)
|
||||
if err != nil {
|
||||
log.Printf("probe %s: %v", p.name, err)
|
||||
log.Printf("probe %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Probe) recordStart() time.Time {
|
||||
st := p.prober.now()
|
||||
func (p *Prober) registerLocked(name string, interval time.Duration) ticker {
|
||||
if _, ok := p.activeProbeCh[name]; ok {
|
||||
panic(fmt.Sprintf("probe named %q already registered", name))
|
||||
}
|
||||
|
||||
stoppedCh := make(chan struct{})
|
||||
p.activeProbeCh[name] = stoppedCh
|
||||
p.probeInterval.Get(name).Set(int64(interval.Seconds()))
|
||||
// Create and return a ticker from here, while Prober is
|
||||
// locked. This ensures that our fake time in tests always sees
|
||||
// the new fake ticker being created before seeing that a new
|
||||
// probe is registered.
|
||||
return p.newTicker(interval)
|
||||
}
|
||||
|
||||
func (p *Prober) unregister(name string) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.start = st
|
||||
close(p.activeProbeCh[name])
|
||||
delete(p.activeProbeCh, name)
|
||||
p.lastStart.Delete(name)
|
||||
p.lastEnd.Delete(name)
|
||||
p.lastResult.Delete(name)
|
||||
p.lastLatency.Delete(name)
|
||||
p.probeInterval.Delete(name)
|
||||
}
|
||||
|
||||
func (p *Prober) start(name string) time.Time {
|
||||
st := p.now()
|
||||
p.lastStart.Get(name).Set(st.Unix())
|
||||
return st
|
||||
}
|
||||
|
||||
func (p *Probe) recordEnd(start time.Time, err error) {
|
||||
end := p.prober.now()
|
||||
func (p *Prober) end(name string, start time.Time, err error) {
|
||||
end := p.now()
|
||||
p.lastEnd.Get(name).Set(end.Unix())
|
||||
p.lastLatency.Get(name).Set(end.Sub(start).Milliseconds())
|
||||
v := int64(1)
|
||||
if err != nil {
|
||||
v = 0
|
||||
}
|
||||
p.lastResult.Get(name).Set(v)
|
||||
}
|
||||
|
||||
// Reports the number of registered probes. For tests only.
|
||||
func (p *Prober) activeProbes() int {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.end = end
|
||||
p.result = err == nil
|
||||
}
|
||||
|
||||
type varExporter struct {
|
||||
p *Prober
|
||||
}
|
||||
|
||||
// probeInfo is the state of a Probe. Used in expvar-format debug
|
||||
// data.
|
||||
type probeInfo struct {
|
||||
Labels map[string]string
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Latency string // as a string because time.Duration doesn't encode readably to JSON
|
||||
Result bool
|
||||
}
|
||||
|
||||
// String implements expvar.Var, returning the prober's state as an
|
||||
// encoded JSON map of probe name to its probeInfo.
|
||||
func (v varExporter) String() string {
|
||||
out := map[string]probeInfo{}
|
||||
|
||||
v.p.mu.Lock()
|
||||
probes := make([]*Probe, 0, len(v.p.probes))
|
||||
for _, probe := range v.p.probes {
|
||||
probes = append(probes, probe)
|
||||
}
|
||||
v.p.mu.Unlock()
|
||||
|
||||
for _, probe := range probes {
|
||||
probe.mu.Lock()
|
||||
inf := probeInfo{
|
||||
Labels: probe.labels,
|
||||
Start: probe.start,
|
||||
End: probe.end,
|
||||
Result: probe.result,
|
||||
}
|
||||
if probe.end.After(probe.start) {
|
||||
inf.Latency = probe.end.Sub(probe.start).String()
|
||||
}
|
||||
out[probe.name] = inf
|
||||
probe.mu.Unlock()
|
||||
}
|
||||
|
||||
bs, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
return fmt.Sprintf(`{"error": %q}`, err)
|
||||
}
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
// WritePrometheus writes the the state of all probes to w.
|
||||
//
|
||||
// For each probe, WritePrometheus exports 5 variables:
|
||||
// - <prefix>_interval_secs, how frequently the probe runs.
|
||||
// - <prefix>_start_secs, when the probe last started running, in seconds since epoch.
|
||||
// - <prefix>_end_secs, when the probe last finished running, in seconds since epoch.
|
||||
// - <prefix>_latency_millis, how long the last probe cycle took, in
|
||||
// milliseconds. This is just (end_secs-start_secs) in an easier to
|
||||
// graph form.
|
||||
// - <prefix>_result, 1 if the last probe succeeded, 0 if it failed.
|
||||
//
|
||||
// Each probe has a set of static key/value labels (defined once at
|
||||
// probe creation), which are added as Prometheus metric labels to
|
||||
// that probe's variables.
|
||||
func (v varExporter) WritePrometheus(w io.Writer, prefix string) {
|
||||
v.p.mu.Lock()
|
||||
probes := make([]*Probe, 0, len(v.p.probes))
|
||||
for _, probe := range v.p.probes {
|
||||
probes = append(probes, probe)
|
||||
}
|
||||
v.p.mu.Unlock()
|
||||
|
||||
sort.Slice(probes, func(i, j int) bool {
|
||||
return probes[i].name < probes[j].name
|
||||
})
|
||||
for _, probe := range probes {
|
||||
probe.mu.Lock()
|
||||
keys := make([]string, 0, len(probe.labels))
|
||||
for k := range probe.labels {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
var sb strings.Builder
|
||||
fmt.Fprintf(&sb, "name=%q", probe.name)
|
||||
for _, k := range keys {
|
||||
fmt.Fprintf(&sb, ",%s=%q", k, probe.labels[k])
|
||||
}
|
||||
labels := sb.String()
|
||||
|
||||
fmt.Fprintf(w, "%s_interval_secs{%s} %f\n", prefix, labels, probe.interval.Seconds())
|
||||
if !probe.start.IsZero() {
|
||||
fmt.Fprintf(w, "%s_start_secs{%s} %d\n", prefix, labels, probe.start.Unix())
|
||||
}
|
||||
if !probe.end.IsZero() {
|
||||
fmt.Fprintf(w, "%s_end_secs{%s} %d\n", prefix, labels, probe.end.Unix())
|
||||
// Start is always present if end is.
|
||||
fmt.Fprintf(w, "%s_latency_millis{%s} %d\n", prefix, labels, probe.end.Sub(probe.start).Milliseconds())
|
||||
if probe.result {
|
||||
fmt.Fprintf(w, "%s_result{%s} 1\n", prefix, labels)
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s_result{%s} 0\n", prefix, labels)
|
||||
}
|
||||
}
|
||||
probe.mu.Unlock()
|
||||
}
|
||||
return len(p.activeProbeCh)
|
||||
}
|
||||
|
||||
// ticker wraps a time.Ticker in a way that can be faked for tests.
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
package prober
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -15,10 +14,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/tsweb"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -27,7 +24,6 @@ const (
|
||||
quarterProbeInterval = probeInterval / 4
|
||||
convergenceTimeout = time.Second
|
||||
convergenceSleep = time.Millisecond
|
||||
aFewMillis = 20 * time.Millisecond
|
||||
)
|
||||
|
||||
var epoch = time.Unix(0, 0)
|
||||
@@ -55,7 +51,7 @@ func TestProberTiming(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
p.Run("test-probe", probeInterval, nil, func(context.Context) error {
|
||||
p.Run("test-probe", probeInterval, func(context.Context) error {
|
||||
invoked <- struct{}{}
|
||||
return nil
|
||||
})
|
||||
@@ -84,10 +80,10 @@ func TestProberRun(t *testing.T) {
|
||||
)
|
||||
|
||||
const startingProbes = 100
|
||||
var probes []*Probe
|
||||
cancels := []context.CancelFunc{}
|
||||
|
||||
for i := 0; i < startingProbes; i++ {
|
||||
probes = append(probes, p.Run(fmt.Sprintf("probe%d", i), probeInterval, nil, func(context.Context) error {
|
||||
cancels = append(cancels, p.Run(fmt.Sprintf("probe%d", i), probeInterval, func(context.Context) error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
cnt++
|
||||
@@ -96,7 +92,6 @@ func TestProberRun(t *testing.T) {
|
||||
}
|
||||
|
||||
checkCnt := func(want int) {
|
||||
t.Helper()
|
||||
err := tstest.WaitFor(convergenceTimeout, func() error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
@@ -119,7 +114,7 @@ func TestProberRun(t *testing.T) {
|
||||
keep := startingProbes / 2
|
||||
|
||||
for i := keep; i < startingProbes; i++ {
|
||||
probes[i].Close()
|
||||
cancels[i]()
|
||||
}
|
||||
waitActiveProbes(t, p, keep)
|
||||
|
||||
@@ -131,8 +126,9 @@ func TestExpvar(t *testing.T) {
|
||||
clk := newFakeTime()
|
||||
p := newForTest(clk.Now, clk.NewTicker)
|
||||
|
||||
const aFewMillis = 20 * time.Millisecond
|
||||
var succeed syncs.AtomicBool
|
||||
p.Run("probe", probeInterval, map[string]string{"label": "value"}, func(context.Context) error {
|
||||
p.Run("probe", probeInterval, func(context.Context) error {
|
||||
clk.Advance(aFewMillis)
|
||||
if succeed.Get() {
|
||||
return nil
|
||||
@@ -142,106 +138,20 @@ func TestExpvar(t *testing.T) {
|
||||
|
||||
waitActiveProbes(t, p, 1)
|
||||
|
||||
check := func(name string, want probeInfo) {
|
||||
t.Helper()
|
||||
err := tstest.WaitFor(convergenceTimeout, func() error {
|
||||
vars := probeExpvar(t, p)
|
||||
if got, want := len(vars), 1; got != want {
|
||||
return fmt.Errorf("wrong probe count in expvar, got %d want %d", got, want)
|
||||
}
|
||||
for k, v := range vars {
|
||||
if k != name {
|
||||
return fmt.Errorf("wrong probe name in expvar, got %q want %q", k, name)
|
||||
}
|
||||
if diff := cmp.Diff(v, &want); diff != "" {
|
||||
return fmt.Errorf("wrong probe stats (-got+want):\n%s", diff)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
check("probe", probeInfo{
|
||||
Labels: map[string]string{"label": "value"},
|
||||
Start: epoch,
|
||||
End: epoch.Add(aFewMillis),
|
||||
Latency: aFewMillis.String(),
|
||||
Result: false,
|
||||
})
|
||||
waitExpInt(t, p, "start_secs/probe", 0)
|
||||
waitExpInt(t, p, "end_secs/probe", 0)
|
||||
waitExpInt(t, p, "interval_secs/probe", int(probeInterval.Seconds()))
|
||||
waitExpInt(t, p, "latency_millis/probe", int(aFewMillis.Milliseconds()))
|
||||
waitExpInt(t, p, "result/probe", 0)
|
||||
|
||||
succeed.Set(true)
|
||||
clk.Advance(probeInterval + halfProbeInterval)
|
||||
|
||||
st := epoch.Add(probeInterval + halfProbeInterval + aFewMillis)
|
||||
check("probe", probeInfo{
|
||||
Labels: map[string]string{"label": "value"},
|
||||
Start: st,
|
||||
End: st.Add(aFewMillis),
|
||||
Latency: aFewMillis.String(),
|
||||
Result: true,
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrometheus(t *testing.T) {
|
||||
clk := newFakeTime()
|
||||
p := newForTest(clk.Now, clk.NewTicker)
|
||||
|
||||
var succeed syncs.AtomicBool
|
||||
p.Run("testprobe", probeInterval, map[string]string{"label": "value"}, func(context.Context) error {
|
||||
clk.Advance(aFewMillis)
|
||||
if succeed.Get() {
|
||||
return nil
|
||||
}
|
||||
return errors.New("failing, as instructed by test")
|
||||
})
|
||||
|
||||
waitActiveProbes(t, p, 1)
|
||||
|
||||
err := tstest.WaitFor(convergenceTimeout, func() error {
|
||||
var b bytes.Buffer
|
||||
p.Expvar().(tsweb.PrometheusVar).WritePrometheus(&b, "probe")
|
||||
want := strings.TrimSpace(fmt.Sprintf(`
|
||||
probe_interval_secs{name="testprobe",label="value"} %f
|
||||
probe_start_secs{name="testprobe",label="value"} %d
|
||||
probe_end_secs{name="testprobe",label="value"} %d
|
||||
probe_latency_millis{name="testprobe",label="value"} %d
|
||||
probe_result{name="testprobe",label="value"} 0
|
||||
`, probeInterval.Seconds(), epoch.Unix(), epoch.Add(aFewMillis).Unix(), aFewMillis.Milliseconds()))
|
||||
if diff := cmp.Diff(strings.TrimSpace(b.String()), want); diff != "" {
|
||||
return fmt.Errorf("wrong probe stats (-got+want):\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
succeed.Set(true)
|
||||
clk.Advance(probeInterval + halfProbeInterval)
|
||||
|
||||
err = tstest.WaitFor(convergenceTimeout, func() error {
|
||||
var b bytes.Buffer
|
||||
p.Expvar().(tsweb.PrometheusVar).WritePrometheus(&b, "probe")
|
||||
start := epoch.Add(probeInterval + halfProbeInterval)
|
||||
end := start.Add(aFewMillis)
|
||||
want := strings.TrimSpace(fmt.Sprintf(`
|
||||
probe_interval_secs{name="testprobe",label="value"} %f
|
||||
probe_start_secs{name="testprobe",label="value"} %d
|
||||
probe_end_secs{name="testprobe",label="value"} %d
|
||||
probe_latency_millis{name="testprobe",label="value"} %d
|
||||
probe_result{name="testprobe",label="value"} 1
|
||||
`, probeInterval.Seconds(), start.Unix(), end.Unix(), aFewMillis.Milliseconds()))
|
||||
if diff := cmp.Diff(strings.TrimSpace(b.String()), want); diff != "" {
|
||||
return fmt.Errorf("wrong probe stats (-got+want):\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
waitExpInt(t, p, "start_secs/probe", int((probeInterval + halfProbeInterval).Seconds()))
|
||||
waitExpInt(t, p, "end_secs/probe", int((probeInterval + halfProbeInterval).Seconds()))
|
||||
waitExpInt(t, p, "interval_secs/probe", int(probeInterval.Seconds()))
|
||||
waitExpInt(t, p, "latency_millis/probe", int(aFewMillis.Milliseconds()))
|
||||
waitExpInt(t, p, "result/probe", 1)
|
||||
}
|
||||
|
||||
type fakeTicker struct {
|
||||
@@ -275,9 +185,7 @@ func (t *fakeTicker) fire(now time.Time) {
|
||||
case t.ch <- now:
|
||||
default:
|
||||
}
|
||||
for now.After(t.next) {
|
||||
t.next = t.next.Add(t.interval)
|
||||
}
|
||||
t.next = now.Add(t.interval)
|
||||
}
|
||||
|
||||
type fakeTime struct {
|
||||
@@ -292,6 +200,7 @@ func newFakeTime() *fakeTime {
|
||||
curTime: epoch,
|
||||
}
|
||||
ret.Cond = &sync.Cond{L: &ret.Mutex}
|
||||
ret.Advance(time.Duration(1)) // so that Now never IsZero
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -299,6 +208,8 @@ func (t *fakeTime) Now() time.Time {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
ret := t.curTime
|
||||
// so that time always seems to advance for the program under test
|
||||
t.curTime = t.curTime.Add(time.Microsecond)
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -326,14 +237,47 @@ func (t *fakeTime) Advance(d time.Duration) {
|
||||
}
|
||||
}
|
||||
|
||||
func probeExpvar(t *testing.T, p *Prober) map[string]*probeInfo {
|
||||
func waitExpInt(t *testing.T, p *Prober, path string, want int) {
|
||||
t.Helper()
|
||||
err := tstest.WaitFor(convergenceTimeout, func() error {
|
||||
got, ok := getExpInt(t, p, path)
|
||||
if !ok {
|
||||
return fmt.Errorf("expvar %q did not get set", path)
|
||||
}
|
||||
if got != want {
|
||||
return fmt.Errorf("expvar %q is %d, want %d", path, got, want)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func getExpInt(t *testing.T, p *Prober, path string) (ret int, ok bool) {
|
||||
t.Helper()
|
||||
s := p.Expvar().String()
|
||||
ret := map[string]*probeInfo{}
|
||||
if err := json.Unmarshal([]byte(s), &ret); err != nil {
|
||||
t.Fatalf("expvar json decode failed: %v", err)
|
||||
dec := map[string]interface{}{}
|
||||
if err := json.Unmarshal([]byte(s), &dec); err != nil {
|
||||
t.Fatalf("couldn't unmarshal expvar data: %v", err)
|
||||
}
|
||||
return ret
|
||||
var v interface{} = dec
|
||||
for _, d := range strings.Split(path, "/") {
|
||||
m, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("expvar path %q ended early with a leaf value", path)
|
||||
}
|
||||
child, ok := m[d]
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
v = child
|
||||
}
|
||||
f, ok := v.(float64)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return int(f), true
|
||||
}
|
||||
|
||||
func waitActiveProbes(t *testing.T, p *Prober, want int) {
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
|
||||
// TCP returns a Probe that healthchecks a TCP endpoint.
|
||||
//
|
||||
// The ProbeFunc reports whether it can successfully connect to addr.
|
||||
func TCP(addr string) ProbeFunc {
|
||||
// The Probe reports whether it can successfully connect to addr.
|
||||
func TCP(addr string) Probe {
|
||||
return func(ctx context.Context) error {
|
||||
return probeTCP(ctx, addr)
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@ import (
|
||||
|
||||
// TLS returns a Probe that healthchecks a TLS endpoint.
|
||||
//
|
||||
// The ProbeFunc connects to hostname, does a TLS handshake, verifies
|
||||
// that the hostname matches the presented certificate, and that the
|
||||
// The Probe connects to hostname, does a TLS handshake, verifies that
|
||||
// the hostname matches the presented certificate, and that the
|
||||
// certificate expires in more than 7 days from the probe time.
|
||||
func TLS(hostname string) ProbeFunc {
|
||||
func TLS(hostname string) Probe {
|
||||
return func(ctx context.Context) error {
|
||||
return probeTLS(ctx, hostname)
|
||||
}
|
||||
|
||||
51
shell.nix
51
shell.nix
@@ -1,4 +1,3 @@
|
||||
# TODO(tom): Use system nixpkgs exclusively once 1.18 is in a stable release.
|
||||
# This is a shell.nix file used to describe the environment that tailscale needs
|
||||
# for development. This includes a lot of the basic tools that you need in order
|
||||
# to get started. We hope this file will be useful for users of Nix on macOS or
|
||||
@@ -10,42 +9,16 @@
|
||||
# Also look into direnv: https://direnv.net/, this can make it so that you can
|
||||
# automatically get your environment set up when you change folders into the
|
||||
# project.
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
|
||||
{
|
||||
pkgs ? import <nixpkgs> {},
|
||||
nixosUnstable ? import (fetchTarball https://github.com/NixOS/nixpkgs/archive/refs/heads/nixpkgs-unstable.tar.gz) { },
|
||||
tailscale-go-rev ? "5ce3ec4d89c72f2a2b6f6f5089c950d7a6a33530",
|
||||
tailscale-go-sha ? "sha256-KMOfzmikh30vEkViEkWUsOHczUifSTiRL6rhKQpHCRI=",
|
||||
}:
|
||||
let
|
||||
tailscale-go = pkgs.lib.overrideDerivation nixosUnstable.go_1_18 (attrs: rec {
|
||||
name = "tailscale-go-${version}";
|
||||
version = tailscale-go-rev;
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "tailscale";
|
||||
repo = "go";
|
||||
rev = tailscale-go-rev;
|
||||
sha256 = tailscale-go-sha;
|
||||
};
|
||||
nativeBuildInputs = attrs.nativeBuildInputs ++ [ pkgs.git ];
|
||||
# Remove dependency on xcbuild as that causes iOS/macOS builds to fail.
|
||||
propagatedBuildInputs = [];
|
||||
checkPhase = "";
|
||||
# Our forked tailscale reads this env var to embed the git hash
|
||||
# into the Go build version.
|
||||
TAILSCALE_TOOLCHAIN_REV = tailscale-go-rev;
|
||||
});
|
||||
in
|
||||
pkgs.mkShell {
|
||||
# This specifies the tools that are needed for people to get started with
|
||||
# development. These tools include:
|
||||
# - The Go compiler toolchain (and all additional tooling with it)
|
||||
# - gotools for goimports, a robust formatting tool for Go source code
|
||||
# - gopls, the language server for Go to increase editor integration
|
||||
# - git, the version control program (used in some scripts)
|
||||
buildInputs = [
|
||||
pkgs.git
|
||||
nixosUnstable.gotools nixosUnstable.gopls
|
||||
tailscale-go
|
||||
];
|
||||
}
|
||||
pkgs.mkShell {
|
||||
# This specifies the tools that are needed for people to get started with
|
||||
# development. These tools include:
|
||||
# - The Go compiler toolchain (and all additional tooling with it)
|
||||
# - goimports, a robust formatting tool for Go source code
|
||||
# - gopls, the language server for Go to increase editor integration
|
||||
# - git, the version control program (used in some scripts)
|
||||
buildInputs = with pkgs; [
|
||||
go goimports gopls git
|
||||
];
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/creack/pty"
|
||||
"github.com/tailscale/ssh"
|
||||
"github.com/u-root/u-root/pkg/termios"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/cmd/tailscaled/childproc"
|
||||
"tailscale.com/tempfork/gliderlabs/ssh"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
@@ -29,14 +28,13 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
gossh "github.com/tailscale/golang-x-crypto/ssh"
|
||||
"github.com/tailscale/ssh"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
"tailscale.com/logtail/backoff"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tempfork/gliderlabs/ssh"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
@@ -76,10 +74,6 @@ func (srv *server) newSSHServer() (*ssh.Server, error) {
|
||||
},
|
||||
Version: "SSH-2.0-Tailscale",
|
||||
LocalPortForwardingCallback: srv.mayForwardLocalPortTo,
|
||||
NoClientAuthCallback: func(m gossh.ConnMetadata) (*gossh.Permissions, error) {
|
||||
srv.logf("SSH connection from %v for %q; client ver %q", m.RemoteAddr(), m.User(), m.ClientVersion())
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
for k, v := range ssh.DefaultRequestHandlers {
|
||||
ss.RequestHandlers[k] = v
|
||||
@@ -220,6 +214,39 @@ func (srv *server) handleSSH(s ssh.Session) {
|
||||
return
|
||||
}
|
||||
|
||||
// Loop processing/fetching Actions until one reaches a
|
||||
// terminal state (Accept, Reject, or invalid Action), or
|
||||
// until fetchSSHAction times out due to the context being
|
||||
// done (client disconnect) or its 30 minute timeout passes.
|
||||
// (Which is a long time for somebody to see login
|
||||
// instructions and go to a URL to do something.)
|
||||
ProcessAction:
|
||||
for {
|
||||
if action.Message != "" {
|
||||
io.WriteString(s.Stderr(), strings.Replace(action.Message, "\n", "\r\n", -1))
|
||||
}
|
||||
if action.Reject {
|
||||
logf("ssh: access denied for %q from %v", ci.uprof.LoginName, ci.src.IP())
|
||||
s.Exit(1)
|
||||
return
|
||||
}
|
||||
if action.Accept {
|
||||
break ProcessAction
|
||||
}
|
||||
url := action.HoldAndDelegate
|
||||
if url == "" {
|
||||
logf("ssh: access denied; SSHAction has neither Reject, Accept, or next step URL")
|
||||
s.Exit(1)
|
||||
return
|
||||
}
|
||||
action, err = srv.fetchSSHAction(s.Context(), url)
|
||||
if err != nil {
|
||||
logf("ssh: fetching SSAction from %s: %v", url, err)
|
||||
s.Exit(1)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
lu, err := user.Lookup(localUser)
|
||||
if err != nil {
|
||||
logf("ssh: user Lookup %q: %v", localUser, err)
|
||||
@@ -227,73 +254,10 @@ func (srv *server) handleSSH(s ssh.Session) {
|
||||
return
|
||||
}
|
||||
|
||||
ss := srv.newSSHSession(s, ci, lu)
|
||||
action, err = ss.resolveTerminalAction(action)
|
||||
if err != nil {
|
||||
logf("ssh: resolveTerminalAction: %v", err)
|
||||
io.WriteString(s.Stderr(), "Access denied: failed to resolve SSHAction.\n")
|
||||
s.Exit(1)
|
||||
return
|
||||
}
|
||||
if action.Reject || !action.Accept {
|
||||
logf("ssh: access denied for %q from %v", ci.uprof.LoginName, ci.src.IP())
|
||||
s.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
ss.action = action
|
||||
ss := srv.newSSHSession(s, ci, lu, action)
|
||||
ss.run()
|
||||
}
|
||||
|
||||
// resolveTerminalAction either returns action (if it's Accept or Reject) or else
|
||||
// loops, fetching new SSHActions from the control plane.
|
||||
//
|
||||
// Any action with a Message in the chain will be printed to ss.
|
||||
//
|
||||
// The returned SSHAction will be either Reject or Accept.
|
||||
func (ss *sshSession) resolveTerminalAction(action *tailcfg.SSHAction) (*tailcfg.SSHAction, error) {
|
||||
// Loop processing/fetching Actions until one reaches a
|
||||
// terminal state (Accept, Reject, or invalid Action), or
|
||||
// until fetchSSHAction times out due to the context being
|
||||
// done (client disconnect) or its 30 minute timeout passes.
|
||||
// (Which is a long time for somebody to see login
|
||||
// instructions and go to a URL to do something.)
|
||||
for {
|
||||
if action.Message != "" {
|
||||
io.WriteString(ss.Stderr(), strings.Replace(action.Message, "\n", "\r\n", -1))
|
||||
}
|
||||
if action.Accept || action.Reject {
|
||||
return action, nil
|
||||
}
|
||||
url := action.HoldAndDelegate
|
||||
if url == "" {
|
||||
return nil, errors.New("reached Action that lacked Accept, Reject, and HoldAndDelegate")
|
||||
}
|
||||
url = ss.expandDelegateURL(url)
|
||||
var err error
|
||||
action, err = ss.srv.fetchSSHAction(ss.Context(), url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching SSHAction from %s: %w", url, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *sshSession) expandDelegateURL(actionURL string) string {
|
||||
nm := ss.srv.lb.NetMap()
|
||||
var dstNodeID string
|
||||
if nm != nil {
|
||||
dstNodeID = fmt.Sprint(int64(nm.SelfNode.ID))
|
||||
}
|
||||
return strings.NewReplacer(
|
||||
"$SRC_NODE_IP", url.QueryEscape(ss.connInfo.src.IP().String()),
|
||||
"$SRC_NODE_ID", fmt.Sprint(int64(ss.connInfo.node.ID)),
|
||||
"$DST_NODE_IP", url.QueryEscape(ss.connInfo.dst.IP().String()),
|
||||
"$DST_NODE_ID", dstNodeID,
|
||||
"$SSH_USER", url.QueryEscape(ss.connInfo.sshUser),
|
||||
"$LOCAL_USER", url.QueryEscape(ss.localUser.Username),
|
||||
).Replace(actionURL)
|
||||
}
|
||||
|
||||
// sshSession is an accepted Tailscale SSH session.
|
||||
type sshSession struct {
|
||||
ssh.Session
|
||||
@@ -320,7 +284,7 @@ type sshSession struct {
|
||||
exitOnce sync.Once
|
||||
}
|
||||
|
||||
func (srv *server) newSSHSession(s ssh.Session, ci *sshConnInfo, lu *user.User) *sshSession {
|
||||
func (srv *server) newSSHSession(s ssh.Session, ci *sshConnInfo, lu *user.User, action *tailcfg.SSHAction) *sshSession {
|
||||
sharedID := fmt.Sprintf("%s-%02x", ci.now.UTC().Format("20060102T150405"), randBytes(5))
|
||||
return &sshSession{
|
||||
Session: s,
|
||||
@@ -328,6 +292,7 @@ func (srv *server) newSSHSession(s ssh.Session, ci *sshConnInfo, lu *user.User)
|
||||
sharedID: sharedID,
|
||||
ctx: newSSHContext(),
|
||||
srv: srv,
|
||||
action: action,
|
||||
localUser: lu,
|
||||
connInfo: ci,
|
||||
logf: logger.WithPrefix(srv.logf, "ssh-session("+sharedID+"): "),
|
||||
@@ -352,20 +317,12 @@ func (srv *server) fetchSSHAction(ctx context.Context, url string) (*tailcfg.SSH
|
||||
continue
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if len(body) > 1<<10 {
|
||||
body = body[:1<<10]
|
||||
}
|
||||
srv.logf("fetch of %v: %s, %s", url, res.Status, body)
|
||||
bo.BackOff(ctx, fmt.Errorf("unexpected status: %v", res.Status))
|
||||
continue
|
||||
}
|
||||
a := new(tailcfg.SSHAction)
|
||||
err = json.NewDecoder(res.Body).Decode(a)
|
||||
res.Body.Close()
|
||||
if err != nil {
|
||||
srv.logf("invalid next SSHAction JSON from %v: %v", url, err)
|
||||
if err := json.NewDecoder(res.Body).Decode(a); err != nil {
|
||||
bo.BackOff(ctx, err)
|
||||
continue
|
||||
}
|
||||
@@ -667,14 +624,10 @@ func matchRule(r *tailcfg.SSHRule, ci *sshConnInfo) (a *tailcfg.SSHAction, local
|
||||
}
|
||||
|
||||
func mapLocalUser(ruleSSHUsers map[string]string, reqSSHUser string) (localUser string) {
|
||||
v, ok := ruleSSHUsers[reqSSHUser]
|
||||
if !ok {
|
||||
v = ruleSSHUsers["*"]
|
||||
if v, ok := ruleSSHUsers[reqSSHUser]; ok {
|
||||
return v
|
||||
}
|
||||
if v == "=" {
|
||||
return reqSSHUser
|
||||
}
|
||||
return v
|
||||
return ruleSSHUsers["*"]
|
||||
}
|
||||
|
||||
func matchesPrincipal(ps []*tailcfg.SSHPrincipal, ci *sshConnInfo) bool {
|
||||
|
||||
@@ -19,12 +19,12 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/tailscale/ssh"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
"tailscale.com/ipn/store/mem"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tempfork/gliderlabs/ssh"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/cibuild"
|
||||
"tailscale.com/util/lineread"
|
||||
@@ -153,18 +153,6 @@ func TestMatchRule(t *testing.T) {
|
||||
ci: &sshConnInfo{uprof: &tailcfg.UserProfile{LoginName: "foo@bar.com"}},
|
||||
wantUser: "ubuntu",
|
||||
},
|
||||
{
|
||||
name: "ssh-user-equal",
|
||||
rule: &tailcfg.SSHRule{
|
||||
Action: someAction,
|
||||
Principals: []*tailcfg.SSHPrincipal{{Any: true}},
|
||||
SSHUsers: map[string]string{
|
||||
"*": "=",
|
||||
},
|
||||
},
|
||||
ci: &sshConnInfo{sshUser: "alice"},
|
||||
wantUser: "alice",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -224,8 +212,7 @@ func TestSSH(t *testing.T) {
|
||||
}
|
||||
|
||||
ss.Handler = func(s ssh.Session) {
|
||||
ss := srv.newSSHSession(s, ci, u)
|
||||
ss.action = &tailcfg.SSHAction{Accept: true}
|
||||
ss := srv.newSSHSession(s, ci, u, &tailcfg.SSHAction{Accept: true})
|
||||
ss.run()
|
||||
}
|
||||
|
||||
|
||||
@@ -65,9 +65,7 @@ type CapabilityVersion int
|
||||
// 26: 2022-01-12: (nothing, just bumping for 1.20.0)
|
||||
// 27: 2022-02-18: start of SSHPolicy being respected
|
||||
// 28: 2022-03-09: client can communicate over Noise.
|
||||
// 29: 2022-03-21: MapResponse.PopBrowserURL
|
||||
// 30: 2022-03-22: client can request id tokens.
|
||||
const CurrentCapabilityVersion CapabilityVersion = 30
|
||||
const CurrentCapabilityVersion CapabilityVersion = 28
|
||||
|
||||
type StableID string
|
||||
|
||||
@@ -453,14 +451,16 @@ type Service struct {
|
||||
// Because it contains pointers (slices), this type should not be used
|
||||
// as a value type.
|
||||
type Hostinfo struct {
|
||||
// TODO(crawshaw): mark all these fields ",omitempty" when all the
|
||||
// iOS apps are updated with the latest swift version of this struct.
|
||||
IPNVersion string `json:",omitempty"` // version of this code
|
||||
FrontendLogID string `json:",omitempty"` // logtail ID of frontend instance
|
||||
BackendLogID string `json:",omitempty"` // logtail ID of backend instance
|
||||
OS string `json:",omitempty"` // operating system the client runs on (a version.OS value)
|
||||
OS string // operating system the client runs on (a version.OS value)
|
||||
OSVersion string `json:",omitempty"` // operating system version, with optional distro prefix ("Debian 10.4", "Windows 10 Pro 10.0.19041")
|
||||
Package string `json:",omitempty"` // Tailscale package to disambiguate ("choco", "appstore", etc; "" for unknown)
|
||||
DeviceModel string `json:",omitempty"` // mobile phone model ("Pixel 3a", "iPhone12,3")
|
||||
Hostname string `json:",omitempty"` // name of the host the client runs on
|
||||
Hostname string // name of the host the client runs on
|
||||
ShieldsUp bool `json:",omitempty"` // indicates whether the host is blocking incoming connections
|
||||
ShareeNode bool `json:",omitempty"` // indicates this node exists in netmap because it's owned by a shared-to user
|
||||
GoArch string `json:",omitempty"` // the host's GOARCH value (of the running binary)
|
||||
@@ -1221,7 +1221,7 @@ type PingRequest struct {
|
||||
type MapResponse struct {
|
||||
// KeepAlive, if set, represents an empty message just to keep
|
||||
// the connection alive. When true, all other fields except
|
||||
// PingRequest, ControlTime, and PopBrowserURL are ignored.
|
||||
// PingRequest are ignored.
|
||||
KeepAlive bool `json:",omitempty"`
|
||||
|
||||
// PingRequest, if non-empty, is a request to the client to
|
||||
@@ -1231,11 +1231,6 @@ type MapResponse struct {
|
||||
// KeepAlive true or false).
|
||||
PingRequest *PingRequest `json:",omitempty"`
|
||||
|
||||
// PopBrowserURL, if non-empty, is a URL for the client to
|
||||
// open to complete an action. The client should dup suppress
|
||||
// identical URLs and only open it once for the same URL.
|
||||
PopBrowserURL string
|
||||
|
||||
// Networking
|
||||
|
||||
// Node describes the node making the map request.
|
||||
@@ -1377,10 +1372,6 @@ type Debug struct {
|
||||
// fixed port.
|
||||
RandomizeClientPort bool `json:",omitempty"`
|
||||
|
||||
// OneCGNATRoute controls whether the client should prefer to make one
|
||||
// big CGNAT /10 route rather than a /32 per peer.
|
||||
OneCGNATRoute opt.Bool `json:",omitempty"`
|
||||
|
||||
// DisableUPnP is whether the client will attempt to perform a UPnP portmapping.
|
||||
// By default, we want to enable it to see if it works on more clients.
|
||||
//
|
||||
@@ -1582,8 +1573,6 @@ type SSHRule struct {
|
||||
// actual user that's logged in.
|
||||
// If the map value is the empty string (for either the
|
||||
// requested SSH user or "*"), the rule doesn't match.
|
||||
// If the map value is "=", it means the ssh-user should map
|
||||
// directly to the local-user.
|
||||
// It may be nil if the Action is reject.
|
||||
SSHUsers map[string]string `json:"sshUsers"`
|
||||
|
||||
@@ -1638,15 +1627,6 @@ type SSHAction struct {
|
||||
// If the long poll breaks before returning a complete HTTP
|
||||
// response, it should be re-fetched as long as the SSH
|
||||
// session is open.
|
||||
//
|
||||
// The following variables in the URL are expanded by tailscaled:
|
||||
//
|
||||
// * $SRC_NODE_IP (URL escaped)
|
||||
// * $SRC_NODE_ID (Node.ID as int64 string)
|
||||
// * $DST_NODE_IP (URL escaped)
|
||||
// * $DST_NODE_ID (Node.ID as int64 string)
|
||||
// * $SSH_USER (URL escaped, ssh user requested)
|
||||
// * $LOCAL_USER (URL escaped, local user mapped)
|
||||
HoldAndDelegate string `json:"holdAndDelegate,omitempty"`
|
||||
|
||||
// AllowLocalPortForwarding, if true, allows accepted connections
|
||||
@@ -1677,42 +1657,3 @@ type OverTLSPublicKeyResponse struct {
|
||||
// control/controlbase and control/controlhttp)
|
||||
PublicKey key.MachinePublic `json:"publicKey"`
|
||||
}
|
||||
|
||||
// TokenRequest is a request to get an OIDC ID token for an audience.
|
||||
// The token can be presented to any resource provider which offers OIDC
|
||||
// Federation.
|
||||
//
|
||||
// It is JSON-encoded and sent over Noise to "/machine/id-token".
|
||||
type TokenRequest struct {
|
||||
// CapVersion is the client's current CapabilityVersion.
|
||||
CapVersion CapabilityVersion
|
||||
// NodeKey is the client's current node key.
|
||||
NodeKey key.NodePublic
|
||||
// Audience the token is being requested for.
|
||||
Audience string
|
||||
}
|
||||
|
||||
// TokenResponse is the response to a TokenRequest.
|
||||
type TokenResponse struct {
|
||||
// IDToken is a JWT encoding the following standard claims:
|
||||
//
|
||||
// `sub` | the MagicDNS name of the node
|
||||
// `aud` | Audience from the request
|
||||
// `exp` | Token expiry
|
||||
// `iat` | Token issuance time
|
||||
// `iss` | Issuer
|
||||
// `jti` | Random token identifier
|
||||
// `nbf` | Not before time
|
||||
//
|
||||
// It also encodes the following Tailscale specific claims:
|
||||
//
|
||||
// `key` | the node public key
|
||||
// `addresses` | the Tailscale IPs of the node
|
||||
// `nid` | the node ID
|
||||
// `node` | the name of the node
|
||||
// `domain` | the domain of the node, it has the same format as MapResponse.Domain.
|
||||
// `tags` | an array of <domain:tag> on the node (like alice.github:tag:foo or example.com:tag:foo)
|
||||
// `user` | user emailish (like alice.github:alice@github or example.com:bob@example.com), if not tagged
|
||||
// `uid` | user ID, if not tagged
|
||||
IDToken string `json:"id_token"`
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
Copyright (c) 2016 Glider Labs. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Glider Labs nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,96 +0,0 @@
|
||||
# gliderlabs/ssh
|
||||
|
||||
[](https://godoc.org/github.com/gliderlabs/ssh)
|
||||
[](https://circleci.com/gh/gliderlabs/ssh)
|
||||
[](https://goreportcard.com/report/github.com/gliderlabs/ssh)
|
||||
[](#sponsors)
|
||||
[](http://slack.gliderlabs.com)
|
||||
[](https://app.convertkit.com/landing_pages/243312)
|
||||
|
||||
> The Glider Labs SSH server package is dope. —[@bradfitz](https://twitter.com/bradfitz), Go team member
|
||||
|
||||
This Go package wraps the [crypto/ssh
|
||||
package](https://godoc.org/golang.org/x/crypto/ssh) with a higher-level API for
|
||||
building SSH servers. The goal of the API was to make it as simple as using
|
||||
[net/http](https://golang.org/pkg/net/http/), so the API is very similar:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"tailscale.com/tempfork/gliderlabs/ssh"
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ssh.Handle(func(s ssh.Session) {
|
||||
io.WriteString(s, "Hello world\n")
|
||||
})
|
||||
|
||||
log.Fatal(ssh.ListenAndServe(":2222", nil))
|
||||
}
|
||||
|
||||
```
|
||||
This package was built by [@progrium](https://twitter.com/progrium) after working on nearly a dozen projects at Glider Labs using SSH and collaborating with [@shazow](https://twitter.com/shazow) (known for [ssh-chat](https://github.com/shazow/ssh-chat)).
|
||||
|
||||
## Examples
|
||||
|
||||
A bunch of great examples are in the `_examples` directory.
|
||||
|
||||
## Usage
|
||||
|
||||
[See GoDoc reference.](https://godoc.org/tailscale.com/tempfork/gliderlabs/ssh)
|
||||
|
||||
## Contributing
|
||||
|
||||
Pull requests are welcome! However, since this project is very much about API
|
||||
design, please submit API changes as issues to discuss before submitting PRs.
|
||||
|
||||
Also, you can [join our Slack](http://slack.gliderlabs.com) to discuss as well.
|
||||
|
||||
## Roadmap
|
||||
|
||||
* Non-session channel handlers
|
||||
* Cleanup callback API
|
||||
* 1.0 release
|
||||
* High-level client?
|
||||
|
||||
## Sponsors
|
||||
|
||||
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/ssh#sponsor)]
|
||||
|
||||
<a href="https://opencollective.com/ssh/sponsor/0/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/1/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/2/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/3/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/4/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/5/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/6/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/7/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/8/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/9/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/10/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/11/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/12/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/13/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/14/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/15/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/16/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/17/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/18/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/19/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/20/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/21/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/22/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/23/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/24/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/25/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/26/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/27/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/28/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/ssh/sponsor/29/website" target="_blank"><img src="https://opencollective.com/ssh/sponsor/29/avatar.svg"></a>
|
||||
|
||||
## License
|
||||
|
||||
[BSD](LICENSE)
|
||||
@@ -1,83 +0,0 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
gossh "github.com/tailscale/golang-x-crypto/ssh"
|
||||
)
|
||||
|
||||
const (
|
||||
agentRequestType = "auth-agent-req@openssh.com"
|
||||
agentChannelType = "auth-agent@openssh.com"
|
||||
|
||||
agentTempDir = "auth-agent"
|
||||
agentListenFile = "listener.sock"
|
||||
)
|
||||
|
||||
// contextKeyAgentRequest is an internal context key for storing if the
|
||||
// client requested agent forwarding
|
||||
var contextKeyAgentRequest = &contextKey{"auth-agent-req"}
|
||||
|
||||
// SetAgentRequested sets up the session context so that AgentRequested
|
||||
// returns true.
|
||||
func SetAgentRequested(ctx Context) {
|
||||
ctx.SetValue(contextKeyAgentRequest, true)
|
||||
}
|
||||
|
||||
// AgentRequested returns true if the client requested agent forwarding.
|
||||
func AgentRequested(sess Session) bool {
|
||||
return sess.Context().Value(contextKeyAgentRequest) == true
|
||||
}
|
||||
|
||||
// NewAgentListener sets up a temporary Unix socket that can be communicated
|
||||
// to the session environment and used for forwarding connections.
|
||||
func NewAgentListener() (net.Listener, error) {
|
||||
dir, err := ioutil.TempDir("", agentTempDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l, err := net.Listen("unix", path.Join(dir, agentListenFile))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// ForwardAgentConnections takes connections from a listener to proxy into the
|
||||
// session on the OpenSSH channel for agent connections. It blocks and services
|
||||
// connections until the listener stop accepting.
|
||||
func ForwardAgentConnections(l net.Listener, s Session) {
|
||||
sshConn := s.Context().Value(ContextKeyConn).(gossh.Conn)
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
channel, reqs, err := sshConn.OpenChannel(agentChannelType, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer channel.Close()
|
||||
go gossh.DiscardRequests(reqs)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
io.Copy(conn, channel)
|
||||
conn.(*net.UnixConn).CloseWrite()
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
io.Copy(channel, conn)
|
||||
channel.CloseWrite()
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
}(conn)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user