authentik.outposts.models

Outpost models

  1"""Outpost models"""
  2
  3from collections.abc import Iterable
  4from dataclasses import asdict, dataclass, field
  5from datetime import datetime
  6from typing import Any
  7from uuid import uuid4
  8
  9from dacite.core import from_dict
 10from django.contrib.auth.models import Permission
 11from django.core.cache import cache
 12from django.db import IntegrityError, models, transaction
 13from django.db.models.base import Model
 14from django.utils.translation import gettext_lazy as _
 15from model_utils.managers import InheritanceManager
 16from packaging.version import parse
 17from rest_framework.serializers import Serializer
 18from structlog.stdlib import get_logger
 19
 20from authentik import authentik_build_hash, authentik_version
 21from authentik.blueprints.models import ManagedModel
 22from authentik.brands.models import Brand
 23from authentik.core.models import (
 24    USER_PATH_SYSTEM_PREFIX,
 25    Provider,
 26    Token,
 27    TokenIntents,
 28    User,
 29    UserTypes,
 30)
 31from authentik.crypto.models import CertificateKeyPair
 32from authentik.events.models import Event, EventAction
 33from authentik.lib.config import CONFIG
 34from authentik.lib.models import InheritanceForeignKey, SerializerModel
 35from authentik.lib.sentry import SentryIgnoredException
 36from authentik.lib.utils.time import fqdn_rand
 37from authentik.outposts.controllers.k8s.utils import get_namespace
 38from authentik.tasks.schedules.common import ScheduleSpec
 39from authentik.tasks.schedules.models import ScheduledModel
 40
 41OUR_VERSION = parse(authentik_version())
 42OUTPOST_HELLO_INTERVAL = 10
 43LOGGER = get_logger()
 44
 45USER_PATH_OUTPOSTS = USER_PATH_SYSTEM_PREFIX + "/outposts"
 46
 47
 48class ServiceConnectionInvalid(SentryIgnoredException):
 49    """Exception raised when a Service Connection has invalid parameters"""
 50
 51
 52@dataclass
 53class OutpostConfig:
 54    """Configuration an outpost uses to configure it self"""
 55
 56    # update website/docs/add-secure-apps/outposts/_config.md
 57
 58    authentik_host: str = ""
 59    authentik_host_insecure: bool = False
 60    authentik_host_browser: str = ""
 61
 62    log_level: str = CONFIG.get("log_level")
 63    object_naming_template: str = field(default="ak-outpost-%(name)s")
 64    refresh_interval: str = "minutes=5"
 65
 66    container_image: str | None = field(default=None)
 67
 68    docker_network: str | None = field(default=None)
 69    docker_map_ports: bool = field(default=True)
 70    docker_labels: dict[str, str] | None = field(default=None)
 71
 72    kubernetes_replicas: int = field(default=1)
 73    kubernetes_namespace: str = field(default_factory=get_namespace)
 74    kubernetes_ingress_annotations: dict[str, str] = field(default_factory=dict)
 75    kubernetes_ingress_secret_name: str = field(default="authentik-outpost-tls")
 76    kubernetes_ingress_class_name: str | None = field(default=None)
 77    kubernetes_ingress_path_type: str | None = field(default=None)
 78    kubernetes_httproute_annotations: dict[str, str] = field(default_factory=dict)
 79    kubernetes_httproute_parent_refs: list[dict[str, str]] = field(default_factory=list)
 80    kubernetes_service_type: str = field(default="ClusterIP")
 81    kubernetes_disabled_components: list[str] = field(default_factory=list)
 82    kubernetes_image_pull_secrets: list[str] = field(default_factory=list)
 83    kubernetes_json_patches: dict[str, list[dict[str, Any]]] | None = field(default=None)
 84    kubernetes_disable_x509_strict: bool = field(default=False)
 85
 86
 87class OutpostModel(Model):
 88    """Base model for providers that need more objects than just themselves"""
 89
 90    def get_required_objects(self) -> Iterable[models.Model | str | tuple[str, models.Model]]:
 91        """Return a list of all required objects"""
 92        return [self]
 93
 94    class Meta:
 95        abstract = True
 96
 97
 98class OutpostType(models.TextChoices):
 99    """Outpost types"""
100
101    PROXY = "proxy"
102    LDAP = "ldap"
103    RADIUS = "radius"
104    RAC = "rac"
105
106
107def default_outpost_config(host: str | None = None):
108    """Get default outpost config"""
109    return asdict(OutpostConfig(authentik_host=host or ""))
110
111
112@dataclass
113class OutpostServiceConnectionState:
114    """State of an Outpost Service Connection"""
115
116    version: str
117    healthy: bool
118
119
120class OutpostServiceConnection(ScheduledModel, models.Model):
121    """Connection details for an Outpost Controller, like Docker or Kubernetes"""
122
123    uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
124    name = models.TextField(unique=True)
125
126    local = models.BooleanField(
127        default=False,
128        help_text=_(
129            "If enabled, use the local connection. Required Docker socket/Kubernetes Integration"
130        ),
131    )
132
133    objects = InheritanceManager()
134
135    class Meta:
136        verbose_name = _("Outpost Service-Connection")
137        verbose_name_plural = _("Outpost Service-Connections")
138
139    def __str__(self) -> str:
140        return f"Outpost service connection {self.name}"
141
142    @property
143    def state_key(self) -> str:
144        """Key used to save connection state in cache"""
145        return f"goauthentik.io/outposts/service_connection_state/{self.pk.hex}"
146
147    @property
148    def state(self) -> OutpostServiceConnectionState:
149        """Get state of service connection"""
150        from authentik.outposts.tasks import outpost_service_connection_monitor
151
152        state = cache.get(self.state_key, None)
153        if not state:
154            outpost_service_connection_monitor.send_with_options(args=(self.pk,), rel_obj=self)
155            return OutpostServiceConnectionState("", False)
156        return state
157
158    @property
159    def component(self) -> str:
160        """Return component used to edit this object"""
161        # This is called when creating an outpost with a service connection
162        # since the response doesn't use the correct inheritance
163        return ""
164
165    @property
166    def schedule_specs(self) -> list[ScheduleSpec]:
167        from authentik.outposts.tasks import outpost_service_connection_monitor
168
169        return [
170            ScheduleSpec(
171                actor=outpost_service_connection_monitor,
172                uid=self.name,
173                args=(self.pk,),
174                crontab="3-59/15 * * * *",
175                send_on_save=True,
176            ),
177        ]
178
179
180class DockerServiceConnection(SerializerModel, OutpostServiceConnection):
181    """Service Connection to a Docker endpoint"""
182
183    url = models.TextField(
184        help_text=_(
185            "Can be in the format of 'unix://<path>' when connecting to a local docker daemon, "
186            "or 'https://<hostname>:2376' when connecting to a remote system."
187        )
188    )
189    tls_verification = models.ForeignKey(
190        CertificateKeyPair,
191        null=True,
192        blank=True,
193        default=None,
194        related_name="+",
195        on_delete=models.SET_DEFAULT,
196        help_text=_(
197            "CA which the endpoint's Certificate is verified against. "
198            "Can be left empty for no validation."
199        ),
200    )
201    tls_authentication = models.ForeignKey(
202        CertificateKeyPair,
203        null=True,
204        blank=True,
205        default=None,
206        related_name="+",
207        on_delete=models.SET_DEFAULT,
208        help_text=_(
209            "Certificate/Key used for authentication. Can be left empty for no authentication."
210        ),
211    )
212
213    class Meta:
214        verbose_name = _("Docker Service-Connection")
215        verbose_name_plural = _("Docker Service-Connections")
216
217    def __str__(self) -> str:
218        return f"Docker Service-Connection {self.name}"
219
220    @property
221    def serializer(self) -> Serializer:
222        from authentik.outposts.api.service_connections import DockerServiceConnectionSerializer
223
224        return DockerServiceConnectionSerializer
225
226    @property
227    def component(self) -> str:
228        return "ak-service-connection-docker-form"
229
230
231class KubernetesServiceConnection(SerializerModel, OutpostServiceConnection):
232    """Service Connection to a Kubernetes cluster"""
233
234    kubeconfig = models.JSONField(
235        help_text=_(
236            "Paste your kubeconfig here. authentik will automatically use "
237            "the currently selected context."
238        ),
239        blank=True,
240    )
241    verify_ssl = models.BooleanField(
242        default=True, help_text=_("Verify SSL Certificates of the Kubernetes API endpoint")
243    )
244
245    class Meta:
246        verbose_name = _("Kubernetes Service-Connection")
247        verbose_name_plural = _("Kubernetes Service-Connections")
248
249    def __str__(self) -> str:
250        return f"Kubernetes Service-Connection {self.name}"
251
252    @property
253    def serializer(self) -> Serializer:
254        from authentik.outposts.api.service_connections import KubernetesServiceConnectionSerializer
255
256        return KubernetesServiceConnectionSerializer
257
258    @property
259    def component(self) -> str:
260        return "ak-service-connection-kubernetes-form"
261
262
263class Outpost(ScheduledModel, SerializerModel, ManagedModel):
264    """Outpost instance which manages a service user and token"""
265
266    uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
267    name = models.TextField(unique=True)
268
269    type = models.TextField(choices=OutpostType.choices, default=OutpostType.PROXY)
270    service_connection = InheritanceForeignKey(
271        OutpostServiceConnection,
272        default=None,
273        null=True,
274        blank=True,
275        help_text=_(
276            "Select Service-Connection authentik should use to manage this outpost. "
277            "Leave empty if authentik should not handle the deployment."
278        ),
279        on_delete=models.SET_DEFAULT,
280    )
281
282    _config = models.JSONField(default=default_outpost_config)
283
284    providers = models.ManyToManyField(Provider)
285
286    @property
287    def serializer(self) -> Serializer:
288        from authentik.outposts.api.outposts import OutpostSerializer
289
290        return OutpostSerializer
291
292    @property
293    def config(self) -> OutpostConfig:
294        """Load config as OutpostConfig object"""
295        return from_dict(OutpostConfig, self._config)
296
297    @config.setter
298    def config(self, value):
299        """Dump config into json"""
300        self._config = asdict(value)
301
302    @property
303    def state_cache_prefix(self) -> str:
304        """Key by which the outposts status is saved"""
305        return f"goauthentik.io/outposts/state/{self.uuid.hex}"
306
307    @property
308    def state(self) -> list[OutpostState]:
309        """Get outpost's health status"""
310        return OutpostState.for_outpost(self)
311
312    @property
313    def user_identifier(self):
314        """Username for service user"""
315        return f"ak-outpost-{self.uuid.hex}"
316
317    @property
318    def schedule_specs(self) -> list[ScheduleSpec]:
319        from authentik.outposts.tasks import outpost_controller
320
321        return [
322            ScheduleSpec(
323                actor=outpost_controller,
324                uid=self.name,
325                args=(self.pk,),
326                kwargs={"action": "up", "from_cache": False},
327                crontab=f"{fqdn_rand('outpost_controller')} */4 * * *",
328                send_on_save=True,
329            ),
330        ]
331
332    def build_user_permissions(self, user: User):
333        """Create per-object and global permissions for outpost service-account"""
334        # To ensure the user only has the correct permissions, we delete all of them and re-add
335        # the ones the user needs
336        try:
337            with transaction.atomic():
338                user.remove_all_perms_from_managed_role()
339                for model_or_perm in self.get_required_objects():
340                    if isinstance(model_or_perm, models.Model):
341                        code_name = (
342                            f"{model_or_perm._meta.app_label}.view_{model_or_perm._meta.model_name}"
343                        )
344                        user.assign_perms_to_managed_role(code_name, model_or_perm)
345                    elif isinstance(model_or_perm, tuple):
346                        perm, obj = model_or_perm
347                        user.assign_perms_to_managed_role(perm, obj)
348                    else:
349                        user.assign_perms_to_managed_role(model_or_perm)
350        except (Permission.DoesNotExist, AttributeError) as exc:
351            LOGGER.warning(
352                "permission doesn't exist",
353                code_name=code_name,
354                user=user,
355                model=model_or_perm,
356            )
357            Event.new(
358                action=EventAction.SYSTEM_EXCEPTION,
359                message=(
360                    "While setting the permissions for the service-account, a "
361                    "permission was not found: Check "
362                    "https://docs.goauthentik.io/troubleshooting/missing_permission"
363                ),
364            ).with_exception(exc).set_user(user).save()
365        LOGGER.debug(
366            "Updated service account's permissions",
367            obj_perms=user.get_all_obj_perms_on_managed_role(),
368            perms=user.get_all_model_perms_on_managed_role(),
369        )
370
371    @property
372    def user(self) -> User:
373        """Get/create user with access to all required objects"""
374        user = User.objects.filter(username=self.user_identifier).first()
375        user_created = False
376        if not user:
377            user: User = User.objects.create(username=self.user_identifier)
378            user_created = True
379        attrs = {
380            "type": UserTypes.INTERNAL_SERVICE_ACCOUNT,
381            "name": f"Outpost {self.name} Service-Account",
382            "path": USER_PATH_OUTPOSTS,
383        }
384        dirty = False
385        for key, value in attrs.items():
386            if getattr(user, key) != value:
387                dirty = True
388                setattr(user, key, value)
389        if user.has_usable_password():
390            user.set_unusable_password()
391            dirty = True
392        if dirty:
393            user.save()
394        if user_created:
395            self.build_user_permissions(user)
396        return user
397
398    @property
399    def token_identifier(self) -> str:
400        """Get Token identifier"""
401        return f"ak-outpost-{self.pk}-api"
402
403    @property
404    def token(self) -> Token:
405        """Get/create token for auto-generated user"""
406        managed = f"goauthentik.io/outpost/{self.token_identifier}"
407        tokens = Token.objects.filter(
408            identifier=self.token_identifier,
409            intent=TokenIntents.INTENT_API,
410            managed=managed,
411        )
412        if tokens.exists():
413            return tokens.first()
414        try:
415            return Token.objects.create(
416                user=self.user,
417                identifier=self.token_identifier,
418                intent=TokenIntents.INTENT_API,
419                description=f"Autogenerated by authentik for Outpost {self.name}",
420                expiring=False,
421                managed=managed,
422            )
423        except IntegrityError:
424            # Integrity error happens mostly when managed is reused
425            Token.objects.filter(managed=managed).delete()
426            Token.objects.filter(identifier=self.token_identifier).delete()
427            return self.token
428
429    def get_required_objects(self) -> Iterable[models.Model | str | tuple[str, models.Model]]:
430        """Get an iterator of all objects the user needs read access to"""
431        objects: list[models.Model | str] = [
432            self,
433            "authentik_events.add_event",
434        ]
435        for provider in Provider.objects.filter(outpost=self).select_related().select_subclasses():
436            if isinstance(provider, OutpostModel):
437                objects.extend(provider.get_required_objects())
438            else:
439                objects.append(provider)
440        if self.managed:
441            for brand in Brand.objects.filter(web_certificate__isnull=False):
442                objects.append(brand)
443                objects.append(("authentik_crypto.view_certificatekeypair", brand.web_certificate))
444                objects.append(
445                    ("authentik_crypto.view_certificatekeypair_certificate", brand.web_certificate)
446                )
447                objects.append(
448                    ("authentik_crypto.view_certificatekeypair_key", brand.web_certificate)
449                )
450        return objects
451
452    def __str__(self) -> str:
453        return f"Outpost {self.name}"
454
455    class Meta:
456        verbose_name = _("Outpost")
457        verbose_name_plural = _("Outposts")
458
459
460@dataclass
461class OutpostState:
462    """Outpost instance state, last_seen and version"""
463
464    uid: str
465    last_seen: datetime | None = field(default=None)
466    version: str | None = field(default=None)
467    build_hash: str = field(default="")
468    golang_version: str = field(default="")
469    openssl_enabled: bool = field(default=False)
470    openssl_version: str = field(default="")
471    fips_enabled: bool = field(default=False)
472    hostname: str = field(default="")
473    args: dict = field(default_factory=dict)
474
475    _outpost: Outpost | None = field(default=None)
476
477    @property
478    def version_outdated(self) -> bool:
479        """Check if outpost version matches our version"""
480        if not self.version:
481            return False
482        if self.build_hash != authentik_build_hash():
483            return False
484        return parse(self.version) != OUR_VERSION
485
486    @staticmethod
487    def for_outpost(outpost: Outpost) -> list[OutpostState]:
488        """Get all states for an outpost"""
489        keys = cache.keys(f"{outpost.state_cache_prefix}/*")
490        if not keys:
491            return []
492        states = []
493        for key in keys:
494            instance_uid = key.replace(f"{outpost.state_cache_prefix}/", "")
495            states.append(OutpostState.for_instance_uid(outpost, instance_uid))
496        return states
497
498    @staticmethod
499    def for_instance_uid(outpost: Outpost, uid: str) -> OutpostState:
500        """Get state for a single instance"""
501        key = f"{outpost.state_cache_prefix}/{uid}"
502        default_data = {"uid": uid}
503        data = cache.get(key, default_data)
504        if isinstance(data, str):
505            cache.delete(key)
506            data = default_data
507        state = from_dict(OutpostState, data)
508
509        state._outpost = outpost
510        return state
511
512    def save(self, timeout=OUTPOST_HELLO_INTERVAL):
513        """Save current state to cache"""
514        full_key = f"{self._outpost.state_cache_prefix}/{self.uid}"
515        return cache.set(full_key, asdict(self), timeout=timeout)
516
517    def delete(self):
518        """Manually delete from cache, used on channel disconnect"""
519        full_key = f"{self._outpost.state_cache_prefix}/{self.uid}"
520        cache.delete(full_key)
OUR_VERSION = <Version('2026.8.0rc1')>
OUTPOST_HELLO_INTERVAL = 10
LOGGER = <BoundLoggerLazyProxy(logger=None, wrapper_class=None, processors=None, context_class=None, initial_values={}, logger_factory_args=())>
USER_PATH_OUTPOSTS = 'goauthentik.io/outposts'
class ServiceConnectionInvalid(authentik.lib.sentry.SentryIgnoredException):
49class ServiceConnectionInvalid(SentryIgnoredException):
50    """Exception raised when a Service Connection has invalid parameters"""

Exception raised when a Service Connection has invalid parameters

@dataclass
class OutpostConfig:
53@dataclass
54class OutpostConfig:
55    """Configuration an outpost uses to configure it self"""
56
57    # update website/docs/add-secure-apps/outposts/_config.md
58
59    authentik_host: str = ""
60    authentik_host_insecure: bool = False
61    authentik_host_browser: str = ""
62
63    log_level: str = CONFIG.get("log_level")
64    object_naming_template: str = field(default="ak-outpost-%(name)s")
65    refresh_interval: str = "minutes=5"
66
67    container_image: str | None = field(default=None)
68
69    docker_network: str | None = field(default=None)
70    docker_map_ports: bool = field(default=True)
71    docker_labels: dict[str, str] | None = field(default=None)
72
73    kubernetes_replicas: int = field(default=1)
74    kubernetes_namespace: str = field(default_factory=get_namespace)
75    kubernetes_ingress_annotations: dict[str, str] = field(default_factory=dict)
76    kubernetes_ingress_secret_name: str = field(default="authentik-outpost-tls")
77    kubernetes_ingress_class_name: str | None = field(default=None)
78    kubernetes_ingress_path_type: str | None = field(default=None)
79    kubernetes_httproute_annotations: dict[str, str] = field(default_factory=dict)
80    kubernetes_httproute_parent_refs: list[dict[str, str]] = field(default_factory=list)
81    kubernetes_service_type: str = field(default="ClusterIP")
82    kubernetes_disabled_components: list[str] = field(default_factory=list)
83    kubernetes_image_pull_secrets: list[str] = field(default_factory=list)
84    kubernetes_json_patches: dict[str, list[dict[str, Any]]] | None = field(default=None)
85    kubernetes_disable_x509_strict: bool = field(default=False)

Configuration an outpost uses to configure it self

OutpostConfig( authentik_host: str = '', authentik_host_insecure: bool = False, authentik_host_browser: str = '', log_level: str = 'debug', object_naming_template: str = 'ak-outpost-%(name)s', refresh_interval: str = 'minutes=5', container_image: str | None = None, docker_network: str | None = None, docker_map_ports: bool = True, docker_labels: dict[str, str] | None = None, kubernetes_replicas: int = 1, kubernetes_namespace: str = <factory>, kubernetes_ingress_annotations: dict[str, str] = <factory>, kubernetes_ingress_secret_name: str = 'authentik-outpost-tls', kubernetes_ingress_class_name: str | None = None, kubernetes_ingress_path_type: str | None = None, kubernetes_httproute_annotations: dict[str, str] = <factory>, kubernetes_httproute_parent_refs: list[dict[str, str]] = <factory>, kubernetes_service_type: str = 'ClusterIP', kubernetes_disabled_components: list[str] = <factory>, kubernetes_image_pull_secrets: list[str] = <factory>, kubernetes_json_patches: dict[str, list[dict[str, Any]]] | None = None, kubernetes_disable_x509_strict: bool = False)
authentik_host: str = ''
authentik_host_insecure: bool = False
authentik_host_browser: str = ''
log_level: str = 'debug'
object_naming_template: str = 'ak-outpost-%(name)s'
refresh_interval: str = 'minutes=5'
container_image: str | None = None
docker_network: str | None = None
docker_map_ports: bool = True
docker_labels: dict[str, str] | None = None
kubernetes_replicas: int = 1
kubernetes_namespace: str
kubernetes_ingress_annotations: dict[str, str]
kubernetes_ingress_secret_name: str = 'authentik-outpost-tls'
kubernetes_ingress_class_name: str | None = None
kubernetes_ingress_path_type: str | None = None
kubernetes_httproute_annotations: dict[str, str]
kubernetes_httproute_parent_refs: list[dict[str, str]]
kubernetes_service_type: str = 'ClusterIP'
kubernetes_disabled_components: list[str]
kubernetes_image_pull_secrets: list[str]
kubernetes_json_patches: dict[str, list[dict[str, Any]]] | None = None
kubernetes_disable_x509_strict: bool = False
class OutpostModel(django.db.models.base.Model):
88class OutpostModel(Model):
89    """Base model for providers that need more objects than just themselves"""
90
91    def get_required_objects(self) -> Iterable[models.Model | str | tuple[str, models.Model]]:
92        """Return a list of all required objects"""
93        return [self]
94
95    class Meta:
96        abstract = True

Base model for providers that need more objects than just themselves

def get_required_objects( self) -> Iterable[django.db.models.base.Model | str | tuple[str, django.db.models.base.Model]]:
91    def get_required_objects(self) -> Iterable[models.Model | str | tuple[str, models.Model]]:
92        """Return a list of all required objects"""
93        return [self]

Return a list of all required objects

class OutpostModel.Meta:
95    class Meta:
96        abstract = True
abstract = False
class OutpostType(django.db.models.enums.TextChoices):
 99class OutpostType(models.TextChoices):
100    """Outpost types"""
101
102    PROXY = "proxy"
103    LDAP = "ldap"
104    RADIUS = "radius"
105    RAC = "rac"

Outpost types

def default_outpost_config(host: str | None = None):
108def default_outpost_config(host: str | None = None):
109    """Get default outpost config"""
110    return asdict(OutpostConfig(authentik_host=host or ""))

Get default outpost config

@dataclass
class OutpostServiceConnectionState:
113@dataclass
114class OutpostServiceConnectionState:
115    """State of an Outpost Service Connection"""
116
117    version: str
118    healthy: bool

State of an Outpost Service Connection

OutpostServiceConnectionState(version: str, healthy: bool)
version: str
healthy: bool
class OutpostServiceConnection(authentik.tasks.schedules.models.ScheduledModel, django.db.models.base.Model):
121class OutpostServiceConnection(ScheduledModel, models.Model):
122    """Connection details for an Outpost Controller, like Docker or Kubernetes"""
123
124    uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
125    name = models.TextField(unique=True)
126
127    local = models.BooleanField(
128        default=False,
129        help_text=_(
130            "If enabled, use the local connection. Required Docker socket/Kubernetes Integration"
131        ),
132    )
133
134    objects = InheritanceManager()
135
136    class Meta:
137        verbose_name = _("Outpost Service-Connection")
138        verbose_name_plural = _("Outpost Service-Connections")
139
140    def __str__(self) -> str:
141        return f"Outpost service connection {self.name}"
142
143    @property
144    def state_key(self) -> str:
145        """Key used to save connection state in cache"""
146        return f"goauthentik.io/outposts/service_connection_state/{self.pk.hex}"
147
148    @property
149    def state(self) -> OutpostServiceConnectionState:
150        """Get state of service connection"""
151        from authentik.outposts.tasks import outpost_service_connection_monitor
152
153        state = cache.get(self.state_key, None)
154        if not state:
155            outpost_service_connection_monitor.send_with_options(args=(self.pk,), rel_obj=self)
156            return OutpostServiceConnectionState("", False)
157        return state
158
159    @property
160    def component(self) -> str:
161        """Return component used to edit this object"""
162        # This is called when creating an outpost with a service connection
163        # since the response doesn't use the correct inheritance
164        return ""
165
166    @property
167    def schedule_specs(self) -> list[ScheduleSpec]:
168        from authentik.outposts.tasks import outpost_service_connection_monitor
169
170        return [
171            ScheduleSpec(
172                actor=outpost_service_connection_monitor,
173                uid=self.name,
174                args=(self.pk,),
175                crontab="3-59/15 * * * *",
176                send_on_save=True,
177            ),
178        ]

Connection details for an Outpost Controller, like Docker or Kubernetes

def uuid(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def name(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def local(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def objects(unknown):

The type of the None singleton.

state_key: str
143    @property
144    def state_key(self) -> str:
145        """Key used to save connection state in cache"""
146        return f"goauthentik.io/outposts/service_connection_state/{self.pk.hex}"

Key used to save connection state in cache

state: OutpostServiceConnectionState
148    @property
149    def state(self) -> OutpostServiceConnectionState:
150        """Get state of service connection"""
151        from authentik.outposts.tasks import outpost_service_connection_monitor
152
153        state = cache.get(self.state_key, None)
154        if not state:
155            outpost_service_connection_monitor.send_with_options(args=(self.pk,), rel_obj=self)
156            return OutpostServiceConnectionState("", False)
157        return state

Get state of service connection

component: str
159    @property
160    def component(self) -> str:
161        """Return component used to edit this object"""
162        # This is called when creating an outpost with a service connection
163        # since the response doesn't use the correct inheritance
164        return ""

Return component used to edit this object

schedule_specs: list[authentik.tasks.schedules.common.ScheduleSpec]
166    @property
167    def schedule_specs(self) -> list[ScheduleSpec]:
168        from authentik.outposts.tasks import outpost_service_connection_monitor
169
170        return [
171            ScheduleSpec(
172                actor=outpost_service_connection_monitor,
173                uid=self.name,
174                args=(self.pk,),
175                crontab="3-59/15 * * * *",
176                send_on_save=True,
177            ),
178        ]
schedules

Accessor to the related objects manager on the one-to-many relation created by GenericRelation.

In the example::

class Post(Model):
    comments = GenericRelation(Comment)

post.comments is a ReverseGenericManyToOneDescriptor instance.

tasks

Accessor to the related objects manager on the one-to-many relation created by GenericRelation.

In the example::

class Post(Model):
    comments = GenericRelation(Comment)

post.comments is a ReverseGenericManyToOneDescriptor instance.

dockerserviceconnection

Accessor to the related object on the reverse side of a one-to-one relation.

In the example::

class Restaurant(Model):
    place = OneToOneField(Place, related_name='restaurant')

Place.restaurant is a ReverseOneToOneDescriptor instance.

kubernetesserviceconnection

Accessor to the related object on the reverse side of a one-to-one relation.

In the example::

class Restaurant(Model):
    place = OneToOneField(Place, related_name='restaurant')

Place.restaurant is a ReverseOneToOneDescriptor instance.

outpost_set

Accessor to the related objects manager on the reverse side of a many-to-one relation.

In the example::

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Parent.children is a ReverseManyToOneDescriptor instance.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

class OutpostServiceConnection.DoesNotExist(django.core.exceptions.ObjectDoesNotExist):

The requested object does not exist

class OutpostServiceConnection.MultipleObjectsReturned(django.core.exceptions.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.

class DockerServiceConnection(authentik.lib.models.SerializerModel, OutpostServiceConnection):
181class DockerServiceConnection(SerializerModel, OutpostServiceConnection):
182    """Service Connection to a Docker endpoint"""
183
184    url = models.TextField(
185        help_text=_(
186            "Can be in the format of 'unix://<path>' when connecting to a local docker daemon, "
187            "or 'https://<hostname>:2376' when connecting to a remote system."
188        )
189    )
190    tls_verification = models.ForeignKey(
191        CertificateKeyPair,
192        null=True,
193        blank=True,
194        default=None,
195        related_name="+",
196        on_delete=models.SET_DEFAULT,
197        help_text=_(
198            "CA which the endpoint's Certificate is verified against. "
199            "Can be left empty for no validation."
200        ),
201    )
202    tls_authentication = models.ForeignKey(
203        CertificateKeyPair,
204        null=True,
205        blank=True,
206        default=None,
207        related_name="+",
208        on_delete=models.SET_DEFAULT,
209        help_text=_(
210            "Certificate/Key used for authentication. Can be left empty for no authentication."
211        ),
212    )
213
214    class Meta:
215        verbose_name = _("Docker Service-Connection")
216        verbose_name_plural = _("Docker Service-Connections")
217
218    def __str__(self) -> str:
219        return f"Docker Service-Connection {self.name}"
220
221    @property
222    def serializer(self) -> Serializer:
223        from authentik.outposts.api.service_connections import DockerServiceConnectionSerializer
224
225        return DockerServiceConnectionSerializer
226
227    @property
228    def component(self) -> str:
229        return "ak-service-connection-docker-form"

Service Connection to a Docker endpoint

def url(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

tls_verification

Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.

In the example::

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Child.parent is a ForwardManyToOneDescriptor instance.

tls_authentication

Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.

In the example::

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Child.parent is a ForwardManyToOneDescriptor instance.

serializer: rest_framework.serializers.Serializer
221    @property
222    def serializer(self) -> Serializer:
223        from authentik.outposts.api.service_connections import DockerServiceConnectionSerializer
224
225        return DockerServiceConnectionSerializer

Get serializer for this model

component: str
227    @property
228    def component(self) -> str:
229        return "ak-service-connection-docker-form"

Return component used to edit this object

schedules

Accessor to the related objects manager on the one-to-many relation created by GenericRelation.

In the example::

class Post(Model):
    comments = GenericRelation(Comment)

post.comments is a ReverseGenericManyToOneDescriptor instance.

tasks

Accessor to the related objects manager on the one-to-many relation created by GenericRelation.

In the example::

class Post(Model):
    comments = GenericRelation(Comment)

post.comments is a ReverseGenericManyToOneDescriptor instance.

tls_verification_id
tls_authentication_id
outpostserviceconnection_ptr_id
outpostserviceconnection_ptr

Accessor to the related object on the forward side of a one-to-one relation.

In the example::

class Restaurant(Model):
    place = OneToOneField(Place, related_name='restaurant')

Restaurant.place is a ForwardOneToOneDescriptor instance.

class DockerServiceConnection.DoesNotExist(OutpostServiceConnection.DoesNotExist):

The requested object does not exist

class DockerServiceConnection.MultipleObjectsReturned(OutpostServiceConnection.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.

class KubernetesServiceConnection(authentik.lib.models.SerializerModel, OutpostServiceConnection):
232class KubernetesServiceConnection(SerializerModel, OutpostServiceConnection):
233    """Service Connection to a Kubernetes cluster"""
234
235    kubeconfig = models.JSONField(
236        help_text=_(
237            "Paste your kubeconfig here. authentik will automatically use "
238            "the currently selected context."
239        ),
240        blank=True,
241    )
242    verify_ssl = models.BooleanField(
243        default=True, help_text=_("Verify SSL Certificates of the Kubernetes API endpoint")
244    )
245
246    class Meta:
247        verbose_name = _("Kubernetes Service-Connection")
248        verbose_name_plural = _("Kubernetes Service-Connections")
249
250    def __str__(self) -> str:
251        return f"Kubernetes Service-Connection {self.name}"
252
253    @property
254    def serializer(self) -> Serializer:
255        from authentik.outposts.api.service_connections import KubernetesServiceConnectionSerializer
256
257        return KubernetesServiceConnectionSerializer
258
259    @property
260    def component(self) -> str:
261        return "ak-service-connection-kubernetes-form"

Service Connection to a Kubernetes cluster

def kubeconfig(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def verify_ssl(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

serializer: rest_framework.serializers.Serializer
253    @property
254    def serializer(self) -> Serializer:
255        from authentik.outposts.api.service_connections import KubernetesServiceConnectionSerializer
256
257        return KubernetesServiceConnectionSerializer

Get serializer for this model

component: str
259    @property
260    def component(self) -> str:
261        return "ak-service-connection-kubernetes-form"

Return component used to edit this object

schedules

Accessor to the related objects manager on the one-to-many relation created by GenericRelation.

In the example::

class Post(Model):
    comments = GenericRelation(Comment)

post.comments is a ReverseGenericManyToOneDescriptor instance.

tasks

Accessor to the related objects manager on the one-to-many relation created by GenericRelation.

In the example::

class Post(Model):
    comments = GenericRelation(Comment)

post.comments is a ReverseGenericManyToOneDescriptor instance.

outpostserviceconnection_ptr_id
outpostserviceconnection_ptr

Accessor to the related object on the forward side of a one-to-one relation.

In the example::

class Restaurant(Model):
    place = OneToOneField(Place, related_name='restaurant')

Restaurant.place is a ForwardOneToOneDescriptor instance.

class KubernetesServiceConnection.DoesNotExist(OutpostServiceConnection.DoesNotExist):

The requested object does not exist

class KubernetesServiceConnection.MultipleObjectsReturned(OutpostServiceConnection.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.

264class Outpost(ScheduledModel, SerializerModel, ManagedModel):
265    """Outpost instance which manages a service user and token"""
266
267    uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
268    name = models.TextField(unique=True)
269
270    type = models.TextField(choices=OutpostType.choices, default=OutpostType.PROXY)
271    service_connection = InheritanceForeignKey(
272        OutpostServiceConnection,
273        default=None,
274        null=True,
275        blank=True,
276        help_text=_(
277            "Select Service-Connection authentik should use to manage this outpost. "
278            "Leave empty if authentik should not handle the deployment."
279        ),
280        on_delete=models.SET_DEFAULT,
281    )
282
283    _config = models.JSONField(default=default_outpost_config)
284
285    providers = models.ManyToManyField(Provider)
286
287    @property
288    def serializer(self) -> Serializer:
289        from authentik.outposts.api.outposts import OutpostSerializer
290
291        return OutpostSerializer
292
293    @property
294    def config(self) -> OutpostConfig:
295        """Load config as OutpostConfig object"""
296        return from_dict(OutpostConfig, self._config)
297
298    @config.setter
299    def config(self, value):
300        """Dump config into json"""
301        self._config = asdict(value)
302
303    @property
304    def state_cache_prefix(self) -> str:
305        """Key by which the outposts status is saved"""
306        return f"goauthentik.io/outposts/state/{self.uuid.hex}"
307
308    @property
309    def state(self) -> list[OutpostState]:
310        """Get outpost's health status"""
311        return OutpostState.for_outpost(self)
312
313    @property
314    def user_identifier(self):
315        """Username for service user"""
316        return f"ak-outpost-{self.uuid.hex}"
317
318    @property
319    def schedule_specs(self) -> list[ScheduleSpec]:
320        from authentik.outposts.tasks import outpost_controller
321
322        return [
323            ScheduleSpec(
324                actor=outpost_controller,
325                uid=self.name,
326                args=(self.pk,),
327                kwargs={"action": "up", "from_cache": False},
328                crontab=f"{fqdn_rand('outpost_controller')} */4 * * *",
329                send_on_save=True,
330            ),
331        ]
332
333    def build_user_permissions(self, user: User):
334        """Create per-object and global permissions for outpost service-account"""
335        # To ensure the user only has the correct permissions, we delete all of them and re-add
336        # the ones the user needs
337        try:
338            with transaction.atomic():
339                user.remove_all_perms_from_managed_role()
340                for model_or_perm in self.get_required_objects():
341                    if isinstance(model_or_perm, models.Model):
342                        code_name = (
343                            f"{model_or_perm._meta.app_label}.view_{model_or_perm._meta.model_name}"
344                        )
345                        user.assign_perms_to_managed_role(code_name, model_or_perm)
346                    elif isinstance(model_or_perm, tuple):
347                        perm, obj = model_or_perm
348                        user.assign_perms_to_managed_role(perm, obj)
349                    else:
350                        user.assign_perms_to_managed_role(model_or_perm)
351        except (Permission.DoesNotExist, AttributeError) as exc:
352            LOGGER.warning(
353                "permission doesn't exist",
354                code_name=code_name,
355                user=user,
356                model=model_or_perm,
357            )
358            Event.new(
359                action=EventAction.SYSTEM_EXCEPTION,
360                message=(
361                    "While setting the permissions for the service-account, a "
362                    "permission was not found: Check "
363                    "https://docs.goauthentik.io/troubleshooting/missing_permission"
364                ),
365            ).with_exception(exc).set_user(user).save()
366        LOGGER.debug(
367            "Updated service account's permissions",
368            obj_perms=user.get_all_obj_perms_on_managed_role(),
369            perms=user.get_all_model_perms_on_managed_role(),
370        )
371
372    @property
373    def user(self) -> User:
374        """Get/create user with access to all required objects"""
375        user = User.objects.filter(username=self.user_identifier).first()
376        user_created = False
377        if not user:
378            user: User = User.objects.create(username=self.user_identifier)
379            user_created = True
380        attrs = {
381            "type": UserTypes.INTERNAL_SERVICE_ACCOUNT,
382            "name": f"Outpost {self.name} Service-Account",
383            "path": USER_PATH_OUTPOSTS,
384        }
385        dirty = False
386        for key, value in attrs.items():
387            if getattr(user, key) != value:
388                dirty = True
389                setattr(user, key, value)
390        if user.has_usable_password():
391            user.set_unusable_password()
392            dirty = True
393        if dirty:
394            user.save()
395        if user_created:
396            self.build_user_permissions(user)
397        return user
398
399    @property
400    def token_identifier(self) -> str:
401        """Get Token identifier"""
402        return f"ak-outpost-{self.pk}-api"
403
404    @property
405    def token(self) -> Token:
406        """Get/create token for auto-generated user"""
407        managed = f"goauthentik.io/outpost/{self.token_identifier}"
408        tokens = Token.objects.filter(
409            identifier=self.token_identifier,
410            intent=TokenIntents.INTENT_API,
411            managed=managed,
412        )
413        if tokens.exists():
414            return tokens.first()
415        try:
416            return Token.objects.create(
417                user=self.user,
418                identifier=self.token_identifier,
419                intent=TokenIntents.INTENT_API,
420                description=f"Autogenerated by authentik for Outpost {self.name}",
421                expiring=False,
422                managed=managed,
423            )
424        except IntegrityError:
425            # Integrity error happens mostly when managed is reused
426            Token.objects.filter(managed=managed).delete()
427            Token.objects.filter(identifier=self.token_identifier).delete()
428            return self.token
429
430    def get_required_objects(self) -> Iterable[models.Model | str | tuple[str, models.Model]]:
431        """Get an iterator of all objects the user needs read access to"""
432        objects: list[models.Model | str] = [
433            self,
434            "authentik_events.add_event",
435        ]
436        for provider in Provider.objects.filter(outpost=self).select_related().select_subclasses():
437            if isinstance(provider, OutpostModel):
438                objects.extend(provider.get_required_objects())
439            else:
440                objects.append(provider)
441        if self.managed:
442            for brand in Brand.objects.filter(web_certificate__isnull=False):
443                objects.append(brand)
444                objects.append(("authentik_crypto.view_certificatekeypair", brand.web_certificate))
445                objects.append(
446                    ("authentik_crypto.view_certificatekeypair_certificate", brand.web_certificate)
447                )
448                objects.append(
449                    ("authentik_crypto.view_certificatekeypair_key", brand.web_certificate)
450                )
451        return objects
452
453    def __str__(self) -> str:
454        return f"Outpost {self.name}"
455
456    class Meta:
457        verbose_name = _("Outpost")
458        verbose_name_plural = _("Outposts")

Outpost instance which manages a service user and token

def uuid(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def name(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def type(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

service_connection

Forward ManyToOne Descriptor that selects subclass. Requires InheritanceAutoManager.

providers

Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.

In the example::

class Pizza(Model):
    toppings = ManyToManyField(Topping, related_name='pizzas')

Pizza.toppings and Topping.pizzas are ManyToManyDescriptor instances.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

serializer: rest_framework.serializers.Serializer
287    @property
288    def serializer(self) -> Serializer:
289        from authentik.outposts.api.outposts import OutpostSerializer
290
291        return OutpostSerializer

Get serializer for this model

config: OutpostConfig
293    @property
294    def config(self) -> OutpostConfig:
295        """Load config as OutpostConfig object"""
296        return from_dict(OutpostConfig, self._config)

Load config as OutpostConfig object

state_cache_prefix: str
303    @property
304    def state_cache_prefix(self) -> str:
305        """Key by which the outposts status is saved"""
306        return f"goauthentik.io/outposts/state/{self.uuid.hex}"

Key by which the outposts status is saved

state: list[OutpostState]
308    @property
309    def state(self) -> list[OutpostState]:
310        """Get outpost's health status"""
311        return OutpostState.for_outpost(self)

Get outpost's health status

user_identifier
313    @property
314    def user_identifier(self):
315        """Username for service user"""
316        return f"ak-outpost-{self.uuid.hex}"

Username for service user

schedule_specs: list[authentik.tasks.schedules.common.ScheduleSpec]
318    @property
319    def schedule_specs(self) -> list[ScheduleSpec]:
320        from authentik.outposts.tasks import outpost_controller
321
322        return [
323            ScheduleSpec(
324                actor=outpost_controller,
325                uid=self.name,
326                args=(self.pk,),
327                kwargs={"action": "up", "from_cache": False},
328                crontab=f"{fqdn_rand('outpost_controller')} */4 * * *",
329                send_on_save=True,
330            ),
331        ]
def build_user_permissions(self, user: authentik.core.models.User):
333    def build_user_permissions(self, user: User):
334        """Create per-object and global permissions for outpost service-account"""
335        # To ensure the user only has the correct permissions, we delete all of them and re-add
336        # the ones the user needs
337        try:
338            with transaction.atomic():
339                user.remove_all_perms_from_managed_role()
340                for model_or_perm in self.get_required_objects():
341                    if isinstance(model_or_perm, models.Model):
342                        code_name = (
343                            f"{model_or_perm._meta.app_label}.view_{model_or_perm._meta.model_name}"
344                        )
345                        user.assign_perms_to_managed_role(code_name, model_or_perm)
346                    elif isinstance(model_or_perm, tuple):
347                        perm, obj = model_or_perm
348                        user.assign_perms_to_managed_role(perm, obj)
349                    else:
350                        user.assign_perms_to_managed_role(model_or_perm)
351        except (Permission.DoesNotExist, AttributeError) as exc:
352            LOGGER.warning(
353                "permission doesn't exist",
354                code_name=code_name,
355                user=user,
356                model=model_or_perm,
357            )
358            Event.new(
359                action=EventAction.SYSTEM_EXCEPTION,
360                message=(
361                    "While setting the permissions for the service-account, a "
362                    "permission was not found: Check "
363                    "https://docs.goauthentik.io/troubleshooting/missing_permission"
364                ),
365            ).with_exception(exc).set_user(user).save()
366        LOGGER.debug(
367            "Updated service account's permissions",
368            obj_perms=user.get_all_obj_perms_on_managed_role(),
369            perms=user.get_all_model_perms_on_managed_role(),
370        )

Create per-object and global permissions for outpost service-account

user: authentik.core.models.User
372    @property
373    def user(self) -> User:
374        """Get/create user with access to all required objects"""
375        user = User.objects.filter(username=self.user_identifier).first()
376        user_created = False
377        if not user:
378            user: User = User.objects.create(username=self.user_identifier)
379            user_created = True
380        attrs = {
381            "type": UserTypes.INTERNAL_SERVICE_ACCOUNT,
382            "name": f"Outpost {self.name} Service-Account",
383            "path": USER_PATH_OUTPOSTS,
384        }
385        dirty = False
386        for key, value in attrs.items():
387            if getattr(user, key) != value:
388                dirty = True
389                setattr(user, key, value)
390        if user.has_usable_password():
391            user.set_unusable_password()
392            dirty = True
393        if dirty:
394            user.save()
395        if user_created:
396            self.build_user_permissions(user)
397        return user

Get/create user with access to all required objects

token_identifier: str
399    @property
400    def token_identifier(self) -> str:
401        """Get Token identifier"""
402        return f"ak-outpost-{self.pk}-api"

Get Token identifier

token: authentik.core.models.Token
404    @property
405    def token(self) -> Token:
406        """Get/create token for auto-generated user"""
407        managed = f"goauthentik.io/outpost/{self.token_identifier}"
408        tokens = Token.objects.filter(
409            identifier=self.token_identifier,
410            intent=TokenIntents.INTENT_API,
411            managed=managed,
412        )
413        if tokens.exists():
414            return tokens.first()
415        try:
416            return Token.objects.create(
417                user=self.user,
418                identifier=self.token_identifier,
419                intent=TokenIntents.INTENT_API,
420                description=f"Autogenerated by authentik for Outpost {self.name}",
421                expiring=False,
422                managed=managed,
423            )
424        except IntegrityError:
425            # Integrity error happens mostly when managed is reused
426            Token.objects.filter(managed=managed).delete()
427            Token.objects.filter(identifier=self.token_identifier).delete()
428            return self.token

Get/create token for auto-generated user

def get_required_objects( self) -> Iterable[django.db.models.base.Model | str | tuple[str, django.db.models.base.Model]]:
430    def get_required_objects(self) -> Iterable[models.Model | str | tuple[str, models.Model]]:
431        """Get an iterator of all objects the user needs read access to"""
432        objects: list[models.Model | str] = [
433            self,
434            "authentik_events.add_event",
435        ]
436        for provider in Provider.objects.filter(outpost=self).select_related().select_subclasses():
437            if isinstance(provider, OutpostModel):
438                objects.extend(provider.get_required_objects())
439            else:
440                objects.append(provider)
441        if self.managed:
442            for brand in Brand.objects.filter(web_certificate__isnull=False):
443                objects.append(brand)
444                objects.append(("authentik_crypto.view_certificatekeypair", brand.web_certificate))
445                objects.append(
446                    ("authentik_crypto.view_certificatekeypair_certificate", brand.web_certificate)
447                )
448                objects.append(
449                    ("authentik_crypto.view_certificatekeypair_key", brand.web_certificate)
450                )
451        return objects

Get an iterator of all objects the user needs read access to

schedules

Accessor to the related objects manager on the one-to-many relation created by GenericRelation.

In the example::

class Post(Model):
    comments = GenericRelation(Comment)

post.comments is a ReverseGenericManyToOneDescriptor instance.

tasks

Accessor to the related objects manager on the one-to-many relation created by GenericRelation.

In the example::

class Post(Model):
    comments = GenericRelation(Comment)

post.comments is a ReverseGenericManyToOneDescriptor instance.

def managed(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def get_type_display(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

service_connection_id
def objects(unknown):

The type of the None singleton.

class Outpost.DoesNotExist(django.core.exceptions.ObjectDoesNotExist):

The requested object does not exist

class Outpost.MultipleObjectsReturned(django.core.exceptions.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.

@dataclass
class OutpostState:
461@dataclass
462class OutpostState:
463    """Outpost instance state, last_seen and version"""
464
465    uid: str
466    last_seen: datetime | None = field(default=None)
467    version: str | None = field(default=None)
468    build_hash: str = field(default="")
469    golang_version: str = field(default="")
470    openssl_enabled: bool = field(default=False)
471    openssl_version: str = field(default="")
472    fips_enabled: bool = field(default=False)
473    hostname: str = field(default="")
474    args: dict = field(default_factory=dict)
475
476    _outpost: Outpost | None = field(default=None)
477
478    @property
479    def version_outdated(self) -> bool:
480        """Check if outpost version matches our version"""
481        if not self.version:
482            return False
483        if self.build_hash != authentik_build_hash():
484            return False
485        return parse(self.version) != OUR_VERSION
486
487    @staticmethod
488    def for_outpost(outpost: Outpost) -> list[OutpostState]:
489        """Get all states for an outpost"""
490        keys = cache.keys(f"{outpost.state_cache_prefix}/*")
491        if not keys:
492            return []
493        states = []
494        for key in keys:
495            instance_uid = key.replace(f"{outpost.state_cache_prefix}/", "")
496            states.append(OutpostState.for_instance_uid(outpost, instance_uid))
497        return states
498
499    @staticmethod
500    def for_instance_uid(outpost: Outpost, uid: str) -> OutpostState:
501        """Get state for a single instance"""
502        key = f"{outpost.state_cache_prefix}/{uid}"
503        default_data = {"uid": uid}
504        data = cache.get(key, default_data)
505        if isinstance(data, str):
506            cache.delete(key)
507            data = default_data
508        state = from_dict(OutpostState, data)
509
510        state._outpost = outpost
511        return state
512
513    def save(self, timeout=OUTPOST_HELLO_INTERVAL):
514        """Save current state to cache"""
515        full_key = f"{self._outpost.state_cache_prefix}/{self.uid}"
516        return cache.set(full_key, asdict(self), timeout=timeout)
517
518    def delete(self):
519        """Manually delete from cache, used on channel disconnect"""
520        full_key = f"{self._outpost.state_cache_prefix}/{self.uid}"
521        cache.delete(full_key)

Outpost instance state, last_seen and version

OutpostState( uid: str, last_seen: datetime.datetime | None = None, version: str | None = None, build_hash: str = '', golang_version: str = '', openssl_enabled: bool = False, openssl_version: str = '', fips_enabled: bool = False, hostname: str = '', args: dict = <factory>, _outpost: Outpost | None = None)
uid: str
last_seen: datetime.datetime | None = None
version: str | None = None
build_hash: str = ''
golang_version: str = ''
openssl_enabled: bool = False
openssl_version: str = ''
fips_enabled: bool = False
hostname: str = ''
args: dict
version_outdated: bool
478    @property
479    def version_outdated(self) -> bool:
480        """Check if outpost version matches our version"""
481        if not self.version:
482            return False
483        if self.build_hash != authentik_build_hash():
484            return False
485        return parse(self.version) != OUR_VERSION

Check if outpost version matches our version

@staticmethod
def for_outpost( outpost: Outpost) -> list[OutpostState]:
487    @staticmethod
488    def for_outpost(outpost: Outpost) -> list[OutpostState]:
489        """Get all states for an outpost"""
490        keys = cache.keys(f"{outpost.state_cache_prefix}/*")
491        if not keys:
492            return []
493        states = []
494        for key in keys:
495            instance_uid = key.replace(f"{outpost.state_cache_prefix}/", "")
496            states.append(OutpostState.for_instance_uid(outpost, instance_uid))
497        return states

Get all states for an outpost

@staticmethod
def for_instance_uid( outpost: Outpost, uid: str) -> OutpostState:
499    @staticmethod
500    def for_instance_uid(outpost: Outpost, uid: str) -> OutpostState:
501        """Get state for a single instance"""
502        key = f"{outpost.state_cache_prefix}/{uid}"
503        default_data = {"uid": uid}
504        data = cache.get(key, default_data)
505        if isinstance(data, str):
506            cache.delete(key)
507            data = default_data
508        state = from_dict(OutpostState, data)
509
510        state._outpost = outpost
511        return state

Get state for a single instance

def save(self, timeout=10):
513    def save(self, timeout=OUTPOST_HELLO_INTERVAL):
514        """Save current state to cache"""
515        full_key = f"{self._outpost.state_cache_prefix}/{self.uid}"
516        return cache.set(full_key, asdict(self), timeout=timeout)

Save current state to cache

def delete(self):
518    def delete(self):
519        """Manually delete from cache, used on channel disconnect"""
520        full_key = f"{self._outpost.state_cache_prefix}/{self.uid}"
521        cache.delete(full_key)

Manually delete from cache, used on channel disconnect