config: add support for host header rewriting (#1457)

* config: add support for host header rewriting

* fix lint
This commit is contained in:
Caleb Doxsey 2020-09-25 09:36:39 -06:00 committed by GitHub
parent 29b2fa4e60
commit 6e385f800a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 189 additions and 29 deletions

View file

@ -43,6 +43,12 @@ type Policy struct {
RegexRewritePattern string `mapstructure:"regex_rewrite_pattern" yaml:"regex_rewrite_pattern,omitempty" json:"regex_rewrite_pattern,omitempty"`
RegexRewriteSubstitution string `mapstructure:"regex_rewrite_substitution" yaml:"regex_rewrite_substitution,omitempty" json:"regex_rewrite_substitution,omitempty"` //nolint
// Host Rewrite Options
HostRewrite string `mapstructure:"host_rewrite" yaml:"host_rewrite,omitempty" json:"host_rewrite,omitempty"`
HostRewriteHeader string `mapstructure:"host_rewrite_header" yaml:"host_rewrite_header,omitempty" json:"host_rewrite_header,omitempty"`
HostPathRegexRewritePattern string `mapstructure:"host_path_regex_rewrite_pattern" yaml:"host_path_regex_rewrite_pattern,omitempty" json:"host_path_regex_rewrite_pattern,omitempty"` //nolint
HostPathRegexRewriteSubstitution string `mapstructure:"host_path_regex_rewrite_substitution" yaml:"host_path_regex_rewrite_substitution,omitempty" json:"host_path_regex_rewrite_substitution,omitempty"` //nolint
// Allow unauthenticated HTTP OPTIONS requests as per the CORS spec
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests
CORSAllowPreflight bool `mapstructure:"cors_allow_preflight" yaml:"cors_allow_preflight,omitempty"`

View file

@ -1057,6 +1057,28 @@ prefix_rewrite: /
A request to `https://from.example.com/admin` would be forwarded to `https://to.example.com/`.
### Host Rewrite
- `yaml`/`json` settings: `host_rewrite`, `host_rewrite_header`, `host_path_regex_rewrite_pattern`, `host_path_regex_rewrite_substitution`
- Type: `string`
- Optional
- Example: `host_rewrite: "example.com"`
The `host` header can be customized via 3 mutually exclusive options:
1. `host_rewrite` which will rewrite the host to a new literal value.
2. `host_rewrite_header` which will rewrite the host to match an incoming header value.
3. `host_path_regex_rewrite_pattern`, `host_path_regex_rewrite_substitution` which will rewrite the host according to a regex matching the path. For example with the following config:
```yaml
host_path_regex_rewrite_pattern: "^/(.+)/.+$"
host_path_regex_rewrite_substitution: \1
```
Would rewrite the host header to `example.com` given the path `/example.com/some/path`.
These options correspond to the envoy route action host related options, which can be found [here](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto.html#config-route-v3-routeaction).
### Public Access
- `yaml`/`json` setting: `allow_public_unauthenticated_access`

3
go.mod
View file

@ -10,9 +10,8 @@ require (
github.com/caddyserver/certmagic v0.12.0
github.com/cenkalti/backoff/v4 v4.0.2
github.com/cespare/xxhash/v2 v2.1.1
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354 // indirect
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/envoyproxy/go-control-plane v0.9.6
github.com/envoyproxy/go-control-plane v0.9.7-0.20200924180459-2fd2c9f35b9c
github.com/fsnotify/fsnotify v1.4.9
github.com/go-chi/chi v4.1.2+incompatible
github.com/golang/mock v1.4.4

6
go.sum
View file

@ -101,8 +101,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533 h1:8wZizuKuZVu5COB7EsBYxBQz8nRcXXn5d4Gt91eJLvU=
github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354 h1:9kRtNpqLHbZVO/NNxhHp2ymxFxsHOe3x2efJGn//Tas=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw=
@ -135,8 +133,8 @@ github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4s
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.6 h1:GgblEiDzxf5ajlAZY4aC8xp7DwkrGfauFNMGdB2bBv0=
github.com/envoyproxy/go-control-plane v0.9.6/go.mod h1:GFqM7v0B62MraO4PWRedIbhThr/Rf7ev6aHOOPXeaDA=
github.com/envoyproxy/go-control-plane v0.9.7-0.20200924180459-2fd2c9f35b9c h1:BtHMDMV2uGF8AkmS5cTX9gii0WgO2RoPzxJWGwh7k6s=
github.com/envoyproxy/go-control-plane v0.9.7-0.20200924180459-2fd2c9f35b9c/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=

View file

@ -12,6 +12,7 @@ import (
"github.com/golang/protobuf/ptypes/wrappers"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/wrapperspb"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/httputil"
@ -122,6 +123,29 @@ func buildPolicyRoutes(options *config.Options, domain string) []*envoy_config_r
routeTimeout := getRouteTimeout(options, &policy)
prefixRewrite, regexRewrite := getRewriteOptions(&policy)
routeAction := &envoy_config_route_v3.RouteAction{
ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{
Cluster: clusterName,
},
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},
},
Timeout: routeTimeout,
PrefixRewrite: prefixRewrite,
RegexRewrite: regexRewrite,
}
setHostRewriteOptions(&policy, routeAction)
routes = append(routes, &envoy_config_route_v3.Route{
Name: fmt.Sprintf("policy-%d", i),
Match: match,
@ -148,29 +172,7 @@ func buildPolicyRoutes(options *config.Options, domain string) []*envoy_config_r
},
},
},
Action: &envoy_config_route_v3.Route_Route{
Route: &envoy_config_route_v3.RouteAction{
ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{
Cluster: clusterName,
},
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},
},
Timeout: routeTimeout,
PrefixRewrite: prefixRewrite,
RegexRewrite: regexRewrite,
},
},
Action: &envoy_config_route_v3.Route_Route{Route: routeAction},
RequestHeadersToAdd: requestHeadersToAdd,
RequestHeadersToRemove: requestHeadersToRemove,
ResponseHeadersToAdd: responseHeadersToAdd,
@ -269,6 +271,39 @@ func getRewriteOptions(policy *config.Policy) (prefixRewrite string, regexRewrit
return prefixRewrite, regexRewrite
}
func setHostRewriteOptions(policy *config.Policy, action *envoy_config_route_v3.RouteAction) {
switch {
case policy.HostRewrite != "":
action.HostRewriteSpecifier = &envoy_config_route_v3.RouteAction_HostRewriteLiteral{
HostRewriteLiteral: policy.HostRewrite,
}
case policy.HostRewriteHeader != "":
action.HostRewriteSpecifier = &envoy_config_route_v3.RouteAction_HostRewriteHeader{
HostRewriteHeader: policy.HostRewriteHeader,
}
case policy.HostPathRegexRewritePattern != "":
action.HostRewriteSpecifier = &envoy_config_route_v3.RouteAction_HostRewritePathRegex{
HostRewritePathRegex: &envoy_type_matcher_v3.RegexMatchAndSubstitute{
Pattern: &envoy_type_matcher_v3.RegexMatcher{
EngineType: &envoy_type_matcher_v3.RegexMatcher_GoogleRe2{
GoogleRe2: &envoy_type_matcher_v3.RegexMatcher_GoogleRE2{},
},
Regex: policy.HostPathRegexRewritePattern,
},
Substitution: policy.HostPathRegexRewriteSubstitution,
},
}
case policy.PreserveHostHeader:
action.HostRewriteSpecifier = &envoy_config_route_v3.RouteAction_AutoHostRewrite{
AutoHostRewrite: wrapperspb.Bool(false),
}
default:
action.HostRewriteSpecifier = &envoy_config_route_v3.RouteAction_AutoHostRewrite{
AutoHostRewrite: wrapperspb.Bool(true),
}
}
}
func hasPublicPolicyMatchingURL(options *config.Options, requestURL *url.URL) bool {
for _, policy := range options.Policies {
if policy.AllowPublicUnauthenticatedAccess && policy.Matches(requestURL) {

View file

@ -535,6 +535,25 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
RegexRewritePattern: "^/service/([^/]+)(/.*)$",
RegexRewriteSubstitution: "\\2/instance/\\1",
},
{
Source: &config.StringURL{URL: mustParseURL("https://example.com")},
Destination: mustParseURL("https://foo.example.com/bar"),
PassIdentityHeaders: true,
HostRewrite: "literal.example.com",
},
{
Source: &config.StringURL{URL: mustParseURL("https://example.com")},
Destination: mustParseURL("https://foo.example.com/bar"),
PassIdentityHeaders: true,
HostRewriteHeader: "HOST_HEADER",
},
{
Source: &config.StringURL{URL: mustParseURL("https://example.com")},
Destination: mustParseURL("https://foo.example.com/bar"),
PassIdentityHeaders: true,
HostPathRegexRewritePattern: "^/(.+)/.+$",
HostPathRegexRewriteSubstitution: "\\1",
},
},
}, "example.com")
@ -620,6 +639,87 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
{ "enabled": false, "upgradeType": "spdy/3.1"}
]
}
},
{
"name": "policy-3",
"match": {
"prefix": "/"
},
"metadata": {
"filterMetadata": {
"envoy.filters.http.lua": {
"remove_impersonate_headers": false,
"remove_pomerium_authorization": true,
"remove_pomerium_cookie": "pomerium"
}
}
},
"route": {
"hostRewriteLiteral": "literal.example.com",
"prefixRewrite": "/bar",
"cluster": "policy-4",
"timeout": "3s",
"upgradeConfigs": [
{ "enabled": false, "upgradeType": "websocket"},
{ "enabled": false, "upgradeType": "spdy/3.1"}
]
}
},
{
"name": "policy-4",
"match": {
"prefix": "/"
},
"metadata": {
"filterMetadata": {
"envoy.filters.http.lua": {
"remove_impersonate_headers": false,
"remove_pomerium_authorization": true,
"remove_pomerium_cookie": "pomerium"
}
}
},
"route": {
"hostRewriteHeader": "HOST_HEADER",
"prefixRewrite": "/bar",
"cluster": "policy-5",
"timeout": "3s",
"upgradeConfigs": [
{ "enabled": false, "upgradeType": "websocket"},
{ "enabled": false, "upgradeType": "spdy/3.1"}
]
}
},
{
"name": "policy-5",
"match": {
"prefix": "/"
},
"metadata": {
"filterMetadata": {
"envoy.filters.http.lua": {
"remove_impersonate_headers": false,
"remove_pomerium_authorization": true,
"remove_pomerium_cookie": "pomerium"
}
}
},
"route": {
"hostRewritePathRegex": {
"pattern": {
"googleRe2": {},
"regex": "^/(.+)/.+$"
},
"substitution": "\\1"
},
"prefixRewrite": "/bar",
"cluster": "policy-6",
"timeout": "3s",
"upgradeConfigs": [
{ "enabled": false, "upgradeType": "websocket"},
{ "enabled": false, "upgradeType": "spdy/3.1"}
]
}
}
]
`, routes)