From cf600f6f26dcbd5d68b1cb9225cc3d452f69f2c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Feb 2022 10:16:37 +0100 Subject: [PATCH 01/21] build(deps): bump uvicorn from 0.17.1 to 0.17.3 (#2229) Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.17.1 to 0.17.3. - [Release notes](https://github.com/encode/uvicorn/releases) - [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md) - [Commits](https://github.com/encode/uvicorn/compare/0.17.1...0.17.3) --- updated-dependencies: - dependency-name: uvicorn dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6a40da829..65ca90c80 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1898,7 +1898,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "uvicorn" -version = "0.17.1" +version = "0.17.3" description = "The lightning-fast ASGI server." category = "main" optional = false @@ -3406,8 +3406,8 @@ urllib3 = [ {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] uvicorn = [ - {file = "uvicorn-0.17.1-py3-none-any.whl", hash = "sha256:8b16d9ecb76500f7804184f182835fe8a2b54716d3b0b6bb2da0b2b192f62c73"}, - {file = "uvicorn-0.17.1.tar.gz", hash = "sha256:dffbacb8cc25d924d68d231d2c478c4fe6727c36537d8de21e5de591b37afc41"}, + {file = "uvicorn-0.17.3-py3-none-any.whl", hash = "sha256:3ab1bf48aa512692db93a91c514576a0739a9d3522827e1656a172ee87118fa5"}, + {file = "uvicorn-0.17.3.tar.gz", hash = "sha256:3cebddac78c7dd6bfce2f8f838c2c9b0cfcf3dddf417315ba62378ebe7e8fae2"}, ] uvloop = [ {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"}, From cac8539d7980267961b331675907ac5e9504a4db Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Thu, 3 Feb 2022 17:58:38 +0100 Subject: [PATCH 02/21] providers/proxy: fix nil error in claims Signed-off-by: Jens Langhammer --- internal/outpost/proxyv2/application/claims.go | 16 ++++++++-------- .../application/mode_forward_nginx_test.go | 2 +- .../application/mode_forward_traefik_test.go | 2 +- .../outpost/proxyv2/application/mode_proxy.go | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/internal/outpost/proxyv2/application/claims.go b/internal/outpost/proxyv2/application/claims.go index 15af36eb2..e806610b7 100644 --- a/internal/outpost/proxyv2/application/claims.go +++ b/internal/outpost/proxyv2/application/claims.go @@ -6,14 +6,14 @@ type ProxyClaims struct { } type Claims struct { - Sub string `json:"sub"` - Exp int `json:"exp"` - Email string `json:"email"` - Verified bool `json:"email_verified"` - Proxy ProxyClaims `json:"ak_proxy"` - Name string `json:"name"` - PreferredUsername string `json:"preferred_username"` - Groups []string `json:"groups"` + Sub string `json:"sub"` + Exp int `json:"exp"` + Email string `json:"email"` + Verified bool `json:"email_verified"` + Proxy *ProxyClaims `json:"ak_proxy"` + Name string `json:"name"` + PreferredUsername string `json:"preferred_username"` + Groups []string `json:"groups"` RawToken string } diff --git a/internal/outpost/proxyv2/application/mode_forward_nginx_test.go b/internal/outpost/proxyv2/application/mode_forward_nginx_test.go index f25e293f1..58873fc15 100644 --- a/internal/outpost/proxyv2/application/mode_forward_nginx_test.go +++ b/internal/outpost/proxyv2/application/mode_forward_nginx_test.go @@ -70,7 +70,7 @@ func TestForwardHandleNginx_Single_Claims(t *testing.T) { s, _ := a.sessions.Get(req, constants.SeesionName) s.Values[constants.SessionClaims] = Claims{ Sub: "foo", - Proxy: ProxyClaims{ + Proxy: &ProxyClaims{ UserAttributes: map[string]interface{}{ "username": "foo", "password": "bar", diff --git a/internal/outpost/proxyv2/application/mode_forward_traefik_test.go b/internal/outpost/proxyv2/application/mode_forward_traefik_test.go index fbca244be..e9abe5259 100644 --- a/internal/outpost/proxyv2/application/mode_forward_traefik_test.go +++ b/internal/outpost/proxyv2/application/mode_forward_traefik_test.go @@ -64,7 +64,7 @@ func TestForwardHandleTraefik_Single_Claims(t *testing.T) { s, _ := a.sessions.Get(req, constants.SeesionName) s.Values[constants.SessionClaims] = Claims{ Sub: "foo", - Proxy: ProxyClaims{ + Proxy: &ProxyClaims{ UserAttributes: map[string]interface{}{ "username": "foo", "password": "bar", diff --git a/internal/outpost/proxyv2/application/mode_proxy.go b/internal/outpost/proxyv2/application/mode_proxy.go index 746861dc5..db61eeee5 100644 --- a/internal/outpost/proxyv2/application/mode_proxy.go +++ b/internal/outpost/proxyv2/application/mode_proxy.go @@ -74,7 +74,7 @@ func (a *Application) configureProxy() error { func (a *Application) proxyModifyRequest(ou *url.URL) func(req *http.Request) { return func(r *http.Request) { claims, _ := a.getClaims(r) - if claims.Proxy.BackendOverride != "" { + if claims.Proxy != nil && claims.Proxy.BackendOverride != "" { u, err := url.Parse(claims.Proxy.BackendOverride) if err != nil { a.log.WithField("backend_override", claims.Proxy.BackendOverride).WithError(err).Warning("failed parse user backend override") From e5773738f4c573a74b982441e3bc5d5b2b0bb208 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Thu, 3 Feb 2022 17:58:54 +0100 Subject: [PATCH 03/21] outposts: fix channel not always having a logger attribute Signed-off-by: Jens Langhammer --- authentik/outposts/channels.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/authentik/outposts/channels.py b/authentik/outposts/channels.py index 64bf11be0..194cd6806 100644 --- a/authentik/outposts/channels.py +++ b/authentik/outposts/channels.py @@ -55,6 +55,10 @@ class OutpostConsumer(AuthJsonConsumer): first_msg = False + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.logger = get_logger() + def connect(self): super().connect() uuid = self.scope["url_route"]["kwargs"]["pk"] @@ -65,7 +69,7 @@ class OutpostConsumer(AuthJsonConsumer): ) if not outpost: raise DenyConnection() - self.logger = get_logger().bind(outpost=outpost) + self.logger = self.logger.bind(outpost=outpost) try: self.accept() except RuntimeError as exc: From fa61696b468ebe1db3d70d12c2d56a6db9378e5c Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Thu, 3 Feb 2022 18:20:06 +0100 Subject: [PATCH 04/21] sources/saml: fix incorrect ProtocolBinding being sent closes #2213 Signed-off-by: Jens Langhammer --- authentik/providers/saml/tests/test_auth_n_request.py | 4 ++++ authentik/sources/saml/models.py | 11 +++++++++++ authentik/sources/saml/processors/request.py | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/authentik/providers/saml/tests/test_auth_n_request.py b/authentik/providers/saml/tests/test_auth_n_request.py index 6820191fa..92d457b61 100644 --- a/authentik/providers/saml/tests/test_auth_n_request.py +++ b/authentik/providers/saml/tests/test_auth_n_request.py @@ -15,6 +15,7 @@ from authentik.providers.saml.processors.request_parser import AuthNRequestParse from authentik.sources.saml.exceptions import MismatchedRequestID from authentik.sources.saml.models import SAMLSource from authentik.sources.saml.processors.constants import ( + SAML_BINDING_REDIRECT, SAML_NAME_ID_FORMAT_EMAIL, SAML_NAME_ID_FORMAT_UNSPECIFIED, ) @@ -98,6 +99,9 @@ class TestAuthNRequest(TestCase): # First create an AuthNRequest request_proc = RequestProcessor(self.source, http_request, "test_state") + auth_n = request_proc.get_auth_n() + self.assertEqual(auth_n.attrib["ProtocolBinding"], SAML_BINDING_REDIRECT) + request = request_proc.build_auth_n() # Now we check the ID and signature parsed_request = AuthNRequestParser(self.provider).parse( diff --git a/authentik/sources/saml/models.py b/authentik/sources/saml/models.py index b25f05e6b..dee01a058 100644 --- a/authentik/sources/saml/models.py +++ b/authentik/sources/saml/models.py @@ -18,6 +18,8 @@ from authentik.sources.saml.processors.constants import ( RSA_SHA256, RSA_SHA384, RSA_SHA512, + SAML_BINDING_POST, + SAML_BINDING_REDIRECT, SAML_NAME_ID_FORMAT_EMAIL, SAML_NAME_ID_FORMAT_PERSISTENT, SAML_NAME_ID_FORMAT_TRANSIENT, @@ -37,6 +39,15 @@ class SAMLBindingTypes(models.TextChoices): POST = "POST", _("POST Binding") POST_AUTO = "POST_AUTO", _("POST Binding with auto-confirmation") + @property + def uri(self) -> str: + """Convert database field to URI""" + return { + SAMLBindingTypes.POST: SAML_BINDING_POST, + SAMLBindingTypes.POST_AUTO: SAML_BINDING_POST, + SAMLBindingTypes.REDIRECT: SAML_BINDING_REDIRECT, + }[self] + class SAMLNameIDPolicy(models.TextChoices): """SAML NameID Policies""" diff --git a/authentik/sources/saml/processors/request.py b/authentik/sources/saml/processors/request.py index 90072f8df..8a6d056df 100644 --- a/authentik/sources/saml/processors/request.py +++ b/authentik/sources/saml/processors/request.py @@ -62,7 +62,7 @@ class RequestProcessor: auth_n_request.attrib["Destination"] = self.source.sso_url auth_n_request.attrib["ID"] = self.request_id auth_n_request.attrib["IssueInstant"] = self.issue_instant - auth_n_request.attrib["ProtocolBinding"] = self.source.binding_type + auth_n_request.attrib["ProtocolBinding"] = self.source.binding_type.uri auth_n_request.attrib["Version"] = "2.0" # Create issuer object auth_n_request.append(self.get_issuer()) From dfe0404c519604797908d0efac4fcdccb69f45e9 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 5 Feb 2022 15:41:26 +0100 Subject: [PATCH 05/21] sources/saml: fix server error Signed-off-by: Jens Langhammer --- authentik/sources/saml/processors/request.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/authentik/sources/saml/processors/request.py b/authentik/sources/saml/processors/request.py index 8a6d056df..f7fe9cc10 100644 --- a/authentik/sources/saml/processors/request.py +++ b/authentik/sources/saml/processors/request.py @@ -10,7 +10,7 @@ from lxml.etree import Element # nosec from authentik.providers.saml.utils import get_random_id from authentik.providers.saml.utils.encoding import deflate_and_base64_encode from authentik.providers.saml.utils.time import get_time_string -from authentik.sources.saml.models import SAMLSource +from authentik.sources.saml.models import SAMLBindingTypes, SAMLSource from authentik.sources.saml.processors.constants import ( DIGEST_ALGORITHM_TRANSLATION_MAP, NS_MAP, @@ -62,7 +62,7 @@ class RequestProcessor: auth_n_request.attrib["Destination"] = self.source.sso_url auth_n_request.attrib["ID"] = self.request_id auth_n_request.attrib["IssueInstant"] = self.issue_instant - auth_n_request.attrib["ProtocolBinding"] = self.source.binding_type.uri + auth_n_request.attrib["ProtocolBinding"] = SAMLBindingTypes(self.source.binding_type).uri auth_n_request.attrib["Version"] = "2.0" # Create issuer object auth_n_request.append(self.get_issuer()) From fca88d9896c3249c43cf7a76085bd36781bc1e5f Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 7 Feb 2022 19:37:27 +0100 Subject: [PATCH 06/21] sources/ldap: log entire exception Signed-off-by: Jens Langhammer --- authentik/sources/ldap/tasks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/authentik/sources/ldap/tasks.py b/authentik/sources/ldap/tasks.py index f29eda623..0c94d0528 100644 --- a/authentik/sources/ldap/tasks.py +++ b/authentik/sources/ldap/tasks.py @@ -3,6 +3,7 @@ from ldap3.core.exceptions import LDAPException from structlog.stdlib import get_logger from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus +from authentik.lib.utils.errors import exception_to_string from authentik.lib.utils.reflection import class_to_path, path_to_class from authentik.root.celery import CELERY_APP from authentik.sources.ldap.models import LDAPSource @@ -52,5 +53,5 @@ def ldap_sync(self: MonitoredTask, source_pk: str, sync_class: str): ) except LDAPException as exc: # No explicit event is created here as .set_status with an error will do that - LOGGER.debug(exc) + LOGGER.warning(exception_to_string(exc)) self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc)) From 07548216285963e7921c1fa20d3647c649c5c26e Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 7 Feb 2022 19:59:06 +0100 Subject: [PATCH 07/21] providers/proxy: improve error handling for invalid backend_override Signed-off-by: Jens Langhammer --- internal/outpost/proxyv2/application/mode_proxy.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/outpost/proxyv2/application/mode_proxy.go b/internal/outpost/proxyv2/application/mode_proxy.go index db61eeee5..81f0803bd 100644 --- a/internal/outpost/proxyv2/application/mode_proxy.go +++ b/internal/outpost/proxyv2/application/mode_proxy.go @@ -74,16 +74,16 @@ func (a *Application) configureProxy() error { func (a *Application) proxyModifyRequest(ou *url.URL) func(req *http.Request) { return func(r *http.Request) { claims, _ := a.getClaims(r) + r.URL.Scheme = ou.Scheme + r.URL.Host = ou.Host if claims.Proxy != nil && claims.Proxy.BackendOverride != "" { u, err := url.Parse(claims.Proxy.BackendOverride) if err != nil { a.log.WithField("backend_override", claims.Proxy.BackendOverride).WithError(err).Warning("failed parse user backend override") + return } r.URL.Scheme = u.Scheme r.URL.Host = u.Host - } else { - r.URL.Scheme = ou.Scheme - r.URL.Host = ou.Host } } } From 055a76393d149212bdcecb575381db4c4d386ae0 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 7 Feb 2022 20:26:14 +0100 Subject: [PATCH 08/21] outposts: remove node_port on V1ServicePort checks to prevent service creation loops Signed-off-by: Jens Langhammer #2095 --- .vscode/settings.json | 3 ++- authentik/outposts/controllers/k8s/utils.py | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 27f7700e4..f6b3f2e39 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,7 +12,8 @@ "totp", "webauthn", "traefik", - "passwordless" + "passwordless", + "kubernetes" ], "python.linting.pylintEnabled": true, "todo-tree.tree.showCountsInTree": true, diff --git a/authentik/outposts/controllers/k8s/utils.py b/authentik/outposts/controllers/k8s/utils.py index c44200555..eb9430251 100644 --- a/authentik/outposts/controllers/k8s/utils.py +++ b/authentik/outposts/controllers/k8s/utils.py @@ -1,7 +1,7 @@ """k8s utils""" from pathlib import Path -from kubernetes.client.models.v1_container_port import V1ContainerPort +from kubernetes.client.models.v1_service_port import V1ServicePort from kubernetes.config.incluster_config import SERVICE_TOKEN_FILENAME from authentik.outposts.controllers.k8s.triggers import NeedsRecreate @@ -16,10 +16,13 @@ def get_namespace() -> str: return "default" -def compare_ports(current: list[V1ContainerPort], reference: list[V1ContainerPort]): +def compare_ports(current: list[V1ServicePort], reference: list[V1ServicePort]): """Compare ports of a list""" if len(current) != len(reference): raise NeedsRecreate() for port in reference: + # We don't need to compare node_ports + # https://github.com/goauthentik/authentik/issues/2095#issuecomment-1020674326 + port.node_port = None if port not in current: raise NeedsRecreate() From e875db8f6683628ac91787d748ce52b3f32d67d7 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 7 Feb 2022 20:31:49 +0100 Subject: [PATCH 09/21] stages/authenticator_validate: handle non-existent device_challenges Signed-off-by: Jens Langhammer --- authentik/stages/authenticator_validate/stage.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/authentik/stages/authenticator_validate/stage.py b/authentik/stages/authenticator_validate/stage.py index b16b8d340..d30ff1ad2 100644 --- a/authentik/stages/authenticator_validate/stage.py +++ b/authentik/stages/authenticator_validate/stage.py @@ -196,7 +196,10 @@ class AuthenticatorValidateStageView(ChallengeStageView): return super().get(request, *args, **kwargs) def get_challenge(self) -> AuthenticatorValidationChallenge: - challenges = self.request.session["device_challenges"] + challenges = self.request.session.get("device_challenges") + if not challenges: + LOGGER.debug("Authenticator Validation stage ran without challenges") + return self.executor.stage_invalid() return AuthenticatorValidationChallenge( data={ "type": ChallengeTypes.NATIVE.value, From 67d68629da380173ce15090ab3a1b79cf4a6edab Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Tue, 8 Feb 2022 16:30:26 +0100 Subject: [PATCH 10/21] providers/proxy: fix Host/:Authority not being modified Signed-off-by: Jens Langhammer --- .../outpost/proxyv2/application/mode_proxy.go | 11 ++- .../proxyv2/application/mode_proxy_test.go | 81 +++++++++++++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 internal/outpost/proxyv2/application/mode_proxy_test.go diff --git a/internal/outpost/proxyv2/application/mode_proxy.go b/internal/outpost/proxyv2/application/mode_proxy.go index 81f0803bd..92dedd766 100644 --- a/internal/outpost/proxyv2/application/mode_proxy.go +++ b/internal/outpost/proxyv2/application/mode_proxy.go @@ -76,15 +76,18 @@ func (a *Application) proxyModifyRequest(ou *url.URL) func(req *http.Request) { claims, _ := a.getClaims(r) r.URL.Scheme = ou.Scheme r.URL.Host = ou.Host - if claims.Proxy != nil && claims.Proxy.BackendOverride != "" { + r.Host = ou.Host + if claims != nil && claims.Proxy != nil && claims.Proxy.BackendOverride != "" { u, err := url.Parse(claims.Proxy.BackendOverride) if err != nil { a.log.WithField("backend_override", claims.Proxy.BackendOverride).WithError(err).Warning("failed parse user backend override") - return + } else { + r.URL.Scheme = u.Scheme + r.URL.Host = u.Host + r.Host = u.Host } - r.URL.Scheme = u.Scheme - r.URL.Host = u.Host } + a.log.WithField("upstream_url", r.URL.String()).Trace("final upstream url") } } diff --git a/internal/outpost/proxyv2/application/mode_proxy_test.go b/internal/outpost/proxyv2/application/mode_proxy_test.go new file mode 100644 index 000000000..6aeb6ccb7 --- /dev/null +++ b/internal/outpost/proxyv2/application/mode_proxy_test.go @@ -0,0 +1,81 @@ +package application + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "goauthentik.io/internal/outpost/proxyv2/constants" +) + +func TestProxy_ModifyRequest(t *testing.T) { + a := newTestApplication() + req, _ := http.NewRequest("GET", "http://frontend/foo", nil) + u, err := url.Parse("http://backend:8012") + if err != nil { + panic(err) + } + a.proxyModifyRequest(u)(req) + + assert.Equal(t, "/foo", req.URL.Path) + assert.Equal(t, "backend:8012", req.URL.Host) + assert.Equal(t, "backend:8012", req.Host) +} + +func TestProxy_ModifyRequest_Claims(t *testing.T) { + a := newTestApplication() + req, _ := http.NewRequest("GET", "http://frontend/foo", nil) + u, err := url.Parse("http://backend:8012") + if err != nil { + panic(err) + } + rr := httptest.NewRecorder() + + s, _ := a.sessions.Get(req, constants.SeesionName) + s.Values[constants.SessionClaims] = Claims{ + Sub: "foo", + Proxy: &ProxyClaims{ + BackendOverride: "http://other-backend:8123", + }, + } + err = a.sessions.Save(req, rr, s) + if err != nil { + panic(err) + } + + a.proxyModifyRequest(u)(req) + + assert.Equal(t, "/foo", req.URL.Path) + assert.Equal(t, "other-backend:8123", req.URL.Host) + assert.Equal(t, "other-backend:8123", req.Host) +} + +func TestProxy_ModifyRequest_Claims_Invalid(t *testing.T) { + a := newTestApplication() + req, _ := http.NewRequest("GET", "http://frontend/foo", nil) + u, err := url.Parse("http://backend:8012") + if err != nil { + panic(err) + } + rr := httptest.NewRecorder() + + s, _ := a.sessions.Get(req, constants.SeesionName) + s.Values[constants.SessionClaims] = Claims{ + Sub: "foo", + Proxy: &ProxyClaims{ + BackendOverride: ":qewr", + }, + } + err = a.sessions.Save(req, rr, s) + if err != nil { + panic(err) + } + + a.proxyModifyRequest(u)(req) + + assert.Equal(t, "/foo", req.URL.Path) + assert.Equal(t, "backend:8012", req.URL.Host) + assert.Equal(t, "backend:8012", req.Host) +} From 91227b1e961238d02759ec4e94f5e8197b62282d Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Tue, 8 Feb 2022 17:22:54 +0100 Subject: [PATCH 11/21] outposts: fix service reconciler re-creating services closes #2095 Signed-off-by: Jens Langhammer --- authentik/outposts/controllers/k8s/utils.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/authentik/outposts/controllers/k8s/utils.py b/authentik/outposts/controllers/k8s/utils.py index eb9430251..2605d46b7 100644 --- a/authentik/outposts/controllers/k8s/utils.py +++ b/authentik/outposts/controllers/k8s/utils.py @@ -16,13 +16,22 @@ def get_namespace() -> str: return "default" +def compare_port(current: V1ServicePort, reference: V1ServicePort) -> bool: + """Compare a single port""" + if current.name != reference.name: + return False + # We only care about the target port + if current.target_port != reference.target_port: + return False + if current.protocol != reference.protocol: + return False + return True + + def compare_ports(current: list[V1ServicePort], reference: list[V1ServicePort]): """Compare ports of a list""" if len(current) != len(reference): raise NeedsRecreate() for port in reference: - # We don't need to compare node_ports - # https://github.com/goauthentik/authentik/issues/2095#issuecomment-1020674326 - port.node_port = None - if port not in current: + if not any(compare_port(port, current_port) for current_port in current): raise NeedsRecreate() From ef2eed0bdf32217cbfe3fb7ea451239d42b999f0 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Tue, 8 Feb 2022 17:40:49 +0100 Subject: [PATCH 12/21] outposts: fix compare_ports to support both service and container ports Signed-off-by: Jens Langhammer --- authentik/outposts/controllers/k8s/utils.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/authentik/outposts/controllers/k8s/utils.py b/authentik/outposts/controllers/k8s/utils.py index 2605d46b7..d1f01811f 100644 --- a/authentik/outposts/controllers/k8s/utils.py +++ b/authentik/outposts/controllers/k8s/utils.py @@ -1,6 +1,7 @@ """k8s utils""" from pathlib import Path +from kubernetes.client.models.v1_container_port import V1ContainerPort from kubernetes.client.models.v1_service_port import V1ServicePort from kubernetes.config.incluster_config import SERVICE_TOKEN_FILENAME @@ -16,19 +17,28 @@ def get_namespace() -> str: return "default" -def compare_port(current: V1ServicePort, reference: V1ServicePort) -> bool: +def compare_port( + current: V1ServicePort | V1ContainerPort, reference: V1ServicePort | V1ContainerPort +) -> bool: """Compare a single port""" if current.name != reference.name: return False - # We only care about the target port - if current.target_port != reference.target_port: - return False if current.protocol != reference.protocol: return False + if isinstance(current, V1ServicePort) and isinstance(reference, V1ServicePort): + # We only care about the target port + if current.target_port != reference.target_port: + return False + if isinstance(current, V1ContainerPort) and isinstance(reference, V1ContainerPort): + # We only care about the target port + if current.container_port != reference.container_port: + return False return True -def compare_ports(current: list[V1ServicePort], reference: list[V1ServicePort]): +def compare_ports( + current: list[V1ServicePort | V1ContainerPort], reference: list[V1ServicePort | V1ContainerPort] +): """Compare ports of a list""" if len(current) != len(reference): raise NeedsRecreate() From 1a072c6c39950df1e4e73026c4baf90fb0fe882c Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Tue, 8 Feb 2022 19:03:57 +0100 Subject: [PATCH 13/21] web/admin: fix mismatched icons in overview and lists Signed-off-by: Jens Langhammer --- web/src/pages/admin-overview/AdminOverviewPage.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/src/pages/admin-overview/AdminOverviewPage.ts b/web/src/pages/admin-overview/AdminOverviewPage.ts index 01f62dbec..935924282 100644 --- a/web/src/pages/admin-overview/AdminOverviewPage.ts +++ b/web/src/pages/admin-overview/AdminOverviewPage.ts @@ -110,7 +110,7 @@ export class AdminOverviewPage extends LitElement { class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container" > @@ -121,7 +121,7 @@ export class AdminOverviewPage extends LitElement { class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container" > @@ -132,7 +132,7 @@ export class AdminOverviewPage extends LitElement { class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container" > @@ -143,7 +143,7 @@ export class AdminOverviewPage extends LitElement { class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container" > From 6314be14ad1b42b04d466b56c567545233ad37ab Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Tue, 8 Feb 2022 23:46:23 +0100 Subject: [PATCH 14/21] core: allow formatting strings to be used for applications' launch URLs Signed-off-by: Jens Langhammer --- authentik/core/api/applications.py | 18 ++++++++++++++++-- authentik/core/tests/test_applications_api.py | 12 +++++++----- website/docs/core/applications.md | 3 +++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/authentik/core/api/applications.py b/authentik/core/api/applications.py index eab41f471..dcbb41017 100644 --- a/authentik/core/api/applications.py +++ b/authentik/core/api/applications.py @@ -1,13 +1,16 @@ """Application API Views""" +from typing import Optional + from django.core.cache import cache from django.db.models import QuerySet from django.http.response import HttpResponseBadRequest from django.shortcuts import get_object_or_404 +from django.utils.functional import SimpleLazyObject from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema from guardian.shortcuts import get_objects_for_user from rest_framework.decorators import action -from rest_framework.fields import ReadOnlyField +from rest_framework.fields import ReadOnlyField, SerializerMethodField from rest_framework.parsers import MultiPartParser from rest_framework.request import Request from rest_framework.response import Response @@ -39,11 +42,22 @@ def user_app_cache_key(user_pk: str) -> str: class ApplicationSerializer(ModelSerializer): """Application Serializer""" - launch_url = ReadOnlyField(source="get_launch_url") + launch_url = SerializerMethodField() provider_obj = ProviderSerializer(source="get_provider", required=False) meta_icon = ReadOnlyField(source="get_meta_icon") + def get_launch_url(self, app: Application) -> Optional[str]: + """Allow formatting of launch URL""" + url = app.get_launch_url() + if not url: + return url + user = self.context["request"].user + if isinstance(user, SimpleLazyObject): + user._setup() + user = user._wrapped + return url % user.__dict__ + class Meta: model = Application diff --git a/authentik/core/tests/test_applications_api.py b/authentik/core/tests/test_applications_api.py index bec6632ff..34cb8855c 100644 --- a/authentik/core/tests/test_applications_api.py +++ b/authentik/core/tests/test_applications_api.py @@ -13,7 +13,9 @@ class TestApplicationsAPI(APITestCase): def setUp(self) -> None: self.user = create_test_admin_user() - self.allowed = Application.objects.create(name="allowed", slug="allowed") + self.allowed = Application.objects.create( + name="allowed", slug="allowed", meta_launch_url="https://goauthentik.io/%(username)s" + ) self.denied = Application.objects.create(name="denied", slug="denied") PolicyBinding.objects.create( target=self.denied, @@ -64,8 +66,8 @@ class TestApplicationsAPI(APITestCase): "slug": "allowed", "provider": None, "provider_obj": None, - "launch_url": None, - "meta_launch_url": "", + "launch_url": f"https://goauthentik.io/{self.user.username}", + "meta_launch_url": "https://goauthentik.io/%(username)s", "meta_icon": None, "meta_description": "", "meta_publisher": "", @@ -100,8 +102,8 @@ class TestApplicationsAPI(APITestCase): "slug": "allowed", "provider": None, "provider_obj": None, - "launch_url": None, - "meta_launch_url": "", + "launch_url": f"https://goauthentik.io/{self.user.username}", + "meta_launch_url": "https://goauthentik.io/%(username)s", "meta_icon": None, "meta_description": "", "meta_publisher": "", diff --git a/website/docs/core/applications.md b/website/docs/core/applications.md index f0d5e0905..3cc8a006e 100644 --- a/website/docs/core/applications.md +++ b/website/docs/core/applications.md @@ -24,6 +24,9 @@ The following aspects can be configured: - *Name*: This is the name shown for the application card - *Launch URL*: The URL that is opened when a user clicks on the application. When left empty, authentik tries to guess it based on the provider + + Starting with authentik 2022.2, you can use placeholders in the launch url to build them dynamically based on logged in user. For example, you can set the Launch URL to `https://goauthentik.io/%(username)s`, which will be replaced with the currently logged in user's username. + - *Icon (URL)*: Optionally configure an Icon for the application - *Publisher*: Text shown below the application - *Description*: Subtext shown on the application card below the publisher From 48f4a971efb5dd594733d643d39286ffdd5afc48 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Wed, 9 Feb 2022 12:33:25 +0100 Subject: [PATCH 15/21] internal: don't attempt to lookup SNI Certificate if no SNI is sent Signed-off-by: Jens Langhammer --- internal/outpost/proxyv2/proxyv2.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/outpost/proxyv2/proxyv2.go b/internal/outpost/proxyv2/proxyv2.go index 7ff1e33b1..5d3363fbd 100644 --- a/internal/outpost/proxyv2/proxyv2.go +++ b/internal/outpost/proxyv2/proxyv2.go @@ -102,7 +102,11 @@ func (ps *ProxyServer) GetCertificate(serverName string) *tls.Certificate { } func (ps *ProxyServer) getCertificates(info *tls.ClientHelloInfo) (*tls.Certificate, error) { - appCert := ps.GetCertificate(info.ServerName) + sn := info.ServerName + if sn == "" { + return &ps.defaultCert, nil + } + appCert := ps.GetCertificate(sn) if appCert == nil { return &ps.defaultCert, nil } From 3e403fa3484fea72b130868b588af6608b919818 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Wed, 9 Feb 2022 12:33:37 +0100 Subject: [PATCH 16/21] internal: improve error handling for internal reverse proxy Signed-off-by: Jens Langhammer --- internal/web/proxy.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/internal/web/proxy.go b/internal/web/proxy.go index 44cb26cdf..4c0b1477b 100644 --- a/internal/web/proxy.go +++ b/internal/web/proxy.go @@ -1,6 +1,7 @@ package web import ( + "encoding/json" "fmt" "net/http" "net/http/httputil" @@ -67,7 +68,18 @@ func (ws *WebServer) configureProxy() { func (ws *WebServer) proxyErrorHandler(rw http.ResponseWriter, req *http.Request, err error) { ws.log.Warning(err.Error()) rw.WriteHeader(http.StatusBadGateway) - _, err = rw.Write([]byte("authentik starting...")) + em := fmt.Sprintf("failed to connect to authentik backend: %v", err) + if !ws.p.IsRunning() { + em = "authentik starting..." + } + // return json if the client asks for json + if req.Header.Get("Accept") == "application/json" { + eem, _ := json.Marshal(map[string]string{ + "error": em, + }) + em = string(eem) + } + _, err = rw.Write([]byte(em)) if err != nil { ws.log.WithError(err).Warning("failed to write error message") } From d0b9c9a26f8994ff20019a0754d92378b7feea34 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Wed, 9 Feb 2022 12:38:47 +0100 Subject: [PATCH 17/21] internal: remove uvicorn server header Signed-off-by: Jens Langhammer --- internal/web/proxy.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/web/proxy.go b/internal/web/proxy.go index 4c0b1477b..269a1ebbc 100644 --- a/internal/web/proxy.go +++ b/internal/web/proxy.go @@ -87,5 +87,6 @@ func (ws *WebServer) proxyErrorHandler(rw http.ResponseWriter, req *http.Request func (ws *WebServer) proxyModifyResponse(r *http.Response) error { r.Header.Set("X-Powered-By", "authentik") + r.Header.Del("Server") return nil } From e70e6b84c245188a5237659f1929e3214e021f4e Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Wed, 9 Feb 2022 12:48:17 +0100 Subject: [PATCH 18/21] internal: trace headers and url for backend requests Signed-off-by: Jens Langhammer --- internal/web/proxy.go | 4 +++- internal/web/tls.go | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/web/proxy.go b/internal/web/proxy.go index 269a1ebbc..1428f28e5 100644 --- a/internal/web/proxy.go +++ b/internal/web/proxy.go @@ -18,6 +18,7 @@ func (ws *WebServer) configureProxy() { director := func(req *http.Request) { req.URL.Scheme = u.Scheme req.URL.Host = u.Host + req.Host = u.Host if _, ok := req.Header["User-Agent"]; !ok { // explicitly disable User-Agent so it's not set to default value req.Header.Set("User-Agent", "") @@ -25,6 +26,7 @@ func (ws *WebServer) configureProxy() { if req.TLS != nil { req.Header.Set("X-Forwarded-Proto", "https") } + ws.log.WithField("url", req.URL.String()).WithField("headers", req.Header).Trace("tracing request to backend") } rp := &httputil.ReverseProxy{Director: director} rp.ErrorHandler = ws.proxyErrorHandler @@ -66,7 +68,7 @@ func (ws *WebServer) configureProxy() { } func (ws *WebServer) proxyErrorHandler(rw http.ResponseWriter, req *http.Request, err error) { - ws.log.Warning(err.Error()) + ws.log.WithError(err).Warning("failed to proxy to backend") rw.WriteHeader(http.StatusBadGateway) em := fmt.Sprintf("failed to connect to authentik backend: %v", err) if !ws.p.IsRunning() { diff --git a/internal/web/tls.go b/internal/web/tls.go index 6a1eb3785..18dce9d1b 100644 --- a/internal/web/tls.go +++ b/internal/web/tls.go @@ -16,6 +16,9 @@ func (ws *WebServer) GetCertificate() func(ch *tls.ClientHelloInfo) (*tls.Certif ws.log.WithError(err).Error("failed to generate default cert") } return func(ch *tls.ClientHelloInfo) (*tls.Certificate, error) { + if ch.ServerName == "" { + return &cert, nil + } if ws.ProxyServer != nil { appCert := ws.ProxyServer.GetCertificate(ch.ServerName) if appCert != nil { From 015810a2fdf7c48c29f758dd7a91037b7a60beee Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Wed, 9 Feb 2022 14:34:48 +0100 Subject: [PATCH 19/21] internal: fix CSRF error caused by Host header Signed-off-by: Jens Langhammer --- internal/web/proxy.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/web/proxy.go b/internal/web/proxy.go index 1428f28e5..6a65b9249 100644 --- a/internal/web/proxy.go +++ b/internal/web/proxy.go @@ -18,7 +18,6 @@ func (ws *WebServer) configureProxy() { director := func(req *http.Request) { req.URL.Scheme = u.Scheme req.URL.Host = u.Host - req.Host = u.Host if _, ok := req.Header["User-Agent"]; !ok { // explicitly disable User-Agent so it's not set to default value req.Header.Set("User-Agent", "") From 511a94975b5302e8159d72ac305f076c2bab1f40 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Wed, 9 Feb 2022 22:31:14 +0100 Subject: [PATCH 20/21] website/docs: add 2022.1.5 release notes Signed-off-by: Jens Langhammer --- website/docs/releases/v2022.1.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/website/docs/releases/v2022.1.md b/website/docs/releases/v2022.1.md index 558218662..dcaf5cf16 100644 --- a/website/docs/releases/v2022.1.md +++ b/website/docs/releases/v2022.1.md @@ -102,6 +102,28 @@ This release mostly removes legacy fields and features that have been deprecated - web/flows: fix width on flow container - web/user: include locale code in locale selection +## Fixed in 2022.1.5 + +- build(deps): bump uvicorn from 0.17.1 to 0.17.3 (#2229) +- core: allow formatting strings to be used for applications' launch URLs +- internal: don't attempt to lookup SNI Certificate if no SNI is sent +- internal: fix CSRF error caused by Host header +- internal: improve error handling for internal reverse proxy +- internal: remove uvicorn server header +- internal: trace headers and url for backend requests +- outposts: fix channel not always having a logger attribute +- outposts: fix compare_ports to support both service and container ports +- outposts: fix service reconciler re-creating services +- outposts: remove node_port on V1ServicePort checks to prevent service creation loops +- providers/proxy: fix Host/:Authority not being modified +- providers/proxy: fix nil error in claims +- providers/proxy: improve error handling for invalid backend_override +- sources/ldap: log entire exception +- sources/saml: fix incorrect ProtocolBinding being sent +- sources/saml: fix server error +- stages/authenticator_validate: handle non-existent device_challenges +- web/admin: fix mismatched icons in overview and lists + ## Upgrading This release does not introduce any new requirements. From eaad564e236e9d62c6a91e235e49ad0fc1e86c9b Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Wed, 9 Feb 2022 22:31:26 +0100 Subject: [PATCH 21/21] release: 2022.1.5 --- .bumpversion.cfg | 2 +- .github/workflows/release-publish.yml | 14 +++++++------- authentik/__init__.py | 2 +- docker-compose.yml | 4 ++-- internal/constants/constants.go | 2 +- pyproject.toml | 2 +- schema.yml | 2 +- web/src/constants.ts | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index ffe2f1912..568a13535 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2022.1.4 +current_version = 2022.1.5 tag = True commit = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)\-?(?P.*) diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index 43836d090..975d97ea6 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -30,14 +30,14 @@ jobs: with: push: ${{ github.event_name == 'release' }} tags: | - beryju/authentik:2022.1.4, + beryju/authentik:2022.1.5, beryju/authentik:latest, - ghcr.io/goauthentik/server:2022.1.4, + ghcr.io/goauthentik/server:2022.1.5, ghcr.io/goauthentik/server:latest platforms: linux/amd64,linux/arm64 context: . - name: Building Docker Image (stable) - if: ${{ github.event_name == 'release' && !contains('2022.1.4', 'rc') }} + if: ${{ github.event_name == 'release' && !contains('2022.1.5', 'rc') }} run: | docker pull beryju/authentik:latest docker tag beryju/authentik:latest beryju/authentik:stable @@ -78,14 +78,14 @@ jobs: with: push: ${{ github.event_name == 'release' }} tags: | - beryju/authentik-${{ matrix.type }}:2022.1.4, + beryju/authentik-${{ matrix.type }}:2022.1.5, beryju/authentik-${{ matrix.type }}:latest, - ghcr.io/goauthentik/${{ matrix.type }}:2022.1.4, + ghcr.io/goauthentik/${{ matrix.type }}:2022.1.5, ghcr.io/goauthentik/${{ matrix.type }}:latest file: ${{ matrix.type }}.Dockerfile platforms: linux/amd64,linux/arm64 - name: Building Docker Image (stable) - if: ${{ github.event_name == 'release' && !contains('2022.1.4', 'rc') }} + if: ${{ github.event_name == 'release' && !contains('2022.1.5', 'rc') }} run: | docker pull beryju/authentik-${{ matrix.type }}:latest docker tag beryju/authentik-${{ matrix.type }}:latest beryju/authentik-${{ matrix.type }}:stable @@ -170,7 +170,7 @@ jobs: SENTRY_PROJECT: authentik SENTRY_URL: https://sentry.beryju.org with: - version: authentik@2022.1.4 + version: authentik@2022.1.5 environment: beryjuorg-prod sourcemaps: './web/dist' url_prefix: '~/static/dist' diff --git a/authentik/__init__.py b/authentik/__init__.py index 6ef2f5a82..5d0716b36 100644 --- a/authentik/__init__.py +++ b/authentik/__init__.py @@ -2,7 +2,7 @@ from os import environ from typing import Optional -__version__ = "2022.1.4" +__version__ = "2022.1.5" ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" diff --git a/docker-compose.yml b/docker-compose.yml index 363e3b161..660d539f1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ services: image: redis:alpine restart: unless-stopped server: - image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.1.4} + image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.1.5} restart: unless-stopped command: server environment: @@ -38,7 +38,7 @@ services: - "0.0.0.0:${AUTHENTIK_PORT_HTTP:-9000}:9000" - "0.0.0.0:${AUTHENTIK_PORT_HTTPS:-9443}:9443" worker: - image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.1.4} + image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.1.5} restart: unless-stopped command: worker environment: diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 41cabe9d4..e86ffa942 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -25,4 +25,4 @@ func OutpostUserAgent() string { return fmt.Sprintf("authentik-outpost@%s", FullVersion()) } -const VERSION = "2022.1.4" +const VERSION = "2022.1.5" diff --git a/pyproject.toml b/pyproject.toml index 139f45d66..a4cf4591b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,7 +92,7 @@ addopts = "-p no:celery --junitxml=unittest.xml" [tool.poetry] name = "authentik" -version = "2022.1.4" +version = "2022.1.5" description = "" authors = ["Jens Langhammer "] diff --git a/schema.yml b/schema.yml index dd2fc8f40..8ad2de101 100644 --- a/schema.yml +++ b/schema.yml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: authentik - version: 2022.1.4 + version: 2022.1.5 description: Making authentication simple. contact: email: hello@beryju.org diff --git a/web/src/constants.ts b/web/src/constants.ts index f1226a633..7c7e747b8 100644 --- a/web/src/constants.ts +++ b/web/src/constants.ts @@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success"; export const ERROR_CLASS = "pf-m-danger"; export const PROGRESS_CLASS = "pf-m-in-progress"; export const CURRENT_CLASS = "pf-m-current"; -export const VERSION = "2022.1.4"; +export const VERSION = "2022.1.5"; export const TITLE_DEFAULT = "authentik"; export const ROUTE_SEPARATOR = ";";