mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-30 02:46:30 +02:00
287 lines
9.5 KiB
Go
287 lines
9.5 KiB
Go
package controlplane
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
|
|
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"
|
|
envoy_type_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
|
|
"github.com/golang/protobuf/ptypes"
|
|
"github.com/golang/protobuf/ptypes/any"
|
|
"github.com/golang/protobuf/ptypes/wrappers"
|
|
"google.golang.org/protobuf/types/known/durationpb"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
|
|
"github.com/pomerium/pomerium/config"
|
|
"github.com/pomerium/pomerium/internal/httputil"
|
|
)
|
|
|
|
func buildGRPCRoutes() []*envoy_config_route_v3.Route {
|
|
action := &envoy_config_route_v3.Route_Route{
|
|
Route: &envoy_config_route_v3.RouteAction{
|
|
ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{
|
|
Cluster: "pomerium-control-plane-grpc",
|
|
},
|
|
},
|
|
}
|
|
return []*envoy_config_route_v3.Route{{
|
|
Name: "pomerium-grpc",
|
|
Match: &envoy_config_route_v3.RouteMatch{
|
|
PathSpecifier: &envoy_config_route_v3.RouteMatch_Prefix{
|
|
Prefix: "/",
|
|
},
|
|
Grpc: &envoy_config_route_v3.RouteMatch_GrpcRouteMatchOptions{},
|
|
},
|
|
Action: action,
|
|
TypedPerFilterConfig: map[string]*any.Any{
|
|
"envoy.filters.http.ext_authz": disableExtAuthz,
|
|
},
|
|
}}
|
|
}
|
|
|
|
func buildPomeriumHTTPRoutes(options *config.Options, domain string) []*envoy_config_route_v3.Route {
|
|
routes := []*envoy_config_route_v3.Route{
|
|
buildControlPlanePathRoute("/ping"),
|
|
buildControlPlanePathRoute("/healthz"),
|
|
buildControlPlanePathRoute("/.pomerium"),
|
|
buildControlPlanePrefixRoute("/.pomerium/"),
|
|
buildControlPlanePathRoute("/.well-known/pomerium"),
|
|
buildControlPlanePrefixRoute("/.well-known/pomerium/"),
|
|
}
|
|
// per #837, only add robots.txt if there are no unauthenticated routes
|
|
if !hasPublicPolicyMatchingURL(options, mustParseURL("https://"+domain+"/robots.txt")) {
|
|
routes = append(routes, buildControlPlanePathRoute("/robots.txt"))
|
|
}
|
|
// if we're handling authentication, add the oauth2 callback url
|
|
if config.IsAuthenticate(options.Services) && hostMatchesDomain(options.GetAuthenticateURL(), domain) {
|
|
routes = append(routes, buildControlPlanePathRoute(options.AuthenticateCallbackPath))
|
|
}
|
|
// if we're the proxy and this is the forward-auth url
|
|
if config.IsProxy(options.Services) && options.ForwardAuthURL != nil && hostMatchesDomain(options.GetForwardAuthURL(), domain) {
|
|
routes = append(routes, buildControlPlanePrefixRoute("/"))
|
|
}
|
|
return routes
|
|
}
|
|
|
|
func buildControlPlanePathRoute(path string) *envoy_config_route_v3.Route {
|
|
return &envoy_config_route_v3.Route{
|
|
Name: "pomerium-path-" + path,
|
|
Match: &envoy_config_route_v3.RouteMatch{
|
|
PathSpecifier: &envoy_config_route_v3.RouteMatch_Path{Path: path},
|
|
},
|
|
Action: &envoy_config_route_v3.Route_Route{
|
|
Route: &envoy_config_route_v3.RouteAction{
|
|
ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{
|
|
Cluster: "pomerium-control-plane-http",
|
|
},
|
|
},
|
|
},
|
|
TypedPerFilterConfig: map[string]*any.Any{
|
|
"envoy.filters.http.ext_authz": disableExtAuthz,
|
|
},
|
|
}
|
|
}
|
|
|
|
func buildControlPlanePrefixRoute(prefix string) *envoy_config_route_v3.Route {
|
|
return &envoy_config_route_v3.Route{
|
|
Name: "pomerium-prefix-" + prefix,
|
|
Match: &envoy_config_route_v3.RouteMatch{
|
|
PathSpecifier: &envoy_config_route_v3.RouteMatch_Prefix{Prefix: prefix},
|
|
},
|
|
Action: &envoy_config_route_v3.Route_Route{
|
|
Route: &envoy_config_route_v3.RouteAction{
|
|
ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{
|
|
Cluster: "pomerium-control-plane-http",
|
|
},
|
|
},
|
|
},
|
|
TypedPerFilterConfig: map[string]*any.Any{
|
|
"envoy.filters.http.ext_authz": disableExtAuthz,
|
|
},
|
|
}
|
|
}
|
|
|
|
var getPolicyName = func(policy *config.Policy) string {
|
|
return fmt.Sprintf("policy-%x", policy.RouteID())
|
|
}
|
|
|
|
func buildPolicyRoutes(options *config.Options, domain string) []*envoy_config_route_v3.Route {
|
|
var routes []*envoy_config_route_v3.Route
|
|
responseHeadersToAdd := toEnvoyHeaders(options.Headers)
|
|
|
|
for i, policy := range options.Policies {
|
|
if !hostMatchesDomain(policy.Source.URL, domain) {
|
|
continue
|
|
}
|
|
|
|
match := mkRouteMatch(&policy)
|
|
clusterName := getPolicyName(&policy)
|
|
requestHeadersToAdd := toEnvoyHeaders(policy.SetRequestHeaders)
|
|
requestHeadersToRemove := getRequestHeadersToRemove(options, &policy)
|
|
routeTimeout := getRouteTimeout(options, &policy)
|
|
prefixRewrite, regexRewrite := getRewriteOptions(&policy)
|
|
|
|
routes = append(routes, &envoy_config_route_v3.Route{
|
|
Name: fmt.Sprintf("policy-%d", i),
|
|
Match: match,
|
|
Metadata: &envoy_config_core_v3.Metadata{
|
|
FilterMetadata: map[string]*structpb.Struct{
|
|
"envoy.filters.http.lua": {
|
|
Fields: map[string]*structpb.Value{
|
|
"remove_pomerium_cookie": {
|
|
Kind: &structpb.Value_StringValue{
|
|
StringValue: options.CookieName,
|
|
},
|
|
},
|
|
"remove_pomerium_authorization": {
|
|
Kind: &structpb.Value_BoolValue{
|
|
BoolValue: true,
|
|
},
|
|
},
|
|
"remove_impersonate_headers": {
|
|
Kind: &structpb.Value_BoolValue{
|
|
BoolValue: policy.KubernetesServiceAccountTokenFile != "" || policy.KubernetesServiceAccountToken != "",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
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,
|
|
},
|
|
},
|
|
RequestHeadersToAdd: requestHeadersToAdd,
|
|
RequestHeadersToRemove: requestHeadersToRemove,
|
|
ResponseHeadersToAdd: responseHeadersToAdd,
|
|
})
|
|
}
|
|
return routes
|
|
}
|
|
|
|
func mkEnvoyHeader(k, v string) *envoy_config_core_v3.HeaderValueOption {
|
|
return &envoy_config_core_v3.HeaderValueOption{
|
|
Header: &envoy_config_core_v3.HeaderValue{
|
|
Key: k,
|
|
Value: v,
|
|
},
|
|
Append: &wrappers.BoolValue{Value: false},
|
|
}
|
|
}
|
|
|
|
func toEnvoyHeaders(headers map[string]string) []*envoy_config_core_v3.HeaderValueOption {
|
|
envoyHeaders := make([]*envoy_config_core_v3.HeaderValueOption, 0, len(headers))
|
|
for k, v := range headers {
|
|
envoyHeaders = append(envoyHeaders, mkEnvoyHeader(k, v))
|
|
}
|
|
return envoyHeaders
|
|
}
|
|
|
|
func mkRouteMatch(policy *config.Policy) *envoy_config_route_v3.RouteMatch {
|
|
match := &envoy_config_route_v3.RouteMatch{}
|
|
switch {
|
|
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,
|
|
},
|
|
}
|
|
case policy.Path != "":
|
|
match.PathSpecifier = &envoy_config_route_v3.RouteMatch_Path{Path: policy.Path}
|
|
case policy.Prefix != "":
|
|
match.PathSpecifier = &envoy_config_route_v3.RouteMatch_Prefix{Prefix: policy.Prefix}
|
|
default:
|
|
match.PathSpecifier = &envoy_config_route_v3.RouteMatch_Prefix{Prefix: "/"}
|
|
}
|
|
return match
|
|
}
|
|
|
|
func getRequestHeadersToRemove(options *config.Options, policy *config.Policy) []string {
|
|
requestHeadersToRemove := policy.RemoveRequestHeaders
|
|
if !policy.PassIdentityHeaders {
|
|
requestHeadersToRemove = append(requestHeadersToRemove, httputil.HeaderPomeriumJWTAssertion)
|
|
for _, claim := range options.JWTClaimsHeaders {
|
|
requestHeadersToRemove = append(requestHeadersToRemove, httputil.PomeriumJWTHeaderName(claim))
|
|
}
|
|
}
|
|
return requestHeadersToRemove
|
|
}
|
|
|
|
func getRouteTimeout(options *config.Options, policy *config.Policy) *durationpb.Duration {
|
|
var routeTimeout *durationpb.Duration
|
|
if policy.AllowWebsockets {
|
|
if policy.UpstreamTimeout != 0 {
|
|
routeTimeout = ptypes.DurationProto(policy.UpstreamTimeout)
|
|
} else {
|
|
// disable the default route timeout for websocket support
|
|
routeTimeout = ptypes.DurationProto(0)
|
|
}
|
|
} else {
|
|
if policy.UpstreamTimeout != 0 {
|
|
routeTimeout = ptypes.DurationProto(policy.UpstreamTimeout)
|
|
} else {
|
|
routeTimeout = ptypes.DurationProto(options.DefaultUpstreamTimeout)
|
|
}
|
|
}
|
|
return routeTimeout
|
|
}
|
|
|
|
func getRewriteOptions(policy *config.Policy) (prefixRewrite string, regexRewrite *envoy_type_matcher_v3.RegexMatchAndSubstitute) {
|
|
if policy.PrefixRewrite != "" {
|
|
prefixRewrite = policy.PrefixRewrite
|
|
} 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,
|
|
}
|
|
} else if policy.Destination != nil && policy.Destination.Path != "" {
|
|
prefixRewrite = policy.Destination.Path
|
|
}
|
|
|
|
return prefixRewrite, regexRewrite
|
|
}
|
|
|
|
func hasPublicPolicyMatchingURL(options *config.Options, requestURL *url.URL) bool {
|
|
for _, policy := range options.Policies {
|
|
if policy.AllowPublicUnauthenticatedAccess && policy.Matches(requestURL) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func mustParseURL(str string) *url.URL {
|
|
u, err := url.Parse(str)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return u
|
|
}
|