mirror of
https://github.com/tailscale/tailscale.git
synced 2026-06-03 21:01:54 +08:00
k8s-operator/api-proxy: resolve capmaps for services
This commit modifies the k8s api server proxy to use the local `WhoIsForService` method when actively called behind a tailscale service. The proxy predates services and was originally written to act as a single instance running in-process alongside the operator. Since then services have enabled us to provide a HA api server proxy. Recently, app caps were applied to tailscale services meaning our api server proxy also needs to take those into account to avoid confused deputy type scenarios. Closes: https://github.com/tailscale/corp/issues/42001 Signed-off-by: David Bond <davidsbond93@gmail.com>
This commit is contained in:
parent
95d874e9b4
commit
166d59f739
@ -134,7 +134,7 @@ func main() {
|
||||
defer s.Close()
|
||||
restConfig := config.GetConfigOrDie()
|
||||
if mode != nil {
|
||||
ap, err := apiproxy.NewAPIServerProxy(zlog, restConfig, s, *mode, true)
|
||||
ap, err := apiproxy.NewAPIServerProxy(zlog, restConfig, s, *mode, true, "")
|
||||
if err != nil {
|
||||
zlog.Fatalf("error creating API server proxy: %v", err)
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/utils/strings/slices"
|
||||
|
||||
"tailscale.com/client/local"
|
||||
"tailscale.com/cmd/k8s-proxy/internal/config"
|
||||
"tailscale.com/health"
|
||||
@ -374,7 +375,7 @@ func run(logger *zap.SugaredLogger) error {
|
||||
if cfg.Parsed.APIServerProxy != nil && cfg.Parsed.APIServerProxy.Mode != nil {
|
||||
mode = *cfg.Parsed.APIServerProxy.Mode
|
||||
}
|
||||
ap, err := apiproxy.NewAPIServerProxy(logger.Named("apiserver-proxy"), restConfig, ts, mode, false)
|
||||
ap, err := apiproxy.NewAPIServerProxy(logger.Named("apiserver-proxy"), restConfig, ts, mode, false, apiServerProxyService(cfg))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating api server proxy: %w", err)
|
||||
}
|
||||
@ -482,7 +483,7 @@ func apiServerProxyService(cfg *conf.Config) tailcfg.ServiceName {
|
||||
cfg.Parsed.APIServerProxy.Enabled.EqualBool(true) &&
|
||||
cfg.Parsed.APIServerProxy.ServiceName != nil &&
|
||||
*cfg.Parsed.APIServerProxy.ServiceName != "" {
|
||||
return tailcfg.ServiceName(*cfg.Parsed.APIServerProxy.ServiceName)
|
||||
return *cfg.Parsed.APIServerProxy.ServiceName
|
||||
}
|
||||
|
||||
return ""
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/transport"
|
||||
|
||||
"tailscale.com/client/local"
|
||||
"tailscale.com/client/tailscale/apitype"
|
||||
ksr "tailscale.com/k8s-operator/sessionrecording"
|
||||
@ -54,7 +55,7 @@
|
||||
// caller's Tailscale identity and the rules defined in the tailnet ACLs.
|
||||
// - false: the proxy is started and requests are passed through to the
|
||||
// Kubernetes API without any auth modifications.
|
||||
func NewAPIServerProxy(zlog *zap.SugaredLogger, restConfig *rest.Config, ts *tsnet.Server, mode kubetypes.APIServerProxyMode, https bool) (*APIServerProxy, error) {
|
||||
func NewAPIServerProxy(zlog *zap.SugaredLogger, restConfig *rest.Config, ts *tsnet.Server, mode kubetypes.APIServerProxyMode, https bool, svcName tailcfg.ServiceName) (*APIServerProxy, error) {
|
||||
if mode == kubetypes.APIServerProxyModeNoAuth {
|
||||
restConfig = rest.AnonymousClientConfig(restConfig)
|
||||
}
|
||||
@ -94,6 +95,7 @@ func NewAPIServerProxy(zlog *zap.SugaredLogger, restConfig *rest.Config, ts *tsn
|
||||
lc: lc,
|
||||
authMode: mode == kubetypes.APIServerProxyModeAuth,
|
||||
https: https,
|
||||
svcName: svcName,
|
||||
upstreamURL: u,
|
||||
ts: ts,
|
||||
sendEventFunc: sessionrecording.SendEvent,
|
||||
@ -196,6 +198,7 @@ type APIServerProxy struct {
|
||||
|
||||
authMode bool // Whether to run with impersonation using caller's tailnet identity.
|
||||
https bool // Whether to serve on https for the device hostname; true for k8s-operator, false (and localhost) for k8s-proxy.
|
||||
svcName tailcfg.ServiceName
|
||||
ts *tsnet.Server
|
||||
hs *http.Server
|
||||
upstreamURL *url.URL
|
||||
@ -470,7 +473,7 @@ func (ap *APIServerProxy) addImpersonationHeadersAsRequired(r *http.Request) {
|
||||
}
|
||||
|
||||
func (ap *APIServerProxy) whoIs(r *http.Request) (*apitype.WhoIsResponse, error) {
|
||||
who, remoteErr := ap.lc.WhoIs(r.Context(), r.RemoteAddr)
|
||||
who, remoteErr := ap.whoIsAddr(r.Context(), r.RemoteAddr)
|
||||
if remoteErr == nil {
|
||||
ap.log.Debugf("WhoIs from remote addr: %s", r.RemoteAddr)
|
||||
return who, nil
|
||||
@ -479,7 +482,7 @@ func (ap *APIServerProxy) whoIs(r *http.Request) (*apitype.WhoIsResponse, error)
|
||||
var fwdErr error
|
||||
fwdFor := r.Header.Get("X-Forwarded-For")
|
||||
if fwdFor != "" && !ap.https {
|
||||
who, fwdErr = ap.lc.WhoIs(r.Context(), fwdFor)
|
||||
who, fwdErr = ap.whoIsAddr(r.Context(), fwdFor)
|
||||
if fwdErr == nil {
|
||||
ap.log.Debugf("WhoIs from X-Forwarded-For header: %s", fwdFor)
|
||||
return who, nil
|
||||
@ -489,6 +492,14 @@ func (ap *APIServerProxy) whoIs(r *http.Request) (*apitype.WhoIsResponse, error)
|
||||
return nil, errors.Join(remoteErr, fwdErr)
|
||||
}
|
||||
|
||||
func (ap *APIServerProxy) whoIsAddr(ctx context.Context, addr string) (*apitype.WhoIsResponse, error) {
|
||||
if ap.svcName != "" {
|
||||
return ap.lc.WhoIsForService(ctx, addr, ap.svcName)
|
||||
}
|
||||
|
||||
return ap.lc.WhoIs(ctx, addr)
|
||||
}
|
||||
|
||||
func (ap *APIServerProxy) authError(w http.ResponseWriter, err error) {
|
||||
ap.log.Errorf("failed to authenticate caller: %v", err)
|
||||
http.Error(w, "failed to authenticate caller", http.StatusInternalServerError)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user