settings.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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
  66. EgressSvcsCfgPath string
  67. }
  68. func configFromEnv() (*settings, error) {
  69. cfg := &settings{
  70. AuthKey: defaultEnvs([]string{"TS_AUTHKEY", "TS_AUTH_KEY"}, ""),
  71. Hostname: defaultEnv("TS_HOSTNAME", ""),
  72. Routes: defaultEnvStringPointer("TS_ROUTES"),
  73. ServeConfigPath: defaultEnv("TS_SERVE_CONFIG", ""),
  74. ProxyTargetIP: defaultEnv("TS_DEST_IP", ""),
  75. ProxyTargetDNSName: defaultEnv("TS_EXPERIMENTAL_DEST_DNS_NAME", ""),
  76. TailnetTargetIP: defaultEnv("TS_TAILNET_TARGET_IP", ""),
  77. TailnetTargetFQDN: defaultEnv("TS_TAILNET_TARGET_FQDN", ""),
  78. DaemonExtraArgs: defaultEnv("TS_TAILSCALED_EXTRA_ARGS", ""),
  79. ExtraArgs: defaultEnv("TS_EXTRA_ARGS", ""),
  80. InKubernetes: os.Getenv("KUBERNETES_SERVICE_HOST") != "",
  81. UserspaceMode: defaultBool("TS_USERSPACE", true),
  82. StateDir: defaultEnv("TS_STATE_DIR", ""),
  83. AcceptDNS: defaultEnvBoolPointer("TS_ACCEPT_DNS"),
  84. KubeSecret: defaultEnv("TS_KUBE_SECRET", "tailscale"),
  85. SOCKSProxyAddr: defaultEnv("TS_SOCKS5_SERVER", ""),
  86. HTTPProxyAddr: defaultEnv("TS_OUTBOUND_HTTP_PROXY_LISTEN", ""),
  87. Socket: defaultEnv("TS_SOCKET", "/tmp/tailscaled.sock"),
  88. AuthOnce: defaultBool("TS_AUTH_ONCE", false),
  89. Root: defaultEnv("TS_TEST_ONLY_ROOT", "/"),
  90. TailscaledConfigFilePath: tailscaledConfigFilePath(),
  91. AllowProxyingClusterTrafficViaIngress: defaultBool("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS", false),
  92. PodIP: defaultEnv("POD_IP", ""),
  93. EnableForwardingOptimizations: defaultBool("TS_EXPERIMENTAL_ENABLE_FORWARDING_OPTIMIZATIONS", false),
  94. HealthCheckAddrPort: defaultEnv("TS_HEALTHCHECK_ADDR_PORT", ""),
  95. EgressSvcsCfgPath: defaultEnv("TS_EGRESS_SERVICES_CONFIG_PATH", ""),
  96. }
  97. podIPs, ok := os.LookupEnv("POD_IPS")
  98. if ok {
  99. ips := strings.Split(podIPs, ",")
  100. if len(ips) > 2 {
  101. return nil, fmt.Errorf("POD_IPs can contain at most 2 IPs, got %d (%v)", len(ips), ips)
  102. }
  103. for _, ip := range ips {
  104. parsed, err := netip.ParseAddr(ip)
  105. if err != nil {
  106. return nil, fmt.Errorf("error parsing IP address %s: %w", ip, err)
  107. }
  108. if parsed.Is4() {
  109. cfg.PodIPv4 = parsed.String()
  110. continue
  111. }
  112. cfg.PodIPv6 = parsed.String()
  113. }
  114. }
  115. if err := cfg.validate(); err != nil {
  116. return nil, fmt.Errorf("invalid configuration: %v", err)
  117. }
  118. return cfg, nil
  119. }
  120. func (s *settings) validate() error {
  121. if s.TailscaledConfigFilePath != "" {
  122. dir, file := path.Split(s.TailscaledConfigFilePath)
  123. if _, err := os.Stat(dir); err != nil {
  124. return fmt.Errorf("error validating whether directory with tailscaled config file %s exists: %w", dir, err)
  125. }
  126. if _, err := os.Stat(s.TailscaledConfigFilePath); err != nil {
  127. 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)
  128. }
  129. if _, err := conffile.Load(s.TailscaledConfigFilePath); err != nil {
  130. return fmt.Errorf("error validating tailscaled configfile contents: %w", err)
  131. }
  132. }
  133. if s.ProxyTargetIP != "" && s.UserspaceMode {
  134. return errors.New("TS_DEST_IP is not supported with TS_USERSPACE")
  135. }
  136. if s.ProxyTargetDNSName != "" && s.UserspaceMode {
  137. return errors.New("TS_EXPERIMENTAL_DEST_DNS_NAME is not supported with TS_USERSPACE")
  138. }
  139. if s.ProxyTargetDNSName != "" && s.ProxyTargetIP != "" {
  140. return errors.New("TS_EXPERIMENTAL_DEST_DNS_NAME and TS_DEST_IP cannot both be set")
  141. }
  142. if s.TailnetTargetIP != "" && s.UserspaceMode {
  143. return errors.New("TS_TAILNET_TARGET_IP is not supported with TS_USERSPACE")
  144. }
  145. if s.TailnetTargetFQDN != "" && s.UserspaceMode {
  146. return errors.New("TS_TAILNET_TARGET_FQDN is not supported with TS_USERSPACE")
  147. }
  148. if s.TailnetTargetFQDN != "" && s.TailnetTargetIP != "" {
  149. return errors.New("Both TS_TAILNET_TARGET_IP and TS_TAILNET_FQDN cannot be set")
  150. }
  151. if s.TailscaledConfigFilePath != "" && (s.AcceptDNS != nil || s.AuthKey != "" || s.Routes != nil || s.ExtraArgs != "" || s.Hostname != "") {
  152. 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.")
  153. }
  154. if s.AllowProxyingClusterTrafficViaIngress && s.UserspaceMode {
  155. return errors.New("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS is not supported in userspace mode")
  156. }
  157. if s.AllowProxyingClusterTrafficViaIngress && s.ServeConfigPath == "" {
  158. return errors.New("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS is set but this is not a cluster ingress proxy")
  159. }
  160. if s.AllowProxyingClusterTrafficViaIngress && s.PodIP == "" {
  161. return errors.New("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS is set but POD_IP is not set")
  162. }
  163. if s.EnableForwardingOptimizations && s.UserspaceMode {
  164. return errors.New("TS_EXPERIMENTAL_ENABLE_FORWARDING_OPTIMIZATIONS is not supported in userspace mode")
  165. }
  166. if s.HealthCheckAddrPort != "" {
  167. if _, err := netip.ParseAddrPort(s.HealthCheckAddrPort); err != nil {
  168. return fmt.Errorf("error parsing TS_HEALTH_CHECK_ADDR_PORT value %q: %w", s.HealthCheckAddrPort, err)
  169. }
  170. }
  171. return nil
  172. }
  173. // setupKube is responsible for doing any necessary configuration and checks to
  174. // ensure that tailscale state storage and authentication mechanism will work on
  175. // Kubernetes.
  176. func (cfg *settings) setupKube(ctx context.Context) error {
  177. if cfg.KubeSecret == "" {
  178. return nil
  179. }
  180. canPatch, canCreate, err := kc.CheckSecretPermissions(ctx, cfg.KubeSecret)
  181. if err != nil {
  182. return fmt.Errorf("some Kubernetes permissions are missing, please check your RBAC configuration: %v", err)
  183. }
  184. cfg.KubernetesCanPatch = canPatch
  185. s, err := kc.GetSecret(ctx, cfg.KubeSecret)
  186. if err != nil {
  187. if !kubeclient.IsNotFoundErr(err) {
  188. return fmt.Errorf("getting Tailscale state Secret %s: %v", cfg.KubeSecret, err)
  189. }
  190. if !canCreate {
  191. return fmt.Errorf("tailscale state Secret %s does not exist and we don't have permissions to create it. "+
  192. "If you intend to store tailscale state elsewhere than a Kubernetes Secret, "+
  193. "you can explicitly set TS_KUBE_SECRET env var to an empty string. "+
  194. "Else ensure that RBAC is set up that allows the service account associated with this installation to create Secrets.", cfg.KubeSecret)
  195. }
  196. }
  197. // Return early if we already have an auth key.
  198. if cfg.AuthKey != "" || isOneStepConfig(cfg) {
  199. return nil
  200. }
  201. if s == nil {
  202. log.Print("TS_AUTHKEY not provided and state Secret does not exist, login will be interactive if needed.")
  203. return nil
  204. }
  205. keyBytes, _ := s.Data["authkey"]
  206. key := string(keyBytes)
  207. if key != "" {
  208. // Enforce that we must be able to patch out the authkey after
  209. // authenticating if you want to use this feature. This avoids
  210. // us having to deal with the case where we might leave behind
  211. // an unnecessary reusable authkey in a secret, like a rake in
  212. // the grass.
  213. if !cfg.KubernetesCanPatch {
  214. return errors.New("authkey found in TS_KUBE_SECRET, but the pod doesn't have patch permissions on the Secret to manage the authkey.")
  215. }
  216. cfg.AuthKey = key
  217. }
  218. log.Print("No authkey found in state Secret and TS_AUTHKEY not provided, login will be interactive if needed.")
  219. return nil
  220. }
  221. // isTwoStepConfigAuthOnce returns true if the Tailscale node should be configured
  222. // in two steps and login should only happen once.
  223. // Step 1: run 'tailscaled'
  224. // Step 2):
  225. // A) if this is the first time starting this node run 'tailscale up --authkey <authkey> <config opts>'
  226. // B) if this is not the first time starting this node run 'tailscale set <config opts>'.
  227. func isTwoStepConfigAuthOnce(cfg *settings) bool {
  228. return cfg.AuthOnce && cfg.TailscaledConfigFilePath == ""
  229. }
  230. // isTwoStepConfigAlwaysAuth returns true if the Tailscale node should be configured
  231. // in two steps and we should log in every time it starts.
  232. // Step 1: run 'tailscaled'
  233. // Step 2): run 'tailscale up --authkey <authkey> <config opts>'
  234. func isTwoStepConfigAlwaysAuth(cfg *settings) bool {
  235. return !cfg.AuthOnce && cfg.TailscaledConfigFilePath == ""
  236. }
  237. // isOneStepConfig returns true if the Tailscale node should always be ran and
  238. // configured in a single step by running 'tailscaled <config opts>'
  239. func isOneStepConfig(cfg *settings) bool {
  240. return cfg.TailscaledConfigFilePath != ""
  241. }
  242. // isL3Proxy returns true if the Tailscale node needs to be configured to act
  243. // as an L3 proxy, proxying to an endpoint provided via one of the config env
  244. // vars.
  245. func isL3Proxy(cfg *settings) bool {
  246. return cfg.ProxyTargetIP != "" || cfg.ProxyTargetDNSName != "" || cfg.TailnetTargetIP != "" || cfg.TailnetTargetFQDN != "" || cfg.AllowProxyingClusterTrafficViaIngress || cfg.EgressSvcsCfgPath != ""
  247. }
  248. // hasKubeStateStore returns true if the state must be stored in a Kubernetes
  249. // Secret.
  250. func hasKubeStateStore(cfg *settings) bool {
  251. return cfg.InKubernetes && cfg.KubernetesCanPatch && cfg.KubeSecret != ""
  252. }
  253. // defaultEnv returns the value of the given envvar name, or defVal if
  254. // unset.
  255. func defaultEnv(name, defVal string) string {
  256. if v, ok := os.LookupEnv(name); ok {
  257. return v
  258. }
  259. return defVal
  260. }
  261. // defaultEnvStringPointer returns a pointer to the given envvar value if set, else
  262. // returns nil. This is useful in cases where we need to distinguish between a
  263. // variable being set to empty string vs unset.
  264. func defaultEnvStringPointer(name string) *string {
  265. if v, ok := os.LookupEnv(name); ok {
  266. return &v
  267. }
  268. return nil
  269. }
  270. // defaultEnvBoolPointer returns a pointer to the given envvar value if set, else
  271. // returns nil. This is useful in cases where we need to distinguish between a
  272. // variable being explicitly set to false vs unset.
  273. func defaultEnvBoolPointer(name string) *bool {
  274. v := os.Getenv(name)
  275. ret, err := strconv.ParseBool(v)
  276. if err != nil {
  277. return nil
  278. }
  279. return &ret
  280. }
  281. func defaultEnvs(names []string, defVal string) string {
  282. for _, name := range names {
  283. if v, ok := os.LookupEnv(name); ok {
  284. return v
  285. }
  286. }
  287. return defVal
  288. }
  289. // defaultBool returns the boolean value of the given envvar name, or
  290. // defVal if unset or not a bool.
  291. func defaultBool(name string, defVal bool) bool {
  292. v := os.Getenv(name)
  293. ret, err := strconv.ParseBool(v)
  294. if err != nil {
  295. return defVal
  296. }
  297. return ret
  298. }