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 {