|
|
@ -20,7 +20,6 @@ import (
|
|
|
|
"path/filepath"
|
|
|
|
"path/filepath"
|
|
|
|
"slices"
|
|
|
|
"slices"
|
|
|
|
"strings"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/gorilla/csrf"
|
|
|
|
"github.com/gorilla/csrf"
|
|
|
|
"tailscale.com/client/tailscale"
|
|
|
|
"tailscale.com/client/tailscale"
|
|
|
@ -61,18 +60,6 @@ type Server struct {
|
|
|
|
cgiMode bool
|
|
|
|
cgiMode bool
|
|
|
|
cgiPath string
|
|
|
|
cgiPath string
|
|
|
|
apiHandler http.Handler // csrf-protected api handler
|
|
|
|
apiHandler http.Handler // csrf-protected api handler
|
|
|
|
|
|
|
|
|
|
|
|
selfMu sync.Mutex // protects self field
|
|
|
|
|
|
|
|
// self is a cached NodeView of the active self node,
|
|
|
|
|
|
|
|
// refreshed by watching the IPN notification bus
|
|
|
|
|
|
|
|
// (see Server.watchSelf).
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// self's hostname and Tailscale IP are used to verify
|
|
|
|
|
|
|
|
// that incoming requests to the web client api are coming
|
|
|
|
|
|
|
|
// from the web client frontend and not some other source.
|
|
|
|
|
|
|
|
// Particularly to protect against DNS rebinding attacks.
|
|
|
|
|
|
|
|
// self should not be used to fill data for frontend views.
|
|
|
|
|
|
|
|
self tailcfg.NodeView
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ServerOpts contains options for constructing a new Server.
|
|
|
|
// ServerOpts contains options for constructing a new Server.
|
|
|
@ -108,14 +95,6 @@ func NewServer(ctx context.Context, opts ServerOpts) (s *Server, cleanup func())
|
|
|
|
s.addProxyToDevServer()
|
|
|
|
s.addProxyToDevServer()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
|
|
|
defer wg.Wait()
|
|
|
|
|
|
|
|
wg.Add(1)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
go s.watchSelf(ctx)
|
|
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Create handler for "/api" requests with CSRF protection.
|
|
|
|
// Create handler for "/api" requests with CSRF protection.
|
|
|
|
// We don't require secure cookies, since the web client is regularly used
|
|
|
|
// We don't require secure cookies, since the web client is regularly used
|
|
|
|
// on network appliances that are served on local non-https URLs.
|
|
|
|
// on network appliances that are served on local non-https URLs.
|
|
|
@ -133,58 +112,6 @@ func init() {
|
|
|
|
staticfiles = http.FileServer(http.FS(buildFiles))
|
|
|
|
staticfiles = http.FileServer(http.FS(buildFiles))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// watchSelf watches the IPN notification bus to refresh
|
|
|
|
|
|
|
|
// the Server's self node cache.
|
|
|
|
|
|
|
|
func (s *Server) watchSelf(ctx context.Context) {
|
|
|
|
|
|
|
|
watchCtx, cancelWatch := context.WithCancel(ctx)
|
|
|
|
|
|
|
|
defer cancelWatch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
watcher, err := s.lc.WatchIPNBus(watchCtx, ipn.NotifyInitialNetMap|ipn.NotifyNoPrivateKeys)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
log.Fatalf("lost connection to tailscaled: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
defer watcher.Close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
|
|
|
n, err := watcher.Next()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
log.Fatalf("lost connection to tailscaled: %v", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if state := n.State; state != nil && *state == ipn.NeedsLogin {
|
|
|
|
|
|
|
|
s.updateSelf(tailcfg.NodeView{})
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if n.NetMap == nil {
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
s.updateSelf(n.NetMap.SelfNode)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// updateSelf grabs the lock and updates s.self.
|
|
|
|
|
|
|
|
// Then logs if anything changed.
|
|
|
|
|
|
|
|
func (s *Server) updateSelf(self tailcfg.NodeView) {
|
|
|
|
|
|
|
|
s.selfMu.Lock()
|
|
|
|
|
|
|
|
prev := s.self
|
|
|
|
|
|
|
|
s.self = self
|
|
|
|
|
|
|
|
s.selfMu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var old, new tailcfg.StableNodeID
|
|
|
|
|
|
|
|
if prev.Valid() {
|
|
|
|
|
|
|
|
old = prev.StableID()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.self.Valid() {
|
|
|
|
|
|
|
|
new = s.self.StableID()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if old != new {
|
|
|
|
|
|
|
|
if new.IsZero() {
|
|
|
|
|
|
|
|
log.Printf("self node logout")
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
log.Printf("self node login")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ServeHTTP processes all requests for the Tailscale web client.
|
|
|
|
// ServeHTTP processes all requests for the Tailscale web client.
|
|
|
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
handler := s.serve
|
|
|
|
handler := s.serve
|
|
|
|