2023-01-27 21:37:20 +00:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
2022-10-25 20:12:54 +00:00
|
|
|
|
|
|
|
//go:build linux
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-08-08 23:03:08 +00:00
|
|
|
"encoding/json"
|
2022-10-25 20:12:54 +00:00
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
2023-08-08 23:03:08 +00:00
|
|
|
"net/netip"
|
2022-10-25 20:12:54 +00:00
|
|
|
"os"
|
2022-12-07 01:03:53 +00:00
|
|
|
|
2024-09-08 19:57:29 +00:00
|
|
|
"tailscale.com/kube/kubeapi"
|
|
|
|
"tailscale.com/kube/kubeclient"
|
2022-12-07 20:29:45 +00:00
|
|
|
"tailscale.com/tailcfg"
|
2022-10-25 20:12:54 +00:00
|
|
|
)
|
|
|
|
|
2024-06-17 17:50:50 +00:00
|
|
|
// storeDeviceID writes deviceID to 'device_id' data field of the named
|
|
|
|
// Kubernetes Secret.
|
|
|
|
func storeDeviceID(ctx context.Context, secretName string, deviceID tailcfg.StableNodeID) error {
|
2024-09-08 18:06:07 +00:00
|
|
|
s := &kubeapi.Secret{
|
2024-06-17 17:50:50 +00:00
|
|
|
Data: map[string][]byte{
|
|
|
|
"device_id": []byte(deviceID),
|
|
|
|
},
|
2022-11-07 17:24:42 +00:00
|
|
|
}
|
2024-06-17 17:50:50 +00:00
|
|
|
return kc.StrategicMergePatchSecret(ctx, secretName, s, "tailscale-container")
|
|
|
|
}
|
2022-11-07 17:24:42 +00:00
|
|
|
|
2024-06-17 17:50:50 +00:00
|
|
|
// storeDeviceEndpoints writes device's tailnet IPs and MagicDNS name to fields
|
|
|
|
// 'device_ips', 'device_fqdn' of the named Kubernetes Secret.
|
|
|
|
func storeDeviceEndpoints(ctx context.Context, secretName string, fqdn string, addresses []netip.Prefix) error {
|
2023-08-08 23:03:08 +00:00
|
|
|
var ips []string
|
|
|
|
for _, addr := range addresses {
|
|
|
|
ips = append(ips, addr.Addr().String())
|
|
|
|
}
|
|
|
|
deviceIPs, err := json.Marshal(ips)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-09-08 18:06:07 +00:00
|
|
|
s := &kubeapi.Secret{
|
2023-03-02 16:10:36 +00:00
|
|
|
Data: map[string][]byte{
|
|
|
|
"device_fqdn": []byte(fqdn),
|
2023-08-08 23:03:08 +00:00
|
|
|
"device_ips": deviceIPs,
|
2022-10-25 20:12:54 +00:00
|
|
|
},
|
|
|
|
}
|
2024-06-17 17:50:50 +00:00
|
|
|
return kc.StrategicMergePatchSecret(ctx, secretName, s, "tailscale-container")
|
2022-10-25 20:12:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// deleteAuthKey deletes the 'authkey' field of the given kube
|
|
|
|
// secret. No-op if there is no authkey in the secret.
|
|
|
|
func deleteAuthKey(ctx context.Context, secretName string) error {
|
|
|
|
// m is a JSON Patch data structure, see https://jsonpatch.com/ or RFC 6902.
|
2024-09-08 18:06:07 +00:00
|
|
|
m := []kubeclient.JSONPatch{
|
2022-10-25 20:12:54 +00:00
|
|
|
{
|
|
|
|
Op: "remove",
|
|
|
|
Path: "/data/authkey",
|
|
|
|
},
|
|
|
|
}
|
2024-11-19 13:07:19 +00:00
|
|
|
if err := kc.JSONPatchResource(ctx, secretName, kubeclient.TypeSecrets, m); err != nil {
|
2024-09-08 18:06:07 +00:00
|
|
|
if s, ok := err.(*kubeapi.Status); ok && s.Code == http.StatusUnprocessableEntity {
|
2022-10-25 20:12:54 +00:00
|
|
|
// This is kubernetes-ese for "the field you asked to
|
|
|
|
// delete already doesn't exist", aka no-op.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-11-20 14:17:58 +00:00
|
|
|
// storeCapVer stores the current capability version of tailscale and, if provided, UID of the Pod in the tailscale
|
|
|
|
// state Secret. This can be used to observe the current capability version of tailscaled running in this container.
|
|
|
|
func storeCapVer(ctx context.Context, secretName string, podUID string) error {
|
|
|
|
capVerS := fmt.Sprintf("%d", tailcfg.CurrentCapabilityVersion)
|
|
|
|
if podUID != "" {
|
|
|
|
capVerS += fmt.Sprintf(":%s", podUID)
|
|
|
|
}
|
|
|
|
s := &kubeapi.Secret{
|
|
|
|
Data: map[string][]byte{
|
|
|
|
"tailscale_capver": []byte(capVerS),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return kc.StrategicMergePatchSecret(ctx, secretName, s, "tailscale-container")
|
|
|
|
}
|
|
|
|
|
2024-09-08 18:06:07 +00:00
|
|
|
var kc kubeclient.Client
|
2024-04-29 16:03:48 +00:00
|
|
|
|
|
|
|
func initKubeClient(root string) {
|
2023-03-02 16:10:36 +00:00
|
|
|
if root != "/" {
|
|
|
|
// If we are running in a test, we need to set the root path to the fake
|
|
|
|
// service account directory.
|
2024-09-08 18:06:07 +00:00
|
|
|
kubeclient.SetRootPathForTesting(root)
|
2022-10-25 20:12:54 +00:00
|
|
|
}
|
2023-03-02 16:10:36 +00:00
|
|
|
var err error
|
2024-11-19 13:07:19 +00:00
|
|
|
kc, err = kubeclient.New("tailscale-container")
|
2022-10-25 20:12:54 +00:00
|
|
|
if err != nil {
|
2023-03-02 16:10:36 +00:00
|
|
|
log.Fatalf("Error creating kube client: %v", err)
|
2022-10-25 20:12:54 +00:00
|
|
|
}
|
2024-05-31 18:39:38 +00:00
|
|
|
if (root != "/") || os.Getenv("TS_KUBERNETES_READ_API_SERVER_ADDRESS_FROM_ENV") == "true" {
|
|
|
|
// Derive the API server address from the environment variables
|
|
|
|
// Used to set http server in tests, or optionally enabled by flag
|
2023-03-02 16:10:36 +00:00
|
|
|
kc.SetURL(fmt.Sprintf("https://%s:%s", os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT_HTTPS")))
|
2022-10-25 20:12:54 +00:00
|
|
|
}
|
|
|
|
}
|