Compare commits
17 Commits
enterprise
...
root/more-
Author | SHA1 | Date | |
---|---|---|---|
06337283e8 | |||
5d28114a4b | |||
b7c154ccd2 | |||
d1fbf2ed65 | |||
f35457492b | |||
af9ba83529 | |||
3c6cb9dbad | |||
1d63359077 | |||
33121d86f2 | |||
0c235909a2 | |||
91ef8c2c8d | |||
4ee45bb5cc | |||
b4ae3ba390 | |||
f3834016dc | |||
661a966e23 | |||
813273338e | |||
99639a9ed0 |
@ -209,7 +209,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="superuser_full_list",
|
||||
name="list_rbac",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.BOOL,
|
||||
),
|
||||
@ -229,10 +229,8 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
|
||||
"""Custom list method that checks Policy based access instead of guardian"""
|
||||
should_cache = request.query_params.get("search", "") == ""
|
||||
|
||||
superuser_full_list = (
|
||||
str(request.query_params.get("superuser_full_list", "false")).lower() == "true"
|
||||
)
|
||||
if superuser_full_list and request.user.is_superuser:
|
||||
list_rbac = str(request.query_params.get("list_rbac", "false")).lower() == "true"
|
||||
if list_rbac:
|
||||
return super().list(request)
|
||||
|
||||
only_with_launch_url = str(
|
||||
|
@ -4,7 +4,7 @@ from typing import Any
|
||||
|
||||
from django.utils.timezone import now
|
||||
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
|
||||
from guardian.shortcuts import assign_perm, get_anonymous_user
|
||||
from guardian.shortcuts import assign_perm
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import CharField
|
||||
@ -138,13 +138,8 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
|
||||
owner_field = "user"
|
||||
rbac_allow_create_without_perm = True
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
if user.is_superuser:
|
||||
return super().get_queryset()
|
||||
return super().get_queryset().filter(user=user.pk)
|
||||
|
||||
def perform_create(self, serializer: TokenSerializer):
|
||||
# TODO: better permission check
|
||||
if not self.request.user.is_superuser:
|
||||
instance = serializer.save(
|
||||
user=self.request.user,
|
||||
|
57
authentik/core/migrations/0043_alter_user_options.py
Normal file
57
authentik/core/migrations/0043_alter_user_options.py
Normal file
@ -0,0 +1,57 @@
|
||||
# Generated by Django 5.0.10 on 2025-01-08 17:39
|
||||
|
||||
from django.db import migrations
|
||||
from django.apps.registry import Apps
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def migrate_user_debug_attribute(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
from django.apps import apps as real_apps
|
||||
from django.contrib.auth.management import create_permissions
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
User = apps.get_model("authentik_core", "User")
|
||||
USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug"
|
||||
|
||||
# Permissions are only created _after_ migrations are run
|
||||
# - https://github.com/django/django/blob/43cdfa8b20e567a801b7d0a09ec67ddd062d5ea4/django/contrib/auth/apps.py#L19
|
||||
# - https://stackoverflow.com/a/72029063/1870445
|
||||
create_permissions(real_apps.get_app_config("authentik_core"), using=db_alias)
|
||||
|
||||
Permission = apps.get_model("auth", "Permission")
|
||||
|
||||
new_prem = Permission.objects.using(db_alias).get(codename="user_view_debug")
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
for user in User.objects.using(db_alias).filter(
|
||||
**{f"attributes__{USER_ATTRIBUTE_DEBUG}": True}
|
||||
):
|
||||
user.permissions.add(new_prem)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0042_authenticatedsession_authentik_c_expires_08251d_idx_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="user",
|
||||
options={
|
||||
"permissions": [
|
||||
("reset_user_password", "Reset Password"),
|
||||
("impersonate", "Can impersonate other users"),
|
||||
("assign_user_permissions", "Can assign permissions to users"),
|
||||
("unassign_user_permissions", "Can unassign permissions from users"),
|
||||
("preview_user", "Can preview user data sent to providers"),
|
||||
("view_user_applications", "View applications the user has access to"),
|
||||
("user_view_debug", "User receives additional details for error messages"),
|
||||
],
|
||||
"verbose_name": "User",
|
||||
"verbose_name_plural": "Users",
|
||||
},
|
||||
),
|
||||
migrations.RunPython(migrate_user_debug_attribute),
|
||||
]
|
@ -41,7 +41,6 @@ from authentik.tenants.models import DEFAULT_TOKEN_DURATION, DEFAULT_TOKEN_LENGT
|
||||
from authentik.tenants.utils import get_current_tenant, get_unique_identifier
|
||||
|
||||
LOGGER = get_logger()
|
||||
USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug"
|
||||
USER_ATTRIBUTE_GENERATED = "goauthentik.io/user/generated"
|
||||
USER_ATTRIBUTE_EXPIRES = "goauthentik.io/user/expires"
|
||||
USER_ATTRIBUTE_DELETE_ON_LOGOUT = "goauthentik.io/user/delete-on-logout"
|
||||
@ -282,6 +281,7 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
|
||||
("unassign_user_permissions", _("Can unassign permissions from users")),
|
||||
("preview_user", _("Can preview user data sent to providers")),
|
||||
("view_user_applications", _("View applications the user has access to")),
|
||||
("user_view_debug", _("User receives additional details for error messages")),
|
||||
]
|
||||
indexes = [
|
||||
models.Index(fields=["last_login"]),
|
||||
|
@ -96,7 +96,7 @@ class EndpointViewSet(UsedByMixin, ModelViewSet):
|
||||
OpenApiTypes.STR,
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="superuser_full_list",
|
||||
name="list_rbac",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.BOOL,
|
||||
),
|
||||
@ -110,8 +110,8 @@ class EndpointViewSet(UsedByMixin, ModelViewSet):
|
||||
"""List accessible endpoints"""
|
||||
should_cache = request.GET.get("search", "") == ""
|
||||
|
||||
superuser_full_list = str(request.GET.get("superuser_full_list", "false")).lower() == "true"
|
||||
if superuser_full_list and request.user.is_superuser:
|
||||
list_rbac = str(request.GET.get("list_rbac", "false")).lower() == "true"
|
||||
if list_rbac:
|
||||
return super().list(request)
|
||||
|
||||
queryset = self._filter_queryset_for_list(self.get_queryset())
|
||||
|
@ -97,12 +97,9 @@ class FlowErrorChallenge(Challenge):
|
||||
if not request or not error:
|
||||
return
|
||||
self.initial_data["request_id"] = request.request_id
|
||||
from authentik.core.models import USER_ATTRIBUTE_DEBUG
|
||||
|
||||
if request.user and request.user.is_authenticated:
|
||||
if request.user.is_superuser or request.user.group_attributes(request).get(
|
||||
USER_ATTRIBUTE_DEBUG, False
|
||||
):
|
||||
if request.user.has_perm("authentik_core.user_view_debug"):
|
||||
self.initial_data["error"] = str(error)
|
||||
self.initial_data["traceback"] = exception_to_string(error)
|
||||
|
||||
|
@ -13,6 +13,7 @@ from paramiko.ssh_exception import SSHException
|
||||
from structlog.stdlib import get_logger
|
||||
from yaml import safe_dump
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||
from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException
|
||||
from authentik.outposts.docker_ssh import DockerInlineSSH, SSHManagedExternallyException
|
||||
@ -182,10 +183,16 @@ class DockerController(BaseController):
|
||||
`outposts.container_image_base`, but fall back to known-good images"""
|
||||
image = self.get_container_image()
|
||||
try:
|
||||
self.client.images.pull(image)
|
||||
except DockerException: # pragma: no cover
|
||||
image = f"ghcr.io/goauthentik/{self.outpost.type}:latest"
|
||||
self.client.images.pull(image)
|
||||
# See if the image exists...
|
||||
self.client.images.get(image)
|
||||
except DockerException:
|
||||
try:
|
||||
# ...otherwise try to pull it...
|
||||
self.client.images.pull(image)
|
||||
except DockerException:
|
||||
# ...and as a fallback to that default to a sane standard
|
||||
image = f"ghcr.io/goauthentik/{self.outpost.type}:{__version__}"
|
||||
self.client.images.pull(image)
|
||||
return image
|
||||
|
||||
def _get_container(self) -> tuple[Container, bool]:
|
||||
|
@ -7,7 +7,7 @@ from django.template.response import TemplateResponse
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from authentik.core.models import USER_ATTRIBUTE_DEBUG
|
||||
from authentik.core.models import User
|
||||
from authentik.policies.types import PolicyResult
|
||||
|
||||
|
||||
@ -31,12 +31,11 @@ class AccessDeniedResponse(TemplateResponse):
|
||||
if self.error_message:
|
||||
context["error"] = self.error_message
|
||||
# Only show policy result if user is authenticated and
|
||||
# either superuser or has USER_ATTRIBUTE_DEBUG set
|
||||
# has permissions to see them
|
||||
if self.policy_result:
|
||||
if self._request.user and self._request.user.is_authenticated:
|
||||
if self._request.user.is_superuser or self._request.user.group_attributes(
|
||||
self._request
|
||||
).get(USER_ATTRIBUTE_DEBUG, False):
|
||||
user: User = self._request.user
|
||||
if user.has_perm("authentik_core.user_view_debug"):
|
||||
context["policy_result"] = self.policy_result
|
||||
context["cancel"] = reverse("authentik_flows:cancel")
|
||||
return context
|
||||
|
@ -2,11 +2,8 @@
|
||||
|
||||
from json import dumps
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from guardian.utils import get_anonymous_user
|
||||
from rest_framework import mixins
|
||||
from rest_framework.fields import CharField, ListField, SerializerMethodField
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
@ -66,17 +63,7 @@ class AuthorizationCodeViewSet(
|
||||
serializer_class = ExpiringBaseGrantModelSerializer
|
||||
filterset_fields = ["user", "provider"]
|
||||
ordering = ["provider", "expires"]
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
OrderingFilter,
|
||||
SearchFilter,
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
if user.is_superuser:
|
||||
return super().get_queryset()
|
||||
return super().get_queryset().filter(user=user.pk)
|
||||
owner_field = "user"
|
||||
|
||||
|
||||
class RefreshTokenViewSet(
|
||||
@ -92,17 +79,7 @@ class RefreshTokenViewSet(
|
||||
serializer_class = TokenModelSerializer
|
||||
filterset_fields = ["user", "provider"]
|
||||
ordering = ["provider", "expires"]
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
OrderingFilter,
|
||||
SearchFilter,
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
if user.is_superuser:
|
||||
return super().get_queryset()
|
||||
return super().get_queryset().filter(user=user.pk)
|
||||
owner_field = "user"
|
||||
|
||||
|
||||
class AccessTokenViewSet(
|
||||
@ -118,14 +95,4 @@ class AccessTokenViewSet(
|
||||
serializer_class = TokenModelSerializer
|
||||
filterset_fields = ["user", "provider"]
|
||||
ordering = ["provider", "expires"]
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
OrderingFilter,
|
||||
SearchFilter,
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
if user.is_superuser:
|
||||
return super().get_queryset()
|
||||
return super().get_queryset().filter(user=user.pk)
|
||||
owner_field = "user"
|
||||
|
@ -12,6 +12,7 @@ from authentik.core.tests.utils import create_test_admin_user, create_test_cert,
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.oauth2.models import (
|
||||
AccessToken,
|
||||
ClientTypes,
|
||||
IDToken,
|
||||
OAuth2Provider,
|
||||
RedirectURI,
|
||||
@ -108,3 +109,29 @@ class TesOAuth2Revoke(OAuthTestCase):
|
||||
},
|
||||
)
|
||||
self.assertEqual(res.status_code, 401)
|
||||
|
||||
def test_revoke_public(self):
|
||||
"""Test revoke public client"""
|
||||
self.provider.client_type = ClientTypes.PUBLIC
|
||||
self.provider.save()
|
||||
token: AccessToken = AccessToken.objects.create(
|
||||
provider=self.provider,
|
||||
user=self.user,
|
||||
token=generate_id(),
|
||||
auth_time=timezone.now(),
|
||||
_scope="openid user profile",
|
||||
_id_token=json.dumps(
|
||||
asdict(
|
||||
IDToken("foo", "bar"),
|
||||
)
|
||||
),
|
||||
)
|
||||
auth_public = b64encode(f"{self.provider.client_id}:{generate_id()}".encode()).decode()
|
||||
res = self.client.post(
|
||||
reverse("authentik_providers_oauth2:token-revoke"),
|
||||
HTTP_AUTHORIZATION=f"Basic {auth_public}",
|
||||
data={
|
||||
"token": token.token,
|
||||
},
|
||||
)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
@ -178,12 +178,18 @@ def protected_resource_view(scopes: list[str]):
|
||||
return wrapper
|
||||
|
||||
|
||||
def authenticate_provider(request: HttpRequest) -> OAuth2Provider | None:
|
||||
"""Attempt to authenticate via Basic auth of client_id:client_secret"""
|
||||
def provider_from_request(request: HttpRequest) -> tuple[OAuth2Provider | None, str, str]:
|
||||
"""Get provider from Basic auth of client_id:client_secret. Does not perform authentication"""
|
||||
client_id, client_secret = extract_client_auth(request)
|
||||
if client_id == client_secret == "":
|
||||
return None
|
||||
return None, "", ""
|
||||
provider: OAuth2Provider | None = OAuth2Provider.objects.filter(client_id=client_id).first()
|
||||
return provider, client_id, client_secret
|
||||
|
||||
|
||||
def authenticate_provider(request: HttpRequest) -> OAuth2Provider | None:
|
||||
"""Attempt to authenticate via Basic auth of client_id:client_secret"""
|
||||
provider, client_id, client_secret = provider_from_request(request)
|
||||
if not provider:
|
||||
return None
|
||||
if client_id != provider.client_id or client_secret != provider.client_secret:
|
||||
|
@ -9,8 +9,12 @@ from django.views.decorators.csrf import csrf_exempt
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.providers.oauth2.errors import TokenRevocationError
|
||||
from authentik.providers.oauth2.models import AccessToken, OAuth2Provider, RefreshToken
|
||||
from authentik.providers.oauth2.utils import TokenResponse, authenticate_provider
|
||||
from authentik.providers.oauth2.models import AccessToken, ClientTypes, OAuth2Provider, RefreshToken
|
||||
from authentik.providers.oauth2.utils import (
|
||||
TokenResponse,
|
||||
authenticate_provider,
|
||||
provider_from_request,
|
||||
)
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@ -27,7 +31,9 @@ class TokenRevocationParams:
|
||||
"""Extract required Parameters from HTTP Request"""
|
||||
raw_token = request.POST.get("token")
|
||||
|
||||
provider = authenticate_provider(request)
|
||||
provider, _, _ = provider_from_request(request)
|
||||
if provider and provider.client_type == ClientTypes.CONFIDENTIAL:
|
||||
provider = authenticate_provider(request)
|
||||
if not provider:
|
||||
raise TokenRevocationError("invalid_client")
|
||||
|
||||
|
@ -83,7 +83,7 @@ class RedirectStageView(ChallengeStageView):
|
||||
target_url_override = self.executor.plan.context.get(PLAN_CONTEXT_REDIRECT_STAGE_TARGET, "")
|
||||
if target_url_override:
|
||||
target = self.parse_target(target_url_override)
|
||||
# `target` is falsy if the override was to a Flow but that Flow doesn't exist.
|
||||
# `target` is false if the override was to a Flow but that Flow doesn't exist.
|
||||
if not target:
|
||||
if current_stage.mode == RedirectMode.STATIC:
|
||||
target = current_stage.target_static
|
||||
|
@ -6445,6 +6445,7 @@
|
||||
"authentik_core.remove_user_from_group",
|
||||
"authentik_core.reset_user_password",
|
||||
"authentik_core.unassign_user_permissions",
|
||||
"authentik_core.user_view_debug",
|
||||
"authentik_core.view_application",
|
||||
"authentik_core.view_applicationentitlement",
|
||||
"authentik_core.view_authenticatedsession",
|
||||
@ -12694,6 +12695,7 @@
|
||||
"authentik_core.remove_user_from_group",
|
||||
"authentik_core.reset_user_password",
|
||||
"authentik_core.unassign_user_permissions",
|
||||
"authentik_core.user_view_debug",
|
||||
"authentik_core.view_application",
|
||||
"authentik_core.view_applicationentitlement",
|
||||
"authentik_core.view_authenticatedsession",
|
||||
@ -13202,6 +13204,7 @@
|
||||
"unassign_user_permissions",
|
||||
"preview_user",
|
||||
"view_user_applications",
|
||||
"user_view_debug",
|
||||
"add_user",
|
||||
"change_user",
|
||||
"delete_user",
|
||||
|
47
poetry.lock
generated
47
poetry.lock
generated
@ -408,13 +408,13 @@ typeguard = ">=2.13.3,<4.3.0"
|
||||
|
||||
[[package]]
|
||||
name = "aws-cdk-lib"
|
||||
version = "2.175.1"
|
||||
version = "2.176.0"
|
||||
description = "Version 2 of the AWS Cloud Development Kit library"
|
||||
optional = false
|
||||
python-versions = "~=3.8"
|
||||
files = [
|
||||
{file = "aws_cdk_lib-2.175.1-py3-none-any.whl", hash = "sha256:d66ac587a3571b6bfcf11b07f04f02ff3f12e42e87c8783aadb6043df7f638f6"},
|
||||
{file = "aws_cdk_lib-2.175.1.tar.gz", hash = "sha256:e7bafecb2b9de7e315f0c615a88bc91d226e1ddea3cdfaf4c72c6b6f48a78c74"},
|
||||
{file = "aws_cdk_lib-2.176.0-py3-none-any.whl", hash = "sha256:c362a92f06b6ea60a7eff7994d3994c462358e7a95ce3de01a28efab4f6d56b6"},
|
||||
{file = "aws_cdk_lib-2.176.0.tar.gz", hash = "sha256:87a39d2f42fd2ea8ba2bfa364355303953fb5cc2886479ca5acf09a14a9fd679"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -1922,13 +1922,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
|
||||
|
||||
[[package]]
|
||||
name = "google-api-python-client"
|
||||
version = "2.158.0"
|
||||
version = "2.159.0"
|
||||
description = "Google API Client Library for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "google_api_python_client-2.158.0-py2.py3-none-any.whl", hash = "sha256:36f8c8d2e79e50f76790ca5946d2f3f8333e210dc8539a6c88e0742416474ad2"},
|
||||
{file = "google_api_python_client-2.158.0.tar.gz", hash = "sha256:b6664597a9955e04977a62752e33fe44cb35c580e190c1cb08a041893172bd67"},
|
||||
{file = "google_api_python_client-2.159.0-py2.py3-none-any.whl", hash = "sha256:baef0bb631a60a0bd7c0bf12a5499e3a40cd4388484de7ee55c1950bf820a0cf"},
|
||||
{file = "google_api_python_client-2.159.0.tar.gz", hash = "sha256:55197f430f25c907394b44fa078545ffef89d33fd4dca501b7db9f0d8e224bd6"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -3107,13 +3107,13 @@ dev = ["bumpver", "isort", "mypy", "pylint", "pytest", "yapf"]
|
||||
|
||||
[[package]]
|
||||
name = "msgraph-sdk"
|
||||
version = "1.16.0"
|
||||
version = "1.17.0"
|
||||
description = "The Microsoft Graph Python SDK"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "msgraph_sdk-1.16.0-py3-none-any.whl", hash = "sha256:1dd26ece74c43167818e2ff58b062180233ce7187ad2a061057af1195395c56c"},
|
||||
{file = "msgraph_sdk-1.16.0.tar.gz", hash = "sha256:980d19617d8d8b20545ef77fa5629fef768ce4ea1f2d1a124c5a9dd88d77940c"},
|
||||
{file = "msgraph_sdk-1.17.0-py3-none-any.whl", hash = "sha256:5582a258ded19a486ab407a67b5f65d666758a63864da77bd20c2581d1c00fba"},
|
||||
{file = "msgraph_sdk-1.17.0.tar.gz", hash = "sha256:577e41942b0f794b8cf2f54db030bc039a750a81b515dcd0ba1d66fd961fa7bf"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -3800,36 +3800,36 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "psycopg"
|
||||
version = "3.2.3"
|
||||
version = "3.2.4"
|
||||
description = "PostgreSQL database adapter for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "psycopg-3.2.3-py3-none-any.whl", hash = "sha256:644d3973fe26908c73d4be746074f6e5224b03c1101d302d9a53bf565ad64907"},
|
||||
{file = "psycopg-3.2.3.tar.gz", hash = "sha256:a5764f67c27bec8bfac85764d23c534af2c27b893550377e37ce59c12aac47a2"},
|
||||
{file = "psycopg-3.2.4-py3-none-any.whl", hash = "sha256:43665368ccd48180744cab26b74332f46b63b7e06e8ce0775547a3533883d381"},
|
||||
{file = "psycopg-3.2.4.tar.gz", hash = "sha256:f26f1346d6bf1ef5f5ef1714dd405c67fb365cfd1c6cea07de1792747b167b92"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
psycopg-c = {version = "3.2.3", optional = true, markers = "implementation_name != \"pypy\" and extra == \"c\""}
|
||||
psycopg-c = {version = "3.2.4", optional = true, markers = "implementation_name != \"pypy\" and extra == \"c\""}
|
||||
typing-extensions = {version = ">=4.6", markers = "python_version < \"3.13\""}
|
||||
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
|
||||
[package.extras]
|
||||
binary = ["psycopg-binary (==3.2.3)"]
|
||||
c = ["psycopg-c (==3.2.3)"]
|
||||
dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.11)", "types-setuptools (>=57.4)", "wheel (>=0.37)"]
|
||||
binary = ["psycopg-binary (==3.2.4)"]
|
||||
c = ["psycopg-c (==3.2.4)"]
|
||||
dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.14)", "pre-commit (>=4.0.1)", "types-setuptools (>=57.4)", "wheel (>=0.37)"]
|
||||
docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
pool = ["psycopg-pool"]
|
||||
test = ["anyio (>=4.0)", "mypy (>=1.11)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"]
|
||||
test = ["anyio (>=4.0)", "mypy (>=1.14)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-c"
|
||||
version = "3.2.3"
|
||||
version = "3.2.4"
|
||||
description = "PostgreSQL database adapter for Python -- C optimisation distribution"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "psycopg_c-3.2.3.tar.gz", hash = "sha256:06ae7db8eaec1a3845960fa7f997f4ccdb1a7a7ab8dc593a680bcc74e1359671"},
|
||||
{file = "psycopg_c-3.2.4.tar.gz", hash = "sha256:22097a04263efb2efd2cc8b00a51fa90e23f9cd4a2e09903fe4d9c6923dac17a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4710,13 +4710,13 @@ websocket-client = ">=1.8,<2.0"
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.19.2"
|
||||
version = "2.20.0"
|
||||
description = "Python client for Sentry (https://sentry.io)"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "sentry_sdk-2.19.2-py2.py3-none-any.whl", hash = "sha256:ebdc08228b4d131128e568d696c210d846e5b9d70aa0327dec6b1272d9d40b84"},
|
||||
{file = "sentry_sdk-2.19.2.tar.gz", hash = "sha256:467df6e126ba242d39952375dd816fbee0f217d119bf454a8ce74cf1e7909e8d"},
|
||||
{file = "sentry_sdk-2.20.0-py2.py3-none-any.whl", hash = "sha256:c359a1edf950eb5e80cffd7d9111f3dbeef57994cb4415df37d39fda2cf22364"},
|
||||
{file = "sentry_sdk-2.20.0.tar.gz", hash = "sha256:afa82713a92facf847df3c6f63cec71eb488d826a50965def3d7722aa6f0fdab"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -4761,6 +4761,7 @@ sqlalchemy = ["sqlalchemy (>=1.2)"]
|
||||
starlette = ["starlette (>=0.19.1)"]
|
||||
starlite = ["starlite (>=1.48)"]
|
||||
tornado = ["tornado (>=6)"]
|
||||
unleash = ["UnleashClient (>=6.0.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "service-identity"
|
||||
|
@ -4,6 +4,9 @@ version = "2024.12.2"
|
||||
description = ""
|
||||
authors = ["authentik Team <hello@goauthentik.io>"]
|
||||
|
||||
[tool.poetry.requires-plugins]
|
||||
poetry-plugin-export = ">1.8"
|
||||
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
target-version = ['py312']
|
||||
|
16
schema.yml
16
schema.yml
@ -3391,6 +3391,10 @@ paths:
|
||||
name: group
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: list_rbac
|
||||
schema:
|
||||
type: boolean
|
||||
- in: query
|
||||
name: meta_description
|
||||
schema:
|
||||
@ -3439,10 +3443,6 @@ paths:
|
||||
name: slug
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: superuser_full_list
|
||||
schema:
|
||||
type: boolean
|
||||
tags:
|
||||
- core
|
||||
security:
|
||||
@ -23204,6 +23204,10 @@ paths:
|
||||
operationId: rac_endpoints_list
|
||||
description: List accessible endpoints
|
||||
parameters:
|
||||
- in: query
|
||||
name: list_rbac
|
||||
schema:
|
||||
type: boolean
|
||||
- in: query
|
||||
name: name
|
||||
schema:
|
||||
@ -23234,10 +23238,6 @@ paths:
|
||||
name: search
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: superuser_full_list
|
||||
schema:
|
||||
type: boolean
|
||||
tags:
|
||||
- rac
|
||||
security:
|
||||
|
@ -66,7 +66,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
|
||||
async apiEndpoint(): Promise<PaginatedResponse<Application>> {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreApplicationsList({
|
||||
...(await this.defaultEndpointConfig()),
|
||||
superuserFullList: true,
|
||||
listRbac: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,7 @@ export class BrandForm extends ModelForm<Brand, string> {
|
||||
.fetchObjects=${async (query?: string): Promise<Application[]> => {
|
||||
const args: CoreApplicationsListRequest = {
|
||||
ordering: "name",
|
||||
superuserFullList: true,
|
||||
listRbac: true,
|
||||
};
|
||||
if (query !== undefined) {
|
||||
args.search = query;
|
||||
|
@ -46,7 +46,7 @@ export class EndpointListPage extends Table<Endpoint> {
|
||||
return new RacApi(DEFAULT_CONFIG).racEndpointsList({
|
||||
...(await this.defaultEndpointConfig()),
|
||||
provider: this.provider?.pk,
|
||||
superuserFullList: true,
|
||||
listRbac: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -117,6 +117,10 @@ The `docker-compose.yml` file statically references the latest version available
|
||||
|
||||
To start the initial setup, navigate to `http://<your server's IP or hostname>:9000/if/flow/initial-setup/`.
|
||||
|
||||
:::info
|
||||
You will get `Not Found` error if initial setup URL doesn't include the trailing forward slash `/`. Make sure you use the complete url (`http://<your server's IP or hostname>:9000/if/flow/initial-setup/`) including the trailing forward slash.
|
||||
:::
|
||||
|
||||
There you are prompted to set a password for the `akadmin` user (the default user).
|
||||
|
||||
For an explanation about what each service in the docker compose file does, see [Architecture](../../core/architecture.md).
|
||||
|
@ -74,6 +74,10 @@ During the installation process, the database migrations will be applied automat
|
||||
|
||||
After the installation is complete, access authentik at `https://<ingress-host-name>/if/flow/initial-setup/`. Here, you can set a password for the default `akadmin` user.
|
||||
|
||||
:::info
|
||||
You will get `Not Found` error if initial setup URL doesn't include the trailing forward slash `/`. Make sure you use the complete url (`http://<ingress-host-name>/if/flow/initial-setup/`) including the trailing forward slash.
|
||||
:::
|
||||
|
||||
### Optional step: Configure global email credentials
|
||||
|
||||
It is recommended to configure global email credentials as well. These are used by authentik to notify you about alerts and configuration issues. Additionally, they can be utilized by [Email stages](../../add-secure-apps/flows-stages/stages/email/index.mdx) to send verification and recovery emails.
|
||||
|
82
website/integrations/services/actual-budget/index.mdx
Normal file
82
website/integrations/services/actual-budget/index.mdx
Normal file
@ -0,0 +1,82 @@
|
||||
---
|
||||
title: Integrate with Actual Budget
|
||||
sidebar_label: Actual Budget
|
||||
---
|
||||
|
||||
# Actual Budget
|
||||
|
||||
<span class="badge badge--secondary">Support level: Community</span>
|
||||
|
||||
## What is Actual Budget
|
||||
|
||||
> Actual Budget is a web-based financial management software. It helps users track and manage their income, expenses, and budgets in real time.
|
||||
> The software compares actual spending with planned budgets to improve financial decisions.
|
||||
>
|
||||
> -- https://actualbudget.org/
|
||||
>
|
||||
> This guide explains how to configure Actual Budget to use authentik as the OAuth provider for logging in to the Web GUI.
|
||||
|
||||
## Preparation
|
||||
|
||||
The following placeholders are used in this guide:
|
||||
|
||||
- _actual.company_ is the FQDN of the Actual Budget install.
|
||||
- _authentik.company_ is the FQDN of the authentik install.
|
||||
|
||||
## authentik configuration
|
||||
|
||||
[Create](https://docs.goauthentik.io/docs/add-secure-apps/applications/manage_apps#add-new-applications) an OAuth2/OpenID provider and an application in authentik. Use the following parameters for the OAuth2/OpenID provider:
|
||||
|
||||
**Provider:**
|
||||
|
||||
- Name: _SP-actual_
|
||||
- Client type: _Confidential_
|
||||
- Redirect URIs/Origins (RegEx): https://_actual.company_/openid/callback
|
||||
- Signing Key: Select any available signing keys.
|
||||
|
||||
:::info
|
||||
Actual Budget supports the RS256 algorithm. Be aware of this when choosing the appropriate signing key.
|
||||
:::
|
||||
|
||||
Take note of the Client ID and Client Secret; you will need to provide them to Actual Budget in the last step.
|
||||
|
||||
Leave the remaining values as default. Durations can be adjusted as needed.
|
||||
|
||||
**Application:**
|
||||
|
||||
- Name: _Actual Budget_
|
||||
- Slug: _actual_
|
||||
- Launch URL: https://_actual.company_/
|
||||
|
||||
## Actual Budget configuration
|
||||
|
||||
1. Sign in to Actual Budget with a browser of your choice and access your budget by clicking on its name.
|
||||
|
||||
2. Click your budget in the top-left corner to open the dropdown menu and select **Settings**.
|
||||
|
||||
3. Scroll to the bottom and select **Show advanced settings**. Scroll again and select **I understand the risks, show experimental features**.
|
||||
|
||||
4. To enable the option **OpenID authentication method** select the checkbox next to it.
|
||||
|
||||
5. Scroll up to the new option **Authentication method...** and click **Start using OpenID**.
|
||||
|
||||
6. Set the following values from the authentik provider:
|
||||
- Set **OpenID Provider** to **authentik**
|
||||
- Set **OpenID provider URL** to https://_authentik.company_/application/o/_actual_/
|
||||
- Set **Client ID** to _client-id_
|
||||
- Set **Client secret** to _client-secret_
|
||||
|
||||
:::warning
|
||||
The first user to log into Actual Budget via OpenID will become the owner and administrator with the highest privileges for the budget. For more information on how to create additional users, see the Note below.
|
||||
:::
|
||||
|
||||
## Test the login
|
||||
|
||||
- Open a browser of your choice and navigate to https://_actual.company_.
|
||||
- Select the OpenID login method in the dropdown menu and click **Sign in with OpenID**.
|
||||
- You should be redirected to authentik (with the login flows you created), and then authentik will redirect you back to the https://_actual.company_ URL.
|
||||
- If you are redirected back to the https://_actual.company_ URL and can see the budget file selection page, the setup was successful.
|
||||
|
||||
:::info
|
||||
Users are not automatically created when logging in with authentik. The owner must manually create each user in Actual Budget. To do so, click **Server online** at the top next to your name and select **User Directory**. Add a new user. The `Username` must match the one in authentik. You can now grant the new user access to your budget by clicking **Server online** next to your name at the top and selecting **User Access**.
|
||||
:::
|
@ -14,7 +14,7 @@ sidebar_label: pgAdmin
|
||||
> -- https://www.pgadmin.org/
|
||||
|
||||
:::note
|
||||
This is based on authentik 2022.3.3 and pgAdmin4 6.19
|
||||
This is based on authentik 2024.12.2 and pgAdmin4 8.14
|
||||
:::
|
||||
|
||||
## Preparation
|
||||
@ -24,78 +24,79 @@ The following placeholders are used in this guide:
|
||||
- `pgadmin.company` is the FQDN of pgAdmin.
|
||||
- `authentik.company` is the FQDN of authentik.
|
||||
|
||||
### Step 1: Create authentik Provider
|
||||
# authentik configuration
|
||||
|
||||
In authentik, under _Providers_, create an _OAuth2/OpenID Provider_ with these settings:
|
||||
1. From the Admin interface, navigate to **Applications** -> **Applications**.
|
||||
2. Use the wizard to create a new application and provider. During this process:
|
||||
- Note the **Client ID**, **Client Secret**, and **slug** values because they will be required later.
|
||||
- Set a `Strict` redirect URI to `https://pgadmin.company/oauth2/authorize`.
|
||||
- Select any available signing key.
|
||||
|
||||
**Provider Settings**
|
||||
## pgAdmin OAuth Configuration
|
||||
|
||||
- Name: pgAdmin
|
||||
- Client ID: Copy and Save this for Later
|
||||
- Client Secret: Copy and Save this for later
|
||||
- Redirect URIs/Origins: `http://pgadmin.company/oauth2/authorize`
|
||||
- Signing Key: Select any available key
|
||||
To configure OAuth in pgAdmin, you can either use the `config_local.py` file or set environment variables if you are deploying pgAdmin in a containerized setup.
|
||||
|
||||
### Step 2: Create authentik Application
|
||||
### Using `config_local.py`
|
||||
|
||||
In authentik, create an application which uses this provider. Optionally apply access restrictions to the application using policy bindings.
|
||||
1. Locate or create the `config_local.py` file in the `/pgadmin4/` directory.
|
||||
|
||||
- Name: pgAdmin
|
||||
- Slug: pgadmin
|
||||
- Provider: pgAdmin
|
||||
- Launch URL: https://pgadmin.company
|
||||
- If the file does not exist, create it manually.
|
||||
|
||||
### Step 3: Configure pgAdmin
|
||||
2. Add the following configuration settings to `config_local.py`:
|
||||
|
||||
All settings for OAuth in pgAdmin are configured in the `config_local.py` file. This file can usually be found in the path `/pgadmin4/config_local.py`
|
||||
```python
|
||||
AUTHENTICATION_SOURCES = ['oauth2', 'internal']
|
||||
OAUTH2_AUTO_CREATE_USER = True
|
||||
OAUTH2_CONFIG = [{
|
||||
'OAUTH2_NAME': 'authentik',
|
||||
'OAUTH2_DISPLAY_NAME': 'authentik',
|
||||
'OAUTH2_CLIENT_ID': '<Client ID from authentik>',
|
||||
'OAUTH2_CLIENT_SECRET': '<Client secret from authentik>',
|
||||
'OAUTH2_TOKEN_URL': 'https://authentik.company/application/o/token/',
|
||||
'OAUTH2_AUTHORIZATION_URL': 'https://authentik.company/application/o/authorize/',
|
||||
'OAUTH2_API_BASE_URL': 'https://authentik.company/',
|
||||
'OAUTH2_USERINFO_ENDPOINT': 'https://authentik.company/application/o/userinfo/',
|
||||
'OAUTH2_SERVER_METADATA_URL': 'https://authentik.company/application/o/<App Slug>/.well-known/openid-configuration',
|
||||
'OAUTH2_SCOPE': 'openid email profile',
|
||||
'OAUTH2_ICON': '<Fontawesome icon key (e.g., fa-key)>',
|
||||
'OAUTH2_BUTTON_COLOR': '<Hexadecimal color code for the login button>'
|
||||
}]
|
||||
```
|
||||
|
||||
:::note
|
||||
More information on that file can be found in the official pgAdmin [documentation](https://www.pgadmin.org/docs/pgadmin4/development/config_py.html)
|
||||
:::
|
||||
3. Save the file and restart pgAdmin for the changes to take effect.
|
||||
|
||||
Copy the following code into the `config_local.py` file and replace all placeholders and FQDN placeholders
|
||||
:::note
|
||||
If the `config_local.py` file does not exist, it needs to be created in the `/pgadmin4/` directory.
|
||||
:::
|
||||
:::note
|
||||
You must restart pgAdmin every time you make changes to `config_local.py`.
|
||||
:::
|
||||
|
||||
```py
|
||||
AUTHENTICATION_SOURCES = ['oauth2', 'internal']
|
||||
OAUTH2_AUTO_CREATE_USER = True
|
||||
OAUTH2_CONFIG = [{
|
||||
'OAUTH2_NAME' : 'authentik',
|
||||
'OAUTH2_DISPLAY_NAME' : '<display-name>',
|
||||
'OAUTH2_CLIENT_ID' : '<client-id>',
|
||||
'OAUTH2_CLIENT_SECRET' : '<client-secret>',
|
||||
'OAUTH2_TOKEN_URL' : 'https://authentik.company/application/o/token/',
|
||||
'OAUTH2_AUTHORIZATION_URL' : 'https://authentik.company/application/o/authorize/',
|
||||
'OAUTH2_API_BASE_URL' : 'https://authentik.company/',
|
||||
'OAUTH2_USERINFO_ENDPOINT' : 'https://authentik.company/application/o/userinfo/',
|
||||
'OAUTH2_SERVER_METADATA_URL' : 'https://authentik.company/application/o/<app-slug>/.well-known/openid-configuration',
|
||||
'OAUTH2_SCOPE' : 'openid email profile',
|
||||
'OAUTH2_ICON' : '<fontawesome-icon>',
|
||||
'OAUTH2_BUTTON_COLOR' : '<button-color>'
|
||||
}]
|
||||
### Using Environment Variables for Containerized Deployments
|
||||
|
||||
For deployments using Docker or Kubernetes, you can configure OAuth using the following environment variables:
|
||||
|
||||
1. Set these environment variables in your container:
|
||||
|
||||
```bash
|
||||
PGADMIN_CONFIG_AUTHENTICATION_SOURCES="['oauth2', 'internal']"
|
||||
PGADMIN_CONFIG_OAUTH2_AUTO_CREATE_USER=True
|
||||
PGADMIN_CONFIG_OAUTH2_CONFIG="[{'OAUTH2_NAME':'authentik','OAUTH2_DISPLAY_NAME':'Login with authentik','OAUTH2_CLIENT_ID':'<Client ID from authentik>','OAUTH2_CLIENT_SECRET':'<Client secret from authentik>','OAUTH2_TOKEN_URL':'https://authentik.company/application/o/token/','OAUTH2_AUTHORIZATION_URL':'https://authentik.company/application/o/authorize/','OAUTH2_API_BASE_URL':'https://authentik.company/','OAUTH2_USERINFO_ENDPOINT':'https://authentik.company/application/o/userinfo/','OAUTH2_SERVER_METADATA_URL':'https://authentik.company/application/o/<App Slug>/.well-known/openid-configuration','OAUTH2_SCOPE':'openid email profile','OAUTH2_ICON':'<Fontawesome icon key (e.g., fa-key)>','OAUTH2_BUTTON_COLOR':'<Hexadecimal color code for the login button>'}]"
|
||||
```
|
||||
|
||||
In the code above the following placeholders have been used:
|
||||
### General Notes
|
||||
|
||||
- `<display-name>`: The name that is displayed on the Login Button
|
||||
- `<client-id>`: The Client ID from step 1
|
||||
- `<client-secret>`: The Client Secret from step 1
|
||||
- `<app-slug>`: The App Slug from step 2, it should be `pgadmin` if you did not change it
|
||||
- `<fontawesome-icon>`: An icon name from [fontawesome](https://fontawesome.com). Only brand icons seem to be supported. This icon is displayed in front of the `<display-name>`. E.g.: _fa-github_.
|
||||
- `<button-color>`: Sets the color of the Login Button. Should be in Hex format, E.g.: _#fd4b2d_
|
||||
- To **only allow OAuth2 login**, set:
|
||||
|
||||
:::note
|
||||
To only allow authentication via authentik set `AUTHENTICATION_SOURCES` to _['oauth2']_. This should **only** be done once at least one user registered via authentik has been made an admin in pgAdmin.
|
||||
:::
|
||||
```python
|
||||
AUTHENTICATION_SOURCES = ['oauth2']
|
||||
```
|
||||
|
||||
:::note
|
||||
To disable user creation on pgAdmin, set `OAUTH2_AUTO_CREATE_USER` to _False_
|
||||
:::
|
||||
Ensure that you promote at least one user to an admin before disabling the internal authentication.
|
||||
|
||||
Finally, restart pgAdmin to apply the changes.
|
||||
- To **disable automatic user creation**, set:
|
||||
```python
|
||||
OAUTH2_AUTO_CREATE_USER = False
|
||||
```
|
||||
Setting this value to `False` disables automatic user creation. This ensures that only the first signed-in user is registered.
|
||||
|
||||
:::note
|
||||
pgAdmin needs to be restarted **every** time changes to `config_local.py` are made
|
||||
:::
|
||||
## Configuration verification
|
||||
|
||||
To confirm that authentik is properly configured with pgAdmin, log out and log back in via authentik. A new button should have appeared on the login page.
|
||||
|
@ -10,6 +10,7 @@ sidebar_label: Semaphore
|
||||
## What is Semaphore UI
|
||||
|
||||
> Semaphore UI is a modern web interface for managing popular DevOps tools.
|
||||
>
|
||||
> -- https://semaphoreui.com/
|
||||
>
|
||||
> This guide explains how to configure Semaphore UI to use authentik as the OAuth provider for logging in to the Web GUI.
|
||||
@ -23,49 +24,63 @@ The following placeholders are used in this guide:
|
||||
|
||||
## authentik configuration
|
||||
|
||||
[Create](https://docs.goauthentik.io/docs/add-secure-apps/applications/manage_apps#add-new-applications) an OAuth2/OpenID provider and an application in authentik. Use the following parameters for the OAuth2/OpenID provider:
|
||||
Start the wizard for adding a new application.
|
||||
|
||||
**Provider:**
|
||||
|
||||
- Name: `SP-semaphore`
|
||||
- Client type: `Confidential`
|
||||
- Redirect URIs/Origins (RegEx): `https://semaphore.company/api/auth/oidc/authentik/redirect/`
|
||||
- Signing Key: `authentik Self-signed Certificate`
|
||||
|
||||
Take note of the Client ID and Client Secret, you'll need to give them to Semaphore UI in Step 3.
|
||||
|
||||
Leave the rest as default values. The durations can be changed as needed.
|
||||
|
||||
**Application:**
|
||||
**1. Application:**
|
||||
|
||||
- Name: `Semaphore UI`
|
||||
- Slug: `semaphore`
|
||||
- Launch URL: `https://semaphore.company/`
|
||||
|
||||
**2. Choose a Provider**
|
||||
|
||||
Select `OAuth2/OpenID Provider`
|
||||
|
||||
**3. Configure Provider**
|
||||
|
||||
Select implicit or explicit authorization flow as desired.
|
||||
|
||||
Take note of the Client ID and Client Secret, you'll need to give them to Semaphore UI later.
|
||||
|
||||
- Redirect URIs/Origins (RegEx): `https://semaphore.company/api/auth/oidc/authentik/redirect/`
|
||||
- Signing Key: `authentik Self-signed Certificate`
|
||||
|
||||
Leave the rest as default values.
|
||||
|
||||
## Semaphore UI configuration
|
||||
|
||||
Log in to your Semaphore UI host via SSH. Edit the `config.json` file (should be located under `/etc/semaphore`) file with the text editor of your choice.
|
||||
Log in to your Semaphore UI host via SSH. Edit the `/etc/semaphore/config.json` file with the text editor of your choice.
|
||||
|
||||
Before the last curly brace, add the following content:
|
||||
Add the `oidc_providers` configuration:
|
||||
|
||||
```
|
||||
"oidc_providers": {
|
||||
"authentik": {
|
||||
"display_name": "SSO-Login",
|
||||
"provider_url": "https://authentik.company/application/o/semaphore/",
|
||||
"client_id": "<client-id>",
|
||||
"client_secret": "<client-secret>",
|
||||
"redirect_url": "https://semaphore.company/api/auth/oidc/authentik/redirect/",
|
||||
"username_claim": "username",
|
||||
"name_claim": "name",
|
||||
"email_claim": "email",
|
||||
"scopes": ["openid", "profile", "email"]
|
||||
}
|
||||
{
|
||||
"oidc_providers": {
|
||||
"authentik": {
|
||||
"display_name": "Sign in with Authentik",
|
||||
"provider_url": "https://authentik.company/application/o/<slug>/",
|
||||
"client_id": "<client-id>",
|
||||
"client_secret": "<client-secret>",
|
||||
"redirect_url": "https://semaphore.company/api/auth/oidc/authentik/redirect/",
|
||||
"username_claim": "username",
|
||||
"name_claim": "name",
|
||||
"email_claim": "email",
|
||||
"scopes": ["openid", "profile", "email"]
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
:::info
|
||||
It is mandatory to include 'authentik' in lowercase letters. There should also be another curly brace above these lines. Make sure to add a `,` after it to maintain proper formatting.
|
||||
The name of the oidc_provider (e.g. `authentik`) needs to match the name on the redirect URL.
|
||||
:::
|
||||
|
||||
:::info
|
||||
If a `Not Found` error is displayed after the login, you might need to set the web_root to `/` (see https://github.com/semaphoreui/semaphore/issues/2681):
|
||||
|
||||
```
|
||||
SEMAPHORE_WEB_ROOT: /
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
More information on this can be found in the Semaphore documentation https://docs.semaphoreui.com/administration-guide/openid/authentik/.
|
||||
|
@ -113,42 +113,9 @@ You must sync your LDAP database with Snipe-IT. Go to People on the sidebar menu
|
||||
- Select your Location
|
||||
- Click Synchronize
|
||||
:::note
|
||||
Snipe-IT will only import users with both a first and last name set. If you do not have first and last names stored in your users attributes, you can create a property mapping to set first and last name.
|
||||
Snipe-IT will only import users with both a first and last name set. You need to create user attributes with first and last names.
|
||||
:::
|
||||
|
||||
## authentik Property Mapping
|
||||
|
||||
To create a policy mapping, go to _Customization/Property Mappings_, click `Create` then `LDAP Property Mapping`. Name is 'sn' and set Object field to sn:
|
||||
|
||||
```ini
|
||||
def getLastName():
|
||||
if len(request.user.name) >= 1:
|
||||
return request.user.name.split(" ")[1]
|
||||
elif len(request.user.name) == 1:
|
||||
return request.user.name.split(" ")[1]
|
||||
else:
|
||||
return ""
|
||||
|
||||
return {
|
||||
"sn": getLastName(),
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Create a second policy mapping, name it 'givenname' and set Object field to 'givenname'
|
||||
|
||||
```
|
||||
def getFirstName():
|
||||
if len(request.user.name) >= 1:
|
||||
return request.user.name.split(" ")[0]
|
||||
else:
|
||||
return f"N/A"
|
||||
|
||||
return {
|
||||
"givenname": getFirstName(),
|
||||
}
|
||||
```
|
||||
|
||||
## authentik SAML Config
|
||||
|
||||
### Step 1
|
||||
|
64
website/integrations/services/terrakube/index.md
Normal file
64
website/integrations/services/terrakube/index.md
Normal file
@ -0,0 +1,64 @@
|
||||
---
|
||||
title: Integrate with Terrakube
|
||||
sidebar_label: Terrakube
|
||||
---
|
||||
|
||||
# Terrakube
|
||||
|
||||
<span class="badge badge--secondary">Support level: Community</span>
|
||||
|
||||
## What is Terrakube
|
||||
|
||||
> Terrakube is an open-source collaboration platform designed for managing remote Infrastructure-as-Code (IaC) operations with Terraform. It serves as a alternative to proprietary tools like Terraform Enterprise.
|
||||
>
|
||||
> -- https://terrakube.io/
|
||||
|
||||
## Preparation
|
||||
|
||||
The following placeholders are used in this guide:
|
||||
|
||||
- `terrakube-dex.company` is the FQDN of the [Dex](https://dexidp.io/) container of the Terrakube installation.
|
||||
- `authentik.company` is the FQDN of the authentik installation.
|
||||
|
||||
## authentik configuration
|
||||
|
||||
1. From the Admin interface, navigate to **Applications** -> **Applications**.
|
||||
2. Use the wizard to create a new application and provider. During this process:
|
||||
- Note the **Client ID**, **Client Secret**, and **slug** values because they will be required later.
|
||||
- Set a `Strict` redirect URI to ` https://terrakube-dex.company/dex/callback`.
|
||||
- Select any available signing key.
|
||||
|
||||
## Terrakube configuration
|
||||
|
||||
This guide assumes that you have environment variables `$TERRAKUBE_OIDC_CLIENT_ID` and `$TERRAKUBE_OIDC_CLIENT_SECRET` set up. You can hard-code values if your setup doesn’t support environment variables, but be aware that doing so is not recommended for security reasons.
|
||||
|
||||
1. **Locate the Dex Configuration File**
|
||||
Find the Dex configuration file, typically named `config.yaml` or `config.docker.yaml`. It’s usually located in the `/etc/dex` directory or the corresponding directory for a containerized setup.
|
||||
|
||||
2. **Update the Dex Configuration**
|
||||
To define the Terrakube OIDC connector, open the configuration file and add the following block:
|
||||
|
||||
```yaml
|
||||
connectors:
|
||||
- type: oidc
|
||||
id: TerrakubeClient
|
||||
name: TerrakubeClient
|
||||
config:
|
||||
issuer: "https://authentik.company/application/o/<Your application slug>/"
|
||||
clientID: $TERRAKUBE_OIDC_CLIENT_ID
|
||||
clientSecret: $TERRAKUBE_OIDC_CLIENT_SECRET
|
||||
redirectURI: "https://terrakube-dex.company/dex/callback"
|
||||
insecureEnableGroups: true
|
||||
```
|
||||
|
||||
3. **Set Environment Variables**
|
||||
Add the following variables to your `.env` file, replacing them with the appropriate values for your Client ID and Client Secret:
|
||||
|
||||
```env
|
||||
TERRAKUBE_OIDC_CLIENT_ID=*your Client ID*
|
||||
TERRAKUBE_OIDC_CLIENT_SECRET=*your Client Secret*
|
||||
```
|
||||
|
||||
## Configuration verification
|
||||
|
||||
To ensure that authentik is correctly configured with Terrakube, log out and log back in through authentik. Depending on the number of connectors you have set up, you should either be redirected to authentik or see a new button appear on the Dex login page.
|
8
website/package-lock.json
generated
8
website/package-lock.json
generated
@ -35,7 +35,7 @@
|
||||
"@docusaurus/tsconfig": "^3.7.0",
|
||||
"@docusaurus/types": "^3.3.2",
|
||||
"@types/react": "^18.3.13",
|
||||
"aws-cdk": "^2.175.1",
|
||||
"aws-cdk": "^2.176.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"prettier": "3.4.2",
|
||||
"typescript": "~5.7.3",
|
||||
@ -5715,9 +5715,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk": {
|
||||
"version": "2.175.1",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.175.1.tgz",
|
||||
"integrity": "sha512-duvy0FtGAAYqJi/x0MjBfCp60ZlDYl0X5/GrADwMz4AfHQ8aTXCyaVsdJuCxz0ZMHSNaFRuCNkAlc2Xu43zQmQ==",
|
||||
"version": "2.176.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.176.0.tgz",
|
||||
"integrity": "sha512-yRjIXzK2ddznwuSjasWAViYBtBSQbEu6GHlylaC3GHsIUPhrK3KguqIuhdlxjMeiQ1Fvok8REDLCReZJdrSLLg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"cdk": "bin/cdk"
|
||||
|
@ -56,7 +56,7 @@
|
||||
"@docusaurus/tsconfig": "^3.7.0",
|
||||
"@docusaurus/types": "^3.3.2",
|
||||
"@types/react": "^18.3.13",
|
||||
"aws-cdk": "^2.175.1",
|
||||
"aws-cdk": "^2.176.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"prettier": "3.4.2",
|
||||
"typescript": "~5.7.3",
|
||||
|
@ -96,6 +96,7 @@ module.exports = {
|
||||
"services/skyhigh/index",
|
||||
"services/snipe-it/index",
|
||||
"services/sssd/index",
|
||||
"services/terrakube/index",
|
||||
"services/truecommand/index",
|
||||
"services/veeam-enterprise-manager/index",
|
||||
"services/zammad/index",
|
||||
@ -119,6 +120,7 @@ module.exports = {
|
||||
type: "category",
|
||||
label: "Miscellaneous",
|
||||
items: [
|
||||
"services/actual-budget/index",
|
||||
"services/engomo/index",
|
||||
"services/frappe/index",
|
||||
"services/freshrss/index",
|
||||
|
Reference in New Issue
Block a user