diff --git a/cmd/mcp/notify.go b/cmd/mcp/notify.go index e65d765f5..cad10a690 100644 --- a/cmd/mcp/notify.go +++ b/cmd/mcp/notify.go @@ -36,6 +36,7 @@ func streamLogsToSession( if line == "" { continue } + //nolint:staticcheck // SA1019: deprecated per SEP-2577; no replacement in go-sdk v1.7.0 _ = session.Log(ctx, &sdkmcp.LoggingMessageParams{ Level: "info", Logger: "devsy", diff --git a/go.mod b/go.mod index e83b6038f..e9139c480 100644 --- a/go.mod +++ b/go.mod @@ -77,6 +77,7 @@ require ( k8s.io/klog/v2 v2.140.0 k8s.io/kube-aggregator v0.36.3 k8s.io/kubectl v0.36.3 + k8s.io/streaming v0.36.3 k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 mvdan.cc/sh/v3 v3.13.1 sigs.k8s.io/controller-runtime v0.24.1 @@ -319,7 +320,6 @@ require ( k8s.io/component-base v0.36.3 // indirect k8s.io/kube-openapi v0.0.0-20260624041617-8f3fa4921821 // indirect k8s.io/metrics v0.36.3 // indirect - k8s.io/streaming v0.36.3 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect diff --git a/pkg/agent/inject.go b/pkg/agent/inject.go index 667a976d5..60bed5dc3 100644 --- a/pkg/agent/inject.go +++ b/pkg/agent/inject.go @@ -38,6 +38,7 @@ type InjectOptions struct { // Ctx is the context for the injection operation. Required. Ctx context.Context // Exec is the function used to execute commands on the remote machine. Required. + //nolint:staticcheck // SA1019: legacy shell injection path, retained until callers migrate to AgentDelivery Exec inject.ExecFunc // IsLocal indicates if the injection target is the local machine. @@ -228,6 +229,7 @@ func injectAgent(ctx *injectContext) error { binaryLoader := createBinaryLoader(ctx) scriptParams := buildScriptParams(ctx) + //nolint:staticcheck // SA1019: legacy shell injection path, retained until callers migrate to AgentDelivery wasExecuted, err := inject.Inject(inject.InjectOptions{ Ctx: opts.Ctx, Exec: opts.Exec, @@ -352,6 +354,7 @@ func (vc *versionChecker) buildExistsCheck(agentPath string) string { agentPath, agentPath, vc.remoteVersion) } +//nolint:staticcheck // SA1019: legacy shell injection path, retained until callers migrate func (vc *versionChecker) detectRemoteAgentVersion( ctx context.Context, exec inject.ExecFunc, diff --git a/pkg/driver/kubernetes/client.go b/pkg/driver/kubernetes/client.go index 904e8c236..64b311fa4 100644 --- a/pkg/driver/kubernetes/client.go +++ b/pkg/driver/kubernetes/client.go @@ -8,12 +8,12 @@ import ( "os" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/remotecommand" + "k8s.io/streaming/pkg/httpstream" ) type Client struct { diff --git a/pkg/ts/workspace_server.go b/pkg/ts/workspace_server.go index 469c8826b..2836db2aa 100644 --- a/pkg/ts/workspace_server.go +++ b/pkg/ts/workspace_server.go @@ -326,13 +326,14 @@ func (s *WorkspaceServer) gitCredentialsHandler( return } - // Build the reverse proxy with a custom Director. - proxy := httputil.NewSingleHostReverseProxy(parsedURL) - proxy.Director = func(req *http.Request) { - dest := *parsedURL - req.URL = &dest - req.Host = dest.Host - req.Header.Set("Authorization", "Bearer "+s.config.AccessKey) + proxy := &httputil.ReverseProxy{ + Rewrite: func(pr *httputil.ProxyRequest) { + dest := *parsedURL + pr.Out.URL = &dest + pr.Out.Host = dest.Host + pr.Out.Header.Set("Authorization", "Bearer "+s.config.AccessKey) + addForwardedFor(pr) + }, } proxy.Transport = transport proxy.ServeHTTP(w, r) @@ -368,13 +369,14 @@ func (s *WorkspaceServer) dockerCredentialsHandler( return } - // Build the reverse proxy with a custom Director. - proxy := httputil.NewSingleHostReverseProxy(parsedURL) - proxy.Director = func(req *http.Request) { - dest := *parsedURL - req.URL = &dest - req.Host = dest.Host - req.Header.Set("Authorization", "Bearer "+s.config.AccessKey) + proxy := &httputil.ReverseProxy{ + Rewrite: func(pr *httputil.ProxyRequest) { + dest := *parsedURL + pr.Out.URL = &dest + pr.Out.Host = dest.Host + pr.Out.Header.Set("Authorization", "Bearer "+s.config.AccessKey) + addForwardedFor(pr) + }, } proxy.Transport = transport proxy.ServeHTTP(w, r) @@ -411,16 +413,17 @@ func (s *WorkspaceServer) httpPortForwardHandler(w http.ResponseWriter, r *http. parsedURL.Host = "127.0.0.1:" + targetPort log.Debugf("httpPortForwardHandler: final target URL=%s", parsedURL.String()) - // Build the reverse proxy with a custom Director. - proxy := httputil.NewSingleHostReverseProxy(parsedURL) - proxy.Director = func(req *http.Request) { - dest := *parsedURL - req.URL = &dest - req.Host = dest.Host - // Remove custom headers so they are not forwarded. - req.Header.Del("X-Loft-Forward-Port") - req.Header.Del("X-Loft-Forward-Url") - req.Header.Del("X-Loft-Forward-Authorization") + proxy := &httputil.ReverseProxy{ + Rewrite: func(pr *httputil.ProxyRequest) { + dest := *parsedURL + pr.Out.URL = &dest + pr.Out.Host = dest.Host + // Remove custom headers so they are not forwarded. + pr.Out.Header.Del("X-Loft-Forward-Port") + pr.Out.Header.Del("X-Loft-Forward-Url") + pr.Out.Header.Del("X-Loft-Forward-Authorization") + addForwardedFor(pr) + }, } proxy.Transport = http.DefaultTransport @@ -432,6 +435,22 @@ func (s *WorkspaceServer) httpPortForwardHandler(w http.ResponseWriter, r *http. proxy.ServeHTTP(w, r) } +// addForwardedFor sets X-Forwarded-For on the outbound proxy request. +func addForwardedFor(pr *httputil.ProxyRequest) { + clientIP, _, err := net.SplitHostPort(pr.In.RemoteAddr) + if err != nil { + return + } + prior, ok := pr.In.Header["X-Forwarded-For"] + omit := ok && prior == nil + if len(prior) > 0 { + clientIP = strings.Join(prior, ", ") + ", " + clientIP + } + if !omit { + pr.Out.Header.Set("X-Forwarded-For", clientIP) + } +} + // handleSSHConnections continuously accepts SSH connections and handles each one. func (s *WorkspaceServer) handleSSHConnections(ctx context.Context, listener net.Listener) { for {