main.go 27 KB

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