@@ -9,30 +9,55 @@ import (
99 "context"
1010 "encoding/json"
1111 "fmt"
12- "log"
1312 "net/http"
1413 "net/netip"
1514 "os"
1615
1716 "tailscale.com/kube/kubeapi"
1817 "tailscale.com/kube/kubeclient"
18+ "tailscale.com/kube/kubetypes"
1919 "tailscale.com/tailcfg"
2020)
2121
22- // storeDeviceID writes deviceID to 'device_id' data field of the named
23- // Kubernetes Secret.
24- func storeDeviceID (ctx context.Context , secretName string , deviceID tailcfg.StableNodeID ) error {
22+ // kubeClient is a wrapper around Tailscale's internal kube client that knows how to talk to the kube API server. We use
23+ // this rather than any of the upstream Kubernetes client libaries to avoid extra imports.
24+ type kubeClient struct {
25+ kubeclient.Client
26+ stateSecret string
27+ }
28+
29+ func newKubeClient (root string , stateSecret string ) (* kubeClient , error ) {
30+ if root != "/" {
31+ // If we are running in a test, we need to set the root path to the fake
32+ // service account directory.
33+ kubeclient .SetRootPathForTesting (root )
34+ }
35+ var err error
36+ kc , err := kubeclient .New ("tailscale-container" )
37+ if err != nil {
38+ return nil , fmt .Errorf ("Error creating kube client: %w" , err )
39+ }
40+ if (root != "/" ) || os .Getenv ("TS_KUBERNETES_READ_API_SERVER_ADDRESS_FROM_ENV" ) == "true" {
41+ // Derive the API server address from the environment variables
42+ // Used to set http server in tests, or optionally enabled by flag
43+ kc .SetURL (fmt .Sprintf ("https://%s:%s" , os .Getenv ("KUBERNETES_SERVICE_HOST" ), os .Getenv ("KUBERNETES_SERVICE_PORT_HTTPS" )))
44+ }
45+ return & kubeClient {Client : kc , stateSecret : stateSecret }, nil
46+ }
47+
48+ // storeDeviceID writes deviceID to 'device_id' data field of the client's state Secret.
49+ func (kc * kubeClient ) storeDeviceID (ctx context.Context , deviceID tailcfg.StableNodeID ) error {
2550 s := & kubeapi.Secret {
2651 Data : map [string ][]byte {
27- "device_id" : []byte (deviceID ),
52+ kubetypes . KeyDeviceID : []byte (deviceID ),
2853 },
2954 }
30- return kc .StrategicMergePatchSecret (ctx , secretName , s , "tailscale-container" )
55+ return kc .StrategicMergePatchSecret (ctx , kc . stateSecret , s , "tailscale-container" )
3156}
3257
33- // storeDeviceEndpoints writes device's tailnet IPs and MagicDNS name to fields
34- // 'device_ips', 'device_fqdn' of the named Kubernetes Secret.
35- func storeDeviceEndpoints (ctx context.Context , secretName string , fqdn string , addresses []netip.Prefix ) error {
58+ // storeDeviceEndpoints writes device's tailnet IPs and MagicDNS name to fields 'device_ips', 'device_fqdn' of client's
59+ // state Secret.
60+ func ( kc * kubeClient ) storeDeviceEndpoints (ctx context.Context , fqdn string , addresses []netip.Prefix ) error {
3661 var ips []string
3762 for _ , addr := range addresses {
3863 ips = append (ips , addr .Addr ().String ())
@@ -44,24 +69,36 @@ func storeDeviceEndpoints(ctx context.Context, secretName string, fqdn string, a
4469
4570 s := & kubeapi.Secret {
4671 Data : map [string ][]byte {
47- "device_fqdn" : []byte (fqdn ),
48- "device_ips" : deviceIPs ,
72+ kubetypes . KeyDeviceFQDN : []byte (fqdn ),
73+ kubetypes . KeyDeviceIPs : deviceIPs ,
4974 },
5075 }
51- return kc .StrategicMergePatchSecret (ctx , secretName , s , "tailscale-container" )
76+ return kc .StrategicMergePatchSecret (ctx , kc .stateSecret , s , "tailscale-container" )
77+ }
78+
79+ // storeHTTPSEndpoint writes an HTTPS endpoint exposed by this device via 'tailscale serve' to the client's state
80+ // Secret. In practice this will be the same value that gets written to 'device_fqdn', but this should only be called
81+ // when the serve config has been successfully set up.
82+ func (kc * kubeClient ) storeHTTPSEndpoint (ctx context.Context , ep string ) error {
83+ s := & kubeapi.Secret {
84+ Data : map [string ][]byte {
85+ kubetypes .KeyHTTPSEndpoint : []byte (ep ),
86+ },
87+ }
88+ return kc .StrategicMergePatchSecret (ctx , kc .stateSecret , s , "tailscale-container" )
5289}
5390
5491// deleteAuthKey deletes the 'authkey' field of the given kube
5592// secret. No-op if there is no authkey in the secret.
56- func deleteAuthKey (ctx context.Context , secretName string ) error {
93+ func ( kc * kubeClient ) deleteAuthKey (ctx context.Context ) error {
5794 // m is a JSON Patch data structure, see https://jsonpatch.com/ or RFC 6902.
5895 m := []kubeclient.JSONPatch {
5996 {
6097 Op : "remove" ,
6198 Path : "/data/authkey" ,
6299 },
63100 }
64- if err := kc .JSONPatchResource (ctx , secretName , kubeclient .TypeSecrets , m ); err != nil {
101+ if err := kc .JSONPatchResource (ctx , kc . stateSecret , kubeclient .TypeSecrets , m ); err != nil {
65102 if s , ok := err .(* kubeapi.Status ); ok && s .Code == http .StatusUnprocessableEntity {
66103 // This is kubernetes-ese for "the field you asked to
67104 // delete already doesn't exist", aka no-op.
@@ -72,22 +109,19 @@ func deleteAuthKey(ctx context.Context, secretName string) error {
72109 return nil
73110}
74111
75- var kc kubeclient. Client
76-
77- func initKubeClient ( root string ) {
78- if root != "/" {
79- // If we are running in a test, we need to set the root path to the fake
80- // service account directory.
81- kubeclient . SetRootPathForTesting ( root )
112+ // storeCapVerUID stores the current capability version of tailscale and, if provided, UID of the Pod in the tailscale
113+ // state Secret.
114+ // These two fields are used by the Kubernetes Operator to observe the current capability version of tailscaled running in this container.
115+ func ( kc * kubeClient ) storeCapVerUID ( ctx context. Context , podUID string ) error {
116+ capVerS := fmt . Sprintf ( "%d" , tailcfg . CurrentCapabilityVersion )
117+ d := map [ string ][] byte {
118+ kubetypes . KeyCapVer : [] byte ( capVerS ),
82119 }
83- var err error
84- kc , err = kubeclient .New ("tailscale-container" )
85- if err != nil {
86- log .Fatalf ("Error creating kube client: %v" , err )
120+ if podUID != "" {
121+ d [kubetypes .KeyPodUID ] = []byte (podUID )
87122 }
88- if (root != "/" ) || os .Getenv ("TS_KUBERNETES_READ_API_SERVER_ADDRESS_FROM_ENV" ) == "true" {
89- // Derive the API server address from the environment variables
90- // Used to set http server in tests, or optionally enabled by flag
91- kc .SetURL (fmt .Sprintf ("https://%s:%s" , os .Getenv ("KUBERNETES_SERVICE_HOST" ), os .Getenv ("KUBERNETES_SERVICE_PORT_HTTPS" )))
123+ s := & kubeapi.Secret {
124+ Data : d ,
92125 }
126+ return kc .StrategicMergePatchSecret (ctx , kc .stateSecret , s , "tailscale-container" )
93127}
0 commit comments