prefs.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package ipn
  4. import (
  5. "bytes"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "log"
  10. "net/netip"
  11. "os"
  12. "path/filepath"
  13. "reflect"
  14. "runtime"
  15. "strings"
  16. "tailscale.com/atomicfile"
  17. "tailscale.com/ipn/ipnstate"
  18. "tailscale.com/net/netaddr"
  19. "tailscale.com/net/tsaddr"
  20. "tailscale.com/tailcfg"
  21. "tailscale.com/types/persist"
  22. "tailscale.com/types/preftype"
  23. "tailscale.com/util/dnsname"
  24. )
  25. // DefaultControlURL is the URL base of the control plane
  26. // ("coordination server") for use when no explicit one is configured.
  27. // The default control plane is the hosted version run by Tailscale.com.
  28. const DefaultControlURL = "https://controlplane.tailscale.com"
  29. var (
  30. // ErrExitNodeIDAlreadySet is returned from (*Prefs).SetExitNodeIP when the
  31. // Prefs.ExitNodeID field is already set.
  32. ErrExitNodeIDAlreadySet = errors.New("cannot set ExitNodeIP when ExitNodeID is already set")
  33. )
  34. // IsLoginServerSynonym reports whether a URL is a drop-in replacement
  35. // for the primary Tailscale login server.
  36. func IsLoginServerSynonym(val any) bool {
  37. return val == "https://login.tailscale.com" || val == "https://controlplane.tailscale.com"
  38. }
  39. // Prefs are the user modifiable settings of the Tailscale node agent.
  40. type Prefs struct {
  41. // ControlURL is the URL of the control server to use.
  42. //
  43. // If empty, the default for new installs, DefaultControlURL
  44. // is used. It's set non-empty once the daemon has been started
  45. // for the first time.
  46. //
  47. // TODO(apenwarr): Make it safe to update this with SetPrefs().
  48. // Right now, you have to pass it in the initial prefs in Start(),
  49. // which is the only code that actually uses the ControlURL value.
  50. // It would be more consistent to restart controlclient
  51. // automatically whenever this variable changes.
  52. //
  53. // Meanwhile, you have to provide this as part of
  54. // Options.LegacyMigrationPrefs or Options.UpdatePrefs when
  55. // calling Backend.Start().
  56. ControlURL string
  57. // RouteAll specifies whether to accept subnets advertised by
  58. // other nodes on the Tailscale network. Note that this does not
  59. // include default routes (0.0.0.0/0 and ::/0), those are
  60. // controlled by ExitNodeID/IP below.
  61. RouteAll bool
  62. // AllowSingleHosts specifies whether to install routes for each
  63. // node IP on the tailscale network, in addition to a route for
  64. // the whole network.
  65. // This corresponds to the "tailscale up --host-routes" value,
  66. // which defaults to true.
  67. //
  68. // TODO(danderson): why do we have this? It dumps a lot of stuff
  69. // into the routing table, and a single network route _should_ be
  70. // all that we need. But when I turn this off in my tailscaled,
  71. // packets stop flowing. What's up with that?
  72. AllowSingleHosts bool
  73. // ExitNodeID and ExitNodeIP specify the node that should be used
  74. // as an exit node for internet traffic. At most one of these
  75. // should be non-zero.
  76. //
  77. // The preferred way to express the chosen node is ExitNodeID, but
  78. // in some cases it's not possible to use that ID (e.g. in the
  79. // linux CLI, before tailscaled has a netmap). For those
  80. // situations, we allow specifying the exit node by IP, and
  81. // ipnlocal.LocalBackend will translate the IP into an ID when the
  82. // node is found in the netmap.
  83. //
  84. // If the selected exit node doesn't exist (e.g. it's not part of
  85. // the current tailnet), or it doesn't offer exit node services, a
  86. // blackhole route will be installed on the local system to
  87. // prevent any traffic escaping to the local network.
  88. ExitNodeID tailcfg.StableNodeID
  89. ExitNodeIP netip.Addr
  90. // ExitNodeAllowLANAccess indicates whether locally accessible subnets should be
  91. // routed directly or via the exit node.
  92. ExitNodeAllowLANAccess bool
  93. // CorpDNS specifies whether to install the Tailscale network's
  94. // DNS configuration, if it exists.
  95. CorpDNS bool
  96. // RunSSH bool is whether this node should run an SSH
  97. // server, permitting access to peers according to the
  98. // policies as configured by the Tailnet's admin(s).
  99. RunSSH bool
  100. // WantRunning indicates whether networking should be active on
  101. // this node.
  102. WantRunning bool
  103. // LoggedOut indicates whether the user intends to be logged out.
  104. // There are other reasons we may be logged out, including no valid
  105. // keys.
  106. // We need to remember this state so that, on next startup, we can
  107. // generate the "Login" vs "Connect" buttons correctly, without having
  108. // to contact the server to confirm our nodekey status first.
  109. LoggedOut bool
  110. // ShieldsUp indicates whether to block all incoming connections,
  111. // regardless of the control-provided packet filter. If false, we
  112. // use the packet filter as provided. If true, we block incoming
  113. // connections. This overrides tailcfg.Hostinfo's ShieldsUp.
  114. ShieldsUp bool
  115. // AdvertiseTags specifies groups that this node wants to join, for
  116. // purposes of ACL enforcement. These can be referenced from the ACL
  117. // security policy. Note that advertising a tag doesn't guarantee that
  118. // the control server will allow you to take on the rights for that
  119. // tag.
  120. AdvertiseTags []string
  121. // Hostname is the hostname to use for identifying the node. If
  122. // not set, os.Hostname is used.
  123. Hostname string
  124. // NotepadURLs is a debugging setting that opens OAuth URLs in
  125. // notepad.exe on Windows, rather than loading them in a browser.
  126. //
  127. // apenwarr 2020-04-29: Unfortunately this is still needed sometimes.
  128. // Windows' default browser setting is sometimes screwy and this helps
  129. // users narrow it down a bit.
  130. NotepadURLs bool
  131. // ForceDaemon specifies whether a platform that normally
  132. // operates in "client mode" (that is, requires an active user
  133. // logged in with the GUI app running) should keep running after the
  134. // GUI ends and/or the user logs out.
  135. //
  136. // The only current applicable platform is Windows. This
  137. // forced Windows to go into "server mode" where Tailscale is
  138. // running even with no users logged in. This might also be
  139. // used for macOS in the future. This setting has no effect
  140. // for Linux/etc, which always operate in daemon mode.
  141. ForceDaemon bool `json:"ForceDaemon,omitempty"`
  142. // Egg is a optional debug flag.
  143. Egg bool `json:",omitempty"`
  144. // The following block of options only have an effect on Linux.
  145. // AdvertiseRoutes specifies CIDR prefixes to advertise into the
  146. // Tailscale network as reachable through the current
  147. // node.
  148. AdvertiseRoutes []netip.Prefix
  149. // NoSNAT specifies whether to source NAT traffic going to
  150. // destinations in AdvertiseRoutes. The default is to apply source
  151. // NAT, which makes the traffic appear to come from the router
  152. // machine rather than the peer's Tailscale IP.
  153. //
  154. // Disabling SNAT requires additional manual configuration in your
  155. // network to route Tailscale traffic back to the subnet relay
  156. // machine.
  157. //
  158. // Linux-only.
  159. NoSNAT bool
  160. // NetfilterMode specifies how much to manage netfilter rules for
  161. // Tailscale, if at all.
  162. NetfilterMode preftype.NetfilterMode
  163. // OperatorUser is the local machine user name who is allowed to
  164. // operate tailscaled without being root or using sudo.
  165. OperatorUser string `json:",omitempty"`
  166. // ProfileName is the desired name of the profile. If empty, then the user's
  167. // LoginName is used. It is only used for display purposes in the client UI
  168. // and CLI.
  169. ProfileName string `json:",omitempty"`
  170. // The Persist field is named 'Config' in the file for backward
  171. // compatibility with earlier versions.
  172. // TODO(apenwarr): We should move this out of here, it's not a pref.
  173. // We can maybe do that once we're sure which module should persist
  174. // it (backend or frontend?)
  175. Persist *persist.Persist `json:"Config"`
  176. }
  177. // MaskedPrefs is a Prefs with an associated bitmask of which fields are set.
  178. type MaskedPrefs struct {
  179. Prefs
  180. ControlURLSet bool `json:",omitempty"`
  181. RouteAllSet bool `json:",omitempty"`
  182. AllowSingleHostsSet bool `json:",omitempty"`
  183. ExitNodeIDSet bool `json:",omitempty"`
  184. ExitNodeIPSet bool `json:",omitempty"`
  185. ExitNodeAllowLANAccessSet bool `json:",omitempty"`
  186. CorpDNSSet bool `json:",omitempty"`
  187. RunSSHSet bool `json:",omitempty"`
  188. WantRunningSet bool `json:",omitempty"`
  189. LoggedOutSet bool `json:",omitempty"`
  190. ShieldsUpSet bool `json:",omitempty"`
  191. AdvertiseTagsSet bool `json:",omitempty"`
  192. HostnameSet bool `json:",omitempty"`
  193. NotepadURLsSet bool `json:",omitempty"`
  194. ForceDaemonSet bool `json:",omitempty"`
  195. EggSet bool `json:",omitempty"`
  196. AdvertiseRoutesSet bool `json:",omitempty"`
  197. NoSNATSet bool `json:",omitempty"`
  198. NetfilterModeSet bool `json:",omitempty"`
  199. OperatorUserSet bool `json:",omitempty"`
  200. ProfileNameSet bool `json:",omitempty"`
  201. }
  202. // ApplyEdits mutates p, assigning fields from m.Prefs for each MaskedPrefs
  203. // Set field that's true.
  204. func (p *Prefs) ApplyEdits(m *MaskedPrefs) {
  205. if p == nil {
  206. panic("can't edit nil Prefs")
  207. }
  208. pv := reflect.ValueOf(p).Elem()
  209. mv := reflect.ValueOf(m).Elem()
  210. mpv := reflect.ValueOf(&m.Prefs).Elem()
  211. fields := mv.NumField()
  212. for i := 1; i < fields; i++ {
  213. if mv.Field(i).Bool() {
  214. newFieldValue := mpv.Field(i - 1)
  215. pv.Field(i - 1).Set(newFieldValue)
  216. }
  217. }
  218. }
  219. // IsEmpty reports whether there are no masks set or if m is nil.
  220. func (m *MaskedPrefs) IsEmpty() bool {
  221. if m == nil {
  222. return true
  223. }
  224. mv := reflect.ValueOf(m).Elem()
  225. fields := mv.NumField()
  226. for i := 1; i < fields; i++ {
  227. if mv.Field(i).Bool() {
  228. return false
  229. }
  230. }
  231. return true
  232. }
  233. func (m *MaskedPrefs) Pretty() string {
  234. if m == nil {
  235. return "MaskedPrefs{<nil>}"
  236. }
  237. var sb strings.Builder
  238. sb.WriteString("MaskedPrefs{")
  239. mv := reflect.ValueOf(m).Elem()
  240. mt := mv.Type()
  241. mpv := reflect.ValueOf(&m.Prefs).Elem()
  242. first := true
  243. format := func(v reflect.Value) string {
  244. switch v.Type().Kind() {
  245. case reflect.String:
  246. return "%s=%q"
  247. case reflect.Slice:
  248. // []string
  249. if v.Type().Elem().Kind() == reflect.String {
  250. return "%s=%q"
  251. }
  252. }
  253. return "%s=%v"
  254. }
  255. for i := 1; i < mt.NumField(); i++ {
  256. name := mt.Field(i).Name
  257. if mv.Field(i).Bool() {
  258. if !first {
  259. sb.WriteString(" ")
  260. }
  261. first = false
  262. f := mpv.Field(i - 1)
  263. fmt.Fprintf(&sb, format(f),
  264. strings.TrimSuffix(name, "Set"),
  265. f.Interface())
  266. }
  267. }
  268. sb.WriteString("}")
  269. return sb.String()
  270. }
  271. // IsEmpty reports whether p is nil or pointing to a Prefs zero value.
  272. func (p *Prefs) IsEmpty() bool { return p == nil || p.Equals(&Prefs{}) }
  273. func (p PrefsView) Pretty() string { return p.ж.Pretty() }
  274. func (p *Prefs) Pretty() string { return p.pretty(runtime.GOOS) }
  275. func (p *Prefs) pretty(goos string) string {
  276. var sb strings.Builder
  277. sb.WriteString("Prefs{")
  278. fmt.Fprintf(&sb, "ra=%v ", p.RouteAll)
  279. if !p.AllowSingleHosts {
  280. sb.WriteString("mesh=false ")
  281. }
  282. fmt.Fprintf(&sb, "dns=%v want=%v ", p.CorpDNS, p.WantRunning)
  283. if p.RunSSH {
  284. sb.WriteString("ssh=true ")
  285. }
  286. if p.LoggedOut {
  287. sb.WriteString("loggedout=true ")
  288. }
  289. if p.ForceDaemon {
  290. sb.WriteString("server=true ")
  291. }
  292. if p.NotepadURLs {
  293. sb.WriteString("notepad=true ")
  294. }
  295. if p.ShieldsUp {
  296. sb.WriteString("shields=true ")
  297. }
  298. if p.ExitNodeIP.IsValid() {
  299. fmt.Fprintf(&sb, "exit=%v lan=%t ", p.ExitNodeIP, p.ExitNodeAllowLANAccess)
  300. } else if !p.ExitNodeID.IsZero() {
  301. fmt.Fprintf(&sb, "exit=%v lan=%t ", p.ExitNodeID, p.ExitNodeAllowLANAccess)
  302. }
  303. if len(p.AdvertiseRoutes) > 0 || goos == "linux" {
  304. fmt.Fprintf(&sb, "routes=%v ", p.AdvertiseRoutes)
  305. }
  306. if len(p.AdvertiseRoutes) > 0 || p.NoSNAT {
  307. fmt.Fprintf(&sb, "snat=%v ", !p.NoSNAT)
  308. }
  309. if len(p.AdvertiseTags) > 0 {
  310. fmt.Fprintf(&sb, "tags=%s ", strings.Join(p.AdvertiseTags, ","))
  311. }
  312. if goos == "linux" {
  313. fmt.Fprintf(&sb, "nf=%v ", p.NetfilterMode)
  314. }
  315. if p.ControlURL != "" && p.ControlURL != DefaultControlURL {
  316. fmt.Fprintf(&sb, "url=%q ", p.ControlURL)
  317. }
  318. if p.Hostname != "" {
  319. fmt.Fprintf(&sb, "host=%q ", p.Hostname)
  320. }
  321. if p.OperatorUser != "" {
  322. fmt.Fprintf(&sb, "op=%q ", p.OperatorUser)
  323. }
  324. if p.Persist != nil {
  325. sb.WriteString(p.Persist.Pretty())
  326. } else {
  327. sb.WriteString("Persist=nil")
  328. }
  329. sb.WriteString("}")
  330. return sb.String()
  331. }
  332. func (p PrefsView) ToBytes() []byte {
  333. return p.ж.ToBytes()
  334. }
  335. func (p *Prefs) ToBytes() []byte {
  336. data, err := json.MarshalIndent(p, "", "\t")
  337. if err != nil {
  338. log.Fatalf("Prefs marshal: %v\n", err)
  339. }
  340. return data
  341. }
  342. func (p PrefsView) Equals(p2 PrefsView) bool {
  343. return p.ж.Equals(p2.ж)
  344. }
  345. func (p *Prefs) Equals(p2 *Prefs) bool {
  346. if p == nil && p2 == nil {
  347. return true
  348. }
  349. if p == nil || p2 == nil {
  350. return false
  351. }
  352. return p != nil && p2 != nil &&
  353. p.ControlURL == p2.ControlURL &&
  354. p.RouteAll == p2.RouteAll &&
  355. p.AllowSingleHosts == p2.AllowSingleHosts &&
  356. p.ExitNodeID == p2.ExitNodeID &&
  357. p.ExitNodeIP == p2.ExitNodeIP &&
  358. p.ExitNodeAllowLANAccess == p2.ExitNodeAllowLANAccess &&
  359. p.CorpDNS == p2.CorpDNS &&
  360. p.RunSSH == p2.RunSSH &&
  361. p.WantRunning == p2.WantRunning &&
  362. p.LoggedOut == p2.LoggedOut &&
  363. p.NotepadURLs == p2.NotepadURLs &&
  364. p.ShieldsUp == p2.ShieldsUp &&
  365. p.NoSNAT == p2.NoSNAT &&
  366. p.NetfilterMode == p2.NetfilterMode &&
  367. p.OperatorUser == p2.OperatorUser &&
  368. p.Hostname == p2.Hostname &&
  369. p.ForceDaemon == p2.ForceDaemon &&
  370. compareIPNets(p.AdvertiseRoutes, p2.AdvertiseRoutes) &&
  371. compareStrings(p.AdvertiseTags, p2.AdvertiseTags) &&
  372. p.Persist.Equals(p2.Persist) &&
  373. p.ProfileName == p2.ProfileName
  374. }
  375. func compareIPNets(a, b []netip.Prefix) bool {
  376. if len(a) != len(b) {
  377. return false
  378. }
  379. for i := range a {
  380. if a[i] != b[i] {
  381. return false
  382. }
  383. }
  384. return true
  385. }
  386. func compareStrings(a, b []string) bool {
  387. if len(a) != len(b) {
  388. return false
  389. }
  390. for i := range a {
  391. if a[i] != b[i] {
  392. return false
  393. }
  394. }
  395. return true
  396. }
  397. // NewPrefs returns the default preferences to use.
  398. func NewPrefs() *Prefs {
  399. // Provide default values for options which might be missing
  400. // from the json data for any reason. The json can still
  401. // override them to false.
  402. return &Prefs{
  403. // ControlURL is explicitly not set to signal that
  404. // it's not yet configured, which relaxes the CLI "up"
  405. // safety net features. It will get set to DefaultControlURL
  406. // on first up. Or, if not, DefaultControlURL will be used
  407. // later anyway.
  408. ControlURL: "",
  409. RouteAll: true,
  410. AllowSingleHosts: true,
  411. CorpDNS: true,
  412. WantRunning: false,
  413. NetfilterMode: preftype.NetfilterOn,
  414. }
  415. }
  416. // ControlURLOrDefault returns the coordination server's URL base.
  417. //
  418. // If not configured, or if the configured value is a legacy name equivalent to
  419. // the default, then DefaultControlURL is returned instead.
  420. func (p PrefsView) ControlURLOrDefault() string {
  421. return p.ж.ControlURLOrDefault()
  422. }
  423. // ControlURLOrDefault returns the coordination server's URL base.
  424. //
  425. // If not configured, or if the configured value is a legacy name equivalent to
  426. // the default, then DefaultControlURL is returned instead.
  427. func (p *Prefs) ControlURLOrDefault() string {
  428. if p.ControlURL != "" {
  429. if p.ControlURL != DefaultControlURL && IsLoginServerSynonym(p.ControlURL) {
  430. return DefaultControlURL
  431. }
  432. return p.ControlURL
  433. }
  434. return DefaultControlURL
  435. }
  436. // AdminPageURL returns the admin web site URL for the current ControlURL.
  437. func (p PrefsView) AdminPageURL() string { return p.ж.AdminPageURL() }
  438. // AdminPageURL returns the admin web site URL for the current ControlURL.
  439. func (p *Prefs) AdminPageURL() string {
  440. url := p.ControlURLOrDefault()
  441. if IsLoginServerSynonym(url) {
  442. // TODO(crawshaw): In future release, make this https://console.tailscale.com
  443. url = "https://login.tailscale.com"
  444. }
  445. return url + "/admin/machines"
  446. }
  447. // AdvertisesExitNode reports whether p is advertising both the v4 and
  448. // v6 /0 exit node routes.
  449. func (p PrefsView) AdvertisesExitNode() bool { return p.ж.AdvertisesExitNode() }
  450. // AdvertisesExitNode reports whether p is advertising both the v4 and
  451. // v6 /0 exit node routes.
  452. func (p *Prefs) AdvertisesExitNode() bool {
  453. if p == nil {
  454. return false
  455. }
  456. return tsaddr.ContainsExitRoutes(p.AdvertiseRoutes)
  457. }
  458. // SetAdvertiseExitNode mutates p (if non-nil) to add or remove the two
  459. // /0 exit node routes.
  460. func (p *Prefs) SetAdvertiseExitNode(runExit bool) {
  461. if p == nil {
  462. return
  463. }
  464. all := p.AdvertiseRoutes
  465. p.AdvertiseRoutes = p.AdvertiseRoutes[:0]
  466. for _, r := range all {
  467. if r.Bits() != 0 {
  468. p.AdvertiseRoutes = append(p.AdvertiseRoutes, r)
  469. }
  470. }
  471. if !runExit {
  472. return
  473. }
  474. p.AdvertiseRoutes = append(p.AdvertiseRoutes,
  475. netip.PrefixFrom(netaddr.IPv4(0, 0, 0, 0), 0),
  476. netip.PrefixFrom(netip.IPv6Unspecified(), 0))
  477. }
  478. // peerWithTailscaleIP returns the peer in st with the provided
  479. // Tailscale IP.
  480. func peerWithTailscaleIP(st *ipnstate.Status, ip netip.Addr) (ps *ipnstate.PeerStatus, ok bool) {
  481. for _, ps := range st.Peer {
  482. for _, ip2 := range ps.TailscaleIPs {
  483. if ip == ip2 {
  484. return ps, true
  485. }
  486. }
  487. }
  488. return nil, false
  489. }
  490. func isRemoteIP(st *ipnstate.Status, ip netip.Addr) bool {
  491. for _, selfIP := range st.TailscaleIPs {
  492. if ip == selfIP {
  493. return false
  494. }
  495. }
  496. return true
  497. }
  498. // ClearExitNode sets the ExitNodeID and ExitNodeIP to their zero values.
  499. func (p *Prefs) ClearExitNode() {
  500. p.ExitNodeID = ""
  501. p.ExitNodeIP = netip.Addr{}
  502. }
  503. // ExitNodeLocalIPError is returned when the requested IP address for an exit
  504. // node belongs to the local machine.
  505. type ExitNodeLocalIPError struct {
  506. hostOrIP string
  507. }
  508. func (e ExitNodeLocalIPError) Error() string {
  509. return fmt.Sprintf("cannot use %s as an exit node as it is a local IP address to this machine", e.hostOrIP)
  510. }
  511. func exitNodeIPOfArg(s string, st *ipnstate.Status) (ip netip.Addr, err error) {
  512. if s == "" {
  513. return ip, os.ErrInvalid
  514. }
  515. ip, err = netip.ParseAddr(s)
  516. if err == nil {
  517. // If we're online already and have a netmap, double check that the IP
  518. // address specified is valid.
  519. if st.BackendState == "Running" {
  520. ps, ok := peerWithTailscaleIP(st, ip)
  521. if !ok {
  522. return ip, fmt.Errorf("no node found in netmap with IP %v", ip)
  523. }
  524. if !ps.ExitNodeOption {
  525. return ip, fmt.Errorf("node %v is not advertising an exit node", ip)
  526. }
  527. }
  528. if !isRemoteIP(st, ip) {
  529. return ip, ExitNodeLocalIPError{s}
  530. }
  531. return ip, nil
  532. }
  533. match := 0
  534. for _, ps := range st.Peer {
  535. baseName := dnsname.TrimSuffix(ps.DNSName, st.MagicDNSSuffix)
  536. if !strings.EqualFold(s, baseName) {
  537. continue
  538. }
  539. match++
  540. if len(ps.TailscaleIPs) == 0 {
  541. return ip, fmt.Errorf("node %q has no Tailscale IP?", s)
  542. }
  543. if !ps.ExitNodeOption {
  544. return ip, fmt.Errorf("node %q is not advertising an exit node", s)
  545. }
  546. ip = ps.TailscaleIPs[0]
  547. }
  548. switch match {
  549. case 0:
  550. return ip, fmt.Errorf("invalid value %q for --exit-node; must be IP or unique node name", s)
  551. case 1:
  552. if !isRemoteIP(st, ip) {
  553. return ip, ExitNodeLocalIPError{s}
  554. }
  555. return ip, nil
  556. default:
  557. return ip, fmt.Errorf("ambiguous exit node name %q", s)
  558. }
  559. }
  560. // SetExitNodeIP validates and sets the ExitNodeIP from a user-provided string
  561. // specifying either an IP address or a MagicDNS base name ("foo", as opposed to
  562. // "foo.bar.beta.tailscale.net"). This method does not mutate ExitNodeID and
  563. // will fail if ExitNodeID is already set.
  564. func (p *Prefs) SetExitNodeIP(s string, st *ipnstate.Status) error {
  565. if !p.ExitNodeID.IsZero() {
  566. return ErrExitNodeIDAlreadySet
  567. }
  568. ip, err := exitNodeIPOfArg(s, st)
  569. if err == nil {
  570. p.ExitNodeIP = ip
  571. }
  572. return err
  573. }
  574. // ShouldSSHBeRunning reports whether the SSH server should be running based on
  575. // the prefs.
  576. func (p PrefsView) ShouldSSHBeRunning() bool {
  577. return p.Valid() && p.ж.ShouldSSHBeRunning()
  578. }
  579. // ShouldSSHBeRunning reports whether the SSH server should be running based on
  580. // the prefs.
  581. func (p *Prefs) ShouldSSHBeRunning() bool {
  582. return p.WantRunning && p.RunSSH
  583. }
  584. // PrefsFromBytes deserializes Prefs from a JSON blob.
  585. func PrefsFromBytes(b []byte) (*Prefs, error) {
  586. p := NewPrefs()
  587. if len(b) == 0 {
  588. return p, nil
  589. }
  590. persist := &persist.Persist{}
  591. err := json.Unmarshal(b, persist)
  592. if err == nil && (persist.Provider != "" || persist.LoginName != "") {
  593. // old-style relaynode config; import it
  594. p.Persist = persist
  595. } else {
  596. err = json.Unmarshal(b, &p)
  597. if err != nil {
  598. log.Printf("Prefs parse: %v: %v\n", err, b)
  599. }
  600. }
  601. return p, err
  602. }
  603. var jsonEscapedZero = []byte(`\u0000`)
  604. // LoadPrefs loads a legacy relaynode config file into Prefs
  605. // with sensible migration defaults set.
  606. func LoadPrefs(filename string) (*Prefs, error) {
  607. data, err := os.ReadFile(filename)
  608. if err != nil {
  609. return nil, fmt.Errorf("LoadPrefs open: %w", err) // err includes path
  610. }
  611. if bytes.Contains(data, jsonEscapedZero) {
  612. // Tailscale 1.2.0 - 1.2.8 on Windows had a memory corruption bug
  613. // in the backend process that ended up sending NULL bytes over JSON
  614. // to the frontend which wrote them out to JSON files on disk.
  615. // So if we see one, treat is as corrupt and the user will need
  616. // to log in again. (better than crashing)
  617. return nil, os.ErrNotExist
  618. }
  619. p, err := PrefsFromBytes(data)
  620. if err != nil {
  621. return nil, fmt.Errorf("LoadPrefs(%q) decode: %w", filename, err)
  622. }
  623. return p, nil
  624. }
  625. func SavePrefs(filename string, p *Prefs) {
  626. log.Printf("Saving prefs %v %v\n", filename, p.Pretty())
  627. data := p.ToBytes()
  628. os.MkdirAll(filepath.Dir(filename), 0700)
  629. if err := atomicfile.WriteFile(filename, data, 0600); err != nil {
  630. log.Printf("SavePrefs: %v\n", err)
  631. }
  632. }
  633. // ProfileID is an auto-generated system-wide unique identifier for a login
  634. // profile. It is a 4 character hex string like "1ab3".
  635. type ProfileID string
  636. // WindowsUserID is a userid (suitable for passing to ipnauth.LookupUserFromID
  637. // or os/user.LookupId) but only set on Windows. It's empty on all other
  638. // platforms, unless envknob.GOOS is in used, making Linux act like Windows for
  639. // tests.
  640. type WindowsUserID string
  641. // LoginProfile represents a single login profile as managed
  642. // by the ProfileManager.
  643. type LoginProfile struct {
  644. // ID is a unique identifier for this profile.
  645. // It is assigned on creation and never changes.
  646. // It may seem redundant to have both ID and UserProfile.ID
  647. // but they are different things. UserProfile.ID may change
  648. // over time (e.g. if a device is tagged).
  649. ID ProfileID
  650. // Name is the user-visible name of this profile.
  651. // It is filled in from the UserProfile.LoginName field.
  652. Name string
  653. // Key is the StateKey under which the profile is stored.
  654. // It is assigned once at profile creation time and never changes.
  655. Key StateKey
  656. // UserProfile is the server provided UserProfile for this profile.
  657. // This is updated whenever the server provides a new UserProfile.
  658. UserProfile tailcfg.UserProfile
  659. // NodeID is the NodeID of the node that this profile is logged into.
  660. // This should be stable across tagging and untagging nodes.
  661. // It may seem redundant to check against both the UserProfile.UserID
  662. // and the NodeID. However the NodeID can change if the node is deleted
  663. // from the admin panel.
  664. NodeID tailcfg.StableNodeID
  665. // LocalUserID is the user ID of the user who created this profile.
  666. // It is only relevant on Windows where we have a multi-user system.
  667. // It is assigned once at profile creation time and never changes.
  668. LocalUserID WindowsUserID
  669. // ControlURL is the URL of the control server that this profile is logged
  670. // into.
  671. ControlURL string
  672. }