From 7a53e6bb42167d86f4197c51da767796c46d3345 Mon Sep 17 00:00:00 2001 From: Travis Groth Date: Tue, 4 Aug 2020 13:26:14 -0400 Subject: [PATCH] proxy: add support for spdy upgrades (#1203) --- config/policy.go | 3 + docs/guides/kubernetes.md | 1 + docs/reference/readme.md | 28 ++++-- internal/controlplane/xds_routes.go | 14 ++- internal/controlplane/xds_routes_test.go | 117 +++++++++++++++++------ 5 files changed, 121 insertions(+), 42 deletions(-) diff --git a/config/policy.go b/config/policy.go index ea57f2f27..7ffbee87d 100644 --- a/config/policy.go +++ b/config/policy.go @@ -51,6 +51,9 @@ type Policy struct { // Caution: Enabling this feature could result in abuse via DOS attacks. AllowWebsockets bool `mapstructure:"allow_websockets" yaml:"allow_websockets,omitempty"` + // AllowSPDY enables proxying of SPDY upgrade requests + AllowSPDY bool `mapstructure:"allow_spdy" yaml:"allow_spdy,omitempty"` + // TLSSkipVerify controls whether a client verifies the server's certificate // chain and host name. // If TLSSkipVerify is true, TLS accepts any certificate presented by the diff --git a/docs/guides/kubernetes.md b/docs/guides/kubernetes.md index c869b1eb7..6c1e5b520 100644 --- a/docs/guides/kubernetes.md +++ b/docs/guides/kubernetes.md @@ -193,6 +193,7 @@ The policy should be a base64-encoded block of yaml: - from: https://k8s.localhost.pomerium.io:30443 to: https://kubernetes.default.svc tls_skip_verify: true + allow_spdy: true allowed_domains: - pomerium.com kubernetes_service_account_token: "..." #$(kubectl get secret/"$(kubectl get serviceaccount/pomerium -o json | jq -r '.secrets[0].name')" -o json | jq -r .data.token | base64 -d) diff --git a/docs/reference/readme.md b/docs/reference/readme.md index 36700be20..03df52771 100644 --- a/docs/reference/readme.md +++ b/docs/reference/readme.md @@ -1134,16 +1134,6 @@ Note: This setting will replace (not append) the system's trust store for a give Pomerium supports client certificates which can be used to enforce [mutually authenticated and encrypted TLS connections](https://en.wikipedia.org/wiki/Mutual_authentication) (mTLS). For more details, see our [mTLS example repository](https://github.com/pomerium/examples/tree/master/mutual-tls) and the [certificate docs](../docs/topics/certificates.md). -### Websocket Connections - -- Config File Key: `allow_websockets` -- Type: `bool` -- Default: `false` - -If set, enables proxying of websocket connections. - -**Use with caution:** By definition, websockets are long-lived connections, so [global timeouts](#global-timeouts) are not enforced. Allowing websocket connections to the proxy could result in abuse via [DOS attacks](https://www.cloudflare.com/learning/ddos/ddos-attack-tools/slowloris/). - ### Pass Identity Headers - `yaml`/`json` setting: `pass_identity_headers` @@ -1156,6 +1146,24 @@ When enabled, this option will pass the identity headers to the downstream appli - X-Pomerium-Jwt-Assertion - X-Pomerium-Claim-* +### SPDY + +- Config File Key: `allow_spdy` +- Type: `bool` +- Default: `false` + +If set, enables proxying of SPDY protocol upgrades. + +### Websocket Connections + +- Config File Key: `allow_websockets` +- Type: `bool` +- Default: `false` + +If set, enables proxying of websocket connections. + +**Use with caution:** By definition, websockets are long-lived connections, so [global timeouts](#global-timeouts) are not enforced. Allowing websocket connections to the proxy could result in abuse via [DOS attacks](https://www.cloudflare.com/learning/ddos/ddos-attack-tools/slowloris/). + ## Authorize Service ### Authenticate Service URL diff --git a/internal/controlplane/xds_routes.go b/internal/controlplane/xds_routes.go index d3d096226..1983aafc5 100644 --- a/internal/controlplane/xds_routes.go +++ b/internal/controlplane/xds_routes.go @@ -140,10 +140,16 @@ func buildPolicyRoutes(options *config.Options, domain string) []*envoy_config_r ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{ Cluster: clusterName, }, - UpgradeConfigs: []*envoy_config_route_v3.RouteAction_UpgradeConfig{{ - UpgradeType: "websocket", - Enabled: &wrappers.BoolValue{Value: policy.AllowWebsockets}, - }}, + UpgradeConfigs: []*envoy_config_route_v3.RouteAction_UpgradeConfig{ + { + UpgradeType: "websocket", + Enabled: &wrappers.BoolValue{Value: policy.AllowWebsockets}, + }, + { + UpgradeType: "spdy/3.1", + Enabled: &wrappers.BoolValue{Value: policy.AllowSPDY}, + }, + }, HostRewriteSpecifier: &envoy_config_route_v3.RouteAction_AutoHostRewrite{ AutoHostRewrite: &wrappers.BoolValue{Value: !policy.PreserveHostHeader}, }, diff --git a/internal/controlplane/xds_routes_test.go b/internal/controlplane/xds_routes_test.go index ee42664b7..a61d87069 100644 --- a/internal/controlplane/xds_routes_test.go +++ b/internal/controlplane/xds_routes_test.go @@ -248,6 +248,21 @@ func Test_buildPolicyRoutes(t *testing.T) { UpstreamTimeout: time.Minute, PassIdentityHeaders: true, }, + { + Source: &config.StringURL{URL: mustParseURL("https://example.com")}, + Path: "/some/path", + AllowSPDY: true, + PreserveHostHeader: true, + PassIdentityHeaders: true, + }, + { + Source: &config.StringURL{URL: mustParseURL("https://example.com")}, + Path: "/some/path", + AllowSPDY: true, + AllowWebsockets: true, + PreserveHostHeader: true, + PassIdentityHeaders: true, + }, }, }, "example.com") @@ -270,10 +285,10 @@ func Test_buildPolicyRoutes(t *testing.T) { "autoHostRewrite": true, "cluster": "policy-701142725541ce1f", "timeout": "3s", - "upgradeConfigs": [{ - "enabled": false, - "upgradeType": "websocket" - }] + "upgradeConfigs": [ + { "enabled": false, "upgradeType": "websocket"}, + { "enabled": false, "upgradeType": "spdy/3.1"} + ] } }, { @@ -293,10 +308,10 @@ func Test_buildPolicyRoutes(t *testing.T) { "autoHostRewrite": false, "cluster": "policy-35b6cce9d52d36ed", "timeout": "0s", - "upgradeConfigs": [{ - "enabled": true, - "upgradeType": "websocket" - }] + "upgradeConfigs": [ + { "enabled": true, "upgradeType": "websocket"}, + { "enabled": false, "upgradeType": "spdy/3.1"} + ] } }, { @@ -316,10 +331,10 @@ func Test_buildPolicyRoutes(t *testing.T) { "autoHostRewrite": true, "cluster": "policy-8935ca8067709cf7", "timeout": "60s", - "upgradeConfigs": [{ - "enabled": false, - "upgradeType": "websocket" - }] + "upgradeConfigs": [ + { "enabled": false, "upgradeType": "websocket"}, + { "enabled": false, "upgradeType": "spdy/3.1"} + ] }, "requestHeadersToAdd": [{ "append": false, @@ -349,10 +364,10 @@ func Test_buildPolicyRoutes(t *testing.T) { "autoHostRewrite": true, "cluster": "policy-45c2908c3d6f0e52", "timeout": "3s", - "upgradeConfigs": [{ - "enabled": false, - "upgradeType": "websocket" - }] + "upgradeConfigs": [ + { "enabled": false, "upgradeType": "websocket"}, + { "enabled": false, "upgradeType": "spdy/3.1"} + ] } }, { @@ -372,12 +387,58 @@ func Test_buildPolicyRoutes(t *testing.T) { "autoHostRewrite": true, "cluster": "policy-8935ca8067709cf7", "timeout": "60s", - "upgradeConfigs": [{ - "enabled": false, - "upgradeType": "websocket" - }] + "upgradeConfigs": [ + { "enabled": false, "upgradeType": "websocket"}, + { "enabled": false, "upgradeType": "spdy/3.1"} + ] }, "requestHeadersToRemove": ["HEADER-KEY"] + }, + { + "name": "policy-6", + "match": { + "path": "/some/path" + }, + "metadata": { + "filterMetadata": { + "envoy.filters.http.lua": { + "remove_pomerium_authorization": true, + "remove_pomerium_cookie": "pomerium" + } + } + }, + "route": { + "autoHostRewrite": false, + "cluster": "policy-35b6cce9d52d36ed", + "timeout": "3s", + "upgradeConfigs": [ + { "enabled": false, "upgradeType": "websocket"}, + { "enabled": true, "upgradeType": "spdy/3.1"} + ] + } + }, + { + "name": "policy-7", + "match": { + "path": "/some/path" + }, + "metadata": { + "filterMetadata": { + "envoy.filters.http.lua": { + "remove_pomerium_authorization": true, + "remove_pomerium_cookie": "pomerium" + } + } + }, + "route": { + "autoHostRewrite": false, + "cluster": "policy-35b6cce9d52d36ed", + "timeout": "0s", + "upgradeConfigs": [ + { "enabled": true, "upgradeType": "websocket"}, + { "enabled": true, "upgradeType": "spdy/3.1"} + ] + } } ] `, routes) @@ -417,10 +478,10 @@ func TestAddOptionsHeadersToResponse(t *testing.T) { "autoHostRewrite": true, "cluster": "policy-701142725541ce1f", "timeout": "3s", - "upgradeConfigs": [{ - "enabled": false, - "upgradeType": "websocket" - }] + "upgradeConfigs": [ + { "enabled": false, "upgradeType": "websocket"}, + { "enabled": false, "upgradeType": "spdy/3.1"} + ] }, "responseHeadersToAdd": [{ "append": false, @@ -467,10 +528,10 @@ func Test_buildPolicyRoutesWithDestinationPath(t *testing.T) { "prefixRewrite": "/bar", "cluster": "policy-605b7be39724cb4f", "timeout": "3s", - "upgradeConfigs": [{ - "enabled": false, - "upgradeType": "websocket" - }] + "upgradeConfigs": [ + { "enabled": false, "upgradeType": "websocket"}, + { "enabled": false, "upgradeType": "spdy/3.1"} + ] } } ]