settings.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build linux
  4. package main
  5. import (
  6. "context"
  7. "errors"
  8. "fmt"
  9. "log"
  10. "net/netip"
  11. "os"
  12. "path"
  13. "strconv"
  14. "strings"
  15. "tailscale.com/ipn/conffile"
  16. "tailscale.com/kube/kubeclient"
  17. )
  18. // settings is all the configuration for containerboot.
  19. type settings struct {
  20. AuthKey string
  21. Hostname string
  22. Routes *string
  23. // ProxyTargetIP is the destination IP to which all incoming
  24. // Tailscale traffic should be proxied. If empty, no proxying
  25. // is done. This is typically a locally reachable IP.
  26. ProxyTargetIP string
  27. // ProxyTargetDNSName is a DNS name to whose backing IP addresses all
  28. // incoming Tailscale traffic should be proxied.
  29. ProxyTargetDNSName string
  30. // TailnetTargetIP is the destination IP to which all incoming
  31. // non-Tailscale traffic should be proxied. This is typically a
  32. // Tailscale IP.
  33. TailnetTargetIP string
  34. // TailnetTargetFQDN is an MagicDNS name to which all incoming
  35. // non-Tailscale traffic should be proxied. This must be a full Tailnet
  36. // node FQDN.
  37. TailnetTargetFQDN string
  38. ServeConfigPath string
  39. DaemonExtraArgs string
  40. ExtraArgs string
  41. InKubernetes bool
  42. UserspaceMode bool
  43. StateDir string
  44. AcceptDNS *bool
  45. KubeSecret string
  46. SOCKSProxyAddr string
  47. HTTPProxyAddr string
  48. Socket string
  49. AuthOnce bool
  50. Root string
  51. KubernetesCanPatch bool
  52. TailscaledConfigFilePath string
  53. EnableForwardingOptimizations bool
  54. // If set to true and, if this containerboot instance is a Kubernetes
  55. // ingress proxy, set up rules to forward incoming cluster traffic to be
  56. // forwarded to the ingress target in cluster.
  57. AllowProxyingClusterTrafficViaIngress bool
  58. // PodIP is the IP of the Pod if running in Kubernetes. This is used
  59. // when setting up rules to proxy cluster traffic to cluster ingress
  60. // target.
  61. // Deprecated: use PodIPv4, PodIPv6 instead to support dual stack clusters
  62. PodIP string
  63. PodIPv4 string
  64. PodIPv6 string
  65. HealthCheckAddrPort string // TODO(tomhjp): use the local addr/port instead.
  66. LocalAddrPort string
  67. MetricsEnabled bool
  68. DebugAddrPort string
  69. EgressSvcsCfgPath string
  70. }
  71. func configFromEnv() (*settings, error) {
  72. defaultLocalAddrPort := ""
  73. if v, ok := os.LookupEnv("POD_IP"); ok && v != "" {
  74. defaultLocalAddrPort = fmt.Sprintf("%s:9002", v)
  75. }
  76. cfg := &settings{
  77. AuthKey: defaultEnvs([]string{"TS_AUTHKEY", "TS_AUTH_KEY"}, ""),
  78. Hostname: defaultEnv("TS_HOSTNAME", ""),
  79. Routes: defaultEnvStringPointer("TS_ROUTES"),
  80. ServeConfigPath: defaultEnv("TS_SERVE_CONFIG", ""),
  81. ProxyTargetIP: defaultEnv("TS_DEST_IP", ""),
  82. ProxyTargetDNSName: defaultEnv("TS_EXPERIMENTAL_DEST_DNS_NAME", ""),
  83. TailnetTargetIP: defaultEnv("TS_TAILNET_TARGET_IP", ""),
  84. TailnetTargetFQDN: defaultEnv("TS_TAILNET_TARGET_FQDN", ""),
  85. DaemonExtraArgs: defaultEnv("TS_TAILSCALED_EXTRA_ARGS", ""),
  86. ExtraArgs: defaultEnv("TS_EXTRA_ARGS", ""),
  87. InKubernetes: os.Getenv("KUBERNETES_SERVICE_HOST") != "",
  88. UserspaceMode: defaultBool("TS_USERSPACE", true),
  89. StateDir: defaultEnv("TS_STATE_DIR", ""),
  90. AcceptDNS: defaultEnvBoolPointer("TS_ACCEPT_DNS"),
  91. KubeSecret: defaultEnv("TS_KUBE_SECRET", "tailscale"),
  92. SOCKSProxyAddr: defaultEnv("TS_SOCKS5_SERVER", ""),
  93. HTTPProxyAddr: defaultEnv("TS_OUTBOUND_HTTP_PROXY_LISTEN", ""),
  94. Socket: defaultEnv("TS_SOCKET", "/tmp/tailscaled.sock"),
  95. AuthOnce: defaultBool("TS_AUTH_ONCE", false),
  96. Root: defaultEnv("TS_TEST_ONLY_ROOT", "/"),
  97. TailscaledConfigFilePath: tailscaledConfigFilePath(),
  98. AllowProxyingClusterTrafficViaIngress: defaultBool("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS", false),
  99. PodIP: defaultEnv("POD_IP", ""),
  100. EnableForwardingOptimizations: defaultBool("TS_EXPERIMENTAL_ENABLE_FORWARDING_OPTIMIZATIONS", false),
  101. HealthCheckAddrPort: defaultEnv("TS_HEALTHCHECK_ADDR_PORT", ""),
  102. LocalAddrPort: defaultEnv("TS_LOCAL_ADDR_PORT", defaultLocalAddrPort),
  103. MetricsEnabled: defaultBool("TS_METRICS_ENABLED", false),
  104. DebugAddrPort: defaultEnv("TS_DEBUG_ADDR_PORT", ""),
  105. EgressSvcsCfgPath: defaultEnv("TS_EGRESS_SERVICES_CONFIG_PATH", ""),
  106. }
  107. podIPs, ok := os.LookupEnv("POD_IPS")
  108. if ok {
  109. ips := strings.Split(podIPs, ",")
  110. if len(ips) > 2 {
  111. return nil, fmt.Errorf("POD_IPs can contain at most 2 IPs, got %d (%v)", len(ips), ips)
  112. }
  113. for _, ip := range ips {
  114. parsed, err := netip.ParseAddr(ip)
  115. if err != nil {
  116. return nil, fmt.Errorf("error parsing IP address %s: %w", ip, err)
  117. }
  118. if parsed.Is4() {
  119. cfg.PodIPv4 = parsed.String()
  120. continue
  121. }
  122. cfg.PodIPv6 = parsed.String()
  123. }
  124. }
  125. if err := cfg.validate(); err != nil {
  126. return nil, fmt.Errorf("invalid configuration: %v", err)
  127. }
  128. return cfg, nil
  129. }
  130. func (s *settings) validate() error {
  131. if s.TailscaledConfigFilePath != "" {
  132. dir, file := path.Split(s.TailscaledConfigFilePath)
  133. if _, err := os.Stat(dir); err != nil {
  134. return fmt.Errorf("error validating whether directory with tailscaled config file %s exists: %w", dir, err)
  135. }
  136. if _, err := os.Stat(s.TailscaledConfigFilePath); err != nil {
  137. return fmt.Errorf("error validating whether tailscaled config directory %q contains tailscaled config for current capability version %q: %w. If this is a Tailscale Kubernetes operator proxy, please ensure that the version of the operator is not older than the version of the proxy", dir, file, err)
  138. }
  139. if _, err := conffile.Load(s.TailscaledConfigFilePath); err != nil {
  140. return fmt.Errorf("error validating tailscaled configfile contents: %w", err)
  141. }
  142. }
  143. if s.ProxyTargetIP != "" && s.UserspaceMode {
  144. return errors.New("TS_DEST_IP is not supported with TS_USERSPACE")
  145. }
  146. if s.ProxyTargetDNSName != "" && s.UserspaceMode {
  147. return errors.New("TS_EXPERIMENTAL_DEST_DNS_NAME is not supported with TS_USERSPACE")
  148. }
  149. if s.ProxyTargetDNSName != "" && s.ProxyTargetIP != "" {
  150. return errors.New("TS_EXPERIMENTAL_DEST_DNS_NAME and TS_DEST_IP cannot both be set")
  151. }
  152. if s.TailnetTargetIP != "" && s.UserspaceMode {
  153. return errors.New("TS_TAILNET_TARGET_IP is not supported with TS_USERSPACE")
  154. }
  155. if s.TailnetTargetFQDN != "" && s.UserspaceMode {
  156. return errors.New("TS_TAILNET_TARGET_FQDN is not supported with TS_USERSPACE")
  157. }
  158. if s.TailnetTargetFQDN != "" && s.TailnetTargetIP != "" {
  159. return errors.New("Both TS_TAILNET_TARGET_IP and TS_TAILNET_FQDN cannot be set")
  160. }
  161. if s.TailscaledConfigFilePath != "" && (s.AcceptDNS != nil || s.AuthKey != "" || s.Routes != nil || s.ExtraArgs != "" || s.Hostname != "") {
  162. return errors.New("TS_EXPERIMENTAL_VERSIONED_CONFIG_DIR cannot be set in combination with TS_HOSTNAME, TS_EXTRA_ARGS, TS_AUTHKEY, TS_ROUTES, TS_ACCEPT_DNS.")
  163. }
  164. if s.AllowProxyingClusterTrafficViaIngress && s.UserspaceMode {
  165. return errors.New("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS is not supported in userspace mode")
  166. }
  167. if s.AllowProxyingClusterTrafficViaIngress && s.ServeConfigPath == "" {
  168. return errors.New("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS is set but this is not a cluster ingress proxy")
  169. }
  170. if s.AllowProxyingClusterTrafficViaIngress && s.PodIP == "" {
  171. return errors.New("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS is set but POD_IP is not set")
  172. }
  173. if s.EnableForwardingOptimizations && s.UserspaceMode {
  174. return errors.New("TS_EXPERIMENTAL_ENABLE_FORWARDING_OPTIMIZATIONS is not supported in userspace mode")
  175. }
  176. if s.HealthCheckAddrPort != "" {
  177. if _, err := netip.ParseAddrPort(s.HealthCheckAddrPort); err != nil {
  178. return fmt.Errorf("error parsing TS_HEALTH_CHECK_ADDR_PORT value %q: %w", s.HealthCheckAddrPort, err)
  179. }
  180. }
  181. if s.LocalAddrPort != "" {
  182. if _, err := netip.ParseAddrPort(s.LocalAddrPort); err != nil {
  183. return fmt.Errorf("error parsing TS_LOCAL_ADDR_PORT value %q: %w", s.LocalAddrPort, err)
  184. }
  185. }
  186. if s.DebugAddrPort != "" {
  187. if _, err := netip.ParseAddrPort(s.DebugAddrPort); err != nil {
  188. return fmt.Errorf("error parsing TS_DEBUG_ADDR_PORT value %q: %w", s.DebugAddrPort, err)
  189. }
  190. }
  191. return nil
  192. }
  193. // setupKube is responsible for doing any necessary configuration and checks to
  194. // ensure that tailscale state storage and authentication mechanism will work on
  195. // Kubernetes.
  196. func (cfg *settings) setupKube(ctx context.Context) error {
  197. if cfg.KubeSecret == "" {
  198. return nil
  199. }
  200. canPatch, canCreate, err := kc.CheckSecretPermissions(ctx, cfg.KubeSecret)
  201. if err != nil {
  202. return fmt.Errorf("some Kubernetes permissions are missing, please check your RBAC configuration: %v", err)
  203. }
  204. cfg.KubernetesCanPatch = canPatch
  205. s, err := kc.GetSecret(ctx, cfg.KubeSecret)
  206. if err != nil {
  207. if !kubeclient.IsNotFoundErr(err) {
  208. return fmt.Errorf("getting Tailscale state Secret %s: %v", cfg.KubeSecret, err)
  209. }
  210. if !canCreate {
  211. return fmt.Errorf("tailscale state Secret %s does not exist and we don't have permissions to create it. "+
  212. "If you intend to store tailscale state elsewhere than a Kubernetes Secret, "+
  213. "you can explicitly set TS_KUBE_SECRET env var to an empty string. "+
  214. "Else ensure that RBAC is set up that allows the service account associated with this installation to create Secrets.", cfg.KubeSecret)
  215. }
  216. }
  217. // Return early if we already have an auth key.
  218. if cfg.AuthKey != "" || isOneStepConfig(cfg) {
  219. return nil
  220. }
  221. if s == nil {
  222. log.Print("TS_AUTHKEY not provided and state Secret does not exist, login will be interactive if needed.")
  223. return nil
  224. }
  225. keyBytes, _ := s.Data["authkey"]
  226. key := string(keyBytes)
  227. if key != "" {
  228. // Enforce that we must be able to patch out the authkey after
  229. // authenticating if you want to use this feature. This avoids
  230. // us having to deal with the case where we might leave behind
  231. // an unnecessary reusable authkey in a secret, like a rake in
  232. // the grass.
  233. if !cfg.KubernetesCanPatch {
  234. return errors.New("authkey found in TS_KUBE_SECRET, but the pod doesn't have patch permissions on the Secret to manage the authkey.")
  235. }
  236. cfg.AuthKey = key
  237. }
  238. log.Print("No authkey found in state Secret and TS_AUTHKEY not provided, login will be interactive if needed.")
  239. return nil
  240. }
  241. // isTwoStepConfigAuthOnce returns true if the Tailscale node should be configured
  242. // in two steps and login should only happen once.
  243. // Step 1: run 'tailscaled'
  244. // Step 2):
  245. // A) if this is the first time starting this node run 'tailscale up --authkey <authkey> <config opts>'
  246. // B) if this is not the first time starting this node run 'tailscale set <config opts>'.
  247. func isTwoStepConfigAuthOnce(cfg *settings) bool {
  248. return cfg.AuthOnce && cfg.TailscaledConfigFilePath == ""
  249. }
  250. // isTwoStepConfigAlwaysAuth returns true if the Tailscale node should be configured
  251. // in two steps and we should log in every time it starts.
  252. // Step 1: run 'tailscaled'
  253. // Step 2): run 'tailscale up --authkey <authkey> <config opts>'
  254. func isTwoStepConfigAlwaysAuth(cfg *settings) bool {
  255. return !cfg.AuthOnce && cfg.TailscaledConfigFilePath == ""
  256. }
  257. // isOneStepConfig returns true if the Tailscale node should always be ran and
  258. // configured in a single step by running 'tailscaled <config opts>'
  259. func isOneStepConfig(cfg *settings) bool {
  260. return cfg.TailscaledConfigFilePath != ""
  261. }
  262. // isL3Proxy returns true if the Tailscale node needs to be configured to act
  263. // as an L3 proxy, proxying to an endpoint provided via one of the config env
  264. // vars.
  265. func isL3Proxy(cfg *settings) bool {
  266. return cfg.ProxyTargetIP != "" || cfg.ProxyTargetDNSName != "" || cfg.TailnetTargetIP != "" || cfg.TailnetTargetFQDN != "" || cfg.AllowProxyingClusterTrafficViaIngress || cfg.EgressSvcsCfgPath != ""
  267. }
  268. // hasKubeStateStore returns true if the state must be stored in a Kubernetes
  269. // Secret.
  270. func hasKubeStateStore(cfg *settings) bool {
  271. return cfg.InKubernetes && cfg.KubernetesCanPatch && cfg.KubeSecret != ""
  272. }
  273. // defaultEnv returns the value of the given envvar name, or defVal if
  274. // unset.
  275. func defaultEnv(name, defVal string) string {
  276. if v, ok := os.LookupEnv(name); ok {
  277. return v
  278. }
  279. return defVal
  280. }
  281. // defaultEnvStringPointer returns a pointer to the given envvar value if set, else
  282. // returns nil. This is useful in cases where we need to distinguish between a
  283. // variable being set to empty string vs unset.
  284. func defaultEnvStringPointer(name string) *string {
  285. if v, ok := os.LookupEnv(name); ok {
  286. return &v
  287. }
  288. return nil
  289. }
  290. // defaultEnvBoolPointer returns a pointer to the given envvar value if set, else
  291. // returns nil. This is useful in cases where we need to distinguish between a
  292. // variable being explicitly set to false vs unset.
  293. func defaultEnvBoolPointer(name string) *bool {
  294. v := os.Getenv(name)
  295. ret, err := strconv.ParseBool(v)
  296. if err != nil {
  297. return nil
  298. }
  299. return &ret
  300. }
  301. func defaultEnvs(names []string, defVal string) string {
  302. for _, name := range names {
  303. if v, ok := os.LookupEnv(name); ok {
  304. return v
  305. }
  306. }
  307. return defVal
  308. }
  309. // defaultBool returns the boolean value of the given envvar name, or
  310. // defVal if unset or not a bool.
  311. func defaultBool(name string, defVal bool) bool {
  312. v := os.Getenv(name)
  313. ret, err := strconv.ParseBool(v)
  314. if err != nil {
  315. return defVal
  316. }
  317. return ret
  318. }