proxy: implement pass-through for authenticate backend (#1870)

* proxy: implement pass-through for authenticate backend

* address comments
This commit is contained in:
Caleb Doxsey 2021-02-09 14:03:54 -07:00 committed by GitHub
parent 4bf5179bb6
commit 963399b53d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 153 additions and 69 deletions

View file

@ -49,51 +49,60 @@ func (srv *Server) buildGRPCRoutes() ([]*envoy_config_route_v3.Route, error) {
func (srv *Server) buildPomeriumHTTPRoutes(options *config.Options, domain string) ([]*envoy_config_route_v3.Route, error) { func (srv *Server) buildPomeriumHTTPRoutes(options *config.Options, domain string) ([]*envoy_config_route_v3.Route, error) {
var routes []*envoy_config_route_v3.Route var routes []*envoy_config_route_v3.Route
// enable ext_authz
r, err := srv.buildControlPlanePathRoute("/.pomerium/jwt", true)
if err != nil {
return nil, err
}
routes = append(routes, r)
// disable ext_authz and passthrough to proxy handlers // if this is the pomerium proxy in front of the the authenticate service, don't add
r, err = srv.buildControlPlanePathRoute("/ping", false) // these routes since they will be handled by authenticate
isFrontingAuthenticate, err := isProxyFrontingAuthenticate(options, domain)
if err != nil { if err != nil {
return nil, err return nil, err
} }
routes = append(routes, r) if !isFrontingAuthenticate {
r, err = srv.buildControlPlanePathRoute("/healthz", false) // enable ext_authz
if err != nil { r, err := srv.buildControlPlanePathRoute("/.pomerium/jwt", true)
return nil, err
}
routes = append(routes, r)
r, err = srv.buildControlPlanePathRoute("/.pomerium", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
r, err = srv.buildControlPlanePrefixRoute("/.pomerium/", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
r, err = srv.buildControlPlanePathRoute("/.well-known/pomerium", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
r, err = srv.buildControlPlanePrefixRoute("/.well-known/pomerium/", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
// per #837, only add robots.txt if there are no unauthenticated routes
if !hasPublicPolicyMatchingURL(options, url.URL{Scheme: "https", Host: domain, Path: "/robots.txt"}) {
r, err := srv.buildControlPlanePathRoute("/robots.txt", false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
routes = append(routes, r) routes = append(routes, r)
// disable ext_authz and passthrough to proxy handlers
r, err = srv.buildControlPlanePathRoute("/ping", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
r, err = srv.buildControlPlanePathRoute("/healthz", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
r, err = srv.buildControlPlanePathRoute("/.pomerium", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
r, err = srv.buildControlPlanePrefixRoute("/.pomerium/", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
r, err = srv.buildControlPlanePathRoute("/.well-known/pomerium", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
r, err = srv.buildControlPlanePrefixRoute("/.well-known/pomerium/", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
// per #837, only add robots.txt if there are no unauthenticated routes
if !hasPublicPolicyMatchingURL(options, url.URL{Scheme: "https", Host: domain, Path: "/robots.txt"}) {
r, err := srv.buildControlPlanePathRoute("/robots.txt", false)
if err != nil {
return nil, err
}
routes = append(routes, r)
}
} }
// if we're handling authentication, add the oauth2 callback url // if we're handling authentication, add the oauth2 callback url
authenticateURL, err := options.GetAuthenticateURL() authenticateURL, err := options.GetAuthenticateURL()
@ -260,37 +269,12 @@ func (srv *Server) buildPolicyRoutes(options *config.Options, domain string) ([]
} }
match := mkRouteMatch(&policy) match := mkRouteMatch(&policy)
requestHeadersToAdd := toEnvoyHeaders(policy.SetRequestHeaders)
requestHeadersToRemove := getRequestHeadersToRemove(options, &policy)
envoyRoute := &envoy_config_route_v3.Route{ envoyRoute := &envoy_config_route_v3.Route{
Name: fmt.Sprintf("policy-%d", i), Name: fmt.Sprintf("policy-%d", i),
Match: match, Match: match,
Metadata: &envoy_config_core_v3.Metadata{ Metadata: &envoy_config_core_v3.Metadata{},
FilterMetadata: map[string]*structpb.Struct{ RequestHeadersToAdd: toEnvoyHeaders(policy.SetRequestHeaders),
"envoy.filters.http.lua": { RequestHeadersToRemove: getRequestHeadersToRemove(options, &policy),
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 != "",
},
},
},
},
},
},
RequestHeadersToAdd: requestHeadersToAdd,
RequestHeadersToRemove: requestHeadersToRemove,
} }
if policy.Redirect != nil { if policy.Redirect != nil {
action, err := srv.buildPolicyRouteRedirectAction(policy.Redirect) action, err := srv.buildPolicyRouteRedirectAction(policy.Redirect)
@ -306,6 +290,39 @@ func (srv *Server) buildPolicyRoutes(options *config.Options, domain string) ([]
envoyRoute.Action = &envoy_config_route_v3.Route_Route{Route: action} envoyRoute.Action = &envoy_config_route_v3.Route_Route{Route: action}
} }
// disable authentication entirely when the proxy is fronting authenticate
isFrontingAuthenticate, err := isProxyFrontingAuthenticate(options, domain)
if err != nil {
return nil, err
}
if isFrontingAuthenticate {
envoyRoute.TypedPerFilterConfig = map[string]*any.Any{
"envoy.filters.http.ext_authz": disableExtAuthz,
}
} else {
envoyRoute.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 != "",
},
},
},
},
}
}
routes = append(routes, envoyRoute) routes = append(routes, envoyRoute)
} }
return routes, nil return routes, nil
@ -529,3 +546,16 @@ func hasPublicPolicyMatchingURL(options *config.Options, requestURL url.URL) boo
} }
return false return false
} }
func isProxyFrontingAuthenticate(options *config.Options, domain string) (bool, error) {
authenticateURL, err := options.GetAuthenticateURL()
if err != nil {
return false, err
}
if !config.IsAuthenticate(options.Services) && hostMatchesDomain(authenticateURL, domain) {
return true, nil
}
return false, nil
}

View file

@ -99,6 +99,16 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
`+routeString("path", "/oauth2/callback", false)+` `+routeString("path", "/oauth2/callback", false)+`
]`, routes) ]`, routes)
}) })
t.Run("proxy fronting authenticate", func(t *testing.T) {
options := &config.Options{
Services: "proxy",
AuthenticateURL: mustParseURL(t, "https://authenticate.example.com"),
AuthenticateCallbackPath: "/oauth2/callback",
}
routes, err := srv.buildPomeriumHTTPRoutes(options, "authenticate.example.com")
require.NoError(t, err)
testutil.AssertProtoJSONEqual(t, "null", routes)
})
t.Run("with robots", func(t *testing.T) { t.Run("with robots", func(t *testing.T) {
options := &config.Options{ options := &config.Options{
@ -484,6 +494,50 @@ func Test_buildPolicyRoutes(t *testing.T) {
] ]
`, routes) `, routes)
t.Run("fronting-authenticate", func(t *testing.T) {
routes, err := srv.buildPolicyRoutes(&config.Options{
AuthenticateURL: mustParseURL(t, "https://authenticate.example.com"),
Services: "proxy",
CookieName: "pomerium",
DefaultUpstreamTimeout: time.Second * 3,
Policies: []config.Policy{
{
Source: &config.StringURL{URL: mustParseURL(t, "https://authenticate.example.com")},
PassIdentityHeaders: true,
},
},
}, "authenticate.example.com")
require.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `
[
{
"name": "policy-0",
"match": {
"prefix": "/"
},
"metadata": {
},
"route": {
"autoHostRewrite": true,
"cluster": "policy-9",
"timeout": "3s",
"upgradeConfigs": [
{ "enabled": false, "upgradeType": "websocket"},
{ "enabled": false, "upgradeType": "spdy/3.1"}
]
},
"typedPerFilterConfig": {
"envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true
}
}
}
]
`, routes)
})
t.Run("tcp", func(t *testing.T) { t.Run("tcp", func(t *testing.T) {
routes, err := srv.buildPolicyRoutes(&config.Options{ routes, err := srv.buildPolicyRoutes(&config.Options{
CookieName: "pomerium", CookieName: "pomerium",
@ -520,7 +574,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
}, },
"route": { "route": {
"autoHostRewrite": true, "autoHostRewrite": true,
"cluster": "policy-9", "cluster": "policy-10",
"idleTimeout": "0s", "idleTimeout": "0s",
"timeout": "0s", "timeout": "0s",
"upgradeConfigs": [ "upgradeConfigs": [
@ -546,7 +600,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
}, },
"route": { "route": {
"autoHostRewrite": true, "autoHostRewrite": true,
"cluster": "policy-10", "cluster": "policy-11",
"idleTimeout": "0s", "idleTimeout": "0s",
"timeout": "10s", "timeout": "10s",
"upgradeConfigs": [ "upgradeConfigs": [