Compare commits
	
		
			21 Commits
		
	
	
		
			version/20
			...
			version/20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| eaad564e23 | |||
| 511a94975b | |||
| 015810a2fd | |||
| e70e6b84c2 | |||
| d0b9c9a26f | |||
| 3e403fa348 | |||
| 48f4a971ef | |||
| 6314be14ad | |||
| 1a072c6c39 | |||
| ef2eed0bdf | |||
| 91227b1e96 | |||
| 67d68629da | |||
| e875db8f66 | |||
| 055a76393d | |||
| 0754821628 | |||
| fca88d9896 | |||
| dfe0404c51 | |||
| fa61696b46 | |||
| e5773738f4 | |||
| cac8539d79 | |||
| cf600f6f26 | 
| @ -1,5 +1,5 @@ | ||||
| [bumpversion] | ||||
| current_version = 2022.1.4 | ||||
| current_version = 2022.1.5 | ||||
| tag = True | ||||
| commit = True | ||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*) | ||||
|  | ||||
							
								
								
									
										14
									
								
								.github/workflows/release-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/release-publish.yml
									
									
									
									
										vendored
									
									
								
							| @ -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' | ||||
|  | ||||
							
								
								
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							| @ -12,7 +12,8 @@ | ||||
|         "totp", | ||||
|         "webauthn", | ||||
|         "traefik", | ||||
|         "passwordless" | ||||
|         "passwordless", | ||||
|         "kubernetes" | ||||
|     ], | ||||
|     "python.linting.pylintEnabled": true, | ||||
|     "todo-tree.tree.showCountsInTree": true, | ||||
|  | ||||
| @ -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" | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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": "", | ||||
|  | ||||
| @ -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: | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| 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 +17,31 @@ def get_namespace() -> str: | ||||
|     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""" | ||||
|     if len(current) != len(reference): | ||||
|         raise NeedsRecreate() | ||||
|     for port in reference: | ||||
|         if port not in current: | ||||
|         if not any(compare_port(port, current_port) for current_port in current): | ||||
|             raise NeedsRecreate() | ||||
|  | ||||
| @ -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( | ||||
|  | ||||
| @ -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)) | ||||
|  | ||||
| @ -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""" | ||||
|  | ||||
| @ -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 | ||||
|         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()) | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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: | ||||
|  | ||||
| @ -25,4 +25,4 @@ func OutpostUserAgent() string { | ||||
| 	return fmt.Sprintf("authentik-outpost@%s", FullVersion()) | ||||
| } | ||||
|  | ||||
| const VERSION = "2022.1.4" | ||||
| const VERSION = "2022.1.5" | ||||
|  | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
| @ -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", | ||||
|  | ||||
| @ -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", | ||||
|  | ||||
| @ -74,17 +74,20 @@ 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 != "" { | ||||
| 		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) | ||||
| 			if err != nil { | ||||
| 				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") | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
							
								
								
									
										81
									
								
								internal/outpost/proxyv2/application/mode_proxy_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								internal/outpost/proxyv2/application/mode_proxy_test.go
									
									
									
									
									
										Normal 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) | ||||
| } | ||||
| @ -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 | ||||
| 	} | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| package web | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/http/httputil" | ||||
| @ -24,6 +25,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 | ||||
| @ -65,9 +67,20 @@ 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) | ||||
| 	_, 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") | ||||
| 	} | ||||
| @ -75,5 +88,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 | ||||
| } | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
							
								
								
									
										6
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							| @ -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"}, | ||||
|  | ||||
| @ -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 <jens.langhammer@beryju.org>"] | ||||
|  | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 = ";"; | ||||
|  | ||||
|  | ||||
| @ -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" | ||||
|                     > | ||||
|                         <ak-aggregate-card | ||||
|                             icon="pf-icon pf-icon-server" | ||||
|                             icon="pf-icon pf-icon-process-automation" | ||||
|                             header=${t`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" | ||||
|                     > | ||||
|                         <ak-aggregate-card | ||||
|                             icon="fa fa-sync-alt" | ||||
|                             icon="pf-icon pf-icon-zone" | ||||
|                             header=${t`Outpost status`} | ||||
|                             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" | ||||
|                     > | ||||
|                         <ak-aggregate-card | ||||
|                             icon="fa fa-sync-alt" | ||||
|                             icon="pf-icon pf-icon-user" | ||||
|                             header=${t`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" | ||||
|                     > | ||||
|                         <ak-aggregate-card | ||||
|                             icon="fa fa-sync-alt" | ||||
|                             icon="pf-icon pf-icon-users" | ||||
|                             header=${t`Groups`} | ||||
|                             headerLink="#/identity/groups" | ||||
|                         > | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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. | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	