mirror of
https://github.com/goauthentik/authentik
synced 2026-05-05 22:52:42 +02:00
Compare commits
42 Commits
docs/invit
...
2025-12/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b8e057402 | ||
|
|
e263af2dd9 | ||
|
|
3a59911a2b | ||
|
|
bbf31e99c3 | ||
|
|
9d5bd42f3e | ||
|
|
e721dae6da | ||
|
|
af3106b144 | ||
|
|
5b55103575 | ||
|
|
ee4ecf929f | ||
|
|
8336556a6f | ||
|
|
709aad1d3b | ||
|
|
fb7ab4937c | ||
|
|
5df1726d80 | ||
|
|
9fdb568843 | ||
|
|
8e76f56f89 | ||
|
|
05d3791577 | ||
|
|
d00dd7eb90 | ||
|
|
8d2e404017 | ||
|
|
95eb2af25e | ||
|
|
cbc00a501b | ||
|
|
480645d897 | ||
|
|
997c767c95 | ||
|
|
5a54e1dc9a | ||
|
|
49b1952566 | ||
|
|
e73edc2fce | ||
|
|
409652e874 | ||
|
|
1d3fb6431f | ||
|
|
76cfada60f | ||
|
|
ac45f80551 | ||
|
|
5ea85f086a | ||
|
|
e3f657746c | ||
|
|
001b56e2cc | ||
|
|
ecbfd2f0de | ||
|
|
45753397e1 | ||
|
|
dc6fe1dafe | ||
|
|
d5e8f2f416 | ||
|
|
d73af5a2b4 | ||
|
|
7042f2bba8 | ||
|
|
efeb260fa8 | ||
|
|
29e90092ea | ||
|
|
0abe865023 | ||
|
|
220c65a41a |
4
.github/workflows/release-tag.yml
vendored
4
.github/workflows/release-tag.yml
vendored
@@ -49,8 +49,12 @@ jobs:
|
||||
test:
|
||||
name: Pre-release test
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- check-inputs
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5
|
||||
with:
|
||||
ref: "version-${{ needs.check-inputs.outputs.major_version }}"
|
||||
- run: make test-docker
|
||||
bump-authentik:
|
||||
name: Bump authentik version
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from functools import lru_cache
|
||||
from os import environ
|
||||
|
||||
VERSION = "2025.12.0-rc1"
|
||||
VERSION = "2025.12.0-rc2"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class VersionSerializer(PassiveSerializer):
|
||||
|
||||
def get_version_latest(self, _) -> str:
|
||||
"""Get latest version from cache"""
|
||||
if get_current_tenant().schema_name == get_public_schema_name():
|
||||
if get_current_tenant().schema_name != get_public_schema_name():
|
||||
return authentik_version()
|
||||
version_in_cache = cache.get(VERSION_CACHE_KEY)
|
||||
if not version_in_cache: # pragma: no cover
|
||||
|
||||
@@ -240,7 +240,9 @@ class FileUsedByView(APIView):
|
||||
for field in fields:
|
||||
q |= Q(**{field: params.get("name")})
|
||||
|
||||
objs = get_objects_for_user(request.user, f"{app}.view_{model_name}", model)
|
||||
objs = get_objects_for_user(
|
||||
request.user, f"{app}.view_{model_name}", model.objects.all()
|
||||
)
|
||||
objs = objs.filter(q)
|
||||
for obj in objs:
|
||||
serializer = UsedBySerializer(
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from authentik.blueprints.apps import ManagedAppConfig
|
||||
from authentik.lib.config import CONFIG
|
||||
|
||||
|
||||
class AuthentikFilesConfig(ManagedAppConfig):
|
||||
@@ -11,20 +6,3 @@ class AuthentikFilesConfig(ManagedAppConfig):
|
||||
label = "authentik_admin_files"
|
||||
verbose_name = "authentik Files"
|
||||
default = True
|
||||
|
||||
@ManagedAppConfig.reconcile_global
|
||||
def check_for_media_mount(self):
|
||||
if settings.TEST:
|
||||
return
|
||||
|
||||
from authentik.events.models import Event, EventAction
|
||||
|
||||
if (
|
||||
CONFIG.get("storage.media.backend", CONFIG.get("storage.backend", "file")) == "file"
|
||||
and Path("/media").exists()
|
||||
):
|
||||
Event.new(
|
||||
EventAction.CONFIGURATION_ERROR,
|
||||
message="/media has been moved to /data/media. "
|
||||
"Check the release notes for migration steps.",
|
||||
).save()
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
from unittest import skipUnless
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from authentik.admin.files.tests.utils import FileTestS3BackendMixin
|
||||
from authentik.admin.files.tests.utils import FileTestS3BackendMixin, s3_test_server_available
|
||||
from authentik.admin.files.usage import FileUsage
|
||||
from authentik.lib.config import CONFIG
|
||||
|
||||
|
||||
@skipUnless(s3_test_server_available(), "S3 test server not available")
|
||||
class TestS3Backend(FileTestS3BackendMixin, TestCase):
|
||||
"""Test S3 backend functionality"""
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
"""Test file service layer"""
|
||||
|
||||
from unittest import skipUnless
|
||||
|
||||
from django.http import HttpRequest
|
||||
from django.test import TestCase
|
||||
|
||||
from authentik.admin.files.manager import FileManager
|
||||
from authentik.admin.files.tests.utils import FileTestFileBackendMixin, FileTestS3BackendMixin
|
||||
from authentik.admin.files.tests.utils import (
|
||||
FileTestFileBackendMixin,
|
||||
FileTestS3BackendMixin,
|
||||
s3_test_server_available,
|
||||
)
|
||||
from authentik.admin.files.usage import FileUsage
|
||||
from authentik.lib.config import CONFIG
|
||||
|
||||
@@ -81,6 +87,7 @@ class TestResolveFileUrlFileBackend(FileTestFileBackendMixin, TestCase):
|
||||
self.assertEqual(result, "http://example.com/files/media/public/test.png")
|
||||
|
||||
|
||||
@skipUnless(s3_test_server_available(), "S3 test server not available")
|
||||
class TestResolveFileUrlS3Backend(FileTestS3BackendMixin, TestCase):
|
||||
@CONFIG.patch("storage.media.s3.custom_domain", "s3.test:8080/test")
|
||||
@CONFIG.patch("storage.media.s3.secure_urls", False)
|
||||
|
||||
@@ -1,11 +1,26 @@
|
||||
import shutil
|
||||
import socket
|
||||
from tempfile import mkdtemp
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from authentik.admin.files.backends.s3 import S3Backend
|
||||
from authentik.admin.files.usage import FileUsage
|
||||
from authentik.lib.config import CONFIG, UNSET
|
||||
from authentik.lib.generators import generate_id
|
||||
|
||||
S3_TEST_ENDPOINT = "http://localhost:8020"
|
||||
|
||||
|
||||
def s3_test_server_available() -> bool:
|
||||
"""Check if the S3 test server is reachable."""
|
||||
|
||||
parsed = urlparse(S3_TEST_ENDPOINT)
|
||||
try:
|
||||
with socket.create_connection((parsed.hostname, parsed.port), timeout=2):
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
class FileTestFileBackendMixin:
|
||||
def setUp(self):
|
||||
@@ -57,7 +72,7 @@ class FileTestS3BackendMixin:
|
||||
for key in s3_config_keys:
|
||||
self.original_media_s3_settings[key] = CONFIG.get(f"storage.media.s3.{key}", UNSET)
|
||||
self.media_s3_bucket_name = f"authentik-test-{generate_id(10)}".lower()
|
||||
CONFIG.set("storage.media.s3.endpoint", "http://localhost:8020")
|
||||
CONFIG.set("storage.media.s3.endpoint", S3_TEST_ENDPOINT)
|
||||
CONFIG.set("storage.media.s3.access_key", "accessKey1")
|
||||
CONFIG.set("storage.media.s3.secret_key", "secretKey1")
|
||||
CONFIG.set("storage.media.s3.bucket_name", self.media_s3_bucket_name)
|
||||
@@ -70,7 +85,7 @@ class FileTestS3BackendMixin:
|
||||
for key in s3_config_keys:
|
||||
self.original_reports_s3_settings[key] = CONFIG.get(f"storage.reports.s3.{key}", UNSET)
|
||||
self.reports_s3_bucket_name = f"authentik-test-{generate_id(10)}".lower()
|
||||
CONFIG.set("storage.reports.s3.endpoint", "http://localhost:8020")
|
||||
CONFIG.set("storage.reports.s3.endpoint", S3_TEST_ENDPOINT)
|
||||
CONFIG.set("storage.reports.s3.access_key", "accessKey1")
|
||||
CONFIG.set("storage.reports.s3.secret_key", "secretKey1")
|
||||
CONFIG.set("storage.reports.s3.bucket_name", self.reports_s3_bucket_name)
|
||||
|
||||
@@ -15,7 +15,9 @@ class Pagination(pagination.PageNumberPagination):
|
||||
|
||||
def get_page_size(self, request):
|
||||
if self.page_size_query_param in request.query_params:
|
||||
return min(super().get_page_size(request), request.tenant.pagination_max_page_size)
|
||||
page_size = super().get_page_size(request)
|
||||
if page_size is not None:
|
||||
return min(super().get_page_size(request), request.tenant.pagination_max_page_size)
|
||||
return request.tenant.pagination_default_page_size
|
||||
|
||||
def get_paginated_response(self, data):
|
||||
|
||||
@@ -180,10 +180,10 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
|
||||
)
|
||||
|
||||
def _filter_applications_with_launch_url(
|
||||
self, applications: QuerySet[Application]
|
||||
self, paginated_apps: QuerySet[Application]
|
||||
) -> list[Application]:
|
||||
applications = []
|
||||
for app in applications:
|
||||
for app in paginated_apps:
|
||||
if app.get_launch_url():
|
||||
applications.append(app)
|
||||
return applications
|
||||
|
||||
@@ -33,6 +33,16 @@ from authentik.endpoints.connectors.agent.auth import AgentAuth
|
||||
from authentik.rbac.api.roles import RoleSerializer
|
||||
from authentik.rbac.decorators import permission_required
|
||||
|
||||
PARTIAL_USER_SERIALIZER_MODEL_FIELDS = [
|
||||
"pk",
|
||||
"username",
|
||||
"name",
|
||||
"is_active",
|
||||
"last_login",
|
||||
"email",
|
||||
"attributes",
|
||||
]
|
||||
|
||||
|
||||
class PartialUserSerializer(ModelSerializer):
|
||||
"""Partial User Serializer, does not include child relations."""
|
||||
@@ -42,16 +52,7 @@ class PartialUserSerializer(ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
"pk",
|
||||
"username",
|
||||
"name",
|
||||
"is_active",
|
||||
"last_login",
|
||||
"email",
|
||||
"attributes",
|
||||
"uid",
|
||||
]
|
||||
fields = PARTIAL_USER_SERIALIZER_MODEL_FIELDS + ["uid"]
|
||||
|
||||
|
||||
class RelatedGroupSerializer(ModelSerializer):
|
||||
@@ -262,7 +263,14 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
|
||||
base_qs = Group.objects.all().prefetch_related("roles")
|
||||
|
||||
if self.serializer_class(context={"request": self.request})._should_include_users:
|
||||
base_qs = base_qs.prefetch_related("users")
|
||||
# Only fetch fields needed by PartialUserSerializer to reduce DB load and instantiation
|
||||
# time
|
||||
base_qs = base_qs.prefetch_related(
|
||||
Prefetch(
|
||||
"users",
|
||||
queryset=User.objects.all().only(*PARTIAL_USER_SERIALIZER_MODEL_FIELDS),
|
||||
)
|
||||
)
|
||||
else:
|
||||
base_qs = base_qs.prefetch_related(
|
||||
Prefetch("users", queryset=User.objects.all().only("id"))
|
||||
|
||||
@@ -18,10 +18,9 @@ def migrate_object_permissions(apps: Apps, schema_editor: BaseDatabaseSchemaEdit
|
||||
RoleModelPermission = apps.get_model("guardian", "RoleModelPermission")
|
||||
|
||||
def get_role_for_user_id(user_id: int) -> Role:
|
||||
name = f"ak-managed-role--user-{user_id}"
|
||||
name = f"ak-migrated-role--user-{user_id}"
|
||||
role, created = Role.objects.using(db_alias).get_or_create(
|
||||
name=name,
|
||||
managed=name,
|
||||
)
|
||||
if created:
|
||||
role.users.add(user_id)
|
||||
@@ -32,11 +31,10 @@ def migrate_object_permissions(apps: Apps, schema_editor: BaseDatabaseSchemaEdit
|
||||
if not role:
|
||||
# Every django group should already have a role, so this should never happen.
|
||||
# But let's be nice.
|
||||
name = f"ak-managed-role--group-{group_id}"
|
||||
name = f"ak-migrated-role--group-{group_id}"
|
||||
role, created = Role.objects.using(db_alias).get_or_create(
|
||||
group_id=group_id,
|
||||
name=name,
|
||||
managed=name,
|
||||
)
|
||||
if created:
|
||||
role.group_id = group_id
|
||||
|
||||
@@ -86,7 +86,7 @@ class OutpostConfig:
|
||||
class OutpostModel(Model):
|
||||
"""Base model for providers that need more objects than just themselves"""
|
||||
|
||||
def get_required_objects(self) -> Iterable[models.Model | str]:
|
||||
def get_required_objects(self) -> Iterable[models.Model | str | tuple[str, models.Model]]:
|
||||
"""Return a list of all required objects"""
|
||||
return [self]
|
||||
|
||||
@@ -332,41 +332,35 @@ class Outpost(ScheduledModel, SerializerModel, ManagedModel):
|
||||
"""Create per-object and global permissions for outpost service-account"""
|
||||
# To ensure the user only has the correct permissions, we delete all of them and re-add
|
||||
# the ones the user needs
|
||||
with transaction.atomic():
|
||||
user.remove_all_perms_from_managed_role()
|
||||
for model_or_perm in self.get_required_objects():
|
||||
if isinstance(model_or_perm, models.Model):
|
||||
model_or_perm: models.Model
|
||||
code_name = (
|
||||
f"{model_or_perm._meta.app_label}.view_{model_or_perm._meta.model_name}"
|
||||
)
|
||||
try:
|
||||
user.assign_perms_to_managed_role(code_name, model_or_perm)
|
||||
except (Permission.DoesNotExist, AttributeError) as exc:
|
||||
LOGGER.warning(
|
||||
"permission doesn't exist",
|
||||
code_name=code_name,
|
||||
user=user,
|
||||
model=model_or_perm,
|
||||
try:
|
||||
with transaction.atomic():
|
||||
user.remove_all_perms_from_managed_role()
|
||||
for model_or_perm in self.get_required_objects():
|
||||
if isinstance(model_or_perm, models.Model):
|
||||
code_name = (
|
||||
f"{model_or_perm._meta.app_label}.view_{model_or_perm._meta.model_name}"
|
||||
)
|
||||
Event.new(
|
||||
action=EventAction.SYSTEM_EXCEPTION,
|
||||
message=(
|
||||
"While setting the permissions for the service-account, a "
|
||||
"permission was not found: Check "
|
||||
"https://docs.goauthentik.io/troubleshooting/missing_permission"
|
||||
),
|
||||
).with_exception(exc).set_user(user).save()
|
||||
else:
|
||||
app_label, perm = model_or_perm.split(".")
|
||||
permission = Permission.objects.filter(
|
||||
codename=perm,
|
||||
content_type__app_label=app_label,
|
||||
)
|
||||
if not permission.exists():
|
||||
LOGGER.warning("permission doesn't exist", perm=model_or_perm)
|
||||
continue
|
||||
user.assign_perms_to_managed_role(permission.first())
|
||||
user.assign_perms_to_managed_role(code_name, model_or_perm)
|
||||
elif isinstance(model_or_perm, tuple):
|
||||
perm, obj = model_or_perm
|
||||
user.assign_perms_to_managed_role(perm, obj)
|
||||
else:
|
||||
user.assign_perms_to_managed_role(model_or_perm)
|
||||
except (Permission.DoesNotExist, AttributeError) as exc:
|
||||
LOGGER.warning(
|
||||
"permission doesn't exist",
|
||||
code_name=code_name,
|
||||
user=user,
|
||||
model=model_or_perm,
|
||||
)
|
||||
Event.new(
|
||||
action=EventAction.SYSTEM_EXCEPTION,
|
||||
message=(
|
||||
"While setting the permissions for the service-account, a "
|
||||
"permission was not found: Check "
|
||||
"https://docs.goauthentik.io/troubleshooting/missing_permission"
|
||||
),
|
||||
).with_exception(exc).set_user(user).save()
|
||||
LOGGER.debug(
|
||||
"Updated service account's permissions",
|
||||
obj_perms=user.get_all_obj_perms_on_managed_role(),
|
||||
@@ -431,7 +425,7 @@ class Outpost(ScheduledModel, SerializerModel, ManagedModel):
|
||||
Token.objects.filter(identifier=self.token_identifier).delete()
|
||||
return self.token
|
||||
|
||||
def get_required_objects(self) -> Iterable[models.Model | str]:
|
||||
def get_required_objects(self) -> Iterable[models.Model | str | tuple[str, models.Model]]:
|
||||
"""Get an iterator of all objects the user needs read access to"""
|
||||
objects: list[models.Model | str] = [
|
||||
self,
|
||||
@@ -445,7 +439,9 @@ class Outpost(ScheduledModel, SerializerModel, ManagedModel):
|
||||
if self.managed:
|
||||
for brand in Brand.objects.filter(web_certificate__isnull=False):
|
||||
objects.append(brand)
|
||||
objects.append(brand.web_certificate)
|
||||
objects.append(("view_certificatekeypair", brand.web_certificate))
|
||||
objects.append(("view_certificatekeypair_certificate", brand.web_certificate))
|
||||
objects.append(("view_certificatekeypair_key", brand.web_certificate))
|
||||
return objects
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
@@ -51,10 +51,12 @@ class OutpostTests(TestCase):
|
||||
permissions = outpost.user.get_all_obj_perms_on_managed_role().order_by(
|
||||
"content_type__model"
|
||||
)
|
||||
self.assertEqual(len(permissions), 3)
|
||||
self.assertEqual(len(permissions), 5)
|
||||
self.assertEqual(permissions[0].object_pk, str(keypair.pk))
|
||||
self.assertEqual(permissions[1].object_pk, str(outpost.pk))
|
||||
self.assertEqual(permissions[2].object_pk, str(provider.pk))
|
||||
self.assertEqual(permissions[1].object_pk, str(keypair.pk))
|
||||
self.assertEqual(permissions[2].object_pk, str(keypair.pk))
|
||||
self.assertEqual(permissions[3].object_pk, str(outpost.pk))
|
||||
self.assertEqual(permissions[4].object_pk, str(provider.pk))
|
||||
|
||||
# Remove provider from outpost, user should only have access to outpost
|
||||
outpost.providers.remove(provider)
|
||||
|
||||
@@ -93,11 +93,13 @@ class LDAPProvider(OutpostModel, BackchannelProvider):
|
||||
def __str__(self):
|
||||
return f"LDAP Provider {self.name}"
|
||||
|
||||
def get_required_objects(self) -> Iterable[models.Model | str]:
|
||||
required_models = [self, "authentik_core.view_user", "authentik_core.view_group"]
|
||||
def get_required_objects(self) -> Iterable[models.Model | str | tuple[str, models.Model]]:
|
||||
required = [self, "authentik_core.view_user", "authentik_core.view_group"]
|
||||
if self.certificate is not None:
|
||||
required_models.append(self.certificate)
|
||||
return required_models
|
||||
required.append(("view_certificatekeypair", self.certificate))
|
||||
required.append(("view_certificatekeypair_certificate", self.certificate))
|
||||
required.append(("view_certificatekeypair_key", self.certificate))
|
||||
return required
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("LDAP Provider")
|
||||
|
||||
@@ -179,11 +179,13 @@ class ProxyProvider(OutpostModel, OAuth2Provider):
|
||||
def __str__(self):
|
||||
return f"Proxy Provider {self.name}"
|
||||
|
||||
def get_required_objects(self) -> Iterable[models.Model | str]:
|
||||
required_models = [self]
|
||||
def get_required_objects(self) -> Iterable[models.Model | str | tuple[str, models.Model]]:
|
||||
required = [self]
|
||||
if self.certificate is not None:
|
||||
required_models.append(self.certificate)
|
||||
return required_models
|
||||
required.append(("view_certificatekeypair", self.certificate))
|
||||
required.append(("view_certificatekeypair_certificate", self.certificate))
|
||||
required.append(("view_certificatekeypair_key", self.certificate))
|
||||
return required
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Proxy Provider")
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
"""proxy provider tests"""
|
||||
|
||||
from json import loads
|
||||
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.outposts.models import Outpost, OutpostType
|
||||
from authentik.providers.oauth2.models import ClientTypes
|
||||
from authentik.providers.proxy.models import ProxyMode, ProxyProvider
|
||||
|
||||
@@ -127,3 +131,55 @@ class ProxyProviderTests(APITestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
provider: ProxyProvider = ProxyProvider.objects.get(name=name)
|
||||
self.assertEqual(provider.client_type, ClientTypes.CONFIDENTIAL)
|
||||
|
||||
def test_sa_fetch(self):
|
||||
"""Test fetching the outpost config as the service account"""
|
||||
outpost = Outpost.objects.create(name=generate_id(), type=OutpostType.PROXY)
|
||||
provider = ProxyProvider.objects.create(name=generate_id())
|
||||
Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider)
|
||||
outpost.providers.add(provider)
|
||||
|
||||
res = self.client.get(
|
||||
reverse("authentik_api:proxyprovideroutpost-list"),
|
||||
HTTP_AUTHORIZATION=f"Bearer {outpost.token.key}",
|
||||
)
|
||||
body = loads(res.content)
|
||||
self.assertEqual(body["pagination"]["count"], 1)
|
||||
|
||||
def test_sa_perms_cert(self):
|
||||
"""Test permissions to access a configured certificate"""
|
||||
cert = create_test_cert()
|
||||
outpost = Outpost.objects.create(name=generate_id(), type=OutpostType.PROXY)
|
||||
provider = ProxyProvider.objects.create(name=generate_id(), certificate=cert)
|
||||
Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider)
|
||||
outpost.providers.add(provider)
|
||||
|
||||
res = self.client.get(
|
||||
reverse("authentik_api:proxyprovideroutpost-list"),
|
||||
HTTP_AUTHORIZATION=f"Bearer {outpost.token.key}",
|
||||
)
|
||||
body = loads(res.content)
|
||||
self.assertEqual(body["pagination"]["count"], 1)
|
||||
cert_id = body["results"][0]["certificate"]
|
||||
self.assertEqual(cert_id, str(cert.pk))
|
||||
|
||||
res = self.client.get(
|
||||
reverse(
|
||||
"authentik_api:certificatekeypair-view-certificate",
|
||||
kwargs={
|
||||
"pk": cert_id,
|
||||
},
|
||||
),
|
||||
HTTP_AUTHORIZATION=f"Bearer {outpost.token.key}",
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
# res = self.client.get(
|
||||
# reverse(
|
||||
# "authentik_api:certificatekeypair-view-private-key",
|
||||
# kwargs={
|
||||
# "pk": cert_id,
|
||||
# },
|
||||
# ),
|
||||
# HTTP_AUTHORIZATION=f"Bearer {outpost.token.key}",
|
||||
# )
|
||||
# self.assertEqual(res.status_code, 200)
|
||||
|
||||
@@ -64,10 +64,12 @@ class RadiusProvider(OutpostModel, Provider):
|
||||
|
||||
return RadiusProviderSerializer
|
||||
|
||||
def get_required_objects(self) -> Iterable[models.Model | str]:
|
||||
def get_required_objects(self) -> Iterable[models.Model | str | tuple[str, models.Model]]:
|
||||
required = [self, "authentik_stages_mtls.pass_outpost_certificate"]
|
||||
if self.certificate is not None:
|
||||
required.append(self.certificate)
|
||||
required.append(("view_certificatekeypair", self.certificate))
|
||||
required.append(("view_certificatekeypair_certificate", self.certificate))
|
||||
required.append(("view_certificatekeypair_key", self.certificate))
|
||||
return required
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -6,7 +6,7 @@ from django.http.request import QueryDict
|
||||
from django.template.exceptions import TemplateSyntaxError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import BooleanField, CharField, IntegerField
|
||||
from rest_framework.fields import BooleanField, CharField
|
||||
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.flows.challenge import (
|
||||
@@ -47,7 +47,7 @@ class AuthenticatorEmailChallengeResponse(ChallengeResponse):
|
||||
|
||||
device: EmailDevice
|
||||
|
||||
code = IntegerField(required=False)
|
||||
code = CharField(required=False)
|
||||
email = CharField(required=False)
|
||||
|
||||
component = CharField(default="ak-stage-authenticator-email")
|
||||
|
||||
@@ -5,7 +5,7 @@ from django.http import HttpRequest, HttpResponse
|
||||
from django.http.request import QueryDict
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import BooleanField, CharField, IntegerField
|
||||
from rest_framework.fields import BooleanField, CharField
|
||||
|
||||
from authentik.flows.challenge import (
|
||||
Challenge,
|
||||
@@ -38,7 +38,7 @@ class AuthenticatorSMSChallengeResponse(ChallengeResponse):
|
||||
|
||||
device: SMSDevice
|
||||
|
||||
code = IntegerField(required=False)
|
||||
code = CharField(required=False)
|
||||
phone_number = CharField(required=False)
|
||||
|
||||
component = CharField(default="ak-stage-authenticator-sms")
|
||||
|
||||
@@ -5,7 +5,7 @@ from urllib.parse import quote
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.http.request import QueryDict
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.fields import CharField, IntegerField
|
||||
from rest_framework.fields import CharField
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
from authentik.flows.challenge import (
|
||||
@@ -32,10 +32,10 @@ class AuthenticatorTOTPChallengeResponse(ChallengeResponse):
|
||||
|
||||
device: TOTPDevice
|
||||
|
||||
code = IntegerField()
|
||||
code = CharField()
|
||||
component = CharField(default="ak-stage-authenticator-totp")
|
||||
|
||||
def validate_code(self, code: int) -> int:
|
||||
def validate_code(self, code: str) -> str:
|
||||
"""Validate totp code"""
|
||||
if not self.device:
|
||||
raise ValidationError(_("Code does not match"))
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
"""Identification stage logic"""
|
||||
|
||||
from dataclasses import asdict
|
||||
from random import SystemRandom
|
||||
from time import sleep
|
||||
from typing import Any
|
||||
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponse
|
||||
@@ -161,8 +160,8 @@ class IdentificationChallengeResponse(ChallengeResponse):
|
||||
op="authentik.stages.identification.validate_invalid_wait",
|
||||
name="Sleep random time on invalid user identifier",
|
||||
):
|
||||
# Sleep a random time (between 90 and 210ms) to "prevent" user enumeration attacks
|
||||
sleep(0.030 * SystemRandom().randint(3, 7))
|
||||
# hash a random password on invalid identifier, same as with a valid identifier
|
||||
make_password(make_password(None))
|
||||
# Log in a similar format to Event.new(), but we don't want to create an event here
|
||||
# as this stage is mostly used by unauthenticated users with very high rate limits
|
||||
self.stage.logger.info(
|
||||
|
||||
@@ -245,7 +245,10 @@ class WorkerStatusMiddleware(Middleware):
|
||||
WorkerStatusMiddleware.keep(status)
|
||||
except DB_ERRORS: # pragma: no cover
|
||||
sleep(10)
|
||||
pass
|
||||
try:
|
||||
connections.close_all()
|
||||
except DB_ERRORS:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def keep(status: WorkerStatus):
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||
"type": "object",
|
||||
"title": "authentik 2025.12.0-rc1 Blueprint schema",
|
||||
"title": "authentik 2025.12.0-rc2 Blueprint schema",
|
||||
"required": [
|
||||
"version",
|
||||
"entries"
|
||||
@@ -14510,7 +14510,8 @@
|
||||
"description": "Show the user the 'Remember me on this device' toggle, allowing repeat users to skip straight to entering their password."
|
||||
},
|
||||
"webauthn_stage": {
|
||||
"type": "integer",
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Webauthn stage",
|
||||
"description": "When set, and conditional WebAuthn is available, allow the user to use their passkey as a first factor."
|
||||
}
|
||||
|
||||
@@ -31,13 +31,13 @@ services:
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
||||
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
|
||||
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.12.0-rc1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.12.0-rc2}
|
||||
ports:
|
||||
- ${COMPOSE_PORT_HTTP:-9000}:9000
|
||||
- ${COMPOSE_PORT_HTTPS:-9443}:9443
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./media:/data/media
|
||||
- ./data:/data
|
||||
- ./custom-templates:/templates
|
||||
worker:
|
||||
command: worker
|
||||
@@ -52,12 +52,12 @@ services:
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
||||
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
|
||||
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.12.0-rc1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.12.0-rc2}
|
||||
restart: unless-stopped
|
||||
user: root
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./media:/data/media
|
||||
- ./data:/data
|
||||
- ./certs:/certs
|
||||
- ./custom-templates:/templates
|
||||
volumes:
|
||||
|
||||
@@ -1 +1 @@
|
||||
2025.12.0-rc1
|
||||
2025.12.0-rc2
|
||||
@@ -18,7 +18,7 @@ Parameters:
|
||||
Description: authentik Docker image
|
||||
AuthentikVersion:
|
||||
Type: String
|
||||
Default: 2025.12.0-rc1
|
||||
Default: 2025.12.0-rc2
|
||||
Description: authentik Docker image tag
|
||||
AuthentikServerCPU:
|
||||
Type: Number
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.12.0-rc1",
|
||||
"version": "2025.12.0-rc2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.12.0-rc1",
|
||||
"version": "2025.12.0-rc2",
|
||||
"dependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@goauthentik/eslint-config": "./packages/eslint-config",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.12.0-rc1",
|
||||
"version": "2025.12.0-rc2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
"""Convenient shortcuts to manage or check object permissions."""
|
||||
|
||||
from functools import lru_cache, partial
|
||||
from functools import lru_cache
|
||||
from typing import Any, TypeVar
|
||||
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import connection
|
||||
from django.db.models import (
|
||||
AutoField,
|
||||
BigIntegerField,
|
||||
CharField,
|
||||
Count,
|
||||
ForeignKey,
|
||||
IntegerField,
|
||||
Model,
|
||||
PositiveIntegerField,
|
||||
PositiveSmallIntegerField,
|
||||
QuerySet,
|
||||
SmallIntegerField,
|
||||
UUIDField,
|
||||
)
|
||||
from django.db.models.expressions import Value
|
||||
from django.db.models.functions import Cast, Replace
|
||||
from django.db.models.expressions import RawSQL
|
||||
|
||||
from guardian.core import ObjectPermissionChecker
|
||||
from guardian.ctypes import get_content_type
|
||||
@@ -295,42 +286,33 @@ def get_objects_for_user( # noqa: PLR0912 PLR0915
|
||||
.filter(object_pk_count__gte=len(codenames))
|
||||
)
|
||||
|
||||
# object_pk is a varchar, while the queryset's pk is probably an integer or a uuid, so we cast
|
||||
handle_pk_field = _handle_pk_field(queryset)
|
||||
if handle_pk_field is not None:
|
||||
perms_queryset = perms_queryset.annotate(obj_pk=handle_pk_field(expression=pk_field))
|
||||
pk_field = "obj_pk"
|
||||
|
||||
return queryset.filter(pk__in=perms_queryset.values_list(pk_field, flat=True))
|
||||
|
||||
|
||||
def _handle_pk_field(queryset):
|
||||
# pk is either UUID or an integer type, while object_pk is a varchar
|
||||
pk = queryset.model._meta.pk
|
||||
|
||||
if isinstance(pk, ForeignKey):
|
||||
return _handle_pk_field(pk.target_field)
|
||||
def _cast_type(pk):
|
||||
if isinstance(pk, ForeignKey):
|
||||
return _cast_type(pk.target_field)
|
||||
if isinstance(pk, UUIDField):
|
||||
return "uuid"
|
||||
return "bigint"
|
||||
|
||||
if isinstance( # noqa: UP038
|
||||
pk,
|
||||
(
|
||||
IntegerField,
|
||||
AutoField,
|
||||
BigIntegerField,
|
||||
PositiveIntegerField,
|
||||
PositiveSmallIntegerField,
|
||||
SmallIntegerField,
|
||||
),
|
||||
):
|
||||
return partial(Cast, output_field=BigIntegerField())
|
||||
cast_type = _cast_type(pk)
|
||||
|
||||
if isinstance(pk, UUIDField):
|
||||
if connection.features.has_native_uuid_field:
|
||||
return partial(Cast, output_field=UUIDField())
|
||||
return partial(
|
||||
Replace,
|
||||
text=Value("-"),
|
||||
replacement=Value(""),
|
||||
output_field=CharField(),
|
||||
)
|
||||
|
||||
return None
|
||||
perms_queryset = perms_queryset.values_list(pk_field, flat=True)
|
||||
# The raw subquery is done to ensure that casting only takes place after the WHERE clause of
|
||||
# `perms_queryset` is ran. Otherwise, the query planner may decide to cast every `object_pk`,
|
||||
# which breaks (for example) if it tries to cast an integer to a UUID. In such a case, the WHERE
|
||||
# of `perms_queryset` will remove any integer.
|
||||
# However, the subquery might get optimized out by the query planner, which would cause the same
|
||||
# cast issue as before. To prevent the subquery from being collapsed in the query below, we add
|
||||
# OFFSET 0.
|
||||
perms_subquery_sql, perms_subquery_params = perms_queryset.query.sql_with_params()
|
||||
subquery = RawSQL(
|
||||
f"""
|
||||
SELECT ("permission_subquery"."{pk_field}")::{cast_type} as "object_pk"
|
||||
FROM ({perms_subquery_sql}) "permission_subquery"
|
||||
OFFSET 0
|
||||
""", # nosec
|
||||
perms_subquery_params,
|
||||
)
|
||||
return queryset.filter(pk__in=subquery)
|
||||
|
||||
@@ -529,3 +529,7 @@ class _PostgresConsumer(Consumer):
|
||||
conn.close()
|
||||
except DATABASE_ERRORS:
|
||||
pass
|
||||
try:
|
||||
connections.close_all()
|
||||
except DATABASE_ERRORS:
|
||||
pass
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "authentik"
|
||||
version = "2025.12.0-rc1"
|
||||
version = "2025.12.0-rc2"
|
||||
description = ""
|
||||
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
|
||||
requires-python = "==3.13.*"
|
||||
|
||||
11
schema.yml
11
schema.yml
@@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: authentik
|
||||
version: 2025.12.0-rc1
|
||||
version: 2025.12.0-rc2
|
||||
description: Making authentication simple.
|
||||
contact:
|
||||
email: hello@goauthentik.io
|
||||
@@ -33586,7 +33586,8 @@ components:
|
||||
minLength: 1
|
||||
default: ak-stage-authenticator-email
|
||||
code:
|
||||
type: integer
|
||||
type: string
|
||||
minLength: 1
|
||||
email:
|
||||
type: string
|
||||
minLength: 1
|
||||
@@ -33833,7 +33834,8 @@ components:
|
||||
minLength: 1
|
||||
default: ak-stage-authenticator-sms
|
||||
code:
|
||||
type: integer
|
||||
type: string
|
||||
minLength: 1
|
||||
phone_number:
|
||||
type: string
|
||||
minLength: 1
|
||||
@@ -34107,7 +34109,8 @@ components:
|
||||
minLength: 1
|
||||
default: ak-stage-authenticator-totp
|
||||
code:
|
||||
type: integer
|
||||
type: string
|
||||
minLength: 1
|
||||
required:
|
||||
- code
|
||||
AuthenticatorTOTPStage:
|
||||
|
||||
6
scripts/generate_docker_compose.py
Normal file → Executable file
6
scripts/generate_docker_compose.py
Normal file → Executable file
@@ -1,3 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from yaml import safe_dump
|
||||
|
||||
from authentik import authentik_version
|
||||
@@ -42,7 +44,7 @@ base = {
|
||||
"image": authentik_image,
|
||||
"ports": ["${COMPOSE_PORT_HTTP:-9000}:9000", "${COMPOSE_PORT_HTTPS:-9443}:9443"],
|
||||
"restart": "unless-stopped",
|
||||
"volumes": ["./media:/data/media", "./custom-templates:/templates"],
|
||||
"volumes": ["./data:/data", "./custom-templates:/templates"],
|
||||
},
|
||||
"worker": {
|
||||
"command": "worker",
|
||||
@@ -62,7 +64,7 @@ base = {
|
||||
"user": "root",
|
||||
"volumes": [
|
||||
"/var/run/docker.sock:/var/run/docker.sock",
|
||||
"./media:/data/media",
|
||||
"./data:/data",
|
||||
"./certs:/certs",
|
||||
"./custom-templates:/templates",
|
||||
],
|
||||
|
||||
@@ -23,8 +23,10 @@ from docker.models.containers import Container
|
||||
from docker.models.networks import Network
|
||||
from selenium import webdriver
|
||||
from selenium.common.exceptions import (
|
||||
DetachedShadowRootException,
|
||||
NoSuchElementException,
|
||||
NoSuchShadowRootException,
|
||||
StaleElementReferenceException,
|
||||
TimeoutException,
|
||||
WebDriverException,
|
||||
)
|
||||
@@ -247,36 +249,60 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
||||
Raises a clear test failure if the element isn't found, the text doesn't appear
|
||||
within `timeout` seconds, or the text is not valid JSON.
|
||||
"""
|
||||
use_body = context is None
|
||||
wait_timeout = timeout or self.wait_timeout
|
||||
|
||||
def get_context() -> WebElement:
|
||||
"""Get or refresh the context element."""
|
||||
if use_body:
|
||||
return self.driver.find_element(By.TAG_NAME, "body")
|
||||
return context
|
||||
|
||||
def get_text_safely() -> str:
|
||||
"""Get element text, re-finding element if stale."""
|
||||
for _ in range(5):
|
||||
try:
|
||||
return get_context().text.strip()
|
||||
except StaleElementReferenceException:
|
||||
sleep(0.5)
|
||||
return get_context().text.strip()
|
||||
|
||||
def get_inner_html_safely() -> str:
|
||||
"""Get innerHTML, re-finding element if stale."""
|
||||
for _ in range(5):
|
||||
try:
|
||||
return get_context().get_attribute("innerHTML") or ""
|
||||
except StaleElementReferenceException:
|
||||
sleep(0.5)
|
||||
return get_context().get_attribute("innerHTML") or ""
|
||||
|
||||
try:
|
||||
if context is None:
|
||||
context = self.driver.find_element(By.TAG_NAME, "body")
|
||||
get_context()
|
||||
except NoSuchElementException:
|
||||
self.fail(
|
||||
f"No element found (defaulted to <body>). Current URL: {self.driver.current_url}"
|
||||
)
|
||||
|
||||
wait_timeout = timeout or self.wait_timeout
|
||||
wait = WebDriverWait(context, wait_timeout)
|
||||
wait = WebDriverWait(self.driver, wait_timeout)
|
||||
|
||||
try:
|
||||
wait.until(lambda d: len(d.text.strip()) != 0)
|
||||
wait.until(lambda d: len(get_text_safely()) != 0)
|
||||
except TimeoutException:
|
||||
snippet = context.text.strip()[:500].replace("\n", " ")
|
||||
snippet = get_text_safely()[:500].replace("\n", " ")
|
||||
self.fail(
|
||||
f"Timed out waiting for element text to appear at {self.driver.current_url}. "
|
||||
f"Current content: {snippet or '<empty>'}"
|
||||
)
|
||||
|
||||
body_text = context.text.strip()
|
||||
inner_html = context.get_attribute("innerHTML") or ""
|
||||
body_text = get_text_safely()
|
||||
inner_html = get_inner_html_safely()
|
||||
|
||||
if "redirecting" in inner_html.lower():
|
||||
try:
|
||||
wait.until(lambda d: "redirecting" not in d.get_attribute("innerHTML").lower())
|
||||
wait.until(lambda d: "redirecting" not in get_inner_html_safely().lower())
|
||||
except TimeoutException:
|
||||
snippet = context.text.strip()[:500].replace("\n", " ")
|
||||
inner_html = context.get_attribute("innerHTML") or ""
|
||||
snippet = get_text_safely()[:500].replace("\n", " ")
|
||||
inner_html = get_inner_html_safely()
|
||||
|
||||
self.fail(
|
||||
f"Timed out waiting for redirect to finish at {self.driver.current_url}. "
|
||||
@@ -284,8 +310,8 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
||||
f"{inner_html or '<empty>'}"
|
||||
)
|
||||
|
||||
inner_html = context.get_attribute("innerHTML") or ""
|
||||
body_text = context.text.strip()
|
||||
inner_html = get_inner_html_safely()
|
||||
body_text = get_text_safely()
|
||||
|
||||
snippet = body_text[:500].replace("\n", " ")
|
||||
|
||||
@@ -326,18 +352,23 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
||||
|
||||
while attempts < SHADOW_ROOT_RETRIES:
|
||||
try:
|
||||
host = container.find_element(By.CSS_SELECTOR, selector)
|
||||
return host.shadow_root
|
||||
except NoSuchShadowRootException:
|
||||
except (
|
||||
NoSuchElementException,
|
||||
NoSuchShadowRootException,
|
||||
DetachedShadowRootException,
|
||||
StaleElementReferenceException,
|
||||
):
|
||||
attempts += 1
|
||||
sleep(0.2)
|
||||
# re-find host in case it was re-attached
|
||||
try:
|
||||
host = container.find_element(By.CSS_SELECTOR, selector)
|
||||
except NoSuchElementException:
|
||||
# loop and retry finding host
|
||||
pass
|
||||
|
||||
inner_html = host.get_attribute("innerHTML") or "<no host>"
|
||||
inner_html = "<no host>"
|
||||
if host is not None:
|
||||
try:
|
||||
inner_html = host.get_attribute("innerHTML") or "<no host>"
|
||||
except (DetachedShadowRootException, StaleElementReferenceException):
|
||||
inner_html = "<stale host>"
|
||||
|
||||
raise RuntimeError(
|
||||
f"Failed to obtain shadow root for {selector} after {attempts} attempts. "
|
||||
|
||||
2
uv.lock
generated
2
uv.lock
generated
@@ -185,7 +185,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "authentik"
|
||||
version = "2025.12.0rc1"
|
||||
version = "2025.12.0rc2"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "ak-guardian" },
|
||||
|
||||
4
web/package-lock.json
generated
4
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@goauthentik/web",
|
||||
"version": "2025.12.0-rc1",
|
||||
"version": "2025.12.0-rc2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@goauthentik/web",
|
||||
"version": "2025.12.0-rc1",
|
||||
"version": "2025.12.0-rc2",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"./packages/*"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@goauthentik/web",
|
||||
"version": "2025.12.0-rc1",
|
||||
"version": "2025.12.0-rc2",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -99,6 +99,9 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
|
||||
const alertMsg = msg(
|
||||
"Using this form will only create an Application. In order to authenticate with the application, you will have to manually pair it with a Provider.",
|
||||
);
|
||||
const providerFromInstance = this.instance?.provider;
|
||||
const providerValue = providerFromInstance ?? this.provider;
|
||||
const providerPrefilled = !this.instance && this.provider !== undefined;
|
||||
|
||||
return html`
|
||||
${this.instance ? nothing : html`<ak-alert level="pf-m-info">${alertMsg}</ak-alert>`}
|
||||
@@ -134,9 +137,10 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
|
||||
<ak-provider-search-input
|
||||
name="provider"
|
||||
label=${msg("Provider")}
|
||||
value=${ifPresent(this.instance?.provider)}
|
||||
.value=${providerValue}
|
||||
.readOnly=${providerPrefilled}
|
||||
?blankable=${!providerPrefilled}
|
||||
help=${msg("Select a provider that this application should use.")}
|
||||
blankable
|
||||
></ak-provider-search-input>
|
||||
<ak-backchannel-providers-input
|
||||
name="backchannelProviders"
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Provider, ProvidersAllListRequest, ProvidersApi } from "@goauthentik/ap
|
||||
|
||||
import { html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
const renderElement = (item: Provider) => item.name;
|
||||
const renderValue = (item: Provider | undefined) => item?.pk;
|
||||
@@ -53,6 +54,9 @@ export class AkProviderInput extends AKElement {
|
||||
@property({ type: Number })
|
||||
value?: number;
|
||||
|
||||
@property({ type: Boolean, attribute: "readonly" })
|
||||
readOnly = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
required = false;
|
||||
|
||||
@@ -76,6 +80,8 @@ export class AkProviderInput extends AKElement {
|
||||
};
|
||||
|
||||
render() {
|
||||
const readOnlyValue = this.readOnly && typeof this.value === "number";
|
||||
|
||||
return html` <ak-form-element-horizontal name=${this.name}>
|
||||
${AKLabel(
|
||||
{
|
||||
@@ -86,7 +92,9 @@ export class AkProviderInput extends AKElement {
|
||||
},
|
||||
this.label,
|
||||
)}
|
||||
|
||||
${readOnlyValue
|
||||
? html`<input type="hidden" name=${this.name} value=${this.value ?? ""} />`
|
||||
: nothing}
|
||||
<ak-search-select
|
||||
.fieldID=${this.fieldID}
|
||||
.selected=${this.#selected}
|
||||
@@ -94,7 +102,9 @@ export class AkProviderInput extends AKElement {
|
||||
.renderElement=${renderElement}
|
||||
.value=${renderValue}
|
||||
.groupBy=${doGroupBy}
|
||||
?blankable=${!!this.blankable}
|
||||
?blankable=${readOnlyValue ? false : !!this.blankable}
|
||||
?readonly=${this.readOnly}
|
||||
name=${ifDefined(readOnlyValue ? undefined : this.name)}
|
||||
>
|
||||
</ak-search-select>
|
||||
${this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing}
|
||||
|
||||
@@ -77,7 +77,11 @@ export class AgentConnectorSetup extends AKElement {
|
||||
<p>${msg("Afterwards, select the enrollment token you want to use:")}</p>
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-12-col">
|
||||
<p>${msg("Then download the configuration to deploy the authentik Agent")}</p>
|
||||
<p>
|
||||
${msg(
|
||||
"Next, download the configuration to deploy the authentik Agent via MDM",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-6-col pf-l-grid">
|
||||
|
||||
@@ -77,10 +77,13 @@ export class EnrollmentTokenForm extends WithBrandConfig(ModelForm<EnrollmentTok
|
||||
value=${ifDefined(this.instance?.name)}
|
||||
required
|
||||
></ak-text-input>
|
||||
<ak-form-element-horizontal label=${msg("Device Group")} name="deviceGroup">
|
||||
<ak-form-element-horizontal label=${msg("Device Access Group")} name="deviceGroup">
|
||||
<ak-endpoints-device-group-search
|
||||
.group=${this.instance?.deviceGroup}
|
||||
></ak-endpoints-device-group-search>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Select a device access group to be added to upon enrollment.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="expiring">
|
||||
<label class="pf-c-switch">
|
||||
|
||||
@@ -68,7 +68,7 @@ export class DeviceViewPage extends AKElement {
|
||||
? msg(str`Device ${this.device?.name}`)
|
||||
: msg("Loading device..."),
|
||||
description: this.device?.facts.data.os
|
||||
? this.device?.facts.data.os?.name + " " + this.device?.facts.data.os?.version
|
||||
? `${this.device?.facts.data.os?.name} ${this.device?.facts.data.os?.version}`
|
||||
: undefined,
|
||||
icon: "fa fa-laptop",
|
||||
});
|
||||
@@ -110,7 +110,7 @@ export class DeviceViewPage extends AKElement {
|
||||
?good=${this.device.facts.data.network?.firewallEnabled}
|
||||
></ak-status-label>`,
|
||||
],
|
||||
[msg("Group"), this.device.accessGroupObj?.name ?? "-"],
|
||||
[msg("Device access group"), this.device.accessGroupObj?.name ?? "-"],
|
||||
[
|
||||
msg("Actions"),
|
||||
html`<ak-forms-modal>
|
||||
@@ -162,13 +162,13 @@ export class DeviceViewPage extends AKElement {
|
||||
></ak-status-label>`,
|
||||
],
|
||||
[
|
||||
msg("Disk size"),
|
||||
msg("Primary disk size"),
|
||||
rootDisk?.capacityTotalBytes
|
||||
? getSize(rootDisk.capacityTotalBytes)
|
||||
: "-",
|
||||
],
|
||||
[
|
||||
msg("Disk usage"),
|
||||
msg("Primary disk usage"),
|
||||
rootDisk?.capacityTotalBytes && rootDisk.capacityUsedBytes
|
||||
? html`<progress
|
||||
value="${rootDisk.capacityUsedBytes}"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "#elements/ak-progress-bar";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
import { PFColor } from "#elements/Label";
|
||||
|
||||
@@ -9,7 +11,6 @@ import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||
import PFProgress from "@patternfly/patternfly/components/Progress/progress.css";
|
||||
import PFSplit from "@patternfly/patternfly/layouts/Split/split.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
@@ -21,7 +22,7 @@ export class EnterpriseStatusCard extends AKElement {
|
||||
@state()
|
||||
summary?: LicenseSummary;
|
||||
|
||||
static styles: CSSResult[] = [PFBase, PFDescriptionList, PFCard, PFSplit, PFProgress];
|
||||
static styles: CSSResult[] = [PFBase, PFDescriptionList, PFCard, PFSplit];
|
||||
|
||||
renderSummaryBadge() {
|
||||
switch (this.summary?.status) {
|
||||
@@ -81,66 +82,32 @@ export class EnterpriseStatusCard extends AKElement {
|
||||
</div>
|
||||
</dl>
|
||||
<div class="pf-l-split__item pf-m-fill">
|
||||
<div
|
||||
class="pf-c-progress ${internalUserPercentage > 100
|
||||
<ak-progress-bar
|
||||
class="${internalUserPercentage > 100
|
||||
? "pf-m-danger"
|
||||
: ""} ${internalUserPercentage >= 80 ? "pf-m-warning" : ""}"
|
||||
id="internalUsers"
|
||||
value=${internalUserPercentage}
|
||||
>
|
||||
<div class="pf-c-progress__description">
|
||||
${msg("Internal user usage")}
|
||||
</div>
|
||||
<div class="pf-c-progress__status" aria-hidden="true">
|
||||
<span class="pf-c-progress__measure"
|
||||
>${msg(
|
||||
str`${internalUserPercentage < Infinity ? internalUserPercentage : "∞"}%`,
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="pf-c-progress__bar"
|
||||
role="progressbar"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuenow="${internalUserPercentage}"
|
||||
>
|
||||
<div
|
||||
class="pf-c-progress__indicator"
|
||||
style="width:${Math.min(internalUserPercentage, 100)}%;"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="pf-c-progress ${externalUserPercentage > 100
|
||||
<span slot="description">${msg("Internal user usage")}</span>
|
||||
<span slot="status">
|
||||
${msg(
|
||||
str`${internalUserPercentage < Infinity ? internalUserPercentage : "∞"}%`,
|
||||
)}
|
||||
</span>
|
||||
</ak-progress-bar>
|
||||
<ak-progress-bar
|
||||
class="${externalUserPercentage > 100
|
||||
? "pf-m-danger"
|
||||
: ""} ${externalUserPercentage >= 80 ? "pf-m-warning" : ""}"
|
||||
id="externalUsers"
|
||||
value=${externalUserPercentage}
|
||||
>
|
||||
<div class="pf-c-progress__description">
|
||||
${msg("External user usage")}
|
||||
</div>
|
||||
<div class="pf-c-progress__status" aria-hidden="true">
|
||||
<span class="pf-c-progress__measure"
|
||||
>${msg(
|
||||
str`${externalUserPercentage < Infinity ? externalUserPercentage : "∞"}%`,
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="pf-c-progress__bar"
|
||||
role="progressbar"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuenow="${externalUserPercentage < Infinity
|
||||
? externalUserPercentage
|
||||
: "∞"}"
|
||||
>
|
||||
<div
|
||||
class="pf-c-progress__indicator"
|
||||
style="width:${Math.min(externalUserPercentage, 100)}%;"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<span slot="description">${msg("External user usage")}</span>
|
||||
<span slot="status">
|
||||
${msg(
|
||||
str`${externalUserPercentage < Infinity ? externalUserPercentage : "∞"}%`,
|
||||
)}
|
||||
</span>
|
||||
</ak-progress-bar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -91,6 +91,20 @@ export class DataExportListPage extends TablePage<DataExport> {
|
||||
</div>
|
||||
</dl>`;
|
||||
}
|
||||
|
||||
protected renderEmpty(_inner?: TemplateResult): TemplateResult {
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state icon=${this.pageIcon}
|
||||
><span
|
||||
>${msg(
|
||||
html`To create a data export, navigate to
|
||||
<a href="#/identity/users">Directory > Users</a> or to
|
||||
<a href="#/events/log">Events > Logs</a>.`,
|
||||
)}</span
|
||||
>
|
||||
</ak-empty-state>`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -16,18 +16,33 @@ import { createRef, ref } from "lit/directives/ref.js";
|
||||
|
||||
// Same regex is used in the backend as well
|
||||
const VALID_FILE_NAME_PATTERN = /^[a-zA-Z0-9._/-]+$/;
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/source
|
||||
// This is perfect for the "pattern" attribute
|
||||
const VALID_FILE_NAME_PATTERN_STRING = VALID_FILE_NAME_PATTERN.source;
|
||||
|
||||
// Note: browsers compile `pattern` using the new `v` RegExp flag (Unicode sets). Under `/v`,
|
||||
// both `/` and `-` must be escaped inside character classes.
|
||||
const VALID_FILE_NAME_PATTERN_STRING = "^[a-zA-Z0-9._\\/\\-]+$";
|
||||
|
||||
function assertValidFileName(fileName: string): void {
|
||||
if (!VALID_FILE_NAME_PATTERN.test(fileName)) {
|
||||
throw new Error(
|
||||
msg("Filename can only contain letters, numbers, dots, hyphens, and underscores"),
|
||||
msg(
|
||||
"Filename can only contain letters, numbers, dots, hyphens, underscores, and slashes",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getFileExtension(fileName: string): string {
|
||||
const lastDot = fileName.lastIndexOf(".");
|
||||
if (lastDot <= 0) return "";
|
||||
return fileName.slice(lastDot);
|
||||
}
|
||||
|
||||
function hasBasenameExtension(fileName: string): boolean {
|
||||
const baseName = fileName.split("/").pop() ?? fileName;
|
||||
const lastDot = baseName.lastIndexOf(".");
|
||||
return lastDot > 0;
|
||||
}
|
||||
|
||||
@customElement("ak-file-upload-form")
|
||||
export class FileUploadForm extends Form<Record<string, unknown>> {
|
||||
@property({ type: String, useDefault: true })
|
||||
@@ -57,36 +72,36 @@ export class FileUploadForm extends Form<Record<string, unknown>> {
|
||||
throw new PreventFormSubmit("Selected file not provided", this);
|
||||
}
|
||||
|
||||
assertValidFileName(this.selectedFile.name);
|
||||
|
||||
const api = new AdminApi(DEFAULT_CONFIG);
|
||||
const customName = typeof data.fileName === "string" ? data.fileName.trim() : "";
|
||||
const customName = typeof data.name === "string" ? data.name.trim() : "";
|
||||
|
||||
// If custom name provided, validate and append original extension
|
||||
// Only validate the original filename if no custom name is provided
|
||||
let finalName = this.selectedFile.name;
|
||||
if (customName) {
|
||||
assertValidFileName(customName);
|
||||
const ext = this.selectedFile.name.substring(this.selectedFile.name.lastIndexOf("."));
|
||||
finalName = customName + ext;
|
||||
const ext = getFileExtension(this.selectedFile.name);
|
||||
finalName =
|
||||
ext && !hasBasenameExtension(customName) ? `${customName}${ext}` : customName;
|
||||
} else {
|
||||
assertValidFileName(this.selectedFile.name);
|
||||
}
|
||||
|
||||
return api
|
||||
.adminFileCreate({
|
||||
file: this.selectedFile,
|
||||
name: finalName,
|
||||
usage: this.usage,
|
||||
})
|
||||
.then(() => {
|
||||
showMessage({
|
||||
level: MessageLevel.success,
|
||||
message: msg("File uploaded successfully"),
|
||||
});
|
||||
assertValidFileName(finalName);
|
||||
|
||||
this.reset();
|
||||
})
|
||||
.finally(() => {
|
||||
this.clearFileInput();
|
||||
});
|
||||
await api.adminFileCreate({
|
||||
file: this.selectedFile,
|
||||
name: finalName,
|
||||
usage: this.usage,
|
||||
});
|
||||
|
||||
showMessage({
|
||||
level: MessageLevel.success,
|
||||
message: msg("File uploaded successfully"),
|
||||
});
|
||||
|
||||
this.reset();
|
||||
this.clearFileInput();
|
||||
}
|
||||
|
||||
renderForm() {
|
||||
@@ -101,7 +116,7 @@ export class FileUploadForm extends Form<Record<string, unknown>> {
|
||||
@change=${this.#fileChangeListener}
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("File Name")} name="fileName">
|
||||
<ak-form-element-horizontal label=${msg("File Name")} name="name">
|
||||
<input
|
||||
type="text"
|
||||
class="pf-c-form-control"
|
||||
|
||||
@@ -95,7 +95,7 @@ export class RoleAssignedObjectPermissionTable extends Table<RoleAssignedObjectP
|
||||
>
|
||||
</ak-rbac-role-object-permission-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||
${msg("Assign role permissions")}
|
||||
${msg("Assign Object Permission")}
|
||||
</button>
|
||||
</ak-forms-modal>`;
|
||||
}
|
||||
@@ -135,9 +135,9 @@ export class RoleAssignedObjectPermissionTable extends Table<RoleAssignedObjectP
|
||||
const assignedToModel = item.modelPermissions.some(
|
||||
(uperm) => uperm.codename === perm.codename,
|
||||
);
|
||||
const assignedToObject = item.objectPermissions.some(
|
||||
(uperm) => uperm.codename === perm.codename,
|
||||
);
|
||||
const assignedToObject = item.objectPermissions
|
||||
.filter((uperm) => uperm.objectPk === this.objectPk)
|
||||
.some((uperm) => uperm.codename === perm.codename);
|
||||
|
||||
let tooltip: string | null = null;
|
||||
if (assignedToModel && assignedToObject) {
|
||||
|
||||
@@ -51,6 +51,11 @@ export class NavigationButtons extends WithSession(AKElement) {
|
||||
Styles,
|
||||
];
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.refreshNotifications();
|
||||
}
|
||||
|
||||
protected async refreshNotifications(): Promise<void> {
|
||||
const { currentUser } = this;
|
||||
|
||||
|
||||
99
web/src/elements/ak-progress-bar.ts
Normal file
99
web/src/elements/ak-progress-bar.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { PFSize } from "#common/enums";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
|
||||
import { css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import PFProgress from "@patternfly/patternfly/components/Progress/progress.css";
|
||||
|
||||
/**
|
||||
* @slot description - Description text above the progress bar, on the left
|
||||
* @slot status - Human-readable status above the progress bar, on the right
|
||||
*/
|
||||
@customElement("ak-progress-bar")
|
||||
export class ProgressBar extends AKElement {
|
||||
static styles = [
|
||||
PFProgress,
|
||||
css`
|
||||
.pf-c-progress {
|
||||
overflow: hidden;
|
||||
}
|
||||
.pf-c-progress.pf-m-indeterminate {
|
||||
--pf-c-progress__bar--Height: 2px;
|
||||
--pf-c-progress--GridGap: 0;
|
||||
margin-bottom: calc(var(--pf-c-progress__bar--Height) * -1);
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
.pf-c-progress.pf-m-indeterminate .pf-c-progress__bar .pf-c-progress__indicator {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
animation: indeterminateAnimation 1s infinite linear;
|
||||
transform-origin: 0% 50%;
|
||||
}
|
||||
@keyframes indeterminateAnimation {
|
||||
0% {
|
||||
transform: translateX(0) scaleX(0);
|
||||
}
|
||||
40% {
|
||||
transform: translateX(0) scaleX(0.4);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%) scaleX(0.5);
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@property({ type: Number })
|
||||
min = 0;
|
||||
@property({ type: Number })
|
||||
max = 100;
|
||||
@property({ type: Number })
|
||||
value = 0;
|
||||
|
||||
@property({ type: Boolean })
|
||||
indeterminate = false;
|
||||
|
||||
@property()
|
||||
size: PFSize = PFSize.Medium;
|
||||
|
||||
render() {
|
||||
const barAttrs: { [key: string]: unknown } = {};
|
||||
const indicatorAttrs: { [key: string]: unknown } = {};
|
||||
if (!this.indeterminate) {
|
||||
barAttrs["aria-valuemin"] = this.min;
|
||||
barAttrs["aria-valuemax"] = this.max;
|
||||
barAttrs["aria-valuenow"] = this.value;
|
||||
indicatorAttrs.style = `"width:${Math.min(this.value, 100)}%;";`;
|
||||
}
|
||||
return html`<div
|
||||
class="pf-c-progress ${this.classList} ${this.indeterminate
|
||||
? "pf-m-indeterminate"
|
||||
: ""} ${this.size.toString()}"
|
||||
>
|
||||
${this.hasSlotted("description")
|
||||
? html`
|
||||
<div class="pf-c-progress__description">
|
||||
<slot name="description"></slot>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${this.hasSlotted("status")
|
||||
? html`
|
||||
<div class="pf-c-progress__status" aria-hidden="true">
|
||||
<span class="pf-c-progress__measure">
|
||||
<slot name="status"></slot>
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
<div class="pf-c-progress__bar" role="progressbar" ${spread(barAttrs)}>
|
||||
<div class="pf-c-progress__indicator" ${spread(indicatorAttrs)}></div>
|
||||
</div>
|
||||
</div> `;
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ type Group<T> = [string, T[]];
|
||||
|
||||
export interface ISearchSelectBase<T> {
|
||||
blankable?: boolean;
|
||||
readOnly?: boolean;
|
||||
query?: string;
|
||||
objects?: T[];
|
||||
selectedObject: T | null;
|
||||
@@ -93,6 +94,14 @@ export abstract class SearchSelectBase<T>
|
||||
@property({ type: Boolean })
|
||||
public creatable?: boolean;
|
||||
|
||||
/**
|
||||
* Prevent user interaction while still rendering the current value.
|
||||
* @property
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: Boolean, attribute: "readonly" })
|
||||
public readOnly = false;
|
||||
|
||||
/**
|
||||
* An initial string to filter the search contents,
|
||||
* and the value of the input which further serves to restrict the search.
|
||||
@@ -254,6 +263,8 @@ export abstract class SearchSelectBase<T>
|
||||
}
|
||||
|
||||
#searchListener = (event: InputEvent) => {
|
||||
if (this.readOnly) return;
|
||||
|
||||
const value = (event.target as SearchSelectView).rawValue;
|
||||
|
||||
if (!value) {
|
||||
@@ -277,6 +288,8 @@ export abstract class SearchSelectBase<T>
|
||||
};
|
||||
|
||||
private onSelect(event: InputEvent) {
|
||||
if (this.readOnly) return;
|
||||
|
||||
const value = (event.target as SearchSelectView).value;
|
||||
|
||||
if (!value) {
|
||||
@@ -381,6 +394,7 @@ export abstract class SearchSelectBase<T>
|
||||
.options=${options}
|
||||
value=${ifPresent(value)}
|
||||
?blankable=${this.blankable}
|
||||
?readonly=${this.readOnly}
|
||||
label=${ifPresent(this.label)}
|
||||
name=${ifPresent(this.name)}
|
||||
placeholder=${ifPresent(this.placeholder)}
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface ISearchSelectView {
|
||||
value?: string;
|
||||
open: boolean;
|
||||
blankable: boolean;
|
||||
readOnly: boolean;
|
||||
caseSensitive: boolean;
|
||||
name?: string;
|
||||
placeholder: string;
|
||||
@@ -126,6 +127,14 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
|
||||
@property({ type: Boolean })
|
||||
public blankable = false;
|
||||
|
||||
/**
|
||||
* Prevents user interaction while showing the current value.
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: Boolean, attribute: "readonly" })
|
||||
public readOnly = false;
|
||||
|
||||
/**
|
||||
* If not managed, make the matcher case-sensitive during interaction. If managed,
|
||||
* the manager must handle this.
|
||||
@@ -248,6 +257,8 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
|
||||
//#region Event Listeners
|
||||
|
||||
#clickListener = (_ev: Event) => {
|
||||
if (this.readOnly) return;
|
||||
|
||||
this.open = !this.open;
|
||||
this.#inputRef.value?.focus();
|
||||
};
|
||||
@@ -263,6 +274,8 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
|
||||
}
|
||||
|
||||
#searchKeyupListener = (event: KeyboardEvent) => {
|
||||
if (this.readOnly) return;
|
||||
|
||||
if (event.key === "Escape") {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
@@ -277,6 +290,8 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
|
||||
};
|
||||
|
||||
#searchKeydownListener = (event: KeyboardEvent) => {
|
||||
if (this.readOnly) return;
|
||||
|
||||
if (!this.open) return;
|
||||
|
||||
switch (event.key) {
|
||||
@@ -339,6 +354,8 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
|
||||
}
|
||||
|
||||
#inputListener = (_ev: InputEvent) => {
|
||||
if (this.readOnly) return;
|
||||
|
||||
if (!this.managed) {
|
||||
this.findValueForInput();
|
||||
this.requestUpdate();
|
||||
@@ -356,6 +373,8 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
|
||||
};
|
||||
|
||||
#listKeydownListener = (event: KeyboardEvent) => {
|
||||
if (this.readOnly) return;
|
||||
|
||||
if (event.key === "Tab" && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -364,6 +383,8 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
|
||||
};
|
||||
|
||||
#changeListener = (event: InputEvent) => {
|
||||
if (this.readOnly) return;
|
||||
|
||||
if (!event.target) {
|
||||
return;
|
||||
}
|
||||
@@ -441,6 +462,7 @@ export class SearchSelectView extends AKElement implements ISearchSelectView {
|
||||
@keyup=${this.#searchKeyupListener}
|
||||
@keydown=${this.#searchKeydownListener}
|
||||
value=${this.displayValue}
|
||||
?readonly=${this.readOnly}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "#elements/ak-progress-bar";
|
||||
import "#elements/EmptyState";
|
||||
import "#elements/buttons/SpinnerButton/index";
|
||||
import "#elements/chips/Chip";
|
||||
@@ -866,6 +867,11 @@ export abstract class Table<T extends object>
|
||||
`;
|
||||
}
|
||||
|
||||
protected renderLoadingBar() {
|
||||
if (!this.loading) return nothing;
|
||||
return html`<ak-progress-bar indeterminate></ak-progress-bar>`;
|
||||
}
|
||||
|
||||
protected renderTable(): TemplateResult {
|
||||
const totalItemCount = this.data?.pagination.count ?? -1;
|
||||
|
||||
@@ -877,7 +883,9 @@ export abstract class Table<T extends object>
|
||||
${this.renderTablePagination()}
|
||||
</div>`;
|
||||
|
||||
return html`${this.needChipGroup ? this.renderChipGroup() : nothing}
|
||||
return html`${this.renderLoadingBar()}${this.needChipGroup
|
||||
? this.renderChipGroup()
|
||||
: nothing}
|
||||
${this.renderToolbarContainer()}
|
||||
<div part="table-container">
|
||||
<table
|
||||
|
||||
@@ -41,9 +41,12 @@ export class RedirectStage extends BaseStage<RedirectChallenge, FlowChallengeRes
|
||||
return new URL(this.challenge.to, document.baseURI).toString();
|
||||
}
|
||||
|
||||
firstUpdated(changed: PropertyValues<this>): void {
|
||||
super.firstUpdated(changed);
|
||||
updated(changed: PropertyValues<this>): void {
|
||||
super.updated(changed);
|
||||
|
||||
if (!changed.has("challenge")) {
|
||||
return;
|
||||
}
|
||||
if (this.promptUser) {
|
||||
document.addEventListener("keydown", (ev) => {
|
||||
if (ev.key === "Enter") {
|
||||
|
||||
@@ -28,7 +28,8 @@ export class AutosubmitStage extends BaseStage<
|
||||
updated(changed: PropertyValues<this>): void {
|
||||
super.updated(changed);
|
||||
|
||||
if (this.challenge.url !== undefined) {
|
||||
if (changed.has("challenge") && this.challenge.url !== undefined) {
|
||||
console.debug("authentik/flow/stages/autosubmit: submitting");
|
||||
this.form?.submit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ Firefox has some known issues regarding TouchID (see https://bugzilla.mozilla.or
|
||||
|
||||
Passwordless authentication currently only supports WebAuthn devices, which provides for the use of passkeys, security keys and biometrics. For an alternate passwordless setup, see [Password stage](../password/index.md#passwordless-login), which supports other types.
|
||||
|
||||
If you want users to authenticate with a passkey via the browser's built-in passkey/autofill UI on the **Identification** screen ("conditional UI" / passkey autofill), configure it in the [Identification stage](../identification/index.mdx#passkey-autofill-webauthn-conditional-ui). This requires a **discoverable credential (aka resident key)**.
|
||||
|
||||
To configure passwordless authentication, create a new Flow with the designation set to _Authentication_.
|
||||
|
||||
As first stage, add an _Authenticator validation_ stage, with the WebAuthn device class allowed.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Endpoint stage
|
||||
---
|
||||
|
||||
This stage integrates with the [Endpoint Device](../../../../endpoint-devices/index.mdx) functionality and allows authentik to verify whether the device executing a flow is registered.
|
||||
|
||||
The Endpoint stage fetches [device facts](../../../../endpoint-devices/device-compliance/device-reporting.md#device-facts) via a configured [connector](../../../../endpoint-devices/device-compliance/connectors.md) and injects them into the flow context. These device facts can be used by other stages and policies to make device compliance decisions.
|
||||
|
||||
## Connector
|
||||
|
||||
Select the [connector](../../../../endpoint-devices/device-compliance/connectors.md) that the Endpoint stage will use to obtain device facts.
|
||||
|
||||
## Mode
|
||||
|
||||
Select whether the presence of a registered endpoint device is required for the stage to succeed.
|
||||
|
||||
- If the mode is set to required, and device verification fails, the the user is not able to proceed with the flow.
|
||||
- If the mode is set to optional, authentik will attempt to verify the device, and if it doesn't receive a response within the specified `challenge_idle_timeout`, authentik will continue without attaching a device to the flow.
|
||||
@@ -26,6 +26,46 @@ The CAPTCHA stage you use must be configured to use the "Invisible" mode, otherw
|
||||
|
||||
To run a CAPTCHA process in the background while the user is entering their identification, a CAPTCHA stage can be selected here. If a CAPTCHA stage is selected in the Identification stage, the CAPTCHA stage should not be bound to the flow.
|
||||
|
||||
## Passkey autofill (WebAuthn conditional UI):ak-version[2025.12]
|
||||
|
||||
When configured, the Identification stage can offer passkey login directly from the browser's passkey/autofill UI (also known as "conditional UI"). This allows a user to select a passkey without first typing their username.
|
||||
|
||||
authentik will automatically fall back to the normal identification flow when passkey autofill is not available.
|
||||
|
||||
### Requirements
|
||||
|
||||
- **HTTPS** is required for WebAuthn (except on `localhost`).
|
||||
- **Browser support** for WebAuthn conditional mediation is required.
|
||||
- Users must have a compatible **discoverable credential (aka resident key)** (most passkeys created by platform authenticators and password managers are discoverable).
|
||||
- **Correct domain**: users must access authentik using the same hostname the passkey was created for.
|
||||
|
||||
### Configuration
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Flows and Stages** > **Stages** and either create or edit an [Authenticator validation stage](../authenticator_validate/index.mdx) that allows the **WebAuthn** device class.
|
||||
3. Navigate to **Flows and Stages** > **Stages** and edit your Identification stage. Under **Passkey settings** set **WebAuthn Authenticator Validation Stage** to the Authenticator validation stage from step 2.
|
||||
4. Click **Update** to save the changes.
|
||||
5. Ensure users have enrolled a passkey/WebAuthn device (for example using the [WebAuthn / FIDO2 / Passkeys Authenticator setup stage](../authenticator_webauthn/index.mdx)).
|
||||
|
||||
### Notes
|
||||
|
||||
- The passkey prompt is triggered by the browser when the user focuses the username field.
|
||||
- If a user has multiple passkeys, the browser will show a picker.
|
||||
- If passkey login is used, the flow context will have `auth_method` set to `auth_webauthn_pwl`.
|
||||
- In the default authentication flow blueprint, authentik skips the MFA validation stage after passkey login using an expression policy. If you want passkey login to still require an additional factor, disable or adjust that policy binding on the MFA stage.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
- **No passkey prompt appears**
|
||||
- Ensure the Identification stage has **WebAuthn Authenticator Validation Stage** set.
|
||||
- Ensure you're using **HTTPS** (except on `localhost`).
|
||||
- Check browser support for conditional UI.
|
||||
- Ensure the login page is not embedded in an iframe as some browsers block conditional UI outside top-level browsing contexts.
|
||||
|
||||
- **Passkey prompt appears, but login falls back to username/password**
|
||||
- Ensure the referenced Authenticator validation stage allows the **WebAuthn** device class.
|
||||
- Ensure the user has a valid, confirmed WebAuthn device enrolled.
|
||||
|
||||
## Enrollment/Recovery Flow
|
||||
|
||||
These fields specify if and which flows are linked on the form. The enrollment flow is linked as `Need an account? Sign up.`, and the recovery flow is linked as `Forgot username or password?`.
|
||||
|
||||
@@ -8,6 +8,8 @@ This is a generic password prompt which authenticates the current `pending_user`
|
||||
|
||||
There are two different ways to configure passwordless authentication; you can follow the instructions [here](../authenticator_validate/index.mdx#passwordless-authentication) to allow users to directly authenticate with their authenticator (only supported for WebAuthn devices), or dynamically skip the password stage depending on the users device, which is documented here.
|
||||
|
||||
If you want users to be able to pick a passkey from the browser's passkey/autofill UI without entering a username first, configure **Passkey autofill (WebAuthn conditional UI)** in the [Identification stage](../identification/index.mdx#passkey-autofill-webauthn-conditional-ui). This is separate from configuring a dedicated passwordless flow, and can be used alongside normal identification flows.
|
||||
|
||||
Depending on what kind of device you want to require the user to have:
|
||||
|
||||
#### WebAuthn
|
||||
|
||||
@@ -394,6 +394,8 @@ When documenting errors, follow this structure:
|
||||
- **Diagrams**:
|
||||
- Use [Mermaid](https://mermaid.js.org/) for creating diagrams directly in markdown. Mermaid is our preferred tool for documentation diagrams as it allows for version control and easy updates.
|
||||
- For more complex diagrams, you can use tools like [Draw.io](https://draw.io). Ensure high contrast and text descriptions.
|
||||
- **authentik icons**:
|
||||
- For authentik icons in integration guides, reference assets from the user's own self-hosted instance to avoid external calls, for example: `https://authentik.company/static/dist/assets/icons/icon.svg`
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @file Docusaurus Documentation config.
|
||||
*
|
||||
* @import { UserThemeConfig, UserThemeConfigExtra } from "@goauthentik/docusaurus-config";
|
||||
* @import { AKReleasesPluginOptions } from "@goauthentik/docusaurus-theme/releases/plugin"
|
||||
* @import { AKReleasesPluginOptions } from "@goauthentik/docusaurus-theme/releases/common"
|
||||
* @import { AKRedirectsPluginOptions } from "@goauthentik/docusaurus-theme/redirects/plugin"
|
||||
* @import { Options as RedirectsPluginOptions } from "@docusaurus/plugin-client-redirects";
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Deployment
|
||||
sidebar_label: Deployment
|
||||
---
|
||||
|
||||
import DocCardList from "@theme/DocCardList";
|
||||
|
||||
You can deploy the authentik Agent on [Linux](./linux.md), [macOS](./macos.md), and [Windows](./windows.md) devices.
|
||||
|
||||
Documentation for large-scale deployments using [Mobile Device Management (MDM)](./mdm.mdx) tools is also available.
|
||||
|
||||
Select a topic below to continue:
|
||||
|
||||
<DocCardList />
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: Deploy authentik Agent on Linux
|
||||
sidebar_label: Linux
|
||||
tags: [authentik Agent, linux, deploy, packages]
|
||||
---
|
||||
|
||||
## What it can do
|
||||
|
||||
- Retrieves information about the host and reports it to authentik, see [Device Compliance](../../device-compliance/index.mdx).
|
||||
- Authorize Sudo elevation, see [Sudo authorization](../../device-authentication/sudo-authorization.md).
|
||||
- SSH to Linux hosts using authentik credentials, see [SSH authentication](../../device-authentication/ssh-authentication.mdx).
|
||||
- Authenticate CLI applications using authentik credentials, see [CLI application authentication](../../device-authentication/cli-app-authentication/index.mdx).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You must [configure your authentik deployment](../configuration.md) to support the authentik Agent.
|
||||
|
||||
## Create an enrollment token
|
||||
|
||||
If you have already created have an enrollment token, skip to the [next section](#install-the-authentik-agent-on-linux).
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Endpoint Devices** > **Connectors**.
|
||||
3. Click on the authentik Agent connector that you created when [configuring your authentik deployment](../configuration.md) to support the authentik agent.
|
||||
4. Under **Enrollment Tokens**, click **Create**, and configure the following settings:
|
||||
- **Token name**: provide a descriptive name for the token
|
||||
- **Device group _(optional)_**: select a device access group for the device to be added to after completing enrollment
|
||||
- **Expiring _(optional)_**: set whether or not the enrollment token will expire
|
||||
5. Click **Create**.
|
||||
6. _(Optional)_ Click the **Copy** icon in the **Actions** column to copy the enrollment token. This value will be required if [enabling a device for device compliance](#enable-device-compliance-and-ssh-access).
|
||||
|
||||
## Install the authentik Agent on Linux
|
||||
|
||||
Follow these steps to install the authentik Agent on your Linux device:
|
||||
|
||||
1. Open a Terminal session and install the required GPG key:
|
||||
|
||||
```sh
|
||||
curl -fsSL https://pkg.goauthentik.io/keys/gpg-key.asc | sudo gpg --dearmor -o /usr/share/keyrings/authentik-keyring.gpg
|
||||
```
|
||||
|
||||
2. Add the repository:
|
||||
|
||||
```sh
|
||||
echo "deb [signed-by=/usr/share/keyrings/authentik-keyring.gpg] https://pkg.goauthentik.io stable main" | sudo tee /etc/apt/sources.list.d/authentik.list
|
||||
```
|
||||
|
||||
3. Update your repositories and install the authentik Agent packages:
|
||||
|
||||
```sh
|
||||
sudo apt update
|
||||
sudo apt install authentik-cli authentik-agent authentik-sysd
|
||||
```
|
||||
|
||||
4. Confirm that the authentik Agent is installed by opening a terminal window and entering the following command: `ak`
|
||||
You should see a response that starts with: `authentik CLI v<version_number>`
|
||||
|
||||
## Enable device authentication
|
||||
|
||||
To enable [device authentication features](../../device-authentication/index.mdx), the device must be connected to an authentik deployment. To do so, follow these steps:
|
||||
|
||||
1. Open a Terminal session and run the following command:
|
||||
|
||||
```sh
|
||||
ak config setup --authentik-url https://authentik.company
|
||||
```
|
||||
|
||||
2. Your default browser will open and direct you to the authentik login page. Once authenticated, the authentik Agent will be configured.
|
||||
|
||||
## Enable device compliance and SSH access
|
||||
|
||||
To enable [device compliance features](../../device-compliance/index.mdx) and the device [accepting SSH connections](../../device-authentication/ssh-authentication.mdx), you must join the device to an authentik domain.
|
||||
|
||||
1. Open a Terminal session and run the following command:
|
||||
|
||||
```sh
|
||||
ak-sysd domains join <deployment_name> --authentik-url https://authentik.company
|
||||
```
|
||||
|
||||
- `deployment_name` is the name that will be used to identify the authentik deployment on the device.
|
||||
- `https://authentik.company` is the fully qualified domain name of the authentik deployment.
|
||||
|
||||
2. You will be prompted to enter your [enrollment token](#create-an-enrollment-token).
|
||||
3. Once provided, the device will be enrolled with your authentik deployment and should appear on the [Devices page](../../manage-devices.mdx) after a [check-in](../../device-compliance/device-reporting.md) is completed.
|
||||
|
||||
## Logging
|
||||
|
||||
authentik Agent logs are available via the system journal (`systemd`) or `syslog`, depending on the distribution.
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: Deploy authentik Agent on macOS
|
||||
sidebar_label: macOS
|
||||
tags: [authentik Agent, mac, macos, deploy]
|
||||
---
|
||||
|
||||
## What it can do
|
||||
|
||||
- Retrieves information about the host for use in authentik, see [Device Compliance](../../device-compliance/index.mdx).
|
||||
- SSH to Linux hosts using authentik credentials, see [SSH authentication](../../device-authentication/ssh-authentication.mdx).
|
||||
- Authenticate CLI applications using authentik credentials, see [CLI application authentication](../../device-authentication/cli-app-authentication/index.mdx).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You must [configure your authentik deployment](../configuration.md) to support the authentik Agent.
|
||||
|
||||
## Create an enrollment token
|
||||
|
||||
If you have already created have an enrollment token, skip to the [next section](#install-the-authentik-agent-on-macos).
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Endpoint Devices** > **Connectors**.
|
||||
3. Click on the authentik Agent connector that you created when [configuring your authentik deployment](../configuration.md) to support the authentik agent.
|
||||
4. Under **Enrollment Tokens**, click **Create**, and configure the following settings:
|
||||
- **Token name**: provide a descriptive name for the token
|
||||
- **Device group _(optional)_**: select a device access group for the device to be added to after completing enrollment
|
||||
- **Expiring _(optional)_**: set whether or not the enrollment token will expire
|
||||
5. Click **Create**.
|
||||
6. _(Optional)_ Click the **Copy** icon in the **Actions** column to copy the enrollment token. This value will be required if [enabling a device for device compliance](#enable-device-compliance).
|
||||
|
||||
## Install the authentik Agent on macOS
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Endpoint Devices** > **Connectors**.
|
||||
3. Click on the authentik Agent connector that you created when [configuring your authentik deployment](../configuration.md) to support the authentik agent.
|
||||
4. Under **Setup**, click **macOS** to download the authentik Agent installer.
|
||||
5. Once the download is complete, attempt to install the package. Default Apple security settings should block the install.
|
||||
- This can be avoided by Option + Right Clicking the package and clicking **Open**.
|
||||
- Alternatively use the following command to remove the package from quarantine: `xattr -r -d com.apple.quarantine "$HOME/Downloads/authentik agent installer.pkg"`
|
||||
6. Confirm that the authentik Agent is installed by opening a Terminal window and entering the following command: `ak`
|
||||
You should see a response that starts with: `authentik CLI v<version_number>`
|
||||
|
||||
## Enable device authentication
|
||||
|
||||
To enable [device authentication features](../../device-authentication/index.mdx), you must connect the device to an authentik deployment. To do so, follow these steps:
|
||||
|
||||
1. Open a Terminal session and run the following command:
|
||||
|
||||
```sh
|
||||
ak config setup --authentik-url https://authentik.company
|
||||
```
|
||||
|
||||
2. Your default browser will open and direct you to the authentik login page. Once authenticated, the authentik Agent will be configured.
|
||||
|
||||
## Enable device compliance
|
||||
|
||||
To enable [device compliance features](../../device-compliance/index.mdx), you must join the device to an authentik domain.
|
||||
|
||||
1. Open a Terminal session and run the following command:
|
||||
|
||||
```sh
|
||||
ak-sysd domains join <deployment_name> --authentik-url https://authentik.company
|
||||
```
|
||||
|
||||
- `deployment_name` is the name that will be used to identify the authentik deployment on the device.
|
||||
- `https://authentik.company` is the fully qualified domain name of the authentik deployment.
|
||||
|
||||
2. You will be prompted to enter your [enrollment token](#create-an-enrollment-token).
|
||||
3. Once provided, the device will be enrolled with your authentik deployment and should appear on the [Devices page](../../manage-devices.mdx) after a [check-in](../../device-compliance/device-reporting.md) is completed.
|
||||
|
||||
## Logging
|
||||
|
||||
The authentik Agent uses macOS's native logging abilities. To retrieve the logs, open the Console application and then filter for authentik-related processes such as `authentik-agent` or `authentik-sysd`.
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: Deploy authentik Agent via MDM
|
||||
sidebar_label: MDM
|
||||
tags: [authentik Agent, mdm, fleet, deploy]
|
||||
---
|
||||
|
||||
authentik Agent can be deployed at scale to multiple devices via Mobile Device Management (MDM) tools.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You must [configure your authentik deployment](../configuration.md) to support the authentik Agent.
|
||||
|
||||
## Create an enrollment token
|
||||
|
||||
If you have already created have an enrollment token, skip to the next section.
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Endpoint Devices** > **Connectors**.
|
||||
3. Click on the authentik Agent connector that you created when [configuring your authentik deployment](../configuration.md) to support the authentik agent.
|
||||
4. Under **Enrollment Tokens**, click **Create**, and configure the following settings:
|
||||
- **Token name**: provide a descriptive name for the token
|
||||
- **Device group _(optional)_**: select a device access group for the device to be added to after completing enrollment
|
||||
- **Expiring _(optional)_**: set whether or not the enrollment token will expire
|
||||
5. Click **Create**.
|
||||
|
||||
## Windows
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Endpoint Devices** > **Connectors**.
|
||||
3. Click on the authentik Agent connector that you created when [configuring your authentik deployment](../configuration.md) to support the authentik agent.
|
||||
4. Under **Setup**, select the enrollment token that you wish to use for enrolling devices.
|
||||
5. Click **Windows** and then click either **Download** or **Copy** to obtain your SyncML MDM configuration snippet.
|
||||
|
||||
This SyncML snippet can be used by Microsoft Intune, Microsoft Endpoint Manager and other MDM tools to deploy the changes required to support the authentik Agent.
|
||||
|
||||
The following two registry keys (`REG_SZ`) are added by the configuration snippet:
|
||||
|
||||
- `HKLM/SOFTWARE/authentik Security Inc./Platform/ManagedConfig/RegistrationToken`
|
||||
- `HKLM/SOFTWARE/authentik Security Inc./Platform/ManagedConfig/URL`
|
||||
|
||||
## macOS
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Endpoint Devices** > **Connectors**.
|
||||
3. Click on the authentik Agent connector that you created when [configuring your authentik deployment](../configuration.md) to support the authentik agent.
|
||||
4. Under **Setup**, select the enrollment token that you wish to use for enrolling devices.
|
||||
5. Click **macOS** and then click either **Download** or **Copy** to obtain your MDM policy.
|
||||
|
||||
This policy can be used by Apple Business Manager, Fleet, and other MDM tools to deploy the changes required to support the authentik Agent.
|
||||
|
||||
:::warning MDM only
|
||||
Apple requires that this policy be applied to a device via an MDM tool. It will not function if manually applied to a device.
|
||||
:::
|
||||
|
||||
### User registration
|
||||
|
||||
Upon deploying the authentik Agent to a device, the user will receive a notification asking them to register with authentik. When a user follows the registration they are asked to authenticate with authentik, once authenticated the device is enrolled in authentik and associated with the user.
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
title: Deploy authentik Agent on Windows
|
||||
sidebar_label: Windows
|
||||
tags: [authentik Agent, windows]
|
||||
---
|
||||
|
||||
## What it can do
|
||||
|
||||
- Retrieves information about the host for use in authentik, see [Device Compliance](../../device-compliance/index.mdx).
|
||||
- SSH to Linux hosts using authentik credentials, see [SSH authentication](../../device-authentication/ssh-authentication.mdx).
|
||||
- Authenticate CLI applications using authentik credentials, see [CLI application authentication](../../device-authentication/cli-app-authentication/index.mdx).
|
||||
|
||||
:::warn Supported Windows Versions
|
||||
The authentik Agent is currently only tested on Windows 11 and Windows Server 2022. Other versions may work but are untested.
|
||||
:::
|
||||
|
||||
## Windows Credential Provider
|
||||
|
||||
Windows Credential Provider (WCP) is a component of the authentik Agent that allows logging in to Windows workstations using authentik credentials.
|
||||
|
||||
It currently only supports local login; RDP login is not supported.
|
||||
|
||||
:::warning
|
||||
|
||||
- When WCP is enabled, the password of the Windows user account that's used to login is set to a random string.
|
||||
- WCP can cause issues with user encrypted directories.
|
||||
- Support with Active directory has not been confirmed yet.
|
||||
- Offline login is currently not supported.
|
||||
:::
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You must [configure your authentik deployment](../configuration.md) to support the authentik Agent.
|
||||
|
||||
## Create an enrollment token
|
||||
|
||||
If you have already created have an enrollment token, skip to the [next section](#install-the-authentik-agent-on-windows).
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Endpoint Devices** > **Connectors**.
|
||||
3. Click on the authentik Agent connector that you created when [configuring your authentik deployment](../configuration.md) to support the authentik agent.
|
||||
4. Under **Enrollment Tokens**, click **Create**, and configure the following settings:
|
||||
- **Token name**: provide a descriptive name for the token
|
||||
- **Device group _(optional)_**: select a device access group for the device to be added to after completing enrollment
|
||||
- **Expiring _(optional)_**: set whether or not the enrollment token will expire
|
||||
5. Click **Create**.
|
||||
6. _(Optional)_ Click the **Copy** icon in the **Actions** column to copy the enrollment token. This value will be required if [enabling a device for device compliance](#enable-device-compliance).
|
||||
|
||||
## Install the authentik Agent on Windows
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Endpoint Devices** > **Connectors**.
|
||||
3. Click on the authentik Agent connector that you created when [configuring your authentik deployment](../configuration.md) to support the authentik agent.
|
||||
4. Under **Setup**, click **Windows** to download the authentik Agent installer.
|
||||
5. Once the download is complete, install the MSI file.
|
||||
6. _(Optional)_ During installation, select [Windows Credential Provider](#windows-credential-provider) if you want to log in to the Windows device using authentik credentials.
|
||||
7. Confirm that the authentik Agent is installed by opening a PowerShell or Terminal window and entering the following command: `ak`
|
||||
You should see a response that starts with: `authentik CLI v<version_number>`
|
||||
|
||||
## Enable device authentication
|
||||
|
||||
To enable [device authentication features](../../device-authentication/index.mdx), you must connect the device to an authentik deployment. To do so, follow these steps:
|
||||
|
||||
1. Open a Terminal and run the following command:
|
||||
|
||||
```sh
|
||||
ak config setup --authentik-url https://authentik.company
|
||||
```
|
||||
|
||||
2. Your default browser will open and direct you to the authentik login page. Once authenticated, the authentik Agent will be configured.
|
||||
|
||||
## Enable device compliance
|
||||
|
||||
To enable [device compliance features](../../device-compliance/index.mdx), you must join the device to an authentik domain.
|
||||
|
||||
1. Open a Terminal session and run the following command:
|
||||
|
||||
```sh
|
||||
ak-sysd domains join <deployment_name> --authentik-url https://authentik.company
|
||||
```
|
||||
|
||||
- `deployment_name` is the name that will be used to identify the authentik deployment on the device.
|
||||
- `https://authentik.company` is the fully qualified domain name of the authentik deployment.
|
||||
|
||||
2. You will be prompted to enter your [enrollment token](#create-an-enrollment-token).
|
||||
3. Once provided, the device will be enrolled with your authentik deployment and should appear on the [Devices page](../../manage-devices.mdx) after a [check-in](../../device-compliance/device-reporting.md) is completed.
|
||||
|
||||
## Logging
|
||||
|
||||
The authentik Agent primarily outputs logs to Windows Event Viewer.
|
||||
|
||||
WCP logs to the `wcp.log` located in `C:\Program Files\Authentik Security Inc\wcp`.
|
||||
140
website/docs/endpoint-devices/authentik-agent/authentik-cli.mdx
Normal file
140
website/docs/endpoint-devices/authentik-agent/authentik-cli.mdx
Normal file
@@ -0,0 +1,140 @@
|
||||
---
|
||||
title: Agent CLI commands
|
||||
sidebar_label: Agent CLI commands
|
||||
tags: [authentik Agent, authentik cli, ak cli, ak, cli, ak-sysd, commands]
|
||||
---
|
||||
|
||||
The following commands are available when interacting with the authentik Agent via the command line.
|
||||
|
||||
:::info Flags
|
||||
Most of the CLI commands have a `-v`/`--verbose` flag for verbose output.
|
||||
Use the `-h`/`--help` flag to access help information.
|
||||
:::
|
||||
|
||||
## authentik-cli commands
|
||||
|
||||
### auth
|
||||
|
||||
Commands for authenticating with different CLI applications.
|
||||
|
||||
```bash
|
||||
ak auth <command>
|
||||
```
|
||||
|
||||
- `aws` - Authenticate to AWS with the authentik profile.
|
||||
- `kubectl` - Authenticate to a Kubernetes Cluster with the authentik profile.
|
||||
- `raw` - Authenticate to arbitrary API calls.
|
||||
- `vault` - Generate a JWT for authenticating to HashiCorp Vault.
|
||||
|
||||
### completion
|
||||
|
||||
Generate the autocompletion script for the specified shell.
|
||||
|
||||
```bash
|
||||
ak completion <command>
|
||||
```
|
||||
|
||||
- `bash` - Generate the autocompletion script for bash.
|
||||
- `fish` - Generate the autocompletion script for fish.
|
||||
- `powershell` - Generate the autocompletion script for PowerShell.
|
||||
- `zsh` - Generate the autocompletion script for zsh.
|
||||
|
||||
### config
|
||||
|
||||
Configure authentik CLI
|
||||
|
||||
```bash
|
||||
ak config <command>
|
||||
```
|
||||
|
||||
- `list-profiles` - List profiles that are enabled on the device. Each profile is associated with a separate authentik deployment.
|
||||
- `setup` - Configure authentik CLI.
|
||||
|
||||
### help
|
||||
|
||||
Output help information about any command.
|
||||
|
||||
```bash
|
||||
ak help <command>
|
||||
```
|
||||
|
||||
Where `<command>` is any authentik CLI command you want help with, for example: `ak help ssh`
|
||||
|
||||
### ssh
|
||||
|
||||
Establish an SSH connection with the target endpoint device.
|
||||
|
||||
```bash
|
||||
ak ssh <hostname>
|
||||
```
|
||||
|
||||
### system
|
||||
|
||||
Commands for interacting with authentik sessions.
|
||||
|
||||
```bash
|
||||
ak system <command>
|
||||
```
|
||||
|
||||
- `status` - Status about the current session.
|
||||
|
||||
### whoami
|
||||
|
||||
Check user account details for a given profile.
|
||||
|
||||
```bash
|
||||
ak whoami
|
||||
```
|
||||
|
||||
## authentik-sysd commands
|
||||
|
||||
### agent
|
||||
|
||||
Used to run the authentik system agent
|
||||
|
||||
```bash
|
||||
ak-sysd agent
|
||||
```
|
||||
|
||||
`-d` for debug
|
||||
`--disable-component` to disable a component, can be used multiple times.
|
||||
TODO @BeryJu document the ids of components
|
||||
|
||||
### completion
|
||||
|
||||
Generate the autocompletion script for the specified shell.
|
||||
|
||||
```bash
|
||||
ak-sysd completion <command>
|
||||
```
|
||||
|
||||
- `bash` - Generate the autocompletion script for bash.
|
||||
- `fish` - Generate the autocompletion script for fish.
|
||||
- `powershell` - Generate the autocompletion script for powershell.
|
||||
- `zsh` - Generate the autocompletion script for zsh.
|
||||
|
||||
### domains
|
||||
|
||||
```bash
|
||||
ak-sysd domains <command>
|
||||
```
|
||||
|
||||
- `join` - Join an authentik domain, for example `ak-sysd domains join <name_for_authentik_domain> -a <authentik_URL>`
|
||||
|
||||
### help
|
||||
|
||||
```bash
|
||||
ak-sysd help <command>
|
||||
```
|
||||
|
||||
Where `<command>` is any authentik CLI command you want help with, for example: `ak-sysd help domains`
|
||||
|
||||
### troubleshoot
|
||||
|
||||
```bash
|
||||
ak-sysd troubleshoot <command>
|
||||
```
|
||||
|
||||
- `check` - Check status of authentik agent components. Useful on Linux as there are various components being used.
|
||||
- `inspect` - Outputs the state database that the agent has.
|
||||
- `facts` - Outputs device facts. These are the facts that are sent to authentik for device reporting.
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
title: Configuration
|
||||
sidebar_label: Configuration
|
||||
tags: [authentik Agent, connector, configure, configuration]
|
||||
---
|
||||
|
||||
Before deploying the authentik Agent, configure your authentik deployment. This involves:
|
||||
|
||||
- Importing the [Device code flow](../../add-secure-apps/providers/oauth2/device_code.md)
|
||||
- Creating an OAuth application and provider
|
||||
- Creating a [Connector](../device-compliance/connectors.md)
|
||||
|
||||
## Import OAuth device code flow
|
||||
|
||||
The OAuth device code flow enables secure authentication for input-limited clients like CLI tools and is required for the authentik Agent to function.
|
||||
|
||||
If you have already deployed the authentik OAuth device code flow, skip to the [next section](#create-an-application-and-provider-in-authentik-for-cli).
|
||||
|
||||
1. Download the [device code flow blueprint file](https://raw.githubusercontent.com/goauthentik/platform/refs/heads/main/hack/authentik/blueprints/oauth2-device-code.yaml).
|
||||
2. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
3. Navigate to **Flows and Stages** > **Flows**.
|
||||
4. Click **Import**
|
||||
5. Select the downloaded blueprint and click **Import**.
|
||||
6. Navigate to **System** > **Brands** and click the **Edit** icon on the default brand.
|
||||
7. Set **Default code flow** to the newly created device code flow and click **Update**.
|
||||
|
||||
Alternatively, manually create the flow by following the instructions in the [Device code flow documentation](../../add-secure-apps/providers/oauth2/device_code.md#create-and-apply-a-device-code-flow).
|
||||
|
||||
## Create an application and provider in authentik for CLI
|
||||
|
||||
The authentik agent requires an OAuth application/provider pair to handle authentication.
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
|
||||
- **Application**: provide a descriptive name (e.g. `authentik-cli`), an optional group for the type of application, the policy engine mode, and optional UI settings.
|
||||
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
|
||||
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
|
||||
- Set the **Client type** to `Public`.
|
||||
- Set the **Client ID** to `authentik-cli`.
|
||||
- Select any available signing key.
|
||||
- Under **Advanced protocol settings**:
|
||||
- In addition to the three default **Selected Scopes**, add the `authentik default OAuth Mapping: OpenID 'offline_access'` scope.
|
||||
- **Configure Bindings** _(optional)_: you can create a [binding](../../../add-secure-apps/flows-stages/bindings/) (policy, group, or user) to manage access to the application.
|
||||
|
||||
3. Click **Submit** to save the new application and provider.
|
||||
|
||||
## Create the authentik Agent connector
|
||||
|
||||
The authentik Agent [Connector](../device-compliance/connectors.md) allows device information to be reported to authentik.
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Endpoint Devices** > **Connectors** and click **Create**.
|
||||
3. Select **Agent Connector** as the agent type and click **Next**.
|
||||
4. Configure the following required settings:
|
||||
- **Connector name**: provide a descriptive name (e.g. `authentik Agent`)
|
||||
- **Refresh interval**: select how often the agent will attempt to update its configuration.
|
||||
- **Enabled**: toggle to enable the connector.
|
||||
- Under **Authentication settings**:
|
||||
- **Federated OIDC Providers**: add the `authentik-cli` provider that you created in the previous section.
|
||||
5. Click **Finish**.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Development
|
||||
sidebar_label: Development
|
||||
tags: [authentik Agent, development, repository]
|
||||
---
|
||||
|
||||
The authentik Agent and associated components are developed in the [authentik Platform GitHub repository](https://github.com/goauthentik/platform). For source code and information on contributing to the project, refer to the documentation included in the GitHub repository.
|
||||
46
website/docs/endpoint-devices/authentik-agent/index.mdx
Normal file
46
website/docs/endpoint-devices/authentik-agent/index.mdx
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: authentik Agent
|
||||
sidebar_label: authentik Agent
|
||||
---
|
||||
|
||||
import DocCardList from "@theme/DocCardList";
|
||||
|
||||
## What is the authentik Agent?
|
||||
|
||||
The authentik Agent is a service that can be installed on Linux, macOS, and Windows devices. It provides the following capabilities:
|
||||
|
||||
- [Device Compliance](../device-compliance/index.mdx) by reporting information about Endpoint Devices to authentik
|
||||
- [Local device login](../device-authentication/local-device-login/index.mdx) with authentik credentials
|
||||
- [Connecting via SSH to Endpoint Devices](../device-authentication/ssh-authentication.mdx) with authentik credentials
|
||||
- [Sudo authorization](../device-authentication/sudo-authorization.md) with authentik credentials
|
||||
- [Authenticating to CLI applications](../device-authentication/cli-app-authentication/index.mdx) such as kubectl and AWS with authentik credentials
|
||||
|
||||
## authentik Agent components
|
||||
|
||||
The authentik Agent consists of several components:
|
||||
|
||||
| Platform | Component | Description | Dependencies |
|
||||
| ------------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
|
||||
| **Linux, macOS, Windows** | `authentik-cli` | Provides CLI commands for interacting with `authentik-agent`. | `authentik-agent` |
|
||||
| **Linux, macOS, Windows** | `authentik-agent` | User service. | `authentik-sysd` |
|
||||
| **Linux, macOS, Windows** | `authentik-sysd` | System service. | None |
|
||||
| **Linux only** | `libpam-authentik` | PAM Module for token-based and interactive authentication via authentik. Used for [SSH authentication](../device-authentication/ssh-authentication.mdx) and [local device login](../device-authentication/local-device-login/index.mdx). | `authentik-sysd` |
|
||||
| **Linux only** | `libnss-authentik` | NSS Module that makes Linux aware of authentik users. All authentik users will be visible to Linux - but won't be able to login unless configured via device access groups. Provides a consistent `uid` and `gid` for users on all Endpoint Devices. | `authentik-sysd`, `libpam-authentik` |
|
||||
| **Windows only** | `Windows Credential Provider` (WCP) | Enables logging in to Windows devices using authentik credentials. | `authentik-sysd` |
|
||||
|
||||
## Technical information
|
||||
|
||||
All authentik Agent components communicate via gRPC and Unix domain sockets.
|
||||
|
||||
- `sys.sock` for general communication
|
||||
- `sys-ctrl.sock` for domain join
|
||||
|
||||
## Important considerations
|
||||
|
||||
Sentry reporting is currently enabled by default and cannot be disabled. This will be configurable in a future release.
|
||||
|
||||
## More information
|
||||
|
||||
For more information refer to each of the topics below:
|
||||
|
||||
<DocCardList />
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: authentik Agent Releases
|
||||
sidebar_label: Release Notes
|
||||
---
|
||||
|
||||
import DocCardList from "@theme/DocCardList";
|
||||
|
||||
Release notes for recent authentik Agent versions
|
||||
|
||||
<DocCardList />
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: temp
|
||||
sidebar_label: temp
|
||||
---
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: AWS CLI authentication
|
||||
sidebar_label: AWS
|
||||
tags: [authentik Agent, authentik cli, aws, cli]
|
||||
---
|
||||
|
||||
You can use the authentik Agent to authenticate to the AWS CLI with authentik credentials.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The [authentik Agent deployed on it](../../authentik-agent/agent-deployment/index.mdx) must be deployed on your device.
|
||||
|
||||
## authentik configuration
|
||||
|
||||
To support the integration of authentik Agent with AWS CLI, you need to create an application/provider pair in authentik.
|
||||
|
||||
### Create an application and provider in authentik for AWS CLI
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
|
||||
- **Application**: provide a descriptive name (e.g. `authentik-aws-cli`), an optional group for the type of application, the policy engine mode, and optional UI settings.
|
||||
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
|
||||
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
|
||||
- Set the **Client type** to `Public`.
|
||||
- Set the **Client ID** to `authentik-aws-cli`.
|
||||
- Select any available signing key.
|
||||
- Under **Machine-to-Machine authentication settings** add the `authentik-cli` provider as a **Federated OIDC Provider**.
|
||||
- **Configure Bindings** _(optional)_: you can create a [binding](../../../../add-secure-apps/flows-stages/bindings/) (policy, group, or user) to manage access to the application.
|
||||
|
||||
3. Click **Submit** to save the new application and provider.
|
||||
|
||||
## Authenticate to AWS CLI with the authentik Agent
|
||||
|
||||
To authenticate to the AWS CLI with the authentik agent, use the following command:
|
||||
|
||||
```bash
|
||||
ak auth aws
|
||||
```
|
||||
|
||||
**Available flags:**
|
||||
|
||||
- `-c, --client-id <string>` - Client ID
|
||||
- `-e, --region <string>` - AWS region (default: `eu-central-1`)
|
||||
- `-r, --role-arn <string>` - IAM Role ARN
|
||||
- `-h, --help` - Display help information
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: CLI application authentication
|
||||
sidebar_label: CLI application authentication
|
||||
tags: [authentik Agent, authentik cli, kubernetes, k8s, aws, cli]
|
||||
---
|
||||
|
||||
import DocCardList from "@theme/DocCardList";
|
||||
|
||||
The authentik Agent can authenticate to CLI applications such as [`aws`](./aws.mdx) and [`kubectl`](./k8s.mdx).
|
||||
|
||||
## How CLI authentication works
|
||||
|
||||
First, `authentik-agent` and `authentik-cli` request an authentik token from the [authentik-cli OAuth Provider](../../authentik-agent/configuration.md#create-an-application-and-provider-in-authentik-for-cli) and exchange it for a token from the specified Kubernetes or AWS provider.
|
||||
|
||||
This token is cached until expiration. This improves performance by eliminating repeated token requests.
|
||||
|
||||
## More information
|
||||
|
||||
For more information refer to each of the topics below:
|
||||
|
||||
<DocCardList />
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: Kubernetes CLI authentication
|
||||
sidebar_label: Kubernetes
|
||||
tags: [authentik Agent, authentik cli, kubernetes, k8s, kubectl, cli]
|
||||
---
|
||||
|
||||
You can use the authentik Agent to authenticate to `kubectl` with authentik credentials.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The device that you're using must have the [authentik Agent deployed on it](../../authentik-agent/agent-deployment/index.mdx).
|
||||
|
||||
## authentik configuration
|
||||
|
||||
To support the integration of authentik Agent with `kubectl`, you need to create an application/provider pair in authentik.
|
||||
|
||||
### Create an application and provider in authentik for Kubernetes
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
|
||||
- **Application**: provide a descriptive name (e.g. `authentik-kubernetes`), an optional group for the type of application, the policy engine mode, and optional UI settings.
|
||||
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
|
||||
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
|
||||
- Set the **Client type** to `Public`.
|
||||
- Set the **Client ID** to `authentik-kubernetes`.
|
||||
- Select any available signing key.
|
||||
- Under **Machine-to-Machine authentication settings** add the `authentik-cli` provider as a **Federated OIDC Provider**.
|
||||
- **Configure Bindings** _(optional)_: you can create a [binding](../../../../add-secure-apps/flows-stages/bindings/) (policy, group, or user) to manage access to the application.
|
||||
|
||||
3. Click **Submit** to save the new application and provider.
|
||||
|
||||
## Kubernetes configuration
|
||||
|
||||
To integrate the authentik Agent with your kubernetes deployment, you'll need to configure kubeadm.
|
||||
|
||||
### Configure kubeadm settings
|
||||
|
||||
Add the following `extraArgs` to your `kubeadm_config.yml` file:
|
||||
|
||||
```yaml
|
||||
- name: oidc-client-id
|
||||
value: authentik-kubernetes
|
||||
- name: oidc-groups-claim
|
||||
value: groups
|
||||
- name: oidc-groups-prefix
|
||||
value: "oidc:"
|
||||
- name: oidc-issuer-url
|
||||
value: https://authentik.company/application/o/<application-slug>/
|
||||
- name: oidc-username-claim
|
||||
value: email
|
||||
```
|
||||
|
||||
Run the following command to apply the changes to an existing Kubernetes cluster:
|
||||
|
||||
:::warning
|
||||
This command will restart the API server. Plan accordingly for production environments.
|
||||
:::
|
||||
|
||||
```sh
|
||||
kubeadm upgrade apply <version> --config=kubeadm_config.yml
|
||||
```
|
||||
|
||||
Where `<version>` matches the target Kubernetes version specified in your config file.
|
||||
|
||||
:::info Example config file
|
||||
An example `kubeadm_config.yml` is available on the [authentik Platform GitHub repository](https://github.com/BeryJu/infrastructure/blob/main/roles/beryjuio_kube/templates/kubeadm_config.yml#L11-L20).
|
||||
:::
|
||||
|
||||
## Authenticate to kubectl with the authentik Agent
|
||||
|
||||
To authenticate to kubectl with the authentik agent, use the following command:
|
||||
|
||||
```bash
|
||||
ak auth kubectl
|
||||
```
|
||||
|
||||
**Available flags:**
|
||||
|
||||
- `-c, --client-id <string>` - Client ID
|
||||
- `-h, --help` - Display help information
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Device access groups
|
||||
sidebar_label: Device access groups
|
||||
tags: [authentik Agent, device authentication, device login, device groups]
|
||||
---
|
||||
|
||||
Device access groups control access to endpoint devices. You can organize devices into groups and bind users, user groups, and policies to determine access.
|
||||
|
||||
## Creating a device access group
|
||||
|
||||
To create a device access group, follow these steps:
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Endpoint Devices** > **Device Access Groups** and click **Create**.
|
||||
3. Provide a **Group name** and click **Create**.
|
||||
4. Expand the newly created device access group.
|
||||
5. Click either **Create and bind Policy** or **Bind existing Policy / Group / User**.
|
||||
6. Once you've configured the desired access for the device access group, click **Finish**.
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: Device authentication
|
||||
sidebar_label: Device authentication
|
||||
---
|
||||
|
||||
import DocCardList from "@theme/DocCardList";
|
||||
|
||||
The [authentik Agent](../authentik-agent/index.mdx) supports multiple types of authentication and authorization using authentik credentials:
|
||||
|
||||
- [Local device login](./local-device-login/index.mdx) - Log in to Windows endpoint devices.
|
||||
- [SSH authentication](./ssh-authentication.mdx) - Connect from one endpoint device to another via SSH.
|
||||
- [Sudo authorization](./sudo-authorization.md) - Authorize sudo elevation on an endpoint device.
|
||||
- [Authenticate CLI applications](./cli-app-authentication/index.mdx) - Authenticate CLI based applications like `aws` and `kubectl`.
|
||||
|
||||
[Device access groups](./device-access-groups.mdx) allow you to control which users have access to a device.
|
||||
|
||||
For more information, pick a topic below:
|
||||
|
||||
<DocCardList />
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Local device login
|
||||
sidebar_label: Local device login
|
||||
tags: [authentik Agent, device login, device authentication, windows credential provider, wcp]
|
||||
---
|
||||
|
||||
import DocCardList from "@theme/DocCardList";
|
||||
|
||||
Local device login allows you to log in to devices using authentik credentials instead of separate local accounts.
|
||||
|
||||
Users authenticate with authentik, just like they do for any application. The operating system login screen on a laptop, desktop, or server is connected to authentik.
|
||||
|
||||
Local device login is currently only supported on Windows. Linux support is possible but not yet implemented. Configuration and testing with various Linux login managers (SDDM, GDM, etc.) and PAM implementations is pending.
|
||||
|
||||
For more information, pick a topic below:
|
||||
|
||||
<DocCardList />
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Linux local device login
|
||||
sidebar_label: Linux
|
||||
tags: [authentik Agent, device login, device authentication, linux]
|
||||
---
|
||||
|
||||
Local device login is currently only supported on Windows.
|
||||
|
||||
Linux support is possible but not yet implemented. Configuration and testing with various Linux login managers (SDDM, GDM, etc.) and PAM implementations is pending.
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Windows local device login
|
||||
sidebar_label: Windows
|
||||
tags: [authentik Agent, device login, device authentication, windows credential provider, wcp]
|
||||
---
|
||||
|
||||
## Windows Credential Provider
|
||||
|
||||
Windows Credential Provider (WCP) is a component of the authentik Agent that allows logging in to Windows devices using authentik credentials.
|
||||
|
||||
It currently only supports local login; RDP login is not supported.
|
||||
|
||||
:::warning
|
||||
|
||||
- WCP is currently only tested on Windows 11 and Windows Server 2022.
|
||||
- When WCP is enabled, the password of the Windows user account that's used to login is set to a random string.
|
||||
- WCP can cause issues with user encrypted directories.
|
||||
- Support with Active directory has not been confirmed yet.
|
||||
- Offline login is currently not supported.
|
||||
:::
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You need to have deployed the authentik Agent including the WCP component on the Windows device, see [Deploy the authentik Agent on Windows](../../authentik-agent/agent-deployment/windows.md) for more details.
|
||||
|
||||
## How it works
|
||||
|
||||
- The system agent requests an authentication and authorization URL from authentik, using its token.
|
||||
- This URL is opened in a browser which also injects the device token information, allowing authenitk to know that the login request is executed on the same machine.
|
||||
- The end user logs in normally using the standard authentik interface and flows
|
||||
- Once finished, the browser is redirect to a well-defined location and uses the token it receives to finish authentication and authorization through the system agent
|
||||
|
||||
## How to log in to a Windows device
|
||||
|
||||
1. On the Windows login screen, click the authentik icon:
|
||||
|
||||

|
||||
|
||||
2. A browser window will open and prompt you for your authentik credentials.
|
||||
3. Once authenticated, you will be logged in to the Windows device.
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: SSH authentication
|
||||
sidebar_label: SSH authentication
|
||||
tags: [ssh, authentik Agent]
|
||||
---
|
||||
|
||||
You can use the [authentik Agent](../authentik-agent/index.mdx) to authenticate SSH connections ubetween endpoint devices using authentik credentials.
|
||||
|
||||
Currently, only [Linux](../authentik-agent/agent-deployment/linux.md) devices can serve as SSH endpoints. See [Configure SSH authentication on an endpoint device](#configure-ssh-authentication-on-an-endpoint-device) section for more details.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The [authentik Agent must be deployed](../authentik-agent/agent-deployment/index.mdx) on both the source and SSH target devices.
|
||||
- The target device needs to be configured, see the [Configure SSH authentication on an endpoint device](#configure-ssh-authentication-on-an-endpoint-device) section below.
|
||||
|
||||
## How to SSH to an endpoint device
|
||||
|
||||
To SSH to a configured [Linux host](../authentik-agent/agent-deployment/linux.md) using the authentik Agent:
|
||||
|
||||
1. Open a Terminal session and run the following command:
|
||||
|
||||
```
|
||||
ak ssh <hostname>
|
||||
```
|
||||
|
||||
2. If not already authenticated, you will be prompted for authentik credentials.
|
||||
3. Once authenticated, the SSH session will connect.
|
||||
|
||||
## Configure SSH authentication on an endpoint device
|
||||
|
||||
If you want a Linux Endpoint Device to support accepting SSH connections using authentik credentials, you will need to install the `libpam-authentik` package in addition to the authentik Agent. This is a PAM Module, which provides token-based and interactive authentication via authentik.
|
||||
|
||||
Authentication is only possible if the Linux device is aware of the authentik user which is attempting to authenticate. This can be achieved in one of two ways:
|
||||
|
||||
1. **Provision user accounts** - Create users on the Linux device with usernames that match authentik users that need to authenticate to the device. This can be done manually or via automation tools like Ansible.
|
||||
2. **`libnss-authentik`** - This is a package that can be installed on the Linux device. It is an NSS module that makes the Linux device aware of authentik users. Similar to adding a Linux device to an Active Directory or LDAP domain.
|
||||
|
||||
### Install the `libpam-authentik` package _(required)_
|
||||
|
||||
:::info Prerequisites
|
||||
You must have already deployed and configured the authentik Agent on the device.
|
||||
:::
|
||||
|
||||
Run the following command to install the `libpam-authentik` package:
|
||||
|
||||
```sh
|
||||
sudo apt install libpam-authentik
|
||||
```
|
||||
|
||||
### Install the `libnss-authentik` package _(optional)_
|
||||
|
||||
Run the following command to install the `libnss-authentik` package:
|
||||
|
||||
```sh
|
||||
sudo apt install libnss-authentik
|
||||
```
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: Sudo authorization
|
||||
sidebar_label: Sudo authorization
|
||||
tags: [sudo, authentik Agent]
|
||||
---
|
||||
|
||||
You can use the [authentik Agent](../authentik-agent/index.mdx) to authorize sudo elevation when connected to a [Linux endpoint device via SSH](./ssh-authentication.mdx).
|
||||
|
||||
When you run a sudo command in this situation, the sudo authorization will be handled by the authentik Agent.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [authentik Agent needs to be deployed](../authentik-agent/agent-deployment/index.mdx) on the device.
|
||||
- Sudo authorization needs to be configured on the device, see the [Configure sudo authorization on an endpoint device](#configure-sudo-authorization-on-an-endpoint-device) section below.
|
||||
|
||||
## Configure sudo authorization on an endpoint device
|
||||
|
||||
If you want a Linux Endpoint Device to support authorizing using authentik credentials, you will need to install the `libpam-authentik` package in addition to the authentik Agent. This is a PAM Module, which provides token-based and interactive authentication via authentik.
|
||||
|
||||
Authorization is only possible if the Linux device is aware of the authentik user which is attempting to authorize. This can be achieved in one of two ways:
|
||||
|
||||
1. **Provision user accounts** - Create users on the Linux device with usernames that match authentik users that need to authorize sudo to the device. This can be done manually or via automation tools like Ansible.
|
||||
2. **`libnss-authentik`** - This is a package that can be installed on the Linux device. It is an NSS module that makes the Linux device aware of authentik users. Similar to adding a Linux device to an Active Directory or LDAP domain.
|
||||
|
||||
### Install the `libpam-authentik` package _(required)_
|
||||
|
||||
:::info Prerequisites
|
||||
You must have already deployed and configured the authentik Agent on the device.
|
||||
:::
|
||||
|
||||
Run the following command to install the `libpam-authentik` package:
|
||||
|
||||
```sh
|
||||
sudo apt install libpam-authentik
|
||||
```
|
||||
|
||||
### Install the `libnss-authentik` package _(optional)_
|
||||
|
||||
Run the following command to install the `libnss-authentik` package:
|
||||
|
||||
```sh
|
||||
sudo apt install libnss-authentik
|
||||
```
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: authentik browser extension
|
||||
sidebar_label: Browser extension
|
||||
tags: [device compliance, compliance, browser extension, extension]
|
||||
---
|
||||
|
||||
import TabItem from "@theme/TabItem";
|
||||
import Tabs from "@theme/Tabs";
|
||||
|
||||
The authentik browser extension is required for device compliance functionality, and is currently available for Chrome-based and Firefox-based browsers.
|
||||
|
||||
The browser extension connects to the [authentik Agent](../authentik-agent/index.mdx). It supplies [device facts](./device-reporting.md#device-facts) that [stages](../../add-secure-apps/flows-stages/stages/index.md) and [policies](../../customize/policies/index.md) can use during execution of authentik [flows](../../add-secure-apps/flows-stages/flow/index.md). This enables device compliance functionality such as limiting access to applications based on operating system, see [device compliance policy](./device-compliance-policy.md) for more details.
|
||||
|
||||
## Deploy the authentik browser extension
|
||||
|
||||
<Tabs
|
||||
defaultValue="chrome"
|
||||
values={[
|
||||
{label: 'Chrome-based', value: 'chrome'},
|
||||
{label: 'Firefox-based', value: 'firefox'},
|
||||
]}>
|
||||
<TabItem value="chrome">
|
||||
|
||||
(TODO - Jens finalizing adding extension to store)
|
||||
|
||||
1. Open Google Chrome.
|
||||
2. Go to the the authentik browser extension page in the [Chrome Web Store](https://chromewebstore.google.com).
|
||||
3. Click **Add to Chrome**.
|
||||
4. Review the permissions and click **Add extension**.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="firefox">
|
||||
|
||||
(TODO - Jens finalizing adding extension to store)
|
||||
|
||||
1. Open Firefox.
|
||||
2. Go to the the authentik browser extension page in the [Firefox Add-ons site](https://addons.mozilla.org).
|
||||
3. Click **Add to Firefox**.
|
||||
4. Review the permissions and click **Add** (or **Install**).
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Configuration verification
|
||||
|
||||
Verify that the extension shows as installed in your extensions list.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Configuration
|
||||
sidebar_label: Configuration
|
||||
tags: [device compliance, compliance, configuration]
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Device compliance functionality requires the following:
|
||||
|
||||
- [Configure authentik to support the authentik Agent](../authentik-agent/configuration.md).
|
||||
- [Deploy the authentik Agent on the device](../authentik-agent/agent-deployment/index.mdx).
|
||||
- [Deploy the authentik browser extension on the endpoint device](./browser-extension.mdx).
|
||||
|
||||
## Configuration verification
|
||||
|
||||
Verify that the endpoint device is reporting its facts on the [Devices](../manage-devices.mdx) page.
|
||||
|
||||
## Using device compliance
|
||||
|
||||
Now that the endpoint device is configured, you can create [device compliance policies](./device-compliance-policy.md) to control access to applications and more.
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: Connectors
|
||||
sidebar_label: Connectors
|
||||
tags: [device compliance, compliance, connectors, authentik Agent, fleet]
|
||||
---
|
||||
|
||||
Connectors allow device information to be reported to authentik.
|
||||
|
||||
They can be used standalone or alongside the [authentik Agent](../authentik-agent/index.mdx).
|
||||
|
||||
Currently, the only supported connectors is the [authentik Agent](#authentik-agent)
|
||||
|
||||
## Connectors
|
||||
|
||||
The following connectors are currently supported:
|
||||
|
||||
### authentik Agent
|
||||
|
||||
- Unlike other connectors, the agent connector is used by the agent directly compared to other connectors talking to separate systems and APIs to integrate with other agents. Hence the functionality of the agent connector behaves differently than other connectors.
|
||||
- the agent connector mainly holds configuration for the agent itself, as well as implementing certain platform specific protocols like Apple's Platform SSO.
|
||||
|
||||
## Adding a connector
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Endpoint Devices** > **Connectors** and click **Create**.
|
||||
3. Select the connector type and click **Next**, and configure the following required settings:
|
||||
- **Connector name**: provide a descriptive name for the connector.
|
||||
- **Refresh interval**: select how often the agent will attempt to update it's configuration.
|
||||
- **Enabled**: enable or disable the connector.
|
||||
4. Click **Finish**.
|
||||
|
||||
## Editing a connector
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Endpoint Devices** > **Connectors**.
|
||||
3. Click on the connector that you wish to edit.
|
||||
4. Update any settings that you want to change.
|
||||
5. Click **Update**.
|
||||
|
||||
## Deleting a connector
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Endpoint Devices** > **Connectors**.
|
||||
3. Select the connector that you wish to delete.
|
||||
4. Click **Delete**.
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
title: Device compliance policy
|
||||
sidebar_label: Device compliance policy
|
||||
tags: [device compliance, compliance, device access, policy]
|
||||
toc_max_heading_level: 4
|
||||
---
|
||||
|
||||
Device compliance policies are used to limit access to authentik and applications based on [Device Compliance](./index.mdx) information.
|
||||
|
||||
Device compliance policies are currently in development and inaccessible.
|
||||
|
||||
However, similar functionality can be achieved with existing stages and policies.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You must have [configured compliance](./configuration.md) in authentik and on the endpoint device.
|
||||
|
||||
## Accessing device facts within a flow
|
||||
|
||||
To access device facts within a flow, the flow must include an [Endpoint stage](../../add-secure-apps/flows-stages/stages/endpoint/index.md). The Endpoint stage fetches device facts via a configured [Connector](./connectors.md) and adds them to the [Flow context](../../add-secure-apps/flows-stages/flow/context/index.mdx).
|
||||
|
||||
The following example shows how to use these facts within an expression policy.
|
||||
|
||||
```python
|
||||
flow_plan = request.context.get("flow_plan") #set a flow_plan object
|
||||
device = flow_plan.context.get("device") #set a device object
|
||||
name = device.name #the name of the device
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
The following are examples of how device compliance can currently be implemented:
|
||||
|
||||
### Only allow authentication via endpoint devices
|
||||
|
||||
If your goal is to only allow authentication via endpoint devices, this is achievable by adding an [Endpoint stage](../../add-secure-apps/flows-stages/stages/endpoint/index.md) to your authentication flow.
|
||||
|
||||
#### Create an Endpoint stage
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Flows and Stages > Flows**.
|
||||
3. Select the authentication flow that you want to modify.
|
||||
4. Open the **Stage Bindings** tab and click **Create and bind stage**.
|
||||
5. Select Endpoint stage as the stage type, click **Next**, and configure the following settings:
|
||||
- **Name**: provide a name for the stage
|
||||
- **Connector**: select a connector for the stage to fetch device facts from (e.g. `authentik agent`)
|
||||
- **Mode**: set to `Device required`
|
||||
6. Click **Next**.
|
||||
7. Select the order for the stage. Ensure that this places the Endpoint stage in the flow wherever you want device access to be checked.
|
||||
8. Click **Finish**.
|
||||
|
||||
### Only allow authentication via a specific type of endpoint device
|
||||
|
||||
If your goal is to only allow authentication via a specific type of endpoint device, this is achievable by adding an [Endpoint stage](../../add-secure-apps/flows-stages/stages/endpoint/index.md) and a [Deny stage](../../add-secure-apps/flows-stages/stages/deny.md) to your authentication flow.
|
||||
|
||||
The following example will only allow authentication via Apple devices.
|
||||
|
||||
#### Create an Endpoint stage
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Flows and Stages > Flows**.
|
||||
3. Select the authentication flow that you want to modify.
|
||||
4. Open the **Stage Bindings** tab and click **Create and bind stage**.
|
||||
5. Select Endpoint stage as the stage type, click **Next**, and configure the following settings:
|
||||
- **Name**: provide a name for the stage
|
||||
- **Connector**: select a connector for the stage to fetch device facts from (e.g. `authentik agent`)
|
||||
- **Mode**: set to `Device required`
|
||||
6. Click **Next**.
|
||||
7. Select the **Order** for the stage. Ensure that this places the Endpoint stage in the flow wherever you want device access to be checked.
|
||||
8. Click **Finish**.
|
||||
|
||||
#### Create a Deny stage
|
||||
|
||||
9. On the **Stage Bindings** tab, click **Create and bind stage**.
|
||||
10. Select **Deny Stage** as the stage type and configure the following settings:
|
||||
- **Name**: provide a name for the stage
|
||||
- **Deny message**: provide a message explaining why access was denied
|
||||
11. Click **Next**.
|
||||
12. Select the **Order** for the stage. Ensure that this number is higher than the Endpoint stage created in the previous section.
|
||||
13. Click **Finish**.
|
||||
14. Expand the Deny stage that you just created and click **Create and bind Policy**.
|
||||
15. Select **Expression policy** as the policy type, click **Next**, and configure the following settings:
|
||||
- **Name**: provide a descriptive name for the policy
|
||||
- **Expression**:
|
||||
```python
|
||||
flow_plan = request.context.get("flow_plan")
|
||||
device = flow_plan.context.get("device")
|
||||
if device.manufacturer.lower() != "apple":
|
||||
return True
|
||||
```
|
||||
:::info Deny stage
|
||||
Because this is a deny stage, the policy must evaluate true when a requirement is not met.
|
||||
:::
|
||||
|
||||
16. Click **Next** and then click **Finish**.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: Device reporting
|
||||
sidebar_label: Device reporting
|
||||
tags:
|
||||
[
|
||||
device compliance,
|
||||
compliance,
|
||||
device facts,
|
||||
device reporting,
|
||||
device check-in,
|
||||
check-in,
|
||||
facts,
|
||||
]
|
||||
---
|
||||
|
||||
Endpoint Devices registered with authentik via a connector, such as the [authentik Agent](./connectors.md#authentik-agent) connector, regularly [check-in](#device-check-in) with authentik and report their [device facts](#device-facts).
|
||||
|
||||
These facts are shown on the [Devices](../manage-devices.mdx) page and are also accessible to policies and can be used to make policy decisions. See [Device Compliance Policy](./device-compliance-policy.md) for more details.
|
||||
|
||||
## Device check-in
|
||||
|
||||
When a device registered with authentik reports its [device facts](#device-facts), this is called a device check-in. These check-ins occur on a regular configurable schedule and can also be set to occur whenever a device is associated with an [Endpoint stage](../../add-secure-apps/flows-stages/stages/endpoint/index.md).
|
||||
|
||||
## Device facts
|
||||
|
||||
Device facts are informational snippets about a device, such as its operating system, serial number, installed applications, running processes, and more. These facts can are supplied to authentik flows via the [authentik browser extension](browser-extension.mdx) to be used in making policy decisions. For example, you can create a policy that only allows endpoint devices that are running a recent OS version to access an application.
|
||||
|
||||
### Advanced device facts :ak-enterprise
|
||||
|
||||
This feature is still in development and will be announced soon.
|
||||
|
||||
## Endpoint devices in event logs
|
||||
|
||||
Authentication events involving endpoint devices are included in the [event logs](../../sys-mgmt/events/logging-events.md). For example:
|
||||
|
||||

|
||||
|
||||
### Search for an endpoint device in the event logs :ak-enterprise
|
||||
|
||||
To search for event logs matching a specific endpoint device:
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Events** > **Logs**.
|
||||
3. In the search bar, enter: `context.device.name = "<device_name>"`
|
||||
|
||||
For more information on searching the events logs, see [Logging events](../../sys-mgmt/events/logging-events.md).
|
||||
14
website/docs/endpoint-devices/device-compliance/index.mdx
Normal file
14
website/docs/endpoint-devices/device-compliance/index.mdx
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Device Compliance
|
||||
sidebar_label: Device Compliance
|
||||
---
|
||||
|
||||
import DocCardList from "@theme/DocCardList";
|
||||
|
||||
Device compliance lets authentik verify that a user's device meets security and configuration criteria, such as operating system version, disk encryption, antivirus status etc, before allowing access to resources.
|
||||
|
||||
Device compliance is currently in very early preview. Device compliance policies are currently inaccessible and therefore have to be emulated with existing policies.
|
||||
|
||||
For more information, pick a topic below:
|
||||
|
||||
<DocCardList />
|
||||
61
website/docs/endpoint-devices/index.mdx
Normal file
61
website/docs/endpoint-devices/index.mdx
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: Endpoint Devices
|
||||
sidebar_label: Endpoint Devices
|
||||
---
|
||||
|
||||
import DocCardList from "@theme/DocCardList";
|
||||
|
||||
:::danger Early preview
|
||||
The endpoint devices feature set is currently in early preview and in development. **It is not recommended for production use.**
|
||||
|
||||
The features, methods, and even the name are yet to be confirmed.
|
||||
|
||||
Breaking changes and bugs should be expected.
|
||||
:::
|
||||
|
||||
:::info Enterprise License Required
|
||||
An enterprise license is required to access some of these features, refer to the [current features overview table](#features-overview) below for more details.
|
||||
|
||||
During this early preview stage, short trial licenses are available for testers. Please contact us via hello@goauthentik.io for more details.
|
||||
:::
|
||||
|
||||
## What are endpoint devices?
|
||||
|
||||
Endpoint devices are end-user devices or servers that are registered with authentik.
|
||||
|
||||
There are two purposes for registration: [Device authentication](./device-authentication/index.mdx) and [Device compliance](./device-compliance/index.mdx).
|
||||
|
||||
Devices can be registered by installing the [authentik Agent](./authentik-agent/index.mdx) which supports:
|
||||
|
||||
- [Device compliance](./device-compliance/index.mdx) by reporting information about endpoint devices to authentik.
|
||||
- [Local device login](./device-authentication/local-device-login/index.mdx) with authentik credentials.
|
||||
- [Connecting via SSH to endpoint devices](./device-authentication/ssh-authentication.mdx) with authentik credentials.
|
||||
- [Sudo authorization](./device-authentication/sudo-authorization.md) with authentik credentials.
|
||||
- [Authenticating to CLI applications](./device-authentication/cli-app-authentication/index.mdx) such as kubectl and AWS with authentik credentials.
|
||||
|
||||
Alternatively, [Connectors](./device-compliance/connectors.md) allow authentik to be integrated with third party services such as Fleet. This allows for device information to be reported to authentik for [Device compliance](./device-compliance/index.mdx) purposes.
|
||||
|
||||
## Features overview
|
||||
|
||||
| Feature | Linux | Windows | macOS | Status |
|
||||
| --------------------------------------------------------- | -------------- | -------------- | ----------------- | ------------------------------------------------------------------------------------------- |
|
||||
| **Local device login** | Open source | :ak-enterprise | :ak-enterprise \* | Available for early preview on Windows. |
|
||||
| **SSH authentication** | Open source | N/A | :ak-enterprise | Available for early preview. Only supports Linux SSH targets. macOS targets in development. |
|
||||
| **Sudo authorization** | Open source | N/A | N/A | Available for early preview. |
|
||||
| **Device compliance** | Open source | Open source | Open source | Available for early preview. |
|
||||
| **Advanced device compliance** | :ak-enterprise | :ak-enterprise | :ak-enterprise | In development. |
|
||||
| **authentik Agent ** | Open source | Open source | Open source | Available for early preview. |
|
||||
| **Fleet Connectors** | :ak-enterprise | :ak-enterprise | :ak-enterprise | Available for early preview. |
|
||||
| **Other Connectors** (Entra, Intune, Cloudflare WARP etc) | :ak-enterprise | :ak-enterprise | :ak-enterprise | In development. |
|
||||
|
||||
\*TODO: explain how PSSO works
|
||||
|
||||
## How to provide feedback and report bugs
|
||||
|
||||
Report issues via our [GitHub](https://github.com/goauthentik/platform/issues). Please include as much information as possible to assist us in troubleshooting.
|
||||
|
||||
## More information
|
||||
|
||||
For more information refer to each of the topics below:
|
||||
|
||||
<DocCardList />
|
||||
38
website/docs/endpoint-devices/manage-devices.mdx
Normal file
38
website/docs/endpoint-devices/manage-devices.mdx
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: Manage devices
|
||||
sidebar_label: Manage devices
|
||||
tags: [devices, device info, device facts, managing devices]
|
||||
---
|
||||
|
||||
The Devices page provides a list of all endpoint devices registered with your authentik deployment. Refer to [Device reporting](./device-compliance/device-reporting.md) for more details on how [device facts](./device-compliance/device-reporting.md#device-facts) are reported to authentik.
|
||||
|
||||
Each Endpoint Device can be expanded to view more detailed information.
|
||||
|
||||
## Accessing the Devices page
|
||||
|
||||
To access the Devices page, follow these steps:
|
||||
|
||||
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||
2. Navigate to **Endpoint Devices** > **Devices**.
|
||||
3. Specific endpoint device information can be accessed by clicking on a device.
|
||||
|
||||
## Specific device
|
||||
|
||||
Once you have selected a specific endpoint device you will have access to the following tabs:
|
||||
|
||||
### Overview
|
||||
|
||||
Provides an overview of the endpoint device:
|
||||
|
||||
- **Device details**: basic facts about the device: name, hostname, serial number, operating system, firewall status and device access group.
|
||||
- **Hardware**: basic hardware facts about the device: manufacturer, model, cpu, memory, disk encryption status, primary disk size, primary disk usage.
|
||||
- **Connections**: shows the current [connectors](./device-compliance/connectors.md) that are enabled for the device and when the last [check-in](./device-compliance/device-reporting.md#device-check-in) occurred.
|
||||
- **Users/Groups**: shows the users and groups that have access to the device. Controlled via [device access groups](./device-authentication/device-access-groups.mdx).
|
||||
|
||||
### Processes
|
||||
|
||||
Lists the processes that were running on the device when its last check-in occurred.
|
||||
|
||||
### Users
|
||||
|
||||
### Groups
|
||||
@@ -389,6 +389,34 @@ If you had persistence for Redis configured, you can delete the PVC and PV after
|
||||
- web/flows: improvements for hCaptcha (cherry-pick #16882 to version-2025.10) (#18128)
|
||||
- web/sfe: downgrade bootstrap that was accidentally upgraded (cherry-pick #18157 to version-2025.10) (#18171)
|
||||
|
||||
## Fixed in 2025.10.3
|
||||
|
||||
- core: list applications fix (cherry-pick #18798 to version-2025.10) (#18827)
|
||||
- core: optimize list applications (cherry-pick #18330 to version-2025.10) (#18791)
|
||||
- enterprise/stages/mtls: fix traefik certificate parsing (cherry-pick #18607 to version-2025.10) (#18645)
|
||||
- flows: refresh unauthenticated tabs (cherry-pick #18621 to version-2025.10) (#18633)
|
||||
- lib/sync/outgoing: check if there is a provider before creating tasks (cherry-pick #18394 to version-2025.10) (#18397)
|
||||
- outpost/proxyv2: more tests, fix pg password with spaces, and existing session on restart (cherry-pick #18211 to version-2025.10) (#18742)
|
||||
- outposts: set container healthcheck inline (cherry-pick #18298 to version-2025.10) (#18370)
|
||||
- packages/django-channels-postgres: fix notify size check (cherry-pick #18347 to version-2025.10) (#18409)
|
||||
- packages/django-dramatiq-postgres: broker: close django connections on consumer close (cherry-pick #18833 to version-2025.10) (#18835)
|
||||
- providers/scim: compare users/groups before sending update request (cherry-pick #18456 to version-2025.10) (#18465)
|
||||
- root: fix missing authentik_device cookie causing error (cherry-pick #18642 to version-2025.10) (#18644)
|
||||
- root: skip current tab when refreshing others (cherry-pick #18674 to version-2025.10) (#18675)
|
||||
- sources/ldap: make server info optional (cherry-pick #18648 to version-2025.10) (#18654)
|
||||
- stages/prompt: set allow_blank for \_read_only fields (cherry-pick #18297 to version-2025.10) (#18406)
|
||||
- web: Fix row expansion on modal trigger buttons. (cherry-pick #18412 to version-2025.10) (#18647)
|
||||
- web: Fix stale table rows (cherry-pick #17940 to version-2025.10) (#18373)
|
||||
- web: Fix stale table rows (cherry-pick #17940 to version-2025.10) (#18408)
|
||||
- web: Hide device picker when challenges are not present. (cherry-pick #18611 to version-2025.10) (#18681)
|
||||
- web: Improved table selection behavior (cherry-pick #18622 to version-2025.10) (#18685)
|
||||
- web: revert Fix stale table rows (cherry-pick #17940 to version-2025.10) (#18407)
|
||||
- web/admin: add entitlement search (cherry-pick #18291 to version-2025.10) (#18390)
|
||||
- web/admin: fix brands default switch label (cherry-pick #18518 to version-2025.10) (#18522)
|
||||
- web/admin: fix event volume chart not updating with query (cherry-pick #18649 to version-2025.10) (#18653)
|
||||
- web/admin: fix wording in password stage (cherry-pick #18393 to version-2025.10) (#18395)
|
||||
- web/admin: fixes capitalization in application wizard title (cherry-pick #17959 to version-2025.10) (#17962)
|
||||
|
||||
## API Changes
|
||||
|
||||
#### What's Changed
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -318,6 +318,7 @@ const items = [
|
||||
"add-secure-apps/flows-stages/stages/captcha/index",
|
||||
"add-secure-apps/flows-stages/stages/deny",
|
||||
"add-secure-apps/flows-stages/stages/email/index",
|
||||
"add-secure-apps/flows-stages/stages/endpoint/index",
|
||||
"add-secure-apps/flows-stages/stages/identification/index",
|
||||
"add-secure-apps/flows-stages/stages/invitation/index",
|
||||
"add-secure-apps/flows-stages/stages/mtls/index",
|
||||
@@ -675,6 +676,138 @@ const items = [
|
||||
{
|
||||
//#endregion
|
||||
|
||||
//#region Endpoint Devices
|
||||
type: "category",
|
||||
label: "Endpoint Devices (Early Preview)",
|
||||
collapsed: true,
|
||||
link: {
|
||||
type: "doc",
|
||||
id: "endpoint-devices/index",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
//#endregion
|
||||
|
||||
//#region authentik Agent
|
||||
type: "category",
|
||||
label: "authentik Agent",
|
||||
collapsed: true,
|
||||
link: {
|
||||
type: "doc",
|
||||
id: "endpoint-devices/authentik-agent/index",
|
||||
},
|
||||
items: [
|
||||
"endpoint-devices/authentik-agent/configuration",
|
||||
{
|
||||
//#endregion
|
||||
|
||||
//#region authentik Agent Deployment
|
||||
type: "category",
|
||||
label: "Deployment",
|
||||
collapsed: true,
|
||||
link: {
|
||||
type: "doc",
|
||||
id: "endpoint-devices/authentik-agent/agent-deployment/index",
|
||||
},
|
||||
items: [
|
||||
"endpoint-devices/authentik-agent/agent-deployment/mdm",
|
||||
"endpoint-devices/authentik-agent/agent-deployment/linux",
|
||||
"endpoint-devices/authentik-agent/agent-deployment/macos",
|
||||
"endpoint-devices/authentik-agent/agent-deployment/windows",
|
||||
],
|
||||
},
|
||||
"endpoint-devices/authentik-agent/authentik-cli",
|
||||
"endpoint-devices/authentik-agent/development",
|
||||
{
|
||||
//#endregion
|
||||
|
||||
//#region authentik Agent Release Notes
|
||||
type: "category",
|
||||
label: "Release notes",
|
||||
description: "Release Notes for recent authentik agent versions",
|
||||
collapsed: true,
|
||||
link: {
|
||||
type: "doc",
|
||||
id: "endpoint-devices/authentik-agent/release-notes/index",
|
||||
},
|
||||
items: ["endpoint-devices/authentik-agent/release-notes/temp"],
|
||||
},
|
||||
],
|
||||
},
|
||||
"endpoint-devices/manage-devices",
|
||||
{
|
||||
//#endregion
|
||||
|
||||
//#region Device Authentication
|
||||
type: "category",
|
||||
label: "Device authentication",
|
||||
collapsed: true,
|
||||
link: {
|
||||
type: "doc",
|
||||
id: "endpoint-devices/device-authentication/index",
|
||||
},
|
||||
items: [
|
||||
"endpoint-devices/device-authentication/device-access-groups",
|
||||
{
|
||||
//#endregion
|
||||
|
||||
//#region local device login
|
||||
type: "category",
|
||||
label: "Local device login",
|
||||
collapsed: true,
|
||||
link: {
|
||||
type: "doc",
|
||||
id: "endpoint-devices/device-authentication/local-device-login/index",
|
||||
},
|
||||
items: [
|
||||
"endpoint-devices/device-authentication/local-device-login/linux",
|
||||
"endpoint-devices/device-authentication/local-device-login/windows",
|
||||
],
|
||||
},
|
||||
"endpoint-devices/device-authentication/ssh-authentication",
|
||||
"endpoint-devices/device-authentication/sudo-authorization",
|
||||
{
|
||||
//#endregion
|
||||
|
||||
//#region cli app authentication
|
||||
type: "category",
|
||||
label: "CLI application authentication",
|
||||
collapsed: true,
|
||||
link: {
|
||||
type: "doc",
|
||||
id: "endpoint-devices/device-authentication/cli-app-authentication/index",
|
||||
},
|
||||
items: [
|
||||
"endpoint-devices/device-authentication/cli-app-authentication/aws",
|
||||
"endpoint-devices/device-authentication/cli-app-authentication/k8s",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
//#endregion
|
||||
|
||||
//#region Device Compliance
|
||||
type: "category",
|
||||
label: "Device compliance",
|
||||
collapsed: true,
|
||||
link: {
|
||||
type: "doc",
|
||||
id: "endpoint-devices/device-compliance/index",
|
||||
},
|
||||
items: [
|
||||
"endpoint-devices/device-compliance/configuration",
|
||||
"endpoint-devices/device-compliance/connectors",
|
||||
"endpoint-devices/device-compliance/device-reporting",
|
||||
"endpoint-devices/device-compliance/device-compliance-policy",
|
||||
"endpoint-devices/device-compliance/browser-extension",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
//#endregion
|
||||
|
||||
//#region Developer Documentation
|
||||
type: "category",
|
||||
label: "Developer Documentation",
|
||||
|
||||
@@ -50,7 +50,7 @@ To assign or remove _object_ permissions for a specific role:
|
||||
2. Select a specific role by clicking on the role's name.
|
||||
3. Click the **Permissions** tab at the top of the page, then click the **Permissions on this object** tab
|
||||
4. To assign permissions that another _role_ has on this specific role:
|
||||
1. Click **Assign role permissions**.
|
||||
1. Click **Assign Object Permission**.
|
||||
2. In the **Role** drop-down, select the role object.
|
||||
3. Use the toggles to set which permissions on that selected role object you want to grant to the specific role.
|
||||
4. Click **Assign** to save your settings and close the box.
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import "./styles.css";
|
||||
|
||||
import { createVersionURL, parseBranchSemVer } from "#components/VersionPicker/utils.ts";
|
||||
import {
|
||||
createVersionURL,
|
||||
normalizeReleaseName,
|
||||
parseBranchSemVer,
|
||||
} from "#components/VersionPicker/utils.ts";
|
||||
|
||||
import type {
|
||||
AKReleaseFrontMatter,
|
||||
@@ -74,15 +78,17 @@ export const VersionDropdown = memo<VersionDropdownProps>((props) => {
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{visibleReleases.map((semVer, idx) => {
|
||||
{visibleReleases.map((releaseName, idx) => {
|
||||
const semVer = normalizeReleaseName(releaseName);
|
||||
|
||||
let label = semVer;
|
||||
const frontmatter = frontMatterRecord[semVer];
|
||||
|
||||
if (frontmatter?.unlisted || frontmatter?.draft) {
|
||||
if (frontmatter?.draft) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (idx === 0) {
|
||||
if (idx === 0 && !frontmatter?.beta) {
|
||||
label += " (Current Release)";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AKReleasesPluginData } from "@goauthentik/docusaurus-theme/releases/plugin";
|
||||
import type { AKReleasesPluginData } from "@goauthentik/docusaurus-theme/releases/common";
|
||||
|
||||
import { usePluginData } from "@docusaurus/useGlobalData";
|
||||
import useIsBrowser from "@docusaurus/useIsBrowser";
|
||||
@@ -7,10 +7,15 @@ import { coerce } from "semver";
|
||||
|
||||
export const LocalhostAliases: ReadonlySet<string> = new Set(["localhost", "127.0.0.1"]);
|
||||
|
||||
export function normalizeReleaseName(name: string): string {
|
||||
return name.split(" ")[0] || name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a semver, create the URL for the version.
|
||||
*/
|
||||
export function createVersionURL(semver: string): string {
|
||||
export function createVersionURL(releaseName: string): string {
|
||||
const semver = normalizeReleaseName(releaseName);
|
||||
const subdomain = `version-${semver.replace(".", "-")}`;
|
||||
|
||||
return `https://${subdomain}.goauthentik.io`;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
/**
|
||||
* @typedef {object} AKReleaseFrontMatter
|
||||
* @property {boolean} [beta] Whether the release is a beta.
|
||||
* @property {boolean} [draft] Whether the release is a draft.
|
||||
* @property {boolean} [unlisted] Whether the release is unlisted.
|
||||
*/
|
||||
|
||||
@@ -58,6 +58,10 @@ export function collectReleaseFiles(releasesParentDirectory) {
|
||||
const { frontMatter } = parseFileContentFrontMatter(fileContent);
|
||||
|
||||
latestRelease.frontMatter = frontMatter;
|
||||
|
||||
if (latestRelease.frontMatter.beta) {
|
||||
latestRelease.name += " (Release Candidate)";
|
||||
}
|
||||
}
|
||||
|
||||
return releaseFiles;
|
||||
@@ -73,7 +77,14 @@ export function createReleaseSidebarEntries(releaseFiles) {
|
||||
/**
|
||||
* @type {SidebarItemConfig[]}
|
||||
*/
|
||||
let sidebarEntries = releaseFiles.map((fileEntry) => fileEntry.path);
|
||||
let sidebarEntries = releaseFiles.map((fileEntry) => {
|
||||
return {
|
||||
type: "doc",
|
||||
id: fileEntry.path,
|
||||
label: fileEntry.name,
|
||||
key: `release-${fileEntry.name}`,
|
||||
};
|
||||
});
|
||||
|
||||
if (releaseFiles.length > SUPPORTED_RELEASE_COUNT) {
|
||||
// Then we add the rest of the releases as a category.
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { useDoc } from "@docusaurus/plugin-content-docs/client";
|
||||
import {
|
||||
ThemeClassNames,
|
||||
UnlistedBannerMessage,
|
||||
UnlistedBannerTitle,
|
||||
UnlistedMetadata,
|
||||
} from "@docusaurus/theme-common";
|
||||
import Translate from "@docusaurus/Translate";
|
||||
import Admonition from "@theme/Admonition";
|
||||
import type { Props } from "@theme/ContentVisibility/Unlisted";
|
||||
import clsx from "clsx";
|
||||
import React, { type ReactNode } from "react";
|
||||
|
||||
function UnlistedBanner({ className }: Props) {
|
||||
const context = useDoc();
|
||||
|
||||
if (context.metadata.id?.startsWith("releases")) {
|
||||
return (
|
||||
<Admonition
|
||||
type="note"
|
||||
title={
|
||||
<Translate
|
||||
id="theme.contentVisibility.unlistedBanner.preRelease.title"
|
||||
description="The unlisted content banner title"
|
||||
>
|
||||
Pre-Release Documentation
|
||||
</Translate>
|
||||
}
|
||||
className={clsx(className, ThemeClassNames.common.unlistedBanner)}
|
||||
>
|
||||
<Translate
|
||||
id="theme.contentVisibility.unlistedBanner.preRelease.message"
|
||||
description="The unlisted content banner message"
|
||||
>
|
||||
This documentation is for an upcoming version of authentik. It may be incomplete
|
||||
or subject to changes before the final release.
|
||||
</Translate>
|
||||
</Admonition>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Admonition
|
||||
type="caution"
|
||||
title={<UnlistedBannerTitle />}
|
||||
className={clsx(className, ThemeClassNames.common.unlistedBanner)}
|
||||
>
|
||||
<UnlistedBannerMessage />
|
||||
</Admonition>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Unlisted(props: Props): ReactNode {
|
||||
return (
|
||||
<>
|
||||
<UnlistedMetadata />
|
||||
<UnlistedBanner {...props} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user