diff --git a/api/http/proxy/factory/reverse_proxy.go b/api/http/proxy/factory/reverse_proxy.go index 1330b4fa0d..b47302307d 100644 --- a/api/http/proxy/factory/reverse_proxy.go +++ b/api/http/proxy/factory/reverse_proxy.go @@ -7,25 +7,29 @@ import ( "strings" httperror "github.com/portainer/portainer/pkg/libhttp/error" + "github.com/portainer/portainer/pkg/libstack/swarm" ) // Note that we discard any non-canonical headers by design var allowedHeaders = map[string]struct{}{ - "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-Registry-Config": {}, - "X-Stream-Protocol-Version": {}, + "Accept": {}, + "Accept-Encoding": {}, + "Accept-Language": {}, + "Cache-Control": {}, + "Connection": {}, + "Content-Length": {}, + "Content-Type": {}, + "Private-Token": {}, + "Upgrade": {}, + "User-Agent": {}, + + "X-Portaineragent-Target": {}, + http.CanonicalHeaderKey(swarm.ManagerOperationHeader): {}, + "X-Portainer-Volumename": {}, + "X-Registry-Auth": {}, + "X-Registry-Config": {}, + "X-Stream-Protocol-Version": {}, + // WebSocket headers those are required for kubectl exec/attach/port-forward operations "Sec-Websocket-Key": {}, "Sec-Websocket-Version": {}, diff --git a/api/http/proxy/factory/reverse_proxy_test.go b/api/http/proxy/factory/reverse_proxy_test.go index 7fc982534d..750b4e9b68 100644 --- a/api/http/proxy/factory/reverse_proxy_test.go +++ b/api/http/proxy/factory/reverse_proxy_test.go @@ -65,22 +65,23 @@ func Test_createRewriteFn(t *testing.T) { "GET", "https://agent-portainer.io/test?c=7", map[string]string{ - "Authorization": "secret", - "Proxy-Authorization": "secret", - "Cookie": "secret", - "X-Csrf-Token": "secret", - "X-Api-Key": "secret", - "Accept": "application/json", - "Accept-Encoding": "gzip", - "Accept-Language": "en-GB", - "Cache-Control": "None", - "Content-Length": "100", - "Content-Type": "application/json", - "Private-Token": "test-private-token", - "User-Agent": "test-user-agent", - "X-Portaineragent-Target": "test-agent-1", - "X-Portainer-Volumename": "test-volume-1", - "X-Registry-Auth": "test-registry-auth", + "Authorization": "secret", + "Proxy-Authorization": "secret", + "Cookie": "secret", + "X-Csrf-Token": "secret", + "X-Api-Key": "secret", + "Accept": "application/json", + "Accept-Encoding": "gzip", + "Accept-Language": "en-GB", + "Cache-Control": "None", + "Content-Length": "100", + "Content-Type": "application/json", + "Private-Token": "test-private-token", + "User-Agent": "test-user-agent", + "X-Portaineragent-Target": "test-agent-1", + "X-Portaineragent-Manageroperation": "1", + "X-Portainer-Volumename": "test-volume-1", + "X-Registry-Auth": "test-registry-auth", }, true, ), @@ -89,17 +90,18 @@ func Test_createRewriteFn(t *testing.T) { "GET", "https://portainer.io/api/docker/test?a=5&b=6&c=7", map[string]string{ - "Accept": "application/json", - "Accept-Encoding": "gzip", - "Accept-Language": "en-GB", - "Cache-Control": "None", - "Content-Length": "100", - "Content-Type": "application/json", - "Private-Token": "test-private-token", - "User-Agent": "test-user-agent", - "X-Portaineragent-Target": "test-agent-1", - "X-Portainer-Volumename": "test-volume-1", - "X-Registry-Auth": "test-registry-auth", + "Accept": "application/json", + "Accept-Encoding": "gzip", + "Accept-Language": "en-GB", + "Cache-Control": "None", + "Content-Length": "100", + "Content-Type": "application/json", + "Private-Token": "test-private-token", + "User-Agent": "test-user-agent", + "X-Portaineragent-Target": "test-agent-1", + "X-Portaineragent-Manageroperation": "1", + "X-Portainer-Volumename": "test-volume-1", + "X-Registry-Auth": "test-registry-auth", }, true, ), diff --git a/pkg/libstack/swarm/swarm.go b/pkg/libstack/swarm/swarm.go index 3f35313136..5f5a62a9d4 100644 --- a/pkg/libstack/swarm/swarm.go +++ b/pkg/libstack/swarm/swarm.go @@ -32,10 +32,10 @@ import ( "github.com/rs/zerolog/log" ) -// managerOperationHeader forces the Portainer agent to route requests to a swarm +// ManagerOperationHeader forces the Portainer agent to route requests to a swarm // manager node. Swarm-scoped operations (e.g. network create/remove) fail on worker // nodes without it. This constant is originally defined in the agent, see package/agent/README.md. -const managerOperationHeader = "X-PortainerAgent-ManagerOperation" +const ManagerOperationHeader = "X-PortainerAgent-ManagerOperation" // cliOptions builds the Docker CLI options for swarm operations, always setting the // manager-operation header so requests proxied through an agent target a manager node. @@ -43,7 +43,7 @@ func cliOptions(host string, registries []configtypes.AuthConfig) libstack.Docke return libstack.DockerCliOptions{ Host: host, Registries: registries, - Headers: map[string]string{managerOperationHeader: "1"}, + Headers: map[string]string{ManagerOperationHeader: "1"}, } } diff --git a/pkg/libstack/swarm/swarm_unit_test.go b/pkg/libstack/swarm/swarm_unit_test.go index c2568eb2cf..711c9d5c5d 100644 --- a/pkg/libstack/swarm/swarm_unit_test.go +++ b/pkg/libstack/swarm/swarm_unit_test.go @@ -101,13 +101,13 @@ func Test_cliOptions(t *testing.T) { expected: libstack.DockerCliOptions{ Host: "tcp://127.0.0.1:2377", Registries: registries, - Headers: map[string]string{managerOperationHeader: "1"}, + Headers: map[string]string{ManagerOperationHeader: "1"}, }, }, { name: "empty host and nil registries still set the header", expected: libstack.DockerCliOptions{ - Headers: map[string]string{managerOperationHeader: "1"}, + Headers: map[string]string{ManagerOperationHeader: "1"}, }, }, }