diff --git a/authentik/endpoints/api/device_access_group.py b/authentik/endpoints/api/device_access_group.py
index 72dc209c376b..29d64d1fd622 100644
--- a/authentik/endpoints/api/device_access_group.py
+++ b/authentik/endpoints/api/device_access_group.py
@@ -12,6 +12,7 @@ class Meta:
fields = [
"pbm_uuid",
"name",
+ "attributes",
]
diff --git a/authentik/endpoints/migrations/0004_deviceaccessgroup_attributes.py b/authentik/endpoints/migrations/0004_deviceaccessgroup_attributes.py
new file mode 100644
index 000000000000..1d705ab1a0af
--- /dev/null
+++ b/authentik/endpoints/migrations/0004_deviceaccessgroup_attributes.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.9 on 2025-12-08 23:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("authentik_endpoints", "0003_alter_endpointstage_options_endpointstage_mode"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="deviceaccessgroup",
+ name="attributes",
+ field=models.JSONField(blank=True, default=dict),
+ ),
+ ]
diff --git a/authentik/endpoints/models.py b/authentik/endpoints/models.py
index 52a908df7245..303d79e7ea05 100644
--- a/authentik/endpoints/models.py
+++ b/authentik/endpoints/models.py
@@ -175,7 +175,7 @@ def schedule_specs(self) -> list[ScheduleSpec]:
]
-class DeviceAccessGroup(PolicyBindingModel):
+class DeviceAccessGroup(AttributesMixin, PolicyBindingModel):
name = models.TextField(unique=True)
diff --git a/authentik/enterprise/endpoints/connectors/fleet/__init__.py b/authentik/enterprise/endpoints/connectors/fleet/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/authentik/enterprise/endpoints/connectors/fleet/api.py b/authentik/enterprise/endpoints/connectors/fleet/api.py
new file mode 100644
index 000000000000..90d11baa7a7a
--- /dev/null
+++ b/authentik/enterprise/endpoints/connectors/fleet/api.py
@@ -0,0 +1,37 @@
+"""FleetConnector API Views"""
+
+from rest_framework.viewsets import ModelViewSet
+
+from authentik.core.api.used_by import UsedByMixin
+from authentik.endpoints.api.connectors import ConnectorSerializer
+from authentik.enterprise.api import EnterpriseRequiredMixin
+from authentik.enterprise.endpoints.connectors.fleet.models import FleetConnector
+
+
+class FleetConnectorSerializer(EnterpriseRequiredMixin, ConnectorSerializer):
+ """FleetConnector Serializer"""
+
+ class Meta(ConnectorSerializer.Meta):
+ model = FleetConnector
+ fields = ConnectorSerializer.Meta.fields + [
+ "url",
+ "token",
+ "headers_mapping",
+ "map_users",
+ "map_teams_access_group",
+ ]
+ extra_kwargs = {
+ "token": {"write_only": True},
+ }
+
+
+class FleetConnectorViewSet(UsedByMixin, ModelViewSet):
+ """FleetConnector Viewset"""
+
+ queryset = FleetConnector.objects.all()
+ serializer_class = FleetConnectorSerializer
+ filterset_fields = [
+ "name",
+ ]
+ search_fields = ["name"]
+ ordering = ["name"]
diff --git a/authentik/enterprise/endpoints/connectors/fleet/apps.py b/authentik/enterprise/endpoints/connectors/fleet/apps.py
new file mode 100644
index 000000000000..f14f9cab7836
--- /dev/null
+++ b/authentik/enterprise/endpoints/connectors/fleet/apps.py
@@ -0,0 +1,12 @@
+"""authentik endpoints app config"""
+
+from authentik.enterprise.apps import EnterpriseConfig
+
+
+class AuthentikEnterpriseEndpointsConnectorFleetAppConfig(EnterpriseConfig):
+ """authentik endpoints app config"""
+
+ name = "authentik.enterprise.endpoints.connectors.fleet"
+ label = "authentik_endpoints_connectors_fleet"
+ verbose_name = "authentik Enterprise.Endpoints.Connectors.Fleet"
+ default = True
diff --git a/authentik/enterprise/endpoints/connectors/fleet/controller.py b/authentik/enterprise/endpoints/connectors/fleet/controller.py
new file mode 100644
index 000000000000..609017340a46
--- /dev/null
+++ b/authentik/enterprise/endpoints/connectors/fleet/controller.py
@@ -0,0 +1,189 @@
+from typing import Any
+
+from django.db import transaction
+from requests import RequestException
+from rest_framework.exceptions import ValidationError
+
+from authentik.core.models import User
+from authentik.endpoints.controller import BaseController, ConnectorSyncException, EnrollmentMethods
+from authentik.endpoints.facts import (
+ DeviceFacts,
+ OSFamily,
+)
+from authentik.endpoints.models import (
+ Device,
+ DeviceAccessGroup,
+ DeviceConnection,
+ DeviceUserBinding,
+)
+from authentik.enterprise.endpoints.connectors.fleet.models import FleetConnector as DBC
+from authentik.events.utils import sanitize_item
+from authentik.lib.utils.http import get_http_session
+from authentik.policies.utils import delete_none_values
+
+
+class FleetController(BaseController[DBC]):
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._session = get_http_session()
+ self._session.headers["Authorization"] = f"Bearer {self.connector.token}"
+ if self.connector.headers_mapping:
+ self._session.headers.update(
+ sanitize_item(
+ self.connector.headers_mapping.evaluate(
+ user=None,
+ request=None,
+ connector=self.connector,
+ )
+ )
+ )
+
+ def supported_enrollment_methods(self) -> list[EnrollmentMethods]:
+ return [EnrollmentMethods.AUTOMATIC_API]
+
+ def _url(self, path: str) -> str:
+ return f"{self.connector.url}{path}"
+
+ def _paginate_hosts(self):
+ try:
+ page = 1
+ while True:
+ res = self._session.get(
+ self._url("/api/v1/fleet/hosts"),
+ params={
+ "order_key": "hardware_serial",
+ "page": page,
+ "per_page": 50,
+ "device_mapping": "true",
+ "populate_software": "true",
+ "populate_users": "true",
+ },
+ )
+ res.raise_for_status()
+ hosts: list[dict[str, Any]] = res.json()["hosts"]
+ if len(hosts) < 1:
+ break
+ yield from hosts
+ page += 1
+ except RequestException as exc:
+ raise ConnectorSyncException(exc) from exc
+
+ @transaction.atomic
+ def sync_endpoints(self) -> None:
+ for host in self._paginate_hosts():
+ serial = host["hardware_serial"]
+ device, _ = Device.objects.get_or_create(
+ identifier=serial, defaults={"name": host["hostname"], "expiring": False}
+ )
+ connection, _ = DeviceConnection.objects.update_or_create(
+ device=device,
+ connector=self.connector,
+ )
+ if self.connector.map_users:
+ self.map_users(host, device)
+ if self.connector.map_teams_access_group:
+ self.map_access_group(host, device)
+ try:
+ connection.create_snapshot(self.convert_host_data(host))
+ except ValidationError as exc:
+ self.logger.warning(
+ "failed to create snapshot for host", host=host["hostname"], exc=exc
+ )
+
+ def map_users(self, host: dict[str, Any], device: Device):
+ for raw_user in host.get("device_mapping", []) or []:
+ user = User.objects.filter(email=raw_user["email"]).first()
+ if not user:
+ continue
+ DeviceUserBinding.objects.update_or_create(
+ target=device,
+ user=user,
+ create_defaults={
+ "is_primary": True,
+ "order": 0,
+ },
+ )
+
+ def map_access_group(self, host: dict[str, Any], device: Device):
+ team_name = host.get("team_name")
+ if not team_name:
+ return
+ group, _ = DeviceAccessGroup.objects.get_or_create(name=team_name)
+ group.attributes["io.goauthentik.endpoints.connectors.fleet.team_id"] = host["team_id"]
+ if device.access_group:
+ return
+ device.access_group = group
+ device.save()
+
+ @staticmethod
+ def os_family(host: dict[str, Any]) -> OSFamily:
+ if host["platform_like"] == "debian":
+ return OSFamily.linux
+ if host["platform_like"] == "windows":
+ return OSFamily.windows
+ if host["platform_like"] == "darwin":
+ return OSFamily.macOS
+ if host["platform"] == "android":
+ return OSFamily.android
+ if host["platform"] == "ipados" or host["platform"] == "ios":
+ return OSFamily.iOS
+ return OSFamily.other
+
+ def or_none(self, value) -> Any | None:
+ if value == "":
+ return None
+ return value
+
+ def convert_host_data(self, host: dict[str, Any]) -> dict[str, Any]:
+ """Convert host data from fleet to authentik"""
+ fleet_version = ""
+ for pkg in host.get("software") or []:
+ if pkg["name"] in ["fleet-osquery", "fleet-desktop"]:
+ fleet_version = pkg["version"]
+ data = {
+ "os": delete_none_values(
+ {
+ "arch": self.or_none(host["cpu_type"]),
+ "family": FleetController.os_family(host),
+ "name": self.or_none(host["platform_like"]),
+ "version": self.or_none(host["os_version"]),
+ }
+ ),
+ "disks": [],
+ "network": delete_none_values(
+ {"hostname": self.or_none(host["hostname"]), "interfaces": []}
+ ),
+ "hardware": delete_none_values(
+ {
+ "model": self.or_none(host["hardware_model"]),
+ "manufacturer": self.or_none(host["hardware_vendor"]),
+ "serial": self.or_none(host["hardware_serial"]),
+ "cpu_name": self.or_none(host["cpu_brand"]),
+ "cpu_count": self.or_none(host["cpu_logical_cores"]),
+ "memory_bytes": self.or_none(host["memory"]),
+ }
+ ),
+ "software": [
+ delete_none_values(
+ {
+ "name": x["name"],
+ "version": x["version"],
+ "source": x["source"],
+ }
+ )
+ for x in (host.get("software") or [])
+ ],
+ "vendor": {
+ "fleetdm.com": {
+ "policies": [
+ delete_none_values({"name": policy["name"], "status": policy["response"]})
+ for policy in host.get("policies", [])
+ ],
+ "agent_version": fleet_version,
+ },
+ },
+ }
+ facts = DeviceFacts(data=data)
+ facts.is_valid(raise_exception=True)
+ return facts.validated_data
diff --git a/authentik/enterprise/endpoints/connectors/fleet/migrations/0001_initial.py b/authentik/enterprise/endpoints/connectors/fleet/migrations/0001_initial.py
new file mode 100644
index 000000000000..68a0c179162b
--- /dev/null
+++ b/authentik/enterprise/endpoints/connectors/fleet/migrations/0001_initial.py
@@ -0,0 +1,53 @@
+# Generated by Django 5.2.9 on 2025-12-08 23:54
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ("authentik_endpoints", "0004_deviceaccessgroup_attributes"),
+ ("authentik_events", "0013_delete_systemtask"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="FleetConnector",
+ fields=[
+ (
+ "connector_ptr",
+ models.OneToOneField(
+ auto_created=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ parent_link=True,
+ primary_key=True,
+ serialize=False,
+ to="authentik_endpoints.connector",
+ ),
+ ),
+ ("url", models.URLField()),
+ ("token", models.TextField()),
+ ("map_users", models.BooleanField(default=True)),
+ ("map_teams_access_group", models.BooleanField(default=False)),
+ (
+ "headers_mapping",
+ models.ForeignKey(
+ default=None,
+ help_text="Configure additional headers to be sent. Mapping should return a dictionary of key-value pairs",
+ null=True,
+ on_delete=django.db.models.deletion.SET_DEFAULT,
+ related_name="+",
+ to="authentik_events.notificationwebhookmapping",
+ ),
+ ),
+ ],
+ options={
+ "verbose_name": "Fleet Connector",
+ "verbose_name_plural": "Fleet Connectors",
+ },
+ bases=("authentik_endpoints.connector",),
+ ),
+ ]
diff --git a/authentik/enterprise/endpoints/connectors/fleet/migrations/__init__.py b/authentik/enterprise/endpoints/connectors/fleet/migrations/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/authentik/enterprise/endpoints/connectors/fleet/models.py b/authentik/enterprise/endpoints/connectors/fleet/models.py
new file mode 100644
index 000000000000..f958ac7d766f
--- /dev/null
+++ b/authentik/enterprise/endpoints/connectors/fleet/models.py
@@ -0,0 +1,51 @@
+from typing import TYPE_CHECKING
+
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+from rest_framework.serializers import Serializer
+
+from authentik.endpoints.models import Connector
+
+if TYPE_CHECKING:
+ from authentik.enterprise.endpoints.connectors.fleet.controller import FleetController
+
+
+class FleetConnector(Connector):
+ """Ingest device data and policy compliance from a Fleet instance."""
+
+ url = models.URLField()
+ token = models.TextField()
+ headers_mapping = models.ForeignKey(
+ "authentik_events.NotificationWebhookMapping",
+ on_delete=models.SET_DEFAULT,
+ null=True,
+ default=None,
+ related_name="+",
+ help_text=_(
+ "Configure additional headers to be sent. "
+ "Mapping should return a dictionary of key-value pairs"
+ ),
+ )
+
+ map_users = models.BooleanField(default=True)
+ map_teams_access_group = models.BooleanField(default=False)
+
+ @property
+ def serializer(self) -> type[Serializer]:
+ from authentik.enterprise.endpoints.connectors.fleet.api import FleetConnectorSerializer
+
+ return FleetConnectorSerializer
+
+ @property
+ def controller(self) -> type["FleetController"]:
+ from authentik.enterprise.endpoints.connectors.fleet.controller import FleetController
+
+ return FleetController
+
+ @property
+ def component(self) -> str:
+ return "ak-endpoints-connector-fleet-form"
+
+ class Meta:
+ verbose_name = _("Fleet Connector")
+ verbose_name_plural = _("Fleet Connectors")
diff --git a/authentik/enterprise/endpoints/connectors/fleet/tests.py b/authentik/enterprise/endpoints/connectors/fleet/tests.py
new file mode 100644
index 000000000000..d23f3258f88f
--- /dev/null
+++ b/authentik/enterprise/endpoints/connectors/fleet/tests.py
@@ -0,0 +1,147 @@
+from requests_mock import Mocker
+from rest_framework.test import APITestCase
+
+from authentik.endpoints.models import Device
+from authentik.enterprise.endpoints.connectors.fleet.models import FleetConnector
+from authentik.events.models import NotificationWebhookMapping
+from authentik.lib.generators import generate_id
+
+TEST_HOST = {
+ "hosts": [
+ {
+ "created_at": "2025-09-20T17:46:17Z",
+ "updated_at": "2025-12-05T17:13:24Z",
+ "software": None,
+ "software_updated_at": "2025-12-05T17:13:24Z",
+ "id": 8,
+ "detail_updated_at": "2025-12-05T17:13:25Z",
+ "label_updated_at": "2025-12-05T17:13:25Z",
+ "policy_updated_at": "2025-12-05T16:41:15Z",
+ "last_enrolled_at": "2025-09-20T17:46:17Z",
+ "seen_time": "2025-12-05T17:38:07Z",
+ "refetch_requested": False,
+ "hostname": "oci-kube-1",
+ "uuid": "72a6f06a-d27d-45ec-996d-0ca298f50c61",
+ "platform": "ubuntu",
+ "osquery_version": "5.20.0",
+ "orbit_version": None,
+ "fleet_desktop_version": None,
+ "scripts_enabled": None,
+ "os_version": "Ubuntu 24.04.3 LTS",
+ "build": "",
+ "platform_like": "debian",
+ "code_name": "noble",
+ "uptime": 8454318000000000,
+ "memory": 25141047296,
+ "cpu_type": "aarch64",
+ "cpu_subtype": "0",
+ "cpu_brand": "",
+ "cpu_physical_cores": 4,
+ "cpu_logical_cores": 4,
+ "hardware_vendor": "QEMU",
+ "hardware_model": "KVM Virtual Machine",
+ "hardware_version": "virt-4.2",
+ "hardware_serial": "8A19B472-3FD7-475F-88F3-B2C2D0BC0D1A",
+ "computer_name": "oci-kube-1",
+ "public_ip": "130.61.116.187",
+ "primary_ip": "10.120.90.154",
+ "primary_mac": "02:00:17:01:d0:d2",
+ "distributed_interval": 10,
+ "config_tls_refresh": 60,
+ "logger_tls_period": 10,
+ "team_id": 2,
+ "pack_stats": None,
+ "team_name": "prod",
+ "gigs_disk_space_available": 74.88,
+ "percent_disk_space_available": 72,
+ "gigs_total_disk_space": 103.86,
+ "gigs_all_disk_space": 103.86,
+ "issues": {
+ "failing_policies_count": 0,
+ "critical_vulnerabilities_count": 0,
+ "total_issues_count": 0,
+ },
+ "device_mapping": None,
+ "mdm": {
+ "enrollment_status": None,
+ "dep_profile_error": False,
+ "server_url": None,
+ "name": "",
+ "encryption_key_available": False,
+ "connected_to_fleet": False,
+ },
+ "refetch_critical_queries_until": None,
+ "last_restarted_at": "2025-08-29T20:48:07Z",
+ "status": "online",
+ "display_text": "oci-kube-1",
+ "display_name": "oci-kube-1",
+ }
+ ]
+}
+
+
+class TestFleetConnector(APITestCase):
+
+ def test_sync(self):
+ connector = FleetConnector.objects.create(
+ name=generate_id(), url="http://localhost", token=generate_id()
+ )
+ controller = connector.controller(connector)
+ with Mocker() as mock:
+ mock.get(
+ "http://localhost/api/v1/fleet/hosts?order_key=hardware_serial&page=1&per_page=50&device_mapping=true&populate_software=true&populate_users=true",
+ json=TEST_HOST,
+ )
+ mock.get(
+ "http://localhost/api/v1/fleet/hosts?order_key=hardware_serial&page=2&per_page=50&device_mapping=true&populate_software=true&populate_users=true",
+ json={"hosts": []},
+ )
+ controller.sync_endpoints()
+ device = Device.objects.filter(identifier="8A19B472-3FD7-475F-88F3-B2C2D0BC0D1A").first()
+ self.assertIsNotNone(device)
+ self.assertEqual(
+ device.cached_facts.data,
+ {
+ "os": {
+ "arch": "aarch64",
+ "name": "debian",
+ "family": "linux",
+ "version": "Ubuntu 24.04.3 LTS",
+ },
+ "disks": [],
+ "vendor": {"fleetdm.com": {"policies": [], "agent_version": ""}},
+ "network": {"hostname": "oci-kube-1", "interfaces": []},
+ "hardware": {
+ "model": "KVM Virtual Machine",
+ "serial": "8A19B472-3FD7-475F-88F3-B2C2D0BC0D1A",
+ "cpu_count": 4,
+ "manufacturer": "QEMU",
+ "memory_bytes": 25141047296,
+ },
+ "software": [],
+ },
+ )
+
+ def test_sync_headers(self):
+ mapping = NotificationWebhookMapping.objects.create(
+ name=generate_id(), expression="""return {"foo": "bar"}"""
+ )
+ connector = FleetConnector.objects.create(
+ name=generate_id(), url="http://localhost", token=generate_id(), headers_mapping=mapping
+ )
+ controller = connector.controller(connector)
+ with Mocker() as mock:
+ mock.get(
+ "http://localhost/api/v1/fleet/hosts?order_key=hardware_serial&page=1&per_page=50&device_mapping=true&populate_software=true&populate_users=true",
+ json=TEST_HOST,
+ )
+ mock.get(
+ "http://localhost/api/v1/fleet/hosts?order_key=hardware_serial&page=2&per_page=50&device_mapping=true&populate_software=true&populate_users=true",
+ json={"hosts": []},
+ )
+ controller.sync_endpoints()
+ self.assertEqual(mock.call_count, 2)
+ self.assertEqual(mock.request_history[0].method, "GET")
+ self.assertEqual(mock.request_history[0].headers["foo"], "bar")
+ self.assertEqual(mock.request_history[1].method, "GET")
+ self.assertEqual(mock.request_history[1].headers["foo"], "bar")
diff --git a/authentik/enterprise/endpoints/connectors/fleet/urls.py b/authentik/enterprise/endpoints/connectors/fleet/urls.py
new file mode 100644
index 000000000000..212a5bf79ddf
--- /dev/null
+++ b/authentik/enterprise/endpoints/connectors/fleet/urls.py
@@ -0,0 +1,3 @@
+from authentik.enterprise.endpoints.connectors.fleet.api import FleetConnectorViewSet
+
+api_urlpatterns = [("endpoints/fleet/connectors", FleetConnectorViewSet)]
diff --git a/authentik/enterprise/settings.py b/authentik/enterprise/settings.py
index 720d7dd00045..e7455b2da9eb 100644
--- a/authentik/enterprise/settings.py
+++ b/authentik/enterprise/settings.py
@@ -3,6 +3,7 @@
TENANT_APPS = [
"authentik.enterprise.audit",
"authentik.enterprise.endpoints.connectors.agent",
+ "authentik.enterprise.endpoints.connectors.fleet",
"authentik.enterprise.policies.unique_password",
"authentik.enterprise.providers.google_workspace",
"authentik.enterprise.providers.microsoft_entra",
diff --git a/blueprints/schema.json b/blueprints/schema.json
index efd00c40595e..612475720739 100644
--- a/blueprints/schema.json
+++ b/blueprints/schema.json
@@ -616,6 +616,46 @@
}
}
},
+ {
+ "type": "object",
+ "required": [
+ "model",
+ "identifiers"
+ ],
+ "properties": {
+ "model": {
+ "const": "authentik_endpoints_connectors_fleet.fleetconnector"
+ },
+ "id": {
+ "type": "string"
+ },
+ "state": {
+ "type": "string",
+ "enum": [
+ "absent",
+ "created",
+ "must_created",
+ "present"
+ ],
+ "default": "present"
+ },
+ "conditions": {
+ "type": "array",
+ "items": {
+ "type": "boolean"
+ }
+ },
+ "permissions": {
+ "$ref": "#/$defs/model_authentik_endpoints_connectors_fleet.fleetconnector_permissions"
+ },
+ "attrs": {
+ "$ref": "#/$defs/model_authentik_endpoints_connectors_fleet.fleetconnector"
+ },
+ "identifiers": {
+ "$ref": "#/$defs/model_authentik_endpoints_connectors_fleet.fleetconnector"
+ }
+ }
+ },
{
"type": "object",
"required": [
@@ -5350,6 +5390,10 @@
"authentik_endpoints_connectors_agent.view_devicetoken",
"authentik_endpoints_connectors_agent.view_enrollment_token_key",
"authentik_endpoints_connectors_agent.view_enrollmenttoken",
+ "authentik_endpoints_connectors_fleet.add_fleetconnector",
+ "authentik_endpoints_connectors_fleet.change_fleetconnector",
+ "authentik_endpoints_connectors_fleet.delete_fleetconnector",
+ "authentik_endpoints_connectors_fleet.view_fleetconnector",
"authentik_enterprise.add_license",
"authentik_enterprise.add_licenseusage",
"authentik_enterprise.change_license",
@@ -6427,6 +6471,77 @@
}
}
},
+ "model_authentik_endpoints_connectors_fleet.fleetconnector": {
+ "type": "object",
+ "properties": {
+ "connector_uuid": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Connector uuid"
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "title": "Name"
+ },
+ "enabled": {
+ "type": "boolean",
+ "title": "Enabled"
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "maxLength": 200,
+ "minLength": 1,
+ "title": "Url"
+ },
+ "token": {
+ "type": "string",
+ "minLength": 1,
+ "title": "Token"
+ },
+ "headers_mapping": {
+ "type": "integer",
+ "title": "Headers mapping",
+ "description": "Configure additional headers to be sent. Mapping should return a dictionary of key-value pairs"
+ },
+ "map_users": {
+ "type": "boolean",
+ "title": "Map users"
+ },
+ "map_teams_access_group": {
+ "type": "boolean",
+ "title": "Map teams access group"
+ }
+ },
+ "required": []
+ },
+ "model_authentik_endpoints_connectors_fleet.fleetconnector_permissions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "permission"
+ ],
+ "properties": {
+ "permission": {
+ "type": "string",
+ "enum": [
+ "add_fleetconnector",
+ "change_fleetconnector",
+ "delete_fleetconnector",
+ "view_fleetconnector"
+ ]
+ },
+ "user": {
+ "type": "integer"
+ },
+ "role": {
+ "type": "string"
+ }
+ }
+ }
+ },
"model_authentik_enterprise.license": {
"type": "object",
"properties": {
@@ -8293,6 +8408,7 @@
"authentik.blueprints",
"authentik.enterprise.audit",
"authentik.enterprise.endpoints.connectors.agent",
+ "authentik.enterprise.endpoints.connectors.fleet",
"authentik.enterprise.policies.unique_password",
"authentik.enterprise.providers.google_workspace",
"authentik.enterprise.providers.microsoft_entra",
@@ -8418,6 +8534,7 @@
"authentik_tasks_schedules.schedule",
"authentik_brands.brand",
"authentik_blueprints.blueprintinstance",
+ "authentik_endpoints_connectors_fleet.fleetconnector",
"authentik_policies_unique_password.uniquepasswordpolicy",
"authentik_providers_google_workspace.googleworkspaceprovider",
"authentik_providers_google_workspace.googleworkspaceprovidermapping",
@@ -10678,6 +10795,10 @@
"authentik_endpoints_connectors_agent.view_devicetoken",
"authentik_endpoints_connectors_agent.view_enrollment_token_key",
"authentik_endpoints_connectors_agent.view_enrollmenttoken",
+ "authentik_endpoints_connectors_fleet.add_fleetconnector",
+ "authentik_endpoints_connectors_fleet.change_fleetconnector",
+ "authentik_endpoints_connectors_fleet.delete_fleetconnector",
+ "authentik_endpoints_connectors_fleet.view_fleetconnector",
"authentik_enterprise.add_license",
"authentik_enterprise.add_licenseusage",
"authentik_enterprise.change_license",
diff --git a/schema.yml b/schema.yml
index 254f3a3b65fe..b4e06172156b 100644
--- a/schema.yml
+++ b/schema.yml
@@ -6237,6 +6237,196 @@ paths:
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
+ /endpoints/fleet/connectors/:
+ get:
+ operationId: endpoints_fleet_connectors_list
+ description: FleetConnector Viewset
+ parameters:
+ - $ref: '#/components/parameters/QueryName'
+ - $ref: '#/components/parameters/QueryPaginationOrdering'
+ - $ref: '#/components/parameters/QueryPaginationPage'
+ - $ref: '#/components/parameters/QueryPaginationPageSize'
+ - $ref: '#/components/parameters/QuerySearch'
+ tags:
+ - endpoints
+ security:
+ - authentik: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PaginatedFleetConnectorList'
+ description: ''
+ '400':
+ $ref: '#/components/responses/ValidationErrorResponse'
+ '403':
+ $ref: '#/components/responses/GenericErrorResponse'
+ post:
+ operationId: endpoints_fleet_connectors_create
+ description: FleetConnector Viewset
+ tags:
+ - endpoints
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/FleetConnectorRequest'
+ required: true
+ security:
+ - authentik: []
+ responses:
+ '201':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/FleetConnector'
+ description: ''
+ '400':
+ $ref: '#/components/responses/ValidationErrorResponse'
+ '403':
+ $ref: '#/components/responses/GenericErrorResponse'
+ /endpoints/fleet/connectors/{connector_uuid}/:
+ get:
+ operationId: endpoints_fleet_connectors_retrieve
+ description: FleetConnector Viewset
+ parameters:
+ - in: path
+ name: connector_uuid
+ schema:
+ type: string
+ format: uuid
+ description: A UUID string identifying this Fleet Connector.
+ required: true
+ tags:
+ - endpoints
+ security:
+ - authentik: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/FleetConnector'
+ description: ''
+ '400':
+ $ref: '#/components/responses/ValidationErrorResponse'
+ '403':
+ $ref: '#/components/responses/GenericErrorResponse'
+ put:
+ operationId: endpoints_fleet_connectors_update
+ description: FleetConnector Viewset
+ parameters:
+ - in: path
+ name: connector_uuid
+ schema:
+ type: string
+ format: uuid
+ description: A UUID string identifying this Fleet Connector.
+ required: true
+ tags:
+ - endpoints
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/FleetConnectorRequest'
+ required: true
+ security:
+ - authentik: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/FleetConnector'
+ description: ''
+ '400':
+ $ref: '#/components/responses/ValidationErrorResponse'
+ '403':
+ $ref: '#/components/responses/GenericErrorResponse'
+ patch:
+ operationId: endpoints_fleet_connectors_partial_update
+ description: FleetConnector Viewset
+ parameters:
+ - in: path
+ name: connector_uuid
+ schema:
+ type: string
+ format: uuid
+ description: A UUID string identifying this Fleet Connector.
+ required: true
+ tags:
+ - endpoints
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PatchedFleetConnectorRequest'
+ security:
+ - authentik: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/FleetConnector'
+ description: ''
+ '400':
+ $ref: '#/components/responses/ValidationErrorResponse'
+ '403':
+ $ref: '#/components/responses/GenericErrorResponse'
+ delete:
+ operationId: endpoints_fleet_connectors_destroy
+ description: FleetConnector Viewset
+ parameters:
+ - in: path
+ name: connector_uuid
+ schema:
+ type: string
+ format: uuid
+ description: A UUID string identifying this Fleet Connector.
+ required: true
+ tags:
+ - endpoints
+ security:
+ - authentik: []
+ responses:
+ '204':
+ description: No response body
+ '400':
+ $ref: '#/components/responses/ValidationErrorResponse'
+ '403':
+ $ref: '#/components/responses/GenericErrorResponse'
+ /endpoints/fleet/connectors/{connector_uuid}/used_by/:
+ get:
+ operationId: endpoints_fleet_connectors_used_by_list
+ description: Get a list of all objects that use this object
+ parameters:
+ - in: path
+ name: connector_uuid
+ schema:
+ type: string
+ format: uuid
+ description: A UUID string identifying this Fleet Connector.
+ required: true
+ tags:
+ - endpoints
+ security:
+ - authentik: []
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/UsedBy'
+ description: ''
+ '400':
+ $ref: '#/components/responses/ValidationErrorResponse'
+ '403':
+ $ref: '#/components/responses/GenericErrorResponse'
/enterprise/license/:
get:
operationId: enterprise_license_list
@@ -19577,6 +19767,7 @@ paths:
- authentik_endpoints_connectors_agent.agentconnector
- authentik_endpoints_connectors_agent.agentdeviceuserbinding
- authentik_endpoints_connectors_agent.enrollmenttoken
+ - authentik_endpoints_connectors_fleet.fleetconnector
- authentik_enterprise.license
- authentik_events.event
- authentik_events.notification
@@ -32681,6 +32872,7 @@ components:
- authentik.blueprints
- authentik.enterprise.audit
- authentik.enterprise.endpoints.connectors.agent
+ - authentik.enterprise.endpoints.connectors.fleet
- authentik.enterprise.policies.unique_password
- authentik.enterprise.providers.google_workspace
- authentik.enterprise.providers.microsoft_entra
@@ -35529,6 +35721,9 @@ components:
readOnly: true
name:
type: string
+ attributes:
+ type: object
+ additionalProperties: {}
required:
- name
- pbm_uuid
@@ -35538,6 +35733,9 @@ components:
name:
type: string
minLength: 1
+ attributes:
+ type: object
+ additionalProperties: {}
required:
- name
DeviceChallenge:
@@ -37311,6 +37509,89 @@ components:
default: media
required:
- file
+ FleetConnector:
+ type: object
+ description: FleetConnector Serializer
+ properties:
+ connector_uuid:
+ type: string
+ format: uuid
+ name:
+ type: string
+ enabled:
+ type: boolean
+ component:
+ type: string
+ description: Get object component so that we know how to edit the object
+ readOnly: true
+ verbose_name:
+ type: string
+ description: Return object's verbose_name
+ readOnly: true
+ verbose_name_plural:
+ type: string
+ description: Return object's plural verbose_name
+ readOnly: true
+ meta_model_name:
+ type: string
+ description: Return internal model name
+ readOnly: true
+ url:
+ type: string
+ format: uri
+ maxLength: 200
+ headers_mapping:
+ type: string
+ format: uuid
+ nullable: true
+ description: Configure additional headers to be sent. Mapping should return
+ a dictionary of key-value pairs
+ map_users:
+ type: boolean
+ map_teams_access_group:
+ type: boolean
+ required:
+ - component
+ - meta_model_name
+ - name
+ - url
+ - verbose_name
+ - verbose_name_plural
+ FleetConnectorRequest:
+ type: object
+ description: FleetConnector Serializer
+ properties:
+ connector_uuid:
+ type: string
+ format: uuid
+ name:
+ type: string
+ minLength: 1
+ enabled:
+ type: boolean
+ url:
+ type: string
+ format: uri
+ minLength: 1
+ maxLength: 200
+ token:
+ type: string
+ writeOnly: true
+ minLength: 1
+ headers_mapping:
+ type: string
+ format: uuid
+ nullable: true
+ description: Configure additional headers to be sent. Mapping should return
+ a dictionary of key-value pairs
+ map_users:
+ type: boolean
+ map_teams_access_group:
+ type: boolean
+ required:
+ - name
+ - token
+ - url
Flow:
type: object
description: Flow Serializer
@@ -41048,6 +41329,7 @@ components:
- authentik_tasks_schedules.schedule
- authentik_brands.brand
- authentik_blueprints.blueprintinstance
+ - authentik_endpoints_connectors_fleet.fleetconnector
- authentik_policies_unique_password.uniquepasswordpolicy
- authentik_providers_google_workspace.googleworkspaceprovider
- authentik_providers_google_workspace.googleworkspaceprovidermapping
@@ -43009,6 +43291,21 @@ components:
required:
- pagination
- results
+ PaginatedFleetConnectorList:
+ type: object
+ properties:
+ pagination:
+ $ref: '#/components/schemas/Pagination'
+ results:
+ type: array
+ items:
+ $ref: '#/components/schemas/FleetConnector'
+ autocomplete:
+ $ref: '#/components/schemas/Autocomplete'
+ required:
+ - pagination
+ - results
+ - autocomplete
PaginatedFlowList:
type: object
properties:
@@ -45674,6 +45971,9 @@ components:
name:
type: string
minLength: 1
+ attributes:
+ type: object
+ additionalProperties: {}
PatchedDeviceUserBindingRequest:
type: object
description: PolicyBinding Serializer
@@ -46030,6 +46330,37 @@ components:
expression:
type: string
minLength: 1
+ PatchedFleetConnectorRequest:
+ type: object
+ description: FleetConnector Serializer
+ properties:
+ connector_uuid:
+ type: string
+ format: uuid
+ name:
+ type: string
+ minLength: 1
+ enabled:
+ type: boolean
+ url:
+ type: string
+ format: uri
+ minLength: 1
+ maxLength: 200
+ token:
+ type: string
+ writeOnly: true
+ minLength: 1
+ headers_mapping:
+ type: string
+ format: uuid
+ nullable: true
+ description: Configure additional headers to be sent. Mapping should return
+ a dictionary of key-value pairs
+ map_users:
+ type: boolean
+ map_teams_access_group:
+ type: boolean
PatchedFlowRequest:
type: object
description: Flow Serializer
diff --git a/web/src/admin/endpoints/connectors/ConnectorViewPage.ts b/web/src/admin/endpoints/connectors/ConnectorViewPage.ts
index c98b9eb36046..aa9f8b420e1e 100644
--- a/web/src/admin/endpoints/connectors/ConnectorViewPage.ts
+++ b/web/src/admin/endpoints/connectors/ConnectorViewPage.ts
@@ -1,4 +1,5 @@
import "#admin/endpoints/connectors/agent/AgentConnectorViewPage";
+import "#admin/endpoints/connectors/fleet/FleetConnectorViewPage";
import "#elements/EmptyState";
import "#elements/buttons/SpinnerButton/ak-spinner-button";
@@ -41,6 +42,10 @@ export class ConnectorViewPage extends AKElement {
return html`
Invalid connector type ${this.connector?.component}
`; } diff --git a/web/src/admin/endpoints/connectors/ConnectorWizard.ts b/web/src/admin/endpoints/connectors/ConnectorWizard.ts index 2f056f845d0b..2ee2fa1db62b 100644 --- a/web/src/admin/endpoints/connectors/ConnectorWizard.ts +++ b/web/src/admin/endpoints/connectors/ConnectorWizard.ts @@ -1,5 +1,6 @@ import "#admin/common/ak-license-notice"; import "#admin/endpoints/connectors/agent/AgentConnectorForm"; +import "#admin/endpoints/connectors/fleet/FleetConnectorForm"; import "#elements/forms/ProxyForm"; import "#elements/wizard/FormWizardPage"; import "#elements/wizard/TypeCreateWizardPage"; diff --git a/web/src/admin/endpoints/connectors/ConnectorsListPage.ts b/web/src/admin/endpoints/connectors/ConnectorsListPage.ts index 52b81580d12b..359a8a89ca91 100644 --- a/web/src/admin/endpoints/connectors/ConnectorsListPage.ts +++ b/web/src/admin/endpoints/connectors/ConnectorsListPage.ts @@ -1,5 +1,6 @@ import "#admin/endpoints/connectors/ConnectorWizard"; import "#admin/endpoints/connectors/agent/AgentConnectorForm"; +import "#admin/endpoints/connectors/fleet/FleetConnectorForm"; import "#elements/forms/DeleteBulkForm"; import "#elements/forms/ProxyForm"; import "#elements/forms/ModalForm"; diff --git a/web/src/admin/endpoints/connectors/fleet/FleetConnectorForm.ts b/web/src/admin/endpoints/connectors/fleet/FleetConnectorForm.ts new file mode 100644 index 000000000000..804688f9803d --- /dev/null +++ b/web/src/admin/endpoints/connectors/fleet/FleetConnectorForm.ts @@ -0,0 +1,95 @@ +import "#components/ak-secret-text-input"; +import "#components/ak-switch-input"; +import "#components/ak-text-input"; +import "#elements/forms/FormGroup"; + +import { DEFAULT_CONFIG } from "#common/api/config"; + +import { ModelForm } from "#elements/forms/ModelForm"; + +import { EndpointsApi, FleetConnector, FleetConnectorRequest } from "@goauthentik/api"; + +import { msg } from "@lit/localize"; +import { html } from "lit"; +import { customElement } from "lit/decorators.js"; + +@customElement("ak-endpoints-connector-fleet-form") +export class FleetConnectorForm extends ModelForm