mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
endpoints: FleetDM connector (#18589)
* enterprise/endpoints/connectors/fleet: init Signed-off-by: Jens Langhammer <jens@goauthentik.io> # Conflicts: # blueprints/schema.json # schema.yml * add ui Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix desc Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add configurable headers Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * Address review feedback on FleetDM connector implementation (#18651) * Initial plan * Add public override modifiers to updated method Co-authored-by: GirlBossRush <592134+GirlBossRush@users.noreply.github.com> * Address additional feedback from PR #18589 Co-authored-by: GirlBossRush <592134+GirlBossRush@users.noreply.github.com> * Fix indentation in ak-switch-input component Co-authored-by: GirlBossRush <592134+GirlBossRush@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: GirlBossRush <592134+GirlBossRush@users.noreply.github.com> * fix permission model Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add attributes to device access group Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add option to map device team Signed-off-by: Jens Langhammer <jens@goauthentik.io> * cleanup Signed-off-by: Jens Langhammer <jens@goauthentik.io> * update schema Signed-off-by: Jens Langhammer <jens@goauthentik.io> * format Signed-off-by: Jens Langhammer <jens@goauthentik.io> * switch connector to grid, add icons Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix pagination Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add software tab Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix pages in test Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add more test devices Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add fedora test machine Signed-off-by: Jens Langhammer <jens@goauthentik.io> * better formatting for OS version Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: GirlBossRush <592134+GirlBossRush@users.noreply.github.com>
This commit is contained in:
@@ -12,6 +12,7 @@ class DeviceAccessGroupSerializer(ModelSerializer):
|
||||
fields = [
|
||||
"pbm_uuid",
|
||||
"name",
|
||||
"attributes",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from typing import TYPE_CHECKING
|
||||
from uuid import uuid4
|
||||
|
||||
from django.db import models
|
||||
from django.templatetags.static import static
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.serializers import Serializer
|
||||
|
||||
@@ -51,6 +52,10 @@ class AgentConnector(Connector):
|
||||
)
|
||||
challenge_trigger_check_in = models.BooleanField(default=False)
|
||||
|
||||
@property
|
||||
def icon_url(self):
|
||||
return static("icons/icon.svg")
|
||||
|
||||
@property
|
||||
def serializer(self) -> type[Serializer]:
|
||||
from authentik.endpoints.connectors.agent.api.connectors import (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django.db.models import TextChoices
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from drf_spectacular.extensions import OpenApiSerializerFieldExtension
|
||||
from drf_spectacular.plumbing import build_basic_type
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
@@ -15,7 +16,6 @@ from authentik.core.api.utils import JSONDictField
|
||||
|
||||
|
||||
class BigIntegerFieldFix(OpenApiSerializerFieldExtension):
|
||||
|
||||
target_class = "authentik.endpoints.facts.BigIntegerField"
|
||||
|
||||
def map_serializer_field(self, auto_schema, direction):
|
||||
@@ -46,9 +46,23 @@ class DiskSerializer(Serializer):
|
||||
|
||||
|
||||
class OperatingSystemSerializer(Serializer):
|
||||
"""For example:
|
||||
{"family":"linux","name":"Ubuntu","version":"24.04.3 LTS (Noble Numbat)","arch":"amd64"}
|
||||
{"family": "windows","name":"Server 2022 Datacenter","version":"10.0.20348.4405","arch":"amd64"}
|
||||
{"family": "windows","name":"Server 2022 Datacenter","version":"10.0.20348.4405","arch":"amd64"}
|
||||
{"family": "mac_os", "name": "", "version": "26.2", "arch": "arm64"}
|
||||
"""
|
||||
|
||||
family = ChoiceField(OSFamily.choices, required=True)
|
||||
name = CharField(required=False)
|
||||
version = CharField(required=False)
|
||||
name = CharField(
|
||||
required=False, help_text=_("Operating System name, such as 'Server 2022' or 'Ubuntu'")
|
||||
)
|
||||
version = CharField(
|
||||
required=False,
|
||||
help_text=_(
|
||||
"Operating System version, must always be the version number but may contain build name"
|
||||
),
|
||||
)
|
||||
arch = CharField(required=True)
|
||||
|
||||
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -175,7 +175,7 @@ class Connector(ScheduledModel, SerializerModel):
|
||||
]
|
||||
|
||||
|
||||
class DeviceAccessGroup(SerializerModel, PolicyBindingModel):
|
||||
class DeviceAccessGroup(AttributesMixin, SerializerModel, PolicyBindingModel):
|
||||
|
||||
name = models.TextField(unique=True)
|
||||
|
||||
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
@@ -0,0 +1,206 @@
|
||||
import re
|
||||
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,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def vendor_identifier() -> str:
|
||||
return "fleetdm.com"
|
||||
|
||||
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 = 0
|
||||
while True:
|
||||
self.logger.info("Fetching page of hosts...", page=page)
|
||||
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:
|
||||
self.logger.info("No more hosts, finished")
|
||||
break
|
||||
self.logger.info("Got hosts", count=len(hosts))
|
||||
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"] in ["debian", "rhel"]:
|
||||
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"] in ["ipados", "ios"]:
|
||||
return OSFamily.iOS
|
||||
return OSFamily.other
|
||||
|
||||
def map_os(self, host: dict[str, Any]) -> dict[str, str]:
|
||||
family = FleetController.os_family(host)
|
||||
os = {
|
||||
"arch": self.or_none(host["cpu_type"]),
|
||||
"family": family,
|
||||
"name": self.or_none(host["platform_like"]),
|
||||
"version": self.or_none(host["os_version"]),
|
||||
}
|
||||
if not host["os_version"]:
|
||||
return delete_none_values(os)
|
||||
version = re.search(r"(\d+\.(?:\d+\.?)+)", host["os_version"])
|
||||
if not version:
|
||||
return delete_none_values(os)
|
||||
os["version"] = host["os_version"][version.start() :].strip()
|
||||
os["name"] = host["os_version"][0 : version.start()].strip()
|
||||
return delete_none_values(os)
|
||||
|
||||
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": self.map_os(host),
|
||||
"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
|
||||
@@ -0,0 +1,53 @@
|
||||
# Generated by Django 5.2.10 on 2026-01-15 13:27
|
||||
|
||||
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", "0014_notification_hyperlink_notification_hyperlink_label_and_more"),
|
||||
]
|
||||
|
||||
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",),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,56 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.db import models
|
||||
from django.templatetags.static import static
|
||||
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 icon_url(self):
|
||||
return static("authentik/connectors/fleet.svg")
|
||||
|
||||
@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")
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"created_at": "2026-01-23T15:32:17Z",
|
||||
"updated_at": "2026-01-23T15:32:28Z",
|
||||
"software": null,
|
||||
"software_updated_at": "2026-01-23T15:32:17Z",
|
||||
"id": 16,
|
||||
"detail_updated_at": "1970-01-02T00:00:00Z",
|
||||
"label_updated_at": "1970-01-02T00:00:00Z",
|
||||
"policy_updated_at": "1970-01-02T00:00:00Z",
|
||||
"last_enrolled_at": "2026-01-23T15:32:19Z",
|
||||
"seen_time": "2026-01-23T15:32:21Z",
|
||||
"refetch_requested": true,
|
||||
"hostname": "fedora-workstation",
|
||||
"uuid": "578c4d56-aff8-0793-14ae-7947392f5fec",
|
||||
"platform": "rhel",
|
||||
"osquery_version": "5.21.0",
|
||||
"orbit_version": null,
|
||||
"fleet_desktop_version": null,
|
||||
"scripts_enabled": null,
|
||||
"os_version": "Fedora Linux 43.0.0",
|
||||
"build": "",
|
||||
"platform_like": "rhel",
|
||||
"code_name": "",
|
||||
"uptime": 0,
|
||||
"memory": 4092518400,
|
||||
"cpu_type": "x86_64",
|
||||
"cpu_subtype": "165",
|
||||
"cpu_brand": "Intel(R) Core(TM) i5-10500T CPU @ 2.30GHz",
|
||||
"cpu_physical_cores": 2,
|
||||
"cpu_logical_cores": 2,
|
||||
"hardware_vendor": "VMware, Inc.",
|
||||
"hardware_model": "VMware20,1",
|
||||
"hardware_version": "None",
|
||||
"hardware_serial": "VMware-56 4d 8c 57 f8 af 93 07-14 ae 79 47 39 2f 5f ec",
|
||||
"computer_name": "fedora-workstation",
|
||||
"public_ip": "",
|
||||
"primary_ip": "",
|
||||
"primary_mac": "",
|
||||
"distributed_interval": 10,
|
||||
"config_tls_refresh": 0,
|
||||
"logger_tls_period": 10,
|
||||
"team_id": 2,
|
||||
"pack_stats": null,
|
||||
"team_name": "prod",
|
||||
"gigs_disk_space_available": 0,
|
||||
"percent_disk_space_available": 0,
|
||||
"gigs_total_disk_space": 0,
|
||||
"gigs_all_disk_space": null,
|
||||
"issues": {
|
||||
"failing_policies_count": 0,
|
||||
"critical_vulnerabilities_count": 0,
|
||||
"total_issues_count": 0
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"created_at": "2025-06-25T22:21:35Z",
|
||||
"updated_at": "2025-12-20T11:42:09Z",
|
||||
"software": null,
|
||||
"software_updated_at": "2025-10-22T02:24:25Z",
|
||||
"id": 1,
|
||||
"detail_updated_at": "2025-10-23T23:30:31Z",
|
||||
"label_updated_at": "2025-10-23T23:30:31Z",
|
||||
"policy_updated_at": "2025-10-23T23:02:11Z",
|
||||
"last_enrolled_at": "2025-06-25T22:21:37Z",
|
||||
"seen_time": "2025-10-23T23:59:08Z",
|
||||
"refetch_requested": false,
|
||||
"hostname": "jens-mac-vm.local",
|
||||
"uuid": "C8B98348-A0A6-5838-A321-57B59D788269",
|
||||
"platform": "darwin",
|
||||
"osquery_version": "5.19.0",
|
||||
"orbit_version": null,
|
||||
"fleet_desktop_version": null,
|
||||
"scripts_enabled": null,
|
||||
"os_version": "macOS 26.0.1",
|
||||
"build": "25A362",
|
||||
"platform_like": "darwin",
|
||||
"code_name": "",
|
||||
"uptime": 256356000000000,
|
||||
"memory": 4294967296,
|
||||
"cpu_type": "arm64e",
|
||||
"cpu_subtype": "ARM64E",
|
||||
"cpu_brand": "Apple M1 Pro (Virtual)",
|
||||
"cpu_physical_cores": 8,
|
||||
"cpu_logical_cores": 8,
|
||||
"hardware_vendor": "Apple Inc.",
|
||||
"hardware_model": "VirtualMac2,1",
|
||||
"hardware_version": "",
|
||||
"hardware_serial": "Z5DDF07GK6",
|
||||
"computer_name": "jens-mac-vm",
|
||||
"public_ip": "92.116.179.252",
|
||||
"primary_ip": "192.168.85.3",
|
||||
"primary_mac": "e6:9d:21:c2:2f:19",
|
||||
"distributed_interval": 10,
|
||||
"config_tls_refresh": 60,
|
||||
"logger_tls_period": 10,
|
||||
"team_id": 2,
|
||||
"pack_stats": null,
|
||||
"team_name": "prod",
|
||||
"gigs_disk_space_available": 23.82,
|
||||
"percent_disk_space_available": 37,
|
||||
"gigs_total_disk_space": 62.83,
|
||||
"gigs_all_disk_space": null,
|
||||
"issues": {
|
||||
"failing_policies_count": 1,
|
||||
"critical_vulnerabilities_count": 2,
|
||||
"total_issues_count": 3
|
||||
},
|
||||
"device_mapping": null,
|
||||
"mdm": {
|
||||
"enrollment_status": "On (manual)",
|
||||
"dep_profile_error": false,
|
||||
"server_url": "https://fleet.beryjuio-home.k8s.beryju.io/mdm/apple/mdm",
|
||||
"name": "Fleet",
|
||||
"encryption_key_available": false,
|
||||
"connected_to_fleet": true
|
||||
},
|
||||
"refetch_critical_queries_until": null,
|
||||
"last_restarted_at": "2025-10-21T00:17:55Z",
|
||||
"status": "offline",
|
||||
"display_text": "jens-mac-vm.local",
|
||||
"display_name": "jens-mac-vm"
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"created_at": "2025-11-01T17:25:34Z",
|
||||
"updated_at": "2026-01-23T12:58:55Z",
|
||||
"software": null,
|
||||
"software_updated_at": "2026-01-23T12:58:55Z",
|
||||
"id": 14,
|
||||
"detail_updated_at": "2026-01-23T12:58:55Z",
|
||||
"label_updated_at": "2026-01-23T12:58:55Z",
|
||||
"policy_updated_at": "2026-01-23T12:29:58Z",
|
||||
"last_enrolled_at": "2025-11-01T17:25:38Z",
|
||||
"seen_time": "2026-01-23T13:17:27Z",
|
||||
"refetch_requested": false,
|
||||
"hostname": "ubuntu-desktop",
|
||||
"uuid": "5a4a4d56-22b0-d77b-9ba5-0bdc8ff23b60",
|
||||
"platform": "ubuntu",
|
||||
"osquery_version": "5.21.0",
|
||||
"orbit_version": null,
|
||||
"fleet_desktop_version": null,
|
||||
"scripts_enabled": null,
|
||||
"os_version": "Ubuntu 24.04.3 LTS",
|
||||
"build": "",
|
||||
"platform_like": "debian",
|
||||
"code_name": "noble",
|
||||
"uptime": 1631433000000000,
|
||||
"memory": 2062721024,
|
||||
"cpu_type": "x86_64",
|
||||
"cpu_subtype": "165",
|
||||
"cpu_brand": "Intel(R) Core(TM) i5-10500T CPU @ 2.30GHz",
|
||||
"cpu_physical_cores": 2,
|
||||
"cpu_logical_cores": 2,
|
||||
"hardware_vendor": "VMware, Inc.",
|
||||
"hardware_model": "VMware20,1",
|
||||
"hardware_version": "None",
|
||||
"hardware_serial": "VMware-56 4d 4a 5a b0 22 7b d7-9b a5 0b dc 8f f2 3b 60",
|
||||
"computer_name": "ubuntu-desktop",
|
||||
"public_ip": "92.116.178.120",
|
||||
"primary_ip": "10.120.20.61",
|
||||
"primary_mac": "00:0c:29:f2:3b:60",
|
||||
"distributed_interval": 10,
|
||||
"config_tls_refresh": 60,
|
||||
"logger_tls_period": 10,
|
||||
"team_id": 2,
|
||||
"pack_stats": null,
|
||||
"team_name": "prod",
|
||||
"gigs_disk_space_available": 7.37,
|
||||
"percent_disk_space_available": 31,
|
||||
"gigs_total_disk_space": 23.08,
|
||||
"gigs_all_disk_space": 23.08,
|
||||
"issues": {
|
||||
"failing_policies_count": 0,
|
||||
"critical_vulnerabilities_count": 0,
|
||||
"total_issues_count": 0
|
||||
},
|
||||
"device_mapping": null,
|
||||
"mdm": {
|
||||
"enrollment_status": null,
|
||||
"dep_profile_error": false,
|
||||
"server_url": null,
|
||||
"name": "",
|
||||
"encryption_key_available": false,
|
||||
"connected_to_fleet": false
|
||||
},
|
||||
"refetch_critical_queries_until": null,
|
||||
"last_restarted_at": "2026-01-04T15:48:22.390118Z",
|
||||
"status": "online",
|
||||
"display_text": "ubuntu-desktop",
|
||||
"display_name": "ubuntu-desktop"
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"created_at": "2025-10-19T12:44:09Z",
|
||||
"updated_at": "2026-01-23T13:11:45Z",
|
||||
"software": null,
|
||||
"software_updated_at": "2026-01-22T06:57:30Z",
|
||||
"id": 13,
|
||||
"detail_updated_at": "2026-01-23T12:51:35Z",
|
||||
"label_updated_at": "2026-01-23T12:51:35Z",
|
||||
"policy_updated_at": "2026-01-23T13:11:45Z",
|
||||
"last_enrolled_at": "2025-11-05T20:27:14Z",
|
||||
"seen_time": "2026-01-23T13:17:33Z",
|
||||
"refetch_requested": false,
|
||||
"hostname": "windows-server",
|
||||
"uuid": "CFF12F42-9F7D-A575-2C48-01BDC6A733FB",
|
||||
"platform": "windows",
|
||||
"osquery_version": "5.21.0",
|
||||
"orbit_version": null,
|
||||
"fleet_desktop_version": null,
|
||||
"scripts_enabled": null,
|
||||
"os_version": "Windows Server 2022 Datacenter 21H2 10.0.20348.4405",
|
||||
"build": "20348",
|
||||
"platform_like": "windows",
|
||||
"code_name": "Microsoft Windows Server 2022 Datacenter",
|
||||
"uptime": 217075000000000,
|
||||
"memory": 4294967296,
|
||||
"cpu_type": "x86_64",
|
||||
"cpu_subtype": "-1",
|
||||
"cpu_brand": "Intel(R) Core(TM) i5-10500T CPU @ 2.30GHz",
|
||||
"cpu_physical_cores": 1,
|
||||
"cpu_logical_cores": 2,
|
||||
"hardware_vendor": "VMware, Inc.",
|
||||
"hardware_model": "VMware20,1",
|
||||
"hardware_version": "-1",
|
||||
"hardware_serial": "VMware-42 2f f1 cf 7d 9f 75 a5-2c 48 01 bd c6 a7 33 fb",
|
||||
"computer_name": "WINDOWS-SERVER",
|
||||
"public_ip": "92.116.178.120",
|
||||
"primary_ip": "10.120.20.78",
|
||||
"primary_mac": "00:50:56:af:fb:3a",
|
||||
"distributed_interval": 10,
|
||||
"config_tls_refresh": 60,
|
||||
"logger_tls_period": 10,
|
||||
"team_id": 2,
|
||||
"pack_stats": null,
|
||||
"team_name": "prod",
|
||||
"gigs_disk_space_available": 68,
|
||||
"percent_disk_space_available": 71,
|
||||
"gigs_total_disk_space": 96,
|
||||
"gigs_all_disk_space": null,
|
||||
"issues": {
|
||||
"failing_policies_count": 0,
|
||||
"critical_vulnerabilities_count": 5,
|
||||
"total_issues_count": 5
|
||||
},
|
||||
"device_mapping": null,
|
||||
"mdm": {
|
||||
"enrollment_status": null,
|
||||
"dep_profile_error": false,
|
||||
"server_url": null,
|
||||
"name": "",
|
||||
"encryption_key_available": false,
|
||||
"connected_to_fleet": false
|
||||
},
|
||||
"refetch_critical_queries_until": null,
|
||||
"last_restarted_at": "2026-01-21T00:33:38.178036Z",
|
||||
"status": "online",
|
||||
"display_text": "windows-server",
|
||||
"display_name": "WINDOWS-SERVER"
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
from json import loads
|
||||
|
||||
from requests_mock import Mocker
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.endpoints.facts import OSFamily
|
||||
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
|
||||
from authentik.lib.tests.utils import load_fixture
|
||||
|
||||
TEST_HOST_UBUNTU = loads(load_fixture("fixtures/host_ubuntu.json"))
|
||||
TEST_HOST_FEDORA = loads(load_fixture("fixtures/host_fedora.json"))
|
||||
TEST_HOST_MACOS = loads(load_fixture("fixtures/host_macos.json"))
|
||||
TEST_HOST_WINDOWS = loads(load_fixture("fixtures/host_windows.json"))
|
||||
|
||||
TEST_HOST = {"hosts": [TEST_HOST_UBUNTU, TEST_HOST_MACOS, TEST_HOST_WINDOWS, TEST_HOST_FEDORA]}
|
||||
|
||||
|
||||
class TestFleetConnector(APITestCase):
|
||||
def setUp(self):
|
||||
self.connector = FleetConnector.objects.create(
|
||||
name=generate_id(), url="http://localhost", token=generate_id()
|
||||
)
|
||||
|
||||
def test_sync(self):
|
||||
controller = self.connector.controller(self.connector)
|
||||
with Mocker() as mock:
|
||||
mock.get(
|
||||
"http://localhost/api/v1/fleet/hosts?order_key=hardware_serial&page=0&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=1&per_page=50&device_mapping=true&populate_software=true&populate_users=true",
|
||||
json={"hosts": []},
|
||||
)
|
||||
controller.sync_endpoints()
|
||||
device = Device.objects.filter(
|
||||
identifier="VMware-56 4d 4a 5a b0 22 7b d7-9b a5 0b dc 8f f2 3b 60"
|
||||
).first()
|
||||
self.assertIsNotNone(device)
|
||||
self.assertEqual(
|
||||
device.cached_facts.data,
|
||||
{
|
||||
"os": {
|
||||
"arch": "x86_64",
|
||||
"name": "Ubuntu",
|
||||
"family": "linux",
|
||||
"version": "24.04.3 LTS",
|
||||
},
|
||||
"disks": [],
|
||||
"vendor": {"fleetdm.com": {"policies": [], "agent_version": ""}},
|
||||
"network": {"hostname": "ubuntu-desktop", "interfaces": []},
|
||||
"hardware": {
|
||||
"model": "VMware20,1",
|
||||
"serial": "VMware-56 4d 4a 5a b0 22 7b d7-9b a5 0b dc 8f f2 3b 60",
|
||||
"cpu_count": 2,
|
||||
"cpu_name": "Intel(R) Core(TM) i5-10500T CPU @ 2.30GHz",
|
||||
"manufacturer": "VMware, Inc.",
|
||||
"memory_bytes": 2062721024,
|
||||
},
|
||||
"software": [],
|
||||
},
|
||||
)
|
||||
|
||||
def test_sync_headers(self):
|
||||
mapping = NotificationWebhookMapping.objects.create(
|
||||
name=generate_id(), expression="""return {"foo": "bar"}"""
|
||||
)
|
||||
self.connector.headers_mapping = mapping
|
||||
self.connector.save()
|
||||
controller = self.connector.controller(self.connector)
|
||||
with Mocker() as mock:
|
||||
mock.get(
|
||||
"http://localhost/api/v1/fleet/hosts?order_key=hardware_serial&page=0&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=1&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")
|
||||
|
||||
def test_map_host_linux(self):
|
||||
controller = self.connector.controller(self.connector)
|
||||
self.assertEqual(
|
||||
controller.map_os(TEST_HOST_UBUNTU),
|
||||
{
|
||||
"arch": "x86_64",
|
||||
"family": OSFamily.linux,
|
||||
"name": "Ubuntu",
|
||||
"version": "24.04.3 LTS",
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
controller.map_os(TEST_HOST_FEDORA),
|
||||
{
|
||||
"arch": "x86_64",
|
||||
"family": OSFamily.linux,
|
||||
"name": "Fedora Linux",
|
||||
"version": "43.0.0",
|
||||
},
|
||||
)
|
||||
|
||||
def test_map_host_windows(self):
|
||||
controller = self.connector.controller(self.connector)
|
||||
self.assertEqual(
|
||||
controller.map_os(TEST_HOST_WINDOWS),
|
||||
{
|
||||
"arch": "x86_64",
|
||||
"family": OSFamily.windows,
|
||||
"name": "Windows Server 2022 Datacenter 21H2",
|
||||
"version": "10.0.20348.4405",
|
||||
},
|
||||
)
|
||||
|
||||
def test_map_host_macos(self):
|
||||
controller = self.connector.controller(self.connector)
|
||||
self.assertEqual(
|
||||
controller.map_os(TEST_HOST_MACOS),
|
||||
{
|
||||
"arch": "arm64e",
|
||||
"family": OSFamily.macOS,
|
||||
"name": "macOS",
|
||||
"version": "26.0.1",
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
from authentik.enterprise.endpoints.connectors.fleet.api import FleetConnectorViewSet
|
||||
|
||||
api_urlpatterns = [("endpoints/fleet/connectors", FleetConnectorViewSet)]
|
||||
@@ -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",
|
||||
|
||||
@@ -656,6 +656,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": [
|
||||
@@ -5432,6 +5472,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",
|
||||
@@ -6319,6 +6363,11 @@
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Name"
|
||||
},
|
||||
"attributes": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"title": "Attributes"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
@@ -6477,6 +6526,78 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": "string",
|
||||
"format": "uuid",
|
||||
"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": {
|
||||
@@ -8161,6 +8282,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",
|
||||
@@ -8288,6 +8410,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",
|
||||
@@ -10551,6 +10674,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",
|
||||
|
||||
+350
@@ -6447,6 +6447,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
|
||||
@@ -19897,6 +20087,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
|
||||
@@ -33121,6 +33312,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
|
||||
@@ -36019,6 +36211,9 @@ components:
|
||||
readOnly: true
|
||||
name:
|
||||
type: string
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
required:
|
||||
- name
|
||||
- pbm_uuid
|
||||
@@ -36028,6 +36223,9 @@ components:
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
required:
|
||||
- name
|
||||
DeviceChallenge:
|
||||
@@ -37801,6 +37999,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
|
||||
@@ -41546,6 +41827,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
|
||||
@@ -42743,13 +43025,22 @@ components:
|
||||
- userinfo_endpoint
|
||||
OperatingSystem:
|
||||
type: object
|
||||
description: |-
|
||||
For example:
|
||||
{"family":"linux","name":"Ubuntu","version":"24.04.3 LTS (Noble Numbat)","arch":"amd64"}
|
||||
{"family": "windows","name":"Server 2022 Datacenter","version":"10.0.20348.4405","arch":"amd64"}
|
||||
{"family": "windows","name":"Server 2022 Datacenter","version":"10.0.20348.4405","arch":"amd64"}
|
||||
{"family": "mac_os", "name": "", "version": "26.2", "arch": "arm64"}
|
||||
properties:
|
||||
family:
|
||||
$ref: '#/components/schemas/DeviceFactsOSFamily'
|
||||
name:
|
||||
type: string
|
||||
description: Operating System name, such as 'Server 2022' or 'Ubuntu'
|
||||
version:
|
||||
type: string
|
||||
description: Operating System version, must always be the version number
|
||||
but may contain build name
|
||||
arch:
|
||||
type: string
|
||||
required:
|
||||
@@ -42757,15 +43048,24 @@ components:
|
||||
- family
|
||||
OperatingSystemRequest:
|
||||
type: object
|
||||
description: |-
|
||||
For example:
|
||||
{"family":"linux","name":"Ubuntu","version":"24.04.3 LTS (Noble Numbat)","arch":"amd64"}
|
||||
{"family": "windows","name":"Server 2022 Datacenter","version":"10.0.20348.4405","arch":"amd64"}
|
||||
{"family": "windows","name":"Server 2022 Datacenter","version":"10.0.20348.4405","arch":"amd64"}
|
||||
{"family": "mac_os", "name": "", "version": "26.2", "arch": "arm64"}
|
||||
properties:
|
||||
family:
|
||||
$ref: '#/components/schemas/DeviceFactsOSFamily'
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: Operating System name, such as 'Server 2022' or 'Ubuntu'
|
||||
version:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: Operating System version, must always be the version number
|
||||
but may contain build name
|
||||
arch:
|
||||
type: string
|
||||
minLength: 1
|
||||
@@ -43535,6 +43835,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:
|
||||
@@ -46154,6 +46469,9 @@ components:
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
attributes:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
PatchedDeviceUserBindingRequest:
|
||||
type: object
|
||||
description: PolicyBinding Serializer
|
||||
@@ -46498,6 +46816,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
|
||||
@@ -55950,6 +56299,7 @@ components:
|
||||
enum:
|
||||
- goauthentik.io/@merged
|
||||
- goauthentik.io/platform
|
||||
- fleetdm.com
|
||||
type: string
|
||||
Version:
|
||||
type: object
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.97739 6C4.62176 6 5.95479 4.65685 5.95479 3C5.95479 1.34314 4.62176 0 2.97739 0C1.33302 0 0 1.34314 0 3C0 4.65685 1.33302 6 2.97739 6Z" fill="#63C740"/>
|
||||
<path d="M11.9097 6C13.554 6 14.8871 4.65685 14.8871 3C14.8871 1.34314 13.554 0 11.9097 0C10.2653 0 8.93225 1.34314 8.93225 3C8.93225 4.65685 10.2653 6 11.9097 6Z" fill="#5CABDF"/>
|
||||
<path d="M20.8418 6C22.4861 6 23.8192 4.65685 23.8192 3C23.8192 1.34314 22.4861 0 20.8418 0C19.1974 0 17.8644 1.34314 17.8644 3C17.8644 4.65685 19.1974 6 20.8418 6Z" fill="#D66C7B"/>
|
||||
<path d="M2.97739 15C4.62176 15 5.95479 13.6569 5.95479 12C5.95479 10.3432 4.62176 9.00002 2.97739 9.00002C1.33302 9.00002 0 10.3432 0 12C0 13.6569 1.33302 15 2.97739 15Z" fill="#C98DEF"/>
|
||||
<path d="M11.9097 15C13.554 15 14.8871 13.6569 14.8871 12C14.8871 10.3432 13.554 9.00002 11.9097 9.00002C10.2653 9.00002 8.93225 10.3432 8.93225 12C8.93225 13.6569 10.2653 15 11.9097 15Z" fill="#FAA669"/>
|
||||
<path d="M2.97739 24C4.62176 24 5.95479 22.6569 5.95479 21C5.95479 19.3432 4.62176 18 2.97739 18C1.33302 18 0 19.3432 0 21C0 22.6569 1.33302 24 2.97739 24Z" fill="#3AEFC4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -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`<ak-endpoints-connector-agent-view
|
||||
connectorID=${ifDefined(this.connector.connectorUuid)}
|
||||
></ak-endpoints-connector-agent-view>`;
|
||||
case "ak-endpoints-connector-fleet-form":
|
||||
return html`<ak-endpoints-connector-fleet-view
|
||||
connectorID=${ifDefined(this.connector.connectorUuid)}
|
||||
></ak-endpoints-connector-fleet-view>`;
|
||||
default:
|
||||
return html`<p>Invalid connector type ${this.connector?.component}</p>`;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import "#admin/common/ak-license-notice";
|
||||
import "#admin/endpoints/connectors/agent/AgentConnectorForm";
|
||||
import "#admin/endpoints/connectors/fleet/FleetConnectorForm";
|
||||
import "#elements/wizard/FormWizardPage";
|
||||
import "#elements/wizard/TypeCreateWizardPage";
|
||||
import "#elements/wizard/Wizard";
|
||||
@@ -8,6 +9,7 @@ import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
import { StrictUnsafe } from "#elements/utils/unsafe";
|
||||
import { TypeCreateWizardPageLayouts } from "#elements/wizard/TypeCreateWizardPage";
|
||||
import { Wizard } from "#elements/wizard/Wizard";
|
||||
|
||||
import { EndpointsApi, TypeCreate } from "@goauthentik/api";
|
||||
@@ -48,6 +50,7 @@ export class EndpointConnectorWizard extends AKElement {
|
||||
<ak-wizard-page-type-create
|
||||
slot="initial"
|
||||
.types=${this.connectorTypes}
|
||||
layout=${TypeCreateWizardPageLayouts.grid}
|
||||
@select=${(ev: CustomEvent<TypeCreate>) => {
|
||||
if (!this.wizard) return;
|
||||
const idx = this.wizard.steps.indexOf("initial") + 1;
|
||||
|
||||
@@ -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/ModalForm";
|
||||
|
||||
|
||||
@@ -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<FleetConnector, string> {
|
||||
loadInstance(pk: string): Promise<FleetConnector> {
|
||||
return new EndpointsApi(DEFAULT_CONFIG).endpointsFleetConnectorsRetrieve({
|
||||
connectorUuid: pk,
|
||||
});
|
||||
}
|
||||
|
||||
public override getSuccessMessage(): string {
|
||||
return this.instance
|
||||
? msg("Successfully updated Fleet connector.")
|
||||
: msg("Successfully created Fleet connector.");
|
||||
}
|
||||
|
||||
async send(data: FleetConnector): Promise<FleetConnector> {
|
||||
if (this.instance) {
|
||||
return new EndpointsApi(DEFAULT_CONFIG).endpointsFleetConnectorsPartialUpdate({
|
||||
connectorUuid: this.instance.connectorUuid!,
|
||||
patchedFleetConnectorRequest: data,
|
||||
});
|
||||
}
|
||||
return new EndpointsApi(DEFAULT_CONFIG).endpointsFleetConnectorsCreate({
|
||||
fleetConnectorRequest: data as unknown as FleetConnectorRequest,
|
||||
});
|
||||
}
|
||||
|
||||
renderForm() {
|
||||
return html`<ak-text-input
|
||||
name="name"
|
||||
placeholder=${msg("Connector name...")}
|
||||
label=${msg("Connector name")}
|
||||
value=${this.instance?.name ?? ""}
|
||||
required
|
||||
></ak-text-input>
|
||||
<ak-switch-input
|
||||
name="enabled"
|
||||
label=${msg("Enabled")}
|
||||
?checked=${this.instance?.enabled ?? true}
|
||||
></ak-switch-input>
|
||||
<ak-form-group label=${msg("Fleet settings")} open>
|
||||
<div class="pf-c-form">
|
||||
<ak-text-input
|
||||
name="url"
|
||||
label=${msg("Fleet Server URL")}
|
||||
value=${this.instance?.url ?? ""}
|
||||
required
|
||||
input-hint="code"
|
||||
>
|
||||
</ak-text-input>
|
||||
<ak-secret-text-input
|
||||
label=${msg("Fleet API Token")}
|
||||
name="token"
|
||||
?revealed=${!this.instance}
|
||||
></ak-secret-text-input>
|
||||
<ak-switch-input
|
||||
name="mapUsers"
|
||||
label=${msg("Map users")}
|
||||
?checked=${this.instance?.mapUsers ?? true}
|
||||
help=${msg(
|
||||
"When enabled, users detected by Fleet will be mapped in authentik, granting them access to the device.",
|
||||
)}
|
||||
></ak-switch-input>
|
||||
<ak-switch-input
|
||||
name="mapTeamsAccessGroup"
|
||||
label=${msg("Map teams to device access group")}
|
||||
?checked=${this.instance?.mapTeamsAccessGroup ?? false}
|
||||
help=${msg(
|
||||
"When enabled, Fleet teams will be mapped to Device access groups. Missing device access groups are automatically created. Devices assigned to a different group are not re-assigned",
|
||||
)}
|
||||
></ak-switch-input>
|
||||
</div>
|
||||
</ak-form-group>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-endpoints-connector-fleet-form": FleetConnectorForm;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import "#elements/Tabs";
|
||||
import "#components/events/ObjectChangelog";
|
||||
import "#admin/rbac/ObjectPermissionsPage";
|
||||
import "#elements/tasks/ScheduleList";
|
||||
import "#elements/tasks/TaskList";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { APIError, parseAPIResponseError } from "#common/errors/network";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
|
||||
import { setPageDetails } from "#components/ak-page-navbar";
|
||||
|
||||
import {
|
||||
EndpointsApi,
|
||||
FleetConnector,
|
||||
ModelEnum,
|
||||
RbacPermissionsAssignedByRolesListModelEnum,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, html, nothing, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||
|
||||
const [FLEET_CONNECTOR_APP_LABEL, FLEET_CONNECTOR_MODEL_NAME] =
|
||||
ModelEnum.AuthentikEndpointsConnectorsFleetFleetconnector.split(".");
|
||||
|
||||
@customElement("ak-endpoints-connector-fleet-view")
|
||||
export class FleetConnectorViewPage extends AKElement {
|
||||
@property({ type: String })
|
||||
public connectorId?: string;
|
||||
|
||||
@state()
|
||||
protected connector?: FleetConnector;
|
||||
|
||||
@state()
|
||||
protected error?: APIError;
|
||||
|
||||
static styles: CSSResult[] = [PFCard, PFPage, PFGrid, PFButton, PFDescriptionList];
|
||||
|
||||
protected fetchDevice(id: string) {
|
||||
new EndpointsApi(DEFAULT_CONFIG)
|
||||
.endpointsFleetConnectorsRetrieve({ connectorUuid: id })
|
||||
.then((conn) => {
|
||||
this.connector = conn;
|
||||
})
|
||||
.catch(async (error) => {
|
||||
this.error = await parseAPIResponseError(error);
|
||||
});
|
||||
}
|
||||
|
||||
public override willUpdate(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has("connectorId") && this.connectorId) {
|
||||
this.fetchDevice(this.connectorId);
|
||||
}
|
||||
}
|
||||
|
||||
public override updated(changed: PropertyValues<this>) {
|
||||
super.updated(changed);
|
||||
setPageDetails({
|
||||
icon: "pf-icon pf-icon-data-source",
|
||||
header: this.connector?.name,
|
||||
description: this.connector?.verboseName,
|
||||
});
|
||||
}
|
||||
|
||||
protected renderTabOverview() {
|
||||
return html`<div
|
||||
class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter"
|
||||
>
|
||||
<div class="pf-l-grid__item pf-m-12-col pf-l-stack__item">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__header">
|
||||
<div class="pf-c-card__title">${msg("Schedules")}</div>
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<ak-schedule-list
|
||||
.relObjAppLabel=${FLEET_CONNECTOR_APP_LABEL}
|
||||
.relObjModel=${FLEET_CONNECTOR_MODEL_NAME}
|
||||
.relObjId="${this.connector?.connectorUuid}"
|
||||
></ak-schedule-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-12-col pf-l-stack__item">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__header">
|
||||
<div class="pf-c-card__title">${msg("Tasks")}</div>
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<ak-task-list
|
||||
.relObjAppLabel=${FLEET_CONNECTOR_APP_LABEL}
|
||||
.relObjModel=${FLEET_CONNECTOR_MODEL_NAME}
|
||||
.relObjId="${this.connector?.connectorUuid}"
|
||||
></ak-task-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> `;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.connector) {
|
||||
return nothing;
|
||||
}
|
||||
return html`<ak-tabs>
|
||||
<div
|
||||
role="tabpanel"
|
||||
tabindex="0"
|
||||
slot="page-overview"
|
||||
id="page-overview"
|
||||
aria-label="${msg("Overview")}"
|
||||
>
|
||||
${this.renderTabOverview()}
|
||||
</div>
|
||||
<div
|
||||
role="tabpanel"
|
||||
tabindex="0"
|
||||
slot="page-changelog"
|
||||
id="page-changelog"
|
||||
aria-label="${msg("Changelog")}"
|
||||
class="pf-c-page__main-section pf-m-no-padding-mobile"
|
||||
>
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<ak-object-changelog
|
||||
targetModelPk=${this.connector?.connectorUuid || ""}
|
||||
targetModelName=${this.connector?.metaModelName || ""}
|
||||
>
|
||||
</ak-object-changelog>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ak-rbac-object-permission-page
|
||||
role="tabpanel"
|
||||
tabindex="0"
|
||||
slot="page-permissions"
|
||||
id="page-permissions"
|
||||
aria-label=${msg("Permissions")}
|
||||
model=${RbacPermissionsAssignedByRolesListModelEnum.AuthentikEndpointsConnectorsFleetFleetconnector}
|
||||
objectPk=${this.connector.connectorUuid!}
|
||||
></ak-rbac-object-permission-page>
|
||||
</ak-tabs> `;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-endpoints-connector-fleet-view": FleetConnectorViewPage;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import "#components/ak-status-label";
|
||||
import "#admin/endpoints/devices/BoundDeviceUsersList";
|
||||
import "#admin/endpoints/devices/facts/DeviceProcessTable";
|
||||
import "#admin/endpoints/devices/facts/DeviceUserTable";
|
||||
import "#admin/endpoints/devices/facts/DeviceSoftwareTable";
|
||||
import "#admin/endpoints/devices/facts/DeviceGroupTable";
|
||||
import "#admin/endpoints/devices/DeviceForm";
|
||||
import "#elements/forms/ModalForm";
|
||||
@@ -192,9 +193,7 @@ export class DeviceViewPage extends AKElement {
|
||||
return [
|
||||
html`${conn.connectorObj.name}`,
|
||||
html`<div class="pf-c-description-list__text">
|
||||
${msg(
|
||||
str`Agent version: ${this.agentVersion(conn) ?? "-"}`,
|
||||
)}
|
||||
${this.agentVersion(conn) ?? "-"}
|
||||
</div>
|
||||
<div class="pf-c-description-list__text">
|
||||
${conn.latestSnapshot?.created
|
||||
@@ -226,7 +225,7 @@ export class DeviceViewPage extends AKElement {
|
||||
const vendorData = vendorContainer[conn.latestSnapshot.vendor];
|
||||
if (!vendorData) return;
|
||||
if (!("agent_version" in vendorData)) return;
|
||||
return vendorData.agent_version;
|
||||
return msg(str`Agent version: ${vendorData.agent_version ?? "-"}`);
|
||||
}
|
||||
|
||||
renderProcesses() {
|
||||
@@ -256,6 +255,15 @@ export class DeviceViewPage extends AKElement {
|
||||
></ak-endpoints-device-groups-table>`;
|
||||
}
|
||||
|
||||
renderSoftware() {
|
||||
if (!this.device) {
|
||||
return nothing;
|
||||
}
|
||||
return html`<ak-endpoints-device-software-table
|
||||
.device=${this.device}
|
||||
></ak-endpoints-device-software-table>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<main part="main">
|
||||
<ak-tabs part="tabs">
|
||||
@@ -299,6 +307,16 @@ export class DeviceViewPage extends AKElement {
|
||||
>
|
||||
${this.renderGroups()}
|
||||
</div>
|
||||
<div
|
||||
role="tabpanel"
|
||||
tabindex="0"
|
||||
slot="page-software"
|
||||
id="page-software"
|
||||
aria-label="${msg("Software")}"
|
||||
class="pf-c-page__main-section"
|
||||
>
|
||||
${this.renderSoftware()}
|
||||
</div>
|
||||
</ak-tabs>
|
||||
</main>`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { groupBy, GroupResult } from "#common/utils";
|
||||
|
||||
import { PaginatedResponse, Table, TableColumn } from "#elements/table/Table";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { EndpointDeviceDetails, Software } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-endpoints-device-software-table")
|
||||
export class DeviceSoftwareTable extends Table<Software> {
|
||||
@property({ attribute: false })
|
||||
device?: EndpointDeviceDetails;
|
||||
|
||||
protected async apiEndpoint(): Promise<PaginatedResponse<Software>> {
|
||||
const items = (this.device?.facts.data.software || []).sort((a, b) =>
|
||||
a.name.localeCompare(b.name),
|
||||
);
|
||||
return {
|
||||
pagination: {
|
||||
count: items.length,
|
||||
current: 1,
|
||||
totalPages: 1,
|
||||
startIndex: 1,
|
||||
endIndex: items.length,
|
||||
next: 0,
|
||||
previous: 0,
|
||||
},
|
||||
results: items,
|
||||
};
|
||||
}
|
||||
|
||||
protected groupBy(items: Software[]): GroupResult<Software>[] {
|
||||
return groupBy(items, (item) => item.source);
|
||||
}
|
||||
|
||||
protected columns: TableColumn[] = [
|
||||
[msg("Name")],
|
||||
[msg("Version")],
|
||||
[msg("Source")],
|
||||
[msg("Path")],
|
||||
];
|
||||
protected row(item: Software): SlottedTemplateResult[] {
|
||||
return [
|
||||
html`${item.name}`,
|
||||
html`${item.version ?? "-"}`,
|
||||
html`${item.source}`,
|
||||
html`${item.path ?? "-"}`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-endpoints-device-software-table": DeviceSoftwareTable;
|
||||
}
|
||||
}
|
||||
@@ -94,61 +94,66 @@ export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) {
|
||||
};
|
||||
|
||||
protected renderGrid(): TemplateResult {
|
||||
return html`<div
|
||||
role="listbox"
|
||||
aria-label="${msg("Select a provider type")}"
|
||||
class="pf-l-grid pf-m-gutter"
|
||||
data-ouid-component-type="ak-type-create-grid"
|
||||
>
|
||||
${this.types.map((type, idx) => {
|
||||
const disabled = !!(type.requiresEnterprise && !this.hasEnterpriseLicense);
|
||||
return html`${this.hasSlotted("above-form")
|
||||
? html`<div class="pf-c-page__main-section"><slot name="above-form"></slot></div>`
|
||||
: nothing}
|
||||
<div
|
||||
role="listbox"
|
||||
aria-label="${msg("Select a provider type")}"
|
||||
class="pf-l-grid pf-m-gutter"
|
||||
data-ouid-component-type="ak-type-create-grid"
|
||||
>
|
||||
${this.types.map((type, idx) => {
|
||||
const disabled = !!(type.requiresEnterprise && !this.hasEnterpriseLicense);
|
||||
|
||||
const selected = this.selectedType === type;
|
||||
const selected = this.selectedType === type;
|
||||
|
||||
return html`<div
|
||||
class=${classMap({
|
||||
"pf-l-grid__item": true,
|
||||
"pf-m-3-col": true,
|
||||
"pf-c-card": true,
|
||||
"pf-m-non-selectable-raised": disabled,
|
||||
"ak-m-enterprise-only": disabled,
|
||||
"pf-m-selectable-raised": !disabled,
|
||||
"pf-m-selected-raised": selected,
|
||||
})}
|
||||
tabindex=${idx}
|
||||
role="option"
|
||||
aria-disabled="${disabled ? "true" : "false"}"
|
||||
aria-selected="${selected ? "true" : "false"}"
|
||||
aria-label="${type.name}"
|
||||
aria-describedby="${type.description}"
|
||||
@click=${() => {
|
||||
if (disabled) return;
|
||||
return html`<div
|
||||
class=${classMap({
|
||||
"pf-l-grid__item": true,
|
||||
"pf-m-3-col": true,
|
||||
"pf-c-card": true,
|
||||
"pf-m-non-selectable-raised": disabled,
|
||||
"ak-m-enterprise-only": disabled,
|
||||
"pf-m-selectable-raised": !disabled,
|
||||
"pf-m-selected-raised": selected,
|
||||
})}
|
||||
tabindex=${idx}
|
||||
role="option"
|
||||
aria-disabled="${disabled ? "true" : "false"}"
|
||||
aria-selected="${selected ? "true" : "false"}"
|
||||
aria-label="${type.name}"
|
||||
aria-describedby="${type.description}"
|
||||
@click=${() => {
|
||||
if (disabled) return;
|
||||
|
||||
this.#selectDispatch(type);
|
||||
this.selectedType = type;
|
||||
}}
|
||||
>
|
||||
${type.iconUrl
|
||||
? html`<div role="presentation" class="pf-c-card__header">
|
||||
<div role="presentation" class="pf-c-card__header-main">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
src=${type.iconUrl}
|
||||
alt=${msg(str`${type.name} Icon`)}
|
||||
/>
|
||||
</div>
|
||||
</div>`
|
||||
: nothing}
|
||||
<div role="heading" aria-level="2" class="pf-c-card__title">${type.name}</div>
|
||||
<div role="presentational" class="pf-c-card__body">${type.description}</div>
|
||||
${disabled
|
||||
? html`<div class="pf-c-card__footer">
|
||||
<ak-license-notice></ak-license-notice>
|
||||
</div> `
|
||||
: nothing}
|
||||
</div>`;
|
||||
})}
|
||||
</div>`;
|
||||
this.#selectDispatch(type);
|
||||
this.selectedType = type;
|
||||
}}
|
||||
>
|
||||
${type.iconUrl
|
||||
? html`<div role="presentation" class="pf-c-card__header">
|
||||
<div role="presentation" class="pf-c-card__header-main">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
src=${type.iconUrl}
|
||||
alt=${msg(str`${type.name} Icon`)}
|
||||
/>
|
||||
</div>
|
||||
</div>`
|
||||
: nothing}
|
||||
<div role="heading" aria-level="2" class="pf-c-card__title">
|
||||
${type.name}
|
||||
</div>
|
||||
<div role="presentational" class="pf-c-card__body">${type.description}</div>
|
||||
${disabled
|
||||
? html`<div class="pf-c-card__footer">
|
||||
<ak-license-notice></ak-license-notice>
|
||||
</div> `
|
||||
: nothing}
|
||||
</div>`;
|
||||
})}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderList(): TemplateResult {
|
||||
|
||||
Reference in New Issue
Block a user