outposts: set cookies for a domain to authenticate an entire domain (#971)
* outposts: initial cookie domain implementation Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web/admin: add cookie domain setting Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * providers/proxy: replace forward_auth_mode with general mode Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web/admin: rebuild proxy provider form Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * providers/proxy: re-add forward_auth_mode for backwards compat Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web/admin: fix data.mode not being set Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * root: always set log level to debug when testing Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * providers/proxy: use new mode attribute Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * providers/proxy: only ingress /akprox on forward_domain Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * providers/proxy: fix lint error Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web/admin: fix error on ProxyProviderForm when not using proxy mode Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web/admin: fix default for outpost form's type missing Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web/admin: add additional desc for proxy modes Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * outposts: fix service account permissions not always being updated Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * outpost/proxy: fix redirecting to incorrect host for domain mode Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: improve error handling for network errors Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * outpost: fix image naming not matching main imaeg Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * outposts/proxy: fix redirects for domain mode and traefik Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: fix colour for paragraphs Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web/flows: fix consent stage not showing permissions correctly Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * website/docs: add domain-level docs Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * website/docs: fix broken links Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * outposts/proxy: remove dead code Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web/flows: fix missing id for #header-text Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
fb8d67a9d9
commit
dad24c03ff
|
@ -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):
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=""),
|
||||
),
|
||||
]
|
44
authentik/providers/proxy/migrations/0013_mode.py
Normal file
44
authentik/providers/proxy/migrations/0013_mode.py
Normal file
|
@ -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",
|
||||
),
|
||||
]
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 == "") {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
40
schema.yml
40
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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<ConsentChallenge, ConsentChallengeResponseRequest> {
|
||||
|
||||
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<ConsentChallenge, ConsentChallengeRe
|
|||
</div>
|
||||
</ak-form-static>
|
||||
<div class="pf-c-form__group">
|
||||
<p id="header-text">
|
||||
<p id="header-text" class="pf-u-mb-xl">
|
||||
${this.challenge.headerText}
|
||||
</p>
|
||||
${this.challenge.permissions.length > 0 ? html`
|
||||
<p>${t`Application requires following permissions`}</p>
|
||||
<p class="pf-u-mb-sm">${t`Application requires following permissions:`}</p>
|
||||
<ul class="pf-c-list" id="permmissions">
|
||||
${this.challenge.permissions.map((permission) => {
|
||||
return html`<li data-permission-code="${permission.id}">${permission.name}</li>`;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import { ModelForm } from "../../elements/forms/ModelForm";
|
|||
export class OutpostForm extends ModelForm<Outpost, string> {
|
||||
|
||||
@property()
|
||||
type!: OutpostTypeEnum;
|
||||
type: OutpostTypeEnum = OutpostTypeEnum.Proxy;
|
||||
|
||||
loadInstance(pk: string): Promise<Outpost> {
|
||||
return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesRetrieve({
|
||||
|
|
|
@ -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<ProxyProvider, number> {
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(PFToggleGroup, PFContent, PFSpacing, css`
|
||||
.pf-c-toggle-group {
|
||||
justify-content: center;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
loadInstance(pk: number): Promise<ProxyProvider> {
|
||||
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<ProxyProvider, number> {
|
|||
@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<ProxyProvider, number> {
|
|||
}
|
||||
|
||||
send = (data: ProxyProvider): Promise<ProxyProvider> => {
|
||||
data.mode = this.mode;
|
||||
if (this.instance) {
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersProxyUpdate({
|
||||
id: this.instance.pk || 0,
|
||||
|
@ -68,11 +80,48 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
|
|||
</ak-form-element-horizontal>`;
|
||||
}
|
||||
|
||||
renderInternalServer(): TemplateResult {
|
||||
if (!this.showInternalServer) {
|
||||
return html``;
|
||||
renderModeSelector(): TemplateResult {
|
||||
return html`
|
||||
<div class="pf-c-toggle-group__item">
|
||||
<button class="pf-c-toggle-group__button ${this.mode === ProxyMode.Proxy ? "pf-m-selected" : ""}" type="button" @click=${() => {
|
||||
this.mode = ProxyMode.Proxy;
|
||||
}}>
|
||||
<span class="pf-c-toggle-group__text">${t`Proxy`}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
|
||||
<div class="pf-c-toggle-group__item">
|
||||
<button class="pf-c-toggle-group__button ${this.mode === ProxyMode.ForwardSingle ? "pf-m-selected" : ""}" type="button" @click=${() => {
|
||||
this.mode = ProxyMode.ForwardSingle;
|
||||
}}>
|
||||
<span class="pf-c-toggle-group__text">${t`Forward auth (single application)`}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
|
||||
<div class="pf-c-toggle-group__item">
|
||||
<button class="pf-c-toggle-group__button ${this.mode === ProxyMode.ForwardDomain ? "pf-m-selected" : ""}" type="button" @click=${() => {
|
||||
this.mode = ProxyMode.ForwardDomain;
|
||||
}}>
|
||||
<span class="pf-c-toggle-group__text">${t`Forward auth (domain level)`}</span>
|
||||
</button>
|
||||
</div>`;
|
||||
}
|
||||
return html`<ak-form-element-horizontal
|
||||
|
||||
renderSettings(): TemplateResult {
|
||||
switch (this.mode) {
|
||||
case ProxyMode.Proxy:
|
||||
return html`
|
||||
<p class="pf-u-mb-xl">
|
||||
${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.`}
|
||||
</p>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`External host`}
|
||||
?required=${true}
|
||||
name="externalHost">
|
||||
<input type="text" value="${ifDefined(this.instance?.externalHost)}" class="pf-c-form-control" required>
|
||||
<p class="pf-c-form__helper-text">${t`The external URL you'll access the application at. Include any non-standard port.`}</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Internal host`}
|
||||
?required=${true}
|
||||
name="internalHost">
|
||||
|
@ -88,6 +137,37 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
|
|||
</div>
|
||||
<p class="pf-c-form__helper-text">${t`Validate SSL Certificates of upstream servers.`}</p>
|
||||
</ak-form-element-horizontal>`;
|
||||
case ProxyMode.ForwardSingle:
|
||||
return html`
|
||||
<p class="pf-u-mb-xl">
|
||||
${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).`}
|
||||
</p>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`External host`}
|
||||
?required=${true}
|
||||
name="externalHost">
|
||||
<input type="text" value="${ifDefined(this.instance?.externalHost)}" class="pf-c-form-control" required>
|
||||
<p class="pf-c-form__helper-text">${t`The external URL you'll access the application at. Include any non-standard port.`}</p>
|
||||
</ak-form-element-horizontal>`;
|
||||
case ProxyMode.ForwardDomain:
|
||||
return html`
|
||||
<p class="pf-u-mb-xl">
|
||||
${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.`}
|
||||
</p>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`External host`}
|
||||
?required=${true}
|
||||
name="externalHost">
|
||||
<input type="text" value="${first(this.instance?.externalHost, window.location.origin)}" class="pf-c-form-control" required>
|
||||
<p class="pf-c-form__helper-text">${t`The external URL you'll authenticate at. Can be the same domain as authentik.`}</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Cookie domain`}
|
||||
name="cookieDomain">
|
||||
<input type="text" value="${ifDefined(this.instance?.cookieDomain)}" class="pf-c-form-control" required>
|
||||
<p class="pf-c-form__helper-text">${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'.`}</p>
|
||||
</ak-form-element-horizontal>`;
|
||||
}
|
||||
}
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
|
@ -115,35 +195,16 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
|
|||
<p class="pf-c-form__helper-text">${t`Flow used when authorizing this provider.`}</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header">
|
||||
${t`Protocol settings`}
|
||||
</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${t`External host`}
|
||||
?required=${true}
|
||||
name="externalHost">
|
||||
<input type="text" value="${ifDefined(this.instance?.externalHost)}" class="pf-c-form-control" required>
|
||||
<p class="pf-c-form__helper-text">${t`The external URL you'll access the application at. Include any non-standard port.`}</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="forwardAuthMode">
|
||||
<div class="pf-c-check">
|
||||
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.forwardAuthMode, false)} @change=${(ev: Event) => {
|
||||
const el = ev.target as HTMLInputElement;
|
||||
this.showInternalServer = !el.checked;
|
||||
}}>
|
||||
<label class="pf-c-check__label">
|
||||
${t`Enable forward-auth mode`}
|
||||
</label>
|
||||
<div class="pf-c-card pf-m-selectable pf-m-selected">
|
||||
<div class="pf-c-card__body">
|
||||
<div class="pf-c-toggle-group">
|
||||
${this.renderModeSelector()}
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-card__footer">
|
||||
${this.renderSettings()}
|
||||
</div>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${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.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
${this.renderInternalServer()}
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
<ak-form-group>
|
||||
<span slot="header">
|
||||
|
|
|
@ -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 {
|
|||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">${t`Forward auth`}</span>
|
||||
<span class="pf-c-description-list__text">${t`Mode`}</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.provider.forwardAuthMode ?
|
||||
html`<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-check-circle" aria-hidden="true"></i>
|
||||
</span>${t`Yes`}`:
|
||||
html`<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-times-circle" aria-hidden="true"></i>
|
||||
</span>${t`No`}`
|
||||
}
|
||||
${convertToTitle(this.provider.mode || "")}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
24
website/docs/outposts/proxy/proxy.md
Normal file
24
website/docs/outposts/proxy/proxy.md
Normal file
|
@ -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.
|
||||
:::
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -34,13 +34,32 @@ module.exports = {
|
|||
label: "Outposts",
|
||||
items: [
|
||||
"outposts/outposts",
|
||||
"outposts/proxy",
|
||||
"outposts/ldap",
|
||||
{
|
||||
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",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Integrations",
|
||||
|
|
Reference in a new issue