2025-07-09 09:21:56 +01:00
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !plan9
// Package conf contains code to load, manipulate, and access config file
// settings for k8s-proxy.
package conf
import (
"encoding/json"
2025-07-21 11:03:21 +01:00
"errors"
2025-07-09 09:21:56 +01:00
"fmt"
2025-07-14 15:39:39 +01:00
"net/netip"
2025-07-09 09:21:56 +01:00
"github.com/tailscale/hujson"
2025-07-22 14:46:38 +01:00
"tailscale.com/kube/kubetypes"
2025-07-21 11:03:21 +01:00
"tailscale.com/tailcfg"
2025-07-09 09:21:56 +01:00
"tailscale.com/types/opt"
)
const v1Alpha1 = "v1alpha1"
// Config describes a config file.
type Config struct {
2025-07-21 11:03:21 +01:00
Raw [ ] byte // raw bytes, in HuJSON form
2025-07-09 09:21:56 +01:00
Std [ ] byte // standardized JSON form
Version string // "v1alpha1"
2025-07-21 11:03:21 +01:00
// Parsed is the parsed config, converted from its raw bytes version to the
2025-07-09 09:21:56 +01:00
// latest known format.
Parsed ConfigV1Alpha1
}
// VersionedConfig allows specifying config at the root of the object, or in
// a versioned sub-object.
// e.g. {"version": "v1alpha1", "authKey": "abc123"}
// or {"version": "v1beta1", "a-beta-config": "a-beta-value", "v1alpha1": {"authKey": "abc123"}}
type VersionedConfig struct {
Version string ` json:",omitempty" ` // "v1alpha1"
// Latest version of the config.
* ConfigV1Alpha1
// Backwards compatibility version(s) of the config. Fields and sub-fields
// from here should only be added to, never changed in place.
V1Alpha1 * ConfigV1Alpha1 ` json:",omitempty" `
// V1Beta1 *ConfigV1Beta1 `json:",omitempty"` // Not yet used.
}
type ConfigV1Alpha1 struct {
2025-07-22 17:07:51 +01:00
AuthKey * string ` json:",omitempty" ` // Tailscale auth key to use.
State * string ` json:",omitempty" ` // Path to the Tailscale state.
LogLevel * string ` json:",omitempty" ` // "debug", "info". Defaults to "info".
App * string ` json:",omitempty" ` // e.g. kubetypes.AppProxyGroupKubeAPIServer
ServerURL * string ` json:",omitempty" ` // URL of the Tailscale coordination server.
LocalAddr * string ` json:",omitempty" ` // The address to use for serving HTTP health checks and metrics (defaults to all interfaces).
LocalPort * uint16 ` json:",omitempty" ` // The port to use for serving HTTP health checks and metrics (defaults to 9002).
MetricsEnabled opt . Bool ` json:",omitempty" ` // Serve metrics on <LocalAddr>:<LocalPort>/metrics.
HealthCheckEnabled opt . Bool ` json:",omitempty" ` // Serve health check on <LocalAddr>:<LocalPort>/metrics.
2025-07-21 11:03:21 +01:00
// TODO(tomhjp): The remaining fields should all be reloadable during
// runtime, but currently missing most of the APIServerProxy fields.
Hostname * string ` json:",omitempty" ` // Tailscale device hostname.
2025-07-22 17:07:51 +01:00
AcceptRoutes opt . Bool ` json:",omitempty" ` // Accepts routes advertised by other Tailscale nodes.
2025-07-21 11:03:21 +01:00
AdvertiseServices [ ] string ` json:",omitempty" ` // Tailscale Services to advertise.
APIServerProxy * APIServerProxyConfig ` json:",omitempty" ` // Config specific to the API Server proxy.
2025-07-22 17:07:51 +01:00
StaticEndpoints [ ] netip . AddrPort ` json:",omitempty" ` // StaticEndpoints are additional, user-defined endpoints that this node should advertise amongst its wireguard endpoints.
2025-07-09 09:21:56 +01:00
}
2025-07-21 11:03:21 +01:00
type APIServerProxyConfig struct {
2025-07-22 14:46:38 +01:00
Enabled opt . Bool ` json:",omitempty" ` // Whether to enable the API Server proxy.
Mode * kubetypes . APIServerProxyMode ` json:",omitempty" ` // "auth" or "noauth" mode.
ServiceName * tailcfg . ServiceName ` json:",omitempty" ` // Name of the Tailscale Service to advertise.
IssueCerts opt . Bool ` json:",omitempty" ` // Whether this replica should issue TLS certs for the Tailscale Service.
2025-07-09 09:21:56 +01:00
}
// Load reads and parses the config file at the provided path on disk.
2025-07-21 11:03:21 +01:00
func Load ( raw [ ] byte ) ( c Config , err error ) {
c . Raw = raw
2025-07-09 09:21:56 +01:00
c . Std , err = hujson . Standardize ( c . Raw )
if err != nil {
2025-07-21 11:03:21 +01:00
return c , fmt . Errorf ( "error parsing config as HuJSON/JSON: %w" , err )
2025-07-09 09:21:56 +01:00
}
var ver VersionedConfig
if err := json . Unmarshal ( c . Std , & ver ) ; err != nil {
2025-07-21 11:03:21 +01:00
return c , fmt . Errorf ( "error parsing config: %w" , err )
2025-07-09 09:21:56 +01:00
}
rootV1Alpha1 := ( ver . Version == v1Alpha1 )
backCompatV1Alpha1 := ( ver . V1Alpha1 != nil )
switch {
case ver . Version == "" :
2025-07-21 11:03:21 +01:00
return c , errors . New ( "error parsing config: no \"version\" field provided" )
2025-07-09 09:21:56 +01:00
case rootV1Alpha1 && backCompatV1Alpha1 :
// Exactly one of these should be set.
2025-07-21 11:03:21 +01:00
return c , errors . New ( "error parsing config: both root and v1alpha1 config provided" )
2025-07-09 09:21:56 +01:00
case rootV1Alpha1 != backCompatV1Alpha1 :
c . Version = v1Alpha1
switch {
case rootV1Alpha1 && ver . ConfigV1Alpha1 != nil :
c . Parsed = * ver . ConfigV1Alpha1
case backCompatV1Alpha1 :
c . Parsed = * ver . V1Alpha1
default :
c . Parsed = ConfigV1Alpha1 { }
}
default :
2025-07-21 11:03:21 +01:00
return c , fmt . Errorf ( "error parsing config: unsupported \"version\" value %q; want \"%s\"" , ver . Version , v1Alpha1 )
2025-07-09 09:21:56 +01:00
}
return c , nil
}
2025-07-22 17:07:51 +01:00
func ( c * Config ) GetLocalAddr ( ) string {
if c . Parsed . LocalAddr == nil {
return "[::]"
}
return * c . Parsed . LocalAddr
}
func ( c * Config ) GetLocalPort ( ) uint16 {
if c . Parsed . LocalPort == nil {
return uint16 ( 9002 )
}
return * c . Parsed . LocalPort
}