Example #1
0
 def __init__(self, verbosity=1, failfast=False, keepdb=False, **_):
     self.verbosity = verbosity
     self.failfast = failfast
     self.keepdb = keepdb
     settings.TEST = True
     settings.CELERY_TASK_ALWAYS_EAGER = True
     CONFIG.y_set("authentik.avatars", "none")
Example #2
0
def should_backup() -> bool:
    """Check if we should be doing backups"""
    if SERVICE_HOST_ENV_NAME in environ and not CONFIG.y("postgresql.s3_backup.bucket"):
        LOGGER.info("Running in k8s and s3 backups are not configured, skipping")
        return False
    if not CONFIG.y_bool("postgresql.backup.enabled"):
        return False
    return True
Example #3
0
 def get(self, request: Request) -> Response:
     """Retrive public configuration options"""
     config = ConfigSerializer({
         "error_reporting_enabled":
         CONFIG.y("error_reporting.enabled"),
         "error_reporting_environment":
         CONFIG.y("error_reporting.environment"),
         "error_reporting_send_pii":
         CONFIG.y("error_reporting.send_pii"),
         "capabilities":
         self.get_capabilities(),
     })
     return Response(config.data)
Example #4
0
    def test_invalid_flow_redirect(self):
        """Tests that an invalid flow still redirects"""
        flow = Flow.objects.create(
            name="test-empty",
            slug="test-empty",
            designation=FlowDesignation.AUTHENTICATION,
        )

        CONFIG.update_from_dict({"domain": "testserver"})
        dest = "/unique-string"
        url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug})
        response = self.client.get(url + f"?{NEXT_ARG_NAME}={dest}")
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response.url, reverse("authentik_core:root-redirect"))
Example #5
0
    def test_invalid_empty_flow(self):
        """Tests that an empty flow returns the correct error message"""
        flow = Flow.objects.create(
            name="test-empty",
            slug="test-empty",
            designation=FlowDesignation.AUTHENTICATION,
        )

        CONFIG.update_from_dict({"domain": "testserver"})
        response = self.client.get(
            reverse("authentik_api:flow-executor",
                    kwargs={"flow_slug": flow.slug}), )
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response.url, reverse("authentik_core:root-redirect"))
Example #6
0
def context_processor(request: HttpRequest) -> dict[str, Any]:
    """Context Processor that injects tenant object into every template"""
    return {
        "tenant": request.tenant,
        "ak_version": __version__,
        "footer_links": CONFIG.y("authentik.footer_links"),
    }
    def test_deployment_reconciler(self):
        """test that deployment requires update"""
        controller = ProxyKubernetesController(self.outpost,
                                               self.service_connection)
        deployment_reconciler = DeploymentReconciler(controller)

        self.assertIsNotNone(deployment_reconciler.retrieve())

        config = self.outpost.config
        config.kubernetes_replicas = 3
        self.outpost.config = config

        with self.assertRaises(NeedsUpdate):
            deployment_reconciler.reconcile(
                deployment_reconciler.retrieve(),
                deployment_reconciler.get_reference_object(),
            )

        with CONFIG.patch("outposts.container_image_base", "test"):
            with self.assertRaises(NeedsUpdate):
                deployment_reconciler.reconcile(
                    deployment_reconciler.retrieve(),
                    deployment_reconciler.get_reference_object(),
                )

        deployment_reconciler.delete(
            deployment_reconciler.get_reference_object())
Example #8
0
class OutpostConfig:
    """Configuration an outpost uses to configure it self"""

    # update website/docs/outposts/outposts.md

    authentik_host: str = ""
    authentik_host_insecure: bool = False
    authentik_host_browser: str = ""

    log_level: str = CONFIG.y("log_level")
    object_naming_template: str = field(default="ak-outpost-%(name)s")

    docker_network: Optional[str] = field(default=None)
    docker_map_ports: bool = field(default=True)

    container_image: Optional[str] = field(default=None)

    kubernetes_replicas: int = field(default=1)
    kubernetes_namespace: str = field(default_factory=get_namespace)
    kubernetes_ingress_annotations: dict[str,
                                         str] = field(default_factory=dict)
    kubernetes_ingress_secret_name: str = field(
        default="authentik-outpost-tls")
    kubernetes_service_type: str = field(default="ClusterIP")
    kubernetes_disabled_components: list[str] = field(default_factory=list)
    kubernetes_image_pull_secrets: Optional[list[str]] = field(
        default_factory=list)
Example #9
0
 def test_discovery(self):
     """Test certificate discovery"""
     builder = CertificateBuilder()
     builder.common_name = "test-cert"
     with self.assertRaises(ValueError):
         builder.save()
     builder.build(
         subject_alt_names=[],
         validity_days=3,
     )
     with TemporaryDirectory() as temp_dir:
         with open(f"{temp_dir}/foo.pem", "w+", encoding="utf-8") as _cert:
             _cert.write(builder.certificate)
         with open(f"{temp_dir}/foo.key", "w+", encoding="utf-8") as _key:
             _key.write(builder.private_key)
         makedirs(f"{temp_dir}/foo.bar", exist_ok=True)
         with open(f"{temp_dir}/foo.bar/fullchain.pem",
                   "w+",
                   encoding="utf-8") as _cert:
             _cert.write(builder.certificate)
         with open(f"{temp_dir}/foo.bar/privkey.pem",
                   "w+",
                   encoding="utf-8") as _key:
             _key.write(builder.private_key)
         with CONFIG.patch("cert_discovery_dir", temp_dir):
             # pyright: reportGeneralTypeIssues=false
             certificate_discovery()  # pylint: disable=no-value-for-parameter
     keypair: CertificateKeyPair = CertificateKeyPair.objects.filter(
         managed=MANAGED_DISCOVERED % "foo").first()
     self.assertIsNotNone(keypair)
     self.assertIsNotNone(keypair.certificate)
     self.assertIsNotNone(keypair.private_key)
     self.assertTrue(
         CertificateKeyPair.objects.filter(managed=MANAGED_DISCOVERED %
                                           "foo.bar").exists())
Example #10
0
 def _get_container(self) -> tuple[Container, bool]:
     container_name = f"authentik-proxy-{self.outpost.uuid.hex}"
     try:
         return self.client.containers.get(container_name), False
     except NotFound:
         self.logger.info("Container does not exist, creating")
         image_prefix = CONFIG.y("outposts.docker_image_base")
         image_name = f"{image_prefix}-{self.outpost.type}:{__version__}"
         self.client.images.pull(image_name)
         container_args = {
             "image": image_name,
             "name": f"authentik-proxy-{self.outpost.uuid.hex}",
             "detach": True,
             "ports": {
                 f"{port.port}/{port.protocol.lower()}": port.port
                 for port in self.deployment_ports
             },
             "environment": self._get_env(),
             "labels": self._get_labels(),
         }
         if settings.TEST:
             del container_args["ports"]
             container_args["network_mode"] = "host"
         return (
             self.client.containers.create(**container_args),
             True,
         )
Example #11
0
 def get_static_deployment(self) -> str:
     """Generate docker-compose yaml for proxy, version 3.5"""
     ports = [
         f"{port.port}:{port.port}/{port.protocol.lower()}"
         for port in self.deployment_ports
     ]
     image_prefix = CONFIG.y("outposts.docker_image_base")
     compose = {
         "version": "3.5",
         "services": {
             f"authentik_{self.outpost.type}": {
                 "image":
                 f"{image_prefix}-{self.outpost.type}:{__version__}",
                 "ports": ports,
                 "environment": {
                     "AUTHENTIK_HOST":
                     self.outpost.config.authentik_host,
                     "AUTHENTIK_INSECURE":
                     str(self.outpost.config.authentik_host_insecure),
                     "AUTHENTIK_TOKEN":
                     self.outpost.token.key,
                 },
                 "labels": self._get_labels(),
             }
         },
     }
     return safe_dump(compose, default_flow_style=False)
Example #12
0
class OutpostConfig:
    """Configuration an outpost uses to configure it self"""

    authentik_host: str
    authentik_host_insecure: bool = False

    log_level: str = CONFIG.y("log_level")
    error_reporting_enabled: bool = CONFIG.y_bool("error_reporting.enabled")
    error_reporting_environment: str = CONFIG.y("error_reporting.environment",
                                                "customer")

    kubernetes_replicas: int = field(default=1)
    kubernetes_namespace: str = field(default="default")
    kubernetes_ingress_annotations: dict[str,
                                         str] = field(default_factory=dict)
    kubernetes_ingress_secret_name: str = field(default="authentik-outpost")
Example #13
0
 def get_container_image(self) -> str:
     """Get container image to use for this outpost"""
     image_name_template: str = CONFIG.y("outposts.docker_image_base")
     return image_name_template % {
         "type": self.outpost.type,
         "version": __version__
     }
Example #14
0
 def run(self):
     self.cur.execute(SQL_STATEMENT)
     self.con.commit()
     # We also need to clean the cache to make sure no pickeled objects still exist
     for db in [
             CONFIG.y("redis.message_queue_db"),
             CONFIG.y("redis.cache_db"),
             CONFIG.y("redis.ws_db"),
     ]:
         redis = Redis(
             host=CONFIG.y("redis.host"),
             port=6379,
             db=db,
             password=CONFIG.y("redis.password"),
         )
         redis.flushall()
Example #15
0
 def get_container_image(self) -> str:
     """Get container image to use for this outpost"""
     image_name_template: str = CONFIG.y("outposts.docker_image_base")
     return image_name_template % {
         "type": self.outpost.type,
         "version": __version__,
         "build_hash": environ.get(ENV_GIT_HASH_KEY, ""),
     }
Example #16
0
 def get_reference_object(self) -> V1Deployment:
     """Get deployment object for outpost"""
     # Generate V1ContainerPort objects
     container_ports = []
     for port in self.controller.deployment_ports:
         container_ports.append(
             V1ContainerPort(
                 container_port=port.port,
                 name=port.name,
                 protocol=port.protocol.upper(),
             ))
     meta = self.get_object_meta(name=self.name)
     secret_name = f"authentik-outpost-{self.controller.outpost.uuid.hex}-api"
     image_prefix = CONFIG.y("outposts.docker_image_base")
     return V1Deployment(
         metadata=meta,
         spec=V1DeploymentSpec(
             replicas=self.outpost.config.kubernetes_replicas,
             selector=V1LabelSelector(match_labels=self.get_pod_meta()),
             template=V1PodTemplateSpec(
                 metadata=V1ObjectMeta(labels=self.get_pod_meta()),
                 spec=V1PodSpec(containers=[
                     V1Container(
                         name=str(self.outpost.type),
                         image=
                         f"{image_prefix}-{self.outpost.type}:{__version__}",
                         ports=container_ports,
                         env=[
                             V1EnvVar(
                                 name="AUTHENTIK_HOST",
                                 value_from=V1EnvVarSource(
                                     secret_key_ref=V1SecretKeySelector(
                                         name=secret_name,
                                         key="authentik_host",
                                     )),
                             ),
                             V1EnvVar(
                                 name="AUTHENTIK_TOKEN",
                                 value_from=V1EnvVarSource(
                                     secret_key_ref=V1SecretKeySelector(
                                         name=secret_name,
                                         key="token",
                                     )),
                             ),
                             V1EnvVar(
                                 name="AUTHENTIK_INSECURE",
                                 value_from=V1EnvVarSource(
                                     secret_key_ref=V1SecretKeySelector(
                                         name=secret_name,
                                         key="authentik_host_insecure",
                                     )),
                             ),
                         ],
                     )
                 ]),
             ),
         ),
     )
Example #17
0
def certificate_discovery(self: MonitoredTask):
    """Discover, import and update certificates from the filesystem"""
    certs = {}
    private_keys = {}
    discovered = 0
    for file in glob(CONFIG.y("cert_discovery_dir") + "/**", recursive=True):
        path = Path(file)
        if not path.exists():
            continue
        if path.is_dir():
            continue
        # For certbot setups, we want to ignore archive.
        if "archive" in file:
            continue
        # Support certbot's directory structure
        if path.name in ["fullchain.pem", "privkey.pem"]:
            cert_name = path.parent.name
        else:
            cert_name = path.name.replace(path.suffix, "")
        try:
            with open(path, "r+", encoding="utf-8") as _file:
                body = _file.read()
                if "PRIVATE KEY" in body:
                    private_keys[cert_name] = ensure_private_key_valid(body)
                else:
                    certs[cert_name] = ensure_certificate_valid(body)
        except (OSError, ValueError) as exc:
            LOGGER.warning("Failed to open file or invalid format",
                           exc=exc,
                           file=path)
        discovered += 1
    for name, cert_data in certs.items():
        cert = CertificateKeyPair.objects.filter(managed=MANAGED_DISCOVERED %
                                                 name).first()
        if not cert:
            cert = CertificateKeyPair(
                name=name,
                managed=MANAGED_DISCOVERED % name,
            )
        dirty = False
        if cert.certificate_data != cert_data:
            cert.certificate_data = cert_data
            dirty = True
        if name in private_keys:
            if cert.key_data != private_keys[name]:
                cert.key_data = private_keys[name]
                dirty = True
        if dirty:
            cert.save()
    self.set_status(
        TaskResult(
            TaskResultStatus.SUCCESSFUL,
            messages=[
                _("Successfully imported %(count)d files." %
                  {"count": discovered})
            ],
        ))
Example #18
0
 def validate_email(self, email: str):
     """Check if the user is allowed to change their email"""
     if self.instance.group_attributes().get(
             USER_ATTRIBUTE_CHANGE_EMAIL,
             CONFIG.y_bool("default_user_change_email", True)):
         return email
     if email != self.instance.email:
         raise ValidationError("Not allowed to change email.")
     return email
Example #19
0
 def validate_username(self, username: str):
     """Check if the user is allowed to change their username"""
     if self.instance.group_attributes().get(
             USER_ATTRIBUTE_CHANGE_USERNAME,
             CONFIG.y_bool("default_user_change_username", True)):
         return username
     if username != self.instance.username:
         raise ValidationError("Not allowed to change username.")
     return username
Example #20
0
    def test_invalid_non_applicable_flow(self):
        """Tests that a non-applicable flow returns the correct error message"""
        flow = Flow.objects.create(
            name="test-non-applicable",
            slug="test-non-applicable",
            designation=FlowDesignation.AUTHENTICATION,
        )

        CONFIG.update_from_dict({"domain": "testserver"})
        response = self.client.get(
            reverse("authentik_api:flow-executor",
                    kwargs={"flow_slug": flow.slug}), )
        self.assertEqual(response.status_code, 200)
        self.assertStageResponse(
            response,
            flow=flow,
            error_message=FlowNonApplicableException.__doc__,
            component="ak-stage-access-denied",
        )
Example #21
0
    def get_container_image(self) -> str:
        """Get container image to use for this outpost"""
        if self.outpost.config.container_image is not None:
            return self.outpost.config.container_image

        image_name_template: str = CONFIG.y("outposts.container_image_base")
        return image_name_template % {
            "type": self.outpost.type,
            "version": __version__,
            "build_hash": get_build_hash(),
        }
Example #22
0
class OutpostConfig:
    """Configuration an outpost uses to configure it self"""

    authentik_host: str
    authentik_host_insecure: bool = False

    log_level: str = CONFIG.y("log_level")
    error_reporting_enabled: bool = CONFIG.y_bool("error_reporting.enabled")
    error_reporting_environment: str = CONFIG.y("error_reporting.environment",
                                                "customer")

    object_naming_template: str = field(default="ak-outpost-%(name)s")
    kubernetes_replicas: int = field(default=1)
    kubernetes_namespace: str = field(default_factory=get_namespace)
    kubernetes_ingress_annotations: dict[str,
                                         str] = field(default_factory=dict)
    kubernetes_ingress_secret_name: str = field(
        default="authentik-outpost-tls")
    kubernetes_service_type: str = field(default="ClusterIP")
    kubernetes_disabled_components: list[str] = field(default_factory=list)
Example #23
0
def get_geoip_reader() -> Optional[Reader]:
    """Get GeoIP Reader, if configured, otherwise none"""
    path = CONFIG.y("authentik.geoip")
    if path == "" or not path:
        return None
    try:
        reader = Reader(path)
        LOGGER.info("Enabled GeoIP support")
        return reader
    except OSError:
        return None
Example #24
0
 def __open(self):
     """Get GeoIP Reader, if configured, otherwise none"""
     path = CONFIG.y("geoip")
     if path == "" or not path:
         return
     try:
         self.__reader = Reader(path)
         self.__last_mtime = stat(path).st_mtime
         LOGGER.info("Loaded GeoIP database", last_write=self.__last_mtime)
     except OSError as exc:
         LOGGER.warning("Failed to load GeoIP database", exc=exc)
Example #25
0
def context_processor(request: HttpRequest) -> dict[str, Any]:
    """Context Processor that injects tenant object into every template"""
    tenant = getattr(request, "tenant", DEFAULT_TENANT)
    trace = ""
    span = Hub.current.scope.span
    if span:
        trace = span.to_traceparent()
    return {
        "tenant": tenant,
        "footer_links": CONFIG.y("footer_links"),
        "sentry_trace": trace,
    }
Example #26
0
    def test_invalid_non_applicable_flow(self):
        """Tests that a non-applicable flow returns the correct error message"""
        flow = Flow.objects.create(
            name="test-non-applicable",
            slug="test-non-applicable",
            designation=FlowDesignation.AUTHENTICATION,
        )

        CONFIG.update_from_dict({"domain": "testserver"})
        response = self.client.get(
            reverse("authentik_api:flow-executor",
                    kwargs={"flow_slug": flow.slug}), )
        self.assertEqual(response.status_code, 200)
        self.assertJSONEqual(
            force_str(response.content),
            {
                "component": "ak-stage-access-denied",
                "error_message": FlowNonApplicableException.__doc__,
                "title": "",
                "type": ChallengeTypes.NATIVE.value,
            },
        )
Example #27
0
 def test_fallback(self):
     """Test fallback tenant"""
     Tenant.objects.all().delete()
     self.assertJSONEqual(
         self.client.get(reverse("authentik_api:tenant-current")).content.decode(),
         {
             "branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
             "branding_favicon": "/static/dist/assets/icons/icon.png",
             "branding_title": "authentik",
             "matched_domain": "fallback",
             "ui_footer_links": CONFIG.y("footer_links"),
         },
     )
Example #28
0
 def test_current_tenant(self):
     """Test Current tenant API"""
     tenant = create_test_tenant()
     self.assertJSONEqual(
         self.client.get(reverse("authentik_api:tenant-current")).content.decode(),
         {
             "branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
             "branding_favicon": "/static/dist/assets/icons/icon.png",
             "branding_title": "authentik",
             "matched_domain": tenant.domain,
             "ui_footer_links": CONFIG.y("footer_links"),
         },
     )
Example #29
0
def get_env() -> str:
    """Get environment in which authentik is currently running"""
    if SERVICE_HOST_ENV_NAME in os.environ:
        return "kubernetes"
    if "CI" in os.environ:
        return "ci"
    if Path("/tmp/authentik-mode").exists():  # nosec
        return "compose"
    if CONFIG.y_bool("debug"):
        return "dev"
    if "AK_APPLIANCE" in os.environ:
        return os.environ["AK_APPLIANCE"]
    return "custom"
Example #30
0
 def test_current_tenant(self):
     """Test Current tenant API"""
     self.assertJSONEqual(
         force_str(
             self.client.get(
                 reverse("authentik_api:tenant-current")).content),
         {
             "branding_logo":
             "/static/dist/assets/icons/icon_left_brand.svg",
             "branding_title": "authentik",
             "matched_domain": "authentik-default",
             "ui_footer_links": CONFIG.y("authentik.footer_links"),
         },
     )