From a2c587be434c80a83874da6cbf9925b7220488f7 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 19 Jul 2021 13:17:13 +0200 Subject: [PATCH] outposts: don't authenticate as service user for flows to set remote-ip set outpost token as additional header and check that token (user) if they can override remote-ip Signed-off-by: Jens Langhammer --- authentik/core/models.py | 1 + authentik/lib/utils/http.py | 26 ++++++++++++++++++++------ authentik/outposts/models.py | 10 ++++++++-- internal/outpost/flow.go | 19 +++++++++++++++++++ internal/outpost/ldap/instance_bind.go | 8 +------- 5 files changed, 49 insertions(+), 15 deletions(-) diff --git a/authentik/core/models.py b/authentik/core/models.py index dd24cbd76..00a6121db 100644 --- a/authentik/core/models.py +++ b/authentik/core/models.py @@ -38,6 +38,7 @@ USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug" USER_ATTRIBUTE_SA = "goauthentik.io/user/service-account" USER_ATTRIBUTE_SOURCES = "goauthentik.io/user/sources" USER_ATTRIBUTE_TOKEN_EXPIRING = "goauthentik.io/user/token-expires" # nosec +USER_ATTRIBUTE_CAN_OVERRIDE_IP = "goauthentik.io/user/override-ips" GRAVATAR_URL = "https://secure.gravatar.com" DEFAULT_AVATAR = static("dist/assets/images/user_default.png") diff --git a/authentik/lib/utils/http.py b/authentik/lib/utils/http.py index ac4ed67af..779b3626c 100644 --- a/authentik/lib/utils/http.py +++ b/authentik/lib/utils/http.py @@ -2,10 +2,12 @@ from typing import Any, Optional from django.http import HttpRequest +from structlog.stdlib import get_logger OUTPOST_REMOTE_IP_HEADER = "HTTP_X_AUTHENTIK_REMOTE_IP" -USER_ATTRIBUTE_CAN_OVERRIDE_IP = "goauthentik.io/user/override-ips" +OUTPOST_TOKEN_HEADER = "HTTP_X_AUTHENTIK_OUTPOST_TOKEN" # nosec DEFAULT_IP = "255.255.255.255" +LOGGER = get_logger() def _get_client_ip_from_meta(meta: dict[str, Any]) -> str: @@ -27,13 +29,25 @@ def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]: """Get the actual remote IP when set by an outpost. Only allowed when the request is authenticated, by a user with USER_ATTRIBUTE_CAN_OVERRIDE_IP set to outpost""" - if not hasattr(request, "user"): + from authentik.core.models import ( + USER_ATTRIBUTE_CAN_OVERRIDE_IP, + Token, + TokenIntents, + ) + + if ( + OUTPOST_REMOTE_IP_HEADER not in request.META + or OUTPOST_TOKEN_HEADER not in request.META + ): return None - if not request.user.is_authenticated: + tokens = Token.filter_not_expired( + key=request.META.get(OUTPOST_TOKEN_HEADER), intent=TokenIntents.INTENT_API + ) + if not tokens.exists(): + LOGGER.warning("Attempted remote-ip override without token") return None - if OUTPOST_REMOTE_IP_HEADER not in request.META: - return None - if request.user.group_attributes().get(USER_ATTRIBUTE_CAN_OVERRIDE_IP, False): + user = tokens.first().user + if user.group_attributes().get(USER_ATTRIBUTE_CAN_OVERRIDE_IP, False): return None return request.META[OUTPOST_REMOTE_IP_HEADER] diff --git a/authentik/outposts/models.py b/authentik/outposts/models.py index ba21fdbc3..23ede35a1 100644 --- a/authentik/outposts/models.py +++ b/authentik/outposts/models.py @@ -28,12 +28,18 @@ from structlog.stdlib import get_logger from urllib3.exceptions import HTTPError from authentik import ENV_GIT_HASH_KEY, __version__ -from authentik.core.models import USER_ATTRIBUTE_SA, Provider, Token, TokenIntents, User +from authentik.core.models import ( + USER_ATTRIBUTE_CAN_OVERRIDE_IP, + USER_ATTRIBUTE_SA, + Provider, + Token, + TokenIntents, + User, +) from authentik.crypto.models import CertificateKeyPair from authentik.lib.config import CONFIG from authentik.lib.models import InheritanceForeignKey from authentik.lib.sentry import SentryIgnoredException -from authentik.lib.utils.http import USER_ATTRIBUTE_CAN_OVERRIDE_IP from authentik.managed.models import ManagedModel from authentik.outposts.controllers.k8s.utils import get_namespace from authentik.outposts.docker_tls import DockerInlineTLS diff --git a/internal/outpost/flow.go b/internal/outpost/flow.go index cbae06990..c98b86245 100644 --- a/internal/outpost/flow.go +++ b/internal/outpost/flow.go @@ -4,10 +4,12 @@ import ( "context" "errors" "fmt" + "net" "net/http" "net/http/cookiejar" "net/url" "strconv" + "strings" log "github.com/sirupsen/logrus" "goauthentik.io/api" @@ -24,6 +26,11 @@ const ( StageAccessDenied = StageComponent("ak-stage-access-denied") ) +const ( + HeaderAuthentikRemoteIP = "X-authentik-remote-ip" + HeaderAuthentikOutpostToken = "X-authentik-outpost-token" +) + type FlowExecutor struct { Params url.Values Answers map[StageComponent]string @@ -31,6 +38,7 @@ type FlowExecutor struct { api *api.APIClient flowSlug string log *log.Entry + token string } func NewFlowExecutor(flowSlug string, refConfig *api.Configuration) *FlowExecutor { @@ -56,6 +64,7 @@ func NewFlowExecutor(flowSlug string, refConfig *api.Configuration) *FlowExecuto api: apiClient, flowSlug: flowSlug, log: l, + token: strings.Split(refConfig.DefaultHeader["Authorization"], " ")[1], } } @@ -69,6 +78,16 @@ type ChallengeInt interface { GetResponseErrors() map[string][]api.ErrorDetail } +func (fe *FlowExecutor) DelegateClientIP(a net.Addr) { + host, _, err := net.SplitHostPort(a.String()) + if err != nil { + fe.log.WithError(err).Warning("Failed to get remote IP") + return + } + fe.api.GetConfig().AddDefaultHeader(HeaderAuthentikRemoteIP, host) + fe.api.GetConfig().AddDefaultHeader(HeaderAuthentikOutpostToken, fe.token) +} + func (fe *FlowExecutor) CheckApplicationAccess(appSlug string) (bool, error) { p, _, err := fe.api.CoreApi.CoreApplicationsCheckAccessRetrieve(context.Background(), appSlug).Execute() if !p.Passing { diff --git a/internal/outpost/ldap/instance_bind.go b/internal/outpost/ldap/instance_bind.go index 5954cd5ef..b9c7860fd 100644 --- a/internal/outpost/ldap/instance_bind.go +++ b/internal/outpost/ldap/instance_bind.go @@ -34,14 +34,8 @@ func (pi *ProviderInstance) getUsername(dn string) (string, error) { } func (pi *ProviderInstance) Bind(username string, bindDN, bindPW string, conn net.Conn) (ldap.LDAPResultCode, error) { - host, _, err := net.SplitHostPort(conn.RemoteAddr().String()) - if err != nil { - pi.log.WithError(err).Warning("Failed to get remote IP") - return ldap.LDAPResultOperationsError, nil - } - fe := outpost.NewFlowExecutor(pi.flowSlug, pi.s.ac.Client.GetConfig()) - fe.ApiClient().GetConfig().AddDefaultHeader("X-authentik-remote-ip", host) + fe.DelegateClientIP(conn.RemoteAddr()) fe.Params.Add("goauthentik.io/outpost/ldap", "true") fe.Answers[outpost.StageIdentification] = username