cmd/k8s-operator,k8s-operator/api-proxy: move k8s proxy code to library

The defaultEnv and defaultBool functions are copied over temporarily
to minimise diff. This lays the ground work for having both the operator
and the new k8s-proxy binary implement the API proxy

Updates #13358

Change-Id: Ieacc79af64df2f13b27a18135517bb31c80a5a02
Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
This commit is contained in:
Tom Proctor 2025-05-01 18:13:54 +01:00
parent 1f1c323eeb
commit 15a8d9ab56
5 changed files with 60 additions and 27 deletions

View File

@ -45,6 +45,7 @@ import (
"tailscale.com/hostinfo" "tailscale.com/hostinfo"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/ipn/store/kubestore" "tailscale.com/ipn/store/kubestore"
apiproxy "tailscale.com/k8s-operator/api-proxy"
tsapi "tailscale.com/k8s-operator/apis/v1alpha1" tsapi "tailscale.com/k8s-operator/apis/v1alpha1"
"tailscale.com/kube/kubetypes" "tailscale.com/kube/kubetypes"
"tailscale.com/tsnet" "tailscale.com/tsnet"
@ -102,8 +103,8 @@ func main() {
// The operator can run either as a plain operator or it can // The operator can run either as a plain operator or it can
// additionally act as api-server proxy // additionally act as api-server proxy
// https://tailscale.com/kb/1236/kubernetes-operator/?q=kubernetes#accessing-the-kubernetes-control-plane-using-an-api-server-proxy. // https://tailscale.com/kb/1236/kubernetes-operator/?q=kubernetes#accessing-the-kubernetes-control-plane-using-an-api-server-proxy.
mode := parseAPIProxyMode() mode := apiproxy.ParseAPIProxyMode()
if mode == apiserverProxyModeDisabled { if mode == apiproxy.APIServerProxyModeDisabled {
hostinfo.SetApp(kubetypes.AppOperator) hostinfo.SetApp(kubetypes.AppOperator)
} else { } else {
hostinfo.SetApp(kubetypes.AppAPIServerProxy) hostinfo.SetApp(kubetypes.AppAPIServerProxy)
@ -112,7 +113,7 @@ func main() {
s, tsc := initTSNet(zlog) s, tsc := initTSNet(zlog)
defer s.Close() defer s.Close()
restConfig := config.GetConfigOrDie() restConfig := config.GetConfigOrDie()
maybeLaunchAPIServerProxy(zlog, restConfig, s, mode) apiproxy.MaybeLaunchAPIServerProxy(zlog, restConfig, s, mode)
rOpts := reconcilerOpts{ rOpts := reconcilerOpts{
log: zlog, log: zlog,
tsServer: s, tsServer: s,

View File

@ -0,0 +1,8 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !plan9
// Package apiproxy contains the Kubernetes API Proxy implementation used by
// k8s-operator and k8s-proxy.
package apiproxy

View File

@ -0,0 +1,24 @@
package apiproxy
import (
"os"
"tailscale.com/types/opt"
)
func defaultBool(envName string, defVal bool) bool {
vs := os.Getenv(envName)
if vs == "" {
return defVal
}
v, _ := opt.Bool(vs).Get()
return v
}
func defaultEnv(envName, defVal string) string {
v := os.Getenv(envName)
if v == "" {
return defVal
}
return v
}

View File

@ -3,7 +3,7 @@
//go:build !plan9 //go:build !plan9
package main package apiproxy
import ( import (
"crypto/tls" "crypto/tls"
@ -37,15 +37,15 @@ var (
whoIsKey = ctxkey.New("", (*apitype.WhoIsResponse)(nil)) whoIsKey = ctxkey.New("", (*apitype.WhoIsResponse)(nil))
) )
type apiServerProxyMode int type APIServerProxyMode int
func (a apiServerProxyMode) String() string { func (a APIServerProxyMode) String() string {
switch a { switch a {
case apiserverProxyModeDisabled: case APIServerProxyModeDisabled:
return "disabled" return "disabled"
case apiserverProxyModeEnabled: case APIServerProxyModeEnabled:
return "auth" return "auth"
case apiserverProxyModeNoAuth: case APIServerProxyModeNoAuth:
return "noauth" return "noauth"
default: default:
return "unknown" return "unknown"
@ -53,12 +53,12 @@ func (a apiServerProxyMode) String() string {
} }
const ( const (
apiserverProxyModeDisabled apiServerProxyMode = iota APIServerProxyModeDisabled APIServerProxyMode = iota
apiserverProxyModeEnabled APIServerProxyModeEnabled
apiserverProxyModeNoAuth APIServerProxyModeNoAuth
) )
func parseAPIProxyMode() apiServerProxyMode { func ParseAPIProxyMode() APIServerProxyMode {
haveAuthProxyEnv := os.Getenv("AUTH_PROXY") != "" haveAuthProxyEnv := os.Getenv("AUTH_PROXY") != ""
haveAPIProxyEnv := os.Getenv("APISERVER_PROXY") != "" haveAPIProxyEnv := os.Getenv("APISERVER_PROXY") != ""
switch { switch {
@ -67,34 +67,34 @@ func parseAPIProxyMode() apiServerProxyMode {
case haveAuthProxyEnv: case haveAuthProxyEnv:
var authProxyEnv = defaultBool("AUTH_PROXY", false) // deprecated var authProxyEnv = defaultBool("AUTH_PROXY", false) // deprecated
if authProxyEnv { if authProxyEnv {
return apiserverProxyModeEnabled return APIServerProxyModeEnabled
} }
return apiserverProxyModeDisabled return APIServerProxyModeDisabled
case haveAPIProxyEnv: case haveAPIProxyEnv:
var apiProxyEnv = defaultEnv("APISERVER_PROXY", "") // true, false or "noauth" var apiProxyEnv = defaultEnv("APISERVER_PROXY", "") // true, false or "noauth"
switch apiProxyEnv { switch apiProxyEnv {
case "true": case "true":
return apiserverProxyModeEnabled return APIServerProxyModeEnabled
case "false", "": case "false", "":
return apiserverProxyModeDisabled return APIServerProxyModeDisabled
case "noauth": case "noauth":
return apiserverProxyModeNoAuth return APIServerProxyModeNoAuth
default: default:
panic(fmt.Sprintf("unknown APISERVER_PROXY value %q", apiProxyEnv)) panic(fmt.Sprintf("unknown APISERVER_PROXY value %q", apiProxyEnv))
} }
} }
return apiserverProxyModeDisabled return APIServerProxyModeDisabled
} }
// maybeLaunchAPIServerProxy launches the auth proxy, which is a small HTTP server // maybeLaunchAPIServerProxy launches the auth proxy, which is a small HTTP server
// that authenticates requests using the Tailscale LocalAPI and then proxies // that authenticates requests using the Tailscale LocalAPI and then proxies
// them to the kube-apiserver. // them to the kube-apiserver.
func maybeLaunchAPIServerProxy(zlog *zap.SugaredLogger, restConfig *rest.Config, s *tsnet.Server, mode apiServerProxyMode) { func MaybeLaunchAPIServerProxy(zlog *zap.SugaredLogger, restConfig *rest.Config, s *tsnet.Server, mode APIServerProxyMode) {
if mode == apiserverProxyModeDisabled { if mode == APIServerProxyModeDisabled {
return return
} }
startlog := zlog.Named("launchAPIProxy") startlog := zlog.Named("launchAPIProxy")
if mode == apiserverProxyModeNoAuth { if mode == APIServerProxyModeNoAuth {
restConfig = rest.AnonymousClientConfig(restConfig) restConfig = rest.AnonymousClientConfig(restConfig)
} }
cfg, err := restConfig.TransportConfig() cfg, err := restConfig.TransportConfig()
@ -132,8 +132,8 @@ func maybeLaunchAPIServerProxy(zlog *zap.SugaredLogger, restConfig *rest.Config,
// are passed through to the Kubernetes API. // are passed through to the Kubernetes API.
// //
// It never returns. // It never returns.
func runAPIServerProxy(ts *tsnet.Server, rt http.RoundTripper, log *zap.SugaredLogger, mode apiServerProxyMode, host string) { func runAPIServerProxy(ts *tsnet.Server, rt http.RoundTripper, log *zap.SugaredLogger, mode APIServerProxyMode, host string) {
if mode == apiserverProxyModeDisabled { if mode == APIServerProxyModeDisabled {
return return
} }
ln, err := ts.Listen("tcp", ":443") ln, err := ts.Listen("tcp", ":443")
@ -192,7 +192,7 @@ type apiserverProxy struct {
lc *local.Client lc *local.Client
rp *httputil.ReverseProxy rp *httputil.ReverseProxy
mode apiServerProxyMode mode APIServerProxyMode
ts *tsnet.Server ts *tsnet.Server
upstreamURL *url.URL upstreamURL *url.URL
} }
@ -285,7 +285,7 @@ func (ap *apiserverProxy) execForProto(w http.ResponseWriter, r *http.Request, p
func (h *apiserverProxy) addImpersonationHeadersAsRequired(r *http.Request) { func (h *apiserverProxy) addImpersonationHeadersAsRequired(r *http.Request) {
r.URL.Scheme = h.upstreamURL.Scheme r.URL.Scheme = h.upstreamURL.Scheme
r.URL.Host = h.upstreamURL.Host r.URL.Host = h.upstreamURL.Host
if h.mode == apiserverProxyModeNoAuth { if h.mode == APIServerProxyModeNoAuth {
// If we are not providing authentication, then we are just // If we are not providing authentication, then we are just
// proxying to the Kubernetes API, so we don't need to do // proxying to the Kubernetes API, so we don't need to do
// anything else. // anything else.

View File

@ -3,7 +3,7 @@
//go:build !plan9 //go:build !plan9
package main package apiproxy
import ( import (
"net/http" "net/http"