Compare commits

..

21 Commits

Author SHA1 Message Date
eaad564e23 release: 2022.1.5 2022-02-09 22:31:26 +01:00
511a94975b website/docs: add 2022.1.5 release notes
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:31:14 +01:00
015810a2fd internal: fix CSRF error caused by Host header
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:53 +01:00
e70e6b84c2 internal: trace headers and url for backend requests
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:50 +01:00
d0b9c9a26f internal: remove uvicorn server header
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:46 +01:00
3e403fa348 internal: improve error handling for internal reverse proxy
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:41 +01:00
48f4a971ef internal: don't attempt to lookup SNI Certificate if no SNI is sent
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:39 +01:00
6314be14ad core: allow formatting strings to be used for applications' launch URLs
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:29 +01:00
1a072c6c39 web/admin: fix mismatched icons in overview and lists
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:26 +01:00
ef2eed0bdf outposts: fix compare_ports to support both service and container ports
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:20 +01:00
91227b1e96 outposts: fix service reconciler re-creating services
closes #2095

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:16 +01:00
67d68629da providers/proxy: fix Host/:Authority not being modified
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:08 +01:00
e875db8f66 stages/authenticator_validate: handle non-existent device_challenges
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:22:02 +01:00
055a76393d outposts: remove node_port on V1ServicePort checks to prevent service creation loops
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#2095
2022-02-09 22:21:58 +01:00
0754821628 providers/proxy: improve error handling for invalid backend_override
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:21:55 +01:00
fca88d9896 sources/ldap: log entire exception
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:21:48 +01:00
dfe0404c51 sources/saml: fix server error
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:21:24 +01:00
fa61696b46 sources/saml: fix incorrect ProtocolBinding being sent
closes #2213

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:21:15 +01:00
e5773738f4 outposts: fix channel not always having a logger attribute
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:21:12 +01:00
cac8539d79 providers/proxy: fix nil error in claims
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2022-02-09 22:21:08 +01:00
cf600f6f26 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] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-09 17:56:53 +01:00
30 changed files with 248 additions and 56 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 2022.1.4 current_version = 2022.1.5
tag = True tag = True
commit = True commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*) parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)

View File

@ -30,14 +30,14 @@ jobs:
with: with:
push: ${{ github.event_name == 'release' }} push: ${{ github.event_name == 'release' }}
tags: | tags: |
beryju/authentik:2022.1.4, beryju/authentik:2022.1.5,
beryju/authentik:latest, beryju/authentik:latest,
ghcr.io/goauthentik/server:2022.1.4, ghcr.io/goauthentik/server:2022.1.5,
ghcr.io/goauthentik/server:latest ghcr.io/goauthentik/server:latest
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
context: . context: .
- name: Building Docker Image (stable) - 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: | run: |
docker pull beryju/authentik:latest docker pull beryju/authentik:latest
docker tag beryju/authentik:latest beryju/authentik:stable docker tag beryju/authentik:latest beryju/authentik:stable
@ -78,14 +78,14 @@ jobs:
with: with:
push: ${{ github.event_name == 'release' }} push: ${{ github.event_name == 'release' }}
tags: | tags: |
beryju/authentik-${{ matrix.type }}:2022.1.4, beryju/authentik-${{ matrix.type }}:2022.1.5,
beryju/authentik-${{ matrix.type }}:latest, 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 ghcr.io/goauthentik/${{ matrix.type }}:latest
file: ${{ matrix.type }}.Dockerfile file: ${{ matrix.type }}.Dockerfile
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
- name: Building Docker Image (stable) - 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: | run: |
docker pull beryju/authentik-${{ matrix.type }}:latest docker pull beryju/authentik-${{ matrix.type }}:latest
docker tag beryju/authentik-${{ matrix.type }}:latest beryju/authentik-${{ matrix.type }}:stable docker tag beryju/authentik-${{ matrix.type }}:latest beryju/authentik-${{ matrix.type }}:stable
@ -170,7 +170,7 @@ jobs:
SENTRY_PROJECT: authentik SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org SENTRY_URL: https://sentry.beryju.org
with: with:
version: authentik@2022.1.4 version: authentik@2022.1.5
environment: beryjuorg-prod environment: beryjuorg-prod
sourcemaps: './web/dist' sourcemaps: './web/dist'
url_prefix: '~/static/dist' url_prefix: '~/static/dist'

View File

@ -12,7 +12,8 @@
"totp", "totp",
"webauthn", "webauthn",
"traefik", "traefik",
"passwordless" "passwordless",
"kubernetes"
], ],
"python.linting.pylintEnabled": true, "python.linting.pylintEnabled": true,
"todo-tree.tree.showCountsInTree": true, "todo-tree.tree.showCountsInTree": true,

View File

@ -2,7 +2,7 @@
from os import environ from os import environ
from typing import Optional from typing import Optional
__version__ = "2022.1.4" __version__ = "2022.1.5"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -1,13 +1,16 @@
"""Application API Views""" """Application API Views"""
from typing import Optional
from django.core.cache import cache from django.core.cache import cache
from django.db.models import QuerySet from django.db.models import QuerySet
from django.http.response import HttpResponseBadRequest from django.http.response import HttpResponseBadRequest
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.functional import SimpleLazyObject
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action 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.parsers import MultiPartParser
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
@ -39,11 +42,22 @@ def user_app_cache_key(user_pk: str) -> str:
class ApplicationSerializer(ModelSerializer): class ApplicationSerializer(ModelSerializer):
"""Application Serializer""" """Application Serializer"""
launch_url = ReadOnlyField(source="get_launch_url") launch_url = SerializerMethodField()
provider_obj = ProviderSerializer(source="get_provider", required=False) provider_obj = ProviderSerializer(source="get_provider", required=False)
meta_icon = ReadOnlyField(source="get_meta_icon") 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: class Meta:
model = Application model = Application

View File

@ -13,7 +13,9 @@ class TestApplicationsAPI(APITestCase):
def setUp(self) -> None: def setUp(self) -> None:
self.user = create_test_admin_user() 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") self.denied = Application.objects.create(name="denied", slug="denied")
PolicyBinding.objects.create( PolicyBinding.objects.create(
target=self.denied, target=self.denied,
@ -64,8 +66,8 @@ class TestApplicationsAPI(APITestCase):
"slug": "allowed", "slug": "allowed",
"provider": None, "provider": None,
"provider_obj": None, "provider_obj": None,
"launch_url": None, "launch_url": f"https://goauthentik.io/{self.user.username}",
"meta_launch_url": "", "meta_launch_url": "https://goauthentik.io/%(username)s",
"meta_icon": None, "meta_icon": None,
"meta_description": "", "meta_description": "",
"meta_publisher": "", "meta_publisher": "",
@ -100,8 +102,8 @@ class TestApplicationsAPI(APITestCase):
"slug": "allowed", "slug": "allowed",
"provider": None, "provider": None,
"provider_obj": None, "provider_obj": None,
"launch_url": None, "launch_url": f"https://goauthentik.io/{self.user.username}",
"meta_launch_url": "", "meta_launch_url": "https://goauthentik.io/%(username)s",
"meta_icon": None, "meta_icon": None,
"meta_description": "", "meta_description": "",
"meta_publisher": "", "meta_publisher": "",

View File

@ -55,6 +55,10 @@ class OutpostConsumer(AuthJsonConsumer):
first_msg = False first_msg = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.logger = get_logger()
def connect(self): def connect(self):
super().connect() super().connect()
uuid = self.scope["url_route"]["kwargs"]["pk"] uuid = self.scope["url_route"]["kwargs"]["pk"]
@ -65,7 +69,7 @@ class OutpostConsumer(AuthJsonConsumer):
) )
if not outpost: if not outpost:
raise DenyConnection() raise DenyConnection()
self.logger = get_logger().bind(outpost=outpost) self.logger = self.logger.bind(outpost=outpost)
try: try:
self.accept() self.accept()
except RuntimeError as exc: except RuntimeError as exc:

View File

@ -2,6 +2,7 @@
from pathlib import Path from pathlib import Path
from kubernetes.client.models.v1_container_port import V1ContainerPort 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 kubernetes.config.incluster_config import SERVICE_TOKEN_FILENAME
from authentik.outposts.controllers.k8s.triggers import NeedsRecreate from authentik.outposts.controllers.k8s.triggers import NeedsRecreate
@ -16,10 +17,31 @@ def get_namespace() -> str:
return "default" return "default"
def compare_ports(current: list[V1ContainerPort], reference: list[V1ContainerPort]): def compare_port(
current: V1ServicePort | V1ContainerPort, reference: V1ServicePort | V1ContainerPort
) -> bool:
"""Compare a single port"""
if current.name != reference.name:
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 | V1ContainerPort], reference: list[V1ServicePort | V1ContainerPort]
):
"""Compare ports of a list""" """Compare ports of a list"""
if len(current) != len(reference): if len(current) != len(reference):
raise NeedsRecreate() raise NeedsRecreate()
for port in reference: for port in reference:
if port not in current: if not any(compare_port(port, current_port) for current_port in current):
raise NeedsRecreate() raise NeedsRecreate()

View File

@ -15,6 +15,7 @@ from authentik.providers.saml.processors.request_parser import AuthNRequestParse
from authentik.sources.saml.exceptions import MismatchedRequestID from authentik.sources.saml.exceptions import MismatchedRequestID
from authentik.sources.saml.models import SAMLSource from authentik.sources.saml.models import SAMLSource
from authentik.sources.saml.processors.constants import ( from authentik.sources.saml.processors.constants import (
SAML_BINDING_REDIRECT,
SAML_NAME_ID_FORMAT_EMAIL, SAML_NAME_ID_FORMAT_EMAIL,
SAML_NAME_ID_FORMAT_UNSPECIFIED, SAML_NAME_ID_FORMAT_UNSPECIFIED,
) )
@ -98,6 +99,9 @@ class TestAuthNRequest(TestCase):
# First create an AuthNRequest # First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state") 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() request = request_proc.build_auth_n()
# Now we check the ID and signature # Now we check the ID and signature
parsed_request = AuthNRequestParser(self.provider).parse( parsed_request = AuthNRequestParser(self.provider).parse(

View File

@ -3,6 +3,7 @@ from ldap3.core.exceptions import LDAPException
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus 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.lib.utils.reflection import class_to_path, path_to_class
from authentik.root.celery import CELERY_APP from authentik.root.celery import CELERY_APP
from authentik.sources.ldap.models import LDAPSource 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: except LDAPException as exc:
# No explicit event is created here as .set_status with an error will do that # 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)) self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))

View File

@ -18,6 +18,8 @@ from authentik.sources.saml.processors.constants import (
RSA_SHA256, RSA_SHA256,
RSA_SHA384, RSA_SHA384,
RSA_SHA512, RSA_SHA512,
SAML_BINDING_POST,
SAML_BINDING_REDIRECT,
SAML_NAME_ID_FORMAT_EMAIL, SAML_NAME_ID_FORMAT_EMAIL,
SAML_NAME_ID_FORMAT_PERSISTENT, SAML_NAME_ID_FORMAT_PERSISTENT,
SAML_NAME_ID_FORMAT_TRANSIENT, SAML_NAME_ID_FORMAT_TRANSIENT,
@ -37,6 +39,15 @@ class SAMLBindingTypes(models.TextChoices):
POST = "POST", _("POST Binding") POST = "POST", _("POST Binding")
POST_AUTO = "POST_AUTO", _("POST Binding with auto-confirmation") 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): class SAMLNameIDPolicy(models.TextChoices):
"""SAML NameID Policies""" """SAML NameID Policies"""

View File

@ -10,7 +10,7 @@ from lxml.etree import Element # nosec
from authentik.providers.saml.utils import get_random_id 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.encoding import deflate_and_base64_encode
from authentik.providers.saml.utils.time import get_time_string 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 ( from authentik.sources.saml.processors.constants import (
DIGEST_ALGORITHM_TRANSLATION_MAP, DIGEST_ALGORITHM_TRANSLATION_MAP,
NS_MAP, NS_MAP,
@ -62,7 +62,7 @@ class RequestProcessor:
auth_n_request.attrib["Destination"] = self.source.sso_url auth_n_request.attrib["Destination"] = self.source.sso_url
auth_n_request.attrib["ID"] = self.request_id auth_n_request.attrib["ID"] = self.request_id
auth_n_request.attrib["IssueInstant"] = self.issue_instant auth_n_request.attrib["IssueInstant"] = self.issue_instant
auth_n_request.attrib["ProtocolBinding"] = self.source.binding_type auth_n_request.attrib["ProtocolBinding"] = SAMLBindingTypes(self.source.binding_type).uri
auth_n_request.attrib["Version"] = "2.0" auth_n_request.attrib["Version"] = "2.0"
# Create issuer object # Create issuer object
auth_n_request.append(self.get_issuer()) auth_n_request.append(self.get_issuer())

View File

@ -196,7 +196,10 @@ class AuthenticatorValidateStageView(ChallengeStageView):
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def get_challenge(self) -> AuthenticatorValidationChallenge: 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( return AuthenticatorValidationChallenge(
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,

View File

@ -17,7 +17,7 @@ services:
image: redis:alpine image: redis:alpine
restart: unless-stopped restart: unless-stopped
server: 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 restart: unless-stopped
command: server command: server
environment: environment:
@ -38,7 +38,7 @@ services:
- "0.0.0.0:${AUTHENTIK_PORT_HTTP:-9000}:9000" - "0.0.0.0:${AUTHENTIK_PORT_HTTP:-9000}:9000"
- "0.0.0.0:${AUTHENTIK_PORT_HTTPS:-9443}:9443" - "0.0.0.0:${AUTHENTIK_PORT_HTTPS:-9443}:9443"
worker: 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 restart: unless-stopped
command: worker command: worker
environment: environment:

View File

@ -25,4 +25,4 @@ func OutpostUserAgent() string {
return fmt.Sprintf("authentik-outpost@%s", FullVersion()) return fmt.Sprintf("authentik-outpost@%s", FullVersion())
} }
const VERSION = "2022.1.4" const VERSION = "2022.1.5"

View File

@ -6,14 +6,14 @@ type ProxyClaims struct {
} }
type Claims struct { type Claims struct {
Sub string `json:"sub"` Sub string `json:"sub"`
Exp int `json:"exp"` Exp int `json:"exp"`
Email string `json:"email"` Email string `json:"email"`
Verified bool `json:"email_verified"` Verified bool `json:"email_verified"`
Proxy ProxyClaims `json:"ak_proxy"` Proxy *ProxyClaims `json:"ak_proxy"`
Name string `json:"name"` Name string `json:"name"`
PreferredUsername string `json:"preferred_username"` PreferredUsername string `json:"preferred_username"`
Groups []string `json:"groups"` Groups []string `json:"groups"`
RawToken string RawToken string
} }

View File

@ -70,7 +70,7 @@ func TestForwardHandleNginx_Single_Claims(t *testing.T) {
s, _ := a.sessions.Get(req, constants.SeesionName) s, _ := a.sessions.Get(req, constants.SeesionName)
s.Values[constants.SessionClaims] = Claims{ s.Values[constants.SessionClaims] = Claims{
Sub: "foo", Sub: "foo",
Proxy: ProxyClaims{ Proxy: &ProxyClaims{
UserAttributes: map[string]interface{}{ UserAttributes: map[string]interface{}{
"username": "foo", "username": "foo",
"password": "bar", "password": "bar",

View File

@ -64,7 +64,7 @@ func TestForwardHandleTraefik_Single_Claims(t *testing.T) {
s, _ := a.sessions.Get(req, constants.SeesionName) s, _ := a.sessions.Get(req, constants.SeesionName)
s.Values[constants.SessionClaims] = Claims{ s.Values[constants.SessionClaims] = Claims{
Sub: "foo", Sub: "foo",
Proxy: ProxyClaims{ Proxy: &ProxyClaims{
UserAttributes: map[string]interface{}{ UserAttributes: map[string]interface{}{
"username": "foo", "username": "foo",
"password": "bar", "password": "bar",

View File

@ -74,17 +74,20 @@ func (a *Application) configureProxy() error {
func (a *Application) proxyModifyRequest(ou *url.URL) func(req *http.Request) { func (a *Application) proxyModifyRequest(ou *url.URL) func(req *http.Request) {
return func(r *http.Request) { return func(r *http.Request) {
claims, _ := a.getClaims(r) claims, _ := a.getClaims(r)
if claims.Proxy.BackendOverride != "" { r.URL.Scheme = ou.Scheme
r.URL.Host = ou.Host
r.Host = ou.Host
if claims != nil && claims.Proxy != nil && claims.Proxy.BackendOverride != "" {
u, err := url.Parse(claims.Proxy.BackendOverride) u, err := url.Parse(claims.Proxy.BackendOverride)
if err != nil { if err != nil {
a.log.WithField("backend_override", claims.Proxy.BackendOverride).WithError(err).Warning("failed parse user backend override") a.log.WithField("backend_override", claims.Proxy.BackendOverride).WithError(err).Warning("failed parse user backend override")
} 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
} else {
r.URL.Scheme = ou.Scheme
r.URL.Host = ou.Host
} }
a.log.WithField("upstream_url", r.URL.String()).Trace("final upstream url")
} }
} }

View File

@ -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)
}

View File

@ -102,7 +102,11 @@ func (ps *ProxyServer) GetCertificate(serverName string) *tls.Certificate {
} }
func (ps *ProxyServer) getCertificates(info *tls.ClientHelloInfo) (*tls.Certificate, error) { 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 { if appCert == nil {
return &ps.defaultCert, nil return &ps.defaultCert, nil
} }

View File

@ -1,6 +1,7 @@
package web package web
import ( import (
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
@ -24,6 +25,7 @@ func (ws *WebServer) configureProxy() {
if req.TLS != nil { if req.TLS != nil {
req.Header.Set("X-Forwarded-Proto", "https") 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 := &httputil.ReverseProxy{Director: director}
rp.ErrorHandler = ws.proxyErrorHandler rp.ErrorHandler = ws.proxyErrorHandler
@ -65,9 +67,20 @@ func (ws *WebServer) configureProxy() {
} }
func (ws *WebServer) proxyErrorHandler(rw http.ResponseWriter, req *http.Request, err error) { 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) 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 { if err != nil {
ws.log.WithError(err).Warning("failed to write error message") ws.log.WithError(err).Warning("failed to write error message")
} }
@ -75,5 +88,6 @@ func (ws *WebServer) proxyErrorHandler(rw http.ResponseWriter, req *http.Request
func (ws *WebServer) proxyModifyResponse(r *http.Response) error { func (ws *WebServer) proxyModifyResponse(r *http.Response) error {
r.Header.Set("X-Powered-By", "authentik") r.Header.Set("X-Powered-By", "authentik")
r.Header.Del("Server")
return nil return nil
} }

View File

@ -16,6 +16,9 @@ func (ws *WebServer) GetCertificate() func(ch *tls.ClientHelloInfo) (*tls.Certif
ws.log.WithError(err).Error("failed to generate default cert") ws.log.WithError(err).Error("failed to generate default cert")
} }
return func(ch *tls.ClientHelloInfo) (*tls.Certificate, error) { return func(ch *tls.ClientHelloInfo) (*tls.Certificate, error) {
if ch.ServerName == "" {
return &cert, nil
}
if ws.ProxyServer != nil { if ws.ProxyServer != nil {
appCert := ws.ProxyServer.GetCertificate(ch.ServerName) appCert := ws.ProxyServer.GetCertificate(ch.ServerName)
if appCert != nil { if appCert != nil {

6
poetry.lock generated
View File

@ -1898,7 +1898,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]] [[package]]
name = "uvicorn" name = "uvicorn"
version = "0.17.1" version = "0.17.3"
description = "The lightning-fast ASGI server." description = "The lightning-fast ASGI server."
category = "main" category = "main"
optional = false optional = false
@ -3406,8 +3406,8 @@ urllib3 = [
{file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
] ]
uvicorn = [ uvicorn = [
{file = "uvicorn-0.17.1-py3-none-any.whl", hash = "sha256:8b16d9ecb76500f7804184f182835fe8a2b54716d3b0b6bb2da0b2b192f62c73"}, {file = "uvicorn-0.17.3-py3-none-any.whl", hash = "sha256:3ab1bf48aa512692db93a91c514576a0739a9d3522827e1656a172ee87118fa5"},
{file = "uvicorn-0.17.1.tar.gz", hash = "sha256:dffbacb8cc25d924d68d231d2c478c4fe6727c36537d8de21e5de591b37afc41"}, {file = "uvicorn-0.17.3.tar.gz", hash = "sha256:3cebddac78c7dd6bfce2f8f838c2c9b0cfcf3dddf417315ba62378ebe7e8fae2"},
] ]
uvloop = [ uvloop = [
{file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"}, {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"},

View File

@ -92,7 +92,7 @@ addopts = "-p no:celery --junitxml=unittest.xml"
[tool.poetry] [tool.poetry]
name = "authentik" name = "authentik"
version = "2022.1.4" version = "2022.1.5"
description = "" description = ""
authors = ["Jens Langhammer <jens.langhammer@beryju.org>"] authors = ["Jens Langhammer <jens.langhammer@beryju.org>"]

View File

@ -1,7 +1,7 @@
openapi: 3.0.3 openapi: 3.0.3
info: info:
title: authentik title: authentik
version: 2022.1.4 version: 2022.1.5
description: Making authentication simple. description: Making authentication simple.
contact: contact:
email: hello@beryju.org email: hello@beryju.org

View File

@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
export const ERROR_CLASS = "pf-m-danger"; export const ERROR_CLASS = "pf-m-danger";
export const PROGRESS_CLASS = "pf-m-in-progress"; export const PROGRESS_CLASS = "pf-m-in-progress";
export const CURRENT_CLASS = "pf-m-current"; 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 TITLE_DEFAULT = "authentik";
export const ROUTE_SEPARATOR = ";"; export const ROUTE_SEPARATOR = ";";

View File

@ -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" class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
> >
<ak-aggregate-card <ak-aggregate-card
icon="pf-icon pf-icon-server" icon="pf-icon pf-icon-process-automation"
header=${t`Flows`} header=${t`Flows`}
headerLink="#/flow/flows" headerLink="#/flow/flows"
> >
@ -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" class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
> >
<ak-aggregate-card <ak-aggregate-card
icon="fa fa-sync-alt" icon="pf-icon pf-icon-zone"
header=${t`Outpost status`} header=${t`Outpost status`}
headerLink="#/outpost/outposts" headerLink="#/outpost/outposts"
> >
@ -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" class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
> >
<ak-aggregate-card <ak-aggregate-card
icon="fa fa-sync-alt" icon="pf-icon pf-icon-user"
header=${t`Users`} header=${t`Users`}
headerLink="#/identity/users" headerLink="#/identity/users"
> >
@ -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" class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl pf-m-2-col-on-2xl graph-container"
> >
<ak-aggregate-card <ak-aggregate-card
icon="fa fa-sync-alt" icon="pf-icon pf-icon-users"
header=${t`Groups`} header=${t`Groups`}
headerLink="#/identity/groups" headerLink="#/identity/groups"
> >

View File

@ -24,6 +24,9 @@ The following aspects can be configured:
- *Name*: This is the name shown for the application card - *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 - *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 - *Icon (URL)*: Optionally configure an Icon for the application
- *Publisher*: Text shown below the application - *Publisher*: Text shown below the application
- *Description*: Subtext shown on the application card below the publisher - *Description*: Subtext shown on the application card below the publisher

View File

@ -102,6 +102,28 @@ This release mostly removes legacy fields and features that have been deprecated
- web/flows: fix width on flow container - web/flows: fix width on flow container
- web/user: include locale code in locale selection - 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 ## Upgrading
This release does not introduce any new requirements. This release does not introduce any new requirements.