From 23fae133f89e4c7ba7c53bf0a49f660c186d2733 Mon Sep 17 00:00:00 2001 From: Raj Singh Date: Tue, 1 Apr 2025 23:39:04 -0500 Subject: [PATCH] init --- .../deploy/chart/templates/operator.yaml | 4 +++ cmd/k8s-operator/deploy/chart/values.yaml | 9 +++++ cmd/k8s-operator/operator.go | 35 +++++++++++-------- cmd/k8s-operator/proxygroup.go | 13 +++---- cmd/k8s-operator/sts.go | 6 ++-- cmd/k8s-operator/tsrecorder.go | 3 +- 6 files changed, 47 insertions(+), 23 deletions(-) create mode 100644 cmd/k8s-operator/deploy/chart/templates/operator.yaml diff --git a/cmd/k8s-operator/deploy/chart/templates/operator.yaml b/cmd/k8s-operator/deploy/chart/templates/operator.yaml new file mode 100644 index 000000000..c147e92d3 --- /dev/null +++ b/cmd/k8s-operator/deploy/chart/templates/operator.yaml @@ -0,0 +1,4 @@ + - name: OPERATOR_DEFAULT_LOAD_BALANCER + value: {{ .Values.defaultLoadBalancer | quote }} + - name: PROXY_EPHEMERAL_KEYS + value: {{ .Values.ephemeralKeys | quote }} \ No newline at end of file diff --git a/cmd/k8s-operator/deploy/chart/values.yaml b/cmd/k8s-operator/deploy/chart/values.yaml index 2d1effc25..e950410ae 100644 --- a/cmd/k8s-operator/deploy/chart/values.yaml +++ b/cmd/k8s-operator/deploy/chart/values.yaml @@ -111,3 +111,12 @@ apiServerProxyConfig: mode: "false" # "true", "false", "noauth" imagePullSecrets: [] + +# If true, the operator will consider LoadBalancer Services with no +# loadBalancerClass field set as Tailscale Services. +defaultLoadBalancer: false + +# If true, use ephemeral device authentication keys for all proxies. +# Ephemeral devices are automatically deleted from your tailnet when they +# disconnect, helping keep your tailnet clean. +ephemeralKeys: false diff --git a/cmd/k8s-operator/operator.go b/cmd/k8s-operator/operator.go index 1f637927b..9419a4aff 100644 --- a/cmd/k8s-operator/operator.go +++ b/cmd/k8s-operator/operator.go @@ -76,6 +76,7 @@ func main() { tsFirewallMode = defaultEnv("PROXY_FIREWALL_MODE", "") defaultProxyClass = defaultEnv("PROXY_DEFAULT_CLASS", "") isDefaultLoadBalancer = defaultBool("OPERATOR_DEFAULT_LOAD_BALANCER", false) + useEphemeralKeys = defaultBool("PROXY_EPHEMERAL_KEYS", false) ) var opts []kzap.Opts @@ -109,7 +110,7 @@ func main() { hostinfo.SetApp(kubetypes.AppAPIServerProxy) } - s, tsc := initTSNet(zlog) + s, tsc := initTSNet(zlog, useEphemeralKeys) defer s.Close() restConfig := config.GetConfigOrDie() maybeLaunchAPIServerProxy(zlog, restConfig, s, mode) @@ -125,6 +126,7 @@ func main() { proxyTags: tags, proxyFirewallMode: tsFirewallMode, defaultProxyClass: defaultProxyClass, + proxyUseEphemeralKeys: useEphemeralKeys, } runReconcilers(rOpts) } @@ -132,7 +134,7 @@ func main() { // initTSNet initializes the tsnet.Server and logs in to Tailscale. It uses the // CLIENT_ID_FILE and CLIENT_SECRET_FILE environment variables to authenticate // with Tailscale. -func initTSNet(zlog *zap.SugaredLogger) (*tsnet.Server, tsClient) { +func initTSNet(zlog *zap.SugaredLogger, useEphemeralKeys bool) (*tsnet.Server, tsClient) { var ( clientIDPath = defaultEnv("CLIENT_ID_FILE", "") clientSecretPath = defaultEnv("CLIENT_SECRET_FILE", "") @@ -196,6 +198,7 @@ waitOnline: Create: tailscale.KeyDeviceCreateCapabilities{ Reusable: false, Preauthorized: true, + Ephemeral: useEphemeralKeys, Tags: strings.Split(operatorTags, ","), }, }, @@ -288,6 +291,7 @@ func runReconcilers(opts reconcilerOpts) { proxyImage: opts.proxyImage, proxyPriorityClassName: opts.proxyPriorityClassName, tsFirewallMode: opts.proxyFirewallMode, + proxyUseEphemeralKeys: opts.proxyUseEphemeralKeys, } err = builder. ControllerManagedBy(mgr). @@ -542,12 +546,13 @@ func runReconcilers(opts reconcilerOpts) { Watches(&rbacv1.Role{}, recorderFilter). Watches(&rbacv1.RoleBinding{}, recorderFilter). Complete(&RecorderReconciler{ - recorder: eventRecorder, - tsNamespace: opts.tailscaleNamespace, - Client: mgr.GetClient(), - l: opts.log.Named("recorder-reconciler"), - clock: tstime.DefaultClock{}, - tsClient: opts.tsClient, + recorder: eventRecorder, + tsNamespace: opts.tailscaleNamespace, + Client: mgr.GetClient(), + l: opts.log.Named("recorder-reconciler"), + clock: tstime.DefaultClock{}, + tsClient: opts.tsClient, + proxyUseEphemeralKeys: opts.proxyUseEphemeralKeys, }) if err != nil { startlog.Fatalf("could not create Recorder reconciler: %v", err) @@ -573,11 +578,12 @@ func runReconcilers(opts reconcilerOpts) { clock: tstime.DefaultClock{}, tsClient: opts.tsClient, - tsNamespace: opts.tailscaleNamespace, - proxyImage: opts.proxyImage, - defaultTags: strings.Split(opts.proxyTags, ","), - tsFirewallMode: opts.proxyFirewallMode, - defaultProxyClass: opts.defaultProxyClass, + tsNamespace: opts.tailscaleNamespace, + proxyImage: opts.proxyImage, + defaultTags: strings.Split(opts.proxyTags, ","), + tsFirewallMode: opts.proxyFirewallMode, + defaultProxyClass: opts.defaultProxyClass, + proxyUseEphemeralKeys: opts.proxyUseEphemeralKeys, }) if err != nil { startlog.Fatalf("could not create ProxyGroup reconciler: %v", err) @@ -627,9 +633,10 @@ type reconcilerOpts struct { // class for proxies that do not have a ProxyClass set. // this is defined by an operator env variable. defaultProxyClass string + proxyUseEphemeralKeys bool } -// enqueueAllIngressEgressProxySvcsinNS returns a reconcile request for each +// enqueueAllIngressEgressProxySvcsInNS returns a reconcile request for each // ingress/egress proxy headless Service found in the provided namespace. func enqueueAllIngressEgressProxySvcsInNS(ns string, cl client.Client, logger *zap.SugaredLogger) handler.MapFunc { return func(ctx context.Context, _ client.Object) []reconcile.Request { diff --git a/cmd/k8s-operator/proxygroup.go b/cmd/k8s-operator/proxygroup.go index f263829d7..8c92a7589 100644 --- a/cmd/k8s-operator/proxygroup.go +++ b/cmd/k8s-operator/proxygroup.go @@ -66,11 +66,12 @@ type ProxyGroupReconciler struct { tsClient tsClient // User-specified defaults from the helm installation. - tsNamespace string - proxyImage string - defaultTags []string - tsFirewallMode string - defaultProxyClass string + tsNamespace string + proxyImage string + defaultTags []string + tsFirewallMode string + defaultProxyClass string + proxyUseEphemeralKeys bool mu sync.Mutex // protects following egressProxyGroups set.Slice[types.UID] // for egress proxygroups gauge @@ -477,7 +478,7 @@ func (r *ProxyGroupReconciler) ensureConfigSecretsCreated(ctx context.Context, p if len(tags) == 0 { tags = r.defaultTags } - authKey, err = newAuthKey(ctx, r.tsClient, tags) + authKey, err = newAuthKey(ctx, r.tsClient, tags, r.proxyUseEphemeralKeys) if err != nil { return "", err } diff --git a/cmd/k8s-operator/sts.go b/cmd/k8s-operator/sts.go index 7434ea79d..ed4f07564 100644 --- a/cmd/k8s-operator/sts.go +++ b/cmd/k8s-operator/sts.go @@ -165,6 +165,7 @@ type tailscaleSTSReconciler struct { proxyImage string proxyPriorityClassName string tsFirewallMode string + proxyUseEphemeralKeys bool } func (sts tailscaleSTSReconciler) validate() error { @@ -376,7 +377,7 @@ func (a *tailscaleSTSReconciler) createOrGetSecret(ctx context.Context, logger * if len(tags) == 0 { tags = a.defaultTags } - authKey, err = newAuthKey(ctx, a.tsClient, tags) + authKey, err = newAuthKey(ctx, a.tsClient, tags, a.proxyUseEphemeralKeys) if err != nil { return "", "", nil, err } @@ -511,12 +512,13 @@ func deviceInfo(sec *corev1.Secret, podUID string, log *zap.SugaredLogger) (dev return dev, nil } -func newAuthKey(ctx context.Context, tsClient tsClient, tags []string) (string, error) { +func newAuthKey(ctx context.Context, tsClient tsClient, tags []string, ephemeral bool) (string, error) { caps := tailscale.KeyCapabilities{ Devices: tailscale.KeyDeviceCapabilities{ Create: tailscale.KeyDeviceCreateCapabilities{ Reusable: false, Preauthorized: true, + Ephemeral: ephemeral, Tags: tags, }, }, diff --git a/cmd/k8s-operator/tsrecorder.go b/cmd/k8s-operator/tsrecorder.go index e9e6b2c6c..8917d9ef9 100644 --- a/cmd/k8s-operator/tsrecorder.go +++ b/cmd/k8s-operator/tsrecorder.go @@ -57,6 +57,7 @@ type RecorderReconciler struct { clock tstime.Clock tsNamespace string tsClient tsClient + proxyUseEphemeralKeys bool mu sync.Mutex // protects following recorders set.Slice[types.UID] // for recorders gauge @@ -289,7 +290,7 @@ func (r *RecorderReconciler) ensureAuthSecretCreated(ctx context.Context, tsr *t if len(tags) == 0 { tags = tsapi.Tags{"tag:k8s"} } - authKey, err := newAuthKey(ctx, r.tsClient, tags.Stringify()) + authKey, err := newAuthKey(ctx, r.tsClient, tags.Stringify(), r.proxyUseEphemeralKeys) if err != nil { return err }