diff --git a/cmd/k8s-operator/deploy/chart/templates/deployment.yaml b/cmd/k8s-operator/deploy/chart/templates/deployment.yaml index 1b9b97186..ecceeb0f3 100644 --- a/cmd/k8s-operator/deploy/chart/templates/deployment.yaml +++ b/cmd/k8s-operator/deploy/chart/templates/deployment.yaml @@ -64,6 +64,8 @@ spec: value: operator - name: OPERATOR_LOGGING value: {{ .Values.operatorConfig.logging }} + - name: OPERATOR_EPHEMERAL + value: "{{ .Values.operatorConfig.ephemeral }}" - name: OPERATOR_NAMESPACE valueFrom: fieldRef: diff --git a/cmd/k8s-operator/deploy/chart/values.yaml b/cmd/k8s-operator/deploy/chart/values.yaml index 2d1effc25..439447281 100644 --- a/cmd/k8s-operator/deploy/chart/values.yaml +++ b/cmd/k8s-operator/deploy/chart/values.yaml @@ -36,6 +36,10 @@ operatorConfig: # Multiple tags are defined as array items and passed to the operator as a comma-separated string defaultTags: - "tag:k8s-operator" + + # When true, the operator will use an ephemeral auth key + # https://tailscale.com/kb/1111/ephemeral-nodes/ + ephemeral: false image: # Repository defaults to DockerHub, but images are also synced to ghcr.io/tailscale/k8s-operator. diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_proxyclasses.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_proxyclasses.yaml index f89e38453..d1cee8664 100644 --- a/cmd/k8s-operator/deploy/crds/tailscale.com_proxyclasses.yaml +++ b/cmd/k8s-operator/deploy/crds/tailscale.com_proxyclasses.yaml @@ -2215,6 +2215,14 @@ spec: https://tailscale.com/kb/1019/subnets#use-your-subnet-routes-from-other-devices Defaults to false. type: boolean + ephemeral: + description: |- + Ephemeral can be set to true to make the proxy instance register + as an ephemeral node. Ephemeral nodes are automatically deleted + from the tailnet when they are offline for a while. + https://tailscale.com/kb/1111/ephemeral-nodes/ + Defaults to false. + type: boolean useLetsEncryptStagingEnvironment: description: |- Set UseLetsEncryptStagingEnvironment to true to issue TLS diff --git a/cmd/k8s-operator/deploy/manifests/operator.yaml b/cmd/k8s-operator/deploy/manifests/operator.yaml index dc8d0634c..c6de7c946 100644 --- a/cmd/k8s-operator/deploy/manifests/operator.yaml +++ b/cmd/k8s-operator/deploy/manifests/operator.yaml @@ -2684,6 +2684,14 @@ spec: https://tailscale.com/kb/1019/subnets#use-your-subnet-routes-from-other-devices Defaults to false. type: boolean + ephemeral: + description: |- + Ephemeral can be set to true to make the proxy instance register + as an ephemeral node. Ephemeral nodes are automatically deleted + from the tailnet when they are offline for a while. + https://tailscale.com/kb/1111/ephemeral-nodes/ + Defaults to false. + type: boolean type: object useLetsEncryptStagingEnvironment: description: |- @@ -5009,6 +5017,8 @@ spec: value: operator - name: OPERATOR_LOGGING value: info + - name: OPERATOR_EPHEMERAL + value: "false" - name: OPERATOR_NAMESPACE valueFrom: fieldRef: diff --git a/cmd/k8s-operator/operator.go b/cmd/k8s-operator/operator.go index 1f637927b..5086486b7 100644 --- a/cmd/k8s-operator/operator.go +++ b/cmd/k8s-operator/operator.go @@ -139,6 +139,7 @@ func initTSNet(zlog *zap.SugaredLogger) (*tsnet.Server, tsClient) { hostname = defaultEnv("OPERATOR_HOSTNAME", "tailscale-operator") kubeSecret = defaultEnv("OPERATOR_SECRET", "") operatorTags = defaultEnv("OPERATOR_INITIAL_TAGS", "tag:k8s-operator") + ephemeral = defaultBool("OPERATOR_EPHEMERAL", false) ) startlog := zlog.Named("startup") if clientIDPath == "" || clientSecretPath == "" { @@ -196,6 +197,7 @@ waitOnline: Create: tailscale.KeyDeviceCreateCapabilities{ Reusable: false, Preauthorized: true, + Ephemeral: ephemeral, Tags: strings.Split(operatorTags, ","), }, }, diff --git a/cmd/k8s-operator/proxygroup.go b/cmd/k8s-operator/proxygroup.go index f263829d7..a9b4dc071 100644 --- a/cmd/k8s-operator/proxygroup.go +++ b/cmd/k8s-operator/proxygroup.go @@ -477,7 +477,11 @@ func (r *ProxyGroupReconciler) ensureConfigSecretsCreated(ctx context.Context, p if len(tags) == 0 { tags = r.defaultTags } - authKey, err = newAuthKey(ctx, r.tsClient, tags) + ephemeral := false + if proxyClass != nil && proxyClass.Spec.TailscaleConfig != nil { + ephemeral = proxyClass.Spec.TailscaleConfig.Ephemeral + } + authKey, err = newAuthKey(ctx, r.tsClient, tags, ephemeral) if err != nil { return "", err } diff --git a/cmd/k8s-operator/sts.go b/cmd/k8s-operator/sts.go index 7434ea79d..91d667f09 100644 --- a/cmd/k8s-operator/sts.go +++ b/cmd/k8s-operator/sts.go @@ -376,7 +376,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, shouldUseEphemeral(stsC.ProxyClass)) if err != nil { return "", "", nil, err } @@ -511,12 +511,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, }, }, @@ -1022,6 +1023,10 @@ func shouldAcceptRoutes(pc *tsapi.ProxyClass) bool { return pc != nil && pc.Spec.TailscaleConfig != nil && pc.Spec.TailscaleConfig.AcceptRoutes } +func shouldUseEphemeral(pc *tsapi.ProxyClass) bool { + return pc != nil && pc.Spec.TailscaleConfig != nil && pc.Spec.TailscaleConfig.Ephemeral +} + // ptrObject is a type constraint for pointer types that implement // client.Object. type ptrObject[T any] interface { diff --git a/cmd/k8s-operator/tsrecorder.go b/cmd/k8s-operator/tsrecorder.go index e9e6b2c6c..cff5dd545 100644 --- a/cmd/k8s-operator/tsrecorder.go +++ b/cmd/k8s-operator/tsrecorder.go @@ -289,7 +289,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(), false) if err != nil { return err } diff --git a/k8s-operator/api.md b/k8s-operator/api.md index 190f99d24..8b9416479 100644 --- a/k8s-operator/api.md +++ b/k8s-operator/api.md @@ -1013,5 +1013,6 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `acceptRoutes` _boolean_ | AcceptRoutes can be set to true to make the proxy instance accept
routes advertized by other nodes on the tailnet, such as subnet
routes.
This is equivalent of passing --accept-routes flag to a tailscale Linux client.
https://tailscale.com/kb/1019/subnets#use-your-subnet-routes-from-other-devices
Defaults to false. | | | +| `ephemeral` _boolean_ | Ephemeral can be set to true to make the proxy instance register
as an ephemeral node. Ephemeral nodes are automatically deleted
from the tailnet when they are offline for a while.
https://tailscale.com/kb/1111/ephemeral-nodes/
Defaults to false. | | | diff --git a/k8s-operator/apis/v1alpha1/types_proxyclass.go b/k8s-operator/apis/v1alpha1/types_proxyclass.go index 3fde0b37a..30f9fc02c 100644 --- a/k8s-operator/apis/v1alpha1/types_proxyclass.go +++ b/k8s-operator/apis/v1alpha1/types_proxyclass.go @@ -91,6 +91,13 @@ type TailscaleConfig struct { // https://tailscale.com/kb/1019/subnets#use-your-subnet-routes-from-other-devices // Defaults to false. AcceptRoutes bool `json:"acceptRoutes,omitempty"` + + // Ephemeral can be set to true to make the proxy instance register + // as an ephemeral node. Ephemeral nodes are automatically deleted + // from the tailnet when they are offline for a while. + // https://tailscale.com/kb/1111/ephemeral-nodes/ + // Defaults to false. + Ephemeral bool `json:"ephemeral,omitempty"` } type StatefulSet struct {