cmd/k8s-operator,k8s-operator/api-proxy: move k8s proxy code to library (#15857)

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-06 14:52:16 +01:00
committed by GitHub
parent 597d0e8fd5
commit 62182f3bcf
6 changed files with 68 additions and 29 deletions

View File

@@ -840,9 +840,10 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/ipn/store/kubestore from tailscale.com/cmd/k8s-operator+ tailscale.com/ipn/store/kubestore from tailscale.com/cmd/k8s-operator+
tailscale.com/ipn/store/mem from tailscale.com/ipn/ipnlocal+ tailscale.com/ipn/store/mem from tailscale.com/ipn/ipnlocal+
tailscale.com/k8s-operator from tailscale.com/cmd/k8s-operator tailscale.com/k8s-operator from tailscale.com/cmd/k8s-operator
tailscale.com/k8s-operator/api-proxy from tailscale.com/cmd/k8s-operator
tailscale.com/k8s-operator/apis from tailscale.com/k8s-operator/apis/v1alpha1 tailscale.com/k8s-operator/apis from tailscale.com/k8s-operator/apis/v1alpha1
tailscale.com/k8s-operator/apis/v1alpha1 from tailscale.com/cmd/k8s-operator+ tailscale.com/k8s-operator/apis/v1alpha1 from tailscale.com/cmd/k8s-operator+
tailscale.com/k8s-operator/sessionrecording from tailscale.com/cmd/k8s-operator tailscale.com/k8s-operator/sessionrecording from tailscale.com/k8s-operator/api-proxy
tailscale.com/k8s-operator/sessionrecording/spdy from tailscale.com/k8s-operator/sessionrecording tailscale.com/k8s-operator/sessionrecording/spdy from tailscale.com/k8s-operator/sessionrecording
tailscale.com/k8s-operator/sessionrecording/tsrecorder from tailscale.com/k8s-operator/sessionrecording+ tailscale.com/k8s-operator/sessionrecording/tsrecorder from tailscale.com/k8s-operator/sessionrecording+
tailscale.com/k8s-operator/sessionrecording/ws from tailscale.com/k8s-operator/sessionrecording tailscale.com/k8s-operator/sessionrecording/ws from tailscale.com/k8s-operator/sessionrecording
@@ -945,7 +946,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/util/clientmetric from tailscale.com/cmd/k8s-operator+ tailscale.com/util/clientmetric from tailscale.com/cmd/k8s-operator+
tailscale.com/util/cloudenv from tailscale.com/hostinfo+ tailscale.com/util/cloudenv from tailscale.com/hostinfo+
tailscale.com/util/cmpver from tailscale.com/clientupdate+ tailscale.com/util/cmpver from tailscale.com/clientupdate+
tailscale.com/util/ctxkey from tailscale.com/cmd/k8s-operator+ tailscale.com/util/ctxkey from tailscale.com/client/tailscale/apitype+
💣 tailscale.com/util/deephash from tailscale.com/ipn/ipnlocal+ 💣 tailscale.com/util/deephash from tailscale.com/ipn/ipnlocal+
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics+ L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics+
tailscale.com/util/dnsname from tailscale.com/appc+ tailscale.com/util/dnsname from tailscale.com/appc+

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,29 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !plan9
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"