serve.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package ipn
  4. import (
  5. "errors"
  6. "fmt"
  7. "net"
  8. "net/netip"
  9. "net/url"
  10. "strconv"
  11. "strings"
  12. "tailscale.com/ipn/ipnstate"
  13. "tailscale.com/tailcfg"
  14. )
  15. // ServeConfigKey returns a StateKey that stores the
  16. // JSON-encoded ServeConfig for a config profile.
  17. func ServeConfigKey(profileID ProfileID) StateKey {
  18. return StateKey("_serve/" + profileID)
  19. }
  20. // ServeConfig is the JSON type stored in the StateStore for
  21. // StateKey "_serve/$PROFILE_ID" as returned by ServeConfigKey.
  22. type ServeConfig struct {
  23. // TCP are the list of TCP port numbers that tailscaled should handle for
  24. // the Tailscale IP addresses. (not subnet routers, etc)
  25. TCP map[uint16]*TCPPortHandler `json:",omitempty"`
  26. // Web maps from "$SNI_NAME:$PORT" to a set of HTTP handlers
  27. // keyed by mount point ("/", "/foo", etc)
  28. Web map[HostPort]*WebServerConfig `json:",omitempty"`
  29. // AllowFunnel is the set of SNI:port values for which funnel
  30. // traffic is allowed, from trusted ingress peers.
  31. AllowFunnel map[HostPort]bool `json:",omitempty"`
  32. // Foreground is a map of an IPN Bus session ID to an alternate foreground
  33. // serve config that's valid for the life of that WatchIPNBus session ID.
  34. // This. This allows the config to specify ephemeral configs that are
  35. // used in the CLI's foreground mode to ensure ungraceful shutdowns
  36. // of either the client or the LocalBackend does not expose ports
  37. // that users are not aware of.
  38. Foreground map[string]*ServeConfig `json:",omitempty"`
  39. // ETag is the checksum of the serve config that's populated
  40. // by the LocalClient through the HTTP ETag header during a
  41. // GetServeConfig request and is translated to an If-Match header
  42. // during a SetServeConfig request.
  43. ETag string `json:"-"`
  44. }
  45. // HostPort is an SNI name and port number, joined by a colon.
  46. // There is no implicit port 443. It must contain a colon.
  47. type HostPort string
  48. // Port extracts just the port number from hp.
  49. // An error is reported in the case that the hp does not
  50. // have a valid numeric port ending.
  51. func (hp HostPort) Port() (uint16, error) {
  52. _, port, err := net.SplitHostPort(string(hp))
  53. if err != nil {
  54. return 0, err
  55. }
  56. port16, err := strconv.ParseUint(port, 10, 16)
  57. if err != nil {
  58. return 0, err
  59. }
  60. return uint16(port16), nil
  61. }
  62. // A FunnelConn wraps a net.Conn that is coming over a
  63. // Funnel connection. It can be used to determine further
  64. // information about the connection, like the source address
  65. // and the target SNI name.
  66. type FunnelConn struct {
  67. // Conn is the underlying connection.
  68. net.Conn
  69. // Target is what was presented in the "Tailscale-Ingress-Target"
  70. // HTTP header.
  71. Target HostPort
  72. // Src is the source address of the connection.
  73. // This is the address of the client that initiated the
  74. // connection, not the address of the Tailscale Funnel
  75. // node which is relaying the connection. That address
  76. // can be found in Conn.RemoteAddr.
  77. Src netip.AddrPort
  78. }
  79. // WebServerConfig describes a web server's configuration.
  80. type WebServerConfig struct {
  81. Handlers map[string]*HTTPHandler // mountPoint => handler
  82. }
  83. // TCPPortHandler describes what to do when handling a TCP
  84. // connection.
  85. type TCPPortHandler struct {
  86. // HTTPS, if true, means that tailscaled should handle this connection as an
  87. // HTTPS request as configured by ServeConfig.Web.
  88. //
  89. // It is mutually exclusive with TCPForward.
  90. HTTPS bool `json:",omitempty"`
  91. // HTTP, if true, means that tailscaled should handle this connection as an
  92. // HTTP request as configured by ServeConfig.Web.
  93. //
  94. // It is mutually exclusive with TCPForward.
  95. HTTP bool `json:",omitempty"`
  96. // TCPForward is the IP:port to forward TCP connections to.
  97. // Whether or not TLS is terminated by tailscaled depends on
  98. // TerminateTLS.
  99. //
  100. // It is mutually exclusive with HTTPS.
  101. TCPForward string `json:",omitempty"`
  102. // TerminateTLS, if non-empty, means that tailscaled should terminate the
  103. // TLS connections before forwarding them to TCPForward, permitting only the
  104. // SNI name with this value. It is only used if TCPForward is non-empty.
  105. // (the HTTPS mode uses ServeConfig.Web)
  106. TerminateTLS string `json:",omitempty"`
  107. }
  108. // HTTPHandler is either a path or a proxy to serve.
  109. type HTTPHandler struct {
  110. // Exactly one of the following may be set.
  111. Path string `json:",omitempty"` // absolute path to directory or file to serve
  112. Proxy string `json:",omitempty"` // http://localhost:3000/, localhost:3030, 3030
  113. Text string `json:",omitempty"` // plaintext to serve (primarily for testing)
  114. // TODO(bradfitz): bool to not enumerate directories? TTL on mapping for
  115. // temporary ones? Error codes? Redirects?
  116. }
  117. // WebHandlerExists reports whether if the ServeConfig Web handler exists for
  118. // the given host:port and mount point.
  119. func (sc *ServeConfig) WebHandlerExists(hp HostPort, mount string) bool {
  120. h := sc.GetWebHandler(hp, mount)
  121. return h != nil
  122. }
  123. // GetWebHandler returns the HTTPHandler for the given host:port and mount point.
  124. // Returns nil if the handler does not exist.
  125. func (sc *ServeConfig) GetWebHandler(hp HostPort, mount string) *HTTPHandler {
  126. if sc == nil || sc.Web[hp] == nil {
  127. return nil
  128. }
  129. return sc.Web[hp].Handlers[mount]
  130. }
  131. // GetTCPPortHandler returns the TCPPortHandler for the given port.
  132. // If the port is not configured, nil is returned.
  133. func (sc *ServeConfig) GetTCPPortHandler(port uint16) *TCPPortHandler {
  134. if sc == nil {
  135. return nil
  136. }
  137. return sc.TCP[port]
  138. }
  139. // HasPathHandler reports whether if ServeConfig has at least
  140. // one path handler, including foreground configs.
  141. func (sc *ServeConfig) HasPathHandler() bool {
  142. if sc.Web != nil {
  143. for _, webServerConfig := range sc.Web {
  144. for _, httpHandler := range webServerConfig.Handlers {
  145. if httpHandler.Path != "" {
  146. return true
  147. }
  148. }
  149. }
  150. }
  151. if sc.Foreground != nil {
  152. for _, fgConfig := range sc.Foreground {
  153. if fgConfig.HasPathHandler() {
  154. return true
  155. }
  156. }
  157. }
  158. return false
  159. }
  160. // IsTCPForwardingAny reports whether ServeConfig is currently forwarding in
  161. // TCPForward mode on any port. This is exclusive of Web/HTTPS serving.
  162. func (sc *ServeConfig) IsTCPForwardingAny() bool {
  163. if sc == nil || len(sc.TCP) == 0 {
  164. return false
  165. }
  166. for _, h := range sc.TCP {
  167. if h.TCPForward != "" {
  168. return true
  169. }
  170. }
  171. return false
  172. }
  173. // IsTCPForwardingOnPort reports whether if ServeConfig is currently forwarding
  174. // in TCPForward mode on the given port. This is exclusive of Web/HTTPS serving.
  175. func (sc *ServeConfig) IsTCPForwardingOnPort(port uint16) bool {
  176. if sc == nil || sc.TCP[port] == nil {
  177. return false
  178. }
  179. return !sc.IsServingWeb(port)
  180. }
  181. // IsServingWeb reports whether if ServeConfig is currently serving Web
  182. // (HTTP/HTTPS) on the given port. This is exclusive of TCPForwarding.
  183. func (sc *ServeConfig) IsServingWeb(port uint16) bool {
  184. return sc.IsServingHTTP(port) || sc.IsServingHTTPS(port)
  185. }
  186. // IsServingHTTPS reports whether if ServeConfig is currently serving HTTPS on
  187. // the given port. This is exclusive of HTTP and TCPForwarding.
  188. func (sc *ServeConfig) IsServingHTTPS(port uint16) bool {
  189. if sc == nil || sc.TCP[port] == nil {
  190. return false
  191. }
  192. return sc.TCP[port].HTTPS
  193. }
  194. // IsServingHTTP reports whether if ServeConfig is currently serving HTTP on the
  195. // given port. This is exclusive of HTTPS and TCPForwarding.
  196. func (sc *ServeConfig) IsServingHTTP(port uint16) bool {
  197. if sc == nil || sc.TCP[port] == nil {
  198. return false
  199. }
  200. return sc.TCP[port].HTTP
  201. }
  202. // IsFunnelOn reports whether if ServeConfig is currently allowing funnel
  203. // traffic for any host:port.
  204. //
  205. // View version of ServeConfig.IsFunnelOn.
  206. func (v ServeConfigView) IsFunnelOn() bool { return v.ж.IsFunnelOn() }
  207. // IsFunnelOn reports whether if ServeConfig is currently allowing funnel
  208. // traffic for any host:port.
  209. func (sc *ServeConfig) IsFunnelOn() bool {
  210. if sc == nil {
  211. return false
  212. }
  213. for _, b := range sc.AllowFunnel {
  214. if b {
  215. return true
  216. }
  217. }
  218. return false
  219. }
  220. // CheckFunnelAccess checks whether Funnel access is allowed for the given node
  221. // and port.
  222. // It checks:
  223. // 1. HTTPS is enabled on the Tailnet
  224. // 2. the node has the "funnel" nodeAttr
  225. // 3. the port is allowed for Funnel
  226. //
  227. // The node arg should be the ipnstate.Status.Self node.
  228. func CheckFunnelAccess(port uint16, node *ipnstate.PeerStatus) error {
  229. if !node.HasCap(tailcfg.CapabilityHTTPS) {
  230. return errors.New("Funnel not available; HTTPS must be enabled. See https://tailscale.com/s/https.")
  231. }
  232. if !node.HasCap(tailcfg.NodeAttrFunnel) {
  233. return errors.New("Funnel not available; \"funnel\" node attribute not set. See https://tailscale.com/s/no-funnel.")
  234. }
  235. return CheckFunnelPort(port, node)
  236. }
  237. // CheckFunnelPort checks whether the given port is allowed for Funnel.
  238. // It uses the tailcfg.CapabilityFunnelPorts nodeAttr to determine the allowed
  239. // ports.
  240. func CheckFunnelPort(wantedPort uint16, node *ipnstate.PeerStatus) error {
  241. deny := func(allowedPorts string) error {
  242. if allowedPorts == "" {
  243. return fmt.Errorf("port %d is not allowed for funnel", wantedPort)
  244. }
  245. return fmt.Errorf("port %d is not allowed for funnel; allowed ports are: %v", wantedPort, allowedPorts)
  246. }
  247. var portsStr string
  248. parseAttr := func(attr string) (string, error) {
  249. u, err := url.Parse(attr)
  250. if err != nil {
  251. return "", deny("")
  252. }
  253. portsStr := u.Query().Get("ports")
  254. if portsStr == "" {
  255. return "", deny("")
  256. }
  257. u.RawQuery = ""
  258. if u.String() != string(tailcfg.CapabilityFunnelPorts) {
  259. return "", deny("")
  260. }
  261. return portsStr, nil
  262. }
  263. for attr := range node.CapMap {
  264. attr := string(attr)
  265. if !strings.HasPrefix(attr, string(tailcfg.CapabilityFunnelPorts)) {
  266. continue
  267. }
  268. var err error
  269. portsStr, err = parseAttr(attr)
  270. if err != nil {
  271. return err
  272. }
  273. break
  274. }
  275. if portsStr == "" {
  276. for _, attr := range node.Capabilities {
  277. attr := string(attr)
  278. if !strings.HasPrefix(attr, string(tailcfg.CapabilityFunnelPorts)) {
  279. continue
  280. }
  281. var err error
  282. portsStr, err = parseAttr(attr)
  283. if err != nil {
  284. return err
  285. }
  286. break
  287. }
  288. }
  289. if portsStr == "" {
  290. return deny("")
  291. }
  292. wantedPortString := strconv.Itoa(int(wantedPort))
  293. for _, ps := range strings.Split(portsStr, ",") {
  294. if ps == "" {
  295. continue
  296. }
  297. first, last, ok := strings.Cut(ps, "-")
  298. if !ok {
  299. if first == wantedPortString {
  300. return nil
  301. }
  302. continue
  303. }
  304. fp, err := strconv.ParseUint(first, 10, 16)
  305. if err != nil {
  306. continue
  307. }
  308. lp, err := strconv.ParseUint(last, 10, 16)
  309. if err != nil {
  310. continue
  311. }
  312. pr := tailcfg.PortRange{First: uint16(fp), Last: uint16(lp)}
  313. if pr.Contains(wantedPort) {
  314. return nil
  315. }
  316. }
  317. return deny(portsStr)
  318. }
  319. // RangeOverTCPs ranges over both background and foreground TCPs.
  320. // If the returned bool from the given f is false, then this function stops
  321. // iterating immediately and does not check other foreground configs.
  322. func (v ServeConfigView) RangeOverTCPs(f func(port uint16, _ TCPPortHandlerView) bool) {
  323. parentCont := true
  324. v.TCP().Range(func(k uint16, v TCPPortHandlerView) (cont bool) {
  325. parentCont = f(k, v)
  326. return parentCont
  327. })
  328. v.Foreground().Range(func(k string, v ServeConfigView) (cont bool) {
  329. if !parentCont {
  330. return false
  331. }
  332. v.TCP().Range(func(k uint16, v TCPPortHandlerView) (cont bool) {
  333. parentCont = f(k, v)
  334. return parentCont
  335. })
  336. return parentCont
  337. })
  338. }
  339. // RangeOverWebs ranges over both background and foreground Webs.
  340. // If the returned bool from the given f is false, then this function stops
  341. // iterating immediately and does not check other foreground configs.
  342. func (v ServeConfigView) RangeOverWebs(f func(_ HostPort, conf WebServerConfigView) bool) {
  343. parentCont := true
  344. v.Web().Range(func(k HostPort, v WebServerConfigView) (cont bool) {
  345. parentCont = f(k, v)
  346. return parentCont
  347. })
  348. v.Foreground().Range(func(k string, v ServeConfigView) (cont bool) {
  349. if !parentCont {
  350. return false
  351. }
  352. v.Web().Range(func(k HostPort, v WebServerConfigView) (cont bool) {
  353. parentCont = f(k, v)
  354. return parentCont
  355. })
  356. return parentCont
  357. })
  358. }
  359. // FindTCP returns the first TCP that matches with the given port. It
  360. // prefers a foreground match first followed by a background search if none
  361. // existed.
  362. func (v ServeConfigView) FindTCP(port uint16) (res TCPPortHandlerView, ok bool) {
  363. v.Foreground().Range(func(_ string, v ServeConfigView) (cont bool) {
  364. res, ok = v.TCP().GetOk(port)
  365. return !ok
  366. })
  367. if ok {
  368. return res, ok
  369. }
  370. return v.TCP().GetOk(port)
  371. }
  372. // FindWeb returns the first Web that matches with the given HostPort. It
  373. // prefers a foreground match first followed by a background search if none
  374. // existed.
  375. func (v ServeConfigView) FindWeb(hp HostPort) (res WebServerConfigView, ok bool) {
  376. v.Foreground().Range(func(_ string, v ServeConfigView) (cont bool) {
  377. res, ok = v.Web().GetOk(hp)
  378. return !ok
  379. })
  380. if ok {
  381. return res, ok
  382. }
  383. return v.Web().GetOk(hp)
  384. }
  385. // HasAllowFunnel returns whether this config has at least one AllowFunnel
  386. // set in the background or foreground configs.
  387. func (v ServeConfigView) HasAllowFunnel() bool {
  388. return v.AllowFunnel().Len() > 0 || func() bool {
  389. var exists bool
  390. v.Foreground().Range(func(k string, v ServeConfigView) (cont bool) {
  391. exists = v.AllowFunnel().Len() > 0
  392. return !exists
  393. })
  394. return exists
  395. }()
  396. }
  397. // FindFunnel reports whether target exists in in either the background AllowFunnel
  398. // or any of the foreground configs.
  399. func (v ServeConfigView) HasFunnelForTarget(target HostPort) bool {
  400. if v.AllowFunnel().Get(target) {
  401. return true
  402. }
  403. var exists bool
  404. v.Foreground().Range(func(_ string, v ServeConfigView) (cont bool) {
  405. if exists = v.AllowFunnel().Get(target); exists {
  406. return false
  407. }
  408. return true
  409. })
  410. return exists
  411. }