mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-29 15:23:45 +00:00
client/web: deprecate gorilla/csrf
Replace gorilla/csrf with a handler that requires the Sec-Fetch-Site header to be set to same-origin preventing CSRF attacks. Ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site Ref: https://caniuse.com/mdn-http_headers_sec-fetch-site Browser support should be now sufficiently broad to minimize false-positive rejections. Updates corp#25340 Signed-off-by: Patrick O'Doherty <patrick@tailscale.com>
This commit is contained in:
parent
dbf13976d3
commit
3d46418020
@ -6,7 +6,6 @@ package web
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -14,14 +13,11 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/csrf"
|
|
||||||
"tailscale.com/client/local"
|
"tailscale.com/client/local"
|
||||||
"tailscale.com/client/tailscale/apitype"
|
"tailscale.com/client/tailscale/apitype"
|
||||||
"tailscale.com/clientupdate"
|
"tailscale.com/clientupdate"
|
||||||
@ -205,7 +201,7 @@ func NewServer(opts ServerOpts) (s *Server, err error) {
|
|||||||
|
|
||||||
var metric string
|
var metric string
|
||||||
s.apiHandler, metric = s.modeAPIHandler(s.mode)
|
s.apiHandler, metric = s.modeAPIHandler(s.mode)
|
||||||
s.apiHandler = s.withCSRF(s.apiHandler)
|
s.apiHandler = s.withSecFetchSite(s.apiHandler)
|
||||||
|
|
||||||
// Don't block startup on reporting metric.
|
// Don't block startup on reporting metric.
|
||||||
// Report in separate go routine with 5 second timeout.
|
// Report in separate go routine with 5 second timeout.
|
||||||
@ -218,23 +214,30 @@ func NewServer(opts ServerOpts) (s *Server, err error) {
|
|||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) withCSRF(h http.Handler) http.Handler {
|
// withSecFetchSite wraps a HTTP handler and ensures requests made to it have
|
||||||
csrfProtect := csrf.Protect(s.csrfKey(), csrf.Secure(false))
|
// the `Sec-Fetch-Site` header set to same-origin to prevent CSRF attacks.
|
||||||
|
func (s *Server) withSecFetchSite(h http.Handler) http.Handler {
|
||||||
|
// Ref https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site
|
||||||
|
// Sent by all modern browsers to indicate the relationship between the
|
||||||
|
// origin from which the request was made and the requested resource.
|
||||||
|
// Require that the Sec-Fetch-Site header is set to "same-origin"
|
||||||
|
// for all requests to the web client, preventing CSRF attacks.
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Header.Get("Sec-Fetch-Site") {
|
||||||
|
case "same-origin":
|
||||||
|
// valid request
|
||||||
|
case "cross-site", "same-site":
|
||||||
|
// consider anything other than the same-origin to be invalid.
|
||||||
|
http.Error(w, "forbidden non `same-origin` request", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
case "":
|
||||||
|
http.Error(w, "missing required Sec-Fetch-Site request header. You may need to update your browser", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// serve all same-site requests.
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
// ref https://github.com/tailscale/tailscale/pull/14822
|
|
||||||
// signal to the CSRF middleware that the request is being served over
|
|
||||||
// plaintext HTTP to skip TLS-only header checks.
|
|
||||||
withSetPlaintext := func(h http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
r = csrf.PlaintextHTTPRequest(r)
|
|
||||||
h.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// NB: the order of the withSetPlaintext and csrfProtect calls is important
|
|
||||||
// to ensure that we signal to the CSRF middleware that the request is being
|
|
||||||
// served over plaintext HTTP and not over TLS as it presumes by default.
|
|
||||||
return withSetPlaintext(csrfProtect(h))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) modeAPIHandler(mode ServerMode) (http.Handler, string) {
|
func (s *Server) modeAPIHandler(mode ServerMode) (http.Handler, string) {
|
||||||
@ -450,9 +453,8 @@ func (s *Server) authorizeRequest(w http.ResponseWriter, r *http.Request) (ok bo
|
|||||||
|
|
||||||
// serveLoginAPI serves requests for the web login client.
|
// serveLoginAPI serves requests for the web login client.
|
||||||
// It should only be called by Server.ServeHTTP, via Server.apiHandler,
|
// It should only be called by Server.ServeHTTP, via Server.apiHandler,
|
||||||
// which protects the handler using gorilla csrf.
|
// which protects the handler using s.withSecFetchSite.
|
||||||
func (s *Server) serveLoginAPI(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) serveLoginAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("X-CSRF-Token", csrf.Token(r))
|
|
||||||
switch {
|
switch {
|
||||||
case r.URL.Path == "/api/data" && r.Method == httpm.GET:
|
case r.URL.Path == "/api/data" && r.Method == httpm.GET:
|
||||||
s.serveGetNodeData(w, r)
|
s.serveGetNodeData(w, r)
|
||||||
@ -575,7 +577,6 @@ func (s *Server) serveAPI(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("X-CSRF-Token", csrf.Token(r))
|
|
||||||
path := strings.TrimPrefix(r.URL.Path, "/api")
|
path := strings.TrimPrefix(r.URL.Path, "/api")
|
||||||
switch {
|
switch {
|
||||||
case path == "/data" && r.Method == httpm.GET:
|
case path == "/data" && r.Method == httpm.GET:
|
||||||
@ -834,12 +835,11 @@ type nodeData struct {
|
|||||||
KeyExpiry string // time.RFC3339
|
KeyExpiry string // time.RFC3339
|
||||||
KeyExpired bool
|
KeyExpired bool
|
||||||
|
|
||||||
TUNMode bool
|
TUNMode bool
|
||||||
IsSynology bool
|
IsSynology bool
|
||||||
DSMVersion int // 6 or 7, if IsSynology=true
|
DSMVersion int // 6 or 7, if IsSynology=true
|
||||||
IsUnraid bool
|
IsUnraid bool
|
||||||
UnraidToken string
|
URLPrefix string // if set, the URL prefix the client is served behind
|
||||||
URLPrefix string // if set, the URL prefix the client is served behind
|
|
||||||
|
|
||||||
UsingExitNode *exitNode
|
UsingExitNode *exitNode
|
||||||
AdvertisingExitNode bool
|
AdvertisingExitNode bool
|
||||||
@ -899,7 +899,6 @@ func (s *Server) serveGetNodeData(w http.ResponseWriter, r *http.Request) {
|
|||||||
IsSynology: distro.Get() == distro.Synology || envknob.Bool("TS_FAKE_SYNOLOGY"),
|
IsSynology: distro.Get() == distro.Synology || envknob.Bool("TS_FAKE_SYNOLOGY"),
|
||||||
DSMVersion: distro.DSMVersion(),
|
DSMVersion: distro.DSMVersion(),
|
||||||
IsUnraid: distro.Get() == distro.Unraid,
|
IsUnraid: distro.Get() == distro.Unraid,
|
||||||
UnraidToken: os.Getenv("UNRAID_CSRF_TOKEN"),
|
|
||||||
RunningSSHServer: prefs.RunSSH,
|
RunningSSHServer: prefs.RunSSH,
|
||||||
URLPrefix: strings.TrimSuffix(s.pathPrefix, "/"),
|
URLPrefix: strings.TrimSuffix(s.pathPrefix, "/"),
|
||||||
ControlAdminURL: prefs.AdminPageURL(),
|
ControlAdminURL: prefs.AdminPageURL(),
|
||||||
@ -1276,37 +1275,6 @@ func (s *Server) proxyRequestToLocalAPI(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// csrfKey returns a key that can be used for CSRF protection.
|
|
||||||
// If an error occurs during key creation, the error is logged and the active process terminated.
|
|
||||||
// If the server is running in CGI mode, the key is cached to disk and reused between requests.
|
|
||||||
// If an error occurs during key storage, the error is logged and the active process terminated.
|
|
||||||
func (s *Server) csrfKey() []byte {
|
|
||||||
csrfFile := filepath.Join(os.TempDir(), "tailscale-web-csrf.key")
|
|
||||||
|
|
||||||
// if running in CGI mode, try to read from disk, but ignore errors
|
|
||||||
if s.cgiMode {
|
|
||||||
key, _ := os.ReadFile(csrfFile)
|
|
||||||
if len(key) == 32 {
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a new key
|
|
||||||
key := make([]byte, 32)
|
|
||||||
if _, err := rand.Read(key); err != nil {
|
|
||||||
log.Fatalf("error generating CSRF key: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if running in CGI mode, try to write the newly created key to disk, and exit if it fails.
|
|
||||||
if s.cgiMode {
|
|
||||||
if err := os.WriteFile(csrfFile, key, 0600); err != nil {
|
|
||||||
log.Fatalf("unable to store CSRF key: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
// enforcePrefix returns a HandlerFunc that enforces a given path prefix is used in requests,
|
// enforcePrefix returns a HandlerFunc that enforces a given path prefix is used in requests,
|
||||||
// then strips it before invoking h.
|
// then strips it before invoking h.
|
||||||
// Unlike http.StripPrefix, it does not return a 404 if the prefix is not present.
|
// Unlike http.StripPrefix, it does not return a 404 if the prefix is not present.
|
||||||
|
@ -11,7 +11,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -21,7 +20,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/gorilla/csrf"
|
|
||||||
"tailscale.com/client/local"
|
"tailscale.com/client/local"
|
||||||
"tailscale.com/client/tailscale/apitype"
|
"tailscale.com/client/tailscale/apitype"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
@ -1491,82 +1489,77 @@ func mockWaitAuthURL(_ context.Context, id string, src tailcfg.NodeID) (*tailcfg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCSRFProtect(t *testing.T) {
|
func TestSecFetchSiteProtect(t *testing.T) {
|
||||||
s := &Server{}
|
tests := []struct {
|
||||||
|
name string
|
||||||
mux := http.NewServeMux()
|
withSameOrigin bool
|
||||||
mux.HandleFunc("GET /test/csrf-token", func(w http.ResponseWriter, r *http.Request) {
|
withSameSite bool
|
||||||
token := csrf.Token(r)
|
withCrossSite bool
|
||||||
_, err := io.WriteString(w, token)
|
expectError bool
|
||||||
if err != nil {
|
}{
|
||||||
t.Fatal(err)
|
{
|
||||||
}
|
name: "requests with same-origin pass",
|
||||||
})
|
withSameOrigin: true,
|
||||||
mux.HandleFunc("POST /test/csrf-protected", func(w http.ResponseWriter, r *http.Request) {
|
},
|
||||||
_, err := io.WriteString(w, "ok")
|
{
|
||||||
if err != nil {
|
name: "requests with same-site fail",
|
||||||
t.Fatal(err)
|
withSameSite: true,
|
||||||
}
|
expectError: true,
|
||||||
})
|
},
|
||||||
h := s.withCSRF(mux)
|
{
|
||||||
ser := nettest.NewHTTPServer(nettest.GetNetwork(t), h)
|
name: "requests with cross-site fail",
|
||||||
defer ser.Close()
|
withCrossSite: true,
|
||||||
|
expectError: true,
|
||||||
jar, err := cookiejar.New(nil)
|
},
|
||||||
if err != nil {
|
{
|
||||||
t.Fatalf("unable to construct cookie jar: %v", err)
|
name: "requests with no Sec-Fetch-Site fail",
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
client := ser.Client()
|
for _, tt := range tests {
|
||||||
client.Jar = jar
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := &Server{}
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
// make GET request to populate cookie jar
|
mux.HandleFunc("POST /protected", func(w http.ResponseWriter, r *http.Request) {
|
||||||
resp, err := client.Get(ser.URL + "/test/csrf-token")
|
_, err := io.WriteString(w, "ok")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to make request: %v", err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
})
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
t.Fatalf("unexpected status: %v", resp.Status)
|
|
||||||
}
|
|
||||||
tokenBytes, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to read body: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
csrfToken := strings.TrimSpace(string(tokenBytes))
|
h := s.withSecFetchSite(mux)
|
||||||
if csrfToken == "" {
|
|
||||||
t.Fatal("empty csrf token")
|
|
||||||
}
|
|
||||||
|
|
||||||
// make a POST request without the CSRF header; ensure it fails
|
serv := nettest.NewHTTPServer(nettest.GetNetwork(t), h)
|
||||||
resp, err = client.Post(ser.URL+"/test/csrf-protected", "text/plain", nil)
|
defer serv.Close()
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to make request: %v", err)
|
|
||||||
}
|
|
||||||
if resp.StatusCode != http.StatusForbidden {
|
|
||||||
t.Fatalf("unexpected status: %v", resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make a POST request with the CSRF header; ensure it succeeds
|
client := serv.Client()
|
||||||
req, err := http.NewRequest("POST", ser.URL+"/test/csrf-protected", nil)
|
|
||||||
if err != nil {
|
req, err := http.NewRequest("POST", serv.URL+"/protected", nil)
|
||||||
t.Fatalf("error building request: %v", err)
|
if err != nil {
|
||||||
}
|
t.Fatalf("error building request: %v", err)
|
||||||
req.Header.Set("X-CSRF-Token", csrfToken)
|
}
|
||||||
resp, err = client.Do(req)
|
|
||||||
if err != nil {
|
if tt.withSameOrigin {
|
||||||
t.Fatalf("unable to make request: %v", err)
|
req.Header.Set("Sec-Fetch-Site", "same-origin")
|
||||||
}
|
} else if tt.withSameSite {
|
||||||
if resp.StatusCode != http.StatusOK {
|
req.Header.Set("Sec-Fetch-Site", "same-site")
|
||||||
t.Fatalf("unexpected status: %v", resp.Status)
|
} else if tt.withCrossSite {
|
||||||
}
|
req.Header.Set("Sec-Fetch-Site", "cross-site")
|
||||||
defer resp.Body.Close()
|
}
|
||||||
out, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
resp, err := client.Do(req)
|
||||||
t.Fatalf("unable to read body: %v", err)
|
if err != nil {
|
||||||
}
|
t.Fatalf("unable to make request: %v", err)
|
||||||
if string(out) != "ok" {
|
}
|
||||||
t.Fatalf("unexpected body: %q", out)
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if tt.expectError && resp.StatusCode == http.StatusOK {
|
||||||
|
t.Fatalf("expected error but got ok")
|
||||||
|
} else if !tt.expectError && resp.StatusCode != http.StatusOK {
|
||||||
|
t.Fatalf("expected ok got %q", resp.Status)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,8 +144,6 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
|||||||
L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+
|
L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+
|
||||||
L github.com/google/nftables/xt from github.com/google/nftables/expr+
|
L github.com/google/nftables/xt from github.com/google/nftables/expr+
|
||||||
github.com/google/uuid from github.com/prometheus-community/pro-bing+
|
github.com/google/uuid from github.com/prometheus-community/pro-bing+
|
||||||
github.com/gorilla/csrf from tailscale.com/client/web
|
|
||||||
github.com/gorilla/securecookie from github.com/gorilla/csrf
|
|
||||||
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+
|
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+
|
||||||
L 💣 github.com/illarion/gonotify/v3 from tailscale.com/net/dns
|
L 💣 github.com/illarion/gonotify/v3 from tailscale.com/net/dns
|
||||||
L github.com/illarion/gonotify/v3/syscallf from github.com/illarion/gonotify/v3
|
L github.com/illarion/gonotify/v3/syscallf from github.com/illarion/gonotify/v3
|
||||||
@ -1131,13 +1129,12 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
|||||||
W debug/dwarf from debug/pe
|
W debug/dwarf from debug/pe
|
||||||
W debug/pe from github.com/dblohm7/wingoes/pe
|
W debug/pe from github.com/dblohm7/wingoes/pe
|
||||||
embed from github.com/tailscale/web-client-prebuilt+
|
embed from github.com/tailscale/web-client-prebuilt+
|
||||||
encoding from encoding/gob+
|
encoding from encoding/json+
|
||||||
encoding/asn1 from crypto/x509+
|
encoding/asn1 from crypto/x509+
|
||||||
encoding/base32 from github.com/fxamacker/cbor/v2+
|
encoding/base32 from github.com/fxamacker/cbor/v2+
|
||||||
encoding/base64 from encoding/json+
|
encoding/base64 from encoding/json+
|
||||||
encoding/binary from compress/gzip+
|
encoding/binary from compress/gzip+
|
||||||
encoding/csv from github.com/spf13/pflag
|
encoding/csv from github.com/spf13/pflag
|
||||||
encoding/gob from github.com/gorilla/securecookie
|
|
||||||
encoding/hex from crypto/x509+
|
encoding/hex from crypto/x509+
|
||||||
encoding/json from expvar+
|
encoding/json from expvar+
|
||||||
encoding/pem from crypto/tls+
|
encoding/pem from crypto/tls+
|
||||||
@ -1159,7 +1156,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
|||||||
hash/fnv from google.golang.org/protobuf/internal/detrand
|
hash/fnv from google.golang.org/protobuf/internal/detrand
|
||||||
hash/maphash from go4.org/mem
|
hash/maphash from go4.org/mem
|
||||||
html from html/template+
|
html from html/template+
|
||||||
html/template from github.com/gorilla/csrf+
|
html/template from tailscale.com/util/eventbus
|
||||||
internal/abi from crypto/x509/internal/macos+
|
internal/abi from crypto/x509/internal/macos+
|
||||||
internal/asan from internal/runtime/maps+
|
internal/asan from internal/runtime/maps+
|
||||||
internal/bisect from internal/godebug
|
internal/bisect from internal/godebug
|
||||||
@ -1191,7 +1188,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
|||||||
internal/runtime/math from internal/runtime/maps+
|
internal/runtime/math from internal/runtime/maps+
|
||||||
internal/runtime/sys from crypto/subtle+
|
internal/runtime/sys from crypto/subtle+
|
||||||
L internal/runtime/syscall from runtime+
|
L internal/runtime/syscall from runtime+
|
||||||
internal/saferio from debug/pe+
|
W internal/saferio from debug/pe
|
||||||
internal/singleflight from net
|
internal/singleflight from net
|
||||||
internal/stringslite from embed+
|
internal/stringslite from embed+
|
||||||
internal/sync from sync+
|
internal/sync from sync+
|
||||||
|
@ -27,8 +27,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+
|
L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+
|
||||||
L github.com/google/nftables/xt from github.com/google/nftables/expr+
|
L github.com/google/nftables/xt from github.com/google/nftables/expr+
|
||||||
DW github.com/google/uuid from tailscale.com/clientupdate+
|
DW github.com/google/uuid from tailscale.com/clientupdate+
|
||||||
github.com/gorilla/csrf from tailscale.com/client/web
|
|
||||||
github.com/gorilla/securecookie from github.com/gorilla/csrf
|
|
||||||
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+
|
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+
|
||||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/netmon
|
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/netmon
|
||||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||||
@ -318,12 +316,11 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
W debug/dwarf from debug/pe
|
W debug/dwarf from debug/pe
|
||||||
W debug/pe from github.com/dblohm7/wingoes/pe
|
W debug/pe from github.com/dblohm7/wingoes/pe
|
||||||
embed from github.com/peterbourgon/ff/v3+
|
embed from github.com/peterbourgon/ff/v3+
|
||||||
encoding from encoding/gob+
|
encoding from encoding/json+
|
||||||
encoding/asn1 from crypto/x509+
|
encoding/asn1 from crypto/x509+
|
||||||
encoding/base32 from github.com/fxamacker/cbor/v2+
|
encoding/base32 from github.com/fxamacker/cbor/v2+
|
||||||
encoding/base64 from encoding/json+
|
encoding/base64 from encoding/json+
|
||||||
encoding/binary from compress/gzip+
|
encoding/binary from compress/gzip+
|
||||||
encoding/gob from github.com/gorilla/securecookie
|
|
||||||
encoding/hex from crypto/x509+
|
encoding/hex from crypto/x509+
|
||||||
encoding/json from expvar+
|
encoding/json from expvar+
|
||||||
encoding/pem from crypto/tls+
|
encoding/pem from crypto/tls+
|
||||||
@ -337,7 +334,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
hash/crc32 from compress/gzip+
|
hash/crc32 from compress/gzip+
|
||||||
hash/maphash from go4.org/mem
|
hash/maphash from go4.org/mem
|
||||||
html from html/template+
|
html from html/template+
|
||||||
html/template from github.com/gorilla/csrf+
|
html/template from tailscale.com/util/eventbus
|
||||||
image from github.com/skip2/go-qrcode+
|
image from github.com/skip2/go-qrcode+
|
||||||
image/color from github.com/skip2/go-qrcode+
|
image/color from github.com/skip2/go-qrcode+
|
||||||
image/png from github.com/skip2/go-qrcode
|
image/png from github.com/skip2/go-qrcode
|
||||||
@ -371,7 +368,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
internal/runtime/math from internal/runtime/maps+
|
internal/runtime/math from internal/runtime/maps+
|
||||||
internal/runtime/sys from crypto/subtle+
|
internal/runtime/sys from crypto/subtle+
|
||||||
L internal/runtime/syscall from runtime+
|
L internal/runtime/syscall from runtime+
|
||||||
internal/saferio from debug/pe+
|
W internal/saferio from debug/pe
|
||||||
internal/singleflight from net
|
internal/singleflight from net
|
||||||
internal/stringslite from embed+
|
internal/stringslite from embed+
|
||||||
internal/sync from sync+
|
internal/sync from sync+
|
||||||
|
@ -116,8 +116,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+
|
L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+
|
||||||
L github.com/google/nftables/xt from github.com/google/nftables/expr+
|
L github.com/google/nftables/xt from github.com/google/nftables/expr+
|
||||||
DW github.com/google/uuid from tailscale.com/clientupdate+
|
DW github.com/google/uuid from tailscale.com/clientupdate+
|
||||||
github.com/gorilla/csrf from tailscale.com/client/web
|
|
||||||
github.com/gorilla/securecookie from github.com/gorilla/csrf
|
|
||||||
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+
|
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+
|
||||||
L 💣 github.com/illarion/gonotify/v3 from tailscale.com/net/dns
|
L 💣 github.com/illarion/gonotify/v3 from tailscale.com/net/dns
|
||||||
L github.com/illarion/gonotify/v3/syscallf from github.com/illarion/gonotify/v3
|
L github.com/illarion/gonotify/v3/syscallf from github.com/illarion/gonotify/v3
|
||||||
@ -580,12 +578,11 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
W debug/dwarf from debug/pe
|
W debug/dwarf from debug/pe
|
||||||
W debug/pe from github.com/dblohm7/wingoes/pe
|
W debug/pe from github.com/dblohm7/wingoes/pe
|
||||||
embed from github.com/tailscale/web-client-prebuilt+
|
embed from github.com/tailscale/web-client-prebuilt+
|
||||||
encoding from encoding/gob+
|
encoding from encoding/json+
|
||||||
encoding/asn1 from crypto/x509+
|
encoding/asn1 from crypto/x509+
|
||||||
encoding/base32 from github.com/fxamacker/cbor/v2+
|
encoding/base32 from github.com/fxamacker/cbor/v2+
|
||||||
encoding/base64 from encoding/json+
|
encoding/base64 from encoding/json+
|
||||||
encoding/binary from compress/gzip+
|
encoding/binary from compress/gzip+
|
||||||
encoding/gob from github.com/gorilla/securecookie
|
|
||||||
encoding/hex from crypto/x509+
|
encoding/hex from crypto/x509+
|
||||||
encoding/json from expvar+
|
encoding/json from expvar+
|
||||||
encoding/pem from crypto/tls+
|
encoding/pem from crypto/tls+
|
||||||
@ -599,7 +596,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
hash/crc32 from compress/gzip+
|
hash/crc32 from compress/gzip+
|
||||||
hash/maphash from go4.org/mem
|
hash/maphash from go4.org/mem
|
||||||
html from html/template+
|
html from html/template+
|
||||||
html/template from github.com/gorilla/csrf+
|
html/template from tailscale.com/util/eventbus
|
||||||
internal/abi from crypto/x509/internal/macos+
|
internal/abi from crypto/x509/internal/macos+
|
||||||
internal/asan from internal/runtime/maps+
|
internal/asan from internal/runtime/maps+
|
||||||
internal/bisect from internal/godebug
|
internal/bisect from internal/godebug
|
||||||
@ -630,7 +627,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
internal/runtime/math from internal/runtime/maps+
|
internal/runtime/math from internal/runtime/maps+
|
||||||
internal/runtime/sys from crypto/subtle+
|
internal/runtime/sys from crypto/subtle+
|
||||||
L internal/runtime/syscall from runtime+
|
L internal/runtime/syscall from runtime+
|
||||||
internal/saferio from debug/pe+
|
W internal/saferio from debug/pe
|
||||||
internal/singleflight from net
|
internal/singleflight from net
|
||||||
internal/stringslite from embed+
|
internal/stringslite from embed+
|
||||||
internal/sync from sync+
|
internal/sync from sync+
|
||||||
|
Loading…
x
Reference in New Issue
Block a user