diff --git a/authenticate/handlers.go b/authenticate/handlers.go index ac8b619bf..f9fb15ed3 100644 --- a/authenticate/handlers.go +++ b/authenticate/handlers.go @@ -113,28 +113,7 @@ func (a *Authenticate) mountDashboard(r *mux.Router) { } func (a *Authenticate) mountWellKnown(r *mux.Router) { - wk := r.PathPrefix("/.well-known/pomerium").Subrouter() - wk.Path("/jwks.json").Handler(httputil.HandlerFunc(a.jwks)).Methods(http.MethodGet) - wk.Path("/").Handler(httputil.HandlerFunc(a.wellKnown)).Methods(http.MethodGet) -} - -// wellKnown returns a list of well known URLS for Pomerium. -// -// https://en.wikipedia.org/wiki/List_of_/.well-known/_services_offered_by_webservers -func (a *Authenticate) wellKnown(w http.ResponseWriter, r *http.Request) error { - state := a.state.Load() - wellKnownURLS := struct { - OAuth2Callback string `json:"authentication_callback_endpoint"` // RFC6749 - JSONWebKeySetURL string `json:"jwks_uri"` // RFC7517 - FrontchannelLogoutURI string `json:"frontchannel_logout_uri"` // https://openid.net/specs/openid-connect-frontchannel-1_0.html - }{ - state.redirectURL.ResolveReference(&url.URL{Path: "/oauth2/callback"}).String(), - state.redirectURL.ResolveReference(&url.URL{Path: "/.well-known/pomerium/jwks.json"}).String(), - state.redirectURL.ResolveReference(&url.URL{Path: "/.pomerium/sign_out"}).String(), - } - w.Header().Set("X-CSRF-Token", csrf.Token(r)) - httputil.RenderJSON(w, http.StatusOK, wellKnownURLS) - return nil + r.Path("/.well-known/pomerium/jwks.json").Handler(httputil.HandlerFunc(a.jwks)).Methods(http.MethodGet) } // jwks returns the signing key(s) the client can use to validate signatures diff --git a/authenticate/handlers_test.go b/authenticate/handlers_test.go index 71fadd942..e39abe0db 100644 --- a/authenticate/handlers_test.go +++ b/authenticate/handlers_test.go @@ -601,22 +601,6 @@ func TestAuthenticate_SessionValidatorMiddleware(t *testing.T) { } } -func TestWellKnownEndpoint(t *testing.T) { - auth := testAuthenticate() - - h := auth.Handler() - if h == nil { - t.Error("handler cannot be nil") - } - req := httptest.NewRequest("GET", "/.well-known/pomerium/", nil) - req.Header.Set("Accept", "application/json") - rr := httptest.NewRecorder() - h.ServeHTTP(rr, req) - body := rr.Body.String() - expected := "{\"authentication_callback_endpoint\":\"https://auth.example.com/oauth2/callback\",\"jwks_uri\":\"https://auth.example.com/.well-known/pomerium/jwks.json\",\"frontchannel_logout_uri\":\"https://auth.example.com/.pomerium/sign_out\"}\n" - assert.Equal(t, body, expected) -} - func TestJwksEndpoint(t *testing.T) { o := newTestOptions(t) o.SigningKey = "LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUpCMFZkbko1VjEvbVlpYUlIWHhnd2Q0Yzd5YWRTeXMxb3Y0bzA1b0F3ekdvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFVUc1eENQMEpUVDFINklvbDhqS3VUSVBWTE0wNENnVzlQbEV5cE5SbVdsb29LRVhSOUhUMwpPYnp6aktZaWN6YjArMUt3VjJmTVRFMTh1dy82MXJVQ0JBPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=" diff --git a/internal/controlplane/http.go b/internal/controlplane/http.go index 084a70002..468974a60 100644 --- a/internal/controlplane/http.go +++ b/internal/controlplane/http.go @@ -3,19 +3,22 @@ package controlplane import ( "net/http" + "net/url" "time" "github.com/CAFxX/httpcompression" "github.com/gorilla/handlers" "github.com/gorilla/mux" + "github.com/pomerium/csrf" + "github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/internal/httputil" "github.com/pomerium/pomerium/internal/log" "github.com/pomerium/pomerium/internal/telemetry" "github.com/pomerium/pomerium/internal/telemetry/requestid" ) -func (srv *Server) addHTTPMiddleware(root *mux.Router) { +func (srv *Server) addHTTPMiddleware(root *mux.Router, cfg *config.Config) { compressor, err := httpcompression.DefaultAdapter() if err != nil { panic(err) @@ -46,4 +49,30 @@ func (srv *Server) addHTTPMiddleware(root *mux.Router) { }, srv.name)) root.HandleFunc("/healthz", httputil.HealthCheck) root.HandleFunc("/ping", httputil.HealthCheck) + root.Handle("/.well-known/pomerium", httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { + return wellKnownPomerium(w, r, cfg) + })) + root.Handle("/.well-known/pomerium/", httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { + return wellKnownPomerium(w, r, cfg) + })) +} + +func wellKnownPomerium(w http.ResponseWriter, r *http.Request, cfg *config.Config) error { + authenticateURL, err := cfg.Options.GetAuthenticateURL() + if err != nil { + return err + } + + wellKnownURLs := struct { + OAuth2Callback string `json:"authentication_callback_endpoint"` // RFC6749 + JSONWebKeySetURL string `json:"jwks_uri"` // RFC7517 + FrontchannelLogoutURI string `json:"frontchannel_logout_uri"` // https://openid.net/specs/openid-connect-frontchannel-1_0.html + }{ + authenticateURL.ResolveReference(&url.URL{Path: "/oauth2/callback"}).String(), + authenticateURL.ResolveReference(&url.URL{Path: "/.well-known/pomerium/jwks.json"}).String(), + authenticateURL.ResolveReference(&url.URL{Path: "/.pomerium/sign_out"}).String(), + } + w.Header().Set("X-CSRF-Token", csrf.Token(r)) + httputil.RenderJSON(w, http.StatusOK, wellKnownURLs) + return nil } diff --git a/internal/controlplane/server.go b/internal/controlplane/server.go index d73301df7..057c9571e 100644 --- a/internal/controlplane/server.go +++ b/internal/controlplane/server.go @@ -284,7 +284,7 @@ func (srv *Server) EnableProxy(svc Service) error { func (srv *Server) updateRouter(cfg *config.Config) error { httpRouter := mux.NewRouter() - srv.addHTTPMiddleware(httpRouter) + srv.addHTTPMiddleware(httpRouter, cfg) if srv.authenticateSvc != nil { authenticateURL, err := cfg.Options.GetInternalAuthenticateURL() if err != nil { diff --git a/internal/controlplane/server_test.go b/internal/controlplane/server_test.go new file mode 100644 index 000000000..5cd5fe5e2 --- /dev/null +++ b/internal/controlplane/server_test.go @@ -0,0 +1,55 @@ +package controlplane + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/pomerium/pomerium/config" + "github.com/pomerium/pomerium/internal/events" + "github.com/pomerium/pomerium/pkg/netutil" +) + +func TestServerWellKnown(t *testing.T) { + ports, err := netutil.AllocatePorts(5) + require.NoError(t, err) + + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + cfg := &config.Config{ + GRPCPort: ports[0], + HTTPPort: ports[1], + OutboundPort: ports[2], + MetricsPort: ports[3], + DebugPort: ports[4], + + Options: config.NewDefaultOptions(), + } + cfg.Options.AuthenticateURLString = "https://authenticate.localhost.pomerium.io" + src := config.NewStaticSource(cfg) + srv, err := NewServer(cfg, config.NewMetricsManager(ctx, src), events.New()) + require.NoError(t, err) + go srv.Run(ctx) + + res, err := http.Get(fmt.Sprintf("http://localhost:%s/.well-known/pomerium", src.GetConfig().HTTPPort)) + require.NoError(t, err) + defer res.Body.Close() + + var actual map[string]any + err = json.NewDecoder(res.Body).Decode(&actual) + require.NoError(t, err) + + expect := map[string]any{ + "authentication_callback_endpoint": "https://authenticate.localhost.pomerium.io/oauth2/callback", + "frontchannel_logout_uri": "https://authenticate.localhost.pomerium.io/.pomerium/sign_out", + "jwks_uri": "https://authenticate.localhost.pomerium.io/.well-known/pomerium/jwks.json", + } + assert.Equal(t, expect, actual) +}