add support for TCP routes (#1695)

This commit is contained in:
Caleb Doxsey 2020-12-16 13:09:48 -07:00 committed by GitHub
parent 64816720c8
commit ad828c6e84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 86 additions and 13 deletions

View file

@ -153,7 +153,7 @@ allowed_route_regex(input_url_obj, policy) {
parse_url(str) = { "scheme": scheme, "host": host, "path": path } { parse_url(str) = { "scheme": scheme, "host": host, "path": path } {
[_, scheme, host, rawpath] = regex.find_all_string_submatch_n( [_, scheme, host, rawpath] = regex.find_all_string_submatch_n(
`(?:(http[s]?)://)?([^/]+)([^?#]*)`, `(?:((?:tcp[+])?http[s]?)://)?([^/]+)([^?#]*)`,
str, 1)[0] str, 1)[0]
path = normalize_url_path(rawpath) path = normalize_url_path(rawpath)
} }

View file

@ -301,6 +301,13 @@ test_parse_url {
url.path == "/some/path" url.path == "/some/path"
} }
test_parse_tcp_url {
url := parse_url("tcp+http://example.com/some/path?qs")
url.scheme == "tcp+http"
url.host == "example.com"
url.path == "/some/path"
}
test_allowed_route_source { test_allowed_route_source {
allowed_route("http://example.com", {"source": "example.com"}) allowed_route("http://example.com", {"source": "example.com"})
allowed_route("http://example.com", {"source": "http://example.com"}) allowed_route("http://example.com", {"source": "http://example.com"})

File diff suppressed because one or more lines are too long

View file

@ -257,7 +257,7 @@ func (p *Policy) Validate() error {
} }
// Make sure there's no path set on the from url // Make sure there's no path set on the from url
if !(source.Path == "" || source.Path == "/") { if (source.Scheme == "http" || source.Scheme == "https") && !(source.Path == "" || source.Path == "/") {
return fmt.Errorf("config: policy source url (%s) contains a path, but it should be set using the path field instead", return fmt.Errorf("config: policy source url (%s) contains a path, but it should be set using the path field instead",
source.String()) source.String())
} }

View file

@ -192,20 +192,29 @@ func buildPolicyRoutes(options *config.Options, domain string) []*envoy_config_r
routeTimeout := getRouteTimeout(options, &policy) routeTimeout := getRouteTimeout(options, &policy)
prefixRewrite, regexRewrite := getRewriteOptions(&policy) prefixRewrite, regexRewrite := getRewriteOptions(&policy)
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},
},
}
if urlutil.IsTCP(policy.Source.URL) {
upgradeConfigs = append(upgradeConfigs, &envoy_config_route_v3.RouteAction_UpgradeConfig{
UpgradeType: "CONNECT",
Enabled: &wrappers.BoolValue{Value: true},
ConnectConfig: &envoy_config_route_v3.RouteAction_UpgradeConfig_ConnectConfig{},
})
}
routeAction := &envoy_config_route_v3.RouteAction{ routeAction := &envoy_config_route_v3.RouteAction{
ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{ ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{
Cluster: clusterName, Cluster: clusterName,
}, },
UpgradeConfigs: []*envoy_config_route_v3.RouteAction_UpgradeConfig{ UpgradeConfigs: upgradeConfigs,
{
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{ HostRewriteSpecifier: &envoy_config_route_v3.RouteAction_AutoHostRewrite{
AutoHostRewrite: &wrappers.BoolValue{Value: !policy.PreserveHostHeader}, AutoHostRewrite: &wrappers.BoolValue{Value: !policy.PreserveHostHeader},
}, },
@ -271,6 +280,10 @@ func toEnvoyHeaders(headers map[string]string) []*envoy_config_core_v3.HeaderVal
func mkRouteMatch(policy *config.Policy) *envoy_config_route_v3.RouteMatch { func mkRouteMatch(policy *config.Policy) *envoy_config_route_v3.RouteMatch {
match := &envoy_config_route_v3.RouteMatch{} match := &envoy_config_route_v3.RouteMatch{}
switch { switch {
case urlutil.IsTCP(policy.Source.URL):
match.PathSpecifier = &envoy_config_route_v3.RouteMatch_ConnectMatcher_{
ConnectMatcher: &envoy_config_route_v3.RouteMatch_ConnectMatcher{},
}
case policy.Regex != "": case policy.Regex != "":
match.PathSpecifier = &envoy_config_route_v3.RouteMatch_SafeRegex{ match.PathSpecifier = &envoy_config_route_v3.RouteMatch_SafeRegex{
SafeRegex: &envoy_type_matcher_v3.RegexMatcher{ SafeRegex: &envoy_type_matcher_v3.RegexMatcher{

View file

@ -464,6 +464,50 @@ func Test_buildPolicyRoutes(t *testing.T) {
} }
] ]
`, routes) `, routes)
t.Run("tcp", func(t *testing.T) {
routes = buildPolicyRoutes(&config.Options{
CookieName: "pomerium",
DefaultUpstreamTimeout: time.Second * 3,
Policies: []config.Policy{
{
Source: &config.StringURL{URL: mustParseURL("tcp+https://example.com:22")},
PassIdentityHeaders: true,
},
},
}, "example.com:22")
testutil.AssertProtoJSONEqual(t, `
[
{
"name": "policy-0",
"match": {
"connectMatcher": {}
},
"metadata": {
"filterMetadata": {
"envoy.filters.http.lua": {
"remove_impersonate_headers": false,
"remove_pomerium_authorization": true,
"remove_pomerium_cookie": "pomerium"
}
}
},
"route": {
"autoHostRewrite": true,
"cluster": "policy-9",
"timeout": "3s",
"upgradeConfigs": [
{ "enabled": false, "upgradeType": "websocket"},
{ "enabled": false, "upgradeType": "spdy/3.1"},
{ "enabled": true, "upgradeType": "CONNECT", "connectConfig": {} }
]
}
}
]
`, routes)
})
} }
// Make sure default Headers are set for response. // Make sure default Headers are set for response.

View file

@ -84,6 +84,10 @@ func GetAbsoluteURL(r *http.Request) *url.URL {
// For standard HTTP (80)/HTTPS (443) ports, it returns `example.com` and `example.com:<port>`. // For standard HTTP (80)/HTTPS (443) ports, it returns `example.com` and `example.com:<port>`.
// Otherwise, return the URL.Host value. // Otherwise, return the URL.Host value.
func GetDomainsForURL(u *url.URL) []string { func GetDomainsForURL(u *url.URL) []string {
if IsTCP(u) {
return []string{u.Host}
}
var defaultPort string var defaultPort string
if u.Scheme == "http" { if u.Scheme == "http" {
defaultPort = "80" defaultPort = "80"
@ -102,6 +106,11 @@ func GetDomainsForURL(u *url.URL) []string {
return []string{u.Hostname(), net.JoinHostPort(u.Hostname(), defaultPort)} return []string{u.Hostname(), net.JoinHostPort(u.Hostname(), defaultPort)}
} }
// IsTCP returns whether or not the given URL is for TCP via HTTP Connect.
func IsTCP(u *url.URL) bool {
return u.Scheme == "tcp+http" || u.Scheme == "tcp+https"
}
// ParseEnvoyQueryParams returns a new URL with queryparams parsed from envoy format. // ParseEnvoyQueryParams returns a new URL with queryparams parsed from envoy format.
func ParseEnvoyQueryParams(u *url.URL) *url.URL { func ParseEnvoyQueryParams(u *url.URL) *url.URL {
nu := &url.URL{ nu := &url.URL{