envknob.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package envknob provides access to environment-variable tweakable
  4. // debug settings.
  5. //
  6. // These are primarily knobs used by Tailscale developers during
  7. // development or by users when instructed to by Tailscale developers
  8. // when debugging something. They are not a stable interface and may
  9. // be removed or any time.
  10. //
  11. // A related package, control/controlknobs, are knobs that can be
  12. // changed at runtime by the control plane. Sometimes both are used:
  13. // an envknob for the default/explicit value, else falling back
  14. // to the controlknob value.
  15. package envknob
  16. import (
  17. "bufio"
  18. "fmt"
  19. "io"
  20. "log"
  21. "os"
  22. "path/filepath"
  23. "runtime"
  24. "sort"
  25. "strconv"
  26. "strings"
  27. "sync"
  28. "sync/atomic"
  29. "time"
  30. "tailscale.com/types/opt"
  31. "tailscale.com/version"
  32. "tailscale.com/version/distro"
  33. )
  34. var (
  35. mu sync.Mutex
  36. set = map[string]string{}
  37. regStr = map[string]*string{}
  38. regBool = map[string]*bool{}
  39. regOptBool = map[string]*opt.Bool{}
  40. regDuration = map[string]*time.Duration{}
  41. regInt = map[string]*int{}
  42. )
  43. func noteEnv(k, v string) {
  44. mu.Lock()
  45. defer mu.Unlock()
  46. noteEnvLocked(k, v)
  47. }
  48. func noteEnvLocked(k, v string) {
  49. if v != "" {
  50. set[k] = v
  51. } else {
  52. delete(set, k)
  53. }
  54. }
  55. // logf is logger.Logf, but logger depends on envknob, so for circular
  56. // dependency reasons, make a type alias (so it's still assignable,
  57. // but has nice docs here).
  58. type logf = func(format string, args ...any)
  59. // LogCurrent logs the currently set environment knobs.
  60. func LogCurrent(logf logf) {
  61. mu.Lock()
  62. defer mu.Unlock()
  63. list := make([]string, 0, len(set))
  64. for k := range set {
  65. list = append(list, k)
  66. }
  67. sort.Strings(list)
  68. for _, k := range list {
  69. logf("envknob: %s=%q", k, set[k])
  70. }
  71. }
  72. // Setenv changes an environment variable.
  73. //
  74. // It is not safe for concurrent reading of environment variables via the
  75. // Register functions. All Setenv calls are meant to happen early in main before
  76. // any goroutines are started.
  77. func Setenv(envVar, val string) {
  78. mu.Lock()
  79. defer mu.Unlock()
  80. os.Setenv(envVar, val)
  81. noteEnvLocked(envVar, val)
  82. if p := regStr[envVar]; p != nil {
  83. *p = val
  84. }
  85. if p := regBool[envVar]; p != nil {
  86. setBoolLocked(p, envVar, val)
  87. }
  88. if p := regOptBool[envVar]; p != nil {
  89. setOptBoolLocked(p, envVar, val)
  90. }
  91. if p := regDuration[envVar]; p != nil {
  92. setDurationLocked(p, envVar, val)
  93. }
  94. }
  95. // String returns the named environment variable, using os.Getenv.
  96. //
  97. // If the variable is non-empty, it's also tracked & logged as being
  98. // an in-use knob.
  99. func String(envVar string) string {
  100. v := os.Getenv(envVar)
  101. noteEnv(envVar, v)
  102. return v
  103. }
  104. // RegisterString returns a func that gets the named environment variable,
  105. // without a map lookup per call. It assumes that mutations happen via
  106. // envknob.Setenv.
  107. func RegisterString(envVar string) func() string {
  108. mu.Lock()
  109. defer mu.Unlock()
  110. p, ok := regStr[envVar]
  111. if !ok {
  112. val := os.Getenv(envVar)
  113. if val != "" {
  114. noteEnvLocked(envVar, val)
  115. }
  116. p = &val
  117. regStr[envVar] = p
  118. }
  119. return func() string { return *p }
  120. }
  121. // RegisterBool returns a func that gets the named environment variable,
  122. // without a map lookup per call. It assumes that mutations happen via
  123. // envknob.Setenv.
  124. func RegisterBool(envVar string) func() bool {
  125. mu.Lock()
  126. defer mu.Unlock()
  127. p, ok := regBool[envVar]
  128. if !ok {
  129. var b bool
  130. p = &b
  131. setBoolLocked(p, envVar, os.Getenv(envVar))
  132. regBool[envVar] = p
  133. }
  134. return func() bool { return *p }
  135. }
  136. // RegisterOptBool returns a func that gets the named environment variable,
  137. // without a map lookup per call. It assumes that mutations happen via
  138. // envknob.Setenv.
  139. func RegisterOptBool(envVar string) func() opt.Bool {
  140. mu.Lock()
  141. defer mu.Unlock()
  142. p, ok := regOptBool[envVar]
  143. if !ok {
  144. var b opt.Bool
  145. p = &b
  146. setOptBoolLocked(p, envVar, os.Getenv(envVar))
  147. regOptBool[envVar] = p
  148. }
  149. return func() opt.Bool { return *p }
  150. }
  151. // RegisterDuration returns a func that gets the named environment variable as a
  152. // duration, without a map lookup per call. It assumes that any mutations happen
  153. // via envknob.Setenv.
  154. func RegisterDuration(envVar string) func() time.Duration {
  155. mu.Lock()
  156. defer mu.Unlock()
  157. p, ok := regDuration[envVar]
  158. if !ok {
  159. val := os.Getenv(envVar)
  160. if val != "" {
  161. noteEnvLocked(envVar, val)
  162. }
  163. p = new(time.Duration)
  164. setDurationLocked(p, envVar, val)
  165. regDuration[envVar] = p
  166. }
  167. return func() time.Duration { return *p }
  168. }
  169. // RegisterInt returns a func that gets the named environment variable as an
  170. // integer, without a map lookup per call. It assumes that any mutations happen
  171. // via envknob.Setenv.
  172. func RegisterInt(envVar string) func() int {
  173. mu.Lock()
  174. defer mu.Unlock()
  175. p, ok := regInt[envVar]
  176. if !ok {
  177. val := os.Getenv(envVar)
  178. if val != "" {
  179. noteEnvLocked(envVar, val)
  180. }
  181. p = new(int)
  182. setIntLocked(p, envVar, val)
  183. regInt[envVar] = p
  184. }
  185. return func() int { return *p }
  186. }
  187. func setBoolLocked(p *bool, envVar, val string) {
  188. noteEnvLocked(envVar, val)
  189. if val == "" {
  190. *p = false
  191. return
  192. }
  193. var err error
  194. *p, err = strconv.ParseBool(val)
  195. if err != nil {
  196. log.Fatalf("invalid boolean environment variable %s value %q", envVar, val)
  197. }
  198. }
  199. func setOptBoolLocked(p *opt.Bool, envVar, val string) {
  200. noteEnvLocked(envVar, val)
  201. if val == "" {
  202. *p = ""
  203. return
  204. }
  205. b, err := strconv.ParseBool(val)
  206. if err != nil {
  207. log.Fatalf("invalid boolean environment variable %s value %q", envVar, val)
  208. }
  209. p.Set(b)
  210. }
  211. func setDurationLocked(p *time.Duration, envVar, val string) {
  212. noteEnvLocked(envVar, val)
  213. if val == "" {
  214. *p = 0
  215. return
  216. }
  217. var err error
  218. *p, err = time.ParseDuration(val)
  219. if err != nil {
  220. log.Fatalf("invalid duration environment variable %s value %q", envVar, val)
  221. }
  222. }
  223. func setIntLocked(p *int, envVar, val string) {
  224. noteEnvLocked(envVar, val)
  225. if val == "" {
  226. *p = 0
  227. return
  228. }
  229. var err error
  230. *p, err = strconv.Atoi(val)
  231. if err != nil {
  232. log.Fatalf("invalid int environment variable %s value %q", envVar, val)
  233. }
  234. }
  235. // Bool returns the boolean value of the named environment variable.
  236. // If the variable is not set, it returns false.
  237. // An invalid value exits the binary with a failure.
  238. func Bool(envVar string) bool {
  239. return boolOr(envVar, false)
  240. }
  241. // BoolDefaultTrue is like Bool, but returns true by default if the
  242. // environment variable isn't present.
  243. func BoolDefaultTrue(envVar string) bool {
  244. return boolOr(envVar, true)
  245. }
  246. func boolOr(envVar string, implicitValue bool) bool {
  247. assertNotInInit()
  248. val := os.Getenv(envVar)
  249. if val == "" {
  250. return implicitValue
  251. }
  252. b, err := strconv.ParseBool(val)
  253. if err == nil {
  254. noteEnv(envVar, strconv.FormatBool(b)) // canonicalize
  255. return b
  256. }
  257. log.Fatalf("invalid boolean environment variable %s value %q", envVar, val)
  258. panic("unreachable")
  259. }
  260. // LookupBool returns the boolean value of the named environment value.
  261. // The ok result is whether a value was set.
  262. // If the value isn't a valid int, it exits the program with a failure.
  263. func LookupBool(envVar string) (v bool, ok bool) {
  264. assertNotInInit()
  265. val := os.Getenv(envVar)
  266. if val == "" {
  267. return false, false
  268. }
  269. b, err := strconv.ParseBool(val)
  270. if err == nil {
  271. return b, true
  272. }
  273. log.Fatalf("invalid boolean environment variable %s value %q", envVar, val)
  274. panic("unreachable")
  275. }
  276. // OptBool is like Bool, but returns an opt.Bool, so the caller can
  277. // distinguish between implicitly and explicitly false.
  278. func OptBool(envVar string) opt.Bool {
  279. assertNotInInit()
  280. b, ok := LookupBool(envVar)
  281. if !ok {
  282. return ""
  283. }
  284. var ret opt.Bool
  285. ret.Set(b)
  286. return ret
  287. }
  288. // LookupInt returns the integer value of the named environment value.
  289. // The ok result is whether a value was set.
  290. // If the value isn't a valid int, it exits the program with a failure.
  291. func LookupInt(envVar string) (v int, ok bool) {
  292. assertNotInInit()
  293. val := os.Getenv(envVar)
  294. if val == "" {
  295. return 0, false
  296. }
  297. v, err := strconv.Atoi(val)
  298. if err == nil {
  299. noteEnv(envVar, val)
  300. return v, true
  301. }
  302. log.Fatalf("invalid integer environment variable %s: %v", envVar, val)
  303. panic("unreachable")
  304. }
  305. // LookupIntSized returns the integer value of the named environment value
  306. // parsed in base and with a maximum bit size bitSize.
  307. // The ok result is whether a value was set.
  308. // If the value isn't a valid int, it exits the program with a failure.
  309. func LookupIntSized(envVar string, base, bitSize int) (v int, ok bool) {
  310. assertNotInInit()
  311. val := os.Getenv(envVar)
  312. if val == "" {
  313. return 0, false
  314. }
  315. i, err := strconv.ParseInt(val, base, bitSize)
  316. if err == nil {
  317. v = int(i)
  318. noteEnv(envVar, val)
  319. return v, true
  320. }
  321. log.Fatalf("invalid integer environment variable %s: %v", envVar, val)
  322. panic("unreachable")
  323. }
  324. // LookupUintSized returns the unsigned integer value of the named environment
  325. // value parsed in base and with a maximum bit size bitSize.
  326. // The ok result is whether a value was set.
  327. // If the value isn't a valid int, it exits the program with a failure.
  328. func LookupUintSized(envVar string, base, bitSize int) (v uint, ok bool) {
  329. assertNotInInit()
  330. val := os.Getenv(envVar)
  331. if val == "" {
  332. return 0, false
  333. }
  334. i, err := strconv.ParseUint(val, base, bitSize)
  335. if err == nil {
  336. v = uint(i)
  337. noteEnv(envVar, val)
  338. return v, true
  339. }
  340. log.Fatalf("invalid unsigned integer environment variable %s: %v", envVar, val)
  341. panic("unreachable")
  342. }
  343. // UseWIPCode is whether TAILSCALE_USE_WIP_CODE is set to permit use
  344. // of Work-In-Progress code.
  345. func UseWIPCode() bool { return Bool("TAILSCALE_USE_WIP_CODE") }
  346. // CanSSHD reports whether the Tailscale SSH server is allowed to run.
  347. //
  348. // If disabled (when this reports false), the SSH server won't start (won't
  349. // intercept port 22) if previously configured to do so and any attempt to
  350. // re-enable it will result in an error.
  351. func CanSSHD() bool { return !Bool("TS_DISABLE_SSH_SERVER") }
  352. // CanTaildrop reports whether the Taildrop feature is allowed to function.
  353. //
  354. // If disabled, Taildrop won't receive files regardless of user & server config.
  355. func CanTaildrop() bool { return !Bool("TS_DISABLE_TAILDROP") }
  356. // SSHPolicyFile returns the path, if any, to the SSHPolicy JSON file for development.
  357. func SSHPolicyFile() string { return String("TS_DEBUG_SSH_POLICY_FILE") }
  358. // SSHIgnoreTailnetPolicy reports whether to ignore the Tailnet SSH policy for development.
  359. func SSHIgnoreTailnetPolicy() bool { return Bool("TS_DEBUG_SSH_IGNORE_TAILNET_POLICY") }
  360. // TKASkipSignatureCheck reports whether to skip node-key signature checking for development.
  361. func TKASkipSignatureCheck() bool { return Bool("TS_UNSAFE_SKIP_NKS_VERIFICATION") }
  362. // CrashOnUnexpected reports whether the Tailscale client should panic
  363. // on unexpected conditions. If TS_DEBUG_CRASH_ON_UNEXPECTED is set, that's
  364. // used. Otherwise the default value is true for unstable builds.
  365. func CrashOnUnexpected() bool {
  366. if v, ok := crashOnUnexpected().Get(); ok {
  367. return v
  368. }
  369. return version.IsUnstableBuild()
  370. }
  371. var crashOnUnexpected = RegisterOptBool("TS_DEBUG_CRASH_ON_UNEXPECTED")
  372. // NoLogsNoSupport reports whether the client's opted out of log uploads and
  373. // technical support.
  374. func NoLogsNoSupport() bool {
  375. return Bool("TS_NO_LOGS_NO_SUPPORT")
  376. }
  377. var allowRemoteUpdate = RegisterBool("TS_ALLOW_ADMIN_CONSOLE_REMOTE_UPDATE")
  378. // AllowsRemoteUpdate reports whether this node has opted-in to letting the
  379. // Tailscale control plane initiate a Tailscale update (e.g. on behalf of an
  380. // admin on the admin console).
  381. func AllowsRemoteUpdate() bool { return allowRemoteUpdate() }
  382. // SetNoLogsNoSupport enables no-logs-no-support mode.
  383. func SetNoLogsNoSupport() {
  384. Setenv("TS_NO_LOGS_NO_SUPPORT", "true")
  385. }
  386. // notInInit is set true the first time we've seen a non-init stack trace.
  387. var notInInit atomic.Bool
  388. func assertNotInInit() {
  389. if notInInit.Load() {
  390. return
  391. }
  392. skip := 0
  393. for {
  394. pc, _, _, ok := runtime.Caller(skip)
  395. if !ok {
  396. notInInit.Store(true)
  397. return
  398. }
  399. fu := runtime.FuncForPC(pc)
  400. if fu == nil {
  401. return
  402. }
  403. name := fu.Name()
  404. name = strings.TrimRightFunc(name, func(r rune) bool { return r >= '0' && r <= '9' })
  405. if strings.HasSuffix(name, ".init") || strings.HasSuffix(name, ".init.") {
  406. stack := make([]byte, 1<<10)
  407. stack = stack[:runtime.Stack(stack, false)]
  408. envCheckedInInitStack = stack
  409. }
  410. skip++
  411. }
  412. }
  413. var envCheckedInInitStack []byte
  414. // PanicIfAnyEnvCheckedInInit panics if environment variables were read during
  415. // init.
  416. func PanicIfAnyEnvCheckedInInit() {
  417. if envCheckedInInitStack != nil {
  418. panic("envknob check of called from init function: " + string(envCheckedInInitStack))
  419. }
  420. }
  421. var applyDiskConfigErr error
  422. // ApplyDiskConfigError returns the most recent result of ApplyDiskConfig.
  423. func ApplyDiskConfigError() error { return applyDiskConfigErr }
  424. // ApplyDiskConfig returns a platform-specific config file of environment
  425. // keys/values and applies them. On Linux and Unix operating systems, it's a
  426. // no-op and always returns nil. If no platform-specific config file is found,
  427. // it also returns nil.
  428. //
  429. // It exists primarily for Windows and macOS to make it easy to apply
  430. // environment variables to a running service in a way similar to modifying
  431. // /etc/default/tailscaled on Linux.
  432. //
  433. // On Windows, you use %ProgramData%\Tailscale\tailscaled-env.txt instead.
  434. //
  435. // On macOS, use one of:
  436. //
  437. // - ~/Library/Containers/io.tailscale.ipn.macsys/Data/tailscaled-env.txt
  438. // for standalone macOS GUI builds
  439. // - ~/Library/Containers/io.tailscale.ipn.macos.network-extension/Data/tailscaled-env.txt
  440. // for App Store builds
  441. // - /etc/tailscale/tailscaled-env.txt for tailscaled-on-macOS (homebrew, etc)
  442. func ApplyDiskConfig() (err error) {
  443. var f *os.File
  444. defer func() {
  445. if err != nil {
  446. // Stash away our return error for the healthcheck package to use.
  447. applyDiskConfigErr = fmt.Errorf("error parsing %s: %w", f.Name(), err)
  448. }
  449. }()
  450. // First try the explicitly-provided value for development testing. Not
  451. // useful for users to use on their own. (if they can set this, they can set
  452. // any environment variable anyway)
  453. if name := os.Getenv("TS_DEBUG_ENV_FILE"); name != "" {
  454. f, err = os.Open(name)
  455. if err != nil {
  456. return fmt.Errorf("error opening explicitly configured TS_DEBUG_ENV_FILE: %w", err)
  457. }
  458. defer f.Close()
  459. return applyKeyValueEnv(f)
  460. }
  461. name := getPlatformEnvFile()
  462. if name == "" {
  463. return nil
  464. }
  465. f, err = os.Open(name)
  466. if os.IsNotExist(err) {
  467. return nil
  468. }
  469. if err != nil {
  470. return err
  471. }
  472. defer f.Close()
  473. return applyKeyValueEnv(f)
  474. }
  475. // getPlatformEnvFile returns the current platform's path to an optional
  476. // tailscaled-env.txt file. It returns an empty string if none is defined
  477. // for the platform.
  478. func getPlatformEnvFile() string {
  479. switch runtime.GOOS {
  480. case "windows":
  481. return filepath.Join(os.Getenv("ProgramData"), "Tailscale", "tailscaled-env.txt")
  482. case "linux":
  483. if distro.Get() == distro.Synology {
  484. return "/etc/tailscale/tailscaled-env.txt"
  485. }
  486. case "darwin":
  487. if version.IsSandboxedMacOS() { // the two GUI variants (App Store or separate download)
  488. // This will be user-visible as ~/Library/Containers/$VARIANT/Data/tailscaled-env.txt
  489. // where $VARIANT is "io.tailscale.ipn.macsys" for macsys (downloadable mac GUI builds)
  490. // or "io.tailscale.ipn.macos.network-extension" for App Store builds.
  491. return filepath.Join(os.Getenv("HOME"), "tailscaled-env.txt")
  492. } else {
  493. // Open source / homebrew variable, running tailscaled-on-macOS.
  494. return "/etc/tailscale/tailscaled-env.txt"
  495. }
  496. }
  497. return ""
  498. }
  499. // applyKeyValueEnv reads key=value lines r and calls Setenv for each.
  500. //
  501. // Empty lines and lines beginning with '#' are skipped.
  502. //
  503. // Values can be double quoted, in which case they're unquoted using
  504. // strconv.Unquote.
  505. func applyKeyValueEnv(r io.Reader) error {
  506. bs := bufio.NewScanner(r)
  507. for bs.Scan() {
  508. line := strings.TrimSpace(bs.Text())
  509. if line == "" || line[0] == '#' {
  510. continue
  511. }
  512. k, v, ok := strings.Cut(line, "=")
  513. k = strings.TrimSpace(k)
  514. if !ok || k == "" {
  515. continue
  516. }
  517. v = strings.TrimSpace(v)
  518. if strings.HasPrefix(v, `"`) {
  519. var err error
  520. v, err = strconv.Unquote(v)
  521. if err != nil {
  522. return fmt.Errorf("invalid value in line %q: %v", line, err)
  523. }
  524. }
  525. Setenv(k, v)
  526. }
  527. return bs.Err()
  528. }
  529. // IPCVersion returns version.Long usually, unless TS_DEBUG_FAKE_IPC_VERSION is
  530. // set, in which it contains that value. This is only used for weird development
  531. // cases when testing mismatched versions and you want the client to act like it's
  532. // compatible with the server.
  533. func IPCVersion() string {
  534. if v := String("TS_DEBUG_FAKE_IPC_VERSION"); v != "" {
  535. return v
  536. }
  537. return version.Long()
  538. }