operator.go 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734
  1. // Copyright (c) Tailscale Inc & contributors
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build !plan9
  4. // tailscale-operator provides a way to expose services running in a Kubernetes
  5. // cluster to your Tailnet.
  6. package main
  7. import (
  8. "context"
  9. "fmt"
  10. "net/http"
  11. "os"
  12. "regexp"
  13. "strconv"
  14. "strings"
  15. "time"
  16. "github.com/go-logr/zapr"
  17. "go.uber.org/zap"
  18. "go.uber.org/zap/zapcore"
  19. appsv1 "k8s.io/api/apps/v1"
  20. corev1 "k8s.io/api/core/v1"
  21. discoveryv1 "k8s.io/api/discovery/v1"
  22. networkingv1 "k8s.io/api/networking/v1"
  23. rbacv1 "k8s.io/api/rbac/v1"
  24. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  25. apiequality "k8s.io/apimachinery/pkg/api/equality"
  26. apierrors "k8s.io/apimachinery/pkg/api/errors"
  27. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  28. "k8s.io/apimachinery/pkg/fields"
  29. klabels "k8s.io/apimachinery/pkg/labels"
  30. "k8s.io/apimachinery/pkg/types"
  31. "k8s.io/client-go/rest"
  32. toolscache "k8s.io/client-go/tools/cache"
  33. "sigs.k8s.io/controller-runtime/pkg/builder"
  34. "sigs.k8s.io/controller-runtime/pkg/cache"
  35. "sigs.k8s.io/controller-runtime/pkg/client"
  36. "sigs.k8s.io/controller-runtime/pkg/client/config"
  37. "sigs.k8s.io/controller-runtime/pkg/handler"
  38. logf "sigs.k8s.io/controller-runtime/pkg/log"
  39. kzap "sigs.k8s.io/controller-runtime/pkg/log/zap"
  40. "sigs.k8s.io/controller-runtime/pkg/manager"
  41. "sigs.k8s.io/controller-runtime/pkg/manager/signals"
  42. "sigs.k8s.io/controller-runtime/pkg/predicate"
  43. "sigs.k8s.io/controller-runtime/pkg/reconcile"
  44. "tailscale.com/client/local"
  45. "tailscale.com/client/tailscale"
  46. "tailscale.com/envknob"
  47. "tailscale.com/hostinfo"
  48. "tailscale.com/ipn"
  49. "tailscale.com/ipn/store/kubestore"
  50. apiproxy "tailscale.com/k8s-operator/api-proxy"
  51. tsapi "tailscale.com/k8s-operator/apis/v1alpha1"
  52. "tailscale.com/k8s-operator/reconciler/tailnet"
  53. "tailscale.com/kube/kubetypes"
  54. "tailscale.com/tsnet"
  55. "tailscale.com/tstime"
  56. "tailscale.com/types/logger"
  57. "tailscale.com/util/set"
  58. "tailscale.com/version"
  59. )
  60. // Generate Connector and ProxyClass CustomResourceDefinition yamls from their Go types.
  61. //go:generate go run sigs.k8s.io/controller-tools/cmd/controller-gen crd schemapatch:manifests=./deploy/crds output:dir=./deploy/crds paths=../../k8s-operator/apis/...
  62. // Generate static manifests for deploying Tailscale operator on Kubernetes from the operator's Helm chart.
  63. //go:generate go run tailscale.com/cmd/k8s-operator/generate staticmanifests
  64. // Generate the helm chart's CRDs (which are ignored from git).
  65. //go:generate go run tailscale.com/cmd/k8s-operator/generate helmcrd
  66. // Generate CRD API docs.
  67. //go:generate go run github.com/elastic/crd-ref-docs --renderer=markdown --source-path=../../k8s-operator/apis/ --config=../../k8s-operator/api-docs-config.yaml --output-path=../../k8s-operator/api.md
  68. func main() {
  69. // Required to use our client API. We're fine with the instability since the
  70. // client lives in the same repo as this code.
  71. tailscale.I_Acknowledge_This_API_Is_Unstable = true
  72. var (
  73. tsNamespace = defaultEnv("OPERATOR_NAMESPACE", "")
  74. tslogging = defaultEnv("OPERATOR_LOGGING", "info")
  75. image = defaultEnv("PROXY_IMAGE", "tailscale/tailscale:latest")
  76. k8sProxyImage = defaultEnv("K8S_PROXY_IMAGE", "tailscale/k8s-proxy:latest")
  77. priorityClassName = defaultEnv("PROXY_PRIORITY_CLASS_NAME", "")
  78. tags = defaultEnv("PROXY_TAGS", "tag:k8s")
  79. tsFirewallMode = defaultEnv("PROXY_FIREWALL_MODE", "")
  80. defaultProxyClass = defaultEnv("PROXY_DEFAULT_CLASS", "")
  81. isDefaultLoadBalancer = defaultBool("OPERATOR_DEFAULT_LOAD_BALANCER", false)
  82. loginServer = strings.TrimSuffix(defaultEnv("OPERATOR_LOGIN_SERVER", ""), "/")
  83. ingressClassName = defaultEnv("OPERATOR_INGRESS_CLASS_NAME", "tailscale")
  84. )
  85. var opts []kzap.Opts
  86. switch tslogging {
  87. case "info":
  88. opts = append(opts, kzap.Level(zapcore.InfoLevel))
  89. case "debug":
  90. opts = append(opts, kzap.Level(zapcore.DebugLevel))
  91. case "dev":
  92. opts = append(opts, kzap.UseDevMode(true), kzap.Level(zapcore.DebugLevel))
  93. }
  94. zlog := kzap.NewRaw(opts...).Sugar()
  95. logf.SetLogger(zapr.NewLogger(zlog.Desugar()))
  96. if tsNamespace == "" {
  97. const namespaceFile = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
  98. b, err := os.ReadFile(namespaceFile)
  99. if err != nil {
  100. zlog.Fatalf("Could not get operator namespace from OPERATOR_NAMESPACE environment variable or default projected volume: %v", err)
  101. }
  102. tsNamespace = strings.TrimSpace(string(b))
  103. }
  104. // The operator can run either as a plain operator or it can
  105. // additionally act as api-server proxy
  106. // https://tailscale.com/kb/1236/kubernetes-operator/?q=kubernetes#accessing-the-kubernetes-control-plane-using-an-api-server-proxy.
  107. mode := parseAPIProxyMode()
  108. if mode == nil {
  109. hostinfo.SetApp(kubetypes.AppOperator)
  110. } else {
  111. hostinfo.SetApp(kubetypes.AppInProcessAPIServerProxy)
  112. }
  113. s, tsc := initTSNet(zlog, loginServer)
  114. defer s.Close()
  115. restConfig := config.GetConfigOrDie()
  116. if mode != nil {
  117. ap, err := apiproxy.NewAPIServerProxy(zlog, restConfig, s, *mode, true)
  118. if err != nil {
  119. zlog.Fatalf("error creating API server proxy: %v", err)
  120. }
  121. go func() {
  122. if err := ap.Run(context.Background()); err != nil {
  123. zlog.Fatalf("error running API server proxy: %v", err)
  124. }
  125. }()
  126. }
  127. // Operator log uploads can be opted-out using the "TS_NO_LOGS_NO_SUPPORT" environment variable.
  128. if !envknob.NoLogsNoSupport() {
  129. zlog = zlog.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core {
  130. return wrapZapCore(core, s.LogtailWriter())
  131. }))
  132. }
  133. rOpts := reconcilerOpts{
  134. log: zlog,
  135. tsServer: s,
  136. tsClient: tsc,
  137. tailscaleNamespace: tsNamespace,
  138. restConfig: restConfig,
  139. proxyImage: image,
  140. k8sProxyImage: k8sProxyImage,
  141. proxyPriorityClassName: priorityClassName,
  142. proxyActAsDefaultLoadBalancer: isDefaultLoadBalancer,
  143. proxyTags: tags,
  144. proxyFirewallMode: tsFirewallMode,
  145. defaultProxyClass: defaultProxyClass,
  146. loginServer: loginServer,
  147. ingressClassName: ingressClassName,
  148. }
  149. runReconcilers(rOpts)
  150. }
  151. // initTSNet initializes the tsnet.Server and logs in to Tailscale. If CLIENT_ID
  152. // is set, it authenticates to the Tailscale API using the federated OIDC workload
  153. // identity flow. Otherwise, it uses the CLIENT_ID_FILE and CLIENT_SECRET_FILE
  154. // environment variables to authenticate with static credentials.
  155. func initTSNet(zlog *zap.SugaredLogger, loginServer string) (*tsnet.Server, tsClient) {
  156. var (
  157. clientID = defaultEnv("CLIENT_ID", "") // Used for workload identity federation.
  158. clientIDPath = defaultEnv("CLIENT_ID_FILE", "") // Used for static client credentials.
  159. clientSecretPath = defaultEnv("CLIENT_SECRET_FILE", "") // Used for static client credentials.
  160. hostname = defaultEnv("OPERATOR_HOSTNAME", "tailscale-operator")
  161. kubeSecret = defaultEnv("OPERATOR_SECRET", "")
  162. operatorTags = defaultEnv("OPERATOR_INITIAL_TAGS", "tag:k8s-operator")
  163. )
  164. startlog := zlog.Named("startup")
  165. if clientID == "" && (clientIDPath == "" || clientSecretPath == "") {
  166. startlog.Fatalf("CLIENT_ID_FILE and CLIENT_SECRET_FILE must be set") // TODO(tomhjp): error message can mention WIF once it's publicly available.
  167. }
  168. tsc, err := newTSClient(zlog.Named("ts-api-client"), clientID, clientIDPath, clientSecretPath, loginServer)
  169. if err != nil {
  170. startlog.Fatalf("error creating Tailscale client: %v", err)
  171. }
  172. s := &tsnet.Server{
  173. Hostname: hostname,
  174. Logf: zlog.Named("tailscaled").Debugf,
  175. ControlURL: loginServer,
  176. }
  177. if p := os.Getenv("TS_PORT"); p != "" {
  178. port, err := strconv.ParseUint(p, 10, 16)
  179. if err != nil {
  180. startlog.Fatalf("TS_PORT %q cannot be parsed as uint16: %v", p, err)
  181. }
  182. s.Port = uint16(port)
  183. }
  184. if kubeSecret != "" {
  185. st, err := kubestore.New(logger.Discard, kubeSecret)
  186. if err != nil {
  187. startlog.Fatalf("creating kube store: %v", err)
  188. }
  189. s.Store = st
  190. }
  191. if err := s.Start(); err != nil {
  192. startlog.Fatalf("starting tailscale server: %v", err)
  193. }
  194. lc, err := s.LocalClient()
  195. if err != nil {
  196. startlog.Fatalf("getting local client: %v", err)
  197. }
  198. ctx := context.Background()
  199. loginDone := false
  200. machineAuthShown := false
  201. waitOnline:
  202. for {
  203. startlog.Debugf("querying tailscaled status")
  204. st, err := lc.StatusWithoutPeers(ctx)
  205. if err != nil {
  206. startlog.Fatalf("getting status: %v", err)
  207. }
  208. switch st.BackendState {
  209. case "Running":
  210. break waitOnline
  211. case "NeedsLogin":
  212. if loginDone {
  213. break
  214. }
  215. caps := tailscale.KeyCapabilities{
  216. Devices: tailscale.KeyDeviceCapabilities{
  217. Create: tailscale.KeyDeviceCreateCapabilities{
  218. Reusable: false,
  219. Preauthorized: true,
  220. Tags: strings.Split(operatorTags, ","),
  221. },
  222. },
  223. }
  224. authkey, _, err := tsc.CreateKey(ctx, caps)
  225. if err != nil {
  226. startlog.Fatalf("creating operator authkey: %v", err)
  227. }
  228. if err := lc.Start(ctx, ipn.Options{
  229. AuthKey: authkey,
  230. }); err != nil {
  231. startlog.Fatalf("starting tailscale: %v", err)
  232. }
  233. if err := lc.StartLoginInteractive(ctx); err != nil {
  234. startlog.Fatalf("starting login: %v", err)
  235. }
  236. startlog.Debugf("requested login by authkey")
  237. loginDone = true
  238. case "NeedsMachineAuth":
  239. if !machineAuthShown {
  240. startlog.Infof("Machine approval required, please visit the admin panel to approve")
  241. machineAuthShown = true
  242. }
  243. default:
  244. startlog.Debugf("waiting for tailscale to start: %v", st.BackendState)
  245. }
  246. time.Sleep(time.Second)
  247. }
  248. return s, tsc
  249. }
  250. // predicate function for filtering to ensure we *don't* reconcile on tailscale managed Kubernetes Services
  251. func serviceManagedResourceFilterPredicate() predicate.Predicate {
  252. return predicate.NewPredicateFuncs(func(object client.Object) bool {
  253. if svc, ok := object.(*corev1.Service); !ok {
  254. return false
  255. } else {
  256. return !isManagedResource(svc)
  257. }
  258. })
  259. }
  260. // runReconcilers starts the controller-runtime manager and registers the
  261. // ServiceReconciler. It blocks forever.
  262. func runReconcilers(opts reconcilerOpts) {
  263. startlog := opts.log.Named("startReconcilers")
  264. // For secrets and statefulsets, we only get permission to touch the objects
  265. // in the controller's own namespace. This cannot be expressed by
  266. // .Watches(...) below, instead you have to add a per-type field selector to
  267. // the cache that sits a few layers below the builder stuff, which will
  268. // implicitly filter what parts of the world the builder code gets to see at
  269. // all.
  270. nsFilter := cache.ByObject{
  271. Field: client.InNamespace(opts.tailscaleNamespace).AsSelector(),
  272. }
  273. // We watch the ServiceMonitor CRD to ensure that reconcilers are re-triggered if user's workflows result in the
  274. // ServiceMonitor CRD applied after some of our resources that define ServiceMonitor creation. This selector
  275. // ensures that we only watch the ServiceMonitor CRD and that we don't cache full contents of it.
  276. serviceMonitorSelector := cache.ByObject{
  277. Field: fields.SelectorFromSet(fields.Set{"metadata.name": serviceMonitorCRD}),
  278. Transform: crdTransformer(startlog),
  279. }
  280. // TODO (irbekrm): stricter filtering what we watch/cache/call
  281. // reconcilers on. c/r by default starts a watch on any
  282. // resources that we GET via the controller manager's client.
  283. mgrOpts := manager.Options{
  284. // The cache will apply the specified filters only to the object types listed below via ByObject.
  285. // Other object types (e.g., EndpointSlices) can still be fetched or watched using the cached client, but they will not have any filtering applied.
  286. Cache: cache.Options{
  287. ByObject: map[client.Object]cache.ByObject{
  288. &corev1.Secret{}: nsFilter,
  289. &corev1.ServiceAccount{}: nsFilter,
  290. &corev1.Pod{}: nsFilter,
  291. &corev1.ConfigMap{}: nsFilter,
  292. &appsv1.StatefulSet{}: nsFilter,
  293. &appsv1.Deployment{}: nsFilter,
  294. &rbacv1.Role{}: nsFilter,
  295. &rbacv1.RoleBinding{}: nsFilter,
  296. &apiextensionsv1.CustomResourceDefinition{}: serviceMonitorSelector,
  297. },
  298. },
  299. Scheme: tsapi.GlobalScheme,
  300. }
  301. mgr, err := manager.New(opts.restConfig, mgrOpts)
  302. if err != nil {
  303. startlog.Fatalf("could not create manager: %v", err)
  304. }
  305. tailnetOptions := tailnet.ReconcilerOptions{
  306. Client: mgr.GetClient(),
  307. TailscaleNamespace: opts.tailscaleNamespace,
  308. Clock: tstime.DefaultClock{},
  309. Logger: opts.log,
  310. }
  311. if err = tailnet.NewReconciler(tailnetOptions).Register(mgr); err != nil {
  312. startlog.Fatalf("could not register tailnet reconciler: %v", err)
  313. }
  314. svcFilter := handler.EnqueueRequestsFromMapFunc(serviceHandler)
  315. svcChildFilter := handler.EnqueueRequestsFromMapFunc(managedResourceHandlerForType("svc"))
  316. // If a ProxyClass changes, enqueue all Services labeled with that
  317. // ProxyClass's name.
  318. proxyClassFilterForSvc := handler.EnqueueRequestsFromMapFunc(proxyClassHandlerForSvc(mgr.GetClient(), startlog))
  319. eventRecorder := mgr.GetEventRecorderFor("tailscale-operator")
  320. ssr := &tailscaleSTSReconciler{
  321. Client: mgr.GetClient(),
  322. tsnetServer: opts.tsServer,
  323. tsClient: opts.tsClient,
  324. defaultTags: strings.Split(opts.proxyTags, ","),
  325. operatorNamespace: opts.tailscaleNamespace,
  326. proxyImage: opts.proxyImage,
  327. proxyPriorityClassName: opts.proxyPriorityClassName,
  328. tsFirewallMode: opts.proxyFirewallMode,
  329. loginServer: opts.tsServer.ControlURL,
  330. }
  331. err = builder.
  332. ControllerManagedBy(mgr).
  333. Named("service-reconciler").
  334. Watches(&corev1.Service{}, svcFilter).
  335. Watches(&appsv1.StatefulSet{}, svcChildFilter).
  336. Watches(&corev1.Secret{}, svcChildFilter).
  337. Watches(&tsapi.ProxyClass{}, proxyClassFilterForSvc).
  338. Complete(&ServiceReconciler{
  339. ssr: ssr,
  340. Client: mgr.GetClient(),
  341. logger: opts.log.Named("service-reconciler"),
  342. isDefaultLoadBalancer: opts.proxyActAsDefaultLoadBalancer,
  343. recorder: eventRecorder,
  344. tsNamespace: opts.tailscaleNamespace,
  345. clock: tstime.DefaultClock{},
  346. defaultProxyClass: opts.defaultProxyClass,
  347. })
  348. if err != nil {
  349. startlog.Fatalf("could not create service reconciler: %v", err)
  350. }
  351. if err := mgr.GetFieldIndexer().IndexField(context.Background(), new(corev1.Service), indexServiceProxyClass, indexProxyClass); err != nil {
  352. startlog.Fatalf("failed setting up ProxyClass indexer for Services: %v", err)
  353. }
  354. ingressChildFilter := handler.EnqueueRequestsFromMapFunc(managedResourceHandlerForType("ingress"))
  355. // If a ProxyClassChanges, enqueue all Ingresses labeled with that
  356. // ProxyClass's name.
  357. proxyClassFilterForIngress := handler.EnqueueRequestsFromMapFunc(proxyClassHandlerForIngress(mgr.GetClient(), startlog))
  358. // Enque Ingress if a managed Service or backend Service associated with a tailscale Ingress changes.
  359. svcHandlerForIngress := handler.EnqueueRequestsFromMapFunc(serviceHandlerForIngress(mgr.GetClient(), startlog, opts.ingressClassName))
  360. err = builder.
  361. ControllerManagedBy(mgr).
  362. For(&networkingv1.Ingress{}).
  363. Named("ingress-reconciler").
  364. Watches(&appsv1.StatefulSet{}, ingressChildFilter).
  365. Watches(&corev1.Secret{}, ingressChildFilter).
  366. Watches(&corev1.Service{}, svcHandlerForIngress).
  367. Watches(&tsapi.ProxyClass{}, proxyClassFilterForIngress).
  368. Complete(&IngressReconciler{
  369. ssr: ssr,
  370. recorder: eventRecorder,
  371. Client: mgr.GetClient(),
  372. logger: opts.log.Named("ingress-reconciler"),
  373. defaultProxyClass: opts.defaultProxyClass,
  374. ingressClassName: opts.ingressClassName,
  375. })
  376. if err != nil {
  377. startlog.Fatalf("could not create ingress reconciler: %v", err)
  378. }
  379. if err := mgr.GetFieldIndexer().IndexField(context.Background(), new(networkingv1.Ingress), indexIngressProxyClass, indexProxyClass); err != nil {
  380. startlog.Fatalf("failed setting up ProxyClass indexer for Ingresses: %v", err)
  381. }
  382. lc, err := opts.tsServer.LocalClient()
  383. if err != nil {
  384. startlog.Fatalf("could not get local client: %v", err)
  385. }
  386. id, err := id(context.Background(), lc)
  387. if err != nil {
  388. startlog.Fatalf("error determining stable ID of the operator's Tailscale device: %v", err)
  389. }
  390. ingressProxyGroupFilter := handler.EnqueueRequestsFromMapFunc(ingressesFromIngressProxyGroup(mgr.GetClient(), opts.log))
  391. err = builder.
  392. ControllerManagedBy(mgr).
  393. For(&networkingv1.Ingress{}).
  394. Named("ingress-pg-reconciler").
  395. Watches(&corev1.Service{}, handler.EnqueueRequestsFromMapFunc(serviceHandlerForIngressPG(mgr.GetClient(), startlog, opts.ingressClassName))).
  396. Watches(&corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(HAIngressesFromSecret(mgr.GetClient(), startlog))).
  397. Watches(&tsapi.ProxyGroup{}, ingressProxyGroupFilter).
  398. Complete(&HAIngressReconciler{
  399. recorder: eventRecorder,
  400. tsClient: opts.tsClient,
  401. tsnetServer: opts.tsServer,
  402. defaultTags: strings.Split(opts.proxyTags, ","),
  403. Client: mgr.GetClient(),
  404. logger: opts.log.Named("ingress-pg-reconciler"),
  405. lc: lc,
  406. operatorID: id,
  407. tsNamespace: opts.tailscaleNamespace,
  408. ingressClassName: opts.ingressClassName,
  409. })
  410. if err != nil {
  411. startlog.Fatalf("could not create ingress-pg-reconciler: %v", err)
  412. }
  413. if err := mgr.GetFieldIndexer().IndexField(context.Background(), new(networkingv1.Ingress), indexIngressProxyGroup, indexPGIngresses); err != nil {
  414. startlog.Fatalf("failed setting up indexer for HA Ingresses: %v", err)
  415. }
  416. ingressSvcFromEpsFilter := handler.EnqueueRequestsFromMapFunc(ingressSvcFromEps(mgr.GetClient(), opts.log.Named("service-pg-reconciler")))
  417. err = builder.
  418. ControllerManagedBy(mgr).
  419. For(&corev1.Service{}, builder.WithPredicates(serviceManagedResourceFilterPredicate())).
  420. Named("service-pg-reconciler").
  421. Watches(&corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(HAServicesFromSecret(mgr.GetClient(), startlog))).
  422. Watches(&tsapi.ProxyGroup{}, ingressProxyGroupFilter).
  423. Watches(&discoveryv1.EndpointSlice{}, ingressSvcFromEpsFilter).
  424. Complete(&HAServiceReconciler{
  425. recorder: eventRecorder,
  426. tsClient: opts.tsClient,
  427. defaultTags: strings.Split(opts.proxyTags, ","),
  428. Client: mgr.GetClient(),
  429. logger: opts.log.Named("service-pg-reconciler"),
  430. lc: lc,
  431. clock: tstime.DefaultClock{},
  432. operatorID: id,
  433. tsNamespace: opts.tailscaleNamespace,
  434. })
  435. if err != nil {
  436. startlog.Fatalf("could not create service-pg-reconciler: %v", err)
  437. }
  438. if err := mgr.GetFieldIndexer().IndexField(context.Background(), new(corev1.Service), indexIngressProxyGroup, indexPGIngresses); err != nil {
  439. startlog.Fatalf("failed setting up indexer for HA Services: %v", err)
  440. }
  441. connectorFilter := handler.EnqueueRequestsFromMapFunc(managedResourceHandlerForType("connector"))
  442. // If a ProxyClassChanges, enqueue all Connectors that have
  443. // .spec.proxyClass set to the name of this ProxyClass.
  444. proxyClassFilterForConnector := handler.EnqueueRequestsFromMapFunc(proxyClassHandlerForConnector(mgr.GetClient(), startlog))
  445. err = builder.ControllerManagedBy(mgr).
  446. For(&tsapi.Connector{}).
  447. Named("connector-reconciler").
  448. Watches(&appsv1.StatefulSet{}, connectorFilter).
  449. Watches(&corev1.Secret{}, connectorFilter).
  450. Watches(&tsapi.ProxyClass{}, proxyClassFilterForConnector).
  451. Complete(&ConnectorReconciler{
  452. ssr: ssr,
  453. recorder: eventRecorder,
  454. Client: mgr.GetClient(),
  455. logger: opts.log.Named("connector-reconciler"),
  456. clock: tstime.DefaultClock{},
  457. })
  458. if err != nil {
  459. startlog.Fatalf("could not create connector reconciler: %v", err)
  460. }
  461. // TODO (irbekrm): switch to metadata-only watches for resources whose
  462. // spec we don't need to inspect to reduce memory consumption.
  463. // https://github.com/kubernetes-sigs/controller-runtime/issues/1159
  464. nameserverFilter := handler.EnqueueRequestsFromMapFunc(managedResourceHandlerForType("nameserver"))
  465. err = builder.ControllerManagedBy(mgr).
  466. For(&tsapi.DNSConfig{}).
  467. Named("nameserver-reconciler").
  468. Watches(&appsv1.Deployment{}, nameserverFilter).
  469. Watches(&corev1.ConfigMap{}, nameserverFilter).
  470. Watches(&corev1.Service{}, nameserverFilter).
  471. Watches(&corev1.ServiceAccount{}, nameserverFilter).
  472. Complete(&NameserverReconciler{
  473. recorder: eventRecorder,
  474. tsNamespace: opts.tailscaleNamespace,
  475. Client: mgr.GetClient(),
  476. logger: opts.log.Named("nameserver-reconciler"),
  477. clock: tstime.DefaultClock{},
  478. })
  479. if err != nil {
  480. startlog.Fatalf("could not create nameserver reconciler: %v", err)
  481. }
  482. egressSvcFilter := handler.EnqueueRequestsFromMapFunc(egressSvcsHandler)
  483. egressProxyGroupFilter := handler.EnqueueRequestsFromMapFunc(egressSvcsFromEgressProxyGroup(mgr.GetClient(), opts.log))
  484. err = builder.
  485. ControllerManagedBy(mgr).
  486. Named("egress-svcs-reconciler").
  487. Watches(&corev1.Service{}, egressSvcFilter).
  488. Watches(&tsapi.ProxyGroup{}, egressProxyGroupFilter).
  489. Complete(&egressSvcsReconciler{
  490. Client: mgr.GetClient(),
  491. tsNamespace: opts.tailscaleNamespace,
  492. recorder: eventRecorder,
  493. clock: tstime.DefaultClock{},
  494. logger: opts.log.Named("egress-svcs-reconciler"),
  495. })
  496. if err != nil {
  497. startlog.Fatalf("could not create egress Services reconciler: %v", err)
  498. }
  499. if err := mgr.GetFieldIndexer().IndexField(context.Background(), new(corev1.Service), indexEgressProxyGroup, indexEgressServices); err != nil {
  500. startlog.Fatalf("failed setting up indexer for egress Services: %v", err)
  501. }
  502. egressSvcFromEpsFilter := handler.EnqueueRequestsFromMapFunc(egressSvcFromEps)
  503. err = builder.
  504. ControllerManagedBy(mgr).
  505. Named("egress-svcs-readiness-reconciler").
  506. Watches(&corev1.Service{}, egressSvcFilter).
  507. Watches(&discoveryv1.EndpointSlice{}, egressSvcFromEpsFilter).
  508. Complete(&egressSvcsReadinessReconciler{
  509. Client: mgr.GetClient(),
  510. tsNamespace: opts.tailscaleNamespace,
  511. clock: tstime.DefaultClock{},
  512. logger: opts.log.Named("egress-svcs-readiness-reconciler"),
  513. })
  514. if err != nil {
  515. startlog.Fatalf("could not create egress Services readiness reconciler: %v", err)
  516. }
  517. epsFilter := handler.EnqueueRequestsFromMapFunc(egressEpsHandler)
  518. podsFilter := handler.EnqueueRequestsFromMapFunc(egressEpsFromPGPods(mgr.GetClient(), opts.tailscaleNamespace))
  519. secretsFilter := handler.EnqueueRequestsFromMapFunc(egressEpsFromPGStateSecrets(mgr.GetClient(), opts.tailscaleNamespace))
  520. epsFromExtNSvcFilter := handler.EnqueueRequestsFromMapFunc(epsFromExternalNameService(mgr.GetClient(), opts.log, opts.tailscaleNamespace))
  521. err = builder.
  522. ControllerManagedBy(mgr).
  523. Named("egress-eps-reconciler").
  524. Watches(&discoveryv1.EndpointSlice{}, epsFilter).
  525. Watches(&corev1.Pod{}, podsFilter).
  526. Watches(&corev1.Secret{}, secretsFilter).
  527. Watches(&corev1.Service{}, epsFromExtNSvcFilter).
  528. Complete(&egressEpsReconciler{
  529. Client: mgr.GetClient(),
  530. tsNamespace: opts.tailscaleNamespace,
  531. logger: opts.log.Named("egress-eps-reconciler"),
  532. })
  533. if err != nil {
  534. startlog.Fatalf("could not create egress EndpointSlices reconciler: %v", err)
  535. }
  536. podsForEps := handler.EnqueueRequestsFromMapFunc(podsFromEgressEps(mgr.GetClient(), opts.log, opts.tailscaleNamespace))
  537. podsER := handler.EnqueueRequestsFromMapFunc(egressPodsHandler)
  538. err = builder.
  539. ControllerManagedBy(mgr).
  540. Named("egress-pods-readiness-reconciler").
  541. Watches(&discoveryv1.EndpointSlice{}, podsForEps).
  542. Watches(&corev1.Pod{}, podsER).
  543. Complete(&egressPodsReconciler{
  544. Client: mgr.GetClient(),
  545. tsNamespace: opts.tailscaleNamespace,
  546. clock: tstime.DefaultClock{},
  547. logger: opts.log.Named("egress-pods-readiness-reconciler"),
  548. httpClient: http.DefaultClient,
  549. })
  550. if err != nil {
  551. startlog.Fatalf("could not create egress Pods readiness reconciler: %v", err)
  552. }
  553. // ProxyClass reconciler gets triggered on ServiceMonitor CRD changes to ensure that any ProxyClasses, that
  554. // define that a ServiceMonitor should be created, were set to invalid because the CRD did not exist get
  555. // reconciled if the CRD is applied at a later point.
  556. kPortRange := getServicesNodePortRange(context.Background(), mgr.GetClient(), opts.tailscaleNamespace, startlog)
  557. serviceMonitorFilter := handler.EnqueueRequestsFromMapFunc(proxyClassesWithServiceMonitor(mgr.GetClient(), opts.log))
  558. err = builder.ControllerManagedBy(mgr).
  559. For(&tsapi.ProxyClass{}).
  560. Named("proxyclass-reconciler").
  561. Watches(&apiextensionsv1.CustomResourceDefinition{}, serviceMonitorFilter).
  562. Complete(&ProxyClassReconciler{
  563. Client: mgr.GetClient(),
  564. nodePortRange: kPortRange,
  565. recorder: eventRecorder,
  566. tsNamespace: opts.tailscaleNamespace,
  567. logger: opts.log.Named("proxyclass-reconciler"),
  568. clock: tstime.DefaultClock{},
  569. })
  570. if err != nil {
  571. startlog.Fatal("could not create proxyclass reconciler: %v", err)
  572. }
  573. logger := startlog.Named("dns-records-reconciler-event-handlers")
  574. // On EndpointSlice events, if it is an EndpointSlice for an
  575. // ingress/egress proxy headless Service, reconcile the headless
  576. // Service.
  577. dnsRREpsOpts := handler.EnqueueRequestsFromMapFunc(dnsRecordsReconcilerEndpointSliceHandler)
  578. // On DNSConfig changes, reconcile all headless Services for
  579. // ingress/egress proxies in operator namespace.
  580. dnsRRDNSConfigOpts := handler.EnqueueRequestsFromMapFunc(enqueueAllIngressEgressProxySvcsInNS(opts.tailscaleNamespace, mgr.GetClient(), logger))
  581. // On Service events, if it is an ingress/egress proxy headless Service, reconcile it.
  582. dnsRRServiceOpts := handler.EnqueueRequestsFromMapFunc(dnsRecordsReconcilerServiceHandler)
  583. // On Ingress events, if it is a tailscale Ingress or if tailscale is the default ingress controller, reconcile the proxy
  584. // headless Service.
  585. dnsRRIngressOpts := handler.EnqueueRequestsFromMapFunc(dnsRecordsReconcilerIngressHandler(opts.tailscaleNamespace, opts.proxyActAsDefaultLoadBalancer, mgr.GetClient(), logger))
  586. err = builder.ControllerManagedBy(mgr).
  587. Named("dns-records-reconciler").
  588. Watches(&corev1.Service{}, dnsRRServiceOpts).
  589. Watches(&networkingv1.Ingress{}, dnsRRIngressOpts).
  590. Watches(&discoveryv1.EndpointSlice{}, dnsRREpsOpts).
  591. Watches(&tsapi.DNSConfig{}, dnsRRDNSConfigOpts).
  592. Complete(&dnsRecordsReconciler{
  593. Client: mgr.GetClient(),
  594. tsNamespace: opts.tailscaleNamespace,
  595. logger: opts.log.Named("dns-records-reconciler"),
  596. isDefaultLoadBalancer: opts.proxyActAsDefaultLoadBalancer,
  597. })
  598. if err != nil {
  599. startlog.Fatalf("could not create DNS records reconciler: %v", err)
  600. }
  601. // Recorder reconciler.
  602. recorderFilter := handler.EnqueueRequestForOwner(mgr.GetScheme(), mgr.GetRESTMapper(), &tsapi.Recorder{})
  603. err = builder.ControllerManagedBy(mgr).
  604. For(&tsapi.Recorder{}).
  605. Named("recorder-reconciler").
  606. Watches(&appsv1.StatefulSet{}, recorderFilter).
  607. Watches(&corev1.ServiceAccount{}, recorderFilter).
  608. Watches(&corev1.Secret{}, recorderFilter).
  609. Watches(&rbacv1.Role{}, recorderFilter).
  610. Watches(&rbacv1.RoleBinding{}, recorderFilter).
  611. Complete(&RecorderReconciler{
  612. recorder: eventRecorder,
  613. tsNamespace: opts.tailscaleNamespace,
  614. Client: mgr.GetClient(),
  615. log: opts.log.Named("recorder-reconciler"),
  616. clock: tstime.DefaultClock{},
  617. tsClient: opts.tsClient,
  618. loginServer: opts.loginServer,
  619. })
  620. if err != nil {
  621. startlog.Fatalf("could not create Recorder reconciler: %v", err)
  622. }
  623. // kube-apiserver's Tailscale Service reconciler.
  624. err = builder.
  625. ControllerManagedBy(mgr).
  626. For(&tsapi.ProxyGroup{}, builder.WithPredicates(
  627. predicate.NewPredicateFuncs(func(obj client.Object) bool {
  628. pg, ok := obj.(*tsapi.ProxyGroup)
  629. return ok && pg.Spec.Type == tsapi.ProxyGroupTypeKubernetesAPIServer
  630. }),
  631. )).
  632. Named("kube-apiserver-ts-service-reconciler").
  633. Watches(&corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(kubeAPIServerPGsFromSecret(mgr.GetClient(), startlog))).
  634. Complete(&KubeAPIServerTSServiceReconciler{
  635. Client: mgr.GetClient(),
  636. recorder: eventRecorder,
  637. logger: opts.log.Named("kube-apiserver-ts-service-reconciler"),
  638. tsClient: opts.tsClient,
  639. tsNamespace: opts.tailscaleNamespace,
  640. lc: lc,
  641. defaultTags: strings.Split(opts.proxyTags, ","),
  642. operatorID: id,
  643. clock: tstime.DefaultClock{},
  644. })
  645. if err != nil {
  646. startlog.Fatalf("could not create Kubernetes API server Tailscale Service reconciler: %v", err)
  647. }
  648. // ProxyGroup reconciler.
  649. ownedByProxyGroupFilter := handler.EnqueueRequestForOwner(mgr.GetScheme(), mgr.GetRESTMapper(), &tsapi.ProxyGroup{})
  650. proxyClassFilterForProxyGroup := handler.EnqueueRequestsFromMapFunc(proxyClassHandlerForProxyGroup(mgr.GetClient(), startlog))
  651. nodeFilterForProxyGroup := handler.EnqueueRequestsFromMapFunc(nodeHandlerForProxyGroup(mgr.GetClient(), opts.defaultProxyClass, startlog))
  652. saFilterForProxyGroup := handler.EnqueueRequestsFromMapFunc(serviceAccountHandlerForProxyGroup(mgr.GetClient(), startlog))
  653. err = builder.ControllerManagedBy(mgr).
  654. For(&tsapi.ProxyGroup{}).
  655. Named("proxygroup-reconciler").
  656. Watches(&corev1.Service{}, ownedByProxyGroupFilter).
  657. Watches(&appsv1.StatefulSet{}, ownedByProxyGroupFilter).
  658. Watches(&corev1.ConfigMap{}, ownedByProxyGroupFilter).
  659. Watches(&corev1.ServiceAccount{}, saFilterForProxyGroup).
  660. Watches(&corev1.Secret{}, ownedByProxyGroupFilter).
  661. Watches(&rbacv1.Role{}, ownedByProxyGroupFilter).
  662. Watches(&rbacv1.RoleBinding{}, ownedByProxyGroupFilter).
  663. Watches(&tsapi.ProxyClass{}, proxyClassFilterForProxyGroup).
  664. Watches(&corev1.Node{}, nodeFilterForProxyGroup).
  665. Complete(&ProxyGroupReconciler{
  666. recorder: eventRecorder,
  667. Client: mgr.GetClient(),
  668. log: opts.log.Named("proxygroup-reconciler"),
  669. clock: tstime.DefaultClock{},
  670. tsClient: opts.tsClient,
  671. tsNamespace: opts.tailscaleNamespace,
  672. tsProxyImage: opts.proxyImage,
  673. k8sProxyImage: opts.k8sProxyImage,
  674. defaultTags: strings.Split(opts.proxyTags, ","),
  675. tsFirewallMode: opts.proxyFirewallMode,
  676. defaultProxyClass: opts.defaultProxyClass,
  677. loginServer: opts.tsServer.ControlURL,
  678. })
  679. if err != nil {
  680. startlog.Fatalf("could not create ProxyGroup reconciler: %v", err)
  681. }
  682. startlog.Infof("Startup complete, operator running, version: %s", version.Long())
  683. if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
  684. startlog.Fatalf("could not start manager: %v", err)
  685. }
  686. }
  687. type reconcilerOpts struct {
  688. log *zap.SugaredLogger
  689. tsServer *tsnet.Server
  690. tsClient tsClient
  691. tailscaleNamespace string // namespace in which operator resources will be deployed
  692. restConfig *rest.Config // config for connecting to the kube API server
  693. proxyImage string // <proxy-image-repo>:<proxy-image-tag>
  694. k8sProxyImage string // <k8s-proxy-image-repo>:<k8s-proxy-image-tag>
  695. // proxyPriorityClassName isPriorityClass to be set for proxy Pods. This
  696. // is a legacy mechanism for cluster resource configuration options -
  697. // going forward use ProxyClass.
  698. // https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/#priorityclass
  699. proxyPriorityClassName string
  700. // proxyTags are ACL tags to tag proxy auth keys. Multiple tags should
  701. // be provided as a string with comma-separated tag values. Proxy tags
  702. // default to tag:k8s.
  703. // https://tailscale.com/kb/1085/auth-keys
  704. proxyTags string
  705. // proxyActAsDefaultLoadBalancer determines whether this operator
  706. // instance should act as the default ingress controller when looking at
  707. // Ingress resources with unset ingress.spec.ingressClassName.
  708. // TODO (irbekrm): this setting does not respect the default
  709. // IngressClass.
  710. // https://kubernetes.io/docs/concepts/services-networking/ingress/#default-ingress-class
  711. // We should fix that and preferably integrate with that mechanism as
  712. // well - perhaps make the operator itself create the default
  713. // IngressClass if this is set to true.
  714. proxyActAsDefaultLoadBalancer bool
  715. // proxyFirewallMode determines whether non-userspace proxies should use
  716. // iptables or nftables for firewall configuration. Accepted values are
  717. // iptables, nftables and auto. If set to auto, proxy will automatically
  718. // determine which mode is supported for a given host (prefer nftables).
  719. // Auto is usually the best choice, unless you want to explicitly set
  720. // specific mode for debugging purposes.
  721. proxyFirewallMode string
  722. // defaultProxyClass is the name of the ProxyClass to use as the default
  723. // class for proxies that do not have a ProxyClass set.
  724. // this is defined by an operator env variable.
  725. defaultProxyClass string
  726. // loginServer is the coordination server URL that should be used by managed resources.
  727. loginServer string
  728. // ingressClassName is the name of the ingress class used by reconcilers of Ingress resources. This defaults
  729. // to "tailscale" but can be customised.
  730. ingressClassName string
  731. }
  732. // enqueueAllIngressEgressProxySvcsinNS returns a reconcile request for each
  733. // ingress/egress proxy headless Service found in the provided namespace.
  734. func enqueueAllIngressEgressProxySvcsInNS(ns string, cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
  735. return func(ctx context.Context, _ client.Object) []reconcile.Request {
  736. reqs := make([]reconcile.Request, 0)
  737. // Get all headless Services for proxies configured using Service.
  738. svcProxyLabels := map[string]string{
  739. kubetypes.LabelManaged: "true",
  740. LabelParentType: "svc",
  741. }
  742. svcHeadlessSvcList := &corev1.ServiceList{}
  743. if err := cl.List(ctx, svcHeadlessSvcList, client.InNamespace(ns), client.MatchingLabels(svcProxyLabels)); err != nil {
  744. logger.Errorf("error listing headless Services for tailscale ingress/egress Services in operator namespace: %v", err)
  745. return nil
  746. }
  747. for _, svc := range svcHeadlessSvcList.Items {
  748. reqs = append(reqs, reconcile.Request{NamespacedName: types.NamespacedName{Namespace: svc.Namespace, Name: svc.Name}})
  749. }
  750. // Get all headless Services for proxies configured using Ingress.
  751. ingProxyLabels := map[string]string{
  752. kubetypes.LabelManaged: "true",
  753. LabelParentType: "ingress",
  754. }
  755. ingHeadlessSvcList := &corev1.ServiceList{}
  756. if err := cl.List(ctx, ingHeadlessSvcList, client.InNamespace(ns), client.MatchingLabels(ingProxyLabels)); err != nil {
  757. logger.Errorf("error listing headless Services for tailscale Ingresses in operator namespace: %v", err)
  758. return nil
  759. }
  760. for _, svc := range ingHeadlessSvcList.Items {
  761. reqs = append(reqs, reconcile.Request{NamespacedName: types.NamespacedName{Namespace: svc.Namespace, Name: svc.Name}})
  762. }
  763. return reqs
  764. }
  765. }
  766. // dnsRecordsReconciler filters EndpointSlice events for which
  767. // dns-records-reconciler should reconcile a headless Service. The only events
  768. // it should reconcile are those for EndpointSlices associated with proxy
  769. // headless Services.
  770. func dnsRecordsReconcilerEndpointSliceHandler(ctx context.Context, o client.Object) []reconcile.Request {
  771. if !isManagedByType(o, "svc") && !isManagedByType(o, "ingress") {
  772. return nil
  773. }
  774. headlessSvcName, ok := o.GetLabels()[discoveryv1.LabelServiceName] // https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/#ownership
  775. if !ok {
  776. return nil
  777. }
  778. return []reconcile.Request{{NamespacedName: types.NamespacedName{Namespace: o.GetNamespace(), Name: headlessSvcName}}}
  779. }
  780. // dnsRecordsReconcilerServiceHandler filters Service events for which
  781. // dns-records-reconciler should reconcile. If the event is for a cluster
  782. // ingress/cluster egress proxy's headless Service, returns the Service for
  783. // reconcile.
  784. func dnsRecordsReconcilerServiceHandler(ctx context.Context, o client.Object) []reconcile.Request {
  785. if isManagedByType(o, "svc") || isManagedByType(o, "ingress") {
  786. return []reconcile.Request{{NamespacedName: types.NamespacedName{Namespace: o.GetNamespace(), Name: o.GetName()}}}
  787. }
  788. return nil
  789. }
  790. // dnsRecordsReconcilerIngressHandler filters Ingress events to ensure that
  791. // dns-records-reconciler only reconciles on tailscale Ingress events. When an
  792. // event is observed on a tailscale Ingress, reconcile the proxy headless Service.
  793. func dnsRecordsReconcilerIngressHandler(ns string, isDefaultLoadBalancer bool, cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
  794. return func(ctx context.Context, o client.Object) []reconcile.Request {
  795. ing, ok := o.(*networkingv1.Ingress)
  796. if !ok {
  797. return nil
  798. }
  799. if !isDefaultLoadBalancer && (ing.Spec.IngressClassName == nil || *ing.Spec.IngressClassName != "tailscale") {
  800. return nil
  801. }
  802. proxyResourceLabels := childResourceLabels(ing.Name, ing.Namespace, "ingress")
  803. headlessSvc, err := getSingleObject[corev1.Service](ctx, cl, ns, proxyResourceLabels)
  804. if err != nil {
  805. logger.Errorf("error getting headless Service from parent labels: %v", err)
  806. return nil
  807. }
  808. if headlessSvc == nil {
  809. return nil
  810. }
  811. return []reconcile.Request{{NamespacedName: types.NamespacedName{Namespace: headlessSvc.Namespace, Name: headlessSvc.Name}}}
  812. }
  813. }
  814. func isManagedResource(o client.Object) bool {
  815. ls := o.GetLabels()
  816. return ls[kubetypes.LabelManaged] == "true"
  817. }
  818. func isManagedByType(o client.Object, typ string) bool {
  819. ls := o.GetLabels()
  820. return isManagedResource(o) && ls[LabelParentType] == typ
  821. }
  822. func parentFromObjectLabels(o client.Object) types.NamespacedName {
  823. ls := o.GetLabels()
  824. return types.NamespacedName{
  825. Namespace: ls[LabelParentNamespace],
  826. Name: ls[LabelParentName],
  827. }
  828. }
  829. func managedResourceHandlerForType(typ string) handler.MapFunc {
  830. return func(_ context.Context, o client.Object) []reconcile.Request {
  831. if !isManagedByType(o, typ) {
  832. return nil
  833. }
  834. return []reconcile.Request{
  835. {NamespacedName: parentFromObjectLabels(o)},
  836. }
  837. }
  838. }
  839. // indexProxyClass is used to select ProxyClass-backed objects which are
  840. // locally indexed in the cache for efficient listing without requiring labels.
  841. func indexProxyClass(o client.Object) []string {
  842. if !hasProxyClassAnnotation(o) {
  843. return nil
  844. }
  845. return []string{o.GetAnnotations()[LabelAnnotationProxyClass]}
  846. }
  847. // proxyClassHandlerForSvc returns a handler that, for a given ProxyClass,
  848. // returns a list of reconcile requests for all Services labeled with
  849. // tailscale.com/proxy-class: <proxy class name>.
  850. func proxyClassHandlerForSvc(cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
  851. return func(ctx context.Context, o client.Object) []reconcile.Request {
  852. svcList := new(corev1.ServiceList)
  853. labels := map[string]string{
  854. LabelAnnotationProxyClass: o.GetName(),
  855. }
  856. if err := cl.List(ctx, svcList, client.MatchingLabels(labels)); err != nil {
  857. logger.Debugf("error listing Services for ProxyClass: %v", err)
  858. return nil
  859. }
  860. reqs := make([]reconcile.Request, 0)
  861. seenSvcs := make(set.Set[string])
  862. for _, svc := range svcList.Items {
  863. reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&svc)})
  864. seenSvcs.Add(fmt.Sprintf("%s/%s", svc.Namespace, svc.Name))
  865. }
  866. svcAnnotationList := new(corev1.ServiceList)
  867. if err := cl.List(ctx, svcAnnotationList, client.MatchingFields{indexServiceProxyClass: o.GetName()}); err != nil {
  868. logger.Debugf("error listing Services for ProxyClass: %v", err)
  869. return nil
  870. }
  871. for _, svc := range svcAnnotationList.Items {
  872. nsname := fmt.Sprintf("%s/%s", svc.Namespace, svc.Name)
  873. if seenSvcs.Contains(nsname) {
  874. continue
  875. }
  876. reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&svc)})
  877. seenSvcs.Add(nsname)
  878. }
  879. return reqs
  880. }
  881. }
  882. // proxyClassHandlerForIngress returns a handler that, for a given ProxyClass,
  883. // returns a list of reconcile requests for all Ingresses labeled with
  884. // tailscale.com/proxy-class: <proxy class name>.
  885. func proxyClassHandlerForIngress(cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
  886. return func(ctx context.Context, o client.Object) []reconcile.Request {
  887. ingList := new(networkingv1.IngressList)
  888. labels := map[string]string{
  889. LabelAnnotationProxyClass: o.GetName(),
  890. }
  891. if err := cl.List(ctx, ingList, client.MatchingLabels(labels)); err != nil {
  892. logger.Debugf("error listing Ingresses for ProxyClass: %v", err)
  893. return nil
  894. }
  895. reqs := make([]reconcile.Request, 0)
  896. seenIngs := make(set.Set[string])
  897. for _, ing := range ingList.Items {
  898. reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&ing)})
  899. seenIngs.Add(fmt.Sprintf("%s/%s", ing.Namespace, ing.Name))
  900. }
  901. ingAnnotationList := new(networkingv1.IngressList)
  902. if err := cl.List(ctx, ingAnnotationList, client.MatchingFields{indexIngressProxyClass: o.GetName()}); err != nil {
  903. logger.Debugf("error listing Ingreses for ProxyClass: %v", err)
  904. return nil
  905. }
  906. for _, ing := range ingAnnotationList.Items {
  907. nsname := fmt.Sprintf("%s/%s", ing.Namespace, ing.Name)
  908. if seenIngs.Contains(nsname) {
  909. continue
  910. }
  911. reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&ing)})
  912. seenIngs.Add(nsname)
  913. }
  914. return reqs
  915. }
  916. }
  917. // proxyClassHandlerForConnector returns a handler that, for a given ProxyClass,
  918. // returns a list of reconcile requests for all Connectors that have
  919. // .spec.proxyClass set.
  920. func proxyClassHandlerForConnector(cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
  921. return func(ctx context.Context, o client.Object) []reconcile.Request {
  922. connList := new(tsapi.ConnectorList)
  923. if err := cl.List(ctx, connList); err != nil {
  924. logger.Debugf("error listing Connectors for ProxyClass: %v", err)
  925. return nil
  926. }
  927. reqs := make([]reconcile.Request, 0)
  928. proxyClassName := o.GetName()
  929. for _, conn := range connList.Items {
  930. if conn.Spec.ProxyClass == proxyClassName {
  931. reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&conn)})
  932. }
  933. }
  934. return reqs
  935. }
  936. }
  937. // nodeHandlerForProxyGroup returns a handler that, for a given Node, returns a
  938. // list of reconcile requests for ProxyGroups that should be reconciled for the
  939. // Node event. ProxyGroups need to be reconciled for Node events if they are
  940. // configured to expose tailscaled static endpoints to tailnet using NodePort
  941. // Services.
  942. func nodeHandlerForProxyGroup(cl client.Client, defaultProxyClass string, logger *zap.SugaredLogger) handler.MapFunc {
  943. return func(ctx context.Context, o client.Object) []reconcile.Request {
  944. pgList := new(tsapi.ProxyGroupList)
  945. if err := cl.List(ctx, pgList); err != nil {
  946. logger.Debugf("error listing ProxyGroups for ProxyClass: %v", err)
  947. return nil
  948. }
  949. reqs := make([]reconcile.Request, 0)
  950. for _, pg := range pgList.Items {
  951. if pg.Spec.ProxyClass == "" && defaultProxyClass == "" {
  952. continue
  953. }
  954. pc := defaultProxyClass
  955. if pc == "" {
  956. pc = pg.Spec.ProxyClass
  957. }
  958. proxyClass := &tsapi.ProxyClass{}
  959. if err := cl.Get(ctx, types.NamespacedName{Name: pc}, proxyClass); err != nil {
  960. if !apierrors.IsNotFound(err) {
  961. logger.Debugf("error getting ProxyClass %q: %v", pg.Spec.ProxyClass, err)
  962. }
  963. return nil
  964. }
  965. stat := proxyClass.Spec.StaticEndpoints
  966. if stat == nil {
  967. continue
  968. }
  969. // If the selector is empty, all nodes match.
  970. // TODO(ChaosInTheCRD): think about how this must be handled if we want to limit the number of nodes used
  971. if len(stat.NodePort.Selector) == 0 {
  972. reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&pg)})
  973. continue
  974. }
  975. selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{
  976. MatchLabels: stat.NodePort.Selector,
  977. })
  978. if err != nil {
  979. logger.Debugf("error converting `spec.staticEndpoints.nodePort.selector` to Selector: %v", err)
  980. return nil
  981. }
  982. if selector.Matches(klabels.Set(o.GetLabels())) {
  983. reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&pg)})
  984. }
  985. }
  986. return reqs
  987. }
  988. }
  989. // proxyClassHandlerForProxyGroup returns a handler that, for a given ProxyClass,
  990. // returns a list of reconcile requests for all ProxyGroups that have
  991. // .spec.proxyClass set to that ProxyClass.
  992. func proxyClassHandlerForProxyGroup(cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
  993. return func(ctx context.Context, o client.Object) []reconcile.Request {
  994. pgList := new(tsapi.ProxyGroupList)
  995. if err := cl.List(ctx, pgList); err != nil {
  996. logger.Debugf("error listing ProxyGroups for ProxyClass: %v", err)
  997. return nil
  998. }
  999. reqs := make([]reconcile.Request, 0)
  1000. proxyClassName := o.GetName()
  1001. for _, pg := range pgList.Items {
  1002. if pg.Spec.ProxyClass == proxyClassName {
  1003. reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&pg)})
  1004. }
  1005. }
  1006. return reqs
  1007. }
  1008. }
  1009. // serviceAccountHandlerForProxyGroup returns a handler that, for a given ServiceAccount,
  1010. // returns a list of reconcile requests for all ProxyGroups that use that ServiceAccount.
  1011. // For most ProxyGroups, this will be a dedicated ServiceAccount owned by a specific
  1012. // ProxyGroup. But for kube-apiserver ProxyGroups running in auth mode, they use a shared
  1013. // static ServiceAccount named "kube-apiserver-auth-proxy".
  1014. func serviceAccountHandlerForProxyGroup(cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
  1015. return func(ctx context.Context, o client.Object) []reconcile.Request {
  1016. pgList := new(tsapi.ProxyGroupList)
  1017. if err := cl.List(ctx, pgList); err != nil {
  1018. logger.Debugf("error listing ProxyGroups for ServiceAccount: %v", err)
  1019. return nil
  1020. }
  1021. reqs := make([]reconcile.Request, 0)
  1022. saName := o.GetName()
  1023. for _, pg := range pgList.Items {
  1024. if saName == authAPIServerProxySAName && isAuthAPIServerProxy(&pg) {
  1025. reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&pg)})
  1026. }
  1027. expectedOwner := pgOwnerReference(&pg)[0]
  1028. saOwnerRefs := o.GetOwnerReferences()
  1029. for _, ref := range saOwnerRefs {
  1030. if apiequality.Semantic.DeepEqual(ref, expectedOwner) {
  1031. reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&pg)})
  1032. break
  1033. }
  1034. }
  1035. }
  1036. return reqs
  1037. }
  1038. }
  1039. // serviceHandlerForIngress returns a handler for Service events for ingress
  1040. // reconciler that ensures that if the Service associated with an event is of
  1041. // interest to the reconciler, the associated Ingress(es) gets be reconciled.
  1042. // The Services of interest are backend Services for tailscale Ingress and
  1043. // managed Services for an StatefulSet for a proxy configured for tailscale
  1044. // Ingress
  1045. func serviceHandlerForIngress(cl client.Client, logger *zap.SugaredLogger, ingressClassName string) handler.MapFunc {
  1046. return func(ctx context.Context, o client.Object) []reconcile.Request {
  1047. if isManagedByType(o, "ingress") {
  1048. ingName := parentFromObjectLabels(o)
  1049. return []reconcile.Request{{NamespacedName: ingName}}
  1050. }
  1051. ingList := networkingv1.IngressList{}
  1052. if err := cl.List(ctx, &ingList, client.InNamespace(o.GetNamespace())); err != nil {
  1053. logger.Debugf("error listing Ingresses: %v", err)
  1054. return nil
  1055. }
  1056. reqs := make([]reconcile.Request, 0)
  1057. for _, ing := range ingList.Items {
  1058. if ing.Spec.IngressClassName == nil || *ing.Spec.IngressClassName != ingressClassName {
  1059. continue
  1060. }
  1061. if hasProxyGroupAnnotation(&ing) {
  1062. // We don't want to reconcile backend Services for Ingresses for ProxyGroups.
  1063. continue
  1064. }
  1065. if ing.Spec.DefaultBackend != nil && ing.Spec.DefaultBackend.Service != nil && ing.Spec.DefaultBackend.Service.Name == o.GetName() {
  1066. reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&ing)})
  1067. }
  1068. for _, rule := range ing.Spec.Rules {
  1069. if rule.HTTP == nil {
  1070. continue
  1071. }
  1072. for _, path := range rule.HTTP.Paths {
  1073. if path.Backend.Service != nil && path.Backend.Service.Name == o.GetName() {
  1074. reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&ing)})
  1075. }
  1076. }
  1077. }
  1078. }
  1079. return reqs
  1080. }
  1081. }
  1082. func serviceHandler(_ context.Context, o client.Object) []reconcile.Request {
  1083. if _, ok := o.GetAnnotations()[AnnotationProxyGroup]; ok {
  1084. // Do not reconcile Services for ProxyGroup.
  1085. return nil
  1086. }
  1087. if isManagedByType(o, "svc") {
  1088. // If this is a Service managed by a Service we want to enqueue its parent
  1089. return []reconcile.Request{{NamespacedName: parentFromObjectLabels(o)}}
  1090. }
  1091. if isManagedResource(o) {
  1092. // If this is a Servce managed by a resource that is not a Service, we leave it alone
  1093. return nil
  1094. }
  1095. // If this is not a managed Service we want to enqueue it
  1096. return []reconcile.Request{
  1097. {
  1098. NamespacedName: types.NamespacedName{
  1099. Namespace: o.GetNamespace(),
  1100. Name: o.GetName(),
  1101. },
  1102. },
  1103. }
  1104. }
  1105. // isMagicDNSName reports whether name is a full tailnet node FQDN (with or
  1106. // without final dot).
  1107. func isMagicDNSName(name string) bool {
  1108. validMagicDNSName := regexp.MustCompile(`^[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+\.ts\.net\.?$`)
  1109. return validMagicDNSName.MatchString(name)
  1110. }
  1111. // egressSvcsHandler returns accepts a Kubernetes object and returns a reconcile
  1112. // request for it , if the object is a Tailscale egress Service meant to be
  1113. // exposed on a ProxyGroup.
  1114. func egressSvcsHandler(_ context.Context, o client.Object) []reconcile.Request {
  1115. if !isEgressSvcForProxyGroup(o) {
  1116. return nil
  1117. }
  1118. return []reconcile.Request{
  1119. {
  1120. NamespacedName: types.NamespacedName{
  1121. Namespace: o.GetNamespace(),
  1122. Name: o.GetName(),
  1123. },
  1124. },
  1125. }
  1126. }
  1127. // egressEpsHandler returns accepts an EndpointSlice and, if the EndpointSlice
  1128. // is for an egress service, returns a reconcile request for it.
  1129. func egressEpsHandler(_ context.Context, o client.Object) []reconcile.Request {
  1130. if typ := o.GetLabels()[labelSvcType]; typ != typeEgress {
  1131. return nil
  1132. }
  1133. return []reconcile.Request{
  1134. {
  1135. NamespacedName: types.NamespacedName{
  1136. Namespace: o.GetNamespace(),
  1137. Name: o.GetName(),
  1138. },
  1139. },
  1140. }
  1141. }
  1142. func egressPodsHandler(_ context.Context, o client.Object) []reconcile.Request {
  1143. if typ := o.GetLabels()[LabelParentType]; typ != proxyTypeProxyGroup {
  1144. return nil
  1145. }
  1146. return []reconcile.Request{
  1147. {
  1148. NamespacedName: types.NamespacedName{
  1149. Namespace: o.GetNamespace(),
  1150. Name: o.GetName(),
  1151. },
  1152. },
  1153. }
  1154. }
  1155. // egressEpsFromEgressPods returns a Pod event handler that checks if Pod is a replica for a ProxyGroup and if it is,
  1156. // returns reconciler requests for all egress EndpointSlices for that ProxyGroup.
  1157. func egressEpsFromPGPods(cl client.Client, ns string) handler.MapFunc {
  1158. return func(_ context.Context, o client.Object) []reconcile.Request {
  1159. if v, ok := o.GetLabels()[kubetypes.LabelManaged]; !ok || v != "true" {
  1160. return nil
  1161. }
  1162. // TODO(irbekrm): for now this is good enough as all ProxyGroups are egress. Add a type check once we
  1163. // have ingress ProxyGroups.
  1164. if typ := o.GetLabels()[LabelParentType]; typ != "proxygroup" {
  1165. return nil
  1166. }
  1167. pg, ok := o.GetLabels()[LabelParentName]
  1168. if !ok {
  1169. return nil
  1170. }
  1171. return reconcileRequestsForPG(pg, cl, ns)
  1172. }
  1173. }
  1174. // egressEpsFromPGStateSecrets returns a Secret event handler that checks if Secret is a state Secret for a ProxyGroup and if it is,
  1175. // returns reconciler requests for all egress EndpointSlices for that ProxyGroup.
  1176. func egressEpsFromPGStateSecrets(cl client.Client, ns string) handler.MapFunc {
  1177. return func(_ context.Context, o client.Object) []reconcile.Request {
  1178. if v, ok := o.GetLabels()[kubetypes.LabelManaged]; !ok || v != "true" {
  1179. return nil
  1180. }
  1181. if parentType := o.GetLabels()[LabelParentType]; parentType != "proxygroup" {
  1182. return nil
  1183. }
  1184. if secretType := o.GetLabels()[kubetypes.LabelSecretType]; secretType != kubetypes.LabelSecretTypeState {
  1185. return nil
  1186. }
  1187. pg, ok := o.GetLabels()[LabelParentName]
  1188. if !ok {
  1189. return nil
  1190. }
  1191. return reconcileRequestsForPG(pg, cl, ns)
  1192. }
  1193. }
  1194. func ingressSvcFromEps(cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
  1195. return func(ctx context.Context, o client.Object) []reconcile.Request {
  1196. svcName := o.GetLabels()[discoveryv1.LabelServiceName]
  1197. if svcName == "" {
  1198. return nil
  1199. }
  1200. svc := &corev1.Service{}
  1201. ns := o.GetNamespace()
  1202. if err := cl.Get(ctx, types.NamespacedName{Name: svcName, Namespace: ns}, svc); err != nil {
  1203. if !apierrors.IsNotFound(err) {
  1204. logger.Debugf("failed to get service: %v", err)
  1205. }
  1206. return nil
  1207. }
  1208. pgName := svc.Annotations[AnnotationProxyGroup]
  1209. if pgName == "" {
  1210. return nil
  1211. }
  1212. return []reconcile.Request{
  1213. {
  1214. NamespacedName: types.NamespacedName{
  1215. Namespace: ns,
  1216. Name: svcName,
  1217. },
  1218. },
  1219. }
  1220. }
  1221. }
  1222. // egressSvcFromEps is an event handler for EndpointSlices. If an EndpointSlice is for an egress ExternalName Service
  1223. // meant to be exposed on a ProxyGroup, returns a reconcile request for the Service.
  1224. func egressSvcFromEps(_ context.Context, o client.Object) []reconcile.Request {
  1225. if typ := o.GetLabels()[labelSvcType]; typ != typeEgress {
  1226. return nil
  1227. }
  1228. if v, ok := o.GetLabels()[kubetypes.LabelManaged]; !ok || v != "true" {
  1229. return nil
  1230. }
  1231. svcName, ok := o.GetLabels()[LabelParentName]
  1232. if !ok {
  1233. return nil
  1234. }
  1235. svcNs, ok := o.GetLabels()[LabelParentNamespace]
  1236. if !ok {
  1237. return nil
  1238. }
  1239. return []reconcile.Request{
  1240. {
  1241. NamespacedName: types.NamespacedName{
  1242. Namespace: svcNs,
  1243. Name: svcName,
  1244. },
  1245. },
  1246. }
  1247. }
  1248. func reconcileRequestsForPG(pg string, cl client.Client, ns string) []reconcile.Request {
  1249. epsList := discoveryv1.EndpointSliceList{}
  1250. if err := cl.List(context.Background(), &epsList,
  1251. client.InNamespace(ns),
  1252. client.MatchingLabels(map[string]string{labelProxyGroup: pg})); err != nil {
  1253. return nil
  1254. }
  1255. reqs := make([]reconcile.Request, 0)
  1256. for _, ep := range epsList.Items {
  1257. reqs = append(reqs, reconcile.Request{
  1258. NamespacedName: types.NamespacedName{
  1259. Namespace: ep.Namespace,
  1260. Name: ep.Name,
  1261. },
  1262. })
  1263. }
  1264. return reqs
  1265. }
  1266. func isTLSSecret(secret *corev1.Secret) bool {
  1267. return secret.Type == corev1.SecretTypeTLS &&
  1268. secret.ObjectMeta.Labels[kubetypes.LabelManaged] == "true" &&
  1269. secret.ObjectMeta.Labels[kubetypes.LabelSecretType] == kubetypes.LabelSecretTypeCerts &&
  1270. secret.ObjectMeta.Labels[labelDomain] != "" &&
  1271. secret.ObjectMeta.Labels[labelProxyGroup] != ""
  1272. }
  1273. func isPGStateSecret(secret *corev1.Secret) bool {
  1274. return secret.ObjectMeta.Labels[kubetypes.LabelManaged] == "true" &&
  1275. secret.ObjectMeta.Labels[LabelParentType] == "proxygroup" &&
  1276. secret.ObjectMeta.Labels[kubetypes.LabelSecretType] == kubetypes.LabelSecretTypeState
  1277. }
  1278. // HAIngressesFromSecret returns a handler that returns reconcile requests for
  1279. // all HA Ingresses that should be reconciled in response to a Secret event.
  1280. func HAIngressesFromSecret(cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
  1281. return func(ctx context.Context, o client.Object) []reconcile.Request {
  1282. secret, ok := o.(*corev1.Secret)
  1283. if !ok {
  1284. logger.Infof("[unexpected] Secret handler triggered for an object that is not a Secret")
  1285. return nil
  1286. }
  1287. if isTLSSecret(secret) {
  1288. return []reconcile.Request{
  1289. {
  1290. NamespacedName: types.NamespacedName{
  1291. Namespace: secret.ObjectMeta.Labels[LabelParentNamespace],
  1292. Name: secret.ObjectMeta.Labels[LabelParentName],
  1293. },
  1294. },
  1295. }
  1296. }
  1297. if !isPGStateSecret(secret) {
  1298. return nil
  1299. }
  1300. pgName, ok := secret.ObjectMeta.Labels[LabelParentName]
  1301. if !ok {
  1302. return nil
  1303. }
  1304. ingList := &networkingv1.IngressList{}
  1305. if err := cl.List(ctx, ingList, client.MatchingFields{indexIngressProxyGroup: pgName}); err != nil {
  1306. logger.Infof("error listing Ingresses, skipping a reconcile for event on Secret %s: %v", secret.Name, err)
  1307. return nil
  1308. }
  1309. reqs := make([]reconcile.Request, 0)
  1310. for _, ing := range ingList.Items {
  1311. reqs = append(reqs, reconcile.Request{
  1312. NamespacedName: types.NamespacedName{
  1313. Namespace: ing.Namespace,
  1314. Name: ing.Name,
  1315. },
  1316. })
  1317. }
  1318. return reqs
  1319. }
  1320. }
  1321. // HAServiceFromSecret returns a handler that returns reconcile requests for
  1322. // all HA Services that should be reconciled in response to a Secret event.
  1323. func HAServicesFromSecret(cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
  1324. return func(ctx context.Context, o client.Object) []reconcile.Request {
  1325. secret, ok := o.(*corev1.Secret)
  1326. if !ok {
  1327. logger.Infof("[unexpected] Secret handler triggered for an object that is not a Secret")
  1328. return nil
  1329. }
  1330. if !isPGStateSecret(secret) {
  1331. return nil
  1332. }
  1333. pgName, ok := secret.ObjectMeta.Labels[LabelParentName]
  1334. if !ok {
  1335. return nil
  1336. }
  1337. svcList := &corev1.ServiceList{}
  1338. if err := cl.List(ctx, svcList, client.MatchingFields{indexIngressProxyGroup: pgName}); err != nil {
  1339. logger.Infof("error listing Services, skipping a reconcile for event on Secret %s: %v", secret.Name, err)
  1340. return nil
  1341. }
  1342. reqs := make([]reconcile.Request, 0)
  1343. for _, svc := range svcList.Items {
  1344. reqs = append(reqs, reconcile.Request{
  1345. NamespacedName: types.NamespacedName{
  1346. Namespace: svc.Namespace,
  1347. Name: svc.Name,
  1348. },
  1349. })
  1350. }
  1351. return reqs
  1352. }
  1353. }
  1354. // kubeAPIServerPGsFromSecret finds ProxyGroups of type "kube-apiserver" that
  1355. // need to be reconciled after a ProxyGroup-owned Secret is updated.
  1356. func kubeAPIServerPGsFromSecret(cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
  1357. return func(ctx context.Context, o client.Object) []reconcile.Request {
  1358. secret, ok := o.(*corev1.Secret)
  1359. if !ok {
  1360. logger.Infof("[unexpected] Secret handler triggered for an object that is not a Secret")
  1361. return nil
  1362. }
  1363. if secret.ObjectMeta.Labels[kubetypes.LabelManaged] != "true" ||
  1364. secret.ObjectMeta.Labels[LabelParentType] != "proxygroup" {
  1365. return nil
  1366. }
  1367. var pg tsapi.ProxyGroup
  1368. if err := cl.Get(ctx, types.NamespacedName{Name: secret.ObjectMeta.Labels[LabelParentName]}, &pg); err != nil {
  1369. if !apierrors.IsNotFound(err) {
  1370. logger.Debugf("error getting ProxyGroup %s: %v", secret.ObjectMeta.Labels[LabelParentName], err)
  1371. }
  1372. return nil
  1373. }
  1374. if pg.Spec.Type != tsapi.ProxyGroupTypeKubernetesAPIServer {
  1375. return nil
  1376. }
  1377. return []reconcile.Request{
  1378. {
  1379. NamespacedName: types.NamespacedName{
  1380. Namespace: secret.ObjectMeta.Labels[LabelParentNamespace],
  1381. Name: secret.ObjectMeta.Labels[LabelParentName],
  1382. },
  1383. },
  1384. }
  1385. }
  1386. }
  1387. // egressSvcsFromEgressProxyGroup is an event handler for egress ProxyGroups. It returns reconcile requests for all
  1388. // user-created ExternalName Services that should be exposed on this ProxyGroup.
  1389. func egressSvcsFromEgressProxyGroup(cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
  1390. return func(ctx context.Context, o client.Object) []reconcile.Request {
  1391. pg, ok := o.(*tsapi.ProxyGroup)
  1392. if !ok {
  1393. logger.Infof("[unexpected] ProxyGroup handler triggered for an object that is not a ProxyGroup")
  1394. return nil
  1395. }
  1396. if pg.Spec.Type != tsapi.ProxyGroupTypeEgress {
  1397. return nil
  1398. }
  1399. svcList := &corev1.ServiceList{}
  1400. if err := cl.List(ctx, svcList, client.MatchingFields{indexEgressProxyGroup: pg.Name}); err != nil {
  1401. logger.Infof("error listing Services: %v, skipping a reconcile for event on ProxyGroup %s", err, pg.Name)
  1402. return nil
  1403. }
  1404. reqs := make([]reconcile.Request, 0)
  1405. for _, svc := range svcList.Items {
  1406. reqs = append(reqs, reconcile.Request{
  1407. NamespacedName: types.NamespacedName{
  1408. Namespace: svc.Namespace,
  1409. Name: svc.Name,
  1410. },
  1411. })
  1412. }
  1413. return reqs
  1414. }
  1415. }
  1416. // ingressesFromIngressProxyGroup is an event handler for ingress ProxyGroups. It returns reconcile requests for all
  1417. // user-created Ingresses that should be exposed on this ProxyGroup.
  1418. func ingressesFromIngressProxyGroup(cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
  1419. return func(ctx context.Context, o client.Object) []reconcile.Request {
  1420. pg, ok := o.(*tsapi.ProxyGroup)
  1421. if !ok {
  1422. logger.Infof("[unexpected] ProxyGroup handler triggered for an object that is not a ProxyGroup")
  1423. return nil
  1424. }
  1425. if pg.Spec.Type != tsapi.ProxyGroupTypeIngress {
  1426. return nil
  1427. }
  1428. ingList := &networkingv1.IngressList{}
  1429. if err := cl.List(ctx, ingList, client.MatchingFields{indexIngressProxyGroup: pg.Name}); err != nil {
  1430. logger.Infof("error listing Ingresses: %v, skipping a reconcile for event on ProxyGroup %s", err, pg.Name)
  1431. return nil
  1432. }
  1433. reqs := make([]reconcile.Request, 0)
  1434. for _, svc := range ingList.Items {
  1435. reqs = append(reqs, reconcile.Request{
  1436. NamespacedName: types.NamespacedName{
  1437. Namespace: svc.Namespace,
  1438. Name: svc.Name,
  1439. },
  1440. })
  1441. }
  1442. return reqs
  1443. }
  1444. }
  1445. // epsFromExternalNameService is an event handler for ExternalName Services that define a Tailscale egress service that
  1446. // should be exposed on a ProxyGroup. It returns reconcile requests for EndpointSlices created for this Service.
  1447. func epsFromExternalNameService(cl client.Client, logger *zap.SugaredLogger, ns string) handler.MapFunc {
  1448. return func(ctx context.Context, o client.Object) []reconcile.Request {
  1449. svc, ok := o.(*corev1.Service)
  1450. if !ok {
  1451. logger.Infof("[unexpected] Service handler triggered for an object that is not a Service")
  1452. return nil
  1453. }
  1454. if !isEgressSvcForProxyGroup(svc) {
  1455. return nil
  1456. }
  1457. epsList := &discoveryv1.EndpointSliceList{}
  1458. if err := cl.List(ctx, epsList, client.InNamespace(ns),
  1459. client.MatchingLabels(egressSvcChildResourceLabels(svc))); err != nil {
  1460. logger.Infof("error listing EndpointSlices: %v, skipping a reconcile for event on Service %s", err, svc.Name)
  1461. return nil
  1462. }
  1463. reqs := make([]reconcile.Request, 0)
  1464. for _, eps := range epsList.Items {
  1465. reqs = append(reqs, reconcile.Request{
  1466. NamespacedName: types.NamespacedName{
  1467. Namespace: eps.Namespace,
  1468. Name: eps.Name,
  1469. },
  1470. })
  1471. }
  1472. return reqs
  1473. }
  1474. }
  1475. func podsFromEgressEps(cl client.Client, logger *zap.SugaredLogger, ns string) handler.MapFunc {
  1476. return func(ctx context.Context, o client.Object) []reconcile.Request {
  1477. eps, ok := o.(*discoveryv1.EndpointSlice)
  1478. if !ok {
  1479. logger.Infof("[unexpected] EndpointSlice handler triggered for an object that is not a EndpointSlice")
  1480. return nil
  1481. }
  1482. if eps.Labels[labelProxyGroup] == "" {
  1483. return nil
  1484. }
  1485. if eps.Labels[labelSvcType] != "egress" {
  1486. return nil
  1487. }
  1488. podLabels := map[string]string{
  1489. kubetypes.LabelManaged: "true",
  1490. LabelParentType: "proxygroup",
  1491. LabelParentName: eps.Labels[labelProxyGroup],
  1492. }
  1493. podList := &corev1.PodList{}
  1494. if err := cl.List(ctx, podList, client.InNamespace(ns),
  1495. client.MatchingLabels(podLabels)); err != nil {
  1496. logger.Infof("error listing EndpointSlices: %v, skipping a reconcile for event on EndpointSlice %s", err, eps.Name)
  1497. return nil
  1498. }
  1499. reqs := make([]reconcile.Request, 0)
  1500. for _, pod := range podList.Items {
  1501. reqs = append(reqs, reconcile.Request{
  1502. NamespacedName: types.NamespacedName{
  1503. Namespace: pod.Namespace,
  1504. Name: pod.Name,
  1505. },
  1506. })
  1507. }
  1508. return reqs
  1509. }
  1510. }
  1511. // proxyClassesWithServiceMonitor returns an event handler that, given that the event is for the Prometheus
  1512. // ServiceMonitor CRD, returns all ProxyClasses that define that a ServiceMonitor should be created.
  1513. func proxyClassesWithServiceMonitor(cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
  1514. return func(ctx context.Context, o client.Object) []reconcile.Request {
  1515. crd, ok := o.(*apiextensionsv1.CustomResourceDefinition)
  1516. if !ok {
  1517. logger.Debugf("[unexpected] ServiceMonitor CRD handler received an object that is not a CustomResourceDefinition")
  1518. return nil
  1519. }
  1520. if crd.Name != serviceMonitorCRD {
  1521. logger.Debugf("[unexpected] ServiceMonitor CRD handler received an unexpected CRD %q", crd.Name)
  1522. return nil
  1523. }
  1524. pcl := &tsapi.ProxyClassList{}
  1525. if err := cl.List(ctx, pcl); err != nil {
  1526. logger.Debugf("[unexpected] error listing ProxyClasses: %v", err)
  1527. return nil
  1528. }
  1529. reqs := make([]reconcile.Request, 0)
  1530. for _, pc := range pcl.Items {
  1531. if pc.Spec.Metrics != nil && pc.Spec.Metrics.ServiceMonitor != nil && pc.Spec.Metrics.ServiceMonitor.Enable {
  1532. reqs = append(reqs, reconcile.Request{
  1533. NamespacedName: types.NamespacedName{Namespace: pc.Namespace, Name: pc.Name},
  1534. })
  1535. }
  1536. }
  1537. return reqs
  1538. }
  1539. }
  1540. // crdTransformer gets called before a CRD is stored to c/r cache, it removes the CRD spec to reduce memory consumption.
  1541. func crdTransformer(log *zap.SugaredLogger) toolscache.TransformFunc {
  1542. return func(o any) (any, error) {
  1543. crd, ok := o.(*apiextensionsv1.CustomResourceDefinition)
  1544. if !ok {
  1545. log.Infof("[unexpected] CRD transformer called for a non-CRD type")
  1546. return crd, nil
  1547. }
  1548. crd.Spec = apiextensionsv1.CustomResourceDefinitionSpec{}
  1549. return crd, nil
  1550. }
  1551. }
  1552. // indexEgressServices adds a local index to cached Tailscale egress Services meant to be exposed on a ProxyGroup. The
  1553. // index is used a list filter.
  1554. func indexEgressServices(o client.Object) []string {
  1555. if !isEgressSvcForProxyGroup(o) {
  1556. return nil
  1557. }
  1558. return []string{o.GetAnnotations()[AnnotationProxyGroup]}
  1559. }
  1560. // indexPGIngresses is used to select ProxyGroup-backed Services which are
  1561. // locally indexed in the cache for efficient listing without requiring labels.
  1562. func indexPGIngresses(o client.Object) []string {
  1563. if !hasProxyGroupAnnotation(o) {
  1564. return nil
  1565. }
  1566. return []string{o.GetAnnotations()[AnnotationProxyGroup]}
  1567. }
  1568. // serviceHandlerForIngressPG returns a handler for Service events that ensures that if the Service
  1569. // associated with an event is a backend Service for a tailscale Ingress with ProxyGroup annotation,
  1570. // the associated Ingress gets reconciled.
  1571. func serviceHandlerForIngressPG(cl client.Client, logger *zap.SugaredLogger, ingressClassName string) handler.MapFunc {
  1572. return func(ctx context.Context, o client.Object) []reconcile.Request {
  1573. ingList := networkingv1.IngressList{}
  1574. if err := cl.List(ctx, &ingList, client.InNamespace(o.GetNamespace())); err != nil {
  1575. logger.Debugf("error listing Ingresses: %v", err)
  1576. return nil
  1577. }
  1578. reqs := make([]reconcile.Request, 0)
  1579. for _, ing := range ingList.Items {
  1580. if ing.Spec.IngressClassName == nil || *ing.Spec.IngressClassName != ingressClassName {
  1581. continue
  1582. }
  1583. if !hasProxyGroupAnnotation(&ing) {
  1584. continue
  1585. }
  1586. if ing.Spec.DefaultBackend != nil && ing.Spec.DefaultBackend.Service != nil && ing.Spec.DefaultBackend.Service.Name == o.GetName() {
  1587. reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&ing)})
  1588. }
  1589. for _, rule := range ing.Spec.Rules {
  1590. if rule.HTTP == nil {
  1591. continue
  1592. }
  1593. for _, path := range rule.HTTP.Paths {
  1594. if path.Backend.Service != nil && path.Backend.Service.Name == o.GetName() {
  1595. reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&ing)})
  1596. }
  1597. }
  1598. }
  1599. }
  1600. return reqs
  1601. }
  1602. }
  1603. func hasProxyGroupAnnotation(obj client.Object) bool {
  1604. return obj.GetAnnotations()[AnnotationProxyGroup] != ""
  1605. }
  1606. func hasProxyClassAnnotation(obj client.Object) bool {
  1607. return obj.GetAnnotations()[LabelAnnotationProxyClass] != ""
  1608. }
  1609. func id(ctx context.Context, lc *local.Client) (string, error) {
  1610. st, err := lc.StatusWithoutPeers(ctx)
  1611. if err != nil {
  1612. return "", fmt.Errorf("error getting tailscale status: %w", err)
  1613. }
  1614. if st.Self == nil {
  1615. return "", fmt.Errorf("unexpected: device's status does not contain self status")
  1616. }
  1617. return string(st.Self.ID), nil
  1618. }