Merge branch 'master' into version-2021.12

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	Dockerfile
This commit is contained in:
Jens Langhammer 2021-12-13 10:54:21 +01:00
commit 5914bbf173
60 changed files with 3819 additions and 4530 deletions

View File

@ -114,16 +114,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Setup Node.js environment - name: Get static files from docker image
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Build web api client and web ui
run: | run: |
export NODE_ENV=production docker pull ghcr.io/goauthentik/server:latest
cd web container=$(docker container create ghcr.io/goauthentik/server:latest)
npm i docker cp ${container}:web/ .
npm run build
- name: Create a Sentry.io release - name: Create a Sentry.io release
uses: getsentry/action-release@v1 uses: getsentry/action-release@v1
if: ${{ github.event_name == 'release' }} if: ${{ github.event_name == 'release' }}

View File

@ -28,7 +28,7 @@ ENV NODE_ENV=production
RUN cd /work/web && npm i && npm run build RUN cd /work/web && npm i && npm run build
# Stage 4: Build go proxy # Stage 4: Build go proxy
FROM docker.io/golang:1.17.4-bullseye AS builder FROM docker.io/golang:1.17.5-bullseye AS builder
WORKDIR /work WORKDIR /work
@ -64,8 +64,8 @@ RUN apt-get update && \
apt-get clean && \ apt-get clean && \
rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/ && \ rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/ && \
adduser --system --no-create-home --uid 1000 --group --home /authentik authentik && \ adduser --system --no-create-home --uid 1000 --group --home /authentik authentik && \
mkdir /backups /certs && \ mkdir -p /backups /certs /media && \
chown authentik:authentik /backups /certs chown authentik:authentik /backups /certs /media
COPY ./authentik/ /authentik COPY ./authentik/ /authentik
COPY ./pyproject.toml / COPY ./pyproject.toml /

View File

@ -32,14 +32,15 @@ geoip2 = "*"
gunicorn = "*" gunicorn = "*"
kubernetes = "==v19.15.0" kubernetes = "==v19.15.0"
ldap3 = "*" ldap3 = "*"
lxml = "*" # 4.6.5 and later remove `lxml-version.h` which is required by xmlsec
lxml = "==4.6.4"
packaging = "*" packaging = "*"
psycopg2-binary = "*" psycopg2-binary = "*"
pycryptodome = "*" pycryptodome = "*"
pyjwt = "*" pyjwt = "*"
pyyaml = "*" pyyaml = "*"
requests-oauthlib = "*" requests-oauthlib = "*"
sentry-sdk = "*" sentry-sdk = { git = 'https://github.com/BeryJu/sentry-python.git', ref = 'bba7d80c05bc6845aa333ebbd87e3b76747ed355' }
service_identity = "*" service_identity = "*"
structlog = "*" structlog = "*"
swagger-spec-validator = "*" swagger-spec-validator = "*"
@ -50,6 +51,7 @@ uvicorn = {extras = ["standard"],version = "*"}
webauthn = "*" webauthn = "*"
xmlsec = "*" xmlsec = "*"
flower = "*" flower = "*"
wsproto = "*"
[dev-packages] [dev-packages]
bandit = "*" bandit = "*"

94
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "6f7f70af39ff6e2d95dd62e225a52c5846957ebac5218aa2b49417fb7f9d8cf1" "sha256": "f826456d2aa4f1379f61f3c1f76ddd2db7af3395a272d6b56fd5402c4aa3ce2f"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": {}, "requires": {},
@ -109,11 +109,11 @@
}, },
"amqp": { "amqp": {
"hashes": [ "hashes": [
"sha256:03e16e94f2b34c31f8bf1206d8ddd3ccaa4c315f7f6a1879b7b1210d229568c2", "sha256:4d9cb6b5d69183ba279e97382ff68a071864c25b561d206dab73499d3ed26d1c",
"sha256:493a2ac6788ce270a2f6a765b017299f60c1998f5a8617908ee9be082f7300fb" "sha256:d757b78fd7d3c6bb60e3ee811b68145583643747ed3ec253329f086aa3a72a5d"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==5.0.6" "version": "==5.0.7"
}, },
"asgiref": { "asgiref": {
"hashes": [ "hashes": [
@ -169,19 +169,19 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:4cdaca9699a266936c04543700a5c6cdf52b563d33703812459cfe42c4c63ace", "sha256:76b3ee0d1dd860c9218bc864cd29f1ee986f6e1e75e8669725dd3c411039379e",
"sha256:c8fbacb7d4e90a17cfe2a68e728c574e5b4a97ab66de417a44c373f7236366a1" "sha256:c39cb6ed376ba1d4689ac8f6759a2b2d8a0b0424dbec0cd3af1558079bcf06e8"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.20.22" "version": "==1.20.23"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:6738e87baa48e4befc447dded787fcadb87a23efb3a5ee6bfe78177bc9630516", "sha256:640b62110aa6d1c25553eceafb5bcd89aedeb84b191598d1f6492ad24374d285",
"sha256:e66e4905dc048d5109df2cc6db55772b9111bfab8907557fa610f9241dca8752" "sha256:7459766c4594f3b8877e8013f93f0dc6c6486acbeb7d9c9ae488396529cc2e84"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==1.23.22" "version": "==1.23.23"
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
@ -191,6 +191,14 @@
"markers": "python_version ~= '3.5'", "markers": "python_version ~= '3.5'",
"version": "==4.2.4" "version": "==4.2.4"
}, },
"cattrs": {
"hashes": [
"sha256:1ef33f089e0a494e8d1b487508356f055c865b1955b125c00c991a4358543c80",
"sha256:8eca49962b1bfc09c24d442aa55688be88efe5c24aeef89d3be135614b95c678"
],
"markers": "python_version >= '3.7' and python_version < '4.0'",
"version": "==1.9.0"
},
"cbor2": { "cbor2": {
"hashes": [ "hashes": [
"sha256:0153635a78e62d70f26f5b3469cb8de822420eda69c996304fb3d0dc1a53d7f3", "sha256:0153635a78e62d70f26f5b3469cb8de822420eda69c996304fb3d0dc1a53d7f3",
@ -317,7 +325,7 @@
"sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667", "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667",
"sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035" "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"
], ],
"markers": "python_version < '4' and python_full_version >= '3.6.2'", "markers": "python_version < '4.0' and python_full_version >= '3.6.2'",
"version": "==0.3.0" "version": "==0.3.0"
}, },
"click-plugins": { "click-plugins": {
@ -1150,34 +1158,6 @@
"index": "pypi", "index": "pypi",
"version": "==3.12.0" "version": "==3.12.0"
}, },
"pydantic": {
"hashes": [
"sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd",
"sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739",
"sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f",
"sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840",
"sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23",
"sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287",
"sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62",
"sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b",
"sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb",
"sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820",
"sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3",
"sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b",
"sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e",
"sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3",
"sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316",
"sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b",
"sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4",
"sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20",
"sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e",
"sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505",
"sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1",
"sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"
],
"markers": "python_full_version >= '3.6.1'",
"version": "==1.8.2"
},
"pyjwt": { "pyjwt": {
"hashes": [ "hashes": [
"sha256:b888b4d56f06f6dcd777210c334e69c737be74755d3e5e9ee3fe67dc18a0ee41", "sha256:b888b4d56f06f6dcd777210c334e69c737be74755d3e5e9ee3fe67dc18a0ee41",
@ -1191,6 +1171,7 @@
"sha256:5e2d8c5e46d0d865ae933bef5230090bdaf5506281e9eec60fa250ee80600cb3", "sha256:5e2d8c5e46d0d865ae933bef5230090bdaf5506281e9eec60fa250ee80600cb3",
"sha256:8935bd4920ab9abfebb07c41a4f58296407ed77f04bd1a92914044b848ba1ed6" "sha256:8935bd4920ab9abfebb07c41a4f58296407ed77f04bd1a92914044b848ba1ed6"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==21.0.0" "version": "==21.0.0"
}, },
"pyparsing": { "pyparsing": {
@ -1331,12 +1312,12 @@
"version": "==0.5.0" "version": "==0.5.0"
}, },
"sentry-sdk": { "sentry-sdk": {
"git": "https://github.com/BeryJu/sentry-python.git",
"hashes": [ "hashes": [
"sha256:0db297ab32e095705c20f742c3a5dac62fe15c4318681884053d0898e5abb2f6", "sha256:0db297ab32e095705c20f742c3a5dac62fe15c4318681884053d0898e5abb2f6",
"sha256:789a11a87ca02491896e121efdd64e8fd93327b69e8f2f7d42f03e2569648e88" "sha256:789a11a87ca02491896e121efdd64e8fd93327b69e8f2f7d42f03e2569648e88"
], ],
"index": "pypi", "ref": "bba7d80c05bc6845aa333ebbd87e3b76747ed355"
"version": "==1.5.0"
}, },
"service-identity": { "service-identity": {
"hashes": [ "hashes": [
@ -1348,11 +1329,11 @@
}, },
"setuptools": { "setuptools": {
"hashes": [ "hashes": [
"sha256:6d10741ff20b89cd8c6a536ee9dc90d3002dec0226c78fb98605bfb9ef8a7adf", "sha256:22c7348c6d2976a52632c67f7ab0cdf40147db7789f9aed18734643fe9cf3373",
"sha256:d144f85102f999444d06f9c0e8c737fd0194f10f2f7e5fdb77573f6e2fa4fad0" "sha256:4ce92f1e1f8f01233ee9952c04f6b81d1e02939d6e1b488428154974a4d0783e"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==59.5.0" "version": "==59.6.0"
}, },
"six": { "six": {
"hashes": [ "hashes": [
@ -1540,11 +1521,11 @@
}, },
"webauthn": { "webauthn": {
"hashes": [ "hashes": [
"sha256:1a24696c67f8f3eb09267b10b3bfa4aade86bf08e2c41333d8c00f199c528b97", "sha256:681b71d803c085d0911e29f2e2bbb0e49feda1ecdaa3982bae3d63b3377efdda",
"sha256:523e6e488a4d5e8c4fe183dde154a7cdfc9dad62e6e30e97c1d52e4a4ae6390e" "sha256:ecae0f99a6dc1a6d53e72f4be323cf58b418c02fe66dffae752eedc1d2ee6a70"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.1.0" "version": "==1.2.0"
}, },
"websocket-client": { "websocket-client": {
"hashes": [ "hashes": [
@ -1607,6 +1588,14 @@
], ],
"version": "==10.1" "version": "==10.1"
}, },
"wsproto": {
"hashes": [
"sha256:868776f8456997ad0d9720f7322b746bbe9193751b5b290b7f924659377c8c38",
"sha256:d8345d1808dd599b5ffb352c25a367adb6157e664e140dbecba3f9bc007edb9f"
],
"index": "pypi",
"version": "==1.0.0"
},
"xmlsec": { "xmlsec": {
"hashes": [ "hashes": [
"sha256:135724cdce60e6bbd072fca6f09a21f72e2cecc59eebb4eed7740c316ecabc7b", "sha256:135724cdce60e6bbd072fca6f09a21f72e2cecc59eebb4eed7740c316ecabc7b",
@ -2029,7 +2018,7 @@
"sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7", "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7",
"sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951" "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"
], ],
"markers": "python_version < '4.0' and python_full_version >= '3.6.1'", "markers": "python_version < '4' and python_full_version >= '3.6.1'",
"version": "==5.10.1" "version": "==5.10.1"
}, },
"lazy-object-proxy": { "lazy-object-proxy": {
@ -2165,6 +2154,7 @@
"sha256:5e2d8c5e46d0d865ae933bef5230090bdaf5506281e9eec60fa250ee80600cb3", "sha256:5e2d8c5e46d0d865ae933bef5230090bdaf5506281e9eec60fa250ee80600cb3",
"sha256:8935bd4920ab9abfebb07c41a4f58296407ed77f04bd1a92914044b848ba1ed6" "sha256:8935bd4920ab9abfebb07c41a4f58296407ed77f04bd1a92914044b848ba1ed6"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==21.0.0" "version": "==21.0.0"
}, },
"pyparsing": { "pyparsing": {
@ -2342,11 +2332,11 @@
}, },
"setuptools": { "setuptools": {
"hashes": [ "hashes": [
"sha256:6d10741ff20b89cd8c6a536ee9dc90d3002dec0226c78fb98605bfb9ef8a7adf", "sha256:22c7348c6d2976a52632c67f7ab0cdf40147db7789f9aed18734643fe9cf3373",
"sha256:d144f85102f999444d06f9c0e8c737fd0194f10f2f7e5fdb77573f6e2fa4fad0" "sha256:4ce92f1e1f8f01233ee9952c04f6b81d1e02939d6e1b488428154974a4d0783e"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==59.5.0" "version": "==59.6.0"
}, },
"six": { "six": {
"hashes": [ "hashes": [
@ -2500,7 +2490,7 @@
"sha256:868776f8456997ad0d9720f7322b746bbe9193751b5b290b7f924659377c8c38", "sha256:868776f8456997ad0d9720f7322b746bbe9193751b5b290b7f924659377c8c38",
"sha256:d8345d1808dd599b5ffb352c25a367adb6157e664e140dbecba3f9bc007edb9f" "sha256:d8345d1808dd599b5ffb352c25a367adb6157e664e140dbecba3f9bc007edb9f"
], ],
"markers": "python_full_version >= '3.6.1'", "index": "pypi",
"version": "==1.0.0" "version": "==1.0.0"
}, },
"zipp": { "zipp": {

View File

@ -1,9 +1,11 @@
"""Groups API Viewset""" """Groups API Viewset"""
from json import loads
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django_filters.filters import ModelMultipleChoiceFilter from django_filters.filters import CharFilter, ModelMultipleChoiceFilter
from django_filters.filterset import FilterSet from django_filters.filterset import FilterSet
from rest_framework.fields import CharField, JSONField from rest_framework.fields import CharField, JSONField
from rest_framework.serializers import ListSerializer, ModelSerializer from rest_framework.serializers import ListSerializer, ModelSerializer, ValidationError
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from rest_framework_guardian.filters import ObjectPermissionsFilter from rest_framework_guardian.filters import ObjectPermissionsFilter
@ -62,6 +64,13 @@ class GroupSerializer(ModelSerializer):
class GroupFilter(FilterSet): class GroupFilter(FilterSet):
"""Filter for groups""" """Filter for groups"""
attributes = CharFilter(
field_name="attributes",
lookup_expr="",
label="Attributes",
method="filter_attributes",
)
members_by_username = ModelMultipleChoiceFilter( members_by_username = ModelMultipleChoiceFilter(
field_name="users__username", field_name="users__username",
to_field_name="username", to_field_name="username",
@ -72,10 +81,28 @@ class GroupFilter(FilterSet):
queryset=User.objects.all(), queryset=User.objects.all(),
) )
# pylint: disable=unused-argument
def filter_attributes(self, queryset, name, value):
"""Filter attributes by query args"""
try:
value = loads(value)
except ValueError:
raise ValidationError(detail="filter: failed to parse JSON")
if not isinstance(value, dict):
raise ValidationError(detail="filter: value must be key:value mapping")
qs = {}
for key, _value in value.items():
qs[f"attributes__{key}"] = _value
try:
_ = len(queryset.filter(**qs))
return queryset.filter(**qs)
except ValueError:
return queryset
class Meta: class Meta:
model = Group model = Group
fields = ["name", "is_superuser", "members_by_pk", "members_by_username"] fields = ["name", "is_superuser", "members_by_pk", "attributes", "members_by_username"]
class GroupViewSet(UsedByMixin, ModelViewSet): class GroupViewSet(UsedByMixin, ModelViewSet):

View File

@ -233,7 +233,11 @@ class UsersFilter(FilterSet):
qs = {} qs = {}
for key, _value in value.items(): for key, _value in value.items():
qs[f"attributes__{key}"] = _value qs[f"attributes__{key}"] = _value
return queryset.filter(**qs) try:
_ = len(queryset.filter(**qs))
return queryset.filter(**qs)
except ValueError:
return queryset
class Meta: class Meta:
model = User model = User

View File

@ -285,7 +285,7 @@ class FlowToken(Token):
return loads(b64decode(self._plan.encode())) # nosec return loads(b64decode(self._plan.encode())) # nosec
def __str__(self) -> str: def __str__(self) -> str:
return f"Flow Token {super.__str__()}" return f"Flow Token {super().__str__()}"
class Meta: class Meta:

View File

@ -126,7 +126,7 @@ class FlowPlanner:
) -> FlowPlan: ) -> FlowPlan:
"""Check each of the flows' policies, check policies for each stage with PolicyBinding """Check each of the flows' policies, check policies for each stage with PolicyBinding
and return ordered list""" and return ordered list"""
with Hub.current.start_span(op="flow.planner.plan") as span: with Hub.current.start_span(op="flow.planner.plan", description=self.flow.slug) as span:
span: Span span: Span
span.set_data("flow", self.flow) span.set_data("flow", self.flow)
span.set_data("request", request) span.set_data("request", request)
@ -181,7 +181,8 @@ class FlowPlanner:
"""Build flow plan by checking each stage in their respective """Build flow plan by checking each stage in their respective
order and checking the applied policies""" order and checking the applied policies"""
with Hub.current.start_span( with Hub.current.start_span(
op="flow.planner.build_plan" op="flow.planner.build_plan",
description=self.flow.slug,
) as span, HIST_FLOWS_PLAN_TIME.labels(flow_slug=self.flow.slug).time(): ) as span, HIST_FLOWS_PLAN_TIME.labels(flow_slug=self.flow.slug).time():
span: Span span: Span
span.set_data("flow", self.flow) span.set_data("flow", self.flow)

View File

@ -19,6 +19,8 @@ from drf_spectacular.utils import OpenApiParameter, PolymorphicProxySerializer,
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from rest_framework.views import APIView from rest_framework.views import APIView
from sentry_sdk import capture_exception from sentry_sdk import capture_exception
from sentry_sdk.api import set_tag
from sentry_sdk.hub import Hub
from structlog.stdlib import BoundLogger, get_logger from structlog.stdlib import BoundLogger, get_logger
from authentik.core.models import USER_ATTRIBUTE_DEBUG from authentik.core.models import USER_ATTRIBUTE_DEBUG
@ -126,6 +128,7 @@ class FlowExecutorView(APIView):
super().setup(request, flow_slug=flow_slug) super().setup(request, flow_slug=flow_slug)
self.flow = get_object_or_404(Flow.objects.select_related(), slug=flow_slug) self.flow = get_object_or_404(Flow.objects.select_related(), slug=flow_slug)
self._logger = get_logger().bind(flow_slug=flow_slug) self._logger = get_logger().bind(flow_slug=flow_slug)
set_tag("authentik.flow", self.flow.slug)
def handle_invalid_flow(self, exc: BaseException) -> HttpResponse: def handle_invalid_flow(self, exc: BaseException) -> HttpResponse:
"""When a flow is non-applicable check if user is on the correct domain""" """When a flow is non-applicable check if user is on the correct domain"""
@ -156,74 +159,80 @@ class FlowExecutorView(APIView):
# pylint: disable=unused-argument, too-many-return-statements # pylint: disable=unused-argument, too-many-return-statements
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse: def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
get_params = QueryDict(request.GET.get("query", "")) with Hub.current.start_span(
if QS_KEY_TOKEN in get_params: op="flow.executor.dispatch", description=self.flow.slug
plan = self._check_flow_token(get_params) ) as span:
if plan: span.set_data("authentik Flow", self.flow.slug)
self.request.session[SESSION_KEY_PLAN] = plan get_params = QueryDict(request.GET.get("query", ""))
# Early check if there's an active Plan for the current session if QS_KEY_TOKEN in get_params:
if SESSION_KEY_PLAN in self.request.session: plan = self._check_flow_token(get_params)
self.plan = self.request.session[SESSION_KEY_PLAN] if plan:
if self.plan.flow_pk != self.flow.pk.hex: self.request.session[SESSION_KEY_PLAN] = plan
self._logger.warning( # Early check if there's an active Plan for the current session
"f(exec): Found existing plan for other flow, deleting plan", if SESSION_KEY_PLAN in self.request.session:
) self.plan = self.request.session[SESSION_KEY_PLAN]
# Existing plan is deleted from session and instance if self.plan.flow_pk != self.flow.pk.hex:
self.plan = None self._logger.warning(
self.cancel() "f(exec): Found existing plan for other flow, deleting plan",
self._logger.debug("f(exec): Continuing existing plan") )
# Existing plan is deleted from session and instance
self.plan = None
self.cancel()
self._logger.debug("f(exec): Continuing existing plan")
# Don't check session again as we've either already loaded the plan or we need to plan # Don't check session again as we've either already loaded the plan or we need to plan
if not self.plan: if not self.plan:
request.session[SESSION_KEY_HISTORY] = [] request.session[SESSION_KEY_HISTORY] = []
self._logger.debug("f(exec): No active Plan found, initiating planner") self._logger.debug("f(exec): No active Plan found, initiating planner")
try:
self.plan = self._initiate_plan()
except FlowNonApplicableException as exc:
self._logger.warning("f(exec): Flow not applicable to current user", exc=exc)
return to_stage_response(self.request, self.handle_invalid_flow(exc))
except EmptyFlowException as exc:
self._logger.warning("f(exec): Flow is empty", exc=exc)
# To match behaviour with loading an empty flow plan from cache,
# we don't show an error message here, but rather call _flow_done()
return self._flow_done()
# Initial flow request, check if we have an upstream query string passed in
request.session[SESSION_KEY_GET] = get_params
# We don't save the Plan after getting the next stage
# as it hasn't been successfully passed yet
try: try:
self.plan = self._initiate_plan() # This is the first time we actually access any attribute on the selected plan
except FlowNonApplicableException as exc: # if the cached plan is from an older version, it might have different attributes
self._logger.warning("f(exec): Flow not applicable to current user", exc=exc) # in which case we just delete the plan and invalidate everything
return to_stage_response(self.request, self.handle_invalid_flow(exc)) next_binding = self.plan.next(self.request)
except EmptyFlowException as exc: except Exception as exc: # pylint: disable=broad-except
self._logger.warning("f(exec): Flow is empty", exc=exc) self._logger.warning(
# To match behaviour with loading an empty flow plan from cache, "f(exec): found incompatible flow plan, invalidating run", exc=exc
# we don't show an error message here, but rather call _flow_done() )
keys = cache.keys("flow_*")
cache.delete_many(keys)
return self.stage_invalid()
if not next_binding:
self._logger.debug("f(exec): no more stages, flow is done.")
return self._flow_done() return self._flow_done()
# Initial flow request, check if we have an upstream query string passed in self.current_binding = next_binding
request.session[SESSION_KEY_GET] = get_params self.current_stage = next_binding.stage
# We don't save the Plan after getting the next stage self._logger.debug(
# as it hasn't been successfully passed yet "f(exec): Current stage",
try: current_stage=self.current_stage,
# This is the first time we actually access any attribute on the selected plan flow_slug=self.flow.slug,
# if the cached plan is from an older version, it might have different attributes )
# in which case we just delete the plan and invalidate everything try:
next_binding = self.plan.next(self.request) stage_cls = self.current_stage.type
except Exception as exc: # pylint: disable=broad-except except NotImplementedError as exc:
self._logger.warning("f(exec): found incompatible flow plan, invalidating run", exc=exc) self._logger.debug("Error getting stage type", exc=exc)
keys = cache.keys("flow_*") return self.stage_invalid()
cache.delete_many(keys) self.current_stage_view = stage_cls(self)
return self.stage_invalid() self.current_stage_view.args = self.args
if not next_binding: self.current_stage_view.kwargs = self.kwargs
self._logger.debug("f(exec): no more stages, flow is done.") self.current_stage_view.request = request
return self._flow_done() try:
self.current_binding = next_binding return super().dispatch(request)
self.current_stage = next_binding.stage except InvalidStageError as exc:
self._logger.debug( return self.stage_invalid(str(exc))
"f(exec): Current stage",
current_stage=self.current_stage,
flow_slug=self.flow.slug,
)
try:
stage_cls = self.current_stage.type
except NotImplementedError as exc:
self._logger.debug("Error getting stage type", exc=exc)
return self.stage_invalid()
self.current_stage_view = stage_cls(self)
self.current_stage_view.args = self.args
self.current_stage_view.kwargs = self.kwargs
self.current_stage_view.request = request
try:
return super().dispatch(request)
except InvalidStageError as exc:
return self.stage_invalid(str(exc))
def handle_exception(self, exc: Exception) -> HttpResponse: def handle_exception(self, exc: Exception) -> HttpResponse:
"""Handle exception in stage execution""" """Handle exception in stage execution"""
@ -265,8 +274,15 @@ class FlowExecutorView(APIView):
stage=self.current_stage, stage=self.current_stage,
) )
try: try:
stage_response = self.current_stage_view.get(request, *args, **kwargs) with Hub.current.start_span(
return to_stage_response(request, stage_response) op="flow.executor.stage",
description=class_to_path(self.current_stage_view.__class__),
) as span:
span.set_data("Method", "GET")
span.set_data("authentik Stage", self.current_stage_view)
span.set_data("authentik Flow", self.flow.slug)
stage_response = self.current_stage_view.get(request, *args, **kwargs)
return to_stage_response(request, stage_response)
except Exception as exc: # pylint: disable=broad-except except Exception as exc: # pylint: disable=broad-except
return self.handle_exception(exc) return self.handle_exception(exc)
@ -302,8 +318,15 @@ class FlowExecutorView(APIView):
stage=self.current_stage, stage=self.current_stage,
) )
try: try:
stage_response = self.current_stage_view.post(request, *args, **kwargs) with Hub.current.start_span(
return to_stage_response(request, stage_response) op="flow.executor.stage",
description=class_to_path(self.current_stage_view.__class__),
) as span:
span.set_data("Method", "POST")
span.set_data("authentik Stage", self.current_stage_view)
span.set_data("authentik Flow", self.flow.slug)
stage_response = self.current_stage_view.post(request, *args, **kwargs)
return to_stage_response(request, stage_response)
except Exception as exc: # pylint: disable=broad-except except Exception as exc: # pylint: disable=broad-except
return self.handle_exception(exc) return self.handle_exception(exc)

View File

@ -87,9 +87,7 @@ class FlowInspectorView(APIView):
@extend_schema( @extend_schema(
responses={ responses={
200: FlowInspectionSerializer(), 200: FlowInspectionSerializer(),
400: OpenApiResponse( 400: OpenApiResponse(description="No flow plan in session."),
description="No flow plan in session."
), # This error can be raised by the email stage
}, },
request=OpenApiTypes.NONE, request=OpenApiTypes.NONE,
operation_id="flows_inspector_get", operation_id="flows_inspector_get",
@ -106,7 +104,10 @@ class FlowInspectorView(APIView):
if SESSION_KEY_PLAN in request.session: if SESSION_KEY_PLAN in request.session:
current_plan: FlowPlan = request.session[SESSION_KEY_PLAN] current_plan: FlowPlan = request.session[SESSION_KEY_PLAN]
else: else:
current_plan = request.session[SESSION_KEY_HISTORY][-1] try:
current_plan = request.session[SESSION_KEY_HISTORY][-1]
except KeyError:
return Response(status=400)
is_completed = True is_completed = True
current_serializer = FlowInspectorPlanSerializer( current_serializer = FlowInspectorPlanSerializer(
instance=current_plan, context={"request": request} instance=current_plan, context={"request": request}

View File

@ -20,7 +20,6 @@ web:
listen: 0.0.0.0:9000 listen: 0.0.0.0:9000
listen_tls: 0.0.0.0:9443 listen_tls: 0.0.0.0:9443
listen_metrics: 0.0.0.0:9300 listen_metrics: 0.0.0.0:9300
load_local_files: false
outpost_port_offset: 0 outpost_port_offset: 0
redis: redis:

View File

@ -4,6 +4,7 @@ from typing import Any, Optional
from django.http import HttpRequest from django.http import HttpRequest
from requests.sessions import Session from requests.sessions import Session
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik import ENV_GIT_HASH_KEY, __version__ from authentik import ENV_GIT_HASH_KEY, __version__
@ -52,6 +53,12 @@ def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]:
fake_ip=fake_ip, fake_ip=fake_ip,
) )
return None return None
# Update sentry scope to include correct IP
user = Hub.current.scope._user
if not user:
user = {}
user["ip_address"] = fake_ip
Hub.current.scope.set_user(user)
return fake_ip return fake_ip

View File

@ -9,7 +9,7 @@ from dacite import from_dict
from dacite.data import Data from dacite.data import Data
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
from prometheus_client import Gauge from prometheus_client import Gauge
from structlog.stdlib import get_logger from structlog.stdlib import BoundLogger, get_logger
from authentik.core.channels import AuthJsonConsumer from authentik.core.channels import AuthJsonConsumer
from authentik.outposts.models import OUTPOST_HELLO_INTERVAL, Outpost, OutpostState from authentik.outposts.models import OUTPOST_HELLO_INTERVAL, Outpost, OutpostState
@ -23,8 +23,6 @@ GAUGE_OUTPOSTS_LAST_UPDATE = Gauge(
["outpost", "uid", "version"], ["outpost", "uid", "version"],
) )
LOGGER = get_logger()
class WebsocketMessageInstruction(IntEnum): class WebsocketMessageInstruction(IntEnum):
"""Commands which can be triggered over Websocket""" """Commands which can be triggered over Websocket"""
@ -51,6 +49,7 @@ class OutpostConsumer(AuthJsonConsumer):
"""Handler for Outposts that connect over websockets for health checks and live updates""" """Handler for Outposts that connect over websockets for health checks and live updates"""
outpost: Optional[Outpost] = None outpost: Optional[Outpost] = None
logger: BoundLogger
last_uid: Optional[str] = None last_uid: Optional[str] = None
@ -59,11 +58,20 @@ class OutpostConsumer(AuthJsonConsumer):
def connect(self): def connect(self):
super().connect() super().connect()
uuid = self.scope["url_route"]["kwargs"]["pk"] uuid = self.scope["url_route"]["kwargs"]["pk"]
outpost = get_objects_for_user(self.user, "authentik_outposts.view_outpost").filter(pk=uuid) outpost = (
if not outpost.exists(): get_objects_for_user(self.user, "authentik_outposts.view_outpost")
.filter(pk=uuid)
.first()
)
if not outpost:
raise DenyConnection() raise DenyConnection()
self.accept() self.logger = get_logger().bind(outpost=outpost)
self.outpost = outpost.first() try:
self.accept()
except RuntimeError as exc:
self.logger.warning("runtime error during accept", exc=exc)
raise DenyConnection()
self.outpost = outpost
self.last_uid = self.channel_name self.last_uid = self.channel_name
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -78,9 +86,8 @@ class OutpostConsumer(AuthJsonConsumer):
uid=self.last_uid, uid=self.last_uid,
expected=self.outpost.config.kubernetes_replicas, expected=self.outpost.config.kubernetes_replicas,
).dec() ).dec()
LOGGER.debug( self.logger.debug(
"removed outpost instance from cache", "removed outpost instance from cache",
outpost=self.outpost,
instance_uuid=self.last_uid, instance_uuid=self.last_uid,
) )
@ -103,9 +110,8 @@ class OutpostConsumer(AuthJsonConsumer):
uid=self.last_uid, uid=self.last_uid,
expected=self.outpost.config.kubernetes_replicas, expected=self.outpost.config.kubernetes_replicas,
).inc() ).inc()
LOGGER.debug( self.logger.debug(
"added outpost instance to cache", "added outpost instance to cache",
outpost=self.outpost,
instance_uuid=self.last_uid, instance_uuid=self.last_uid,
) )
self.first_msg = True self.first_msg = True

View File

@ -24,6 +24,8 @@ class DockerController(BaseController):
def __init__(self, outpost: Outpost, connection: DockerServiceConnection) -> None: def __init__(self, outpost: Outpost, connection: DockerServiceConnection) -> None:
super().__init__(outpost, connection) super().__init__(outpost, connection)
if outpost.managed == MANAGED_OUTPOST:
return
try: try:
self.client = connection.client() self.client = connection.client()
except ServiceConnectionInvalid as exc: except ServiceConnectionInvalid as exc:
@ -225,12 +227,14 @@ class DockerController(BaseController):
raise ControllerException(str(exc)) from exc raise ControllerException(str(exc)) from exc
def down(self): def down(self):
if self.outpost.managed != MANAGED_OUTPOST: if self.outpost.managed == MANAGED_OUTPOST:
return return
try: try:
container, _ = self._get_container() container, _ = self._get_container()
if container.status == "running": if container.status == "running":
self.logger.info("Stopping container.")
container.kill() container.kill()
self.logger.info("Removing container.")
container.remove(force=True) container.remove(force=True)
except DockerException as exc: except DockerException as exc:
raise ControllerException(str(exc)) from exc raise ControllerException(str(exc)) from exc

View File

@ -401,6 +401,7 @@ class Outpost(ManagedModel):
user = users.first() user = users.first()
user.attributes[USER_ATTRIBUTE_SA] = True user.attributes[USER_ATTRIBUTE_SA] = True
user.attributes[USER_ATTRIBUTE_CAN_OVERRIDE_IP] = True user.attributes[USER_ATTRIBUTE_CAN_OVERRIDE_IP] = True
user.name = f"Outpost {self.name} Service-Account"
user.save() user.save()
if should_create_user: if should_create_user:
self.build_user_permissions(user) self.build_user_permissions(user)

View File

@ -105,9 +105,12 @@ def outpost_controller(
logs = [] logs = []
if from_cache: if from_cache:
outpost: Outpost = cache.get(CACHE_KEY_OUTPOST_DOWN % outpost_pk) outpost: Outpost = cache.get(CACHE_KEY_OUTPOST_DOWN % outpost_pk)
LOGGER.debug("Getting outpost from cache to delete")
else: else:
outpost: Outpost = Outpost.objects.filter(pk=outpost_pk).first() outpost: Outpost = Outpost.objects.filter(pk=outpost_pk).first()
LOGGER.debug("Getting outpost from DB")
if not outpost: if not outpost:
LOGGER.warning("No outpost")
return return
self.set_uid(slugify(outpost.name)) self.set_uid(slugify(outpost.name))
try: try:

View File

@ -90,7 +90,8 @@ class PolicyEngine:
def build(self) -> "PolicyEngine": def build(self) -> "PolicyEngine":
"""Build wrapper which monitors performance""" """Build wrapper which monitors performance"""
with Hub.current.start_span( with Hub.current.start_span(
op="policy.engine.build" op="policy.engine.build",
description=self.__pbm,
) as span, HIST_POLICIES_BUILD_TIME.labels( ) as span, HIST_POLICIES_BUILD_TIME.labels(
object_name=self.__pbm, object_name=self.__pbm,
object_type=f"{self.__pbm._meta.app_label}.{self.__pbm._meta.model_name}", object_type=f"{self.__pbm._meta.app_label}.{self.__pbm._meta.model_name}",

View File

@ -23,6 +23,6 @@ def invalidate_policy_cache(sender, instance, **_):
total += len(keys) total += len(keys)
cache.delete_many(keys) cache.delete_many(keys)
LOGGER.debug("Invalidating policy cache", policy=instance, keys=total) LOGGER.debug("Invalidating policy cache", policy=instance, keys=total)
# Also delete user application cache # Also delete user application cache
keys = cache.keys(user_app_cache_key("*")) or [] keys = cache.keys(user_app_cache_key("*")) or []
cache.delete_many(keys) cache.delete_many(keys)

View File

@ -37,7 +37,7 @@ def config_loggers(*args, **kwargs):
def after_task_publish_hook(sender=None, headers=None, body=None, **kwargs): def after_task_publish_hook(sender=None, headers=None, body=None, **kwargs):
"""Log task_id after it was published""" """Log task_id after it was published"""
info = headers if "task" in headers else body info = headers if "task" in headers else body
LOGGER.debug("Task published", task_id=info.get("id", ""), task_name=info.get("task", "")) LOGGER.info("Task published", task_id=info.get("id", ""), task_name=info.get("task", ""))
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -48,14 +48,14 @@ def task_prerun_hook(task_id: str, task, *args, **kwargs):
LOCAL.authentik_task = { LOCAL.authentik_task = {
"request_id": request_id, "request_id": request_id,
} }
LOGGER.debug("Task started", task_id=task_id, task_name=task.__name__) LOGGER.info("Task started", task_id=task_id, task_name=task.__name__)
# pylint: disable=unused-argument # pylint: disable=unused-argument
@task_postrun.connect @task_postrun.connect
def task_postrun_hook(task_id, task, *args, retval=None, state=None, **kwargs): def task_postrun_hook(task_id, task, *args, retval=None, state=None, **kwargs):
"""Log task_id on worker""" """Log task_id on worker"""
LOGGER.debug("Task finished", task_id=task_id, task_name=task.__name__, state=state) LOGGER.info("Task finished", task_id=task_id, task_name=task.__name__, state=state)
if not hasattr(LOCAL, "authentik_task"): if not hasattr(LOCAL, "authentik_task"):
return return
for key in list(LOCAL.authentik_task.keys()): for key in list(LOCAL.authentik_task.keys()):

View File

@ -24,6 +24,7 @@ import structlog
from celery.schedules import crontab from celery.schedules import crontab
from sentry_sdk import init as sentry_init from sentry_sdk import init as sentry_init
from sentry_sdk.api import set_tag from sentry_sdk.api import set_tag
from sentry_sdk.integrations.boto3 import Boto3Integration
from sentry_sdk.integrations.celery import CeleryIntegration from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.django import DjangoIntegration from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.redis import RedisIntegration from sentry_sdk.integrations.redis import RedisIntegration
@ -231,6 +232,7 @@ CACHES = {
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"}, "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
} }
} }
DJANGO_REDIS_SCAN_ITERSIZE = 1000
DJANGO_REDIS_IGNORE_EXCEPTIONS = True DJANGO_REDIS_IGNORE_EXCEPTIONS = True
DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True
SESSION_ENGINE = "django.contrib.sessions.backends.cache" SESSION_ENGINE = "django.contrib.sessions.backends.cache"
@ -421,6 +423,7 @@ if _ERROR_REPORTING:
DjangoIntegration(transaction_style="function_name"), DjangoIntegration(transaction_style="function_name"),
CeleryIntegration(), CeleryIntegration(),
RedisIntegration(), RedisIntegration(),
Boto3Integration(),
], ],
before_send=before_send, before_send=before_send,
release=f"authentik@{__version__}", release=f"authentik@{__version__}",

View File

@ -30,7 +30,7 @@ class PytestTestRunner: # pragma: no cover
CONFIG.y_set("authentik.geoip", "tests/GeoLite2-City-Test.mmdb") CONFIG.y_set("authentik.geoip", "tests/GeoLite2-City-Test.mmdb")
CONFIG.y_set( CONFIG.y_set(
"outposts.container_image_base", "outposts.container_image_base",
f"goauthentik.io/dev-%(type)s:{get_docker_tag()}", f"ghcr.io/goauthentik/dev-%(type)s:{get_docker_tag()}",
) )
@classmethod @classmethod

View File

@ -53,9 +53,6 @@ class AuthenticatorWebAuthnChallengeResponse(ChallengeResponse):
def validate_response(self, response: dict) -> dict: def validate_response(self, response: dict) -> dict:
"""Validate webauthn challenge response""" """Validate webauthn challenge response"""
# pylint: disable=no-name-in-module
from pydantic.error_wrappers import ValidationError as PydanticValidationError
challenge = self.request.session["challenge"] challenge = self.request.session["challenge"]
try: try:
@ -65,7 +62,7 @@ class AuthenticatorWebAuthnChallengeResponse(ChallengeResponse):
expected_rp_id=get_rp_id(self.request), expected_rp_id=get_rp_id(self.request),
expected_origin=get_origin(self.request), expected_origin=get_origin(self.request),
) )
except (InvalidRegistrationResponse, PydanticValidationError) as exc: except InvalidRegistrationResponse as exc:
LOGGER.warning("registration failed", exc=exc) LOGGER.warning("registration failed", exc=exc)
raise ValidationError(f"Registration failed. Error: {exc}") raise ValidationError(f"Registration failed. Error: {exc}")

3
go.mod
View File

@ -27,9 +27,8 @@ require (
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect
github.com/prometheus/client_golang v1.11.0 github.com/prometheus/client_golang v1.11.0
github.com/recws-org/recws v1.3.1
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
goauthentik.io/api v0.2021104.10 goauthentik.io/api v0.2021104.11
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558

7
go.sum
View File

@ -356,7 +356,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@ -481,8 +480,6 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/recws-org/recws v1.3.1 h1:vtRhYpgNPBs3iFyu/+zxBqNzLYgID7UPC5siThkvbs0=
github.com/recws-org/recws v1.3.1/go.mod h1:gRH/uJLMsO7lbcecAB1Im1Zc6eKxs93ftGR0R39QeYA=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@ -561,8 +558,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
goauthentik.io/api v0.2021104.10 h1:5A2KLhwe5uSkPPiZDg8td3OLFKxcODSMqkyvRSavcUM= goauthentik.io/api v0.2021104.11 h1:LqT0LM0e/RRrxPuo6Xl5uz3PCR5ytuE+YlNlfW9w0yU=
goauthentik.io/api v0.2021104.10/go.mod h1:02nnD4FRd8lu8A1+ZuzqownBgvAhdCKzqkKX8v7JMTE= goauthentik.io/api v0.2021104.11/go.mod h1:02nnD4FRd8lu8A1+ZuzqownBgvAhdCKzqkKX8v7JMTE=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

View File

@ -16,9 +16,8 @@ func DefaultConfig() {
G = Config{ G = Config{
Debug: false, Debug: false,
Web: WebConfig{ Web: WebConfig{
Listen: "localhost:9000", Listen: "localhost:9000",
ListenTLS: "localhost:9443", ListenTLS: "localhost:9443",
LoadLocalFiles: false,
}, },
Paths: PathsConfig{ Paths: PathsConfig{
Media: "./media", Media: "./media",

View File

@ -30,7 +30,6 @@ type WebConfig struct {
Listen string `yaml:"listen"` Listen string `yaml:"listen"`
ListenTLS string `yaml:"listen_tls"` ListenTLS string `yaml:"listen_tls"`
ListenMetrics string `yaml:"listen_metrics"` ListenMetrics string `yaml:"listen_metrics"`
LoadLocalFiles bool `yaml:"load_local_files" env:"AUTHENTIK_WEB_LOAD_LOCAL_FILES"`
DisableEmbeddedOutpost bool `yaml:"disable_embedded_outpost" env:"AUTHENTIK_WEB__DISABLE_EMBEDDED_OUTPOST"` DisableEmbeddedOutpost bool `yaml:"disable_embedded_outpost" env:"AUTHENTIK_WEB__DISABLE_EMBEDDED_OUTPOST"`
} }

View File

@ -11,10 +11,10 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/go-openapi/strfmt" "github.com/getsentry/sentry-go"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/gorilla/websocket"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/recws-org/recws"
"goauthentik.io/api" "goauthentik.io/api"
"goauthentik.io/internal/constants" "goauthentik.io/internal/constants"
@ -35,20 +35,25 @@ type APIController struct {
logger *log.Entry logger *log.Entry
reloadOffset time.Duration reloadOffset time.Duration
lastWsReconnect time.Time
wsConn *websocket.Conn
lastWsReconnect time.Time
wsIsReconnecting bool
wsBackoffMultiplier int
wsConn *recws.RecConn
instanceUUID uuid.UUID instanceUUID uuid.UUID
} }
// NewAPIController initialise new API Controller instance from URL and API token // NewAPIController initialise new API Controller instance from URL and API token
func NewAPIController(akURL url.URL, token string) *APIController { func NewAPIController(akURL url.URL, token string) *APIController {
rsp := sentry.StartSpan(context.TODO(), "authentik.outposts.init")
config := api.NewConfiguration() config := api.NewConfiguration()
config.Host = akURL.Host config.Host = akURL.Host
config.Scheme = akURL.Scheme config.Scheme = akURL.Scheme
config.HTTPClient = &http.Client{ config.HTTPClient = &http.Client{
Transport: NewUserAgentTransport(constants.OutpostUserAgent(), NewTracingTransport(context.TODO(), GetTLSTransport())), Transport: NewUserAgentTransport(constants.OutpostUserAgent(), NewTracingTransport(rsp.Context(), GetTLSTransport())),
} }
config.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token)) config.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token))
@ -85,12 +90,16 @@ func NewAPIController(akURL url.URL, token string) *APIController {
token: token, token: token,
logger: log, logger: log,
reloadOffset: time.Duration(rand.Intn(10)) * time.Second, reloadOffset: time.Duration(rand.Intn(10)) * time.Second,
instanceUUID: uuid.New(), instanceUUID: uuid.New(),
Outpost: outpost, Outpost: outpost,
wsBackoffMultiplier: 1,
} }
ac.logger.WithField("offset", ac.reloadOffset.String()).Debug("HA Reload offset") ac.logger.WithField("offset", ac.reloadOffset.String()).Debug("HA Reload offset")
ac.initWS(akURL, strfmt.UUID(outpost.Pk)) err = ac.initWS(akURL, outpost.Pk)
if err != nil {
go ac.reconnectWS()
}
ac.configureRefreshSignal() ac.configureRefreshSignal()
return ac return ac
} }
@ -148,10 +157,6 @@ func (a *APIController) StartBackgorundTasks() error {
"version": constants.VERSION, "version": constants.VERSION,
"build": constants.BUILD(), "build": constants.BUILD(),
}).Set(1) }).Set(1)
go func() {
a.logger.Debug("Starting WS re-connector...")
a.startWSReConnector()
}()
go func() { go func() {
a.logger.Debug("Starting WS Handler...") a.logger.Debug("Starting WS Handler...")
a.startWSHandler() a.startWSHandler()

View File

@ -6,18 +6,17 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"strconv"
"strings" "strings"
"time" "time"
"github.com/go-openapi/strfmt"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/recws-org/recws"
"goauthentik.io/internal/constants" "goauthentik.io/internal/constants"
) )
func (ac *APIController) initWS(akURL url.URL, outpostUUID strfmt.UUID) { func (ac *APIController) initWS(akURL url.URL, outpostUUID string) error {
pathTemplate := "%s://%s/ws/outpost/%s/" pathTemplate := "%s://%s/ws/outpost/%s/?%s"
scheme := strings.ReplaceAll(akURL.Scheme, "http", "ws") scheme := strings.ReplaceAll(akURL.Scheme, "http", "ws")
authHeader := fmt.Sprintf("Bearer %s", ac.token) authHeader := fmt.Sprintf("Bearer %s", ac.token)
@ -32,15 +31,19 @@ func (ac *APIController) initWS(akURL url.URL, outpostUUID strfmt.UUID) {
value = "false" value = "false"
} }
ws := &recws.RecConn{ dialer := websocket.Dialer{
NonVerbose: true, Proxy: http.ProxyFromEnvironment,
HandshakeTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
InsecureSkipVerify: strings.ToLower(value) == "true", InsecureSkipVerify: strings.ToLower(value) == "true",
}, },
} }
ws.Dial(fmt.Sprintf(pathTemplate, scheme, akURL.Host, outpostUUID.String()), header)
ac.logger.WithField("logger", "authentik.outpost.ak-ws").WithField("outpost", outpostUUID.String()).Debug("Connecting to authentik") ws, _, err := dialer.Dial(fmt.Sprintf(pathTemplate, scheme, akURL.Host, outpostUUID, akURL.Query().Encode()), header)
if err != nil {
ac.logger.WithError(err).Warning("failed to connect websocket")
return err
}
ac.wsConn = ws ac.wsConn = ws
// Send hello message with our version // Send hello message with our version
@ -52,11 +55,14 @@ func (ac *APIController) initWS(akURL url.URL, outpostUUID strfmt.UUID) {
"uuid": ac.instanceUUID.String(), "uuid": ac.instanceUUID.String(),
}, },
} }
err := ws.WriteJSON(msg) err = ws.WriteJSON(msg)
if err != nil { if err != nil {
ac.logger.WithField("logger", "authentik.outpost.ak-ws").WithError(err).Warning("Failed to hello to authentik") ac.logger.WithField("logger", "authentik.outpost.ak-ws").WithError(err).Warning("Failed to hello to authentik")
return err
} }
ac.lastWsReconnect = time.Now() ac.lastWsReconnect = time.Now()
ac.logger.WithField("logger", "authentik.outpost.ak-ws").WithField("outpost", outpostUUID).Debug("Successfully connected websocket")
return nil
} }
// Shutdown Gracefully stops all workers, disconnects from websocket // Shutdown Gracefully stops all workers, disconnects from websocket
@ -65,21 +71,43 @@ func (ac *APIController) Shutdown() {
// waiting (with timeout) for the server to close the connection. // waiting (with timeout) for the server to close the connection.
err := ac.wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) err := ac.wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil { if err != nil {
ac.logger.Println("write close:", err) ac.logger.WithError(err).Warning("failed to write close message")
return return
} }
err = ac.wsConn.Close()
if err != nil {
ac.logger.WithError(err).Warning("failed to close websocket")
}
ac.logger.Info("finished shutdown")
} }
func (ac *APIController) startWSReConnector() { func (ac *APIController) reconnectWS() {
if ac.wsIsReconnecting {
return
}
ac.wsIsReconnecting = true
u := url.URL{
Host: ac.Client.GetConfig().Host,
Scheme: ac.Client.GetConfig().Scheme,
}
attempt := 1
for { for {
time.Sleep(time.Second * 5) q := u.Query()
if ac.wsConn.IsConnected() { q.Set("attempt", strconv.Itoa(attempt))
continue u.RawQuery = q.Encode()
} err := ac.initWS(u, ac.Outpost.Pk)
if time.Since(ac.lastWsReconnect).Seconds() > 30 { attempt += 1
ac.wsConn.CloseAndReconnect() if err != nil {
ac.logger.Info("Reconnecting websocket") ac.logger.Infof("waiting %d seconds to reconnect", ac.wsBackoffMultiplier)
ac.lastWsReconnect = time.Now() time.Sleep(time.Duration(ac.wsBackoffMultiplier) * time.Second)
ac.wsBackoffMultiplier = ac.wsBackoffMultiplier * 2
// Limit to 300 seconds (5m)
if ac.wsBackoffMultiplier >= 300 {
ac.wsBackoffMultiplier = 300
}
} else {
ac.wsIsReconnecting = false
return
} }
} }
} }
@ -88,6 +116,11 @@ func (ac *APIController) startWSHandler() {
logger := ac.logger.WithField("loop", "ws-handler") logger := ac.logger.WithField("loop", "ws-handler")
for { for {
var wsMsg websocketMessage var wsMsg websocketMessage
if ac.wsConn == nil {
go ac.reconnectWS()
time.Sleep(time.Second * 5)
continue
}
err := ac.wsConn.ReadJSON(&wsMsg) err := ac.wsConn.ReadJSON(&wsMsg)
if err != nil { if err != nil {
ConnectionStatus.With(prometheus.Labels{ ConnectionStatus.With(prometheus.Labels{
@ -96,6 +129,7 @@ func (ac *APIController) startWSHandler() {
"uuid": ac.instanceUUID.String(), "uuid": ac.instanceUUID.String(),
}).Set(0) }).Set(0)
logger.WithError(err).Warning("ws read error") logger.WithError(err).Warning("ws read error")
go ac.reconnectWS()
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)
continue continue
} }
@ -126,9 +160,6 @@ func (ac *APIController) startWSHandler() {
func (ac *APIController) startWSHealth() { func (ac *APIController) startWSHealth() {
ticker := time.NewTicker(time.Second * 10) ticker := time.NewTicker(time.Second * 10)
for ; true; <-ticker.C { for ; true; <-ticker.C {
if !ac.wsConn.IsConnected() {
continue
}
aliveMsg := websocketMessage{ aliveMsg := websocketMessage{
Instruction: WebsocketInstructionHello, Instruction: WebsocketInstructionHello,
Args: map[string]interface{}{ Args: map[string]interface{}{
@ -137,10 +168,16 @@ func (ac *APIController) startWSHealth() {
"uuid": ac.instanceUUID.String(), "uuid": ac.instanceUUID.String(),
}, },
} }
if ac.wsConn == nil {
go ac.reconnectWS()
time.Sleep(time.Second * 5)
continue
}
err := ac.wsConn.WriteJSON(aliveMsg) err := ac.wsConn.WriteJSON(aliveMsg)
ac.logger.WithField("loop", "ws-health").Trace("hello'd") ac.logger.WithField("loop", "ws-health").Trace("hello'd")
if err != nil { if err != nil {
ac.logger.WithField("loop", "ws-health").WithError(err).Warning("ws write error") ac.logger.WithField("loop", "ws-health").WithError(err).Warning("ws write error")
go ac.reconnectWS()
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)
continue continue
} else { } else {

View File

@ -73,7 +73,7 @@ func NewFlowExecutor(ctx context.Context, flowSlug string, refConfig *api.Config
config.Scheme = refConfig.Scheme config.Scheme = refConfig.Scheme
config.HTTPClient = &http.Client{ config.HTTPClient = &http.Client{
Jar: jar, Jar: jar,
Transport: ak.NewUserAgentTransport(constants.OutpostUserAgent(), ak.NewTracingTransport(ctx, ak.GetTLSTransport())), Transport: ak.NewUserAgentTransport(constants.OutpostUserAgent(), ak.NewTracingTransport(rsp.Context(), ak.GetTLSTransport())),
} }
token := strings.Split(refConfig.DefaultHeader["Authorization"], " ")[1] token := strings.Split(refConfig.DefaultHeader["Authorization"], " ")[1]
config.AddDefaultHeader(HeaderAuthentikOutpostToken, token) config.AddDefaultHeader(HeaderAuthentikOutpostToken, token)

View File

@ -52,7 +52,7 @@ func (ls *LDAPServer) StartLDAPServer() error {
ln, err := net.Listen("tcp", listen) ln, err := net.Listen("tcp", listen)
if err != nil { if err != nil {
ls.log.WithField("listen", listen).WithError(err).Fatalf("FATAL: listen failed") ls.log.WithField("listen", listen).WithError(err).Fatalf("listen failed")
} }
proxyListener := &proxyproto.Listener{Listener: ln} proxyListener := &proxyproto.Listener{Listener: ln}
defer proxyListener.Close() defer proxyListener.Close()

View File

@ -37,7 +37,7 @@ func (ls *LDAPServer) StartLDAPTLSServer() error {
ln, err := net.Listen("tcp", listen) ln, err := net.Listen("tcp", listen)
if err != nil { if err != nil {
ls.log.WithField("listen", listen).WithError(err).Fatalf("FATAL: listen failed") ls.log.WithField("listen", listen).WithError(err).Fatalf("listen failed")
} }
proxyListener := &proxyproto.Listener{Listener: ln} proxyListener := &proxyproto.Listener{Listener: ln}

View File

@ -3,6 +3,7 @@ package ldap
import ( import (
"errors" "errors"
"net" "net"
"strings"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
goldap "github.com/go-ldap/ldap/v3" goldap "github.com/go-ldap/ldap/v3"
@ -41,13 +42,13 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
if searchReq.BaseDN == "" { if searchReq.BaseDN == "" {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultSuccess}, nil return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultSuccess}, nil
} }
bd, err := goldap.ParseDN(searchReq.BaseDN) bd, err := goldap.ParseDN(strings.ToLower(searchReq.BaseDN))
if err != nil { if err != nil {
req.Log().WithError(err).Info("failed to parse basedn") req.Log().WithError(err).Info("failed to parse basedn")
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, errors.New("invalid DN") return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, errors.New("invalid DN")
} }
for _, provider := range ls.providers { for _, provider := range ls.providers {
providerBase, _ := goldap.ParseDN(provider.BaseDN) providerBase, _ := goldap.ParseDN(strings.ToLower(provider.BaseDN))
if providerBase.AncestorOf(bd) || providerBase.Equal(bd) { if providerBase.AncestorOf(bd) || providerBase.Equal(bd) {
return provider.searcher.Search(req) return provider.searcher.Search(req)
} }

View File

@ -13,4 +13,6 @@ type Claims struct {
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
} }

View File

@ -25,6 +25,7 @@ func (a *Application) addHeaders(headers http.Header, c *Claims) {
headers.Set("X-authentik-email", c.Email) headers.Set("X-authentik-email", c.Email)
headers.Set("X-authentik-name", c.Name) headers.Set("X-authentik-name", c.Name)
headers.Set("X-authentik-uid", c.Sub) headers.Set("X-authentik-uid", c.Sub)
headers.Set("X-authentik-jwt", c.RawToken)
// System headers // System headers
headers.Set("X-authentik-meta-jwks", a.proxyConfig.OidcConfiguration.JwksUri) headers.Set("X-authentik-meta-jwks", a.proxyConfig.OidcConfiguration.JwksUri)

View File

@ -8,6 +8,7 @@ import (
"net/url" "net/url"
"time" "time"
"github.com/getsentry/sentry-go"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"goauthentik.io/internal/outpost/ak" "goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/proxyv2/metrics" "goauthentik.io/internal/outpost/proxyv2/metrics"
@ -28,7 +29,8 @@ func (a *Application) configureProxy() error {
return err return err
} }
rp := &httputil.ReverseProxy{Director: a.proxyModifyRequest(u)} rp := &httputil.ReverseProxy{Director: a.proxyModifyRequest(u)}
rp.Transport = ak.NewTracingTransport(context.TODO(), a.getUpstreamTransport()) rsp := sentry.StartSpan(context.TODO(), "authentik.outposts.proxy.application_transport")
rp.Transport = ak.NewTracingTransport(rsp.Context(), a.getUpstreamTransport())
rp.ErrorHandler = a.newProxyErrorHandler(templates.GetTemplates()) rp.ErrorHandler = a.newProxyErrorHandler(templates.GetTemplates())
rp.ModifyResponse = a.proxyModifyResponse rp.ModifyResponse = a.proxyModifyResponse
a.mux.PathPrefix("/").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { a.mux.PathPrefix("/").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {

View File

@ -45,5 +45,6 @@ func (a *Application) redeemCallback(r *http.Request, shouldState string) (*Clai
if err := idToken.Claims(&claims); err != nil { if err := idToken.Claims(&claims); err != nil {
return nil, err return nil, err
} }
claims.RawToken = rawIDToken
return claims, nil return claims, nil
} }

View File

@ -2,6 +2,7 @@ package application
import ( import (
"fmt" "fmt"
"os"
"strconv" "strconv"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
@ -26,14 +27,14 @@ func (a *Application) getStore(p api.ProxyOutpostConfig) sessions.Store {
a.log.Info("using redis session backend") a.log.Info("using redis session backend")
store = rs store = rs
} else { } else {
cs := sessions.NewCookieStore([]byte(*p.CookieSecret)) cs := sessions.NewFilesystemStore(os.TempDir(), []byte(*p.CookieSecret))
cs.Options.Domain = *p.CookieDomain cs.Options.Domain = *p.CookieDomain
if p.TokenValidity.IsSet() { if p.TokenValidity.IsSet() {
t := p.TokenValidity.Get() t := p.TokenValidity.Get()
// Add one to the validity to ensure we don't have a session with indefinite length // Add one to the validity to ensure we don't have a session with indefinite length
cs.Options.MaxAge = int(*t) + 1 cs.Options.MaxAge = int(*t) + 1
} }
a.log.Info("using cookie session backend") a.log.Info("using filesystem session backend")
store = cs store = cs
} }
return store return store

View File

@ -103,7 +103,7 @@ func (ps *ProxyServer) ServeHTTP() {
listenAddress := fmt.Sprintf(ps.Listen, 9000+ps.PortOffset) listenAddress := fmt.Sprintf(ps.Listen, 9000+ps.PortOffset)
listener, err := net.Listen("tcp", listenAddress) listener, err := net.Listen("tcp", listenAddress)
if err != nil { if err != nil {
ps.log.Fatalf("FATAL: listen (%s) failed - %s", listenAddress, err) ps.log.WithField("listen", listenAddress).WithError(err).Fatalf("listen failed")
} }
proxyListener := &proxyproto.Listener{Listener: listener} proxyListener := &proxyproto.Listener{Listener: listener}
defer proxyListener.Close() defer proxyListener.Close()

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/getsentry/sentry-go"
"goauthentik.io/internal/constants" "goauthentik.io/internal/constants"
"goauthentik.io/internal/outpost/ak" "goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/proxyv2/application" "goauthentik.io/internal/outpost/proxyv2/application"
@ -20,9 +21,10 @@ func (ps *ProxyServer) Refresh() error {
} }
apps := make(map[string]*application.Application) apps := make(map[string]*application.Application)
for _, provider := range providers.Results { for _, provider := range providers.Results {
rsp := sentry.StartSpan(context.Background(), "authentik.outposts.proxy.application_ss")
ua := fmt.Sprintf(" (provider=%s)", provider.Name) ua := fmt.Sprintf(" (provider=%s)", provider.Name)
hc := &http.Client{ hc := &http.Client{
Transport: ak.NewUserAgentTransport(constants.OutpostUserAgent()+ua, ak.NewTracingTransport(context.TODO(), ak.GetTLSTransport())), Transport: ak.NewUserAgentTransport(constants.OutpostUserAgent()+ua, ak.NewTracingTransport(rsp.Context(), ak.GetTLSTransport())),
} }
a, err := application.NewApplication(provider, hc, ps.cryptoStore, ps.akAPI) a, err := application.NewApplication(provider, hc, ps.cryptoStore, ps.akAPI)
if err != nil { if err != nil {

View File

@ -1,5 +1,5 @@
# Stage 1: Build # Stage 1: Build
FROM docker.io/golang:1.17.4-bullseye AS builder FROM docker.io/golang:1.17.5-bullseye AS builder
WORKDIR /go/src/goauthentik.io WORKDIR /go/src/goauthentik.io

View File

@ -16,7 +16,7 @@ try:
except KeyError: except KeyError:
pass pass
worker_class = "uvicorn.workers.UvicornWorker" worker_class = "lifecycle.worker.DjangoUvicornWorker"
# Docker containers don't have /tmp as tmpfs # Docker containers don't have /tmp as tmpfs
if os.path.exists("/dev/shm"): # nosec if os.path.exists("/dev/shm"): # nosec
worker_tmp_dir = "/dev/shm" # nosec worker_tmp_dir = "/dev/shm" # nosec

13
lifecycle/worker.py Normal file
View File

@ -0,0 +1,13 @@
"""Uvicorn worker"""
from uvicorn.workers import UvicornWorker
class DjangoUvicornWorker(UvicornWorker):
"""Custom configured Uvicorn Worker without lifespan"""
CONFIG_KWARGS = {
"loop": "uvloop",
"http": "httptools",
"lifespan": "off",
"ws": "wsproto",
}

View File

@ -7,7 +7,7 @@ ENV NODE_ENV=production
RUN cd /static && npm i && npm run build-proxy RUN cd /static && npm i && npm run build-proxy
# Stage 2: Build # Stage 2: Build
FROM docker.io/golang:1.17.4-bullseye AS builder FROM docker.io/golang:1.17.5-bullseye AS builder
WORKDIR /go/src/goauthentik.io WORKDIR /go/src/goauthentik.io

View File

@ -2058,6 +2058,11 @@ paths:
operationId: core_groups_list operationId: core_groups_list
description: Group Viewset description: Group Viewset
parameters: parameters:
- in: query
name: attributes
schema:
type: string
description: Attributes
- in: query - in: query
name: is_superuser name: is_superuser
schema: schema:
@ -11846,6 +11851,7 @@ paths:
- ml - ml
- mn - mn
- mr - mr
- ms
- my - my
- nb - nb
- ne - ne

View File

@ -34,7 +34,7 @@ class TestProviderLDAP(SeleniumTestCase):
"""Start ldap container based on outpost created""" """Start ldap container based on outpost created"""
client: DockerClient = from_env() client: DockerClient = from_env()
container = client.containers.run( container = client.containers.run(
image=self.get_container_image("goauthentik.io/dev-ldap"), image=self.get_container_image("ghcr.io/goauthentik/dev-ldap"),
detach=True, detach=True,
network_mode="host", network_mode="host",
auto_remove=True, auto_remove=True,
@ -183,7 +183,7 @@ class TestProviderLDAP(SeleniumTestCase):
User.objects.filter(username="akadmin").delete() User.objects.filter(username="akadmin").delete()
_connection.search( _connection.search(
"ou=users,dc=ldap,dc=goauthentik,dc=io", "ou=Users,DC=ldaP,dc=goauthentik,dc=io",
"(objectClass=user)", "(objectClass=user)",
search_scope=SUBTREE, search_scope=SUBTREE,
attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES], attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES],
@ -203,8 +203,8 @@ class TestProviderLDAP(SeleniumTestCase):
"cn": [o_user.username], "cn": [o_user.username],
"sAMAccountName": [o_user.username], "sAMAccountName": [o_user.username],
"uid": [o_user.uid], "uid": [o_user.uid],
"name": [""], "name": [o_user.name],
"displayName": [""], "displayName": [o_user.name],
"mail": [""], "mail": [""],
"objectClass": [ "objectClass": [
"user", "user",
@ -230,8 +230,8 @@ class TestProviderLDAP(SeleniumTestCase):
"cn": [embedded_account.username], "cn": [embedded_account.username],
"sAMAccountName": [embedded_account.username], "sAMAccountName": [embedded_account.username],
"uid": [embedded_account.uid], "uid": [embedded_account.uid],
"name": [""], "name": [embedded_account.name],
"displayName": [""], "displayName": [embedded_account.name],
"mail": [""], "mail": [""],
"objectClass": [ "objectClass": [
"user", "user",

View File

@ -42,7 +42,7 @@ class TestProviderProxy(SeleniumTestCase):
"""Start proxy container based on outpost created""" """Start proxy container based on outpost created"""
client: DockerClient = from_env() client: DockerClient = from_env()
container = client.containers.run( container = client.containers.run(
image=self.get_container_image("goauthentik.io/dev-proxy"), image=self.get_container_image("ghcr.io/goauthentik/dev-proxy"),
detach=True, detach=True,
network_mode="host", network_mode="host",
environment={ environment={
@ -158,8 +158,7 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
sleep(0.5) sleep(0.5)
state = outpost.state state = outpost.state
self.assertEqual(len(state), 1) self.assertTrue(len(state) >= 1)
self.assertEqual(state[0].version, __version__)
# Make sure to delete the outpost to remove the container # Make sure to delete the outpost to remove the container
outpost.delete() outpost.delete()

View File

@ -93,10 +93,10 @@ class SeleniumTestCase(StaticLiveServerTestCase):
def output_container_logs(self, container: Optional[Container] = None): def output_container_logs(self, container: Optional[Container] = None):
"""Output the container logs to our STDOUT""" """Output the container logs to our STDOUT"""
_container = container or self.container _container = container or self.container
self.logger.debug("--------container logs", container=_container.image.tags[0]) print(f"--------container logs {_container.image.tags[0]}")
for log in _container.logs().decode().split("\n"): for log in _container.logs().decode().split("\n"):
self.logger.debug(log, container=_container.image.tags[0]) print(log)
self.logger.debug("--------end container logs", container=_container.image.tags[0]) print(f"--------end container logs {_container.image.tags[0]}")
def get_container_specs(self) -> Optional[dict[str, Any]]: def get_container_specs(self) -> Optional[dict[str, Any]]:
"""Optionally get container specs which will launched on setup, wait for the container to """Optionally get container specs which will launched on setup, wait for the container to

View File

@ -116,5 +116,5 @@ class OutpostDockerTests(ChannelsLiveServerTestCase):
self.assertEqual(compose["version"], "3.5") self.assertEqual(compose["version"], "3.5")
self.assertEqual( self.assertEqual(
compose["services"]["authentik_proxy"]["image"], compose["services"]["authentik_proxy"]["image"],
f"goauthentik.io/dev-proxy:{get_docker_tag()}", f"ghcr.io/goauthentik/dev-proxy:{get_docker_tag()}",
) )

View File

@ -116,5 +116,5 @@ class TestProxyDocker(ChannelsLiveServerTestCase):
self.assertEqual(compose["version"], "3.5") self.assertEqual(compose["version"], "3.5")
self.assertEqual( self.assertEqual(
compose["services"]["authentik_proxy"]["image"], compose["services"]["authentik_proxy"]["image"],
f"goauthentik.io/dev-proxy:{get_docker_tag()}", f"ghcr.io/goauthentik/dev-proxy:{get_docker_tag()}",
) )

211
web/package-lock.json generated
View File

@ -15,7 +15,7 @@
"@babel/preset-env": "^7.16.4", "@babel/preset-env": "^7.16.4",
"@babel/preset-typescript": "^7.16.0", "@babel/preset-typescript": "^7.16.0",
"@fortawesome/fontawesome-free": "^5.15.4", "@fortawesome/fontawesome-free": "^5.15.4",
"@goauthentik/api": "^2021.10.4-1638781871", "@goauthentik/api": "^2021.10.4-1639076050",
"@jackfranklin/rollup-plugin-markdown": "^0.3.0", "@jackfranklin/rollup-plugin-markdown": "^0.3.0",
"@lingui/cli": "^3.13.0", "@lingui/cli": "^3.13.0",
"@lingui/core": "^3.13.0", "@lingui/core": "^3.13.0",
@ -29,8 +29,8 @@
"@rollup/plugin-node-resolve": "^13.0.6", "@rollup/plugin-node-resolve": "^13.0.6",
"@rollup/plugin-replace": "^3.0.0", "@rollup/plugin-replace": "^3.0.0",
"@rollup/plugin-typescript": "^8.3.0", "@rollup/plugin-typescript": "^8.3.0",
"@sentry/browser": "^6.16.0", "@sentry/browser": "^6.16.1",
"@sentry/tracing": "^6.16.0", "@sentry/tracing": "^6.16.1",
"@squoosh/cli": "^0.7.2", "@squoosh/cli": "^0.7.2",
"@trivago/prettier-plugin-sort-imports": "^3.1.1", "@trivago/prettier-plugin-sort-imports": "^3.1.1",
"@types/chart.js": "^2.9.34", "@types/chart.js": "^2.9.34",
@ -44,7 +44,7 @@
"chart.js": "^3.6.2", "chart.js": "^3.6.2",
"chartjs-adapter-moment": "^1.0.0", "chartjs-adapter-moment": "^1.0.0",
"codemirror": "^5.64.0", "codemirror": "^5.64.0",
"construct-style-sheets-polyfill": "^2.4.16", "construct-style-sheets-polyfill": "^3.0.5",
"eslint": "^8.4.1", "eslint": "^8.4.1",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.4", "eslint-plugin-custom-elements": "0.0.4",
@ -55,7 +55,7 @@
"moment": "^2.29.1", "moment": "^2.29.1",
"prettier": "^2.5.1", "prettier": "^2.5.1",
"rapidoc": "^9.1.3", "rapidoc": "^9.1.3",
"rollup": "^2.60.2", "rollup": "^2.61.1",
"rollup-plugin-copy": "^3.4.0", "rollup-plugin-copy": "^3.4.0",
"rollup-plugin-cssimport": "^1.0.2", "rollup-plugin-cssimport": "^1.0.2",
"rollup-plugin-minify-html-literals": "^1.2.6", "rollup-plugin-minify-html-literals": "^1.2.6",
@ -63,7 +63,7 @@
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"ts-lit-plugin": "^1.2.1", "ts-lit-plugin": "^1.2.1",
"tslib": "^2.3.1", "tslib": "^2.3.1",
"typescript": "^4.5.2", "typescript": "^4.5.3",
"webcomponent-qr-code": "^1.0.5", "webcomponent-qr-code": "^1.0.5",
"yaml": "^1.10.2" "yaml": "^1.10.2"
} }
@ -1708,9 +1708,9 @@
} }
}, },
"node_modules/@goauthentik/api": { "node_modules/@goauthentik/api": {
"version": "2021.10.4-1638781871", "version": "2021.10.4-1639076050",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.10.4-1638781871.tgz", "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.10.4-1639076050.tgz",
"integrity": "sha512-QI/pqVVCt/W+iXZGXXipAYX39CBpuUEPDxFKPCiQyU+G0+rLYt1T1umBjTIlIEXRKC8xKSInLkDS/GEz32kXNA==" "integrity": "sha512-Ud8hYHeaz9BCpJwYlOGce41MqhHeHiGVh7nlsWdUIOGCyrraJD3lFaPBgnG1l0M2RpxUeHz2owSUetS23X164g=="
}, },
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.9.2", "version": "0.9.2",
@ -2370,13 +2370,13 @@
} }
}, },
"node_modules/@sentry/browser": { "node_modules/@sentry/browser": {
"version": "6.16.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.16.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.16.1.tgz",
"integrity": "sha512-rpFrS/DPKH9NAWfEhrgpVmqJtfUIGvl9y6KQv0QsNv7X0ZISNtsoHIUe2jVrbjysjWXrJCryCxcSxNgqsa4Www==", "integrity": "sha512-F2I5RL7RTLQF9CccMrqt73GRdK3FdqaChED3RulGQX5lH6U3exHGFxwyZxSrY4x6FedfBFYlfXWWCJXpLnFkow==",
"dependencies": { "dependencies": {
"@sentry/core": "6.16.0", "@sentry/core": "6.16.1",
"@sentry/types": "6.16.0", "@sentry/types": "6.16.1",
"@sentry/utils": "6.16.0", "@sentry/utils": "6.16.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2389,14 +2389,14 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/core": { "node_modules/@sentry/core": {
"version": "6.16.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.16.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.16.1.tgz",
"integrity": "sha512-XqIlMjefuJmwQSAzv9J1PtV6+sXiz1dgBbtRr6e+QGIYZ+BDkuyDQv/HsGPfxxMHxgJBxBzi71FFLjEJsF6CBg==", "integrity": "sha512-UFI0264CPUc5cR1zJH+S2UPOANpm6dLJOnsvnIGTjsrwzR0h8Hdl6rC2R/GPq+WNbnipo9hkiIwDlqbqvIU5vw==",
"dependencies": { "dependencies": {
"@sentry/hub": "6.16.0", "@sentry/hub": "6.16.1",
"@sentry/minimal": "6.16.0", "@sentry/minimal": "6.16.1",
"@sentry/types": "6.16.0", "@sentry/types": "6.16.1",
"@sentry/utils": "6.16.0", "@sentry/utils": "6.16.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2409,12 +2409,12 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/hub": { "node_modules/@sentry/hub": {
"version": "6.16.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.16.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.16.1.tgz",
"integrity": "sha512-NBkcgGjnYsoXyIJwi2TGCxGnxbDJc/t++0ukFoBRy6RL/pw2YnryCu8PWNFsDkZdlb1zt5SIC6Kui+q1ViNS/A==", "integrity": "sha512-4PGtg6AfpqMkreTpL7ymDeQ/U1uXv03bKUuFdtsSTn/FRf9TLS4JB0KuTZCxfp1IRgAA+iFg6B784dDkT8R9eg==",
"dependencies": { "dependencies": {
"@sentry/types": "6.16.0", "@sentry/types": "6.16.1",
"@sentry/utils": "6.16.0", "@sentry/utils": "6.16.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2427,12 +2427,12 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/minimal": { "node_modules/@sentry/minimal": {
"version": "6.16.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.16.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.16.1.tgz",
"integrity": "sha512-9/h0J9BDDY5W/dKILGEq3ewECspNoxcXuly/WOWQdt2SQpIcoh8l/dF8iTXle+icndin0EiMEyHOzaCPWG24oQ==", "integrity": "sha512-dq+mI1EQIvUM+zJtGCVgH3/B3Sbx4hKlGf2Usovm9KoqWYA+QpfVBholYDe/H2RXgO7LFEefDLvOdHDkqeJoyA==",
"dependencies": { "dependencies": {
"@sentry/hub": "6.16.0", "@sentry/hub": "6.16.1",
"@sentry/types": "6.16.0", "@sentry/types": "6.16.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2445,14 +2445,14 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/tracing": { "node_modules/@sentry/tracing": {
"version": "6.16.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.16.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.16.1.tgz",
"integrity": "sha512-vTTjGnLc9fa3jM0RKkEgOLW23CiPb1Kh6bkHbUw68d3DVz6o0Tj2SqzW+Y+LaIwlFjhrozf+YV/KS9vj4BhHTw==", "integrity": "sha512-MPSbqXX59P+OEeST+U2V/8Hu/8QjpTUxTNeNyTHWIbbchdcMMjDbXTS3etCgajZR6Ro+DHElOz5cdSxH6IBGlA==",
"dependencies": { "dependencies": {
"@sentry/hub": "6.16.0", "@sentry/hub": "6.16.1",
"@sentry/minimal": "6.16.0", "@sentry/minimal": "6.16.1",
"@sentry/types": "6.16.0", "@sentry/types": "6.16.1",
"@sentry/utils": "6.16.0", "@sentry/utils": "6.16.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -2465,19 +2465,19 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/types": { "node_modules/@sentry/types": {
"version": "6.16.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.16.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.16.1.tgz",
"integrity": "sha512-ZgIyLYlQS4SPi+d68XD8n9FzoObrNQLWxBuMYMnG3uJSuFeYAJrVYkDRtW4OW0D3awuajYGiHJZC2O5qTRGflA==", "integrity": "sha512-Wh354g30UsJ5kYJbercektGX4ZMc9MHU++1NjeN2bTMnbofEcpUDWIiKeulZEY65IC1iU+1zRQQgtYO+/hgCUQ==",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/@sentry/utils": { "node_modules/@sentry/utils": {
"version": "6.16.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.16.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.16.1.tgz",
"integrity": "sha512-FJl1AyUVAIzxfEXufWsgX7KxIvOrQawxhAhLXO4vU5xrFrJOteicxAIFJO+GG0QDELgr9siP0Qgeb8LoINWcrw==", "integrity": "sha512-7ngq/i4R8JZitJo9Sl8PDnjSbDehOxgr1vsoMmerIsyRZ651C/8B+jVkMhaAPgSdyJ0AlE3O7DKKTP1FXFw9qw==",
"dependencies": { "dependencies": {
"@sentry/types": "6.16.0", "@sentry/types": "6.16.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"engines": { "engines": {
@ -3750,9 +3750,12 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
}, },
"node_modules/construct-style-sheets-polyfill": { "node_modules/construct-style-sheets-polyfill": {
"version": "2.4.17", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/construct-style-sheets-polyfill/-/construct-style-sheets-polyfill-2.4.17.tgz", "resolved": "https://registry.npmjs.org/construct-style-sheets-polyfill/-/construct-style-sheets-polyfill-3.0.5.tgz",
"integrity": "sha512-rKtZGWWAVFE6HgdBuuui1emic/t8aAKQbePQ7Je6ird8nZYSd3mqqBX9IvFn2CMtnbh7mQTk/vxc9mfaLl7cGQ==" "integrity": "sha512-prLKSx9gYwtmqWtq+pZxppU1SaH9o4ug7JIc0I/1zMV2bFE3GvRtQaMTIpotlhw33XjtC7rGQFOZJsOFnlAAhQ==",
"engines": {
"npm": ">=7"
}
}, },
"node_modules/convert-source-map": { "node_modules/convert-source-map": {
"version": "1.8.0", "version": "1.8.0",
@ -7188,9 +7191,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "2.60.2", "version": "2.61.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.60.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.61.1.tgz",
"integrity": "sha512-1Bgjpq61sPjgoZzuiDSGvbI1tD91giZABgjCQBKM5aYLnzjq52GoDuWVwT/cm/MCxCMPU8gqQvkj8doQ5C8Oqw==", "integrity": "sha512-BbTXlEvB8d+XFbK/7E5doIcRtxWPRiqr0eb5vQ0+2paMM04Ye4PZY5nHOQef2ix24l/L0SpLd5hwcH15QHPdvA==",
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
}, },
@ -8328,9 +8331,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "4.5.2", "version": "4.5.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.3.tgz",
"integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", "integrity": "sha512-eVYaEHALSt+s9LbvgEv4Ef+Tdq7hBiIZgii12xXJnukryt3pMgJf6aKhoCZ3FWQsu6sydEnkg11fYXLzhLBjeQ==",
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@ -9895,9 +9898,9 @@
"integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg==" "integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg=="
}, },
"@goauthentik/api": { "@goauthentik/api": {
"version": "2021.10.4-1638781871", "version": "2021.10.4-1639076050",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.10.4-1638781871.tgz", "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.10.4-1639076050.tgz",
"integrity": "sha512-QI/pqVVCt/W+iXZGXXipAYX39CBpuUEPDxFKPCiQyU+G0+rLYt1T1umBjTIlIEXRKC8xKSInLkDS/GEz32kXNA==" "integrity": "sha512-Ud8hYHeaz9BCpJwYlOGce41MqhHeHiGVh7nlsWdUIOGCyrraJD3lFaPBgnG1l0M2RpxUeHz2owSUetS23X164g=="
}, },
"@humanwhocodes/config-array": { "@humanwhocodes/config-array": {
"version": "0.9.2", "version": "0.9.2",
@ -10415,13 +10418,13 @@
} }
}, },
"@sentry/browser": { "@sentry/browser": {
"version": "6.16.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.16.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.16.1.tgz",
"integrity": "sha512-rpFrS/DPKH9NAWfEhrgpVmqJtfUIGvl9y6KQv0QsNv7X0ZISNtsoHIUe2jVrbjysjWXrJCryCxcSxNgqsa4Www==", "integrity": "sha512-F2I5RL7RTLQF9CccMrqt73GRdK3FdqaChED3RulGQX5lH6U3exHGFxwyZxSrY4x6FedfBFYlfXWWCJXpLnFkow==",
"requires": { "requires": {
"@sentry/core": "6.16.0", "@sentry/core": "6.16.1",
"@sentry/types": "6.16.0", "@sentry/types": "6.16.1",
"@sentry/utils": "6.16.0", "@sentry/utils": "6.16.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -10433,14 +10436,14 @@
} }
}, },
"@sentry/core": { "@sentry/core": {
"version": "6.16.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.16.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.16.1.tgz",
"integrity": "sha512-XqIlMjefuJmwQSAzv9J1PtV6+sXiz1dgBbtRr6e+QGIYZ+BDkuyDQv/HsGPfxxMHxgJBxBzi71FFLjEJsF6CBg==", "integrity": "sha512-UFI0264CPUc5cR1zJH+S2UPOANpm6dLJOnsvnIGTjsrwzR0h8Hdl6rC2R/GPq+WNbnipo9hkiIwDlqbqvIU5vw==",
"requires": { "requires": {
"@sentry/hub": "6.16.0", "@sentry/hub": "6.16.1",
"@sentry/minimal": "6.16.0", "@sentry/minimal": "6.16.1",
"@sentry/types": "6.16.0", "@sentry/types": "6.16.1",
"@sentry/utils": "6.16.0", "@sentry/utils": "6.16.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -10452,12 +10455,12 @@
} }
}, },
"@sentry/hub": { "@sentry/hub": {
"version": "6.16.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.16.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.16.1.tgz",
"integrity": "sha512-NBkcgGjnYsoXyIJwi2TGCxGnxbDJc/t++0ukFoBRy6RL/pw2YnryCu8PWNFsDkZdlb1zt5SIC6Kui+q1ViNS/A==", "integrity": "sha512-4PGtg6AfpqMkreTpL7ymDeQ/U1uXv03bKUuFdtsSTn/FRf9TLS4JB0KuTZCxfp1IRgAA+iFg6B784dDkT8R9eg==",
"requires": { "requires": {
"@sentry/types": "6.16.0", "@sentry/types": "6.16.1",
"@sentry/utils": "6.16.0", "@sentry/utils": "6.16.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -10469,12 +10472,12 @@
} }
}, },
"@sentry/minimal": { "@sentry/minimal": {
"version": "6.16.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.16.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.16.1.tgz",
"integrity": "sha512-9/h0J9BDDY5W/dKILGEq3ewECspNoxcXuly/WOWQdt2SQpIcoh8l/dF8iTXle+icndin0EiMEyHOzaCPWG24oQ==", "integrity": "sha512-dq+mI1EQIvUM+zJtGCVgH3/B3Sbx4hKlGf2Usovm9KoqWYA+QpfVBholYDe/H2RXgO7LFEefDLvOdHDkqeJoyA==",
"requires": { "requires": {
"@sentry/hub": "6.16.0", "@sentry/hub": "6.16.1",
"@sentry/types": "6.16.0", "@sentry/types": "6.16.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -10486,14 +10489,14 @@
} }
}, },
"@sentry/tracing": { "@sentry/tracing": {
"version": "6.16.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.16.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.16.1.tgz",
"integrity": "sha512-vTTjGnLc9fa3jM0RKkEgOLW23CiPb1Kh6bkHbUw68d3DVz6o0Tj2SqzW+Y+LaIwlFjhrozf+YV/KS9vj4BhHTw==", "integrity": "sha512-MPSbqXX59P+OEeST+U2V/8Hu/8QjpTUxTNeNyTHWIbbchdcMMjDbXTS3etCgajZR6Ro+DHElOz5cdSxH6IBGlA==",
"requires": { "requires": {
"@sentry/hub": "6.16.0", "@sentry/hub": "6.16.1",
"@sentry/minimal": "6.16.0", "@sentry/minimal": "6.16.1",
"@sentry/types": "6.16.0", "@sentry/types": "6.16.1",
"@sentry/utils": "6.16.0", "@sentry/utils": "6.16.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -10505,16 +10508,16 @@
} }
}, },
"@sentry/types": { "@sentry/types": {
"version": "6.16.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.16.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.16.1.tgz",
"integrity": "sha512-ZgIyLYlQS4SPi+d68XD8n9FzoObrNQLWxBuMYMnG3uJSuFeYAJrVYkDRtW4OW0D3awuajYGiHJZC2O5qTRGflA==" "integrity": "sha512-Wh354g30UsJ5kYJbercektGX4ZMc9MHU++1NjeN2bTMnbofEcpUDWIiKeulZEY65IC1iU+1zRQQgtYO+/hgCUQ=="
}, },
"@sentry/utils": { "@sentry/utils": {
"version": "6.16.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.16.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.16.1.tgz",
"integrity": "sha512-FJl1AyUVAIzxfEXufWsgX7KxIvOrQawxhAhLXO4vU5xrFrJOteicxAIFJO+GG0QDELgr9siP0Qgeb8LoINWcrw==", "integrity": "sha512-7ngq/i4R8JZitJo9Sl8PDnjSbDehOxgr1vsoMmerIsyRZ651C/8B+jVkMhaAPgSdyJ0AlE3O7DKKTP1FXFw9qw==",
"requires": { "requires": {
"@sentry/types": "6.16.0", "@sentry/types": "6.16.1",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -11457,9 +11460,9 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
}, },
"construct-style-sheets-polyfill": { "construct-style-sheets-polyfill": {
"version": "2.4.17", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/construct-style-sheets-polyfill/-/construct-style-sheets-polyfill-2.4.17.tgz", "resolved": "https://registry.npmjs.org/construct-style-sheets-polyfill/-/construct-style-sheets-polyfill-3.0.5.tgz",
"integrity": "sha512-rKtZGWWAVFE6HgdBuuui1emic/t8aAKQbePQ7Je6ird8nZYSd3mqqBX9IvFn2CMtnbh7mQTk/vxc9mfaLl7cGQ==" "integrity": "sha512-prLKSx9gYwtmqWtq+pZxppU1SaH9o4ug7JIc0I/1zMV2bFE3GvRtQaMTIpotlhw33XjtC7rGQFOZJsOFnlAAhQ=="
}, },
"convert-source-map": { "convert-source-map": {
"version": "1.8.0", "version": "1.8.0",
@ -14025,9 +14028,9 @@
} }
}, },
"rollup": { "rollup": {
"version": "2.60.2", "version": "2.61.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.60.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.61.1.tgz",
"integrity": "sha512-1Bgjpq61sPjgoZzuiDSGvbI1tD91giZABgjCQBKM5aYLnzjq52GoDuWVwT/cm/MCxCMPU8gqQvkj8doQ5C8Oqw==", "integrity": "sha512-BbTXlEvB8d+XFbK/7E5doIcRtxWPRiqr0eb5vQ0+2paMM04Ye4PZY5nHOQef2ix24l/L0SpLd5hwcH15QHPdvA==",
"requires": { "requires": {
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
@ -14913,9 +14916,9 @@
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="
}, },
"typescript": { "typescript": {
"version": "4.5.2", "version": "4.5.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.3.tgz",
"integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==" "integrity": "sha512-eVYaEHALSt+s9LbvgEv4Ef+Tdq7hBiIZgii12xXJnukryt3pMgJf6aKhoCZ3FWQsu6sydEnkg11fYXLzhLBjeQ=="
}, },
"uglify-js": { "uglify-js": {
"version": "3.14.1", "version": "3.14.1",

View File

@ -51,7 +51,7 @@
"@babel/preset-env": "^7.16.4", "@babel/preset-env": "^7.16.4",
"@babel/preset-typescript": "^7.16.0", "@babel/preset-typescript": "^7.16.0",
"@fortawesome/fontawesome-free": "^5.15.4", "@fortawesome/fontawesome-free": "^5.15.4",
"@goauthentik/api": "^2021.10.4-1638781871", "@goauthentik/api": "^2021.10.4-1639076050",
"@jackfranklin/rollup-plugin-markdown": "^0.3.0", "@jackfranklin/rollup-plugin-markdown": "^0.3.0",
"@lingui/cli": "^3.13.0", "@lingui/cli": "^3.13.0",
"@lingui/core": "^3.13.0", "@lingui/core": "^3.13.0",
@ -65,8 +65,8 @@
"@rollup/plugin-node-resolve": "^13.0.6", "@rollup/plugin-node-resolve": "^13.0.6",
"@rollup/plugin-replace": "^3.0.0", "@rollup/plugin-replace": "^3.0.0",
"@rollup/plugin-typescript": "^8.3.0", "@rollup/plugin-typescript": "^8.3.0",
"@sentry/browser": "^6.16.0", "@sentry/browser": "^6.16.1",
"@sentry/tracing": "^6.16.0", "@sentry/tracing": "^6.16.1",
"@squoosh/cli": "^0.7.2", "@squoosh/cli": "^0.7.2",
"@trivago/prettier-plugin-sort-imports": "^3.1.1", "@trivago/prettier-plugin-sort-imports": "^3.1.1",
"@types/chart.js": "^2.9.34", "@types/chart.js": "^2.9.34",
@ -80,7 +80,7 @@
"chart.js": "^3.6.2", "chart.js": "^3.6.2",
"chartjs-adapter-moment": "^1.0.0", "chartjs-adapter-moment": "^1.0.0",
"codemirror": "^5.64.0", "codemirror": "^5.64.0",
"construct-style-sheets-polyfill": "^2.4.16", "construct-style-sheets-polyfill": "^3.0.5",
"eslint": "^8.4.1", "eslint": "^8.4.1",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.4", "eslint-plugin-custom-elements": "0.0.4",
@ -91,7 +91,7 @@
"moment": "^2.29.1", "moment": "^2.29.1",
"prettier": "^2.5.1", "prettier": "^2.5.1",
"rapidoc": "^9.1.3", "rapidoc": "^9.1.3",
"rollup": "^2.60.2", "rollup": "^2.61.1",
"rollup-plugin-copy": "^3.4.0", "rollup-plugin-copy": "^3.4.0",
"rollup-plugin-cssimport": "^1.0.2", "rollup-plugin-cssimport": "^1.0.2",
"rollup-plugin-minify-html-literals": "^1.2.6", "rollup-plugin-minify-html-literals": "^1.2.6",
@ -99,7 +99,7 @@
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"ts-lit-plugin": "^1.2.1", "ts-lit-plugin": "^1.2.1",
"tslib": "^2.3.1", "tslib": "^2.3.1",
"typescript": "^4.5.2", "typescript": "^4.5.3",
"webcomponent-qr-code": "^1.0.5", "webcomponent-qr-code": "^1.0.5",
"yaml": "^1.10.2" "yaml": "^1.10.2"
} }

View File

@ -338,6 +338,9 @@ html > form > input {
.pf-c-notification-drawer__list-item { .pf-c-notification-drawer__list-item {
background-color: var(--ak-dark-background-light-ish); background-color: var(--ak-dark-background-light-ish);
color: var(--ak-dark-foreground); color: var(--ak-dark-foreground);
--pf-c-notification-drawer__list-item--BorderBottomColor: var(
--ak-dark-background-lighter
) !important;
} }
/* data list */ /* data list */
.pf-c-data-list__item { .pf-c-data-list__item {

View File

@ -62,7 +62,7 @@ export class UserListPage extends TablePage<User> {
search: this.search || "", search: this.search || "",
attributes: this.hideServiceAccounts attributes: this.hideServiceAccounts
? JSON.stringify({ ? JSON.stringify({
"goauthentik.io/user/service-account__isnull": "true", "goauthentik.io/user/service-account__isnull": true,
}) })
: undefined, : undefined,
}); });

View File

@ -21,7 +21,6 @@ If you want to only make changes on the UI, you don't need a backend running fro
AUTHENTIK_TAG=gh-next AUTHENTIK_TAG=gh-next
AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE=goauthentik.io/dev-%(type)s:gh-next AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE=goauthentik.io/dev-%(type)s:gh-next
AUTHENTIK_LOG_LEVEL=debug AUTHENTIK_LOG_LEVEL=debug
AUTHENTIK_WEB_LOAD_LOCAL_FILES=true
``` ```
This will cause authentik to use the beta images. This will cause authentik to use the beta images.

View File

@ -7,31 +7,16 @@ services:
container_name: traefik container_name: traefik
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
labels:
traefik.enable: true
traefik.http.routers.api.rule: Host(`traefik.example.com`)
traefik.http.routers.api.entrypoints: https
traefik.http.routers.api.service: api@internal
traefik.http.routers.api.tls: true
ports: ports:
- 80:80 - 80:80
- 443:443
command: command:
- '--api' - '--api'
- '--log=true'
- '--log.level=DEBUG'
- '--log.filepath=/var/log/traefik.log'
- '--providers.docker=true' - '--providers.docker=true'
- '--providers.docker.exposedByDefault=false' - '--providers.docker.exposedByDefault=false'
- '--entrypoints.http=true' - "--entrypoints.web.address=:80"
- '--entrypoints.http.address=:80'
- '--entrypoints.http.http.redirections.entrypoint.to=https'
- '--entrypoints.http.http.redirections.entrypoint.scheme=https'
- '--entrypoints.https=true'
- '--entrypoints.https.address=:443'
authentik_proxy: authentik-proxy:
image: goauthentik.io/proxy:2021.5.1 image: goauthentik.io/proxy:latest
ports: ports:
- 9000:9000 - 9000:9000
- 9443:9443 - 9443:9443
@ -46,11 +31,10 @@ services:
traefik.enable: true traefik.enable: true
traefik.port: 9000 traefik.port: 9000
traefik.http.routers.authentik.rule: Host(`app.company`) && PathPrefix(`/akprox/`) traefik.http.routers.authentik.rule: Host(`app.company`) && PathPrefix(`/akprox/`)
traefik.http.routers.authentik.entrypoints: https # `authentik-proxy` refers to the service name in the compose file.
traefik.http.routers.authentik.tls: true traefik.http.middlewares.authentik.forwardauth.address: http://authentik-proxy:9000/akprox/auth/traefik
traefik.http.middlewares.authentik.forwardauth.address: http://outpost.company:9000/akprox/auth/traefik
traefik.http.middlewares.authentik.forwardauth.trustForwardHeader: true traefik.http.middlewares.authentik.forwardauth.trustForwardHeader: true
traefik.http.middlewares.authentik.forwardauth.authResponseHeadersRegex: ^.*$ traefik.http.middlewares.authentik.forwardauth.authResponseHeadersRegex: ^.*$$
restart: unless-stopped restart: unless-stopped
whoami: whoami:
@ -58,8 +42,6 @@ services:
labels: labels:
traefik.enable: true traefik.enable: true
traefik.http.routers.whoami.rule: Host(`app.company`) traefik.http.routers.whoami.rule: Host(`app.company`)
traefik.http.routers.whoami.entrypoints: https
traefik.http.routers.whoami.tls: true
traefik.http.routers.whoami.middlewares: authentik@docker traefik.http.routers.whoami.middlewares: authentik@docker
restart: unless-stopped restart: unless-stopped
``` ```

View File

@ -86,6 +86,23 @@ This release does not have any headline features, and mostly fixes bugs.
- web/flows: fix linting errors - web/flows: fix linting errors
- web/flows: Revise duo authenticator login prompt text (#1872) - web/flows: Revise duo authenticator login prompt text (#1872)
## Fixed in 2021.12.1-rc3
- core: add FlowToken which saves the pickled flow plan, replace standard token in email stage to allow finishing flows in different sessions
- core: fix missing permission check for group creating when creating service account
- outposts/ldap: Fix search case sensitivity. (#1897)
- policies/expression: add ak_call_policy
- providers/saml: add ?force_binding to limit bindings for metadata endpoint
- root: add request_id to celery tasks, prefixed with "task-"
- sources/*: Allow creation of source connections via API
- stages/prompt: use policyenginemode all
- tests/e2e: add post binding test
- web: fix duplicate classes, make generic icon clickable
- web: fix text colour for bad request on light mode
- web/admin: show outpost warning on application page too
- web/elements: close dropdown when refresh event is dispatched
- web/user: allow custom font-awesome icons for applications
## Upgrading ## Upgrading
This release does not introduce any new requirements. This release does not introduce any new requirements.

View File

@ -0,0 +1,54 @@
---
title: FortiManager
---
## What is FortiManager
From https://www.fortinet.com/products/management/fortimanager
:::note
FortiManager supports network operations use cases for centralized management, best practices compliance, and workflow automation to provide better protection against breaches.
FortiManager is a paid enterprise product.
:::
## Preparation
The following placeholders will be used:
- `fgm.company` is the FQDN of the FortiManager install.
- `authentik.company` is the FQDN of the authentik install.
Create an application and Provider in authentik, note the slug, as this will be used later. Create a SAML provider with the following parameters:
Provider:
- ACS URL: `https://fgm.company/saml/?acs`
- Issuer: `https://authentik.company/application/saml/fgm/sso/binding/redirect/`
- Service Provider Binding: Post
You can of course use a custom signing certificate, and adjust durations.
Application:
- Launch URL: 'https://fgm.company/p/sso_sp/'
## FortiManager Configuration
Navigate to `https://fgm.company/p/app/#!/sys/sso_settings` and select SAML SSO settings to configure SAML.
Select 'Service Provider (SP)' under Single Sign-On Mode to enable SAML authentication.
Set the Field 'SP Address' to the FortiManager FQDN 'fgm.company'. (This gives you the URLs to configure in Authentik)
Set the Default Login Page to either 'Normal' or 'Single-Sign On'. (Normal allows both local and SAML authentication vs only SAML SSO)
FortiManager create a new user by default if one does not exist so you will need to set the Default Admin Profile to the permissions you want any new users to have. (We created a no_permissions profile to assign by default)
Set the Field 'IdP Type' to 'Custom'
Set the Field `IdP entity ID` to `https://authentik.company/application/saml/fgm/sso/binding/redirect/`.
Set the Field `IdP Login URL` to `https://authentik.company/application/saml/fgm/sso/binding/redirect/`.
Set the Field `IdP Logout URL` to `https://authentik.company/`
For the Field 'IdP Certificate" Import your Authentik cert. (Self Signed or real)

7394
website/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,11 +12,11 @@
"serve": "docusaurus serve" "serve": "docusaurus serve"
}, },
"dependencies": { "dependencies": {
"@docusaurus/plugin-client-redirects": "2.0.0-beta.9", "@docusaurus/plugin-client-redirects": "2.0.0-beta.13",
"@docusaurus/preset-classic": "2.0.0-beta.9", "@docusaurus/preset-classic": "2.0.0-beta.13",
"@mdx-js/react": "^1.6.22", "@mdx-js/react": "^1.6.22",
"clsx": "^1.1.1", "clsx": "^1.1.1",
"postcss": "^8.4.4", "postcss": "^8.4.5",
"rapidoc": "^9.1.3", "rapidoc": "^9.1.3",
"react": "^17.0.2", "react": "^17.0.2",
"react-before-after-slider-component": "^1.1.1", "react-before-after-slider-component": "^1.1.1",

View File

@ -31,6 +31,7 @@ module.exports = {
"services/awx-tower/index", "services/awx-tower/index",
"services/bookstack/index", "services/bookstack/index",
"services/budibase/index", "services/budibase/index",
"services/fortimanager/index",
"services/gitea/index", "services/gitea/index",
"services/gitlab/index", "services/gitlab/index",
"services/grafana/index", "services/grafana/index",