mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 00:01:40 +01:00 
			
		
		
		
	Adds a new reconciler for ProxyGroups of type kube-apiserver that will provision a Tailscale Service for each replica to advertise. Adds two new condition types to the ProxyGroup, TailscaleServiceValid and TailscaleServiceConfigured, to post updates on the state of that reconciler in a way that's consistent with the service-pg reconciler. The created Tailscale Service name is configurable via a new ProxyGroup field spec.kubeAPISserver.ServiceName, which expects a string of the form "svc:<dns-label>". Lots of supporting changes were needed to implement this in a way that's consistent with other operator workflows, including: * Pulled containerboot's ensureServicesUnadvertised and certManager into kube/ libraries to be shared with k8s-proxy. Use those in k8s-proxy to aid Service cert sharing between replicas and graceful Service shutdown. * For certManager, add an initial wait to the cert loop to wait until the domain appears in the devices's netmap to avoid a guaranteed error on the first issue attempt when it's quick to start. * Made several methods in ingress-for-pg.go and svc-for-pg.go into functions to share with the new reconciler * Added a Resource struct to the owner refs stored in Tailscale Service annotations to be able to distinguish between Ingress- and ProxyGroup- based Services that need cleaning up in the Tailscale API. * Added a ListVIPServices method to the internal tailscale client to aid cleaning up orphaned Services * Support for reading config from a kube Secret, and partial support for config reloading, to prevent us having to force Pod restarts when config changes. * Fixed up the zap logger so it's possible to set debug log level. Updates #13358 Change-Id: Ia9607441157dd91fb9b6ecbc318eecbef446e116 Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
		
			
				
	
	
		
			66 lines
		
	
	
		
			2.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			66 lines
		
	
	
		
			2.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| //go:build !plan9
 | |
| 
 | |
| package main
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 
 | |
| 	"golang.org/x/oauth2/clientcredentials"
 | |
| 	"tailscale.com/internal/client/tailscale"
 | |
| 	"tailscale.com/ipn"
 | |
| 	"tailscale.com/tailcfg"
 | |
| )
 | |
| 
 | |
| // defaultTailnet is a value that can be used in Tailscale API calls instead of tailnet name to indicate that the API
 | |
| // call should be performed on the default tailnet for the provided credentials.
 | |
| const (
 | |
| 	defaultTailnet = "-"
 | |
| )
 | |
| 
 | |
| func newTSClient(ctx context.Context, clientIDPath, clientSecretPath, loginServer string) (tsClient, error) {
 | |
| 	clientID, err := os.ReadFile(clientIDPath)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("error reading client ID %q: %w", clientIDPath, err)
 | |
| 	}
 | |
| 	clientSecret, err := os.ReadFile(clientSecretPath)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("reading client secret %q: %w", clientSecretPath, err)
 | |
| 	}
 | |
| 	const tokenURLPath = "/api/v2/oauth/token"
 | |
| 	tokenURL := fmt.Sprintf("%s%s", ipn.DefaultControlURL, tokenURLPath)
 | |
| 	if loginServer != "" {
 | |
| 		tokenURL = fmt.Sprintf("%s%s", loginServer, tokenURLPath)
 | |
| 	}
 | |
| 	credentials := clientcredentials.Config{
 | |
| 		ClientID:     string(clientID),
 | |
| 		ClientSecret: string(clientSecret),
 | |
| 		TokenURL:     tokenURL,
 | |
| 	}
 | |
| 	c := tailscale.NewClient(defaultTailnet, nil)
 | |
| 	c.UserAgent = "tailscale-k8s-operator"
 | |
| 	c.HTTPClient = credentials.Client(ctx)
 | |
| 	if loginServer != "" {
 | |
| 		c.BaseURL = loginServer
 | |
| 	}
 | |
| 	return c, nil
 | |
| }
 | |
| 
 | |
| type tsClient interface {
 | |
| 	CreateKey(ctx context.Context, caps tailscale.KeyCapabilities) (string, *tailscale.Key, error)
 | |
| 	Device(ctx context.Context, deviceID string, fields *tailscale.DeviceFieldsOpts) (*tailscale.Device, error)
 | |
| 	DeleteDevice(ctx context.Context, nodeStableID string) error
 | |
| 	// GetVIPService is a method for getting a Tailscale Service. VIPService is the original name for Tailscale Service.
 | |
| 	GetVIPService(ctx context.Context, name tailcfg.ServiceName) (*tailscale.VIPService, error)
 | |
| 	// ListVIPServices is a method for listing all Tailscale Services. VIPService is the original name for Tailscale Service.
 | |
| 	ListVIPServices(ctx context.Context) (*tailscale.VIPServiceList, error)
 | |
| 	// CreateOrUpdateVIPService is a method for creating or updating a Tailscale Service.
 | |
| 	CreateOrUpdateVIPService(ctx context.Context, svc *tailscale.VIPService) error
 | |
| 	// DeleteVIPService is a method for deleting a Tailscale Service.
 | |
| 	DeleteVIPService(ctx context.Context, name tailcfg.ServiceName) error
 | |
| }
 |