main.go 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build linux
  4. // The containerboot binary is a wrapper for starting tailscaled in a container.
  5. // It handles reading the desired mode of operation out of environment
  6. // variables, bringing up and authenticating Tailscale, and any other
  7. // kubernetes-specific side jobs.
  8. //
  9. // As with most container things, configuration is passed through environment
  10. // variables. All configuration is optional.
  11. //
  12. // - TS_AUTHKEY: the authkey to use for login.
  13. // - TS_HOSTNAME: the hostname to request for the node.
  14. // - TS_ROUTES: subnet routes to advertise. Explicitly setting it to an empty
  15. // value will cause containerboot to stop acting as a subnet router for any
  16. // previously advertised routes. To accept routes, use TS_EXTRA_ARGS to pass
  17. // in --accept-routes.
  18. // - TS_DEST_IP: proxy all incoming Tailscale traffic to the given
  19. // destination.
  20. // - TS_TAILNET_TARGET_IP: proxy all incoming non-Tailscale traffic to the given
  21. // destination defined by an IP.
  22. // - TS_TAILNET_TARGET_FQDN: proxy all incoming non-Tailscale traffic to the given
  23. // destination defined by a MagicDNS name.
  24. // - TS_TAILSCALED_EXTRA_ARGS: extra arguments to 'tailscaled'.
  25. // - TS_EXTRA_ARGS: extra arguments to 'tailscale up'.
  26. // - TS_USERSPACE: run with userspace networking (the default)
  27. // instead of kernel networking.
  28. // - TS_STATE_DIR: the directory in which to store tailscaled
  29. // state. The data should persist across container
  30. // restarts.
  31. // - TS_ACCEPT_DNS: whether to use the tailnet's DNS configuration.
  32. // - TS_KUBE_SECRET: the name of the Kubernetes secret in which to
  33. // store tailscaled state.
  34. // - TS_SOCKS5_SERVER: the address on which to listen for SOCKS5
  35. // proxying into the tailnet.
  36. // - TS_OUTBOUND_HTTP_PROXY_LISTEN: the address on which to listen
  37. // for HTTP proxying into the tailnet.
  38. // - TS_SOCKET: the path where the tailscaled LocalAPI socket should
  39. // be created.
  40. // - TS_AUTH_ONCE: if true, only attempt to log in if not already
  41. // logged in. If false (the default, for backwards
  42. // compatibility), forcibly log in every time the
  43. // container starts.
  44. // - TS_SERVE_CONFIG: if specified, is the file path where the ipn.ServeConfig is located.
  45. // It will be applied once tailscaled is up and running. If the file contains
  46. // ${TS_CERT_DOMAIN}, it will be replaced with the value of the available FQDN.
  47. // It cannot be used in conjunction with TS_DEST_IP. The file is watched for changes,
  48. // and will be re-applied when it changes.
  49. // - EXPERIMENTAL_TS_CONFIGFILE_PATH: if specified, a path to tailscaled
  50. // config. If this is set, TS_HOSTNAME, TS_EXTRA_ARGS, TS_AUTHKEY,
  51. // TS_ROUTES, TS_ACCEPT_DNS env vars must not be set. If this is set,
  52. // containerboot only runs `tailscaled --config <path-to-this-configfile>`
  53. // and not `tailscale up` or `tailscale set`.
  54. // The config file contents are currently read once on container start.
  55. // NB: This env var is currently experimental and the logic will likely change!
  56. // - EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS: if set to true
  57. // and if this containerboot instance is an L7 ingress proxy (created by
  58. // the Kubernetes operator), set up rules to allow proxying cluster traffic,
  59. // received on the Pod IP of this node, to the ingress target in the cluster.
  60. // This, in conjunction with MagicDNS name resolution in cluster, can be
  61. // useful for cases where a cluster workload needs to access a target in
  62. // cluster using the same hostname (in this case, the MagicDNS name of the ingress proxy)
  63. // as a non-cluster workload on tailnet.
  64. // This is only meant to be configured by the Kubernetes operator.
  65. //
  66. // When running on Kubernetes, containerboot defaults to storing state in the
  67. // "tailscale" kube secret. To store state on local disk instead, set
  68. // TS_KUBE_SECRET="" and TS_STATE_DIR=/path/to/storage/dir. The state dir should
  69. // be persistent storage.
  70. //
  71. // Additionally, if TS_AUTHKEY is not set and the TS_KUBE_SECRET contains an
  72. // "authkey" field, that key is used as the tailscale authkey.
  73. package main
  74. import (
  75. "bytes"
  76. "context"
  77. "encoding/json"
  78. "errors"
  79. "fmt"
  80. "io/fs"
  81. "log"
  82. "net/netip"
  83. "os"
  84. "os/exec"
  85. "os/signal"
  86. "path/filepath"
  87. "reflect"
  88. "strconv"
  89. "strings"
  90. "sync"
  91. "sync/atomic"
  92. "syscall"
  93. "time"
  94. "github.com/fsnotify/fsnotify"
  95. "golang.org/x/sys/unix"
  96. "tailscale.com/client/tailscale"
  97. "tailscale.com/ipn"
  98. "tailscale.com/ipn/conffile"
  99. "tailscale.com/tailcfg"
  100. "tailscale.com/types/logger"
  101. "tailscale.com/types/ptr"
  102. "tailscale.com/util/deephash"
  103. "tailscale.com/util/linuxfw"
  104. )
  105. func newNetfilterRunner(logf logger.Logf) (linuxfw.NetfilterRunner, error) {
  106. if defaultBool("TS_TEST_FAKE_NETFILTER", false) {
  107. return linuxfw.NewFakeIPTablesRunner(), nil
  108. }
  109. return linuxfw.New(logf, "")
  110. }
  111. func main() {
  112. log.SetPrefix("boot: ")
  113. tailscale.I_Acknowledge_This_API_Is_Unstable = true
  114. cfg := &settings{
  115. AuthKey: defaultEnvs([]string{"TS_AUTHKEY", "TS_AUTH_KEY"}, ""),
  116. Hostname: defaultEnv("TS_HOSTNAME", ""),
  117. Routes: defaultEnvStringPointer("TS_ROUTES"),
  118. ServeConfigPath: defaultEnv("TS_SERVE_CONFIG", ""),
  119. ProxyTo: defaultEnv("TS_DEST_IP", ""),
  120. TailnetTargetIP: defaultEnv("TS_TAILNET_TARGET_IP", ""),
  121. TailnetTargetFQDN: defaultEnv("TS_TAILNET_TARGET_FQDN", ""),
  122. DaemonExtraArgs: defaultEnv("TS_TAILSCALED_EXTRA_ARGS", ""),
  123. ExtraArgs: defaultEnv("TS_EXTRA_ARGS", ""),
  124. InKubernetes: os.Getenv("KUBERNETES_SERVICE_HOST") != "",
  125. UserspaceMode: defaultBool("TS_USERSPACE", true),
  126. StateDir: defaultEnv("TS_STATE_DIR", ""),
  127. AcceptDNS: defaultEnvBoolPointer("TS_ACCEPT_DNS"),
  128. KubeSecret: defaultEnv("TS_KUBE_SECRET", "tailscale"),
  129. SOCKSProxyAddr: defaultEnv("TS_SOCKS5_SERVER", ""),
  130. HTTPProxyAddr: defaultEnv("TS_OUTBOUND_HTTP_PROXY_LISTEN", ""),
  131. Socket: defaultEnv("TS_SOCKET", "/tmp/tailscaled.sock"),
  132. AuthOnce: defaultBool("TS_AUTH_ONCE", false),
  133. Root: defaultEnv("TS_TEST_ONLY_ROOT", "/"),
  134. TailscaledConfigFilePath: defaultEnv("EXPERIMENTAL_TS_CONFIGFILE_PATH", ""),
  135. AllowProxyingClusterTrafficViaIngress: defaultBool("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS", false),
  136. PodIP: defaultEnv("POD_IP", ""),
  137. }
  138. if err := cfg.validate(); err != nil {
  139. log.Fatalf("invalid configuration: %v", err)
  140. }
  141. if !cfg.UserspaceMode {
  142. if err := ensureTunFile(cfg.Root); err != nil {
  143. log.Fatalf("Unable to create tuntap device file: %v", err)
  144. }
  145. if cfg.ProxyTo != "" || cfg.Routes != nil || cfg.TailnetTargetIP != "" || cfg.TailnetTargetFQDN != "" {
  146. if err := ensureIPForwarding(cfg.Root, cfg.ProxyTo, cfg.TailnetTargetIP, cfg.TailnetTargetFQDN, cfg.Routes); err != nil {
  147. log.Printf("Failed to enable IP forwarding: %v", err)
  148. log.Printf("To run tailscale as a proxy or router container, IP forwarding must be enabled.")
  149. if cfg.InKubernetes {
  150. log.Fatalf("You can either set the sysctls as a privileged initContainer, or run the tailscale container with privileged=true.")
  151. } else {
  152. log.Fatalf("You can fix this by running the container with privileged=true, or the equivalent in your container runtime that permits access to sysctls.")
  153. }
  154. }
  155. }
  156. }
  157. if cfg.InKubernetes {
  158. initKube(cfg.Root)
  159. }
  160. // Context is used for all setup stuff until we're in steady
  161. // state, so that if something is hanging we eventually time out
  162. // and crashloop the container.
  163. bootCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
  164. defer cancel()
  165. if cfg.InKubernetes && cfg.KubeSecret != "" {
  166. canPatch, err := kc.CheckSecretPermissions(bootCtx, cfg.KubeSecret)
  167. if err != nil {
  168. log.Fatalf("Some Kubernetes permissions are missing, please check your RBAC configuration: %v", err)
  169. }
  170. cfg.KubernetesCanPatch = canPatch
  171. if cfg.AuthKey == "" && !isOneStepConfig(cfg) {
  172. key, err := findKeyInKubeSecret(bootCtx, cfg.KubeSecret)
  173. if err != nil {
  174. log.Fatalf("Getting authkey from kube secret: %v", err)
  175. }
  176. if key != "" {
  177. // This behavior of pulling authkeys from kube secrets was added
  178. // at the same time as the patch permission, so we can enforce
  179. // that we must be able to patch out the authkey after
  180. // authenticating if you want to use this feature. This avoids
  181. // us having to deal with the case where we might leave behind
  182. // an unnecessary reusable authkey in a secret, like a rake in
  183. // the grass.
  184. if !cfg.KubernetesCanPatch {
  185. log.Fatalf("authkey found in TS_KUBE_SECRET, but the pod doesn't have patch permissions on the secret to manage the authkey.")
  186. }
  187. log.Print("Using authkey found in kube secret")
  188. cfg.AuthKey = key
  189. } else {
  190. log.Print("No authkey found in kube secret and TS_AUTHKEY not provided, login will be interactive if needed.")
  191. }
  192. }
  193. }
  194. client, daemonProcess, err := startTailscaled(bootCtx, cfg)
  195. if err != nil {
  196. log.Fatalf("failed to bring up tailscale: %v", err)
  197. }
  198. killTailscaled := func() {
  199. if err := daemonProcess.Signal(unix.SIGTERM); err != nil {
  200. log.Fatalf("error shutting tailscaled down: %v", err)
  201. }
  202. }
  203. defer killTailscaled()
  204. w, err := client.WatchIPNBus(bootCtx, ipn.NotifyInitialNetMap|ipn.NotifyInitialPrefs|ipn.NotifyInitialState)
  205. if err != nil {
  206. log.Fatalf("failed to watch tailscaled for updates: %v", err)
  207. }
  208. // Now that we've started tailscaled, we can symlink the socket to the
  209. // default location if needed.
  210. const defaultTailscaledSocketPath = "/var/run/tailscale/tailscaled.sock"
  211. if cfg.Socket != "" && cfg.Socket != defaultTailscaledSocketPath {
  212. // If we were given a socket path, symlink it to the default location so
  213. // that the CLI can find it without any extra flags.
  214. // See #6849.
  215. dir := filepath.Dir(defaultTailscaledSocketPath)
  216. err := os.MkdirAll(dir, 0700)
  217. if err == nil {
  218. err = syscall.Symlink(cfg.Socket, defaultTailscaledSocketPath)
  219. }
  220. if err != nil {
  221. log.Printf("[warning] failed to symlink socket: %v\n\tTo interact with the Tailscale CLI please use `tailscale --socket=%q`", err, cfg.Socket)
  222. }
  223. }
  224. // Because we're still shelling out to `tailscale up` to get access to its
  225. // flag parser, we have to stop watching the IPN bus so that we can block on
  226. // the subcommand without stalling anything. Then once it's done, we resume
  227. // watching the bus.
  228. //
  229. // Depending on the requested mode of operation, this auth step happens at
  230. // different points in containerboot's lifecycle, hence the helper function.
  231. didLogin := false
  232. authTailscale := func() error {
  233. if didLogin {
  234. return nil
  235. }
  236. didLogin = true
  237. w.Close()
  238. if err := tailscaleUp(bootCtx, cfg); err != nil {
  239. return fmt.Errorf("failed to auth tailscale: %v", err)
  240. }
  241. w, err = client.WatchIPNBus(bootCtx, ipn.NotifyInitialNetMap|ipn.NotifyInitialState)
  242. if err != nil {
  243. return fmt.Errorf("rewatching tailscaled for updates after auth: %v", err)
  244. }
  245. return nil
  246. }
  247. if isTwoStepConfigAlwaysAuth(cfg) {
  248. if err := authTailscale(); err != nil {
  249. log.Fatalf("failed to auth tailscale: %v", err)
  250. }
  251. }
  252. authLoop:
  253. for {
  254. n, err := w.Next()
  255. if err != nil {
  256. log.Fatalf("failed to read from tailscaled: %v", err)
  257. }
  258. if n.State != nil {
  259. switch *n.State {
  260. case ipn.NeedsLogin:
  261. if isOneStepConfig(cfg) {
  262. // This could happen if this is the
  263. // first time tailscaled was run for
  264. // this device and the auth key was not
  265. // passed via the configfile.
  266. log.Fatalf("invalid state: tailscaled daemon started with a config file, but tailscale is not logged in: ensure you pass a valid auth key in the config file.")
  267. }
  268. if err := authTailscale(); err != nil {
  269. log.Fatalf("failed to auth tailscale: %v", err)
  270. }
  271. case ipn.NeedsMachineAuth:
  272. log.Printf("machine authorization required, please visit the admin panel")
  273. case ipn.Running:
  274. // Technically, all we want is to keep monitoring the bus for
  275. // netmap updates. However, in order to make the container crash
  276. // if tailscale doesn't initially come up, the watch has a
  277. // startup deadline on it. So, we have to break out of this
  278. // watch loop, cancel the watch, and watch again with no
  279. // deadline to continue monitoring for changes.
  280. break authLoop
  281. default:
  282. log.Printf("tailscaled in state %q, waiting", *n.State)
  283. }
  284. }
  285. }
  286. w.Close()
  287. ctx, cancel := contextWithExitSignalWatch()
  288. defer cancel()
  289. if isTwoStepConfigAuthOnce(cfg) {
  290. // Now that we are authenticated, we can set/reset any of the
  291. // settings that we need to.
  292. if err := tailscaleSet(ctx, cfg); err != nil {
  293. log.Fatalf("failed to auth tailscale: %v", err)
  294. }
  295. }
  296. if cfg.ServeConfigPath != "" {
  297. // Remove any serve config that may have been set by a previous run of
  298. // containerboot, but only if we're providing a new one.
  299. if err := client.SetServeConfig(ctx, new(ipn.ServeConfig)); err != nil {
  300. log.Fatalf("failed to unset serve config: %v", err)
  301. }
  302. }
  303. if cfg.InKubernetes && cfg.KubeSecret != "" && cfg.KubernetesCanPatch && isTwoStepConfigAuthOnce(cfg) {
  304. // We were told to only auth once, so any secret-bound
  305. // authkey is no longer needed. We don't strictly need to
  306. // wipe it, but it's good hygiene.
  307. log.Printf("Deleting authkey from kube secret")
  308. if err := deleteAuthKey(ctx, cfg.KubeSecret); err != nil {
  309. log.Fatalf("deleting authkey from kube secret: %v", err)
  310. }
  311. }
  312. w, err = client.WatchIPNBus(ctx, ipn.NotifyInitialNetMap|ipn.NotifyInitialState)
  313. if err != nil {
  314. log.Fatalf("rewatching tailscaled for updates after auth: %v", err)
  315. }
  316. var (
  317. wantProxy = cfg.ProxyTo != "" || cfg.TailnetTargetIP != "" || cfg.TailnetTargetFQDN != "" || cfg.AllowProxyingClusterTrafficViaIngress
  318. wantDeviceInfo = cfg.InKubernetes && cfg.KubeSecret != "" && cfg.KubernetesCanPatch
  319. startupTasksDone = false
  320. currentIPs deephash.Sum // tailscale IPs assigned to device
  321. currentDeviceInfo deephash.Sum // device ID and fqdn
  322. currentEgressIPs deephash.Sum
  323. certDomain = new(atomic.Pointer[string])
  324. certDomainChanged = make(chan bool, 1)
  325. )
  326. if cfg.ServeConfigPath != "" {
  327. go watchServeConfigChanges(ctx, cfg.ServeConfigPath, certDomainChanged, certDomain, client)
  328. }
  329. var nfr linuxfw.NetfilterRunner
  330. if wantProxy {
  331. nfr, err = newNetfilterRunner(log.Printf)
  332. if err != nil {
  333. log.Fatalf("error creating new netfilter runner: %v", err)
  334. }
  335. }
  336. notifyChan := make(chan ipn.Notify)
  337. errChan := make(chan error)
  338. go func() {
  339. for {
  340. n, err := w.Next()
  341. if err != nil {
  342. errChan <- err
  343. break
  344. } else {
  345. notifyChan <- n
  346. }
  347. }
  348. }()
  349. var wg sync.WaitGroup
  350. runLoop:
  351. for {
  352. select {
  353. case <-ctx.Done():
  354. // Although killTailscaled() is deferred earlier, if we
  355. // have started the reaper defined below, we need to
  356. // kill tailscaled and let reaper clean up child
  357. // processes.
  358. killTailscaled()
  359. break runLoop
  360. case err := <-errChan:
  361. log.Fatalf("failed to read from tailscaled: %v", err)
  362. case n := <-notifyChan:
  363. if n.State != nil && *n.State != ipn.Running {
  364. // Something's gone wrong and we've left the authenticated state.
  365. // Our container image never recovered gracefully from this, and the
  366. // control flow required to make it work now is hard. So, just crash
  367. // the container and rely on the container runtime to restart us,
  368. // whereupon we'll go through initial auth again.
  369. log.Fatalf("tailscaled left running state (now in state %q), exiting", *n.State)
  370. }
  371. if n.NetMap != nil {
  372. addrs := n.NetMap.SelfNode.Addresses().AsSlice()
  373. newCurrentIPs := deephash.Hash(&addrs)
  374. ipsHaveChanged := newCurrentIPs != currentIPs
  375. if cfg.TailnetTargetFQDN != "" {
  376. var (
  377. egressAddrs []netip.Prefix
  378. newCurentEgressIPs deephash.Sum
  379. egressIPsHaveChanged bool
  380. node tailcfg.NodeView
  381. nodeFound bool
  382. )
  383. for _, n := range n.NetMap.Peers {
  384. if strings.EqualFold(n.Name(), cfg.TailnetTargetFQDN) {
  385. node = n
  386. nodeFound = true
  387. break
  388. }
  389. }
  390. if !nodeFound {
  391. log.Printf("Tailscale node %q not found; it either does not exist, or not reachable because of ACLs", cfg.TailnetTargetFQDN)
  392. break
  393. }
  394. egressAddrs = node.Addresses().AsSlice()
  395. newCurentEgressIPs = deephash.Hash(&egressAddrs)
  396. egressIPsHaveChanged = newCurentEgressIPs != currentEgressIPs
  397. if egressIPsHaveChanged && len(egressAddrs) > 0 {
  398. for _, egressAddr := range egressAddrs {
  399. ea := egressAddr.Addr()
  400. // TODO (irbekrm): make it work for IPv6 too.
  401. if ea.Is6() {
  402. log.Println("Not installing egress forwarding rules for IPv6 as this is currently not supported")
  403. continue
  404. }
  405. log.Printf("Installing forwarding rules for destination %v", ea.String())
  406. if err := installEgressForwardingRule(ctx, ea.String(), addrs, nfr); err != nil {
  407. log.Fatalf("installing egress proxy rules for destination %s: %v", ea.String(), err)
  408. }
  409. }
  410. }
  411. currentEgressIPs = newCurentEgressIPs
  412. }
  413. if cfg.ProxyTo != "" && len(addrs) > 0 && ipsHaveChanged {
  414. log.Printf("Installing proxy rules")
  415. if err := installIngressForwardingRule(ctx, cfg.ProxyTo, addrs, nfr); err != nil {
  416. log.Fatalf("installing ingress proxy rules: %v", err)
  417. }
  418. }
  419. if cfg.ServeConfigPath != "" && len(n.NetMap.DNS.CertDomains) > 0 {
  420. cd := n.NetMap.DNS.CertDomains[0]
  421. prev := certDomain.Swap(ptr.To(cd))
  422. if prev == nil || *prev != cd {
  423. select {
  424. case certDomainChanged <- true:
  425. default:
  426. }
  427. }
  428. }
  429. if cfg.TailnetTargetIP != "" && ipsHaveChanged && len(addrs) > 0 {
  430. log.Printf("Installing forwarding rules for destination %v", cfg.TailnetTargetIP)
  431. if err := installEgressForwardingRule(ctx, cfg.TailnetTargetIP, addrs, nfr); err != nil {
  432. log.Fatalf("installing egress proxy rules: %v", err)
  433. }
  434. }
  435. // If this is a L7 cluster ingress proxy (set up
  436. // by Kubernetes operator) and proxying of
  437. // cluster traffic to the ingress target is
  438. // enabled, set up proxy rule each time the
  439. // tailnet IPs of this node change (including
  440. // the first time they become available).
  441. if cfg.AllowProxyingClusterTrafficViaIngress && cfg.ServeConfigPath != "" && ipsHaveChanged && len(addrs) > 0 {
  442. log.Printf("installing rules to forward traffic for %s to node's tailnet IP", cfg.PodIP)
  443. if err := installTSForwardingRuleForDestination(ctx, cfg.PodIP, addrs, nfr); err != nil {
  444. log.Fatalf("installing rules to forward traffic to node's tailnet IP: %v", err)
  445. }
  446. }
  447. currentIPs = newCurrentIPs
  448. deviceInfo := []any{n.NetMap.SelfNode.StableID(), n.NetMap.SelfNode.Name()}
  449. if cfg.InKubernetes && cfg.KubernetesCanPatch && cfg.KubeSecret != "" && deephash.Update(&currentDeviceInfo, &deviceInfo) {
  450. if err := storeDeviceInfo(ctx, cfg.KubeSecret, n.NetMap.SelfNode.StableID(), n.NetMap.SelfNode.Name(), n.NetMap.SelfNode.Addresses().AsSlice()); err != nil {
  451. log.Fatalf("storing device ID in kube secret: %v", err)
  452. }
  453. }
  454. }
  455. if !startupTasksDone {
  456. if (!wantProxy || currentIPs != deephash.Sum{}) && (!wantDeviceInfo || currentDeviceInfo != deephash.Sum{}) {
  457. // This log message is used in tests to detect when all
  458. // post-auth configuration is done.
  459. log.Println("Startup complete, waiting for shutdown signal")
  460. startupTasksDone = true
  461. // Reap all processes, since we are PID1 and need to collect zombies. We can
  462. // only start doing this once we've stopped shelling out to things
  463. // `tailscale up`, otherwise this goroutine can reap the CLI subprocesses
  464. // and wedge bringup.
  465. reaper := func() {
  466. defer wg.Done()
  467. for {
  468. var status unix.WaitStatus
  469. pid, err := unix.Wait4(-1, &status, 0, nil)
  470. if errors.Is(err, unix.EINTR) {
  471. continue
  472. }
  473. if err != nil {
  474. log.Fatalf("Waiting for exited processes: %v", err)
  475. }
  476. if pid == daemonProcess.Pid {
  477. log.Printf("Tailscaled exited")
  478. os.Exit(0)
  479. }
  480. }
  481. }
  482. wg.Add(1)
  483. go reaper()
  484. }
  485. }
  486. }
  487. }
  488. wg.Wait()
  489. }
  490. // watchServeConfigChanges watches path for changes, and when it sees one, reads
  491. // the serve config from it, replacing ${TS_CERT_DOMAIN} with certDomain, and
  492. // applies it to lc. It exits when ctx is canceled. cdChanged is a channel that
  493. // is written to when the certDomain changes, causing the serve config to be
  494. // re-read and applied.
  495. func watchServeConfigChanges(ctx context.Context, path string, cdChanged <-chan bool, certDomainAtomic *atomic.Pointer[string], lc *tailscale.LocalClient) {
  496. if certDomainAtomic == nil {
  497. panic("cd must not be nil")
  498. }
  499. var tickChan <-chan time.Time
  500. var eventChan <-chan fsnotify.Event
  501. if w, err := fsnotify.NewWatcher(); err != nil {
  502. log.Printf("failed to create fsnotify watcher, timer-only mode: %v", err)
  503. ticker := time.NewTicker(5 * time.Second)
  504. defer ticker.Stop()
  505. tickChan = ticker.C
  506. } else {
  507. defer w.Close()
  508. if err := w.Add(filepath.Dir(path)); err != nil {
  509. log.Fatalf("failed to add fsnotify watch: %v", err)
  510. }
  511. eventChan = w.Events
  512. }
  513. var certDomain string
  514. var prevServeConfig *ipn.ServeConfig
  515. for {
  516. select {
  517. case <-ctx.Done():
  518. return
  519. case <-cdChanged:
  520. certDomain = *certDomainAtomic.Load()
  521. case <-tickChan:
  522. case <-eventChan:
  523. // We can't do any reasonable filtering on the event because of how
  524. // k8s handles these mounts. So just re-read the file and apply it
  525. // if it's changed.
  526. }
  527. if certDomain == "" {
  528. continue
  529. }
  530. sc, err := readServeConfig(path, certDomain)
  531. if err != nil {
  532. log.Fatalf("failed to read serve config: %v", err)
  533. }
  534. if prevServeConfig != nil && reflect.DeepEqual(sc, prevServeConfig) {
  535. continue
  536. }
  537. log.Printf("Applying serve config")
  538. if err := lc.SetServeConfig(ctx, sc); err != nil {
  539. log.Fatalf("failed to set serve config: %v", err)
  540. }
  541. prevServeConfig = sc
  542. }
  543. }
  544. // readServeConfig reads the ipn.ServeConfig from path, replacing
  545. // ${TS_CERT_DOMAIN} with certDomain.
  546. func readServeConfig(path, certDomain string) (*ipn.ServeConfig, error) {
  547. if path == "" {
  548. return nil, nil
  549. }
  550. j, err := os.ReadFile(path)
  551. if err != nil {
  552. return nil, err
  553. }
  554. j = bytes.ReplaceAll(j, []byte("${TS_CERT_DOMAIN}"), []byte(certDomain))
  555. var sc ipn.ServeConfig
  556. if err := json.Unmarshal(j, &sc); err != nil {
  557. return nil, err
  558. }
  559. return &sc, nil
  560. }
  561. func startTailscaled(ctx context.Context, cfg *settings) (*tailscale.LocalClient, *os.Process, error) {
  562. args := tailscaledArgs(cfg)
  563. // tailscaled runs without context, since it needs to persist
  564. // beyond the startup timeout in ctx.
  565. cmd := exec.Command("tailscaled", args...)
  566. cmd.Stdout = os.Stdout
  567. cmd.Stderr = os.Stderr
  568. cmd.SysProcAttr = &syscall.SysProcAttr{
  569. Setpgid: true,
  570. }
  571. log.Printf("Starting tailscaled")
  572. if err := cmd.Start(); err != nil {
  573. return nil, nil, fmt.Errorf("starting tailscaled failed: %v", err)
  574. }
  575. // Wait for the socket file to appear, otherwise API ops will racily fail.
  576. log.Printf("Waiting for tailscaled socket")
  577. for {
  578. if ctx.Err() != nil {
  579. log.Fatalf("Timed out waiting for tailscaled socket")
  580. }
  581. _, err := os.Stat(cfg.Socket)
  582. if errors.Is(err, fs.ErrNotExist) {
  583. time.Sleep(100 * time.Millisecond)
  584. continue
  585. } else if err != nil {
  586. log.Fatalf("Waiting for tailscaled socket: %v", err)
  587. }
  588. break
  589. }
  590. tsClient := &tailscale.LocalClient{
  591. Socket: cfg.Socket,
  592. UseSocketOnly: true,
  593. }
  594. return tsClient, cmd.Process, nil
  595. }
  596. // tailscaledArgs uses cfg to construct the argv for tailscaled.
  597. func tailscaledArgs(cfg *settings) []string {
  598. args := []string{"--socket=" + cfg.Socket}
  599. switch {
  600. case cfg.InKubernetes && cfg.KubeSecret != "":
  601. args = append(args, "--state=kube:"+cfg.KubeSecret)
  602. if cfg.StateDir == "" {
  603. cfg.StateDir = "/tmp"
  604. }
  605. fallthrough
  606. case cfg.StateDir != "":
  607. args = append(args, "--statedir="+cfg.StateDir)
  608. default:
  609. args = append(args, "--state=mem:", "--statedir=/tmp")
  610. }
  611. if cfg.UserspaceMode {
  612. args = append(args, "--tun=userspace-networking")
  613. } else if err := ensureTunFile(cfg.Root); err != nil {
  614. log.Fatalf("ensuring that /dev/net/tun exists: %v", err)
  615. }
  616. if cfg.SOCKSProxyAddr != "" {
  617. args = append(args, "--socks5-server="+cfg.SOCKSProxyAddr)
  618. }
  619. if cfg.HTTPProxyAddr != "" {
  620. args = append(args, "--outbound-http-proxy-listen="+cfg.HTTPProxyAddr)
  621. }
  622. if cfg.TailscaledConfigFilePath != "" {
  623. args = append(args, "--config="+cfg.TailscaledConfigFilePath)
  624. }
  625. if cfg.DaemonExtraArgs != "" {
  626. args = append(args, strings.Fields(cfg.DaemonExtraArgs)...)
  627. }
  628. return args
  629. }
  630. // tailscaleUp uses cfg to run 'tailscale up' everytime containerboot starts, or
  631. // if TS_AUTH_ONCE is set, only the first time containerboot starts.
  632. func tailscaleUp(ctx context.Context, cfg *settings) error {
  633. args := []string{"--socket=" + cfg.Socket, "up"}
  634. if cfg.AcceptDNS != nil && *cfg.AcceptDNS {
  635. args = append(args, "--accept-dns=true")
  636. } else {
  637. args = append(args, "--accept-dns=false")
  638. }
  639. if cfg.AuthKey != "" {
  640. args = append(args, "--authkey="+cfg.AuthKey)
  641. }
  642. // --advertise-routes can be passed an empty string to configure a
  643. // device (that might have previously advertised subnet routes) to not
  644. // advertise any routes. Respect an empty string passed by a user and
  645. // use it to explicitly unset the routes.
  646. if cfg.Routes != nil {
  647. args = append(args, "--advertise-routes="+*cfg.Routes)
  648. }
  649. if cfg.Hostname != "" {
  650. args = append(args, "--hostname="+cfg.Hostname)
  651. }
  652. if cfg.ExtraArgs != "" {
  653. args = append(args, strings.Fields(cfg.ExtraArgs)...)
  654. }
  655. log.Printf("Running 'tailscale up'")
  656. cmd := exec.CommandContext(ctx, "tailscale", args...)
  657. cmd.Stdout = os.Stdout
  658. cmd.Stderr = os.Stderr
  659. if err := cmd.Run(); err != nil {
  660. return fmt.Errorf("tailscale up failed: %v", err)
  661. }
  662. return nil
  663. }
  664. // tailscaleSet uses cfg to run 'tailscale set' to set any known configuration
  665. // options that are passed in via environment variables. This is run after the
  666. // node is in Running state and only if TS_AUTH_ONCE is set.
  667. func tailscaleSet(ctx context.Context, cfg *settings) error {
  668. args := []string{"--socket=" + cfg.Socket, "set"}
  669. if cfg.AcceptDNS != nil && *cfg.AcceptDNS {
  670. args = append(args, "--accept-dns=true")
  671. } else {
  672. args = append(args, "--accept-dns=false")
  673. }
  674. // --advertise-routes can be passed an empty string to configure a
  675. // device (that might have previously advertised subnet routes) to not
  676. // advertise any routes. Respect an empty string passed by a user and
  677. // use it to explicitly unset the routes.
  678. if cfg.Routes != nil {
  679. args = append(args, "--advertise-routes="+*cfg.Routes)
  680. }
  681. if cfg.Hostname != "" {
  682. args = append(args, "--hostname="+cfg.Hostname)
  683. }
  684. log.Printf("Running 'tailscale set'")
  685. cmd := exec.CommandContext(ctx, "tailscale", args...)
  686. cmd.Stdout = os.Stdout
  687. cmd.Stderr = os.Stderr
  688. if err := cmd.Run(); err != nil {
  689. return fmt.Errorf("tailscale set failed: %v", err)
  690. }
  691. return nil
  692. }
  693. // ensureTunFile checks that /dev/net/tun exists, creating it if
  694. // missing.
  695. func ensureTunFile(root string) error {
  696. // Verify that /dev/net/tun exists, in some container envs it
  697. // needs to be mknod-ed.
  698. if _, err := os.Stat(filepath.Join(root, "dev/net")); errors.Is(err, fs.ErrNotExist) {
  699. if err := os.MkdirAll(filepath.Join(root, "dev/net"), 0755); err != nil {
  700. return err
  701. }
  702. }
  703. if _, err := os.Stat(filepath.Join(root, "dev/net/tun")); errors.Is(err, fs.ErrNotExist) {
  704. dev := unix.Mkdev(10, 200) // tuntap major and minor
  705. if err := unix.Mknod(filepath.Join(root, "dev/net/tun"), 0600|unix.S_IFCHR, int(dev)); err != nil {
  706. return err
  707. }
  708. }
  709. return nil
  710. }
  711. // ensureIPForwarding enables IPv4/IPv6 forwarding for the container.
  712. func ensureIPForwarding(root, clusterProxyTarget, tailnetTargetiP, tailnetTargetFQDN string, routes *string) error {
  713. var (
  714. v4Forwarding, v6Forwarding bool
  715. )
  716. if clusterProxyTarget != "" {
  717. proxyIP, err := netip.ParseAddr(clusterProxyTarget)
  718. if err != nil {
  719. return fmt.Errorf("invalid cluster destination IP: %v", err)
  720. }
  721. if proxyIP.Is4() {
  722. v4Forwarding = true
  723. } else {
  724. v6Forwarding = true
  725. }
  726. }
  727. if tailnetTargetiP != "" {
  728. proxyIP, err := netip.ParseAddr(tailnetTargetiP)
  729. if err != nil {
  730. return fmt.Errorf("invalid tailnet destination IP: %v", err)
  731. }
  732. if proxyIP.Is4() {
  733. v4Forwarding = true
  734. } else {
  735. v6Forwarding = true
  736. }
  737. }
  738. // Currently we only proxy traffic to the IPv4 address of the tailnet
  739. // target.
  740. if tailnetTargetFQDN != "" {
  741. v4Forwarding = true
  742. }
  743. if routes != nil && *routes != "" {
  744. for _, route := range strings.Split(*routes, ",") {
  745. cidr, err := netip.ParsePrefix(route)
  746. if err != nil {
  747. return fmt.Errorf("invalid subnet route: %v", err)
  748. }
  749. if cidr.Addr().Is4() {
  750. v4Forwarding = true
  751. } else {
  752. v6Forwarding = true
  753. }
  754. }
  755. }
  756. var paths []string
  757. if v4Forwarding {
  758. paths = append(paths, filepath.Join(root, "proc/sys/net/ipv4/ip_forward"))
  759. }
  760. if v6Forwarding {
  761. paths = append(paths, filepath.Join(root, "proc/sys/net/ipv6/conf/all/forwarding"))
  762. }
  763. // In some common configurations (e.g. default docker,
  764. // kubernetes), the container environment denies write access to
  765. // most sysctls, including IP forwarding controls. Check the
  766. // sysctl values before trying to change them, so that we
  767. // gracefully do nothing if the container's already been set up
  768. // properly by e.g. a k8s initContainer.
  769. for _, path := range paths {
  770. bs, err := os.ReadFile(path)
  771. if err != nil {
  772. return fmt.Errorf("reading %q: %w", path, err)
  773. }
  774. if v := strings.TrimSpace(string(bs)); v != "1" {
  775. if err := os.WriteFile(path, []byte("1"), 0644); err != nil {
  776. return fmt.Errorf("enabling %q: %w", path, err)
  777. }
  778. }
  779. }
  780. return nil
  781. }
  782. func installEgressForwardingRule(ctx context.Context, dstStr string, tsIPs []netip.Prefix, nfr linuxfw.NetfilterRunner) error {
  783. dst, err := netip.ParseAddr(dstStr)
  784. if err != nil {
  785. return err
  786. }
  787. var local netip.Addr
  788. for _, pfx := range tsIPs {
  789. if !pfx.IsSingleIP() {
  790. continue
  791. }
  792. if pfx.Addr().Is4() != dst.Is4() {
  793. continue
  794. }
  795. local = pfx.Addr()
  796. break
  797. }
  798. if !local.IsValid() {
  799. return fmt.Errorf("no tailscale IP matching family of %s found in %v", dstStr, tsIPs)
  800. }
  801. if err := nfr.DNATNonTailscaleTraffic("tailscale0", dst); err != nil {
  802. return fmt.Errorf("installing egress proxy rules: %w", err)
  803. }
  804. if err := nfr.AddSNATRuleForDst(local, dst); err != nil {
  805. return fmt.Errorf("installing egress proxy rules: %w", err)
  806. }
  807. if err := nfr.ClampMSSToPMTU("tailscale0", dst); err != nil {
  808. return fmt.Errorf("installing egress proxy rules: %w", err)
  809. }
  810. return nil
  811. }
  812. // installTSForwardingRuleForDestination accepts a destination address and a
  813. // list of node's tailnet addresses, sets up rules to forward traffic for
  814. // destination to the tailnet IP matching the destination IP family.
  815. // Destination can be Pod IP of this node.
  816. func installTSForwardingRuleForDestination(ctx context.Context, dstFilter string, tsIPs []netip.Prefix, nfr linuxfw.NetfilterRunner) error {
  817. dst, err := netip.ParseAddr(dstFilter)
  818. if err != nil {
  819. return err
  820. }
  821. var local netip.Addr
  822. for _, pfx := range tsIPs {
  823. if !pfx.IsSingleIP() {
  824. continue
  825. }
  826. if pfx.Addr().Is4() != dst.Is4() {
  827. continue
  828. }
  829. local = pfx.Addr()
  830. break
  831. }
  832. if !local.IsValid() {
  833. return fmt.Errorf("no tailscale IP matching family of %s found in %v", dstFilter, tsIPs)
  834. }
  835. if err := nfr.AddDNATRule(dst, local); err != nil {
  836. return fmt.Errorf("installing rule for forwarding traffic to tailnet IP: %w", err)
  837. }
  838. return nil
  839. }
  840. func installIngressForwardingRule(ctx context.Context, dstStr string, tsIPs []netip.Prefix, nfr linuxfw.NetfilterRunner) error {
  841. dst, err := netip.ParseAddr(dstStr)
  842. if err != nil {
  843. return err
  844. }
  845. var local netip.Addr
  846. for _, pfx := range tsIPs {
  847. if !pfx.IsSingleIP() {
  848. continue
  849. }
  850. if pfx.Addr().Is4() != dst.Is4() {
  851. continue
  852. }
  853. local = pfx.Addr()
  854. break
  855. }
  856. if !local.IsValid() {
  857. return fmt.Errorf("no tailscale IP matching family of %s found in %v", dstStr, tsIPs)
  858. }
  859. if err := nfr.AddDNATRule(local, dst); err != nil {
  860. return fmt.Errorf("installing ingress proxy rules: %w", err)
  861. }
  862. if err := nfr.ClampMSSToPMTU("tailscale0", dst); err != nil {
  863. return fmt.Errorf("installing ingress proxy rules: %w", err)
  864. }
  865. return nil
  866. }
  867. // settings is all the configuration for containerboot.
  868. type settings struct {
  869. AuthKey string
  870. Hostname string
  871. Routes *string
  872. // ProxyTo is the destination IP to which all incoming
  873. // Tailscale traffic should be proxied. If empty, no proxying
  874. // is done. This is typically a locally reachable IP.
  875. ProxyTo string
  876. // TailnetTargetIP is the destination IP to which all incoming
  877. // non-Tailscale traffic should be proxied. This is typically a
  878. // Tailscale IP.
  879. TailnetTargetIP string
  880. // TailnetTargetFQDN is an MagicDNS name to which all incoming
  881. // non-Tailscale traffic should be proxied. This must be a full Tailnet
  882. // node FQDN.
  883. TailnetTargetFQDN string
  884. ServeConfigPath string
  885. DaemonExtraArgs string
  886. ExtraArgs string
  887. InKubernetes bool
  888. UserspaceMode bool
  889. StateDir string
  890. AcceptDNS *bool
  891. KubeSecret string
  892. SOCKSProxyAddr string
  893. HTTPProxyAddr string
  894. Socket string
  895. AuthOnce bool
  896. Root string
  897. KubernetesCanPatch bool
  898. TailscaledConfigFilePath string
  899. // If set to true and, if this containerboot instance is a Kubernetes
  900. // ingress proxy, set up rules to forward incoming cluster traffic to be
  901. // forwarded to the ingress target in cluster.
  902. AllowProxyingClusterTrafficViaIngress bool
  903. // PodIP is the IP of the Pod if running in Kubernetes. This is used
  904. // when setting up rules to proxy cluster traffic to cluster ingress
  905. // target.
  906. PodIP string
  907. }
  908. func (s *settings) validate() error {
  909. if s.TailscaledConfigFilePath != "" {
  910. if _, err := conffile.Load(s.TailscaledConfigFilePath); err != nil {
  911. return fmt.Errorf("error validating tailscaled configfile contents: %w", err)
  912. }
  913. }
  914. if s.ProxyTo != "" && s.UserspaceMode {
  915. return errors.New("TS_DEST_IP is not supported with TS_USERSPACE")
  916. }
  917. if s.TailnetTargetIP != "" && s.UserspaceMode {
  918. return errors.New("TS_TAILNET_TARGET_IP is not supported with TS_USERSPACE")
  919. }
  920. if s.TailnetTargetFQDN != "" && s.UserspaceMode {
  921. return errors.New("TS_TAILNET_TARGET_FQDN is not supported with TS_USERSPACE")
  922. }
  923. if s.TailnetTargetFQDN != "" && s.TailnetTargetIP != "" {
  924. return errors.New("Both TS_TAILNET_TARGET_IP and TS_TAILNET_FQDN cannot be set")
  925. }
  926. if s.TailscaledConfigFilePath != "" && (s.AcceptDNS != nil || s.AuthKey != "" || s.Routes != nil || s.ExtraArgs != "" || s.Hostname != "") {
  927. return errors.New("EXPERIMENTAL_TS_CONFIGFILE_PATH cannot be set in combination with TS_HOSTNAME, TS_EXTRA_ARGS, TS_AUTHKEY, TS_ROUTES, TS_ACCEPT_DNS.")
  928. }
  929. if s.AllowProxyingClusterTrafficViaIngress && s.UserspaceMode {
  930. return errors.New("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS is not supported in userspace mode")
  931. }
  932. if s.AllowProxyingClusterTrafficViaIngress && s.ServeConfigPath == "" {
  933. return errors.New("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS is set but this is not a cluster ingress proxy")
  934. }
  935. if s.AllowProxyingClusterTrafficViaIngress && s.PodIP == "" {
  936. return errors.New("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS is set but POD_IP is not set")
  937. }
  938. return nil
  939. }
  940. // defaultEnv returns the value of the given envvar name, or defVal if
  941. // unset.
  942. func defaultEnv(name, defVal string) string {
  943. if v, ok := os.LookupEnv(name); ok {
  944. return v
  945. }
  946. return defVal
  947. }
  948. // defaultEnvStringPointer returns a pointer to the given envvar value if set, else
  949. // returns nil. This is useful in cases where we need to distinguish between a
  950. // variable being set to empty string vs unset.
  951. func defaultEnvStringPointer(name string) *string {
  952. if v, ok := os.LookupEnv(name); ok {
  953. return &v
  954. }
  955. return nil
  956. }
  957. // defaultEnvBoolPointer returns a pointer to the given envvar value if set, else
  958. // returns nil. This is useful in cases where we need to distinguish between a
  959. // variable being explicitly set to false vs unset.
  960. func defaultEnvBoolPointer(name string) *bool {
  961. v := os.Getenv(name)
  962. ret, err := strconv.ParseBool(v)
  963. if err != nil {
  964. return nil
  965. }
  966. return &ret
  967. }
  968. func defaultEnvs(names []string, defVal string) string {
  969. for _, name := range names {
  970. if v, ok := os.LookupEnv(name); ok {
  971. return v
  972. }
  973. }
  974. return defVal
  975. }
  976. // defaultBool returns the boolean value of the given envvar name, or
  977. // defVal if unset or not a bool.
  978. func defaultBool(name string, defVal bool) bool {
  979. v := os.Getenv(name)
  980. ret, err := strconv.ParseBool(v)
  981. if err != nil {
  982. return defVal
  983. }
  984. return ret
  985. }
  986. // contextWithExitSignalWatch watches for SIGTERM/SIGINT signals. It returns a
  987. // context that gets cancelled when a signal is received and a cancel function
  988. // that can be called to free the resources when the watch should be stopped.
  989. func contextWithExitSignalWatch() (context.Context, func()) {
  990. closeChan := make(chan string)
  991. ctx, cancel := context.WithCancel(context.Background())
  992. signalChan := make(chan os.Signal, 1)
  993. signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
  994. go func() {
  995. select {
  996. case <-signalChan:
  997. cancel()
  998. case <-closeChan:
  999. return
  1000. }
  1001. }()
  1002. f := func() {
  1003. closeChan <- "goodbye"
  1004. }
  1005. return ctx, f
  1006. }
  1007. // isTwoStepConfigAuthOnce returns true if the Tailscale node should be configured
  1008. // in two steps and login should only happen once.
  1009. // Step 1: run 'tailscaled'
  1010. // Step 2):
  1011. // A) if this is the first time starting this node run 'tailscale up --authkey <authkey> <config opts>'
  1012. // B) if this is not the first time starting this node run 'tailscale set <config opts>'.
  1013. func isTwoStepConfigAuthOnce(cfg *settings) bool {
  1014. return cfg.AuthOnce && cfg.TailscaledConfigFilePath == ""
  1015. }
  1016. // isTwoStepConfigAlwaysAuth returns true if the Tailscale node should be configured
  1017. // in two steps and we should log in every time it starts.
  1018. // Step 1: run 'tailscaled'
  1019. // Step 2): run 'tailscale up --authkey <authkey> <config opts>'
  1020. func isTwoStepConfigAlwaysAuth(cfg *settings) bool {
  1021. return !cfg.AuthOnce && cfg.TailscaledConfigFilePath == ""
  1022. }
  1023. // isOneStepConfig returns true if the Tailscale node should always be ran and
  1024. // configured in a single step by running 'tailscaled <config opts>'
  1025. func isOneStepConfig(cfg *settings) bool {
  1026. return cfg.TailscaledConfigFilePath != ""
  1027. }