providers/proxy: rework endpoints logic (#4993)
* providers/proxy: rework endpoints logic again...this time with tests and better logic Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
		| @ -2,7 +2,6 @@ package application | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	log "github.com/sirupsen/logrus" | 	log "github.com/sirupsen/logrus" | ||||||
| 	"goauthentik.io/api/v3" | 	"goauthentik.io/api/v3" | ||||||
| @ -31,40 +30,55 @@ func updateURL(rawUrl string, scheme string, host string) string { | |||||||
| func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string, embedded bool) OIDCEndpoint { | func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string, embedded bool) OIDCEndpoint { | ||||||
| 	authUrl := p.OidcConfiguration.AuthorizationEndpoint | 	authUrl := p.OidcConfiguration.AuthorizationEndpoint | ||||||
| 	endUrl := p.OidcConfiguration.EndSessionEndpoint | 	endUrl := p.OidcConfiguration.EndSessionEndpoint | ||||||
| 	tokenUrl := p.OidcConfiguration.TokenEndpoint |  | ||||||
| 	jwksUrl := p.OidcConfiguration.JwksUri |  | ||||||
| 	issuer := p.OidcConfiguration.Issuer | 	issuer := p.OidcConfiguration.Issuer | ||||||
| 	if config.Get().AuthentikHostBrowser != "" { |  | ||||||
| 		authUrl = strings.ReplaceAll(authUrl, authentikHost, config.Get().AuthentikHostBrowser) |  | ||||||
| 		endUrl = strings.ReplaceAll(endUrl, authentikHost, config.Get().AuthentikHostBrowser) |  | ||||||
| 		jwksUrl = strings.ReplaceAll(jwksUrl, authentikHost, config.Get().AuthentikHostBrowser) |  | ||||||
| 		issuer = strings.ReplaceAll(issuer, authentikHost, config.Get().AuthentikHostBrowser) |  | ||||||
| 	} |  | ||||||
| 	ep := OIDCEndpoint{ | 	ep := OIDCEndpoint{ | ||||||
| 		Endpoint: oauth2.Endpoint{ | 		Endpoint: oauth2.Endpoint{ | ||||||
| 			AuthURL:   authUrl, | 			AuthURL:   authUrl, | ||||||
| 			TokenURL:  tokenUrl, | 			TokenURL:  p.OidcConfiguration.TokenEndpoint, | ||||||
| 			AuthStyle: oauth2.AuthStyleInParams, | 			AuthStyle: oauth2.AuthStyleInParams, | ||||||
| 		}, | 		}, | ||||||
| 		EndSessionEndpoint: endUrl, | 		EndSessionEndpoint: endUrl, | ||||||
| 		JwksUri:            jwksUrl, | 		JwksUri:            p.OidcConfiguration.JwksUri, | ||||||
| 		TokenIntrospection: p.OidcConfiguration.IntrospectionEndpoint, | 		TokenIntrospection: p.OidcConfiguration.IntrospectionEndpoint, | ||||||
| 		Issuer:             issuer, | 		Issuer:             issuer, | ||||||
| 	} | 	} | ||||||
| 	if !embedded { | 	// For the embedded outpost, we use the configure `authentik_host` for the browser URLs | ||||||
|  | 	// and localhost (which is what we've got from the API) for backchannel URLs | ||||||
|  | 	// | ||||||
|  | 	// For other outposts, when `AUTHENTIK_HOST_BROWSER` is set, we use that for the browser URLs | ||||||
|  | 	// and use what we got from the API for backchannel | ||||||
|  | 	hostBrowser := config.Get().AuthentikHostBrowser | ||||||
|  | 	if !embedded && hostBrowser == "" { | ||||||
| 		return ep | 		return ep | ||||||
| 	} | 	} | ||||||
| 	if authentikHost == "" { | 	var newHost *url.URL | ||||||
| 		log.Warning("Outpost has localhost/blank API Connection but no authentik_host is configured.") | 	if embedded { | ||||||
| 		return ep | 		if authentikHost == "" { | ||||||
|  | 			log.Warning("Outpost has localhost/blank API Connection but no authentik_host is configured.") | ||||||
|  | 			return ep | ||||||
|  | 		} | ||||||
|  | 		aku, err := url.Parse(authentikHost) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return ep | ||||||
|  | 		} | ||||||
|  | 		newHost = aku | ||||||
|  | 	} else if hostBrowser != "" { | ||||||
|  | 		aku, err := url.Parse(hostBrowser) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return ep | ||||||
|  | 		} | ||||||
|  | 		newHost = aku | ||||||
| 	} | 	} | ||||||
| 	aku, err := url.Parse(authentikHost) | 	// Update all browser-accessed URLs to use the new host and scheme | ||||||
| 	if err != nil { | 	ep.AuthURL = updateURL(authUrl, newHost.Scheme, newHost.Host) | ||||||
| 		return ep | 	ep.EndSessionEndpoint = updateURL(endUrl, newHost.Scheme, newHost.Host) | ||||||
|  | 	// Update issuer to use the same host and scheme, which would normally break as we don't | ||||||
|  | 	// change the token URL here, but the token HTTP transport overwrites the Host header | ||||||
|  | 	// | ||||||
|  | 	// This is only used in embedded outposts as there we can guarantee that the request | ||||||
|  | 	// is routed correctly | ||||||
|  | 	if embedded { | ||||||
|  | 		ep.Issuer = updateURL(ep.Issuer, newHost.Scheme, newHost.Host) | ||||||
| 	} | 	} | ||||||
| 	ep.AuthURL = updateURL(authUrl, aku.Scheme, aku.Host) |  | ||||||
| 	ep.EndSessionEndpoint = updateURL(endUrl, aku.Scheme, aku.Host) |  | ||||||
| 	ep.JwksUri = updateURL(jwksUrl, aku.Scheme, aku.Host) |  | ||||||
| 	ep.Issuer = updateURL(ep.Issuer, aku.Scheme, aku.Host) |  | ||||||
| 	return ep | 	return ep | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										88
									
								
								internal/outpost/proxyv2/application/endpoint_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								internal/outpost/proxyv2/application/endpoint_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,88 @@ | |||||||
|  | package application | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"goauthentik.io/api/v3" | ||||||
|  | 	"goauthentik.io/internal/config" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestEndpointDefault(t *testing.T) { | ||||||
|  | 	pc := api.ProxyOutpostConfig{ | ||||||
|  | 		OidcConfiguration: api.ProxyOutpostConfigOidcConfiguration{ | ||||||
|  | 			AuthorizationEndpoint: "https://test.goauthentik.io/application/o/authorize/", | ||||||
|  | 			EndSessionEndpoint:    "https://test.goauthentik.io/application/o/test-app/end-session/", | ||||||
|  | 			IntrospectionEndpoint: "https://test.goauthentik.io/application/o/introspect/", | ||||||
|  | 			Issuer:                "https://test.goauthentik.io/application/o/test-app/", | ||||||
|  | 			JwksUri:               "https://test.goauthentik.io/application/o/test-app/jwks/", | ||||||
|  | 			TokenEndpoint:         "https://test.goauthentik.io/application/o/token/", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ep := GetOIDCEndpoint(pc, "https://authentik-host.test.goauthentik.io", false) | ||||||
|  | 	// Standard outpost, non embedded | ||||||
|  | 	// All URLs should use the host that they get from the config | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/authorize/", ep.AuthURL) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/token/", ep.TokenURL) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/", ep.Issuer) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/jwks/", ep.JwksUri) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/end-session/", ep.EndSessionEndpoint) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/introspect/", ep.TokenIntrospection) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestEndpointAuthentikHostBrowser(t *testing.T) { | ||||||
|  | 	c := config.Get() | ||||||
|  | 	c.AuthentikHostBrowser = "https://browser.test.goauthentik.io" | ||||||
|  | 	defer func() { | ||||||
|  | 		c.AuthentikHostBrowser = "" | ||||||
|  | 	}() | ||||||
|  | 	pc := api.ProxyOutpostConfig{ | ||||||
|  | 		OidcConfiguration: api.ProxyOutpostConfigOidcConfiguration{ | ||||||
|  | 			AuthorizationEndpoint: "https://test.goauthentik.io/application/o/authorize/", | ||||||
|  | 			EndSessionEndpoint:    "https://test.goauthentik.io/application/o/test-app/end-session/", | ||||||
|  | 			IntrospectionEndpoint: "https://test.goauthentik.io/application/o/introspect/", | ||||||
|  | 			Issuer:                "https://test.goauthentik.io/application/o/test-app/", | ||||||
|  | 			JwksUri:               "https://test.goauthentik.io/application/o/test-app/jwks/", | ||||||
|  | 			TokenEndpoint:         "https://test.goauthentik.io/application/o/token/", | ||||||
|  | 			UserinfoEndpoint:      "https://test.goauthentik.io/application/o/userinfo/", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ep := GetOIDCEndpoint(pc, "https://authentik-host.test.goauthentik.io", false) | ||||||
|  | 	// Standard outpost, with AUTHENTIK_HOST_BROWSER set | ||||||
|  | 	// Only the authorize/end session URLs should be changed | ||||||
|  | 	assert.Equal(t, "https://browser.test.goauthentik.io/application/o/authorize/", ep.AuthURL) | ||||||
|  | 	assert.Equal(t, "https://browser.test.goauthentik.io/application/o/test-app/end-session/", ep.EndSessionEndpoint) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/token/", ep.TokenURL) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/", ep.Issuer) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/jwks/", ep.JwksUri) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/introspect/", ep.TokenIntrospection) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestEndpointEmbedded(t *testing.T) { | ||||||
|  | 	pc := api.ProxyOutpostConfig{ | ||||||
|  | 		OidcConfiguration: api.ProxyOutpostConfigOidcConfiguration{ | ||||||
|  | 			AuthorizationEndpoint: "https://test.goauthentik.io/application/o/authorize/", | ||||||
|  | 			EndSessionEndpoint:    "https://test.goauthentik.io/application/o/test-app/end-session/", | ||||||
|  | 			IntrospectionEndpoint: "https://test.goauthentik.io/application/o/introspect/", | ||||||
|  | 			Issuer:                "https://test.goauthentik.io/application/o/test-app/", | ||||||
|  | 			JwksUri:               "https://test.goauthentik.io/application/o/test-app/jwks/", | ||||||
|  | 			TokenEndpoint:         "https://test.goauthentik.io/application/o/token/", | ||||||
|  | 			UserinfoEndpoint:      "https://test.goauthentik.io/application/o/userinfo/", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ep := GetOIDCEndpoint(pc, "https://authentik-host.test.goauthentik.io", true) | ||||||
|  | 	// Embedded outpost | ||||||
|  | 	// Browser URLs should use the config of "authentik_host", everything else can use what's | ||||||
|  | 	// received from the API endpoint | ||||||
|  | 	// Token URL is an exception since it's sent via a special HTTP transport that overrides the | ||||||
|  | 	// HTTP Host header, to make sure it's the same value as the issuer | ||||||
|  | 	assert.Equal(t, "https://authentik-host.test.goauthentik.io/application/o/authorize/", ep.AuthURL) | ||||||
|  | 	assert.Equal(t, "https://authentik-host.test.goauthentik.io/application/o/test-app/", ep.Issuer) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/token/", ep.TokenURL) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/jwks/", ep.JwksUri) | ||||||
|  | 	assert.Equal(t, "https://authentik-host.test.goauthentik.io/application/o/test-app/end-session/", ep.EndSessionEndpoint) | ||||||
|  | 	assert.Equal(t, "https://test.goauthentik.io/application/o/introspect/", ep.TokenIntrospection) | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 Jens L
					Jens L