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