From dad24c03ff46478c1297c5173974a31e25b26864 Mon Sep 17 00:00:00 2001 From: Jens L Date: Tue, 8 Jun 2021 23:10:17 +0200 Subject: [PATCH] outposts: set cookies for a domain to authenticate an entire domain (#971) * outposts: initial cookie domain implementation Signed-off-by: Jens Langhammer * web/admin: add cookie domain setting Signed-off-by: Jens Langhammer * providers/proxy: replace forward_auth_mode with general mode Signed-off-by: Jens Langhammer * web/admin: rebuild proxy provider form Signed-off-by: Jens Langhammer * providers/proxy: re-add forward_auth_mode for backwards compat Signed-off-by: Jens Langhammer * web/admin: fix data.mode not being set Signed-off-by: Jens Langhammer * root: always set log level to debug when testing Signed-off-by: Jens Langhammer * providers/proxy: use new mode attribute Signed-off-by: Jens Langhammer * providers/proxy: only ingress /akprox on forward_domain Signed-off-by: Jens Langhammer * providers/proxy: fix lint error Signed-off-by: Jens Langhammer * web/admin: fix error on ProxyProviderForm when not using proxy mode Signed-off-by: Jens Langhammer * web/admin: fix default for outpost form's type missing Signed-off-by: Jens Langhammer * web/admin: add additional desc for proxy modes Signed-off-by: Jens Langhammer * outposts: fix service account permissions not always being updated Signed-off-by: Jens Langhammer * outpost/proxy: fix redirecting to incorrect host for domain mode Signed-off-by: Jens Langhammer * web: improve error handling for network errors Signed-off-by: Jens Langhammer * outpost: fix image naming not matching main imaeg Signed-off-by: Jens Langhammer * outposts/proxy: fix redirects for domain mode and traefik Signed-off-by: Jens Langhammer * web: fix colour for paragraphs Signed-off-by: Jens Langhammer * web/flows: fix consent stage not showing permissions correctly Signed-off-by: Jens Langhammer * website/docs: add domain-level docs Signed-off-by: Jens Langhammer * website/docs: fix broken links Signed-off-by: Jens Langhammer * outposts/proxy: remove dead code Signed-off-by: Jens Langhammer * web/flows: fix missing id for #header-text Signed-off-by: Jens Langhammer --- authentik/outposts/tasks.py | 4 +- authentik/providers/proxy/api.py | 20 ++- .../proxy/controllers/k8s/ingress.py | 8 +- .../proxy/controllers/k8s/traefik.py | 4 +- .../0012_proxyprovider_cookie_domain.py | 18 ++ .../providers/proxy/migrations/0013_mode.py | 44 +++++ authentik/providers/proxy/models.py | 14 +- authentik/root/settings.py | 9 +- outpost/azure-pipelines.yml | 8 +- outpost/pkg/proxy/api_bundle.go | 12 +- outpost/pkg/proxy/oauth.go | 27 +-- outpost/pkg/proxy/proxy.go | 73 ++++---- schema.yml | 40 ++++- web/src/api/Sentry.ts | 4 +- web/src/authentik.css | 3 + web/src/flows/stages/consent/ConsentStage.ts | 8 +- web/src/locales/en.po | 50 ++++-- web/src/locales/pseudo-LOCALE.po | 46 +++-- web/src/pages/outposts/OutpostForm.ts | 2 +- .../providers/proxy/ProxyProviderForm.ts | 165 ++++++++++++------ .../providers/proxy/ProxyProviderViewPage.ts | 12 +- website/docs/outposts/{ => ldap}/ldap.md | 24 +-- .../{proxy.mdx => proxy/forward_auth.mdx} | 43 +++-- website/docs/outposts/proxy/proxy.md | 24 +++ website/docs/policies/index.md | 12 +- website/docs/releases/v2021.5.md | 2 +- website/sidebars.js | 29 ++- 27 files changed, 473 insertions(+), 232 deletions(-) create mode 100644 authentik/providers/proxy/migrations/0012_proxyprovider_cookie_domain.py create mode 100644 authentik/providers/proxy/migrations/0013_mode.py rename website/docs/outposts/{ => ldap}/ldap.md (76%) rename website/docs/outposts/{proxy.mdx => proxy/forward_auth.mdx} (82%) create mode 100644 website/docs/outposts/proxy/proxy.md diff --git a/authentik/outposts/tasks.py b/authentik/outposts/tasks.py index e13d6fed8..7bcca0471 100644 --- a/authentik/outposts/tasks.py +++ b/authentik/outposts/tasks.py @@ -149,8 +149,9 @@ def outpost_post_save(model_class: str, model_pk: Any): return if isinstance(instance, Outpost): - LOGGER.debug("Ensuring token for outpost", instance=instance) + LOGGER.debug("Ensuring token and permissions for outpost", instance=instance) _ = instance.token + _ = instance.user LOGGER.debug("Trigger reconcile for outpost") outpost_controller.delay(instance.pk) @@ -201,6 +202,7 @@ def _outpost_single_update(outpost: Outpost, layer=None): # Ensure token again, because this function is called when anything related to an # OutpostModel is saved, so we can be sure permissions are right _ = outpost.token + _ = outpost.user if not layer: # pragma: no cover layer = get_channel_layer() for state in OutpostState.for_outpost(outpost): diff --git a/authentik/providers/proxy/api.py b/authentik/providers/proxy/api.py index 82a929c33..3d3075bdd 100644 --- a/authentik/providers/proxy/api.py +++ b/authentik/providers/proxy/api.py @@ -10,7 +10,7 @@ from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet from authentik.core.api.providers import ProviderSerializer from authentik.core.api.utils import PassiveSerializer from authentik.providers.oauth2.views.provider import ProviderInfoView -from authentik.providers.proxy.models import ProxyProvider +from authentik.providers.proxy.models import ProxyMode, ProxyProvider class OpenIDConnectConfigurationSerializer(PassiveSerializer): @@ -36,9 +36,9 @@ class ProxyProviderSerializer(ProviderSerializer): redirect_uris = CharField(read_only=True) def validate(self, attrs) -> dict[Any, str]: - """Check that internal_host is set when forward_auth_mode is disabled""" + """Check that internal_host is set when mode is Proxy""" if ( - not attrs.get("forward_auth_mode", False) + attrs.get("mode", ProxyMode.PROXY) == ProxyMode.PROXY and attrs.get("internal_host", "") == "" ): raise ValidationError( @@ -70,8 +70,9 @@ class ProxyProviderSerializer(ProviderSerializer): "basic_auth_enabled", "basic_auth_password_attribute", "basic_auth_user_attribute", - "forward_auth_mode", + "mode", "redirect_uris", + "cookie_domain", ] @@ -84,9 +85,15 @@ class ProxyProviderViewSet(ModelViewSet): class ProxyOutpostConfigSerializer(ModelSerializer): - """ProxyProvider Serializer""" + """Proxy provider serializer for outposts""" oidc_configuration = SerializerMethodField() + forward_auth_mode = SerializerMethodField() + + def get_forward_auth_mode(self, instance: ProxyProvider) -> bool: + """Legacy field for 2021.5 outposts""" + # TODO: remove in 2021.7 + return instance.mode in [ProxyMode.FORWARD_SINGLE, ProxyMode.FORWARD_DOMAIN] class Meta: @@ -106,6 +113,9 @@ class ProxyOutpostConfigSerializer(ModelSerializer): "basic_auth_enabled", "basic_auth_password_attribute", "basic_auth_user_attribute", + "mode", + "cookie_domain", + # Legacy field, remove in 2021.7 "forward_auth_mode", ] diff --git a/authentik/providers/proxy/controllers/k8s/ingress.py b/authentik/providers/proxy/controllers/k8s/ingress.py index bf0c588ab..401db2e89 100644 --- a/authentik/providers/proxy/controllers/k8s/ingress.py +++ b/authentik/providers/proxy/controllers/k8s/ingress.py @@ -20,7 +20,7 @@ from authentik.outposts.controllers.k8s.base import ( KubernetesObjectReconciler, NeedsUpdate, ) -from authentik.providers.proxy.models import ProxyProvider +from authentik.providers.proxy.models import ProxyMode, ProxyProvider if TYPE_CHECKING: from authentik.outposts.controllers.kubernetes import KubernetesController @@ -51,7 +51,6 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]): expected_hosts_tls = [] for proxy_provider in ProxyProvider.objects.filter( outpost__in=[self.controller.outpost], - forward_auth_mode=False, ): proxy_provider: ProxyProvider external_host_name = urlparse(proxy_provider.external_host) @@ -105,7 +104,10 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]): external_host_name = urlparse(proxy_provider.external_host) if external_host_name.scheme == "https": tls_hosts.append(external_host_name.hostname) - if proxy_provider.forward_auth_mode: + if proxy_provider.mode in [ + ProxyMode.FORWARD_SINGLE, + ProxyMode.FORWARD_DOMAIN, + ]: rule = NetworkingV1beta1IngressRule( host=external_host_name.hostname, http=NetworkingV1beta1HTTPIngressRuleValue( diff --git a/authentik/providers/proxy/controllers/k8s/traefik.py b/authentik/providers/proxy/controllers/k8s/traefik.py index 3e3c98827..681c764a7 100644 --- a/authentik/providers/proxy/controllers/k8s/traefik.py +++ b/authentik/providers/proxy/controllers/k8s/traefik.py @@ -10,7 +10,7 @@ from authentik.outposts.controllers.k8s.base import ( KubernetesObjectReconciler, NeedsUpdate, ) -from authentik.providers.proxy.models import ProxyProvider +from authentik.providers.proxy.models import ProxyMode, ProxyProvider if TYPE_CHECKING: from authentik.outposts.controllers.kubernetes import KubernetesController @@ -73,7 +73,7 @@ class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware]) def noop(self) -> bool: if not ProxyProvider.objects.filter( outpost__in=[self.controller.outpost], - forward_auth_mode=True, + mode__in=[ProxyMode.FORWARD_SINGLE, ProxyMode.FORWARD_DOMAIN], ).exists(): self.logger.debug("No providers with forward auth enabled.") return True diff --git a/authentik/providers/proxy/migrations/0012_proxyprovider_cookie_domain.py b/authentik/providers/proxy/migrations/0012_proxyprovider_cookie_domain.py new file mode 100644 index 000000000..f88dd6968 --- /dev/null +++ b/authentik/providers/proxy/migrations/0012_proxyprovider_cookie_domain.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.3 on 2021-05-31 20:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_providers_proxy", "0011_proxyprovider_forward_auth_mode"), + ] + + operations = [ + migrations.AddField( + model_name="proxyprovider", + name="cookie_domain", + field=models.TextField(blank=True, default=""), + ), + ] diff --git a/authentik/providers/proxy/migrations/0013_mode.py b/authentik/providers/proxy/migrations/0013_mode.py new file mode 100644 index 000000000..f0acd2548 --- /dev/null +++ b/authentik/providers/proxy/migrations/0013_mode.py @@ -0,0 +1,44 @@ +# Generated by Django 3.2.3 on 2021-06-06 16:29 + +from django.apps.registry import Apps +from django.db import migrations, models +from django.db.backends.base.schema import BaseDatabaseSchemaEditor + + +def migrate_mode(apps: Apps, schema_editor: BaseDatabaseSchemaEditor): + from authentik.providers.proxy.models import ProxyMode + + db_alias = schema_editor.connection.alias + ProxyProvider = apps.get_model("authentik_providers_proxy", "proxyprovider") + for provider in ProxyProvider.objects.using(db_alias).all(): + if provider.forward_auth_mode: + provider.mode = ProxyMode.FORWARD_SINGLE + provider.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_providers_proxy", "0012_proxyprovider_cookie_domain"), + ] + + operations = [ + migrations.AddField( + model_name="proxyprovider", + name="mode", + field=models.TextField( + choices=[ + ("proxy", "Proxy"), + ("forward_single", "Forward Single"), + ("forward_domain", "Forward Domain"), + ], + default="proxy", + help_text="Enable support for forwardAuth in traefik and nginx auth_request. Exclusive with internal_host.", + ), + ), + migrations.RunPython(migrate_mode), + migrations.RemoveField( + model_name="proxyprovider", + name="forward_auth_mode", + ), + ] diff --git a/authentik/providers/proxy/models.py b/authentik/providers/proxy/models.py index 0f668ab29..9672d9f31 100644 --- a/authentik/providers/proxy/models.py +++ b/authentik/providers/proxy/models.py @@ -37,6 +37,14 @@ def _get_callback_url(uri: str) -> str: return urljoin(uri, "/akprox/callback") +class ProxyMode(models.TextChoices): + """All modes a Proxy provider can operate in""" + + PROXY = "proxy" + FORWARD_SINGLE = "forward_single" + FORWARD_DOMAIN = "forward_domain" + + class ProxyProvider(OutpostModel, OAuth2Provider): """Protect applications that don't support any of the other Protocols by using a Reverse-Proxy.""" @@ -53,8 +61,9 @@ class ProxyProvider(OutpostModel, OAuth2Provider): help_text=_("Validate SSL Certificates of upstream servers"), verbose_name=_("Internal host SSL Validation"), ) - forward_auth_mode = models.BooleanField( - default=False, + mode = models.TextField( + default=ProxyMode.PROXY, + choices=ProxyMode.choices, help_text=_( "Enable support for forwardAuth in traefik and nginx auth_request. Exclusive with " "internal_host." @@ -107,6 +116,7 @@ class ProxyProvider(OutpostModel, OAuth2Provider): ) cookie_secret = models.TextField(default=get_cookie_secret) + cookie_domain = models.TextField(default="", blank=True) @property def component(self) -> str: diff --git a/authentik/root/settings.py b/authentik/root/settings.py index 7848a5703..5685f83c2 100644 --- a/authentik/root/settings.py +++ b/authentik/root/settings.py @@ -155,6 +155,7 @@ SPECTACULAR_SETTINGS = { "ChallengeChoices": "authentik.flows.challenge.ChallengeTypes", "FlowDesignationEnum": "authentik.flows.models.FlowDesignation", "PolicyEngineMode": "authentik.policies.models.PolicyEngineMode", + "ProxyMode": "authentik.providers.proxy.models.ProxyMode", }, "ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE": False, "POSTPROCESSING_HOOKS": [ @@ -391,7 +392,10 @@ if _ERROR_REPORTING: STATIC_URL = "/static/" MEDIA_URL = "/media/" -LOG_LEVEL = CONFIG.y("log_level").upper() +TEST = False +TEST_RUNNER = "authentik.root.test_runner.PytestTestRunner" + +LOG_LEVEL = CONFIG.y("log_level").upper() if not TEST else "DEBUG" structlog.configure_once( @@ -449,9 +453,6 @@ LOGGING = { "loggers": {}, } -TEST = False -TEST_RUNNER = "authentik.root.test_runner.PytestTestRunner" - _LOGGING_HANDLER_MAP = { "": LOG_LEVEL, "authentik": LOG_LEVEL, diff --git a/outpost/azure-pipelines.yml b/outpost/azure-pipelines.yml index cf6f6eeb5..7267e1acd 100644 --- a/outpost/azure-pipelines.yml +++ b/outpost/azure-pipelines.yml @@ -77,7 +77,7 @@ stages: buildContext: '$(Build.SourcesDirectory)' tags: | gh-$(branchName) - gh-$(Build.SourceVersion) + gh-$(branchName)-$(timestamp) arguments: '--build-arg GIT_BUILD_HASH=$(Build.SourceVersion)' - task: Docker@2 inputs: @@ -86,7 +86,7 @@ stages: command: 'push' tags: | gh-$(branchName) - gh-$(Build.SourceVersion) + gh-$(branchName)-$(timestamp) - job: ldap_build_docker pool: vmImage: 'ubuntu-latest' @@ -108,7 +108,7 @@ stages: buildContext: '$(Build.SourcesDirectory)' tags: | gh-$(branchName) - gh-$(Build.SourceVersion) + gh-$(branchName)-$(timestamp) arguments: '--build-arg GIT_BUILD_HASH=$(Build.SourceVersion)' - task: Docker@2 inputs: @@ -117,4 +117,4 @@ stages: command: 'push' tags: | gh-$(branchName) - gh-$(Build.SourceVersion) + gh-$(branchName)-$(timestamp) diff --git a/outpost/pkg/proxy/api_bundle.go b/outpost/pkg/proxy/api_bundle.go index 6febe2d61..463072522 100644 --- a/outpost/pkg/proxy/api_bundle.go +++ b/outpost/pkg/proxy/api_bundle.go @@ -64,7 +64,7 @@ func (pb *providerBundle) prepareOpts(provider api.ProxyOutpostConfig) *options. providerOpts.SkipAuthRegex = skipRegexes } - if *provider.ForwardAuthMode { + if *provider.Mode == api.PROXYMODE_FORWARD_SINGLE || *provider.Mode == api.PROXYMODE_FORWARD_DOMAIN { providerOpts.UpstreamServers = []options.Upstream{ { ID: "static", @@ -111,6 +111,10 @@ func (pb *providerBundle) prepareOpts(provider api.ProxyOutpostConfig) *options. func (pb *providerBundle) Build(provider api.ProxyOutpostConfig) { opts := pb.prepareOpts(provider) + if *provider.Mode == api.PROXYMODE_FORWARD_DOMAIN { + opts.Cookie.Domains = []string{*provider.CookieDomain} + } + chain := alice.New() if opts.ForceHTTPS { @@ -123,10 +127,6 @@ func (pb *providerBundle) Build(provider api.ProxyOutpostConfig) { healthCheckPaths := []string{opts.PingPath} healthCheckUserAgents := []string{opts.PingUserAgent} - if opts.GCPHealthChecks { - healthCheckPaths = append(healthCheckPaths, "/liveness_check", "/readiness_check") - healthCheckUserAgents = append(healthCheckUserAgents, "GoogleHC/1.0") - } // To silence logging of health checks, register the health check handler before // the logging handler @@ -153,6 +153,8 @@ func (pb *providerBundle) Build(provider api.ProxyOutpostConfig) { oauthproxy.BasicAuthPasswordAttribute = *provider.BasicAuthPasswordAttribute } + oauthproxy.ExternalHost = pb.Host + pb.proxy = oauthproxy pb.Handler = chain.Then(oauthproxy) } diff --git a/outpost/pkg/proxy/oauth.go b/outpost/pkg/proxy/oauth.go index 3da88990d..3483a83b0 100644 --- a/outpost/pkg/proxy/oauth.go +++ b/outpost/pkg/proxy/oauth.go @@ -106,35 +106,22 @@ func (p *OAuthProxy) IsValidRedirect(redirect string) bool { case strings.HasPrefix(redirect, "http://") || strings.HasPrefix(redirect, "https://"): redirectURL, err := url.Parse(redirect) if err != nil { - p.logger.Printf("Rejecting invalid redirect %q: scheme unsupported or missing", redirect) + p.logger.WithField("redirect", redirect).Printf("Rejecting invalid redirect %q: scheme unsupported or missing", redirect) return false } redirectHostname := redirectURL.Hostname() - for _, domain := range p.whitelistDomains { - domainHostname, domainPort := splitHostPort(strings.TrimLeft(domain, ".")) - if domainHostname == "" { - continue - } - - if (redirectHostname == domainHostname) || (strings.HasPrefix(domain, ".") && strings.HasSuffix(redirectHostname, domainHostname)) { - // the domain names match, now validate the ports - // if the whitelisted domain's port is '*', allow all ports - // if the whitelisted domain contains a specific port, only allow that port - // if the whitelisted domain doesn't contain a port at all, only allow empty redirect ports ie http and https - redirectPort := redirectURL.Port() - if (domainPort == "*") || - (domainPort == redirectPort) || - (domainPort == "" && redirectPort == "") { - return true - } + for _, domain := range p.CookieDomains { + if strings.HasSuffix(redirectHostname, domain) { + p.logger.WithField("redirect", redirect).WithField("domain", domain).Debug("allowing redirect") + return true } } - p.logger.Printf("Rejecting invalid redirect %q: domain / port not in whitelist", redirect) + p.logger.WithField("redirect", redirect).Printf("Rejecting invalid redirect %q: domain / port not in whitelist", redirect) return false default: - p.logger.Printf("Rejecting invalid redirect %q: not an absolute or relative URL", redirect) + p.logger.WithField("redirect", redirect).Printf("Rejecting invalid redirect %q: not an absolute or relative URL", redirect) return false } } diff --git a/outpost/pkg/proxy/proxy.go b/outpost/pkg/proxy/proxy.go index fcfb746be..c0f7ce5f4 100644 --- a/outpost/pkg/proxy/proxy.go +++ b/outpost/pkg/proxy/proxy.go @@ -65,7 +65,7 @@ type OAuthProxy struct { AuthOnlyPath string UserInfoPath string - forwardAuthMode bool + mode api.ProxyMode redirectURL *url.URL // the url to receive requests at whitelistDomains []string provider providers.Provider @@ -77,6 +77,7 @@ type OAuthProxy struct { PassUserHeaders bool BasicAuthUserAttribute string BasicAuthPasswordAttribute string + ExternalHost string PassAccessToken bool SetAuthorization bool PassAuthorization bool @@ -136,7 +137,7 @@ func NewOAuthProxy(opts *options.Options, provider api.ProxyOutpostConfig, c *ht CookieRefresh: opts.Cookie.Refresh, CookieSameSite: opts.Cookie.SameSite, - forwardAuthMode: *provider.ForwardAuthMode, + mode: *provider.Mode, RobotsPath: "/robots.txt", SignInPath: fmt.Sprintf("%s/sign_in", opts.ProxyPrefix), SignOutPath: fmt.Sprintf("%s/sign_out", opts.ProxyPrefix), @@ -216,43 +217,6 @@ func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, m } } -// splitHostPort separates host and port. If the port is not valid, it returns -// the entire input as host, and it doesn't check the validity of the host. -// Unlike net.SplitHostPort, but per RFC 3986, it requires ports to be numeric. -// *** taken from net/url, modified validOptionalPort() to accept ":*" -func splitHostPort(hostport string) (host, port string) { - host = hostport - - colon := strings.LastIndexByte(host, ':') - if colon != -1 && validOptionalPort(host[colon:]) { - host, port = host[:colon], host[colon+1:] - } - - if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { - host = host[1 : len(host)-1] - } - - return -} - -// validOptionalPort reports whether port is either an empty string -// or matches /^:\d*$/ -// *** taken from net/url, modified to accept ":*" -func validOptionalPort(port string) bool { - if port == "" || port == ":*" { - return true - } - if port[0] != ':' { - return false - } - for _, b := range port[1:] { - if b < '0' || b > '9' { - return false - } - } - return true -} - // See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=en var noCacheHeaders = map[string]string{ "Expires": time.Unix(0, 0).Format(time.RFC1123), @@ -340,18 +304,41 @@ func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) { func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) { session, err := p.getAuthenticatedSession(rw, req) if err != nil { - if p.forwardAuthMode { + if p.mode == api.PROXYMODE_FORWARD_SINGLE || p.mode == api.PROXYMODE_FORWARD_DOMAIN { if _, ok := req.URL.Query()["nginx"]; ok { rw.WriteHeader(401) return } if _, ok := req.URL.Query()["traefik"]; ok { - host := getHost(req) + host := "" + // Optional suffix, which is appended to the URL + suffix := "" + if p.mode == api.PROXYMODE_FORWARD_SINGLE { + host = getHost(req) + } else if p.mode == api.PROXYMODE_FORWARD_DOMAIN { + host = p.ExternalHost + // set the ?rd flag to the current URL we have, since we redirect + // to a (possibly) different domain, but we want to be redirected back + // to the application + v := url.Values{ + // see https://doc.traefik.io/traefik/middlewares/forwardauth/ + // X-Forwarded-Uri is only the path, so we need to build the entire URL + "rd": []string{fmt.Sprintf( + "%s://%s%s", + req.Header.Get("X-Forwarded-Proto"), + req.Header.Get("X-Forwarded-Host"), + req.Header.Get("X-Forwarded-Uri"), + )}, + } + suffix = fmt.Sprintf("?%s", v.Encode()) + } proto := req.Header.Get("X-Forwarded-Proto") if proto != "" { proto = proto + ":" } - http.Redirect(rw, req, fmt.Sprintf("%s//%s%s", proto, host, p.OAuthStartPath), http.StatusTemporaryRedirect) + rdFinal := fmt.Sprintf("%s//%s%s%s", proto, host, p.OAuthStartPath, suffix) + p.logger.WithField("url", rdFinal).Debug("Redirecting to login") + http.Redirect(rw, req, rdFinal, http.StatusTemporaryRedirect) return } } @@ -360,7 +347,7 @@ func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) } // we are authenticated p.addHeadersForProxying(rw, req, session) - if p.forwardAuthMode { + if p.mode == api.PROXYMODE_FORWARD_SINGLE || p.mode == api.PROXYMODE_FORWARD_DOMAIN { for headerKey, headers := range req.Header { for _, value := range headers { rw.Header().Set(headerKey, value) diff --git a/schema.yml b/schema.yml index c731fb2ab..57d13354f 100644 --- a/schema.yml +++ b/schema.yml @@ -22966,10 +22966,13 @@ components: title: HTTP-Basic Username Key description: User/Group Attribute used for the user part of the HTTP-Basic Header. If not set, the user's Email address is used. - forward_auth_mode: - type: boolean + mode: + allOf: + - $ref: '#/components/schemas/ProxyMode' description: Enable support for forwardAuth in traefik and nginx auth_request. Exclusive with internal_host. + cookie_domain: + type: string PatchedReputationPolicyRequest: type: object description: Reputation Policy Serializer @@ -23971,9 +23974,15 @@ components: required: - authorization_flow - name + ProxyMode: + enum: + - proxy + - forward_single + - forward_domain + type: string ProxyOutpostConfig: type: object - description: ProxyProvider Serializer + description: Proxy provider serializer for outposts properties: pk: type: integer @@ -24025,12 +24034,19 @@ components: title: HTTP-Basic Username Key description: User/Group Attribute used for the user part of the HTTP-Basic Header. If not set, the user's Email address is used. - forward_auth_mode: - type: boolean + mode: + allOf: + - $ref: '#/components/schemas/ProxyMode' description: Enable support for forwardAuth in traefik and nginx auth_request. Exclusive with internal_host. + cookie_domain: + type: string + forward_auth_mode: + type: boolean + readOnly: true required: - external_host + - forward_auth_mode - name - oidc_configuration - pk @@ -24102,13 +24118,16 @@ components: title: HTTP-Basic Username Key description: User/Group Attribute used for the user part of the HTTP-Basic Header. If not set, the user's Email address is used. - forward_auth_mode: - type: boolean + mode: + allOf: + - $ref: '#/components/schemas/ProxyMode' description: Enable support for forwardAuth in traefik and nginx auth_request. Exclusive with internal_host. redirect_uris: type: string readOnly: true + cookie_domain: + type: string required: - assigned_application_name - assigned_application_slug @@ -24167,10 +24186,13 @@ components: title: HTTP-Basic Username Key description: User/Group Attribute used for the user part of the HTTP-Basic Header. If not set, the user's Email address is used. - forward_auth_mode: - type: boolean + mode: + allOf: + - $ref: '#/components/schemas/ProxyMode' description: Enable support for forwardAuth in traefik and nginx auth_request. Exclusive with internal_host. + cookie_domain: + type: string required: - authorization_flow - external_host diff --git a/web/src/api/Sentry.ts b/web/src/api/Sentry.ts index 69807097d..22790c746 100644 --- a/web/src/api/Sentry.ts +++ b/web/src/api/Sentry.ts @@ -25,8 +25,8 @@ export function configureSentry(canDoPpi: boolean = false, tags: { [key: string] if (hint.originalException instanceof SentryIgnoredError) { return null; } - if (hint.originalException instanceof Error) { - if (hint.originalException.name == 'NetworkError') { + if ((hint.originalException as Error | undefined)?.hasOwnProperty("name")) { + if ((hint.originalException as Error | undefined)?.name == 'NetworkError') { return null; } } diff --git a/web/src/authentik.css b/web/src/authentik.css index 113ab0fd7..8ee207833 100644 --- a/web/src/authentik.css +++ b/web/src/authentik.css @@ -120,6 +120,9 @@ body { .pf-c-title { color: var(--ak-dark-foreground); } + .pf-u-mb-xl { + color: var(--ak-dark-foreground); + } /* Header sections */ .pf-c-page__main-section { --pf-c-page__main-section--BackgroundColor: var(--ak-dark-background); diff --git a/web/src/flows/stages/consent/ConsentStage.ts b/web/src/flows/stages/consent/ConsentStage.ts index 07c62dcb2..20404b8bf 100644 --- a/web/src/flows/stages/consent/ConsentStage.ts +++ b/web/src/flows/stages/consent/ConsentStage.ts @@ -1,11 +1,13 @@ import { t } from "@lingui/macro"; import { CSSResult, customElement, html, TemplateResult } from "lit-element"; import PFLogin from "@patternfly/patternfly/components/Login/login.css"; +import PFList from "@patternfly/patternfly/components/List/list.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; import PFTitle from "@patternfly/patternfly/components/Title/title.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; +import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css"; import AKGlobal from "../../../authentik.css"; import { BaseStage } from "../base"; import "../../../elements/EmptyState"; @@ -18,7 +20,7 @@ import { ifDefined } from "lit-html/directives/if-defined"; export class ConsentStage extends BaseStage { static get styles(): CSSResult[] { - return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton, AKGlobal]; + return [PFBase, PFLogin, PFList, PFForm, PFSpacing, PFFormControl, PFTitle, PFButton, AKGlobal]; } render(): TemplateResult { @@ -44,11 +46,11 @@ export class ConsentStage extends BaseStage
-

+

${this.challenge.headerText}

${this.challenge.permissions.length > 0 ? html` -

${t`Application requires following permissions`}

+

${t`Application requires following permissions:`}

    ${this.challenge.permissions.map((permission) => { return html`
  • ${permission.name}
  • `; diff --git a/web/src/locales/en.po b/web/src/locales/en.po index 6c6e5ad02..0d2e4d167 100644 --- a/web/src/locales/en.po +++ b/web/src/locales/en.po @@ -793,6 +793,10 @@ msgstr "Continue flow without invitation" msgid "Control how authentik exposes and interprets information." msgstr "Control how authentik exposes and interprets information." +#: src/pages/providers/proxy/ProxyProviderForm.ts +msgid "Cookie domain" +msgstr "Cookie domain" + #: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts msgid "Copy" msgstr "Copy" @@ -1302,14 +1306,6 @@ msgstr "Enable TOTP" msgid "Enable compatibility mode, increases compatibility with password managers on mobile devices." msgstr "Enable compatibility mode, increases compatibility with password managers on mobile devices." -#: src/pages/providers/proxy/ProxyProviderForm.ts -msgid "Enable forward-auth mode" -msgstr "Enable forward-auth mode" - -#: src/pages/providers/proxy/ProxyProviderForm.ts -msgid "Enable this if you don't want to use this provider as a proxy, and want to use it with Traefik's forwardAuth or nginx's auth_request." -msgstr "Enable this if you don't want to use this provider as a proxy, and want to use it with Traefik's forwardAuth or nginx's auth_request." - #: src/pages/policies/BoundPoliciesList.ts #: src/pages/policies/PolicyBindingForm.ts #: src/pages/sources/ldap/LDAPSourceForm.ts @@ -1471,6 +1467,8 @@ msgstr "External Applications which use authentik as Identity-Provider, utilizin msgid "External Host" msgstr "External Host" +#: src/pages/providers/proxy/ProxyProviderForm.ts +#: src/pages/providers/proxy/ProxyProviderForm.ts #: src/pages/providers/proxy/ProxyProviderForm.ts msgid "External host" msgstr "External host" @@ -1624,9 +1622,13 @@ msgstr "Forgot username or password?" msgid "Form didn't return a promise for submitting" msgstr "Form didn't return a promise for submitting" -#: src/pages/providers/proxy/ProxyProviderViewPage.ts -msgid "Forward auth" -msgstr "Forward auth" +#: src/pages/providers/proxy/ProxyProviderForm.ts +msgid "Forward auth (domain level)" +msgstr "Forward auth (domain level)" + +#: src/pages/providers/proxy/ProxyProviderForm.ts +msgid "Forward auth (single application)" +msgstr "Forward auth (single application)" #: src/pages/property-mappings/PropertyMappingSAMLForm.ts msgid "Friendly Name" @@ -2179,6 +2181,7 @@ msgstr "Minimum length" #: src/pages/events/TransportForm.ts #: src/pages/events/TransportListPage.ts +#: src/pages/providers/proxy/ProxyProviderViewPage.ts #: src/pages/stages/consent/ConsentStageForm.ts msgid "Mode" msgstr "Mode" @@ -2308,7 +2311,6 @@ msgstr "New version available!" #: src/pages/policies/BoundPoliciesList.ts #: src/pages/policies/PolicyTestForm.ts #: src/pages/providers/proxy/ProxyProviderViewPage.ts -#: src/pages/providers/proxy/ProxyProviderViewPage.ts #: src/pages/tenants/TenantListPage.ts #: src/pages/tokens/TokenListPage.ts #: src/pages/user-settings/tokens/UserTokenList.ts @@ -2514,6 +2516,10 @@ msgstr "Optionally pre-fill the input value" msgid "Optionally set the 'FriendlyName' value of the Assertion attribute." msgstr "Optionally set the 'FriendlyName' value of the Assertion attribute." +#: src/pages/providers/proxy/ProxyProviderForm.ts +msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'." +msgstr "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'." + #: src/pages/flows/BoundStagesList.ts #: src/pages/flows/StageBindingForm.ts #: src/pages/policies/BoundPoliciesList.ts @@ -2753,7 +2759,6 @@ msgstr "Protocol Settings" #: src/pages/providers/ldap/LDAPProviderForm.ts #: src/pages/providers/oauth2/OAuth2ProviderForm.ts -#: src/pages/providers/proxy/ProxyProviderForm.ts #: src/pages/providers/saml/SAMLProviderForm.ts #: src/pages/sources/oauth/OAuthSourceForm.ts #: src/pages/sources/plex/PlexSourceForm.ts @@ -2791,6 +2796,7 @@ msgid "Providers" msgstr "Providers" #: src/pages/outposts/OutpostForm.ts +#: src/pages/providers/proxy/ProxyProviderForm.ts msgid "Proxy" msgstr "Proxy" @@ -3776,10 +3782,15 @@ msgstr "Text: Simple Text input" msgid "The URL \"{0}\" was not found." msgstr "The URL \"{0}\" was not found." +#: src/pages/providers/proxy/ProxyProviderForm.ts #: src/pages/providers/proxy/ProxyProviderForm.ts msgid "The external URL you'll access the application at. Include any non-standard port." msgstr "The external URL you'll access the application at. Include any non-standard port." +#: src/pages/providers/proxy/ProxyProviderForm.ts +msgid "The external URL you'll authenticate at. Can be the same domain as authentik." +msgstr "The external URL you'll authenticate at. Can be the same domain as authentik." + #: src/pages/policies/dummy/DummyPolicyForm.ts msgid "The policy takes a random time to execute. This controls the minimum time it will take." msgstr "The policy takes a random time to execute. This controls the minimum time it will take." @@ -3814,6 +3825,10 @@ msgstr "" msgid "These policies control which users can access this application." msgstr "These policies control which users can access this application." +#: src/pages/providers/proxy/ProxyProviderForm.ts +msgid "This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well." +msgstr "This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well." + #: src/pages/stages/invitation/InvitationStageForm.ts msgid "This stage can be included in enrollment flows to accept invitations." msgstr "This stage can be included in enrollment flows to accept invitations." @@ -4169,6 +4184,14 @@ msgstr "Use the user's email address, but deny enrollment when the email address msgid "Use the user's username, but deny enrollment when the username already exists." msgstr "Use the user's username, but deny enrollment when the username already exists." +#: src/pages/providers/proxy/ProxyProviderForm.ts +msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)." +msgstr "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)." + +#: src/pages/providers/proxy/ProxyProviderForm.ts +msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application." +msgstr "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application." + #: src/pages/tenants/TenantForm.ts msgid "Use this tenant for each domain that doesn't have a dedicated tenant." msgstr "Use this tenant for each domain that doesn't have a dedicated tenant." @@ -4447,7 +4470,6 @@ msgstr "X509 Subject" #: src/pages/policies/BoundPoliciesList.ts #: src/pages/policies/PolicyTestForm.ts #: src/pages/providers/proxy/ProxyProviderViewPage.ts -#: src/pages/providers/proxy/ProxyProviderViewPage.ts #: src/pages/tenants/TenantListPage.ts #: src/pages/tokens/TokenListPage.ts #: src/pages/user-settings/tokens/UserTokenList.ts diff --git a/web/src/locales/pseudo-LOCALE.po b/web/src/locales/pseudo-LOCALE.po index 1d658a95c..4938498a3 100644 --- a/web/src/locales/pseudo-LOCALE.po +++ b/web/src/locales/pseudo-LOCALE.po @@ -787,6 +787,10 @@ msgstr "" msgid "Control how authentik exposes and interprets information." msgstr "" +#: +msgid "Cookie domain" +msgstr "" + #: msgid "Copy" msgstr "" @@ -1294,14 +1298,6 @@ msgstr "" msgid "Enable compatibility mode, increases compatibility with password managers on mobile devices." msgstr "" -#: -msgid "Enable forward-auth mode" -msgstr "" - -#: -msgid "Enable this if you don't want to use this provider as a proxy, and want to use it with Traefik's forwardAuth or nginx's auth_request." -msgstr "" - #: #: #: @@ -1463,6 +1459,8 @@ msgstr "" msgid "External Host" msgstr "" +#: +#: #: msgid "External host" msgstr "" @@ -1617,7 +1615,11 @@ msgid "Form didn't return a promise for submitting" msgstr "" #: -msgid "Forward auth" +msgid "Forward auth (domain level)" +msgstr "" + +#: +msgid "Forward auth (single application)" msgstr "" #: @@ -2172,6 +2174,7 @@ msgstr "" #: #: #: +#: msgid "Mode" msgstr "" @@ -2304,7 +2307,6 @@ msgstr "" #: #: #: -#: msgid "No" msgstr "" @@ -2506,6 +2508,10 @@ msgstr "" msgid "Optionally set the 'FriendlyName' value of the Assertion attribute." msgstr "" +#: +msgid "Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'." +msgstr "" + #: #: #: @@ -2749,7 +2755,6 @@ msgstr "" #: #: #: -#: msgid "Protocol settings" msgstr "" @@ -2782,6 +2787,7 @@ msgstr "" msgid "Providers" msgstr "" +#: #: msgid "Proxy" msgstr "" @@ -3768,10 +3774,15 @@ msgstr "" msgid "The URL \"{0}\" was not found." msgstr "" +#: #: msgid "The external URL you'll access the application at. Include any non-standard port." msgstr "" +#: +msgid "The external URL you'll authenticate at. Can be the same domain as authentik." +msgstr "" + #: msgid "The policy takes a random time to execute. This controls the minimum time it will take." msgstr "" @@ -3802,6 +3813,10 @@ msgstr "" msgid "These policies control which users can access this application." msgstr "" +#: +msgid "This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well." +msgstr "" + #: msgid "This stage can be included in enrollment flows to accept invitations." msgstr "" @@ -4157,6 +4172,14 @@ msgstr "" msgid "Use the user's username, but deny enrollment when the username already exists." msgstr "" +#: +msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you)." +msgstr "" + +#: +msgid "Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application." +msgstr "" + #: msgid "Use this tenant for each domain that doesn't have a dedicated tenant." msgstr "" @@ -4437,7 +4460,6 @@ msgstr "" #: #: #: -#: msgid "Yes" msgstr "" diff --git a/web/src/pages/outposts/OutpostForm.ts b/web/src/pages/outposts/OutpostForm.ts index 38d27771a..268f9df15 100644 --- a/web/src/pages/outposts/OutpostForm.ts +++ b/web/src/pages/outposts/OutpostForm.ts @@ -14,7 +14,7 @@ import { ModelForm } from "../../elements/forms/ModelForm"; export class OutpostForm extends ModelForm { @property() - type!: OutpostTypeEnum; + type: OutpostTypeEnum = OutpostTypeEnum.Proxy; loadInstance(pk: string): Promise { return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesRetrieve({ diff --git a/web/src/pages/providers/proxy/ProxyProviderForm.ts b/web/src/pages/providers/proxy/ProxyProviderForm.ts index 9303c5345..9b21c2dbc 100644 --- a/web/src/pages/providers/proxy/ProxyProviderForm.ts +++ b/web/src/pages/providers/proxy/ProxyProviderForm.ts @@ -1,9 +1,12 @@ -import { CryptoApi, FlowsApi, FlowsInstancesListDesignationEnum, ProvidersApi, ProxyProvider } from "authentik-api"; +import { CryptoApi, FlowsApi, FlowsInstancesListDesignationEnum, ProvidersApi, ProxyMode, ProxyProvider } from "authentik-api"; import { t } from "@lingui/macro"; -import { customElement, property } from "lit-element"; +import { css, CSSResult, customElement, property } from "lit-element"; import { html, TemplateResult } from "lit-html"; import { DEFAULT_CONFIG } from "../../../api/Config"; import { ModelForm } from "../../../elements/forms/ModelForm"; +import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css"; +import PFContent from "@patternfly/patternfly/components/Content/content.css"; +import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css"; import { until } from "lit-html/directives/until"; import { ifDefined } from "lit-html/directives/if-defined"; import "../../../elements/forms/HorizontalFormElement"; @@ -13,12 +16,20 @@ import { first } from "../../../utils"; @customElement("ak-provider-proxy-form") export class ProxyProviderFormPage extends ModelForm { + static get styles(): CSSResult[] { + return super.styles.concat(PFToggleGroup, PFContent, PFSpacing, css` + .pf-c-toggle-group { + justify-content: center; + } + `); + } + loadInstance(pk: number): Promise { return new ProvidersApi(DEFAULT_CONFIG).providersProxyRetrieve({ id: pk, }).then(provider => { this.showHttpBasic = first(provider.basicAuthEnabled, true); - this.showInternalServer = first(!provider.forwardAuthMode, true); + this.mode = first(provider.mode, ProxyMode.Proxy); return provider; }); } @@ -26,8 +37,8 @@ export class ProxyProviderFormPage extends ModelForm { @property({type: Boolean}) showHttpBasic = true; - @property({type: Boolean}) - showInternalServer = true; + @property({attribute: false}) + mode: ProxyMode = ProxyMode.Proxy; getSuccessMessage(): string { if (this.instance) { @@ -38,6 +49,7 @@ export class ProxyProviderFormPage extends ModelForm { } send = (data: ProxyProvider): Promise => { + data.mode = this.mode; if (this.instance) { return new ProvidersApi(DEFAULT_CONFIG).providersProxyUpdate({ id: this.instance.pk || 0, @@ -68,26 +80,94 @@ export class ProxyProviderFormPage extends ModelForm { `; } - renderInternalServer(): TemplateResult { - if (!this.showInternalServer) { - return html``; + renderModeSelector(): TemplateResult { + return html` +
    + +
    + +
    + +
    + +
    + +
    `; + } + + renderSettings(): TemplateResult { + switch (this.mode) { + case ProxyMode.Proxy: + return html` +

    + ${t`This provider will behave like a transparent reverse-proxy, except requests must be authenticated. If your upstream application uses HTTPS, make sure to connect to the outpost using HTTPS as well.`} +

    + + +

    ${t`The external URL you'll access the application at. Include any non-standard port.`}

    +
    + + +

    ${t`Upstream host that the requests are forwarded to.`}

    +
    + +
    + + +
    +

    ${t`Validate SSL Certificates of upstream servers.`}

    +
    `; + case ProxyMode.ForwardSingle: + return html` +

    + ${t`Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /akprox must be routed to the outpost (when using a manged outpost, this is done for you).`} +

    + + +

    ${t`The external URL you'll access the application at. Include any non-standard port.`}

    +
    `; + case ProxyMode.ForwardDomain: + return html` +

    + ${t`Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application.`} +

    + + +

    ${t`The external URL you'll authenticate at. Can be the same domain as authentik.`}

    +
    + + +

    ${t`Optionally set this to your parent domain, if you want authentication and authorization to happen on a domain level. If you're running applications as app1.domain.tld, app2.domain.tld, set this to 'domain.tld'.`}

    +
    `; } - return html` - -

    ${t`Upstream host that the requests are forwarded to.`}

    -
    - -
    - - -
    -

    ${t`Validate SSL Certificates of upstream servers.`}

    -
    `; } renderForm(): TemplateResult { @@ -115,35 +195,16 @@ export class ProxyProviderFormPage extends ModelForm {

    ${t`Flow used when authorizing this provider.`}

    - - - ${t`Protocol settings`} - -
    - - -

    ${t`The external URL you'll access the application at. Include any non-standard port.`}

    -
    - -
    - { - const el = ev.target as HTMLInputElement; - this.showInternalServer = !el.checked; - }}> - -
    -

    - ${t`Enable this if you don't want to use this provider as a proxy, and want to use it with Traefik's forwardAuth or nginx's auth_request.`} -

    -
    - ${this.renderInternalServer()} +
    +
    +
    + ${this.renderModeSelector()} +
    - + +
    diff --git a/web/src/pages/providers/proxy/ProxyProviderViewPage.ts b/web/src/pages/providers/proxy/ProxyProviderViewPage.ts index 77260fb51..1bbcec6bc 100644 --- a/web/src/pages/providers/proxy/ProxyProviderViewPage.ts +++ b/web/src/pages/providers/proxy/ProxyProviderViewPage.ts @@ -20,6 +20,7 @@ import "./ProxyProviderForm"; import { ProvidersApi, ProxyProvider } from "authentik-api"; import { DEFAULT_CONFIG } from "../../../api/Config"; import { EVENT_REFRESH } from "../../../constants"; +import { convertToTitle } from "../../../utils"; @customElement("ak-provider-proxy-view") export class ProxyProviderViewPage extends LitElement { @@ -115,18 +116,11 @@ export class ProxyProviderViewPage extends LitElement {
    - ${t`Forward auth`} + ${t`Mode`}
    - ${this.provider.forwardAuthMode ? - html` -   - ${t`Yes`}`: - html` -   - ${t`No`}` - } + ${convertToTitle(this.provider.mode || "")}
    diff --git a/website/docs/outposts/ldap.md b/website/docs/outposts/ldap/ldap.md similarity index 76% rename from website/docs/outposts/ldap.md rename to website/docs/outposts/ldap/ldap.md index 7ce58d598..90f7c740a 100644 --- a/website/docs/outposts/ldap.md +++ b/website/docs/outposts/ldap/ldap.md @@ -31,24 +31,24 @@ ldapsearch \ The following fields are currently sent for users: -- cn: User's username -- uid: Unique user identifier -- name: User's name -- displayName: User's name -- mail: User's email address -- objectClass: A list of these strings: +- `cn`: User's username +- `uid`: Unique user identifier +- `name`: User's name +- `displayName`: User's name +- `mail`: User's email address +- `objectClass`: A list of these strings: - "user" - "organizationalPerson" - "goauthentik.io/ldap/user" -- accountStatus: "active" if the account is active, otherwise "inactive" -- superuser: "active" if the account is part of a group with superuser permissions, otherwise "inactive" -- memberOf: A list of all DNs that the user is a member of +- `accountStatus`: "active" if the account is active, otherwise "inactive" +- `superuser`: "active" if the account is part of a group with superuser permissions, otherwise "inactive" +- `memberOf`: A list of all DNs that the user is a member of The following fields are current set for groups: -- cn: The group's name -- uid: Unique group identifier -- objectClass: A list of these strings: +- `cn`: The group's name +- `uid`: Unique group identifier +- `objectClass`: A list of these strings: - "group" - "goauthentik.io/ldap/group" diff --git a/website/docs/outposts/proxy.mdx b/website/docs/outposts/proxy/forward_auth.mdx similarity index 82% rename from website/docs/outposts/proxy.mdx rename to website/docs/outposts/proxy/forward_auth.mdx index 0424a2427..8550f82f8 100644 --- a/website/docs/outposts/proxy.mdx +++ b/website/docs/outposts/proxy/forward_auth.mdx @@ -1,31 +1,36 @@ --- -title: Proxy Outpost +title: Forward auth --- -The proxy outpost sets the following headers: +Using forward auth uses your existing reverse proxy to do the proxying, and only uses the +authentik outpost to check authentication and authoirzation. -``` -X-Auth-Username: akadmin # The username of the currently logged in user -X-Forwarded-Email: root@localhost # The email address of the currently logged in user -X-Forwarded-Preferred-Username: akadmin # The username of the currently logged in user -X-Forwarded-User: 900347b8a29876b45ca6f75722635ecfedf0e931c6022e3a29a8aa13fb5516fb # The hashed identifier of the currently logged in user. -``` +To use forward auth instead of proxying, you have to change a couple of settings. +In the Proxy Provider, make sure to use one of the Forward auth modes. -Additionally, you can set `additionalHeaders` on groups or users to set additional headers. +## Single application -If you enable *Set HTTP-Basic Authentication* option, the HTTP Authorization header is being set. +Single application mode works for a single application hosted on its dedicated subdomain. This +has the advantage that you can still do per-application access policies in authentik. -# HTTPS +## Domain level -The outpost listens on both 4180 for HTTP and 4443 for HTTPS. +To use forward auth instead of proxying, you have to change a couple of settings. +In the Proxy Provider, make sure to use the *Forward auth (domain level)* mode. -:::warning -If your upstream host is HTTPS, and you're not using forward auth, you need to access the outpost over HTTPS too. -::: +This mode differs from the *Forward auth (single application)* mode in the following points: +- You don't have to configure an application in authentik for each domain +- Users don't have to authorize multiple times -# Forward auth +There are however also some downsides, mainly the fact that you **can't** restrict individual +applications to different users. -To use forward auth instead of proxying, you have to change a couple of settings. In the Proxy Provider, make sure to enable `Enable forward-auth mode` on the provider. +The only configuration difference between single application and domain level is the host you specify. + +For single application, you'd use the domain which the application is running on, and only /akprox +is redirect to the outpost. + +For domain level, you'd use the same domain as authentik. ## Nginx @@ -42,8 +47,8 @@ import TabItem from '@theme/TabItem'; ``` location /akprox { - proxy_pass http://*ip of your outpost*:4180; - error_page 401 = @akprox_signin; + proxy_pass http://*ip of your outpost*:4180; + error_page 401 = @akprox_signin; proxy_set_header X-Forwarded-Host $http_host; auth_request_set $auth_cookie $upstream_http_set_cookie; add_header Set-Cookie $auth_cookie; diff --git a/website/docs/outposts/proxy/proxy.md b/website/docs/outposts/proxy/proxy.md new file mode 100644 index 000000000..367a148dd --- /dev/null +++ b/website/docs/outposts/proxy/proxy.md @@ -0,0 +1,24 @@ +--- +title: Proxy Outpost +--- + +The proxy outpost sets the following headers: + +``` +X-Auth-Username: akadmin # The username of the currently logged in user +X-Forwarded-Email: root@localhost # The email address of the currently logged in user +X-Forwarded-Preferred-Username: akadmin # The username of the currently logged in user +X-Forwarded-User: 900347b8a29876b45ca6f75722635ecfedf0e931c6022e3a29a8aa13fb5516fb # The hashed identifier of the currently logged in user. +``` + +Additionally, you can set `additionalHeaders` on groups or users to set additional headers. + +If you enable *Set HTTP-Basic Authentication* option, the HTTP Authorization header is being set. + +# HTTPS + +The outpost listens on both 4180 for HTTP and 4443 for HTTPS. + +:::info +If your upstream host is HTTPS, and you're not using forward auth, you need to access the outpost over HTTPS too. +::: diff --git a/website/docs/policies/index.md b/website/docs/policies/index.md index 68ac7899d..8a9b20145 100644 --- a/website/docs/policies/index.md +++ b/website/docs/policies/index.md @@ -2,6 +2,10 @@ title: Policies --- +## Event-matcher policy + +This policy is used by the events subsystem. You can use this policy to match events by multiple different criteria, to choose when you get notified. + ## Reputation Policy authentik keeps track of failed login attempts by source IP and attempted username. These values are saved as scores. Each failed login decreases the score for the client IP as well as the targeted username by 1 (one). @@ -12,10 +16,6 @@ This policy can be used, for example, to prompt clients with a low score to pass See [Expression Policy](expression.mdx). -## Password Policies - ---- - ## Password Policy This policy allows you to specify password rules, such as length and required characters. @@ -34,3 +34,7 @@ This policy checks the hashed password against the [Have I Been Pwned](https://h ## Password-Expiry Policy This policy can enforce regular password rotation by expiring set passwords after a finite amount of time. This forces users to set a new password. + +## Reputation Policy + +This policy checks the reputation of the client's IP address and the username is attempted to be authenticated as. diff --git a/website/docs/releases/v2021.5.md b/website/docs/releases/v2021.5.md index 76daf27b0..203d4e0f7 100644 --- a/website/docs/releases/v2021.5.md +++ b/website/docs/releases/v2021.5.md @@ -20,7 +20,7 @@ This feature is still in technical preview, so please report any Bugs you run in - Compatibility with forwardAuth/auth_request The authentik proxy is now compatible with forwardAuth (traefik) / auth_request (nginx). All that is required is the latest version of the outpost, - and the correct config from [here](../outposts/proxy.mdx). + and the correct config from [here](../outposts/proxy/forward_auth.mdx). - Docker images for ARM diff --git a/website/sidebars.js b/website/sidebars.js index 051085c15..6b415fd74 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -34,11 +34,30 @@ module.exports = { label: "Outposts", items: [ "outposts/outposts", - "outposts/proxy", - "outposts/ldap", - "outposts/upgrading", - "outposts/manual-deploy-docker-compose", - "outposts/manual-deploy-kubernetes", + { + type: "category", + label: "Running and upgrading", + items: [ + "outposts/upgrading", + "outposts/manual-deploy-docker-compose", + "outposts/manual-deploy-kubernetes", + ], + }, + { + type: "category", + label: "Proxy", + items: [ + "outposts/proxy/proxy", + "outposts/proxy/forward_auth", + ], + }, + { + type: "category", + label: "LDAP", + items: [ + "outposts/ldap/ldap", + ], + }, ], }, {