localapi.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. // Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // Package localapi contains the HTTP server handlers for tailscaled's API server.
  5. package localapi
  6. import (
  7. "crypto/rand"
  8. "encoding/hex"
  9. "encoding/json"
  10. "errors"
  11. "fmt"
  12. "io"
  13. "net/http"
  14. "net/http/httputil"
  15. "net/url"
  16. "reflect"
  17. "runtime"
  18. "strconv"
  19. "strings"
  20. "time"
  21. "inet.af/netaddr"
  22. "tailscale.com/client/tailscale/apitype"
  23. "tailscale.com/ipn"
  24. "tailscale.com/ipn/ipnlocal"
  25. "tailscale.com/ipn/ipnstate"
  26. "tailscale.com/tailcfg"
  27. "tailscale.com/types/logger"
  28. "tailscale.com/util/clientmetric"
  29. "tailscale.com/version"
  30. )
  31. func randHex(n int) string {
  32. b := make([]byte, n)
  33. rand.Read(b)
  34. return hex.EncodeToString(b)
  35. }
  36. func NewHandler(b *ipnlocal.LocalBackend, logf logger.Logf, logID string) *Handler {
  37. return &Handler{b: b, logf: logf, backendLogID: logID}
  38. }
  39. type Handler struct {
  40. // RequiredPassword, if non-empty, forces all HTTP
  41. // requests to have HTTP basic auth with this password.
  42. // It's used by the sandboxed macOS sameuserproof GUI auth mechanism.
  43. RequiredPassword string
  44. // PermitRead is whether read-only HTTP handlers are allowed.
  45. PermitRead bool
  46. // PermitWrite is whether mutating HTTP handlers are allowed.
  47. // If PermitWrite is true, everything is allowed.
  48. // It effectively means that the user is root or the admin
  49. // (operator user).
  50. PermitWrite bool
  51. // PermitCert is whether the client is additionally granted
  52. // cert fetching access.
  53. PermitCert bool
  54. b *ipnlocal.LocalBackend
  55. logf logger.Logf
  56. backendLogID string
  57. }
  58. func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  59. if h.b == nil {
  60. http.Error(w, "server has no local backend", http.StatusInternalServerError)
  61. return
  62. }
  63. w.Header().Set("Tailscale-Version", version.Long)
  64. if h.RequiredPassword != "" {
  65. _, pass, ok := r.BasicAuth()
  66. if !ok {
  67. http.Error(w, "auth required", http.StatusUnauthorized)
  68. return
  69. }
  70. if pass != h.RequiredPassword {
  71. http.Error(w, "bad password", http.StatusForbidden)
  72. return
  73. }
  74. }
  75. if strings.HasPrefix(r.URL.Path, "/localapi/v0/files/") {
  76. h.serveFiles(w, r)
  77. return
  78. }
  79. if strings.HasPrefix(r.URL.Path, "/localapi/v0/file-put/") {
  80. h.serveFilePut(w, r)
  81. return
  82. }
  83. if strings.HasPrefix(r.URL.Path, "/localapi/v0/cert/") {
  84. h.serveCert(w, r)
  85. return
  86. }
  87. switch r.URL.Path {
  88. case "/localapi/v0/whois":
  89. h.serveWhoIs(w, r)
  90. case "/localapi/v0/goroutines":
  91. h.serveGoroutines(w, r)
  92. case "/localapi/v0/profile":
  93. h.serveProfile(w, r)
  94. case "/localapi/v0/status":
  95. h.serveStatus(w, r)
  96. case "/localapi/v0/logout":
  97. h.serveLogout(w, r)
  98. case "/localapi/v0/prefs":
  99. h.servePrefs(w, r)
  100. case "/localapi/v0/check-ip-forwarding":
  101. h.serveCheckIPForwarding(w, r)
  102. case "/localapi/v0/bugreport":
  103. h.serveBugReport(w, r)
  104. case "/localapi/v0/file-targets":
  105. h.serveFileTargets(w, r)
  106. case "/localapi/v0/set-dns":
  107. h.serveSetDNS(w, r)
  108. case "/localapi/v0/derpmap":
  109. h.serveDERPMap(w, r)
  110. case "/localapi/v0/metrics":
  111. h.serveMetrics(w, r)
  112. case "/localapi/v0/debug":
  113. h.serveDebug(w, r)
  114. case "/":
  115. io.WriteString(w, "tailscaled\n")
  116. default:
  117. http.Error(w, "404 not found", 404)
  118. }
  119. }
  120. func (h *Handler) serveBugReport(w http.ResponseWriter, r *http.Request) {
  121. if !h.PermitRead {
  122. http.Error(w, "bugreport access denied", http.StatusForbidden)
  123. return
  124. }
  125. logMarker := fmt.Sprintf("BUG-%v-%v-%v", h.backendLogID, time.Now().UTC().Format("20060102150405Z"), randHex(8))
  126. h.logf("user bugreport: %s", logMarker)
  127. if note := r.FormValue("note"); len(note) > 0 {
  128. h.logf("user bugreport note: %s", note)
  129. }
  130. w.Header().Set("Content-Type", "text/plain")
  131. fmt.Fprintln(w, logMarker)
  132. }
  133. func (h *Handler) serveWhoIs(w http.ResponseWriter, r *http.Request) {
  134. if !h.PermitRead {
  135. http.Error(w, "whois access denied", http.StatusForbidden)
  136. return
  137. }
  138. b := h.b
  139. var ipp netaddr.IPPort
  140. if v := r.FormValue("addr"); v != "" {
  141. var err error
  142. ipp, err = netaddr.ParseIPPort(v)
  143. if err != nil {
  144. http.Error(w, "invalid 'addr' parameter", 400)
  145. return
  146. }
  147. } else {
  148. http.Error(w, "missing 'addr' parameter", 400)
  149. return
  150. }
  151. n, u, ok := b.WhoIs(ipp)
  152. if !ok {
  153. http.Error(w, "no match for IP:port", 404)
  154. return
  155. }
  156. res := &apitype.WhoIsResponse{
  157. Node: n,
  158. UserProfile: &u,
  159. }
  160. j, err := json.MarshalIndent(res, "", "\t")
  161. if err != nil {
  162. http.Error(w, "JSON encoding error", 500)
  163. return
  164. }
  165. w.Header().Set("Content-Type", "application/json")
  166. w.Write(j)
  167. }
  168. func (h *Handler) serveGoroutines(w http.ResponseWriter, r *http.Request) {
  169. // Require write access out of paranoia that the goroutine dump
  170. // (at least its arguments) might contain something sensitive.
  171. if !h.PermitWrite {
  172. http.Error(w, "goroutine dump access denied", http.StatusForbidden)
  173. return
  174. }
  175. buf := make([]byte, 2<<20)
  176. buf = buf[:runtime.Stack(buf, true)]
  177. w.Header().Set("Content-Type", "text/plain")
  178. w.Write(buf)
  179. }
  180. func (h *Handler) serveMetrics(w http.ResponseWriter, r *http.Request) {
  181. // Require write access out of paranoia that the metrics
  182. // might contain something sensitive.
  183. if !h.PermitWrite {
  184. http.Error(w, "metric access denied", http.StatusForbidden)
  185. return
  186. }
  187. w.Header().Set("Content-Type", "text/plain")
  188. clientmetric.WritePrometheusExpositionFormat(w)
  189. }
  190. func (h *Handler) serveDebug(w http.ResponseWriter, r *http.Request) {
  191. if !h.PermitWrite {
  192. http.Error(w, "debug access denied", http.StatusForbidden)
  193. return
  194. }
  195. if r.Method != "POST" {
  196. http.Error(w, "POST required", http.StatusMethodNotAllowed)
  197. return
  198. }
  199. action := r.FormValue("action")
  200. var err error
  201. switch action {
  202. case "rebind":
  203. err = h.b.DebugRebind()
  204. case "restun":
  205. err = h.b.DebugReSTUN()
  206. case "":
  207. err = fmt.Errorf("missing parameter 'action'")
  208. default:
  209. err = fmt.Errorf("unknown action %q", action)
  210. }
  211. if err != nil {
  212. http.Error(w, err.Error(), 400)
  213. return
  214. }
  215. w.Header().Set("Content-Type", "text/plain")
  216. io.WriteString(w, "done\n")
  217. }
  218. // serveProfileFunc is the implementation of Handler.serveProfile, after auth,
  219. // for platforms where we want to link it in.
  220. var serveProfileFunc func(http.ResponseWriter, *http.Request)
  221. func (h *Handler) serveProfile(w http.ResponseWriter, r *http.Request) {
  222. // Require write access out of paranoia that the profile dump
  223. // might contain something sensitive.
  224. if !h.PermitWrite {
  225. http.Error(w, "profile access denied", http.StatusForbidden)
  226. return
  227. }
  228. if serveProfileFunc == nil {
  229. http.Error(w, "not implemented on this platform", http.StatusServiceUnavailable)
  230. return
  231. }
  232. serveProfileFunc(w, r)
  233. }
  234. func (h *Handler) serveCheckIPForwarding(w http.ResponseWriter, r *http.Request) {
  235. if !h.PermitRead {
  236. http.Error(w, "IP forwarding check access denied", http.StatusForbidden)
  237. return
  238. }
  239. var warning string
  240. if err := h.b.CheckIPForwarding(); err != nil {
  241. warning = err.Error()
  242. }
  243. w.Header().Set("Content-Type", "application/json")
  244. json.NewEncoder(w).Encode(struct {
  245. Warning string
  246. }{
  247. Warning: warning,
  248. })
  249. }
  250. func (h *Handler) serveStatus(w http.ResponseWriter, r *http.Request) {
  251. if !h.PermitRead {
  252. http.Error(w, "status access denied", http.StatusForbidden)
  253. return
  254. }
  255. w.Header().Set("Content-Type", "application/json")
  256. var st *ipnstate.Status
  257. if defBool(r.FormValue("peers"), true) {
  258. st = h.b.Status()
  259. } else {
  260. st = h.b.StatusWithoutPeers()
  261. }
  262. e := json.NewEncoder(w)
  263. e.SetIndent("", "\t")
  264. e.Encode(st)
  265. }
  266. func (h *Handler) serveLogout(w http.ResponseWriter, r *http.Request) {
  267. if !h.PermitWrite {
  268. http.Error(w, "logout access denied", http.StatusForbidden)
  269. return
  270. }
  271. if r.Method != "POST" {
  272. http.Error(w, "want POST", 400)
  273. return
  274. }
  275. err := h.b.LogoutSync(r.Context())
  276. if err == nil {
  277. w.WriteHeader(http.StatusNoContent)
  278. return
  279. }
  280. http.Error(w, err.Error(), 500)
  281. }
  282. func (h *Handler) servePrefs(w http.ResponseWriter, r *http.Request) {
  283. if !h.PermitRead {
  284. http.Error(w, "prefs access denied", http.StatusForbidden)
  285. return
  286. }
  287. var prefs *ipn.Prefs
  288. switch r.Method {
  289. case "PATCH":
  290. if !h.PermitWrite {
  291. http.Error(w, "prefs write access denied", http.StatusForbidden)
  292. return
  293. }
  294. mp := new(ipn.MaskedPrefs)
  295. if err := json.NewDecoder(r.Body).Decode(mp); err != nil {
  296. http.Error(w, err.Error(), 400)
  297. return
  298. }
  299. var err error
  300. prefs, err = h.b.EditPrefs(mp)
  301. if err != nil {
  302. http.Error(w, err.Error(), 400)
  303. return
  304. }
  305. case "GET", "HEAD":
  306. prefs = h.b.Prefs()
  307. default:
  308. http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
  309. return
  310. }
  311. w.Header().Set("Content-Type", "application/json")
  312. e := json.NewEncoder(w)
  313. e.SetIndent("", "\t")
  314. e.Encode(prefs)
  315. }
  316. func (h *Handler) serveFiles(w http.ResponseWriter, r *http.Request) {
  317. if !h.PermitWrite {
  318. http.Error(w, "file access denied", http.StatusForbidden)
  319. return
  320. }
  321. suffix := strings.TrimPrefix(r.URL.EscapedPath(), "/localapi/v0/files/")
  322. if suffix == "" {
  323. if r.Method != "GET" {
  324. http.Error(w, "want GET to list files", 400)
  325. return
  326. }
  327. wfs, err := h.b.WaitingFiles()
  328. if err != nil {
  329. http.Error(w, err.Error(), 500)
  330. return
  331. }
  332. w.Header().Set("Content-Type", "application/json")
  333. json.NewEncoder(w).Encode(wfs)
  334. return
  335. }
  336. name, err := url.PathUnescape(suffix)
  337. if err != nil {
  338. http.Error(w, "bad filename", 400)
  339. return
  340. }
  341. if r.Method == "DELETE" {
  342. if err := h.b.DeleteFile(name); err != nil {
  343. http.Error(w, err.Error(), 500)
  344. return
  345. }
  346. w.WriteHeader(http.StatusNoContent)
  347. return
  348. }
  349. rc, size, err := h.b.OpenFile(name)
  350. if err != nil {
  351. http.Error(w, err.Error(), 500)
  352. return
  353. }
  354. defer rc.Close()
  355. w.Header().Set("Content-Length", fmt.Sprint(size))
  356. io.Copy(w, rc)
  357. }
  358. func writeErrorJSON(w http.ResponseWriter, err error) {
  359. if err == nil {
  360. err = errors.New("unexpected nil error")
  361. }
  362. w.Header().Set("Content-Type", "application/json")
  363. w.WriteHeader(500)
  364. type E struct {
  365. Error string `json:"error"`
  366. }
  367. json.NewEncoder(w).Encode(E{err.Error()})
  368. }
  369. func (h *Handler) serveFileTargets(w http.ResponseWriter, r *http.Request) {
  370. if !h.PermitRead {
  371. http.Error(w, "access denied", http.StatusForbidden)
  372. return
  373. }
  374. if r.Method != "GET" {
  375. http.Error(w, "want GET to list targets", 400)
  376. return
  377. }
  378. fts, err := h.b.FileTargets()
  379. if err != nil {
  380. writeErrorJSON(w, err)
  381. return
  382. }
  383. makeNonNil(&fts)
  384. w.Header().Set("Content-Type", "application/json")
  385. json.NewEncoder(w).Encode(fts)
  386. }
  387. // serveFilePut sends a file to another node.
  388. //
  389. // It's sometimes possible for clients to do this themselves, without
  390. // tailscaled, except in the case of tailscaled running in
  391. // userspace-networking ("netstack") mode, in which case tailscaled
  392. // needs to a do a netstack dial out.
  393. //
  394. // Instead, the CLI also goes through tailscaled so it doesn't need to be
  395. // aware of the network mode in use.
  396. //
  397. // macOS/iOS have always used this localapi method to simplify the GUI
  398. // clients.
  399. //
  400. // The Windows client currently (2021-11-30) uses the peerapi (/v0/put/)
  401. // directly, as the Windows GUI always runs in tun mode anyway.
  402. //
  403. // URL format:
  404. //
  405. // * PUT /localapi/v0/file-put/:stableID/:escaped-filename
  406. func (h *Handler) serveFilePut(w http.ResponseWriter, r *http.Request) {
  407. if !h.PermitWrite {
  408. http.Error(w, "file access denied", http.StatusForbidden)
  409. return
  410. }
  411. if r.Method != "PUT" {
  412. http.Error(w, "want PUT to put file", 400)
  413. return
  414. }
  415. fts, err := h.b.FileTargets()
  416. if err != nil {
  417. http.Error(w, err.Error(), 500)
  418. return
  419. }
  420. upath := strings.TrimPrefix(r.URL.EscapedPath(), "/localapi/v0/file-put/")
  421. slash := strings.Index(upath, "/")
  422. if slash == -1 {
  423. http.Error(w, "bogus URL", 400)
  424. return
  425. }
  426. stableID, filenameEscaped := tailcfg.StableNodeID(upath[:slash]), upath[slash+1:]
  427. var ft *apitype.FileTarget
  428. for _, x := range fts {
  429. if x.Node.StableID == stableID {
  430. ft = x
  431. break
  432. }
  433. }
  434. if ft == nil {
  435. http.Error(w, "node not found", 404)
  436. return
  437. }
  438. dstURL, err := url.Parse(ft.PeerAPIURL)
  439. if err != nil {
  440. http.Error(w, "bogus peer URL", 500)
  441. return
  442. }
  443. outReq, err := http.NewRequestWithContext(r.Context(), "PUT", "http://peer/v0/put/"+filenameEscaped, r.Body)
  444. if err != nil {
  445. http.Error(w, "bogus outreq", 500)
  446. return
  447. }
  448. outReq.ContentLength = r.ContentLength
  449. rp := httputil.NewSingleHostReverseProxy(dstURL)
  450. rp.Transport = h.b.Dialer().PeerAPITransport()
  451. rp.ServeHTTP(w, outReq)
  452. }
  453. func (h *Handler) serveSetDNS(w http.ResponseWriter, r *http.Request) {
  454. if !h.PermitWrite {
  455. http.Error(w, "access denied", http.StatusForbidden)
  456. return
  457. }
  458. if r.Method != "POST" {
  459. http.Error(w, "want POST", 400)
  460. return
  461. }
  462. ctx := r.Context()
  463. err := h.b.SetDNS(ctx, r.FormValue("name"), r.FormValue("value"))
  464. if err != nil {
  465. writeErrorJSON(w, err)
  466. return
  467. }
  468. w.Header().Set("Content-Type", "application/json")
  469. json.NewEncoder(w).Encode(struct{}{})
  470. }
  471. func (h *Handler) serveDERPMap(w http.ResponseWriter, r *http.Request) {
  472. if r.Method != "GET" {
  473. http.Error(w, "want GET", 400)
  474. return
  475. }
  476. w.Header().Set("Content-Type", "application/json")
  477. e := json.NewEncoder(w)
  478. e.SetIndent("", "\t")
  479. e.Encode(h.b.DERPMap())
  480. }
  481. func defBool(a string, def bool) bool {
  482. if a == "" {
  483. return def
  484. }
  485. v, err := strconv.ParseBool(a)
  486. if err != nil {
  487. return def
  488. }
  489. return v
  490. }
  491. // makeNonNil takes a pointer to a Go data structure
  492. // (currently only a slice or a map) and makes sure it's non-nil for
  493. // JSON serialization. (In particular, JavaScript clients usually want
  494. // the field to be defined after they decode the JSON.)
  495. func makeNonNil(ptr interface{}) {
  496. if ptr == nil {
  497. panic("nil interface")
  498. }
  499. rv := reflect.ValueOf(ptr)
  500. if rv.Kind() != reflect.Ptr {
  501. panic(fmt.Sprintf("kind %v, not Ptr", rv.Kind()))
  502. }
  503. if rv.Pointer() == 0 {
  504. panic("nil pointer")
  505. }
  506. rv = rv.Elem()
  507. if rv.Pointer() != 0 {
  508. return
  509. }
  510. switch rv.Type().Kind() {
  511. case reflect.Slice:
  512. rv.Set(reflect.MakeSlice(rv.Type(), 0, 0))
  513. case reflect.Map:
  514. rv.Set(reflect.MakeMap(rv.Type()))
  515. }
  516. }