providers/proxy: no exposed urls (#3151)
* test any callback Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * cleanup Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * dont detect callback in per-server handler Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * use full redirect uri with both path and query param Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * update tests Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * correctly route to embedded outpost for callback signature Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix allowed redirects Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
b7b5168910
commit
393d7ec486
|
@ -14,6 +14,7 @@ from authentik.outposts.models import OutpostModel
|
||||||
from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider, ScopeMapping
|
from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider, ScopeMapping
|
||||||
|
|
||||||
SCOPE_AK_PROXY = "ak_proxy"
|
SCOPE_AK_PROXY = "ak_proxy"
|
||||||
|
OUTPOST_CALLBACK_SIGNATURE = "X-authentik-auth-callback"
|
||||||
|
|
||||||
|
|
||||||
def get_cookie_secret():
|
def get_cookie_secret():
|
||||||
|
@ -22,7 +23,13 @@ def get_cookie_secret():
|
||||||
|
|
||||||
|
|
||||||
def _get_callback_url(uri: str) -> str:
|
def _get_callback_url(uri: str) -> str:
|
||||||
return urljoin(uri, "outpost.goauthentik.io/callback")
|
return "\n".join(
|
||||||
|
[
|
||||||
|
urljoin(uri, "outpost.goauthentik.io/callback")
|
||||||
|
+ f"\\?{OUTPOST_CALLBACK_SIGNATURE}=true",
|
||||||
|
uri + f"\\?{OUTPOST_CALLBACK_SIGNATURE}=true",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProxyMode(models.TextChoices):
|
class ProxyMode(models.TextChoices):
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -34,7 +35,7 @@ type Application struct {
|
||||||
Cert *tls.Certificate
|
Cert *tls.Certificate
|
||||||
UnauthenticatedRegex []*regexp.Regexp
|
UnauthenticatedRegex []*regexp.Regexp
|
||||||
|
|
||||||
endpint OIDCEndpoint
|
endpoint OIDCEndpoint
|
||||||
oauthConfig oauth2.Config
|
oauthConfig oauth2.Config
|
||||||
tokenVerifier *oidc.IDTokenVerifier
|
tokenVerifier *oidc.IDTokenVerifier
|
||||||
outpostName string
|
outpostName string
|
||||||
|
@ -72,12 +73,18 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
|
||||||
SupportedSigningAlgs: []string{"RS256", "HS256"},
|
SupportedSigningAlgs: []string{"RS256", "HS256"},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
redirectUri, _ := url.Parse(p.ExternalHost)
|
||||||
|
redirectUri.Path = path.Join(redirectUri.Path, "/outpost.goauthentik.io/callback")
|
||||||
|
redirectUri.RawQuery = url.Values{
|
||||||
|
CallbackSignature: []string{"true"},
|
||||||
|
}.Encode()
|
||||||
|
|
||||||
// Configure an OpenID Connect aware OAuth2 client.
|
// Configure an OpenID Connect aware OAuth2 client.
|
||||||
endpoint := GetOIDCEndpoint(p, ak.Outpost.Config["authentik_host"].(string))
|
endpoint := GetOIDCEndpoint(p, ak.Outpost.Config["authentik_host"].(string))
|
||||||
oauth2Config := oauth2.Config{
|
oauth2Config := oauth2.Config{
|
||||||
ClientID: *p.ClientId,
|
ClientID: *p.ClientId,
|
||||||
ClientSecret: *p.ClientSecret,
|
ClientSecret: *p.ClientSecret,
|
||||||
RedirectURL: urlJoin(p.ExternalHost, "/outpost.goauthentik.io/callback"),
|
RedirectURL: redirectUri.String(),
|
||||||
Endpoint: endpoint.Endpoint,
|
Endpoint: endpoint.Endpoint,
|
||||||
Scopes: p.ScopesToRequest,
|
Scopes: p.ScopesToRequest,
|
||||||
}
|
}
|
||||||
|
@ -86,7 +93,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
|
||||||
Host: externalHost.Host,
|
Host: externalHost.Host,
|
||||||
log: muxLogger,
|
log: muxLogger,
|
||||||
outpostName: ak.Outpost.Name,
|
outpostName: ak.Outpost.Name,
|
||||||
endpint: endpoint,
|
endpoint: endpoint,
|
||||||
oauthConfig: oauth2Config,
|
oauthConfig: oauth2Config,
|
||||||
tokenVerifier: verifier,
|
tokenVerifier: verifier,
|
||||||
proxyConfig: p,
|
proxyConfig: p,
|
||||||
|
@ -139,11 +146,18 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
mux.Use(sentryhttp.New(sentryhttp.Options{}).Handle)
|
mux.Use(sentryhttp.New(sentryhttp.Options{}).Handle)
|
||||||
|
mux.Use(func(inner http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if _, set := r.URL.Query()[CallbackSignature]; set {
|
||||||
|
a.handleAuthCallback(w, r)
|
||||||
|
} else {
|
||||||
|
inner.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// Support /start and /sign_in for backwards compatibility
|
mux.HandleFunc("/outpost.goauthentik.io/start", a.handleAuthStart)
|
||||||
mux.HandleFunc("/outpost.goauthentik.io/start", a.handleRedirect)
|
mux.HandleFunc("/outpost.goauthentik.io/callback", a.handleAuthCallback)
|
||||||
mux.HandleFunc("/outpost.goauthentik.io/sign_in", a.handleRedirect)
|
|
||||||
mux.HandleFunc("/outpost.goauthentik.io/callback", a.handleCallback)
|
|
||||||
mux.HandleFunc("/outpost.goauthentik.io/sign_out", a.handleSignOut)
|
mux.HandleFunc("/outpost.goauthentik.io/sign_out", a.handleSignOut)
|
||||||
switch *p.Mode.Get() {
|
switch *p.Mode.Get() {
|
||||||
case api.PROXYMODE_PROXY:
|
case api.PROXYMODE_PROXY:
|
||||||
|
@ -197,14 +211,14 @@ func (a *Application) handleSignOut(rw http.ResponseWriter, r *http.Request) {
|
||||||
//TODO: Token revocation
|
//TODO: Token revocation
|
||||||
s, err := a.sessions.Get(r, constants.SessionName)
|
s, err := a.sessions.Get(r, constants.SessionName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(rw, r, a.endpint.EndSessionEndpoint, http.StatusFound)
|
http.Redirect(rw, r, a.endpoint.EndSessionEndpoint, http.StatusFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.Options.MaxAge = -1
|
s.Options.MaxAge = -1
|
||||||
err = s.Save(r, rw)
|
err = s.Save(r, rw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(rw, r, a.endpint.EndSessionEndpoint, http.StatusFound)
|
http.Redirect(rw, r, a.endpoint.EndSessionEndpoint, http.StatusFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.Redirect(rw, r, a.endpint.EndSessionEndpoint, http.StatusFound)
|
http.Redirect(rw, r, a.endpoint.EndSessionEndpoint, http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,6 @@ import (
|
||||||
"goauthentik.io/internal/constants"
|
"goauthentik.io/internal/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
var hasReportedMisconfiguration = false
|
|
||||||
|
|
||||||
func (a *Application) addHeaders(headers http.Header, c *Claims) {
|
func (a *Application) addHeaders(headers http.Header, c *Claims) {
|
||||||
// https://goauthentik.io/docs/providers/proxy/proxy
|
// https://goauthentik.io/docs/providers/proxy/proxy
|
||||||
|
|
||||||
|
@ -26,7 +24,7 @@ func (a *Application) addHeaders(headers http.Header, c *Claims) {
|
||||||
headers.Set("X-authentik-jwt", c.RawToken)
|
headers.Set("X-authentik-jwt", c.RawToken)
|
||||||
|
|
||||||
// System headers
|
// System headers
|
||||||
headers.Set("X-authentik-meta-jwks", a.endpint.JwksUri)
|
headers.Set("X-authentik-meta-jwks", a.endpoint.JwksUri)
|
||||||
headers.Set("X-authentik-meta-outpost", a.outpostName)
|
headers.Set("X-authentik-meta-outpost", a.outpostName)
|
||||||
headers.Set("X-authentik-meta-provider", a.proxyConfig.Name)
|
headers.Set("X-authentik-meta-provider", a.proxyConfig.Name)
|
||||||
headers.Set("X-authentik-meta-app", a.proxyConfig.AssignedApplicationSlug)
|
headers.Set("X-authentik-meta-app", a.proxyConfig.AssignedApplicationSlug)
|
||||||
|
@ -105,9 +103,6 @@ func (a *Application) getNginxForwardUrl(r *http.Request) (*url.URL, error) {
|
||||||
func (a *Application) ReportMisconfiguration(r *http.Request, msg string, fields map[string]interface{}) {
|
func (a *Application) ReportMisconfiguration(r *http.Request, msg string, fields map[string]interface{}) {
|
||||||
fields["message"] = msg
|
fields["message"] = msg
|
||||||
a.log.WithFields(fields).Error("Reporting configuration error")
|
a.log.WithFields(fields).Error("Reporting configuration error")
|
||||||
if hasReportedMisconfiguration {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
req := api.EventRequest{
|
req := api.EventRequest{
|
||||||
Action: api.EVENTACTIONS_CONFIGURATION_ERROR,
|
Action: api.EVENTACTIONS_CONFIGURATION_ERROR,
|
||||||
App: "authentik.providers.proxy", // must match python apps.py name
|
App: "authentik.providers.proxy", // must match python apps.py name
|
||||||
|
@ -117,8 +112,6 @@ func (a *Application) ReportMisconfiguration(r *http.Request, msg string, fields
|
||||||
_, _, err := a.ak.Client.EventsApi.EventsEventsCreate(context.Background()).EventRequest(req).Execute()
|
_, _, err := a.ak.Client.EventsApi.EventsEventsCreate(context.Background()).EventRequest(req).Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.log.WithError(err).Warning("failed to report configuration error")
|
a.log.WithError(err).Warning("failed to report configuration error")
|
||||||
} else {
|
|
||||||
hasReportedMisconfiguration = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,9 @@ package application
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"goauthentik.io/api/v3"
|
|
||||||
"goauthentik.io/internal/outpost/proxyv2/constants"
|
"goauthentik.io/internal/outpost/proxyv2/constants"
|
||||||
"goauthentik.io/internal/utils/web"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -40,7 +37,7 @@ func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Reque
|
||||||
http.Error(rw, "configuration error", http.StatusInternalServerError)
|
http.Error(rw, "configuration error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Check if we're authenticated, or the request path is on the allowlist
|
||||||
claims, err := a.getClaims(r)
|
claims, err := a.getClaims(r)
|
||||||
if claims != nil && err == nil {
|
if claims != nil && err == nil {
|
||||||
a.addHeaders(rw.Header(), claims)
|
a.addHeaders(rw.Header(), claims)
|
||||||
|
@ -51,22 +48,9 @@ func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Reque
|
||||||
a.log.Trace("path can be accessed without authentication")
|
a.log.Trace("path can be accessed without authentication")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(r.Header.Get("X-Forwarded-Uri"), "/outpost.goauthentik.io") {
|
tr := r.Clone(r.Context())
|
||||||
a.log.WithField("url", r.URL.String()).Trace("path begins with /outpost.goauthentik.io, allowing access")
|
tr.URL = fwd
|
||||||
return
|
a.handleAuthStart(rw, r)
|
||||||
}
|
|
||||||
host := ""
|
|
||||||
// Optional suffix, which is appended to the URL
|
|
||||||
if *a.proxyConfig.Mode.Get() == api.PROXYMODE_FORWARD_SINGLE {
|
|
||||||
host = web.GetHost(r)
|
|
||||||
} else if *a.proxyConfig.Mode.Get() == api.PROXYMODE_FORWARD_DOMAIN {
|
|
||||||
eh, err := url.Parse(a.proxyConfig.ExternalHost)
|
|
||||||
if err != nil {
|
|
||||||
a.log.WithField("host", a.proxyConfig.ExternalHost).WithError(err).Warning("invalid external_host")
|
|
||||||
} else {
|
|
||||||
host = eh.Host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// set the redirect flag to the current URL we have, since we redirect
|
// set the redirect flag to the current URL we have, since we redirect
|
||||||
// to a (possibly) different domain, but we want to be redirected back
|
// to a (possibly) different domain, but we want to be redirected back
|
||||||
// to the application
|
// to the application
|
||||||
|
@ -76,17 +60,9 @@ func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Reque
|
||||||
s.Values[constants.SessionRedirect] = fwd.String()
|
s.Values[constants.SessionRedirect] = fwd.String()
|
||||||
err = s.Save(r, rw)
|
err = s.Save(r, rw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.log.WithError(err).Warning("failed to save session before redirect")
|
a.log.WithError(err).Warning("failed to save session")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proto := r.Header.Get("X-Forwarded-Proto")
|
|
||||||
if proto != "" {
|
|
||||||
proto = proto + ":"
|
|
||||||
}
|
|
||||||
rdFinal := fmt.Sprintf("%s//%s%s", proto, host, "/outpost.goauthentik.io/start")
|
|
||||||
a.log.WithField("url", rdFinal).Debug("Redirecting to login")
|
|
||||||
http.Redirect(rw, r, rdFinal, http.StatusTemporaryRedirect)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Application) forwardHandleCaddy(rw http.ResponseWriter, r *http.Request) {
|
func (a *Application) forwardHandleCaddy(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -103,7 +79,7 @@ func (a *Application) forwardHandleCaddy(rw http.ResponseWriter, r *http.Request
|
||||||
http.Error(rw, "configuration error", http.StatusInternalServerError)
|
http.Error(rw, "configuration error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Check if we're authenticated, or the request path is on the allowlist
|
||||||
claims, err := a.getClaims(r)
|
claims, err := a.getClaims(r)
|
||||||
if claims != nil && err == nil {
|
if claims != nil && err == nil {
|
||||||
a.addHeaders(rw.Header(), claims)
|
a.addHeaders(rw.Header(), claims)
|
||||||
|
@ -114,22 +90,9 @@ func (a *Application) forwardHandleCaddy(rw http.ResponseWriter, r *http.Request
|
||||||
a.log.Trace("path can be accessed without authentication")
|
a.log.Trace("path can be accessed without authentication")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(r.Header.Get("X-Forwarded-Uri"), "/outpost.goauthentik.io") {
|
tr := r.Clone(r.Context())
|
||||||
a.log.WithField("url", r.URL.String()).Trace("path begins with /outpost.goauthentik.io, allowing access")
|
tr.URL = fwd
|
||||||
return
|
a.handleAuthStart(rw, r)
|
||||||
}
|
|
||||||
host := ""
|
|
||||||
// Optional suffix, which is appended to the URL
|
|
||||||
if *a.proxyConfig.Mode.Get() == api.PROXYMODE_FORWARD_SINGLE {
|
|
||||||
host = web.GetHost(r)
|
|
||||||
} else if *a.proxyConfig.Mode.Get() == api.PROXYMODE_FORWARD_DOMAIN {
|
|
||||||
eh, err := url.Parse(a.proxyConfig.ExternalHost)
|
|
||||||
if err != nil {
|
|
||||||
a.log.WithField("host", a.proxyConfig.ExternalHost).WithError(err).Warning("invalid external_host")
|
|
||||||
} else {
|
|
||||||
host = eh.Host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// set the redirect flag to the current URL we have, since we redirect
|
// set the redirect flag to the current URL we have, since we redirect
|
||||||
// to a (possibly) different domain, but we want to be redirected back
|
// to a (possibly) different domain, but we want to be redirected back
|
||||||
// to the application
|
// to the application
|
||||||
|
@ -139,17 +102,9 @@ func (a *Application) forwardHandleCaddy(rw http.ResponseWriter, r *http.Request
|
||||||
s.Values[constants.SessionRedirect] = fwd.String()
|
s.Values[constants.SessionRedirect] = fwd.String()
|
||||||
err = s.Save(r, rw)
|
err = s.Save(r, rw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.log.WithError(err).Warning("failed to save session before redirect")
|
a.log.WithError(err).Warning("failed to save session")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proto := r.Header.Get("X-Forwarded-Proto")
|
|
||||||
if proto != "" {
|
|
||||||
proto = proto + ":"
|
|
||||||
}
|
|
||||||
rdFinal := fmt.Sprintf("%s//%s%s", proto, host, "/outpost.goauthentik.io/start")
|
|
||||||
a.log.WithField("url", rdFinal).Debug("Redirecting to login")
|
|
||||||
http.Redirect(rw, r, rdFinal, http.StatusTemporaryRedirect)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Application) forwardHandleNginx(rw http.ResponseWriter, r *http.Request) {
|
func (a *Application) forwardHandleNginx(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -200,7 +155,7 @@ func (a *Application) forwardHandleEnvoy(rw http.ResponseWriter, r *http.Request
|
||||||
a.log.WithField("header", r.Header).Trace("tracing headers for debug")
|
a.log.WithField("header", r.Header).Trace("tracing headers for debug")
|
||||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, envoyPrefix)
|
r.URL.Path = strings.TrimPrefix(r.URL.Path, envoyPrefix)
|
||||||
fwd := r.URL
|
fwd := r.URL
|
||||||
|
// Check if we're authenticated, or the request path is on the allowlist
|
||||||
claims, err := a.getClaims(r)
|
claims, err := a.getClaims(r)
|
||||||
if claims != nil && err == nil {
|
if claims != nil && err == nil {
|
||||||
a.addHeaders(rw.Header(), claims)
|
a.addHeaders(rw.Header(), claims)
|
||||||
|
@ -211,22 +166,7 @@ func (a *Application) forwardHandleEnvoy(rw http.ResponseWriter, r *http.Request
|
||||||
a.log.Trace("path can be accessed without authentication")
|
a.log.Trace("path can be accessed without authentication")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(r.URL.Path, "/outpost.goauthentik.io") {
|
a.handleAuthStart(rw, r)
|
||||||
a.log.WithField("url", r.URL.String()).Trace("path begins with /outpost.goauthentik.io, allowing access")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
host := ""
|
|
||||||
// Optional suffix, which is appended to the URL
|
|
||||||
if *a.proxyConfig.Mode.Get() == api.PROXYMODE_FORWARD_SINGLE {
|
|
||||||
host = web.GetHost(r)
|
|
||||||
} else if *a.proxyConfig.Mode.Get() == api.PROXYMODE_FORWARD_DOMAIN {
|
|
||||||
eh, err := url.Parse(a.proxyConfig.ExternalHost)
|
|
||||||
if err != nil {
|
|
||||||
a.log.WithField("host", a.proxyConfig.ExternalHost).WithError(err).Warning("invalid external_host")
|
|
||||||
} else {
|
|
||||||
host = eh.Host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// set the redirect flag to the current URL we have, since we redirect
|
// set the redirect flag to the current URL we have, since we redirect
|
||||||
// to a (possibly) different domain, but we want to be redirected back
|
// to a (possibly) different domain, but we want to be redirected back
|
||||||
// to the application
|
// to the application
|
||||||
|
@ -239,7 +179,4 @@ func (a *Application) forwardHandleEnvoy(rw http.ResponseWriter, r *http.Request
|
||||||
a.log.WithError(err).Warning("failed to save session before redirect")
|
a.log.WithError(err).Warning("failed to save session before redirect")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rdFinal := fmt.Sprintf("//%s%s", host, "/outpost.goauthentik.io/start")
|
|
||||||
a.log.WithField("url", rdFinal).Debug("Redirecting to login")
|
|
||||||
http.Redirect(rw, r, rdFinal, http.StatusTemporaryRedirect)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package application
|
package application
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -43,11 +45,16 @@ func TestForwardHandleCaddy_Single_Headers(t *testing.T) {
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
a.forwardHandleCaddy(rr, req)
|
a.forwardHandleCaddy(rr, req)
|
||||||
|
|
||||||
assert.Equal(t, rr.Code, http.StatusTemporaryRedirect)
|
assert.Equal(t, http.StatusFound, rr.Code)
|
||||||
loc, _ := rr.Result().Location()
|
loc, _ := rr.Result().Location()
|
||||||
assert.Equal(t, loc.String(), "http://test.goauthentik.io/outpost.goauthentik.io/start")
|
|
||||||
|
|
||||||
s, _ := a.sessions.Get(req, constants.SessionName)
|
s, _ := a.sessions.Get(req, constants.SessionName)
|
||||||
|
shouldUrl := url.Values{
|
||||||
|
"client_id": []string{*a.proxyConfig.ClientId},
|
||||||
|
"redirect_uri": []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"},
|
||||||
|
"response_type": []string{"code"},
|
||||||
|
"state": []string{s.Values[constants.SessionOAuthState].([]string)[0]},
|
||||||
|
}
|
||||||
|
assert.Equal(t, fmt.Sprintf("http://fake-auth.t.goauthentik.io/auth?%s", shouldUrl.Encode()), loc.String())
|
||||||
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,10 +130,15 @@ func TestForwardHandleCaddy_Domain_Header(t *testing.T) {
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
a.forwardHandleCaddy(rr, req)
|
a.forwardHandleCaddy(rr, req)
|
||||||
|
|
||||||
assert.Equal(t, http.StatusTemporaryRedirect, rr.Code)
|
assert.Equal(t, http.StatusFound, rr.Code)
|
||||||
loc, _ := rr.Result().Location()
|
loc, _ := rr.Result().Location()
|
||||||
assert.Equal(t, "http://auth.test.goauthentik.io/outpost.goauthentik.io/start", loc.String())
|
|
||||||
|
|
||||||
s, _ := a.sessions.Get(req, constants.SessionName)
|
s, _ := a.sessions.Get(req, constants.SessionName)
|
||||||
|
shouldUrl := url.Values{
|
||||||
|
"client_id": []string{*a.proxyConfig.ClientId},
|
||||||
|
"redirect_uri": []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"},
|
||||||
|
"response_type": []string{"code"},
|
||||||
|
"state": []string{s.Values[constants.SessionOAuthState].([]string)[0]},
|
||||||
|
}
|
||||||
|
assert.Equal(t, fmt.Sprintf("http://fake-auth.t.goauthentik.io/auth?%s", shouldUrl.Encode()), loc.String())
|
||||||
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package application
|
package application
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -27,11 +29,16 @@ func TestForwardHandleEnvoy_Single_Headers(t *testing.T) {
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
a.forwardHandleEnvoy(rr, req)
|
a.forwardHandleEnvoy(rr, req)
|
||||||
|
|
||||||
assert.Equal(t, rr.Code, http.StatusTemporaryRedirect)
|
assert.Equal(t, http.StatusFound, rr.Code)
|
||||||
loc, _ := rr.Result().Location()
|
loc, _ := rr.Result().Location()
|
||||||
assert.Equal(t, loc.String(), "//test.goauthentik.io/outpost.goauthentik.io/start")
|
|
||||||
|
|
||||||
s, _ := a.sessions.Get(req, constants.SessionName)
|
s, _ := a.sessions.Get(req, constants.SessionName)
|
||||||
|
shouldUrl := url.Values{
|
||||||
|
"client_id": []string{*a.proxyConfig.ClientId},
|
||||||
|
"redirect_uri": []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"},
|
||||||
|
"response_type": []string{"code"},
|
||||||
|
"state": []string{s.Values[constants.SessionOAuthState].([]string)[0]},
|
||||||
|
}
|
||||||
|
assert.Equal(t, fmt.Sprintf("http://fake-auth.t.goauthentik.io/auth?%s", shouldUrl.Encode()), loc.String())
|
||||||
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,10 +96,16 @@ func TestForwardHandleEnvoy_Domain_Header(t *testing.T) {
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
a.forwardHandleEnvoy(rr, req)
|
a.forwardHandleEnvoy(rr, req)
|
||||||
|
|
||||||
assert.Equal(t, http.StatusTemporaryRedirect, rr.Code)
|
assert.Equal(t, http.StatusFound, rr.Code)
|
||||||
loc, _ := rr.Result().Location()
|
loc, _ := rr.Result().Location()
|
||||||
assert.Equal(t, "//auth.test.goauthentik.io/outpost.goauthentik.io/start", loc.String())
|
|
||||||
|
|
||||||
s, _ := a.sessions.Get(req, constants.SessionName)
|
s, _ := a.sessions.Get(req, constants.SessionName)
|
||||||
|
|
||||||
|
shouldUrl := url.Values{
|
||||||
|
"client_id": []string{*a.proxyConfig.ClientId},
|
||||||
|
"redirect_uri": []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"},
|
||||||
|
"response_type": []string{"code"},
|
||||||
|
"state": []string{s.Values[constants.SessionOAuthState].([]string)[0]},
|
||||||
|
}
|
||||||
|
assert.Equal(t, fmt.Sprintf("http://fake-auth.t.goauthentik.io/auth?%s", shouldUrl.Encode()), loc.String())
|
||||||
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ func TestForwardHandleNginx_Single_Headers(t *testing.T) {
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
a.forwardHandleNginx(rr, req)
|
a.forwardHandleNginx(rr, req)
|
||||||
|
|
||||||
assert.Equal(t, rr.Code, http.StatusUnauthorized)
|
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||||
|
|
||||||
s, _ := a.sessions.Get(req, constants.SessionName)
|
s, _ := a.sessions.Get(req, constants.SessionName)
|
||||||
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
||||||
|
@ -53,7 +53,7 @@ func TestForwardHandleNginx_Single_URI(t *testing.T) {
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
a.forwardHandleNginx(rr, req)
|
a.forwardHandleNginx(rr, req)
|
||||||
|
|
||||||
assert.Equal(t, rr.Code, http.StatusUnauthorized)
|
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||||
|
|
||||||
s, _ := a.sessions.Get(req, constants.SessionName)
|
s, _ := a.sessions.Get(req, constants.SessionName)
|
||||||
assert.Equal(t, "/app", s.Values[constants.SessionRedirect])
|
assert.Equal(t, "/app", s.Values[constants.SessionRedirect])
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package application
|
package application
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -43,11 +45,16 @@ func TestForwardHandleTraefik_Single_Headers(t *testing.T) {
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
a.forwardHandleTraefik(rr, req)
|
a.forwardHandleTraefik(rr, req)
|
||||||
|
|
||||||
assert.Equal(t, rr.Code, http.StatusTemporaryRedirect)
|
assert.Equal(t, http.StatusFound, rr.Code)
|
||||||
loc, _ := rr.Result().Location()
|
loc, _ := rr.Result().Location()
|
||||||
assert.Equal(t, loc.String(), "http://test.goauthentik.io/outpost.goauthentik.io/start")
|
|
||||||
|
|
||||||
s, _ := a.sessions.Get(req, constants.SessionName)
|
s, _ := a.sessions.Get(req, constants.SessionName)
|
||||||
|
shouldUrl := url.Values{
|
||||||
|
"client_id": []string{*a.proxyConfig.ClientId},
|
||||||
|
"redirect_uri": []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"},
|
||||||
|
"response_type": []string{"code"},
|
||||||
|
"state": []string{s.Values[constants.SessionOAuthState].([]string)[0]},
|
||||||
|
}
|
||||||
|
assert.Equal(t, fmt.Sprintf("http://fake-auth.t.goauthentik.io/auth?%s", shouldUrl.Encode()), loc.String())
|
||||||
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,10 +130,15 @@ func TestForwardHandleTraefik_Domain_Header(t *testing.T) {
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
a.forwardHandleTraefik(rr, req)
|
a.forwardHandleTraefik(rr, req)
|
||||||
|
|
||||||
assert.Equal(t, http.StatusTemporaryRedirect, rr.Code)
|
assert.Equal(t, http.StatusFound, rr.Code)
|
||||||
loc, _ := rr.Result().Location()
|
loc, _ := rr.Result().Location()
|
||||||
assert.Equal(t, "http://auth.test.goauthentik.io/outpost.goauthentik.io/start", loc.String())
|
|
||||||
|
|
||||||
s, _ := a.sessions.Get(req, constants.SessionName)
|
s, _ := a.sessions.Get(req, constants.SessionName)
|
||||||
|
shouldUrl := url.Values{
|
||||||
|
"client_id": []string{*a.proxyConfig.ClientId},
|
||||||
|
"redirect_uri": []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"},
|
||||||
|
"response_type": []string{"code"},
|
||||||
|
"state": []string{s.Values[constants.SessionOAuthState].([]string)[0]},
|
||||||
|
}
|
||||||
|
assert.Equal(t, fmt.Sprintf("http://fake-auth.t.goauthentik.io/auth?%s", shouldUrl.Encode()), loc.String())
|
||||||
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
redirectParam = "rd"
|
redirectParam = "rd"
|
||||||
|
CallbackSignature = "X-authentik-auth-callback"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *Application) checkRedirectParam(r *http.Request) (string, bool) {
|
func (a *Application) checkRedirectParam(r *http.Request) (string, bool) {
|
||||||
|
@ -41,7 +42,7 @@ func (a *Application) checkRedirectParam(r *http.Request) (string, bool) {
|
||||||
return u.String(), true
|
return u.String(), true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Application) handleRedirect(rw http.ResponseWriter, r *http.Request) {
|
func (a *Application) handleAuthStart(rw http.ResponseWriter, r *http.Request) {
|
||||||
newState := base64.RawURLEncoding.EncodeToString(securecookie.GenerateRandomKey(32))
|
newState := base64.RawURLEncoding.EncodeToString(securecookie.GenerateRandomKey(32))
|
||||||
s, err := a.sessions.Get(r, constants.SessionName)
|
s, err := a.sessions.Get(r, constants.SessionName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -65,7 +66,7 @@ func (a *Application) handleRedirect(rw http.ResponseWriter, r *http.Request) {
|
||||||
http.Redirect(rw, r, a.oauthConfig.AuthCodeURL(newState), http.StatusFound)
|
http.Redirect(rw, r, a.oauthConfig.AuthCodeURL(newState), http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Application) handleCallback(rw http.ResponseWriter, r *http.Request) {
|
func (a *Application) handleAuthCallback(rw http.ResponseWriter, r *http.Request) {
|
||||||
s, err := a.sessions.Get(r, constants.SessionName)
|
s, err := a.sessions.Get(r, constants.SessionName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.log.WithError(err).Trace("failed to get session")
|
a.log.WithError(err).Trace("failed to get session")
|
||||||
|
|
|
@ -22,6 +22,11 @@ func newTestApplication() *Application {
|
||||||
BasicAuthEnabled: api.PtrBool(true),
|
BasicAuthEnabled: api.PtrBool(true),
|
||||||
BasicAuthUserAttribute: api.PtrString("username"),
|
BasicAuthUserAttribute: api.PtrString("username"),
|
||||||
BasicAuthPasswordAttribute: api.PtrString("password"),
|
BasicAuthPasswordAttribute: api.PtrString("password"),
|
||||||
|
OidcConfiguration: api.ProxyOutpostConfigOidcConfiguration{
|
||||||
|
AuthorizationEndpoint: "http://fake-auth.t.goauthentik.io/auth",
|
||||||
|
TokenEndpoint: "http://fake-auth.t.goauthentik.io/token",
|
||||||
|
UserinfoEndpoint: "http://fake-auth.t.goauthentik.io/userinfo",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
http.DefaultClient,
|
http.DefaultClient,
|
||||||
nil,
|
nil,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"goauthentik.io/internal/outpost/proxyv2/application"
|
||||||
"goauthentik.io/internal/utils/sentry"
|
"goauthentik.io/internal/utils/sentry"
|
||||||
"goauthentik.io/internal/utils/web"
|
"goauthentik.io/internal/utils/web"
|
||||||
)
|
)
|
||||||
|
@ -52,7 +53,8 @@ func (ws *WebServer) configureProxy() {
|
||||||
}
|
}
|
||||||
before := time.Now()
|
before := time.Now()
|
||||||
if ws.ProxyServer != nil {
|
if ws.ProxyServer != nil {
|
||||||
if ws.ProxyServer.HandleHost(rw, r) {
|
_, oauthCallbackSet := r.URL.Query()[application.CallbackSignature]
|
||||||
|
if ws.ProxyServer.HandleHost(rw, r) || oauthCallbackSet {
|
||||||
Requests.With(prometheus.Labels{
|
Requests.With(prometheus.Labels{
|
||||||
"dest": "embedded_outpost",
|
"dest": "embedded_outpost",
|
||||||
}).Observe(float64(time.Since(before)))
|
}).Observe(float64(time.Since(before)))
|
||||||
|
|
|
@ -39,12 +39,12 @@ func NewWebServer(g *gounicorn.GoUnicorn) *WebServer {
|
||||||
mainHandler.Use(sentryhttp.New(sentryhttp.Options{}).Handle)
|
mainHandler.Use(sentryhttp.New(sentryhttp.Options{}).Handle)
|
||||||
mainHandler.Use(handlers.ProxyHeaders)
|
mainHandler.Use(handlers.ProxyHeaders)
|
||||||
mainHandler.Use(handlers.CompressHandler)
|
mainHandler.Use(handlers.CompressHandler)
|
||||||
logginRouter := mainHandler.NewRoute().Subrouter()
|
loggingHandler := mainHandler.NewRoute().Subrouter()
|
||||||
logginRouter.Use(web.NewLoggingHandler(l, nil))
|
loggingHandler.Use(web.NewLoggingHandler(l, nil))
|
||||||
|
|
||||||
ws := &WebServer{
|
ws := &WebServer{
|
||||||
m: mainHandler,
|
m: mainHandler,
|
||||||
lh: logginRouter,
|
lh: loggingHandler,
|
||||||
log: l,
|
log: l,
|
||||||
p: g,
|
p: g,
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue