localapi.go 13 KB

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