mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-29 15:23:45 +00:00
cmd/{k8s-operator,k8s-proxy},kube: use consistent type for auth mode config (#16626)
Updates k8s-proxy's config so its auth mode config matches that we set in kube-apiserver ProxyGroups for consistency. Updates #13358 Change-Id: I95e29cec6ded2dc7c6d2d03f968a25c822bc0e01 Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
This commit is contained in:
parent
6f7e78b10f
commit
22a8e0ac50
@ -9,30 +9,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"tailscale.com/kube/kubetypes"
|
||||||
|
"tailscale.com/types/ptr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type apiServerProxyMode int
|
func parseAPIProxyMode() *kubetypes.APIServerProxyMode {
|
||||||
|
|
||||||
func (a apiServerProxyMode) String() string {
|
|
||||||
switch a {
|
|
||||||
case apiServerProxyModeDisabled:
|
|
||||||
return "disabled"
|
|
||||||
case apiServerProxyModeEnabled:
|
|
||||||
return "auth"
|
|
||||||
case apiServerProxyModeNoAuth:
|
|
||||||
return "noauth"
|
|
||||||
default:
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
apiServerProxyModeDisabled apiServerProxyMode = iota
|
|
||||||
apiServerProxyModeEnabled
|
|
||||||
apiServerProxyModeNoAuth
|
|
||||||
)
|
|
||||||
|
|
||||||
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 {
|
||||||
@ -41,21 +23,21 @@ 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 ptr.To(kubetypes.APIServerProxyModeAuth)
|
||||||
}
|
}
|
||||||
return apiServerProxyModeDisabled
|
return nil
|
||||||
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 ptr.To(kubetypes.APIServerProxyModeAuth)
|
||||||
case "false", "":
|
case "false", "":
|
||||||
return apiServerProxyModeDisabled
|
return nil
|
||||||
case "noauth":
|
case "noauth":
|
||||||
return apiServerProxyModeNoAuth
|
return ptr.To(kubetypes.APIServerProxyModeNoAuth)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unknown APISERVER_PROXY value %q", apiProxyEnv))
|
panic(fmt.Sprintf("unknown APISERVER_PROXY value %q", apiProxyEnv))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return apiServerProxyModeDisabled
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,7 @@ func main() {
|
|||||||
// 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 := parseAPIProxyMode()
|
||||||
if mode == apiServerProxyModeDisabled {
|
if mode == nil {
|
||||||
hostinfo.SetApp(kubetypes.AppOperator)
|
hostinfo.SetApp(kubetypes.AppOperator)
|
||||||
} else {
|
} else {
|
||||||
hostinfo.SetApp(kubetypes.AppInProcessAPIServerProxy)
|
hostinfo.SetApp(kubetypes.AppInProcessAPIServerProxy)
|
||||||
@ -122,8 +122,8 @@ func main() {
|
|||||||
s, tsc := initTSNet(zlog, loginServer)
|
s, tsc := initTSNet(zlog, loginServer)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
restConfig := config.GetConfigOrDie()
|
restConfig := config.GetConfigOrDie()
|
||||||
if mode != apiServerProxyModeDisabled {
|
if mode != nil {
|
||||||
ap, err := apiproxy.NewAPIServerProxy(zlog, restConfig, s, mode == apiServerProxyModeEnabled, true)
|
ap, err := apiproxy.NewAPIServerProxy(zlog, restConfig, s, *mode, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zlog.Fatalf("error creating API server proxy: %v", err)
|
zlog.Fatalf("error creating API server proxy: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -805,6 +805,10 @@ func (r *ProxyGroupReconciler) ensureConfigSecretsCreated(ctx context.Context, p
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mode := kubetypes.APIServerProxyModeAuth
|
||||||
|
if !isAuthAPIServerProxy(pg) {
|
||||||
|
mode = kubetypes.APIServerProxyModeNoAuth
|
||||||
|
}
|
||||||
cfg := conf.VersionedConfig{
|
cfg := conf.VersionedConfig{
|
||||||
Version: "v1alpha1",
|
Version: "v1alpha1",
|
||||||
ConfigV1Alpha1: &conf.ConfigV1Alpha1{
|
ConfigV1Alpha1: &conf.ConfigV1Alpha1{
|
||||||
@ -816,8 +820,8 @@ func (r *ProxyGroupReconciler) ensureConfigSecretsCreated(ctx context.Context, p
|
|||||||
// Reloadable fields.
|
// Reloadable fields.
|
||||||
Hostname: &hostname,
|
Hostname: &hostname,
|
||||||
APIServerProxy: &conf.APIServerProxyConfig{
|
APIServerProxy: &conf.APIServerProxyConfig{
|
||||||
Enabled: opt.NewBool(true),
|
Enabled: opt.NewBool(true),
|
||||||
AuthMode: opt.NewBool(isAuthAPIServerProxy(pg)),
|
Mode: &mode,
|
||||||
// The first replica is elected as the cert issuer, same
|
// The first replica is elected as the cert issuer, same
|
||||||
// as containerboot does for ingress-pg-reconciler.
|
// as containerboot does for ingress-pg-reconciler.
|
||||||
IssueCerts: opt.NewBool(i == 0),
|
IssueCerts: opt.NewBool(i == 0),
|
||||||
|
@ -1376,7 +1376,7 @@ func TestKubeAPIServerType_DoesNotOverwriteServicesConfig(t *testing.T) {
|
|||||||
Hostname: ptr.To("test-k8s-apiserver-0"),
|
Hostname: ptr.To("test-k8s-apiserver-0"),
|
||||||
APIServerProxy: &conf.APIServerProxyConfig{
|
APIServerProxy: &conf.APIServerProxyConfig{
|
||||||
Enabled: opt.NewBool(true),
|
Enabled: opt.NewBool(true),
|
||||||
AuthMode: opt.NewBool(false),
|
Mode: ptr.To(kubetypes.APIServerProxyModeNoAuth),
|
||||||
IssueCerts: opt.NewBool(true),
|
IssueCerts: opt.NewBool(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -34,6 +34,7 @@ import (
|
|||||||
apiproxy "tailscale.com/k8s-operator/api-proxy"
|
apiproxy "tailscale.com/k8s-operator/api-proxy"
|
||||||
"tailscale.com/kube/certs"
|
"tailscale.com/kube/certs"
|
||||||
"tailscale.com/kube/k8s-proxy/conf"
|
"tailscale.com/kube/k8s-proxy/conf"
|
||||||
|
"tailscale.com/kube/kubetypes"
|
||||||
klc "tailscale.com/kube/localclient"
|
klc "tailscale.com/kube/localclient"
|
||||||
"tailscale.com/kube/services"
|
"tailscale.com/kube/services"
|
||||||
"tailscale.com/kube/state"
|
"tailscale.com/kube/state"
|
||||||
@ -238,11 +239,11 @@ func run(logger *zap.SugaredLogger) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup for the API server proxy.
|
// Setup for the API server proxy.
|
||||||
authMode := true
|
mode := kubetypes.APIServerProxyModeAuth
|
||||||
if cfg.Parsed.APIServerProxy != nil && cfg.Parsed.APIServerProxy.AuthMode.EqualBool(false) {
|
if cfg.Parsed.APIServerProxy != nil && cfg.Parsed.APIServerProxy.Mode != nil {
|
||||||
authMode = false
|
mode = *cfg.Parsed.APIServerProxy.Mode
|
||||||
}
|
}
|
||||||
ap, err := apiproxy.NewAPIServerProxy(logger.Named("apiserver-proxy"), restConfig, ts, authMode, false)
|
ap, err := apiproxy.NewAPIServerProxy(logger.Named("apiserver-proxy"), restConfig, ts, mode, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error creating api server proxy: %w", err)
|
return fmt.Errorf("error creating api server proxy: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,8 @@ var (
|
|||||||
// caller's Tailscale identity and the rules defined in the tailnet ACLs.
|
// caller's Tailscale identity and the rules defined in the tailnet ACLs.
|
||||||
// - false: the proxy is started and requests are passed through to the
|
// - false: the proxy is started and requests are passed through to the
|
||||||
// Kubernetes API without any auth modifications.
|
// Kubernetes API without any auth modifications.
|
||||||
func NewAPIServerProxy(zlog *zap.SugaredLogger, restConfig *rest.Config, ts *tsnet.Server, authMode bool, https bool) (*APIServerProxy, error) {
|
func NewAPIServerProxy(zlog *zap.SugaredLogger, restConfig *rest.Config, ts *tsnet.Server, mode kubetypes.APIServerProxyMode, https bool) (*APIServerProxy, error) {
|
||||||
if !authMode {
|
if mode == kubetypes.APIServerProxyModeNoAuth {
|
||||||
restConfig = rest.AnonymousClientConfig(restConfig)
|
restConfig = rest.AnonymousClientConfig(restConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ func NewAPIServerProxy(zlog *zap.SugaredLogger, restConfig *rest.Config, ts *tsn
|
|||||||
ap := &APIServerProxy{
|
ap := &APIServerProxy{
|
||||||
log: zlog,
|
log: zlog,
|
||||||
lc: lc,
|
lc: lc,
|
||||||
authMode: authMode,
|
authMode: mode == kubetypes.APIServerProxyModeAuth,
|
||||||
https: https,
|
https: https,
|
||||||
upstreamURL: u,
|
upstreamURL: u,
|
||||||
ts: ts,
|
ts: ts,
|
||||||
@ -278,7 +278,7 @@ func (ap *APIServerProxy) sessionForProto(w http.ResponseWriter, r *http.Request
|
|||||||
Namespace: r.PathValue(namespaceNameKey),
|
Namespace: r.PathValue(namespaceNameKey),
|
||||||
Log: ap.log,
|
Log: ap.log,
|
||||||
}
|
}
|
||||||
h := ksr.New(opts)
|
h := ksr.NewHijacker(opts)
|
||||||
|
|
||||||
ap.rp.ServeHTTP(h, r.WithContext(whoIsKey.WithValue(r.Context(), who)))
|
ap.rp.ServeHTTP(h, r.WithContext(whoIsKey.WithValue(r.Context(), who)))
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ var (
|
|||||||
counterSessionRecordingsUploaded = clientmetric.NewCounter("k8s_auth_proxy_session_recordings_uploaded")
|
counterSessionRecordingsUploaded = clientmetric.NewCounter("k8s_auth_proxy_session_recordings_uploaded")
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(opts HijackerOpts) *Hijacker {
|
func NewHijacker(opts HijackerOpts) *Hijacker {
|
||||||
return &Hijacker{
|
return &Hijacker{
|
||||||
ts: opts.TS,
|
ts: opts.TS,
|
||||||
req: opts.Req,
|
req: opts.Req,
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/tailscale/hujson"
|
"github.com/tailscale/hujson"
|
||||||
|
"tailscale.com/kube/kubetypes"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/opt"
|
"tailscale.com/types/opt"
|
||||||
)
|
)
|
||||||
@ -66,10 +67,10 @@ type ConfigV1Alpha1 struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type APIServerProxyConfig struct {
|
type APIServerProxyConfig struct {
|
||||||
Enabled opt.Bool `json:",omitempty"` // Whether to enable the API Server proxy.
|
Enabled opt.Bool `json:",omitempty"` // Whether to enable the API Server proxy.
|
||||||
AuthMode opt.Bool `json:",omitempty"` // Run in auth or noauth mode.
|
Mode *kubetypes.APIServerProxyMode `json:",omitempty"` // "auth" or "noauth" mode.
|
||||||
ServiceName *tailcfg.ServiceName `json:",omitempty"` // Name of the Tailscale Service to advertise.
|
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.
|
IssueCerts opt.Bool `json:",omitempty"` // Whether this replica should issue TLS certs for the Tailscale Service.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load reads and parses the config file at the provided path on disk.
|
// Load reads and parses the config file at the provided path on disk.
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
package kubetypes
|
package kubetypes
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Hostinfo App values for the Tailscale Kubernetes Operator components.
|
// Hostinfo App values for the Tailscale Kubernetes Operator components.
|
||||||
AppOperator = "k8s-operator"
|
AppOperator = "k8s-operator"
|
||||||
@ -59,5 +61,24 @@ const (
|
|||||||
LabelSecretTypeState = "state"
|
LabelSecretTypeState = "state"
|
||||||
LabelSecretTypeCerts = "certs"
|
LabelSecretTypeCerts = "certs"
|
||||||
|
|
||||||
KubeAPIServerConfigFile = "config.hujson"
|
KubeAPIServerConfigFile = "config.hujson"
|
||||||
|
APIServerProxyModeAuth APIServerProxyMode = "auth"
|
||||||
|
APIServerProxyModeNoAuth APIServerProxyMode = "noauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// APIServerProxyMode specifies whether the API server proxy will add
|
||||||
|
// impersonation headers to requests based on the caller's Tailscale identity.
|
||||||
|
// May be "auth" or "noauth".
|
||||||
|
type APIServerProxyMode string
|
||||||
|
|
||||||
|
func (a *APIServerProxyMode) UnmarshalJSON(data []byte) error {
|
||||||
|
switch string(data) {
|
||||||
|
case `"auth"`:
|
||||||
|
*a = APIServerProxyModeAuth
|
||||||
|
case `"noauth"`:
|
||||||
|
*a = APIServerProxyModeNoAuth
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown APIServerProxyMode %q", data)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
42
kube/kubetypes/types_test.go
Normal file
42
kube/kubetypes/types_test.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package kubetypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnmarshalAPIServerProxyMode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
data string
|
||||||
|
expected APIServerProxyMode
|
||||||
|
}{
|
||||||
|
{data: `{"mode":"auth"}`, expected: APIServerProxyModeAuth},
|
||||||
|
{data: `{"mode":"noauth"}`, expected: APIServerProxyModeNoAuth},
|
||||||
|
{data: `{"mode":""}`, expected: ""},
|
||||||
|
{data: `{"mode":"Auth"}`, expected: ""},
|
||||||
|
{data: `{"mode":"unknown"}`, expected: ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
var s struct {
|
||||||
|
Mode *APIServerProxyMode `json:",omitempty"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal([]byte(tc.data), &s)
|
||||||
|
if tc.expected == "" {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected error for %q, got none", tc.data)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error for %q: %v", tc.data, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if *s.Mode != tc.expected {
|
||||||
|
t.Errorf("for %q expected %q, got %q", tc.data, tc.expected, *s.Mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user