mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-10 07:37:33 +02:00
authenticate: protect /.pomerium/admin endpoint (#1500)
* authenticate: protect /.pomerium/admin endpoint * add integration test
This commit is contained in:
parent
dc1c83c4de
commit
27d0cf180a
4 changed files with 131 additions and 78 deletions
|
@ -8,6 +8,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/integration/internal/flows"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDashboard(t *testing.T) {
|
func TestDashboard(t *testing.T) {
|
||||||
|
@ -15,6 +17,28 @@ func TestDashboard(t *testing.T) {
|
||||||
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
|
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
|
||||||
defer clearTimeout()
|
defer clearTimeout()
|
||||||
|
|
||||||
|
t.Run("admin impersonate", func(t *testing.T) {
|
||||||
|
client := testcluster.NewHTTPClient()
|
||||||
|
|
||||||
|
res, err := flows.Authenticate(ctx, client, mustParseURL("https://httpdetails.localhost.pomerium.io/by-user"),
|
||||||
|
flows.WithEmail("bob@dogs.test"), flows.WithGroups("user"))
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", "https://httpdetails.localhost.pomerium.io/.pomerium/admin/impersonate", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err = client.Do(req)
|
||||||
|
if !assert.NoError(t, err, "unexpected http error") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
assertDeniedAccess(t, res)
|
||||||
|
})
|
||||||
t.Run("user dashboard", func(t *testing.T) {
|
t.Run("user dashboard", func(t *testing.T) {
|
||||||
client := testcluster.NewHTTPClient()
|
client := testcluster.NewHTTPClient()
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
|
||||||
"domains": ["example.com"],
|
"domains": ["example.com"],
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"name": "pomerium-protected-path-/.pomerium/jwt",
|
"name": "pomerium-path-/.pomerium/jwt",
|
||||||
"match": {
|
"match": {
|
||||||
"path": "/.pomerium/jwt"
|
"path": "/.pomerium/jwt"
|
||||||
},
|
},
|
||||||
|
@ -132,6 +132,24 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "pomerium-path-/.pomerium/admin",
|
||||||
|
"match": {
|
||||||
|
"path": "/.pomerium/admin"
|
||||||
|
},
|
||||||
|
"route": {
|
||||||
|
"cluster": "pomerium-control-plane-http"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pomerium-prefix-/.pomerium/admin/",
|
||||||
|
"match": {
|
||||||
|
"prefix": "/.pomerium/admin/"
|
||||||
|
},
|
||||||
|
"route": {
|
||||||
|
"cluster": "pomerium-control-plane-http"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "pomerium-path-/.pomerium",
|
"name": "pomerium-path-/.pomerium",
|
||||||
"match": {
|
"match": {
|
||||||
|
@ -214,7 +232,7 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
|
||||||
"domains": ["*"],
|
"domains": ["*"],
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"name": "pomerium-protected-path-/.pomerium/jwt",
|
"name": "pomerium-path-/.pomerium/jwt",
|
||||||
"match": {
|
"match": {
|
||||||
"path": "/.pomerium/jwt"
|
"path": "/.pomerium/jwt"
|
||||||
},
|
},
|
||||||
|
@ -252,6 +270,24 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "pomerium-path-/.pomerium/admin",
|
||||||
|
"match": {
|
||||||
|
"path": "/.pomerium/admin"
|
||||||
|
},
|
||||||
|
"route": {
|
||||||
|
"cluster": "pomerium-control-plane-http"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pomerium-prefix-/.pomerium/admin/",
|
||||||
|
"match": {
|
||||||
|
"prefix": "/.pomerium/admin/"
|
||||||
|
},
|
||||||
|
"route": {
|
||||||
|
"cluster": "pomerium-control-plane-http"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "pomerium-path-/.pomerium",
|
"name": "pomerium-path-/.pomerium",
|
||||||
"match": {
|
"match": {
|
||||||
|
|
|
@ -49,22 +49,24 @@ func buildGRPCRoutes() []*envoy_config_route_v3.Route {
|
||||||
func buildPomeriumHTTPRoutes(options *config.Options, domain string) []*envoy_config_route_v3.Route {
|
func buildPomeriumHTTPRoutes(options *config.Options, domain string) []*envoy_config_route_v3.Route {
|
||||||
routes := []*envoy_config_route_v3.Route{
|
routes := []*envoy_config_route_v3.Route{
|
||||||
// enable ext_authz
|
// enable ext_authz
|
||||||
buildControlPlaneProtectedPathRoute("/.pomerium/jwt"),
|
buildControlPlanePathRoute("/.pomerium/jwt", true),
|
||||||
// disable ext_authz and passthrough to proxy handlers
|
// disable ext_authz and passthrough to proxy handlers
|
||||||
buildControlPlanePathRoute("/ping"),
|
buildControlPlanePathRoute("/ping", false),
|
||||||
buildControlPlanePathRoute("/healthz"),
|
buildControlPlanePathRoute("/healthz", false),
|
||||||
buildControlPlanePathRoute("/.pomerium"),
|
buildControlPlanePathRoute("/.pomerium/admin", true),
|
||||||
buildControlPlanePrefixRoute("/.pomerium/"),
|
buildControlPlanePrefixRoute("/.pomerium/admin/", true),
|
||||||
buildControlPlanePathRoute("/.well-known/pomerium"),
|
buildControlPlanePathRoute("/.pomerium", false),
|
||||||
buildControlPlanePrefixRoute("/.well-known/pomerium/"),
|
buildControlPlanePrefixRoute("/.pomerium/", false),
|
||||||
|
buildControlPlanePathRoute("/.well-known/pomerium", false),
|
||||||
|
buildControlPlanePrefixRoute("/.well-known/pomerium/", false),
|
||||||
}
|
}
|
||||||
// per #837, only add robots.txt if there are no unauthenticated routes
|
// per #837, only add robots.txt if there are no unauthenticated routes
|
||||||
if !hasPublicPolicyMatchingURL(options, mustParseURL("https://"+domain+"/robots.txt")) {
|
if !hasPublicPolicyMatchingURL(options, mustParseURL("https://"+domain+"/robots.txt")) {
|
||||||
routes = append(routes, buildControlPlanePathRoute("/robots.txt"))
|
routes = append(routes, buildControlPlanePathRoute("/robots.txt", false))
|
||||||
}
|
}
|
||||||
// if we're handling authentication, add the oauth2 callback url
|
// if we're handling authentication, add the oauth2 callback url
|
||||||
if config.IsAuthenticate(options.Services) && hostMatchesDomain(options.GetAuthenticateURL(), domain) {
|
if config.IsAuthenticate(options.Services) && hostMatchesDomain(options.GetAuthenticateURL(), domain) {
|
||||||
routes = append(routes, buildControlPlanePathRoute(options.AuthenticateCallbackPath))
|
routes = append(routes, buildControlPlanePathRoute(options.AuthenticateCallbackPath, false))
|
||||||
}
|
}
|
||||||
// if we're the proxy and this is the forward-auth url
|
// if we're the proxy and this is the forward-auth url
|
||||||
if config.IsProxy(options.Services) && options.ForwardAuthURL != nil && hostMatchesDomain(options.GetForwardAuthURL(), domain) {
|
if config.IsProxy(options.Services) && options.ForwardAuthURL != nil && hostMatchesDomain(options.GetForwardAuthURL(), domain) {
|
||||||
|
@ -97,22 +99,6 @@ func buildControlPlaneProtectedPrefixRoute(prefix string) *envoy_config_route_v3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildControlPlaneProtectedPathRoute(path string) *envoy_config_route_v3.Route {
|
|
||||||
return &envoy_config_route_v3.Route{
|
|
||||||
Name: "pomerium-protected-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: httpCluster,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildControlPlanePathAndQueryRoute(path string, queryparams []string) *envoy_config_route_v3.Route {
|
func buildControlPlanePathAndQueryRoute(path string, queryparams []string) *envoy_config_route_v3.Route {
|
||||||
var queryParameterMatchers []*envoy_config_route_v3.QueryParameterMatcher
|
var queryParameterMatchers []*envoy_config_route_v3.QueryParameterMatcher
|
||||||
for _, q := range queryparams {
|
for _, q := range queryparams {
|
||||||
|
@ -142,8 +128,8 @@ func buildControlPlanePathAndQueryRoute(path string, queryparams []string) *envo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildControlPlanePathRoute(path string) *envoy_config_route_v3.Route {
|
func buildControlPlanePathRoute(path string, protected bool) *envoy_config_route_v3.Route {
|
||||||
return &envoy_config_route_v3.Route{
|
r := &envoy_config_route_v3.Route{
|
||||||
Name: "pomerium-path-" + path,
|
Name: "pomerium-path-" + path,
|
||||||
Match: &envoy_config_route_v3.RouteMatch{
|
Match: &envoy_config_route_v3.RouteMatch{
|
||||||
PathSpecifier: &envoy_config_route_v3.RouteMatch_Path{Path: path},
|
PathSpecifier: &envoy_config_route_v3.RouteMatch_Path{Path: path},
|
||||||
|
@ -155,14 +141,17 @@ func buildControlPlanePathRoute(path string) *envoy_config_route_v3.Route {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypedPerFilterConfig: map[string]*any.Any{
|
|
||||||
"envoy.filters.http.ext_authz": disableExtAuthz,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
if !protected {
|
||||||
|
r.TypedPerFilterConfig = map[string]*any.Any{
|
||||||
|
"envoy.filters.http.ext_authz": disableExtAuthz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildControlPlanePrefixRoute(prefix string) *envoy_config_route_v3.Route {
|
func buildControlPlanePrefixRoute(prefix string, protected bool) *envoy_config_route_v3.Route {
|
||||||
return &envoy_config_route_v3.Route{
|
r := &envoy_config_route_v3.Route{
|
||||||
Name: "pomerium-prefix-" + prefix,
|
Name: "pomerium-prefix-" + prefix,
|
||||||
Match: &envoy_config_route_v3.RouteMatch{
|
Match: &envoy_config_route_v3.RouteMatch{
|
||||||
PathSpecifier: &envoy_config_route_v3.RouteMatch_Prefix{Prefix: prefix},
|
PathSpecifier: &envoy_config_route_v3.RouteMatch_Prefix{Prefix: prefix},
|
||||||
|
@ -174,10 +163,13 @@ func buildControlPlanePrefixRoute(prefix string) *envoy_config_route_v3.Route {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypedPerFilterConfig: map[string]*any.Any{
|
|
||||||
"envoy.filters.http.ext_authz": disableExtAuthz,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
if !protected {
|
||||||
|
r.TypedPerFilterConfig = map[string]*any.Any{
|
||||||
|
"envoy.filters.http.ext_authz": disableExtAuthz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
var getPolicyName = func(policy *config.Policy) string {
|
var getPolicyName = func(policy *config.Policy) string {
|
||||||
|
|
|
@ -42,33 +42,28 @@ func Test_buildGRPCRoutes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_buildPomeriumHTTPRoutes(t *testing.T) {
|
func Test_buildPomeriumHTTPRoutes(t *testing.T) {
|
||||||
routeString := func(typ, name string) string {
|
routeString := func(typ, name string, protected bool) string {
|
||||||
return `{
|
str := `{
|
||||||
"name": "pomerium-` + typ + `-` + name + `",
|
"name": "pomerium-` + typ + `-` + name + `",
|
||||||
"match": {
|
"match": {
|
||||||
"` + typ + `": "` + name + `"
|
"` + typ + `": "` + name + `"
|
||||||
},
|
},
|
||||||
"route": {
|
"route": {
|
||||||
"cluster": "pomerium-control-plane-http"
|
"cluster": "pomerium-control-plane-http"
|
||||||
},
|
}
|
||||||
|
`
|
||||||
|
if !protected {
|
||||||
|
str += `,
|
||||||
"typedPerFilterConfig": {
|
"typedPerFilterConfig": {
|
||||||
"envoy.filters.http.ext_authz": {
|
"envoy.filters.http.ext_authz": {
|
||||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
|
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
|
||||||
"disabled": true
|
"disabled": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`
|
`
|
||||||
}
|
}
|
||||||
protectedRouteString := func(typ, name string) string {
|
str += "}"
|
||||||
return `{
|
return str
|
||||||
"name": "pomerium-protected-` + typ + `-` + name + `",
|
|
||||||
"match": {
|
|
||||||
"` + typ + `": "` + name + `"
|
|
||||||
},
|
|
||||||
"route": {
|
|
||||||
"cluster": "pomerium-control-plane-http"
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
}
|
}
|
||||||
t.Run("authenticate", func(t *testing.T) {
|
t.Run("authenticate", func(t *testing.T) {
|
||||||
options := &config.Options{
|
options := &config.Options{
|
||||||
|
@ -80,15 +75,17 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
|
||||||
routes := buildPomeriumHTTPRoutes(options, "authenticate.example.com")
|
routes := buildPomeriumHTTPRoutes(options, "authenticate.example.com")
|
||||||
|
|
||||||
testutil.AssertProtoJSONEqual(t, `[
|
testutil.AssertProtoJSONEqual(t, `[
|
||||||
`+protectedRouteString("path", "/.pomerium/jwt")+`,
|
`+routeString("path", "/.pomerium/jwt", true)+`,
|
||||||
`+routeString("path", "/ping")+`,
|
`+routeString("path", "/ping", false)+`,
|
||||||
`+routeString("path", "/healthz")+`,
|
`+routeString("path", "/healthz", false)+`,
|
||||||
`+routeString("path", "/.pomerium")+`,
|
`+routeString("path", "/.pomerium/admin", true)+`,
|
||||||
`+routeString("prefix", "/.pomerium/")+`,
|
`+routeString("prefix", "/.pomerium/admin/", true)+`,
|
||||||
`+routeString("path", "/.well-known/pomerium")+`,
|
`+routeString("path", "/.pomerium", false)+`,
|
||||||
`+routeString("prefix", "/.well-known/pomerium/")+`,
|
`+routeString("prefix", "/.pomerium/", false)+`,
|
||||||
`+routeString("path", "/robots.txt")+`,
|
`+routeString("path", "/.well-known/pomerium", false)+`,
|
||||||
`+routeString("path", "/oauth2/callback")+`
|
`+routeString("prefix", "/.well-known/pomerium/", false)+`,
|
||||||
|
`+routeString("path", "/robots.txt", false)+`,
|
||||||
|
`+routeString("path", "/oauth2/callback", false)+`
|
||||||
]`, routes)
|
]`, routes)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -107,14 +104,16 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
|
||||||
routes := buildPomeriumHTTPRoutes(options, "from.example.com")
|
routes := buildPomeriumHTTPRoutes(options, "from.example.com")
|
||||||
|
|
||||||
testutil.AssertProtoJSONEqual(t, `[
|
testutil.AssertProtoJSONEqual(t, `[
|
||||||
`+protectedRouteString("path", "/.pomerium/jwt")+`,
|
`+routeString("path", "/.pomerium/jwt", true)+`,
|
||||||
`+routeString("path", "/ping")+`,
|
`+routeString("path", "/ping", false)+`,
|
||||||
`+routeString("path", "/healthz")+`,
|
`+routeString("path", "/healthz", false)+`,
|
||||||
`+routeString("path", "/.pomerium")+`,
|
`+routeString("path", "/.pomerium/admin", true)+`,
|
||||||
`+routeString("prefix", "/.pomerium/")+`,
|
`+routeString("prefix", "/.pomerium/admin/", true)+`,
|
||||||
`+routeString("path", "/.well-known/pomerium")+`,
|
`+routeString("path", "/.pomerium", false)+`,
|
||||||
`+routeString("prefix", "/.well-known/pomerium/")+`,
|
`+routeString("prefix", "/.pomerium/", false)+`,
|
||||||
`+routeString("path", "/robots.txt")+`
|
`+routeString("path", "/.well-known/pomerium", false)+`,
|
||||||
|
`+routeString("prefix", "/.well-known/pomerium/", false)+`,
|
||||||
|
`+routeString("path", "/robots.txt", false)+`
|
||||||
]`, routes)
|
]`, routes)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -134,19 +133,21 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
|
||||||
routes := buildPomeriumHTTPRoutes(options, "from.example.com")
|
routes := buildPomeriumHTTPRoutes(options, "from.example.com")
|
||||||
|
|
||||||
testutil.AssertProtoJSONEqual(t, `[
|
testutil.AssertProtoJSONEqual(t, `[
|
||||||
`+protectedRouteString("path", "/.pomerium/jwt")+`,
|
`+routeString("path", "/.pomerium/jwt", true)+`,
|
||||||
`+routeString("path", "/ping")+`,
|
`+routeString("path", "/ping", false)+`,
|
||||||
`+routeString("path", "/healthz")+`,
|
`+routeString("path", "/healthz", false)+`,
|
||||||
`+routeString("path", "/.pomerium")+`,
|
`+routeString("path", "/.pomerium/admin", true)+`,
|
||||||
`+routeString("prefix", "/.pomerium/")+`,
|
`+routeString("prefix", "/.pomerium/admin/", true)+`,
|
||||||
`+routeString("path", "/.well-known/pomerium")+`,
|
`+routeString("path", "/.pomerium", false)+`,
|
||||||
`+routeString("prefix", "/.well-known/pomerium/")+`
|
`+routeString("prefix", "/.pomerium/", false)+`,
|
||||||
|
`+routeString("path", "/.well-known/pomerium", false)+`,
|
||||||
|
`+routeString("prefix", "/.well-known/pomerium/", false)+`
|
||||||
]`, routes)
|
]`, routes)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_buildControlPlanePathRoute(t *testing.T) {
|
func Test_buildControlPlanePathRoute(t *testing.T) {
|
||||||
route := buildControlPlanePathRoute("/hello/world")
|
route := buildControlPlanePathRoute("/hello/world", false)
|
||||||
testutil.AssertProtoJSONEqual(t, `
|
testutil.AssertProtoJSONEqual(t, `
|
||||||
{
|
{
|
||||||
"name": "pomerium-path-/hello/world",
|
"name": "pomerium-path-/hello/world",
|
||||||
|
@ -167,7 +168,7 @@ func Test_buildControlPlanePathRoute(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_buildControlPlanePrefixRoute(t *testing.T) {
|
func Test_buildControlPlanePrefixRoute(t *testing.T) {
|
||||||
route := buildControlPlanePrefixRoute("/hello/world/")
|
route := buildControlPlanePrefixRoute("/hello/world/", false)
|
||||||
testutil.AssertProtoJSONEqual(t, `
|
testutil.AssertProtoJSONEqual(t, `
|
||||||
{
|
{
|
||||||
"name": "pomerium-prefix-/hello/world/",
|
"name": "pomerium-prefix-/hello/world/",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue