mirror of
https://github.com/pomerium/pomerium.git
synced 2025-06-05 12:23:03 +02:00
config: add support for wildcard from addresses (#4131)
* config: add support for wildcards * update policy matching, header generation * remove deprecated field * fix test
This commit is contained in:
parent
949454e886
commit
18bc86d632
12 changed files with 445 additions and 115 deletions
|
@ -38,6 +38,7 @@ type Request struct {
|
|||
// RequestHTTP is the HTTP field in the request.
|
||||
type RequestHTTP struct {
|
||||
Method string `json:"method"`
|
||||
Hostname string `json:"hostname"`
|
||||
Path string `json:"path"`
|
||||
URL string `json:"url"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
|
@ -55,6 +56,7 @@ func NewRequestHTTP(
|
|||
) RequestHTTP {
|
||||
return RequestHTTP{
|
||||
Method: method,
|
||||
Hostname: requestURL.Hostname(),
|
||||
Path: requestURL.Path,
|
||||
URL: requestURL.String(),
|
||||
Headers: headers,
|
||||
|
@ -162,7 +164,7 @@ func (e *Evaluator) Evaluate(ctx context.Context, req *Request) (*Result, error)
|
|||
|
||||
var headersOutput *HeadersResponse
|
||||
eg.Go(func() error {
|
||||
headersReq := NewHeadersRequestFromPolicy(req.Policy)
|
||||
headersReq := NewHeadersRequestFromPolicy(req.Policy, req.HTTP.Hostname)
|
||||
headersReq.Session = req.Session
|
||||
var err error
|
||||
headersOutput, err = e.headersEvaluators.Evaluate(ectx, headersReq)
|
||||
|
|
|
@ -12,7 +12,6 @@ import (
|
|||
"github.com/pomerium/pomerium/authorize/internal/store"
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/telemetry/trace"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
configpb "github.com/pomerium/pomerium/pkg/grpc/config"
|
||||
)
|
||||
|
||||
|
@ -29,14 +28,12 @@ type HeadersRequest struct {
|
|||
}
|
||||
|
||||
// NewHeadersRequestFromPolicy creates a new HeadersRequest from a policy.
|
||||
func NewHeadersRequestFromPolicy(policy *config.Policy) *HeadersRequest {
|
||||
func NewHeadersRequestFromPolicy(policy *config.Policy, hostname string) *HeadersRequest {
|
||||
input := new(HeadersRequest)
|
||||
input.EnableGoogleCloudServerlessAuthentication = policy.EnableGoogleCloudServerlessAuthentication
|
||||
input.EnableRoutingKey = policy.EnvoyOpts.GetLbPolicy() == envoy_config_cluster_v3.Cluster_RING_HASH ||
|
||||
policy.EnvoyOpts.GetLbPolicy() == envoy_config_cluster_v3.Cluster_MAGLEV
|
||||
if u, err := urlutil.ParseAndValidateURL(policy.From); err == nil {
|
||||
input.Issuer = u.Hostname()
|
||||
}
|
||||
input.Issuer = hostname
|
||||
input.KubernetesServiceAccountToken = policy.KubernetesServiceAccountToken
|
||||
for _, wu := range policy.To {
|
||||
input.ToAudience = "https://" + wu.URL.Hostname()
|
||||
|
|
|
@ -22,13 +22,13 @@ import (
|
|||
func TestNewHeadersRequestFromPolicy(t *testing.T) {
|
||||
req := NewHeadersRequestFromPolicy(&config.Policy{
|
||||
EnableGoogleCloudServerlessAuthentication: true,
|
||||
From: "https://from.example.com",
|
||||
From: "https://*.example.com",
|
||||
To: config.WeightedURLs{
|
||||
{
|
||||
URL: *mustParseURL("http://to.example.com"),
|
||||
},
|
||||
},
|
||||
})
|
||||
}, "from.example.com")
|
||||
assert.Equal(t, &HeadersRequest{
|
||||
EnableGoogleCloudServerlessAuthentication: true,
|
||||
Issuer: "from.example.com",
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||
|
@ -570,7 +571,13 @@ func getAllRouteableHosts(options *config.Options, addr string) ([]string, error
|
|||
allHosts.Add(hosts...)
|
||||
}
|
||||
|
||||
return allHosts.ToSlice(), nil
|
||||
var filtered []string
|
||||
for _, host := range allHosts.ToSlice() {
|
||||
if !strings.Contains(host, "*") {
|
||||
filtered = append(filtered, host)
|
||||
}
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func urlsMatchHost(urls []*url.URL, host string) bool {
|
||||
|
|
|
@ -78,7 +78,7 @@ func (b *Builder) buildMainRouteConfiguration(
|
|||
|
||||
// if we're the proxy, add all the policy routes
|
||||
if config.IsProxy(cfg.Options.Services) {
|
||||
rs, err := b.buildPolicyRoutes(cfg.Options, host, requireStrictTransportSecurity)
|
||||
rs, err := b.buildRoutesForPoliciesWithHost(cfg, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -94,6 +94,14 @@ func (b *Builder) buildMainRouteConfiguration(
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config.IsProxy(cfg.Options.Services) {
|
||||
rs, err := b.buildRoutesForPoliciesWithCatchAll(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vh.Routes = append(vh.Routes, rs...)
|
||||
}
|
||||
|
||||
virtualHosts = append(virtualHosts, vh)
|
||||
|
||||
rc, err := b.buildRouteConfiguration("main", virtualHosts)
|
||||
|
|
141
config/envoyconfig/route_configurations_test.go
Normal file
141
config/envoyconfig/route_configurations_test.go
Normal file
|
@ -0,0 +1,141 @@
|
|||
package envoyconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/config/envoyconfig/filemgr"
|
||||
"github.com/pomerium/pomerium/internal/testutil"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
)
|
||||
|
||||
func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
cfg := &config.Config{Options: &config.Options{
|
||||
CookieName: "pomerium",
|
||||
DefaultUpstreamTimeout: time.Second * 3,
|
||||
SharedKey: cryptutil.NewBase64Key(),
|
||||
Services: "proxy",
|
||||
Policies: []config.Policy{
|
||||
{
|
||||
From: "https://*.example.com",
|
||||
},
|
||||
},
|
||||
}}
|
||||
b := New("grpc", "http", "metrics", filemgr.NewManager(), nil)
|
||||
routeConfiguration, err := b.buildMainRouteConfiguration(ctx, cfg)
|
||||
assert.NoError(t, err)
|
||||
testutil.AssertProtoJSONEqual(t, `{
|
||||
"name": "main",
|
||||
"validateClusters": false,
|
||||
"virtualHosts": [
|
||||
{
|
||||
"name": "catch-all",
|
||||
"domains": ["*"],
|
||||
"routes": [
|
||||
`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/.pomerium/jwt", true, false))+`,
|
||||
`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/.pomerium/webauthn", true, false))+`,
|
||||
`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/ping", false, false))+`,
|
||||
`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/healthz", false, false))+`,
|
||||
`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/.pomerium", false, false))+`,
|
||||
`+protojson.Format(b.buildControlPlanePrefixRoute(cfg.Options, "/.pomerium/", false, false))+`,
|
||||
`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/.well-known/pomerium", false, false))+`,
|
||||
`+protojson.Format(b.buildControlPlanePrefixRoute(cfg.Options, "/.well-known/pomerium/", false, false))+`,
|
||||
`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/robots.txt", false, false))+`,
|
||||
{
|
||||
"name": "policy-0",
|
||||
"match": {
|
||||
"headers": [
|
||||
{ "name": ":authority", "stringMatch": { "safeRegex": { "regex": "^(.*)\\.example\\.com$" } }}
|
||||
],
|
||||
"prefix": "/"
|
||||
},
|
||||
"metadata": {
|
||||
"filterMetadata": {
|
||||
"envoy.filters.http.lua": {
|
||||
"remove_impersonate_headers": false,
|
||||
"remove_pomerium_authorization": true,
|
||||
"remove_pomerium_cookie": "pomerium",
|
||||
"rewrite_response_headers": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"requestHeadersToRemove": [
|
||||
"x-pomerium-jwt-assertion",
|
||||
"x-pomerium-jwt-assertion-for",
|
||||
"x-pomerium-reproxy-policy",
|
||||
"x-pomerium-reproxy-policy-hmac"
|
||||
],
|
||||
"responseHeadersToAdd": [
|
||||
{ "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", "header": { "key": "X-Frame-Options", "value": "SAMEORIGIN" } },
|
||||
{ "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", "header": { "key": "X-XSS-Protection", "value": "1; mode=block" } }
|
||||
],
|
||||
"route": {
|
||||
"autoHostRewrite": true,
|
||||
"cluster": "route-0",
|
||||
"hashPolicy": [
|
||||
{ "header": { "headerName": "x-pomerium-routing-key" }, "terminal": true },
|
||||
{ "connectionProperties": { "sourceIp": true }, "terminal": true }
|
||||
],
|
||||
"timeout": "3s",
|
||||
"upgradeConfigs": [
|
||||
{ "enabled": false, "upgradeType": "websocket" },
|
||||
{ "enabled": false, "upgradeType": "spdy/3.1" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "policy-0",
|
||||
"match": {
|
||||
"headers": [
|
||||
{ "name": ":authority", "stringMatch": { "safeRegex": { "regex": "^(.*)\\.example\\.com:443$" } }}
|
||||
],
|
||||
"prefix": "/"
|
||||
},
|
||||
"metadata": {
|
||||
"filterMetadata": {
|
||||
"envoy.filters.http.lua": {
|
||||
"remove_impersonate_headers": false,
|
||||
"remove_pomerium_authorization": true,
|
||||
"remove_pomerium_cookie": "pomerium",
|
||||
"rewrite_response_headers": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"requestHeadersToRemove": [
|
||||
"x-pomerium-jwt-assertion",
|
||||
"x-pomerium-jwt-assertion-for",
|
||||
"x-pomerium-reproxy-policy",
|
||||
"x-pomerium-reproxy-policy-hmac"
|
||||
],
|
||||
"responseHeadersToAdd": [
|
||||
{ "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", "header": { "key": "X-Frame-Options", "value": "SAMEORIGIN" } },
|
||||
{ "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", "header": { "key": "X-XSS-Protection", "value": "1; mode=block" } }
|
||||
],
|
||||
"route": {
|
||||
"autoHostRewrite": true,
|
||||
"cluster": "route-0",
|
||||
"hashPolicy": [
|
||||
{ "header": { "headerName": "x-pomerium-routing-key" }, "terminal": true },
|
||||
{ "connectionProperties": { "sourceIp": true }, "terminal": true }
|
||||
],
|
||||
"timeout": "3s",
|
||||
"upgradeConfigs": [
|
||||
{ "enabled": false, "upgradeType": "websocket" },
|
||||
{ "enabled": false, "upgradeType": "spdy/3.1" }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
}`, routeConfiguration)
|
||||
}
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||
envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
||||
|
@ -18,6 +19,7 @@ import (
|
|||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -189,14 +191,12 @@ func getClusterStatsName(policy *config.Policy) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (b *Builder) buildPolicyRoutes(
|
||||
options *config.Options,
|
||||
func (b *Builder) buildRoutesForPoliciesWithHost(
|
||||
cfg *config.Config,
|
||||
host string,
|
||||
requireStrictTransportSecurity bool,
|
||||
) ([]*envoy_config_route_v3.Route, error) {
|
||||
var routes []*envoy_config_route_v3.Route
|
||||
|
||||
for i, p := range options.GetAllPolicies() {
|
||||
for i, p := range cfg.Options.GetAllPolicies() {
|
||||
policy := p
|
||||
fromURL, err := urlutil.ParseAndValidateURL(policy.From)
|
||||
if err != nil {
|
||||
|
@ -207,83 +207,162 @@ func (b *Builder) buildPolicyRoutes(
|
|||
continue
|
||||
}
|
||||
|
||||
match := mkRouteMatch(&policy)
|
||||
envoyRoute := &envoy_config_route_v3.Route{
|
||||
Name: fmt.Sprintf("policy-%d", i),
|
||||
Match: match,
|
||||
Metadata: &envoy_config_core_v3.Metadata{},
|
||||
RequestHeadersToAdd: toEnvoyHeaders(policy.SetRequestHeaders),
|
||||
RequestHeadersToRemove: getRequestHeadersToRemove(options, &policy),
|
||||
ResponseHeadersToAdd: toEnvoyHeaders(options.GetSetResponseHeadersForPolicy(&policy, requireStrictTransportSecurity)),
|
||||
}
|
||||
if policy.Redirect != nil {
|
||||
action, err := b.buildPolicyRouteRedirectAction(policy.Redirect)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envoyRoute.Action = &envoy_config_route_v3.Route_Redirect{Redirect: action}
|
||||
} else {
|
||||
action, err := b.buildPolicyRouteRouteAction(options, &policy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envoyRoute.Action = &envoy_config_route_v3.Route_Route{Route: action}
|
||||
}
|
||||
|
||||
luaMetadata := map[string]*structpb.Value{
|
||||
"rewrite_response_headers": getRewriteHeadersMetadata(policy.RewriteResponseHeaders),
|
||||
}
|
||||
|
||||
// disable authentication entirely when the proxy is fronting authenticate
|
||||
isFrontingAuthenticate, err := isProxyFrontingAuthenticate(options, host)
|
||||
policyRoutes, err := b.buildRoutesForPolicy(cfg, &policy, fmt.Sprintf("policy-%d", i))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isFrontingAuthenticate {
|
||||
envoyRoute.TypedPerFilterConfig = map[string]*any.Any{
|
||||
"envoy.filters.http.ext_authz": disableExtAuthz,
|
||||
}
|
||||
} else {
|
||||
luaMetadata["remove_pomerium_cookie"] = &structpb.Value{
|
||||
Kind: &structpb.Value_StringValue{
|
||||
StringValue: options.CookieName,
|
||||
},
|
||||
}
|
||||
luaMetadata["remove_pomerium_authorization"] = &structpb.Value{
|
||||
Kind: &structpb.Value_BoolValue{
|
||||
BoolValue: true,
|
||||
},
|
||||
}
|
||||
luaMetadata["remove_impersonate_headers"] = &structpb.Value{
|
||||
Kind: &structpb.Value_BoolValue{
|
||||
BoolValue: policy.IsForKubernetes(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if policy.IsForKubernetes() {
|
||||
policyID, _ := policy.RouteID()
|
||||
for _, hdr := range b.reproxy.GetPolicyIDHeaders(policyID) {
|
||||
envoyRoute.RequestHeadersToAdd = append(envoyRoute.RequestHeadersToAdd,
|
||||
&envoy_config_core_v3.HeaderValueOption{
|
||||
Header: &envoy_config_core_v3.HeaderValue{
|
||||
Key: hdr[0],
|
||||
Value: hdr[1],
|
||||
},
|
||||
AppendAction: envoy_config_core_v3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
envoyRoute.Metadata.FilterMetadata = map[string]*structpb.Struct{
|
||||
"envoy.filters.http.lua": {Fields: luaMetadata},
|
||||
}
|
||||
|
||||
routes = append(routes, envoyRoute)
|
||||
routes = append(routes, policyRoutes...)
|
||||
}
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
func (b *Builder) buildRoutesForPoliciesWithCatchAll(
|
||||
cfg *config.Config,
|
||||
) ([]*envoy_config_route_v3.Route, error) {
|
||||
var routes []*envoy_config_route_v3.Route
|
||||
for i, p := range cfg.Options.GetAllPolicies() {
|
||||
policy := p
|
||||
fromURL, err := urlutil.ParseAndValidateURL(policy.From)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !strings.Contains(fromURL.Host, "*") {
|
||||
continue
|
||||
}
|
||||
|
||||
policyRoutes, err := b.buildRoutesForPolicy(cfg, &policy, fmt.Sprintf("policy-%d", i))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
routes = append(routes, policyRoutes...)
|
||||
}
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
func (b *Builder) buildRoutesForPolicy(
|
||||
cfg *config.Config,
|
||||
policy *config.Policy,
|
||||
name string,
|
||||
) ([]*envoy_config_route_v3.Route, error) {
|
||||
fromURL, err := urlutil.ParseAndValidateURL(policy.From)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var routes []*envoy_config_route_v3.Route
|
||||
if strings.Contains(fromURL.Host, "*") {
|
||||
// we have to match '*.example.com' and '*.example.com:443', so there are two routes
|
||||
for _, host := range urlutil.GetDomainsForURL(fromURL) {
|
||||
route, err := b.buildRouteForPolicyAndMatch(cfg, policy, name, mkRouteMatchForHost(policy, host))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
routes = append(routes, route)
|
||||
}
|
||||
} else {
|
||||
route, err := b.buildRouteForPolicyAndMatch(cfg, policy, name, mkRouteMatch(policy))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
routes = append(routes, route)
|
||||
}
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
func (b *Builder) buildRouteForPolicyAndMatch(
|
||||
cfg *config.Config,
|
||||
policy *config.Policy,
|
||||
name string,
|
||||
match *envoy_config_route_v3.RouteMatch,
|
||||
) (*envoy_config_route_v3.Route, error) {
|
||||
fromURL, err := urlutil.ParseAndValidateURL(policy.From)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certs, err := getAllCertificates(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
requireStrictTransportSecurity := cryptutil.HasCertificateForServerName(certs, fromURL.Hostname())
|
||||
|
||||
route := &envoy_config_route_v3.Route{
|
||||
Name: name,
|
||||
Match: match,
|
||||
Metadata: &envoy_config_core_v3.Metadata{},
|
||||
RequestHeadersToAdd: toEnvoyHeaders(policy.SetRequestHeaders),
|
||||
RequestHeadersToRemove: getRequestHeadersToRemove(cfg.Options, policy),
|
||||
ResponseHeadersToAdd: toEnvoyHeaders(cfg.Options.GetSetResponseHeadersForPolicy(policy, requireStrictTransportSecurity)),
|
||||
}
|
||||
if policy.Redirect != nil {
|
||||
action, err := b.buildPolicyRouteRedirectAction(policy.Redirect)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
route.Action = &envoy_config_route_v3.Route_Redirect{Redirect: action}
|
||||
} else {
|
||||
action, err := b.buildPolicyRouteRouteAction(cfg.Options, policy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
route.Action = &envoy_config_route_v3.Route_Route{Route: action}
|
||||
}
|
||||
|
||||
luaMetadata := map[string]*structpb.Value{
|
||||
"rewrite_response_headers": getRewriteHeadersMetadata(policy.RewriteResponseHeaders),
|
||||
}
|
||||
|
||||
// disable authentication entirely when the proxy is fronting authenticate
|
||||
isFrontingAuthenticate, err := isProxyFrontingAuthenticate(cfg.Options, fromURL.Hostname())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isFrontingAuthenticate {
|
||||
route.TypedPerFilterConfig = map[string]*any.Any{
|
||||
"envoy.filters.http.ext_authz": disableExtAuthz,
|
||||
}
|
||||
} else {
|
||||
luaMetadata["remove_pomerium_cookie"] = &structpb.Value{
|
||||
Kind: &structpb.Value_StringValue{
|
||||
StringValue: cfg.Options.CookieName,
|
||||
},
|
||||
}
|
||||
luaMetadata["remove_pomerium_authorization"] = &structpb.Value{
|
||||
Kind: &structpb.Value_BoolValue{
|
||||
BoolValue: true,
|
||||
},
|
||||
}
|
||||
luaMetadata["remove_impersonate_headers"] = &structpb.Value{
|
||||
Kind: &structpb.Value_BoolValue{
|
||||
BoolValue: policy.IsForKubernetes(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if policy.IsForKubernetes() {
|
||||
policyID, _ := policy.RouteID()
|
||||
for _, hdr := range b.reproxy.GetPolicyIDHeaders(policyID) {
|
||||
route.RequestHeadersToAdd = append(route.RequestHeadersToAdd,
|
||||
&envoy_config_core_v3.HeaderValueOption{
|
||||
Header: &envoy_config_core_v3.HeaderValue{
|
||||
Key: hdr[0],
|
||||
Value: hdr[1],
|
||||
},
|
||||
AppendAction: envoy_config_core_v3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
route.Metadata.FilterMetadata = map[string]*structpb.Struct{
|
||||
"envoy.filters.http.lua": {Fields: luaMetadata},
|
||||
}
|
||||
return route, nil
|
||||
}
|
||||
|
||||
func (b *Builder) buildPolicyRouteRedirectAction(r *config.PolicyRedirect) (*envoy_config_route_v3.RedirectAction, error) {
|
||||
action := &envoy_config_route_v3.RedirectAction{}
|
||||
switch {
|
||||
|
@ -420,9 +499,6 @@ func mkRouteMatch(policy *config.Policy) *envoy_config_route_v3.RouteMatch {
|
|||
case policy.Regex != "":
|
||||
match.PathSpecifier = &envoy_config_route_v3.RouteMatch_SafeRegex{
|
||||
SafeRegex: &envoy_type_matcher_v3.RegexMatcher{
|
||||
EngineType: &envoy_type_matcher_v3.RegexMatcher_GoogleRe2{
|
||||
GoogleRe2: &envoy_type_matcher_v3.RegexMatcher_GoogleRE2{},
|
||||
},
|
||||
Regex: policy.Regex,
|
||||
},
|
||||
}
|
||||
|
@ -436,6 +512,26 @@ func mkRouteMatch(policy *config.Policy) *envoy_config_route_v3.RouteMatch {
|
|||
return match
|
||||
}
|
||||
|
||||
func mkRouteMatchForHost(
|
||||
policy *config.Policy,
|
||||
host string,
|
||||
) *envoy_config_route_v3.RouteMatch {
|
||||
match := mkRouteMatch(policy)
|
||||
match.Headers = append(match.Headers, &envoy_config_route_v3.HeaderMatcher{
|
||||
Name: ":authority",
|
||||
HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{
|
||||
StringMatch: &envoy_type_matcher_v3.StringMatcher{
|
||||
MatchPattern: &envoy_type_matcher_v3.StringMatcher_SafeRegex{
|
||||
SafeRegex: &envoy_type_matcher_v3.RegexMatcher{
|
||||
Regex: config.WildcardToRegex(host),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return match
|
||||
}
|
||||
|
||||
func getRequestHeadersToRemove(options *config.Options, policy *config.Policy) []string {
|
||||
requestHeadersToRemove := policy.RemoveRequestHeaders
|
||||
if !policy.PassIdentityHeaders {
|
||||
|
@ -489,9 +585,6 @@ func getRewriteOptions(policy *config.Policy) (prefixRewrite string, regexRewrit
|
|||
} else if policy.RegexRewritePattern != "" {
|
||||
regexRewrite = &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.RegexRewritePattern,
|
||||
},
|
||||
Substitution: policy.RegexRewriteSubstitution,
|
||||
|
@ -517,9 +610,6 @@ func setHostRewriteOptions(policy *config.Policy, action *envoy_config_route_v3.
|
|||
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,
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/pomerium/pomerium/config/envoyconfig/filemgr"
|
||||
"github.com/pomerium/pomerium/internal/testutil"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
)
|
||||
|
||||
func policyNameFunc() func(*config.Policy) string {
|
||||
|
@ -293,9 +294,10 @@ func TestTimeouts(t *testing.T) {
|
|||
|
||||
for _, tc := range testCases {
|
||||
b := &Builder{filemgr: filemgr.NewManager()}
|
||||
routes, err := b.buildPolicyRoutes(&config.Options{
|
||||
routes, err := b.buildRoutesForPoliciesWithHost(&config.Config{Options: &config.Options{
|
||||
CookieName: "pomerium",
|
||||
DefaultUpstreamTimeout: time.Second * 3,
|
||||
SharedKey: cryptutil.NewBase64Key(),
|
||||
Policies: []config.Policy{
|
||||
{
|
||||
From: "https://example.com",
|
||||
|
@ -305,7 +307,7 @@ func TestTimeouts(t *testing.T) {
|
|||
AllowWebsockets: tc.allowWebsockets,
|
||||
},
|
||||
},
|
||||
}, "example.com", false)
|
||||
}}, "example.com")
|
||||
if !assert.NoError(t, err, "%v", tc) || !assert.Len(t, routes, 1, tc) || !assert.NotNil(t, routes[0].GetRoute(), "%v", tc) {
|
||||
continue
|
||||
}
|
||||
|
@ -347,9 +349,10 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
|||
ten := time.Second * 10
|
||||
|
||||
b := &Builder{filemgr: filemgr.NewManager()}
|
||||
routes, err := b.buildPolicyRoutes(&config.Options{
|
||||
routes, err := b.buildRoutesForPoliciesWithHost(&config.Config{Options: &config.Options{
|
||||
CookieName: "pomerium",
|
||||
DefaultUpstreamTimeout: time.Second * 3,
|
||||
SharedKey: cryptutil.NewBase64Key(),
|
||||
Policies: []config.Policy{
|
||||
{
|
||||
From: "https://ignore.example.com",
|
||||
|
@ -409,7 +412,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
|||
UpstreamTimeout: &ten,
|
||||
},
|
||||
},
|
||||
}, "example.com", false)
|
||||
}}, "example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
testutil.AssertProtoJSONEqual(t, `
|
||||
|
@ -603,7 +606,6 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
|||
"name": "policy-4",
|
||||
"match": {
|
||||
"safeRegex": {
|
||||
"googleRe2": {},
|
||||
"regex": "^/[a]+$"
|
||||
}
|
||||
},
|
||||
|
@ -904,18 +906,19 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
|||
`, routes)
|
||||
|
||||
t.Run("fronting-authenticate", func(t *testing.T) {
|
||||
routes, err := b.buildPolicyRoutes(&config.Options{
|
||||
routes, err := b.buildRoutesForPoliciesWithHost(&config.Config{Options: &config.Options{
|
||||
AuthenticateURLString: "https://authenticate.example.com",
|
||||
Services: "proxy",
|
||||
CookieName: "pomerium",
|
||||
DefaultUpstreamTimeout: time.Second * 3,
|
||||
SharedKey: cryptutil.NewBase64Key(),
|
||||
Policies: []config.Policy{
|
||||
{
|
||||
From: "https://authenticate.example.com",
|
||||
PassIdentityHeaders: true,
|
||||
},
|
||||
},
|
||||
}, "authenticate.example.com", false)
|
||||
}}, "authenticate.example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
testutil.AssertProtoJSONEqual(t, `
|
||||
|
@ -987,9 +990,10 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("tcp", func(t *testing.T) {
|
||||
routes, err := b.buildPolicyRoutes(&config.Options{
|
||||
routes, err := b.buildRoutesForPoliciesWithHost(&config.Config{Options: &config.Options{
|
||||
CookieName: "pomerium",
|
||||
DefaultUpstreamTimeout: time.Second * 3,
|
||||
SharedKey: cryptutil.NewBase64Key(),
|
||||
Policies: []config.Policy{
|
||||
{
|
||||
From: "tcp+https://example.com:22",
|
||||
|
@ -1001,7 +1005,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
|||
UpstreamTimeout: &ten,
|
||||
},
|
||||
},
|
||||
}, "example.com:22", false)
|
||||
}}, "example.com:22")
|
||||
require.NoError(t, err)
|
||||
|
||||
testutil.AssertProtoJSONEqual(t, `
|
||||
|
@ -1133,11 +1137,12 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("remove-pomerium-headers", func(t *testing.T) {
|
||||
routes, err := b.buildPolicyRoutes(&config.Options{
|
||||
routes, err := b.buildRoutesForPoliciesWithHost(&config.Config{Options: &config.Options{
|
||||
AuthenticateURLString: "https://authenticate.example.com",
|
||||
Services: "proxy",
|
||||
CookieName: "pomerium",
|
||||
DefaultUpstreamTimeout: time.Second * 3,
|
||||
SharedKey: cryptutil.NewBase64Key(),
|
||||
JWTClaimsHeaders: map[string]string{
|
||||
"x-email": "email",
|
||||
},
|
||||
|
@ -1146,7 +1151,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
|
|||
From: "https://from.example.com",
|
||||
},
|
||||
},
|
||||
}, "from.example.com", false)
|
||||
}}, "from.example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
testutil.AssertProtoJSONEqual(t, `
|
||||
|
@ -1224,9 +1229,10 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
|
|||
}(getClusterID)
|
||||
getClusterID = policyNameFunc()
|
||||
b := &Builder{filemgr: filemgr.NewManager()}
|
||||
routes, err := b.buildPolicyRoutes(&config.Options{
|
||||
routes, err := b.buildRoutesForPoliciesWithHost(&config.Config{Options: &config.Options{
|
||||
CookieName: "pomerium",
|
||||
DefaultUpstreamTimeout: time.Second * 3,
|
||||
SharedKey: cryptutil.NewBase64Key(),
|
||||
Policies: []config.Policy{
|
||||
{
|
||||
From: "https://example.com",
|
||||
|
@ -1266,7 +1272,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
|
|||
HostPathRegexRewriteSubstitution: "\\1",
|
||||
},
|
||||
},
|
||||
}, "example.com", false)
|
||||
}}, "example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
testutil.AssertProtoJSONEqual(t, `
|
||||
|
@ -1410,7 +1416,6 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
|
|||
"autoHostRewrite": true,
|
||||
"regexRewrite": {
|
||||
"pattern": {
|
||||
"googleRe2": {},
|
||||
"regex": "^/service/([^/]+)(/.*)$"
|
||||
},
|
||||
"substitution": "\\2/instance/\\1"
|
||||
|
@ -1595,7 +1600,6 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
|
|||
"route": {
|
||||
"hostRewritePathRegex": {
|
||||
"pattern": {
|
||||
"googleRe2": {},
|
||||
"regex": "^/(.+)/.+$"
|
||||
},
|
||||
"substitution": "\\1"
|
||||
|
|
47
config/from.go
Normal file
47
config/from.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
)
|
||||
|
||||
// FromURLMatchesRequestURL returns true if the from URL matches the request URL.
|
||||
func FromURLMatchesRequestURL(fromURL, requestURL *url.URL) bool {
|
||||
for _, domain := range urlutil.GetDomainsForURL(fromURL) {
|
||||
if domain == requestURL.Host {
|
||||
return true
|
||||
}
|
||||
|
||||
if !strings.Contains(domain, "*") {
|
||||
continue
|
||||
}
|
||||
|
||||
reStr := WildcardToRegex(domain)
|
||||
re := regexp.MustCompile(reStr)
|
||||
if re.MatchString(requestURL.Host) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// WildcardToRegex converts a wildcard string to a regular expression.
|
||||
func WildcardToRegex(wildcard string) string {
|
||||
var b strings.Builder
|
||||
b.WriteByte('^')
|
||||
for {
|
||||
idx := strings.IndexByte(wildcard, '*')
|
||||
if idx < 0 {
|
||||
break
|
||||
}
|
||||
b.WriteString(regexp.QuoteMeta(wildcard[:idx]))
|
||||
b.WriteString("(.*)")
|
||||
wildcard = wildcard[idx+1:]
|
||||
}
|
||||
b.WriteString(regexp.QuoteMeta(wildcard))
|
||||
b.WriteByte('$')
|
||||
return b.String()
|
||||
}
|
38
config/from_test.go
Normal file
38
config/from_test.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
)
|
||||
|
||||
func TestFromURLMatchesRequestURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
pattern string
|
||||
input string
|
||||
matches bool
|
||||
}{
|
||||
{"https://from.example.com", "https://from.example.com/some/path", true},
|
||||
{"https://from.example.com", "https://to.example.com/some/path", false},
|
||||
{"https://*.example.com", "https://from.example.com/some/path", true},
|
||||
{"https://*.example.com", "https://example.com/some/path", false},
|
||||
} {
|
||||
fromURL := urlutil.MustParseAndValidateURL(tc.pattern)
|
||||
requestURL := urlutil.MustParseAndValidateURL(tc.input)
|
||||
assert.Equal(t, tc.matches, FromURLMatchesRequestURL(&fromURL, &requestURL),
|
||||
"from-url: %s\nrequest-url: %s", tc.pattern, tc.input)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWildcardToRegex(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
re, err := regexp.Compile(WildcardToRegex("*.internal.*.example.com"))
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, re.MatchString("a.internal.b.example.com"))
|
||||
}
|
|
@ -595,12 +595,7 @@ func (p *Policy) Matches(requestURL url.URL) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// make sure one of the host domains matches the incoming url
|
||||
found := false
|
||||
for _, host := range urlutil.GetDomainsForURL(fromURL) {
|
||||
found = found || host == requestURL.Host
|
||||
}
|
||||
if !found {
|
||||
if !FromURLMatchesRequestURL(fromURL, &requestURL) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
@ -439,7 +440,7 @@ func sourceHostnames(cfg *config.Config) []string {
|
|||
|
||||
dedupe := map[string]struct{}{}
|
||||
for _, p := range policies {
|
||||
if u, _ := urlutil.ParseAndValidateURL(p.From); u != nil {
|
||||
if u, _ := urlutil.ParseAndValidateURL(p.From); u != nil && !strings.Contains(u.Host, "*") {
|
||||
dedupe[u.Hostname()] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue