diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index 30493a3da..818025bdf 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -451,7 +451,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server { snapshotService.Start() - proxyManager.NewProxyFactory(dataStore, signatureService, reverseTunnelService, dockerClientFactory, kubernetesClientFactory, kubernetesTokenCacheManager, gitService, snapshotService) + proxyManager.NewProxyFactory(dataStore, signatureService, reverseTunnelService, dockerClientFactory, kubernetesClientFactory, kubernetesTokenCacheManager, gitService, snapshotService, jwtService) helmPackageManager, err := initHelmPackageManager() if err != nil { diff --git a/api/http/handler/endpoints/endpoint_delete_test.go b/api/http/handler/endpoints/endpoint_delete_test.go index 559c1b680..73b2b878b 100644 --- a/api/http/handler/endpoints/endpoint_delete_test.go +++ b/api/http/handler/endpoints/endpoint_delete_test.go @@ -22,7 +22,7 @@ func TestEndpointDeleteEdgeGroupsConcurrently(t *testing.T) { handler := NewHandler(testhelpers.NewTestRequestBouncer()) handler.DataStore = store handler.ProxyManager = proxy.NewManager(nil) - handler.ProxyManager.NewProxyFactory(nil, nil, nil, nil, nil, nil, nil, nil) + handler.ProxyManager.NewProxyFactory(nil, nil, nil, nil, nil, nil, nil, nil, nil) // Create all the environments and add them to the same edge group diff --git a/api/http/proxy/factory/factory.go b/api/http/proxy/factory/factory.go index 28d05dec5..b45629630 100644 --- a/api/http/proxy/factory/factory.go +++ b/api/http/proxy/factory/factory.go @@ -24,11 +24,12 @@ type ( kubernetesTokenCacheManager *kubernetes.TokenCacheManager gitService portainer.GitService snapshotService portainer.SnapshotService + jwtService portainer.JWTService } ) // NewProxyFactory returns a pointer to a new instance of a ProxyFactory -func NewProxyFactory(dataStore dataservices.DataStore, signatureService portainer.DigitalSignatureService, tunnelService portainer.ReverseTunnelService, clientFactory *dockerclient.ClientFactory, kubernetesClientFactory *cli.ClientFactory, kubernetesTokenCacheManager *kubernetes.TokenCacheManager, gitService portainer.GitService, snapshotService portainer.SnapshotService) *ProxyFactory { +func NewProxyFactory(dataStore dataservices.DataStore, signatureService portainer.DigitalSignatureService, tunnelService portainer.ReverseTunnelService, clientFactory *dockerclient.ClientFactory, kubernetesClientFactory *cli.ClientFactory, kubernetesTokenCacheManager *kubernetes.TokenCacheManager, gitService portainer.GitService, snapshotService portainer.SnapshotService, jwtService portainer.JWTService) *ProxyFactory { return &ProxyFactory{ dataStore: dataStore, signatureService: signatureService, @@ -38,6 +39,7 @@ func NewProxyFactory(dataStore dataservices.DataStore, signatureService portaine kubernetesTokenCacheManager: kubernetesTokenCacheManager, gitService: gitService, snapshotService: snapshotService, + jwtService: jwtService, } } diff --git a/api/http/proxy/factory/kubernetes.go b/api/http/proxy/factory/kubernetes.go index eceee181a..ea08467e5 100644 --- a/api/http/proxy/factory/kubernetes.go +++ b/api/http/proxy/factory/kubernetes.go @@ -38,7 +38,7 @@ func (factory *ProxyFactory) newKubernetesLocalProxy(endpoint *portainer.Endpoin return nil, err } - transport, err := kubernetes.NewLocalTransport(tokenManager, endpoint, factory.kubernetesClientFactory, factory.dataStore) + transport, err := kubernetes.NewLocalTransport(tokenManager, endpoint, factory.kubernetesClientFactory, factory.dataStore, factory.jwtService) if err != nil { return nil, err } @@ -74,7 +74,7 @@ func (factory *ProxyFactory) newKubernetesEdgeHTTPProxy(endpoint *portainer.Endp endpointURL.Scheme = "http" proxy := NewSingleHostReverseProxyWithHostHeader(endpointURL) - proxy.Transport = kubernetes.NewEdgeTransport(factory.dataStore, factory.signatureService, factory.reverseTunnelService, endpoint, tokenManager, factory.kubernetesClientFactory) + proxy.Transport = kubernetes.NewEdgeTransport(factory.dataStore, factory.signatureService, factory.reverseTunnelService, endpoint, tokenManager, factory.kubernetesClientFactory, factory.jwtService) return proxy, nil } @@ -105,7 +105,7 @@ func (factory *ProxyFactory) newKubernetesAgentHTTPSProxy(endpoint *portainer.En } proxy := NewSingleHostReverseProxyWithHostHeader(remoteURL) - proxy.Transport = kubernetes.NewAgentTransport(factory.signatureService, tlsConfig, tokenManager, endpoint, factory.kubernetesClientFactory, factory.dataStore) + proxy.Transport = kubernetes.NewAgentTransport(factory.signatureService, tlsConfig, tokenManager, endpoint, factory.kubernetesClientFactory, factory.dataStore, factory.jwtService) return proxy, nil } diff --git a/api/http/proxy/factory/kubernetes/agent_transport.go b/api/http/proxy/factory/kubernetes/agent_transport.go index b6ab548ae..b127b85fd 100644 --- a/api/http/proxy/factory/kubernetes/agent_transport.go +++ b/api/http/proxy/factory/kubernetes/agent_transport.go @@ -16,7 +16,7 @@ type agentTransport struct { } // NewAgentTransport returns a new transport that can be used to send signed requests to a Portainer agent -func NewAgentTransport(signatureService portainer.DigitalSignatureService, tlsConfig *tls.Config, tokenManager *tokenManager, endpoint *portainer.Endpoint, k8sClientFactory *cli.ClientFactory, dataStore dataservices.DataStore) *agentTransport { +func NewAgentTransport(signatureService portainer.DigitalSignatureService, tlsConfig *tls.Config, tokenManager *tokenManager, endpoint *portainer.Endpoint, k8sClientFactory *cli.ClientFactory, dataStore dataservices.DataStore, jwtService portainer.JWTService) *agentTransport { transport := &agentTransport{ baseTransport: newBaseTransport( &http.Transport{ @@ -26,6 +26,7 @@ func NewAgentTransport(signatureService portainer.DigitalSignatureService, tlsCo endpoint, k8sClientFactory, dataStore, + jwtService, ), signatureService: signatureService, } diff --git a/api/http/proxy/factory/kubernetes/edge_transport.go b/api/http/proxy/factory/kubernetes/edge_transport.go index 4eed6934a..73946114e 100644 --- a/api/http/proxy/factory/kubernetes/edge_transport.go +++ b/api/http/proxy/factory/kubernetes/edge_transport.go @@ -16,7 +16,7 @@ type edgeTransport struct { } // NewAgentTransport returns a new transport that can be used to send signed requests to a Portainer Edge agent -func NewEdgeTransport(dataStore dataservices.DataStore, signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, endpoint *portainer.Endpoint, tokenManager *tokenManager, k8sClientFactory *cli.ClientFactory) *edgeTransport { +func NewEdgeTransport(dataStore dataservices.DataStore, signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, endpoint *portainer.Endpoint, tokenManager *tokenManager, k8sClientFactory *cli.ClientFactory, jwtService portainer.JWTService) *edgeTransport { transport := &edgeTransport{ reverseTunnelService: reverseTunnelService, signatureService: signatureService, @@ -26,6 +26,7 @@ func NewEdgeTransport(dataStore dataservices.DataStore, signatureService portain endpoint, k8sClientFactory, dataStore, + jwtService, ), } diff --git a/api/http/proxy/factory/kubernetes/local_transport.go b/api/http/proxy/factory/kubernetes/local_transport.go index 4ae4082d9..6fe255ff6 100644 --- a/api/http/proxy/factory/kubernetes/local_transport.go +++ b/api/http/proxy/factory/kubernetes/local_transport.go @@ -14,7 +14,7 @@ type localTransport struct { } // NewLocalTransport returns a new transport that can be used to send requests to the local Kubernetes API -func NewLocalTransport(tokenManager *tokenManager, endpoint *portainer.Endpoint, k8sClientFactory *cli.ClientFactory, dataStore dataservices.DataStore) (*localTransport, error) { +func NewLocalTransport(tokenManager *tokenManager, endpoint *portainer.Endpoint, k8sClientFactory *cli.ClientFactory, dataStore dataservices.DataStore, jwtService portainer.JWTService) (*localTransport, error) { config, err := crypto.CreateTLSConfigurationFromBytes(nil, nil, nil, true, true) if err != nil { return nil, err @@ -29,6 +29,7 @@ func NewLocalTransport(tokenManager *tokenManager, endpoint *portainer.Endpoint, endpoint, k8sClientFactory, dataStore, + jwtService, ), } diff --git a/api/http/proxy/factory/kubernetes/pods.go b/api/http/proxy/factory/kubernetes/pods.go index 6c36e079a..a2e5f1860 100644 --- a/api/http/proxy/factory/kubernetes/pods.go +++ b/api/http/proxy/factory/kubernetes/pods.go @@ -2,12 +2,18 @@ package kubernetes import ( "net/http" + "strings" ) -func (transport *baseTransport) proxyPodsRequest(request *http.Request, namespace, requestPath string) (*http.Response, error) { +func (transport *baseTransport) proxyPodsRequest(request *http.Request, namespace string) (*http.Response, error) { if request.Method == http.MethodDelete { transport.refreshRegistry(request, namespace) } + if request.Method == http.MethodPost && strings.Contains(request.URL.Path, "/exec") { + if err := transport.addTokenForExec(request); err != nil { + return nil, err + } + } return transport.executeKubernetesRequest(request) } diff --git a/api/http/proxy/factory/kubernetes/transport.go b/api/http/proxy/factory/kubernetes/transport.go index ddab7a096..b4d06bcce 100644 --- a/api/http/proxy/factory/kubernetes/transport.go +++ b/api/http/proxy/factory/kubernetes/transport.go @@ -26,15 +26,17 @@ type baseTransport struct { endpoint *portainer.Endpoint k8sClientFactory *cli.ClientFactory dataStore dataservices.DataStore + jwtService portainer.JWTService } -func newBaseTransport(httpTransport *http.Transport, tokenManager *tokenManager, endpoint *portainer.Endpoint, k8sClientFactory *cli.ClientFactory, dataStore dataservices.DataStore) *baseTransport { +func newBaseTransport(httpTransport *http.Transport, tokenManager *tokenManager, endpoint *portainer.Endpoint, k8sClientFactory *cli.ClientFactory, dataStore dataservices.DataStore, jwtService portainer.JWTService) *baseTransport { return &baseTransport{ httpTransport: httpTransport, tokenManager: tokenManager, endpoint: endpoint, k8sClientFactory: k8sClientFactory, dataStore: dataStore, + jwtService: jwtService, } } @@ -82,7 +84,7 @@ func (transport *baseTransport) proxyNamespacedRequest(request *http.Request, fu switch { case strings.HasPrefix(requestPath, "pods"): - return transport.proxyPodsRequest(request, namespace, requestPath) + return transport.proxyPodsRequest(request, namespace) case strings.HasPrefix(requestPath, "deployments"): return transport.proxyDeploymentsRequest(request, namespace, requestPath) case requestPath == "" && request.Method == "DELETE": @@ -92,6 +94,23 @@ func (transport *baseTransport) proxyNamespacedRequest(request *http.Request, fu } } +// addTokenForExec injects a kubeconfig token into the request header +// this is only used with kubeconfig for kubectl exec requests +func (transport *baseTransport) addTokenForExec(request *http.Request) error { + tokenData, err := security.RetrieveTokenData(request) + if err != nil { + return err + } + + token, err := transport.jwtService.GenerateTokenForKubeconfig(tokenData) + if err != nil { + return err + } + + request.Header.Set("Authorization", "Bearer "+token) + return nil +} + func (transport *baseTransport) executeKubernetesRequest(request *http.Request) (*http.Response, error) { resp, err := transport.httpTransport.RoundTrip(request) diff --git a/api/http/proxy/factory/kubernetes/transport_test.go b/api/http/proxy/factory/kubernetes/transport_test.go new file mode 100644 index 000000000..713714d93 --- /dev/null +++ b/api/http/proxy/factory/kubernetes/transport_test.go @@ -0,0 +1,359 @@ +package kubernetes + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + portainer "github.com/portainer/portainer/api" + "github.com/portainer/portainer/api/datastore" + "github.com/portainer/portainer/api/http/security" + "github.com/portainer/portainer/api/jwt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// MockJWTService implements portainer.JWTService for testing +type MockJWTService struct { + generateTokenFunc func(data *portainer.TokenData) (string, error) +} + +func (m *MockJWTService) GenerateToken(data *portainer.TokenData) (string, time.Time, error) { + if m.generateTokenFunc != nil { + token, err := m.generateTokenFunc(data) + return token, time.Now().Add(24 * time.Hour), err + } + return "mock-token", time.Now().Add(24 * time.Hour), nil +} + +func (m *MockJWTService) GenerateTokenForKubeconfig(data *portainer.TokenData) (string, error) { + if m.generateTokenFunc != nil { + return m.generateTokenFunc(data) + } + return "mock-kubeconfig-token", nil +} + +func (m *MockJWTService) ParseAndVerifyToken(token string) (*portainer.TokenData, string, time.Time, error) { + return &portainer.TokenData{ID: 1, Username: "mock", Role: portainer.AdministratorRole}, "mock-id", time.Now().Add(24 * time.Hour), nil +} + +func (m *MockJWTService) SetUserSessionDuration(userSessionDuration time.Duration) { + // Mock implementation - not used in tests +} + +func TestBaseTransport_AddTokenForExec(t *testing.T) { + // Setup test store and JWT service + _, store := datastore.MustNewTestStore(t, true, false) + + // Create test users + adminUser := &portainer.User{ + ID: 1, + Username: "admin", + Role: portainer.AdministratorRole, + } + err := store.User().Create(adminUser) + require.NoError(t, err) + + standardUser := &portainer.User{ + ID: 2, + Username: "standard", + Role: portainer.StandardUserRole, + } + err = store.User().Create(standardUser) + require.NoError(t, err) + + // Create JWT service + jwtService, err := jwt.NewService("24h", store) + require.NoError(t, err) + + // Create base transport + transport := &baseTransport{ + jwtService: jwtService, + } + + tests := []struct { + name string + tokenData *portainer.TokenData + setupRequest func(*http.Request) *http.Request + expectError bool + errorMsg string + expectPanic bool + verifyResponse func(*testing.T, *http.Request, *portainer.TokenData) + }{ + { + name: "admin user - successful token generation", + tokenData: &portainer.TokenData{ + ID: adminUser.ID, + Username: adminUser.Username, + Role: adminUser.Role, + }, + setupRequest: func(req *http.Request) *http.Request { + return req.WithContext(security.StoreTokenData(req, &portainer.TokenData{ + ID: adminUser.ID, + Username: adminUser.Username, + Role: adminUser.Role, + })) + }, + expectError: false, + verifyResponse: func(t *testing.T, req *http.Request, tokenData *portainer.TokenData) { + authHeader := req.Header.Get("Authorization") + assert.NotEmpty(t, authHeader) + assert.True(t, strings.HasPrefix(authHeader, "Bearer ")) + + token := authHeader[7:] // Remove "Bearer " prefix + parsedTokenData, _, _, err := jwtService.ParseAndVerifyToken(token) + assert.NoError(t, err) + assert.Equal(t, tokenData.ID, parsedTokenData.ID) + assert.Equal(t, tokenData.Username, parsedTokenData.Username) + assert.Equal(t, tokenData.Role, parsedTokenData.Role) + }, + }, + { + name: "standard user - successful token generation", + tokenData: &portainer.TokenData{ + ID: standardUser.ID, + Username: standardUser.Username, + Role: standardUser.Role, + }, + setupRequest: func(req *http.Request) *http.Request { + return req.WithContext(security.StoreTokenData(req, &portainer.TokenData{ + ID: standardUser.ID, + Username: standardUser.Username, + Role: standardUser.Role, + })) + }, + expectError: false, + verifyResponse: func(t *testing.T, req *http.Request, tokenData *portainer.TokenData) { + authHeader := req.Header.Get("Authorization") + assert.NotEmpty(t, authHeader) + assert.True(t, strings.HasPrefix(authHeader, "Bearer ")) + + token := authHeader[7:] // Remove "Bearer " prefix + parsedTokenData, _, _, err := jwtService.ParseAndVerifyToken(token) + assert.NoError(t, err) + assert.Equal(t, tokenData.ID, parsedTokenData.ID) + assert.Equal(t, tokenData.Username, parsedTokenData.Username) + assert.Equal(t, tokenData.Role, parsedTokenData.Role) + }, + }, + { + name: "request without token data in context", + tokenData: nil, + setupRequest: func(req *http.Request) *http.Request { + return req // Don't add token data to context + }, + expectError: true, + errorMsg: "Unable to find JWT data in request context", + }, + { + name: "request with nil token data", + tokenData: nil, + setupRequest: func(req *http.Request) *http.Request { + return req.WithContext(security.StoreTokenData(req, nil)) + }, + expectPanic: true, + }, + { + name: "JWT service failure", + tokenData: &portainer.TokenData{ + ID: 1, + Username: "test", + Role: portainer.AdministratorRole, + }, + setupRequest: func(req *http.Request) *http.Request { + return req.WithContext(security.StoreTokenData(req, &portainer.TokenData{ + ID: 1, + Username: "test", + Role: portainer.AdministratorRole, + })) + }, + expectPanic: true, + }, + { + name: "verify authorization header format", + tokenData: &portainer.TokenData{ + ID: adminUser.ID, + Username: adminUser.Username, + Role: adminUser.Role, + }, + setupRequest: func(req *http.Request) *http.Request { + return req.WithContext(security.StoreTokenData(req, &portainer.TokenData{ + ID: adminUser.ID, + Username: adminUser.Username, + Role: adminUser.Role, + })) + }, + expectError: false, + verifyResponse: func(t *testing.T, req *http.Request, tokenData *portainer.TokenData) { + authHeader := req.Header.Get("Authorization") + assert.NotEmpty(t, authHeader) + assert.True(t, strings.HasPrefix(authHeader, "Bearer ")) + + token := authHeader[7:] // Remove "Bearer " prefix + assert.NotEmpty(t, token) + assert.Greater(t, len(token), 0, "Token should not be empty") + }, + }, + { + name: "verify header is overwritten on subsequent calls", + tokenData: &portainer.TokenData{ + ID: adminUser.ID, + Username: adminUser.Username, + Role: adminUser.Role, + }, + setupRequest: func(req *http.Request) *http.Request { + req = req.WithContext(security.StoreTokenData(req, &portainer.TokenData{ + ID: adminUser.ID, + Username: adminUser.Username, + Role: adminUser.Role, + })) + // Set an existing Authorization header + req.Header.Set("Authorization", "Bearer old-token") + return req + }, + expectError: false, + verifyResponse: func(t *testing.T, req *http.Request, tokenData *portainer.TokenData) { + authHeader := req.Header.Get("Authorization") + assert.NotEqual(t, "Bearer old-token", authHeader) + assert.True(t, strings.HasPrefix(authHeader, "Bearer ")) + + token := authHeader[7:] // Remove "Bearer " prefix + parsedTokenData, _, _, err := jwtService.ParseAndVerifyToken(token) + assert.NoError(t, err) + assert.Equal(t, tokenData.ID, parsedTokenData.ID) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create request + request := httptest.NewRequest("GET", "/", nil) + request = tt.setupRequest(request) + + // Determine which transport to use based on test case + var testTransport *baseTransport + if tt.name == "JWT service failure" { + testTransport = &baseTransport{ + jwtService: nil, + } + } else { + testTransport = transport + } + + // Call the function + if tt.expectPanic { + assert.Panics(t, func() { + _ = testTransport.addTokenForExec(request) + }) + return + } + + err := testTransport.addTokenForExec(request) + + // Check results + if tt.expectError { + assert.Error(t, err) + if tt.errorMsg != "" { + assert.Contains(t, err.Error(), tt.errorMsg) + } + } else { + assert.NoError(t, err) + if tt.verifyResponse != nil { + tt.verifyResponse(t, request, tt.tokenData) + } + } + }) + } +} + +func TestBaseTransport_AddTokenForExec_Integration(t *testing.T) { + // Create a test HTTP server to capture requests + var capturedRequest *http.Request + var capturedHeaders http.Header + + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + capturedRequest = r + capturedHeaders = r.Header.Clone() + w.WriteHeader(http.StatusOK) + w.Write([]byte("success")) + })) + defer testServer.Close() + + // Create mock JWT service + mockJWTService := &MockJWTService{ + generateTokenFunc: func(data *portainer.TokenData) (string, error) { + return "mock-token-" + data.Username, nil + }, + } + + // Create base transport + transport := &baseTransport{ + httpTransport: &http.Transport{}, + jwtService: mockJWTService, + } + + tests := []struct { + name string + tokenData *portainer.TokenData + requestPath string + expectedToken string + }{ + { + name: "admin user exec request", + tokenData: &portainer.TokenData{ + ID: 1, + Username: "admin", + Role: portainer.AdministratorRole, + }, + requestPath: "/api/endpoints/1/kubernetes/api/v1/namespaces/default/pods/test-pod/exec", + expectedToken: "mock-token-admin", + }, + { + name: "standard user exec request", + tokenData: &portainer.TokenData{ + ID: 2, + Username: "standard", + Role: portainer.StandardUserRole, + }, + requestPath: "/api/endpoints/1/kubernetes/api/v1/namespaces/default/pods/test-pod/exec", + expectedToken: "mock-token-standard", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Reset captured data + capturedRequest = nil + capturedHeaders = nil + + // Create request to the test server + request, err := http.NewRequest("POST", testServer.URL+tt.requestPath, strings.NewReader("")) + require.NoError(t, err) + + // Add token data to request context + request = request.WithContext(security.StoreTokenData(request, tt.tokenData)) + + // Call proxyPodsRequest which triggers addTokenForExec for POST /exec requests + resp, err := transport.proxyPodsRequest(request, "default") + require.NoError(t, err) + defer resp.Body.Close() + + // Verify the response + assert.Equal(t, http.StatusOK, resp.StatusCode) + + // Verify the request was captured + assert.NotNil(t, capturedRequest) + assert.Equal(t, "POST", capturedRequest.Method) + assert.Equal(t, tt.requestPath, capturedRequest.URL.Path) + + // Verify the authorization header was set correctly + capturedAuthHeader := capturedHeaders.Get("Authorization") + assert.NotEmpty(t, capturedAuthHeader) + assert.True(t, strings.HasPrefix(capturedAuthHeader, "Bearer ")) + assert.Equal(t, "Bearer "+tt.expectedToken, capturedAuthHeader) + }) + } +} diff --git a/api/http/proxy/factory/reverse_proxy.go b/api/http/proxy/factory/reverse_proxy.go index c40e6c485..a1bb3fa28 100644 --- a/api/http/proxy/factory/reverse_proxy.go +++ b/api/http/proxy/factory/reverse_proxy.go @@ -9,17 +9,20 @@ import ( // Note that we discard any non-canonical headers by design var allowedHeaders = map[string]struct{}{ - "Accept": {}, - "Accept-Encoding": {}, - "Accept-Language": {}, - "Cache-Control": {}, - "Content-Length": {}, - "Content-Type": {}, - "Private-Token": {}, - "User-Agent": {}, - "X-Portaineragent-Target": {}, - "X-Portainer-Volumename": {}, - "X-Registry-Auth": {}, + "Accept": {}, + "Accept-Encoding": {}, + "Accept-Language": {}, + "Cache-Control": {}, + "Connection": {}, + "Content-Length": {}, + "Content-Type": {}, + "Private-Token": {}, + "Upgrade": {}, + "User-Agent": {}, + "X-Portaineragent-Target": {}, + "X-Portainer-Volumename": {}, + "X-Registry-Auth": {}, + "X-Stream-Protocol-Version": {}, } // newSingleHostReverseProxyWithHostHeader is based on NewSingleHostReverseProxy diff --git a/api/http/proxy/manager.go b/api/http/proxy/manager.go index 16f822028..477bc547b 100644 --- a/api/http/proxy/manager.go +++ b/api/http/proxy/manager.go @@ -32,8 +32,8 @@ func NewManager(kubernetesClientFactory *cli.ClientFactory) *Manager { } } -func (manager *Manager) NewProxyFactory(dataStore dataservices.DataStore, signatureService portainer.DigitalSignatureService, tunnelService portainer.ReverseTunnelService, clientFactory *dockerclient.ClientFactory, kubernetesClientFactory *cli.ClientFactory, kubernetesTokenCacheManager *kubernetes.TokenCacheManager, gitService portainer.GitService, snapshotService portainer.SnapshotService) { - manager.proxyFactory = factory.NewProxyFactory(dataStore, signatureService, tunnelService, clientFactory, kubernetesClientFactory, kubernetesTokenCacheManager, gitService, snapshotService) +func (manager *Manager) NewProxyFactory(dataStore dataservices.DataStore, signatureService portainer.DigitalSignatureService, tunnelService portainer.ReverseTunnelService, clientFactory *dockerclient.ClientFactory, kubernetesClientFactory *cli.ClientFactory, kubernetesTokenCacheManager *kubernetes.TokenCacheManager, gitService portainer.GitService, snapshotService portainer.SnapshotService, jwtService portainer.JWTService) { + manager.proxyFactory = factory.NewProxyFactory(dataStore, signatureService, tunnelService, clientFactory, kubernetesClientFactory, kubernetesTokenCacheManager, gitService, snapshotService, jwtService) } // CreateAndRegisterEndpointProxy creates a new HTTP reverse proxy based on environment(endpoint) properties and adds it to the registered proxies.