remote_impl.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package driveimpl
  4. import (
  5. "bufio"
  6. "context"
  7. "encoding/hex"
  8. "fmt"
  9. "log"
  10. "math"
  11. "net"
  12. "net/http"
  13. "net/netip"
  14. "net/url"
  15. "os"
  16. "os/exec"
  17. "os/user"
  18. "slices"
  19. "strings"
  20. "sync"
  21. "time"
  22. "github.com/tailscale/xnet/webdav"
  23. "tailscale.com/drive"
  24. "tailscale.com/drive/driveimpl/compositedav"
  25. "tailscale.com/drive/driveimpl/dirfs"
  26. "tailscale.com/drive/driveimpl/shared"
  27. "tailscale.com/safesocket"
  28. "tailscale.com/types/logger"
  29. )
  30. func NewFileSystemForRemote(logf logger.Logf) *FileSystemForRemote {
  31. if logf == nil {
  32. logf = log.Printf
  33. }
  34. fs := &FileSystemForRemote{
  35. logf: logf,
  36. lockSystem: webdav.NewMemLS(),
  37. children: make(map[string]*compositedav.Child),
  38. userServers: make(map[string]*userServer),
  39. }
  40. return fs
  41. }
  42. // FileSystemForRemote implements drive.FileSystemForRemote.
  43. type FileSystemForRemote struct {
  44. logf logger.Logf
  45. lockSystem webdav.LockSystem
  46. // mu guards the below values. Acquire a write lock before updating any of
  47. // them, acquire a read lock before reading any of them.
  48. mu sync.RWMutex
  49. // fileServerTokenAndAddr is the secretToken|fileserverAddress
  50. fileServerTokenAndAddr string
  51. shares []*drive.Share
  52. children map[string]*compositedav.Child
  53. userServers map[string]*userServer
  54. }
  55. // SetFileServerAddr implements drive.FileSystemForRemote.
  56. func (s *FileSystemForRemote) SetFileServerAddr(addr string) {
  57. s.mu.Lock()
  58. s.fileServerTokenAndAddr = addr
  59. s.mu.Unlock()
  60. }
  61. // SetShares implements drive.FileSystemForRemote. Shares must be sorted
  62. // according to drive.CompareShares.
  63. func (s *FileSystemForRemote) SetShares(shares []*drive.Share) {
  64. userServers := make(map[string]*userServer)
  65. if drive.AllowShareAs() {
  66. // Set up per-user server by running the current executable as an
  67. // unprivileged user in order to avoid privilege escalation.
  68. executable, err := os.Executable()
  69. if err != nil {
  70. s.logf("can't find executable: %v", err)
  71. return
  72. }
  73. for _, share := range shares {
  74. p, found := userServers[share.As]
  75. if !found {
  76. p = &userServer{
  77. logf: s.logf,
  78. username: share.As,
  79. executable: executable,
  80. }
  81. userServers[share.As] = p
  82. }
  83. p.shares = append(p.shares, share)
  84. }
  85. for _, p := range userServers {
  86. go p.runLoop()
  87. }
  88. }
  89. children := make(map[string]*compositedav.Child, len(shares))
  90. for _, share := range shares {
  91. children[share.Name] = s.buildChild(share)
  92. }
  93. s.mu.Lock()
  94. s.shares = shares
  95. oldUserServers := s.userServers
  96. oldChildren := s.children
  97. s.children = children
  98. s.userServers = userServers
  99. s.mu.Unlock()
  100. s.stopUserServers(oldUserServers)
  101. s.closeChildren(oldChildren)
  102. }
  103. func (s *FileSystemForRemote) buildChild(share *drive.Share) *compositedav.Child {
  104. getTokenAndAddr := func(shareName string) (string, string, error) {
  105. s.mu.RLock()
  106. var share *drive.Share
  107. i, shareFound := slices.BinarySearchFunc(s.shares, shareName, func(s *drive.Share, name string) int {
  108. return strings.Compare(s.Name, name)
  109. })
  110. if shareFound {
  111. share = s.shares[i]
  112. }
  113. userServers := s.userServers
  114. fileServerTokenAndAddr := s.fileServerTokenAndAddr
  115. s.mu.RUnlock()
  116. if !shareFound {
  117. return "", "", fmt.Errorf("unknown share %v", shareName)
  118. }
  119. var tokenAndAddr string
  120. if !drive.AllowShareAs() {
  121. tokenAndAddr = fileServerTokenAndAddr
  122. } else {
  123. userServer, found := userServers[share.As]
  124. if found {
  125. userServer.mu.RLock()
  126. tokenAndAddr = userServer.tokenAndAddr
  127. userServer.mu.RUnlock()
  128. }
  129. }
  130. if tokenAndAddr == "" {
  131. return "", "", fmt.Errorf("unable to determine address for share %v", shareName)
  132. }
  133. parts := strings.Split(tokenAndAddr, "|")
  134. if len(parts) != 2 {
  135. return "", "", fmt.Errorf("invalid address for share %v", shareName)
  136. }
  137. return parts[0], parts[1], nil
  138. }
  139. return &compositedav.Child{
  140. Child: &dirfs.Child{
  141. Name: share.Name,
  142. },
  143. BaseURL: func() (string, error) {
  144. secretToken, _, err := getTokenAndAddr(share.Name)
  145. if err != nil {
  146. return "", err
  147. }
  148. return fmt.Sprintf("http://%s/%s/%s", hex.EncodeToString([]byte(share.Name)), secretToken, url.PathEscape(share.Name)), nil
  149. },
  150. Transport: &http.Transport{
  151. DialContext: func(ctx context.Context, _, shareAddr string) (net.Conn, error) {
  152. shareNameHex, _, err := net.SplitHostPort(shareAddr)
  153. if err != nil {
  154. return nil, fmt.Errorf("unable to parse share address %v: %w", shareAddr, err)
  155. }
  156. // We had to encode the share name in hex to make sure it's a valid hostname
  157. shareNameBytes, err := hex.DecodeString(shareNameHex)
  158. if err != nil {
  159. return nil, fmt.Errorf("unable to decode share name from host %v: %v", shareNameHex, err)
  160. }
  161. shareName := string(shareNameBytes)
  162. _, addr, err := getTokenAndAddr(shareName)
  163. if err != nil {
  164. return nil, err
  165. }
  166. _, err = netip.ParseAddrPort(addr)
  167. if err == nil {
  168. // this is a regular network address, dial normally
  169. var std net.Dialer
  170. return std.DialContext(ctx, "tcp", addr)
  171. }
  172. // assume this is a safesocket address
  173. return safesocket.ConnectContext(ctx, addr)
  174. },
  175. },
  176. }
  177. }
  178. // ServeHTTPWithPerms implements drive.FileSystemForRemote.
  179. func (s *FileSystemForRemote) ServeHTTPWithPerms(permissions drive.Permissions, w http.ResponseWriter, r *http.Request) {
  180. isWrite := writeMethods[r.Method]
  181. if isWrite {
  182. share := shared.CleanAndSplit(r.URL.Path)[0]
  183. switch permissions.For(share) {
  184. case drive.PermissionNone:
  185. // If we have no permissions to this share, treat it as not found
  186. // to avoid leaking any information about the share's existence.
  187. http.Error(w, "not found", http.StatusNotFound)
  188. return
  189. case drive.PermissionReadOnly:
  190. http.Error(w, "permission denied", http.StatusForbidden)
  191. return
  192. }
  193. }
  194. s.mu.RLock()
  195. childrenMap := s.children
  196. s.mu.RUnlock()
  197. children := make([]*compositedav.Child, 0, len(childrenMap))
  198. // filter out shares to which the connecting principal has no access
  199. for name, child := range childrenMap {
  200. if permissions.For(name) == drive.PermissionNone {
  201. continue
  202. }
  203. children = append(children, child)
  204. }
  205. h := compositedav.Handler{
  206. Logf: s.logf,
  207. }
  208. h.SetChildren("", children...)
  209. h.ServeHTTP(w, r)
  210. }
  211. func (s *FileSystemForRemote) stopUserServers(userServers map[string]*userServer) {
  212. for _, server := range userServers {
  213. if err := server.Close(); err != nil {
  214. s.logf("error closing taildrive user server: %v", err)
  215. }
  216. }
  217. }
  218. func (s *FileSystemForRemote) closeChildren(children map[string]*compositedav.Child) {
  219. for _, child := range children {
  220. child.CloseIdleConnections()
  221. }
  222. }
  223. // Close() implements drive.FileSystemForRemote.
  224. func (s *FileSystemForRemote) Close() error {
  225. s.mu.Lock()
  226. userServers := s.userServers
  227. children := s.children
  228. s.userServers = make(map[string]*userServer)
  229. s.children = make(map[string]*compositedav.Child)
  230. s.mu.Unlock()
  231. s.stopUserServers(userServers)
  232. s.closeChildren(children)
  233. return nil
  234. }
  235. // userServer runs tailscaled serve-taildrive to serve webdav content for the
  236. // given Shares. All Shares are assumed to have the same Share.As, and the
  237. // content is served as that Share.As user.
  238. type userServer struct {
  239. logf logger.Logf
  240. shares []*drive.Share
  241. username string
  242. executable string
  243. // mu guards the below values. Acquire a write lock before updating any of
  244. // them, acquire a read lock before reading any of them.
  245. mu sync.RWMutex
  246. cmd *exec.Cmd
  247. tokenAndAddr string
  248. closed bool
  249. }
  250. func (s *userServer) Close() error {
  251. s.mu.Lock()
  252. cmd := s.cmd
  253. s.closed = true
  254. s.mu.Unlock()
  255. if cmd != nil && cmd.Process != nil {
  256. return cmd.Process.Kill()
  257. }
  258. // not running, that's okay
  259. return nil
  260. }
  261. func (s *userServer) runLoop() {
  262. maxSleepTime := 30 * time.Second
  263. consecutiveFailures := float64(0)
  264. var timeOfLastFailure time.Time
  265. for {
  266. s.mu.RLock()
  267. closed := s.closed
  268. s.mu.RUnlock()
  269. if closed {
  270. return
  271. }
  272. err := s.run()
  273. now := time.Now()
  274. timeSinceLastFailure := now.Sub(timeOfLastFailure)
  275. timeOfLastFailure = now
  276. if timeSinceLastFailure < maxSleepTime {
  277. consecutiveFailures++
  278. } else {
  279. consecutiveFailures = 1
  280. }
  281. sleepTime := time.Duration(math.Pow(2, consecutiveFailures)) * time.Millisecond
  282. if sleepTime > maxSleepTime {
  283. sleepTime = maxSleepTime
  284. }
  285. s.logf("user server % v stopped with error %v, will try again in %v", s.executable, err, sleepTime)
  286. time.Sleep(sleepTime)
  287. }
  288. }
  289. // Run runs the user server using the configured executable. This function only
  290. // works on UNIX systems, but those are the only ones on which we use
  291. // userServers anyway.
  292. func (s *userServer) run() error {
  293. // set up the command
  294. args := []string{"serve-taildrive"}
  295. for _, s := range s.shares {
  296. args = append(args, s.Name, s.Path)
  297. }
  298. var cmd *exec.Cmd
  299. if s.canSudo() {
  300. s.logf("starting taildrive file server with sudo as user %q", s.username)
  301. allArgs := []string{"-n", "-u", s.username, s.executable}
  302. allArgs = append(allArgs, args...)
  303. cmd = exec.Command("sudo", allArgs...)
  304. } else if su := s.canSU(); su != "" {
  305. s.logf("starting taildrive file server with su as user %q", s.username)
  306. // Quote and escape arguments. Use single quotes to prevent shell substitutions.
  307. for i, arg := range args {
  308. args[i] = "'" + strings.ReplaceAll(arg, "'", "'\"'\"'") + "'"
  309. }
  310. cmdString := fmt.Sprintf("%s %s", s.executable, strings.Join(args, " "))
  311. allArgs := []string{s.username, "-c", cmdString}
  312. cmd = exec.Command(su, allArgs...)
  313. } else {
  314. // If we were root, we should have been able to sudo or su as a specific
  315. // user, but let's check just to make sure, since we never want to
  316. // access shared folders as root.
  317. err := s.assertNotRoot()
  318. if err != nil {
  319. return err
  320. }
  321. s.logf("starting taildrive file server as ourselves")
  322. cmd = exec.Command(s.executable, args...)
  323. }
  324. stdout, err := cmd.StdoutPipe()
  325. if err != nil {
  326. return fmt.Errorf("stdout pipe: %w", err)
  327. }
  328. defer stdout.Close()
  329. stderr, err := cmd.StderrPipe()
  330. if err != nil {
  331. return fmt.Errorf("stderr pipe: %w", err)
  332. }
  333. defer stderr.Close()
  334. err = cmd.Start()
  335. if err != nil {
  336. return fmt.Errorf("start: %w", err)
  337. }
  338. s.mu.Lock()
  339. s.cmd = cmd
  340. s.mu.Unlock()
  341. // read address
  342. stdoutScanner := bufio.NewScanner(stdout)
  343. stdoutScanner.Scan()
  344. if stdoutScanner.Err() != nil {
  345. return fmt.Errorf("read addr: %w", stdoutScanner.Err())
  346. }
  347. addr := stdoutScanner.Text()
  348. // send the rest of stdout and stderr to logger to avoid blocking
  349. go func() {
  350. for stdoutScanner.Scan() {
  351. s.logf("tailscaled serve-taildrive stdout: %v", stdoutScanner.Text())
  352. }
  353. }()
  354. stderrScanner := bufio.NewScanner(stderr)
  355. go func() {
  356. for stderrScanner.Scan() {
  357. s.logf("tailscaled serve-taildrive stderr: %v", stderrScanner.Text())
  358. }
  359. }()
  360. s.mu.Lock()
  361. s.tokenAndAddr = strings.TrimSpace(addr)
  362. s.mu.Unlock()
  363. return cmd.Wait()
  364. }
  365. var writeMethods = map[string]bool{
  366. "PUT": true,
  367. "POST": true,
  368. "COPY": true,
  369. "LOCK": true,
  370. "UNLOCK": true,
  371. "MKCOL": true,
  372. "MOVE": true,
  373. "PROPPATCH": true,
  374. "DELETE": true,
  375. }
  376. // canSudo checks wether we can sudo -u the configured executable as the
  377. // configured user by attempting to call the executable with the '-h' flag to
  378. // print help.
  379. func (s *userServer) canSudo() bool {
  380. ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
  381. defer cancel()
  382. if err := exec.CommandContext(ctx, "sudo", "-n", "-u", s.username, s.executable, "-h").Run(); err != nil {
  383. return false
  384. }
  385. return true
  386. }
  387. // canSU checks whether the current process can run su with the right username.
  388. // If su can be run, this returns the path to the su command.
  389. // If not, this returns the empty string "".
  390. func (s *userServer) canSU() string {
  391. su, err := exec.LookPath("su")
  392. if err != nil {
  393. s.logf("can't find su command: %v", err)
  394. return ""
  395. }
  396. // First try to execute su <user> -c true to make sure we can su.
  397. err = exec.Command(
  398. su,
  399. s.username,
  400. "-c", "true",
  401. ).Run()
  402. if err != nil {
  403. s.logf("su check failed: %s", err)
  404. return ""
  405. }
  406. return su
  407. }
  408. // assertNotRoot returns an error if the current user has UID 0 or if we cannot
  409. // determine the current user.
  410. //
  411. // On Linux, root users will always have UID 0.
  412. //
  413. // On BSD, root users should always have UID 0.
  414. func (s *userServer) assertNotRoot() error {
  415. u, err := user.Current()
  416. if err != nil {
  417. return fmt.Errorf("assertNotRoot failed to find current user: %s", err)
  418. }
  419. if u.Uid == "0" {
  420. return fmt.Errorf("%q is root", u.Name)
  421. }
  422. return nil
  423. }