Compare commits

..

10 Commits

Author SHA1 Message Date
e095e9f694 release: 2023.10.7 2024-01-29 17:44:48 +01:00
10e311534f security: fix CVE-2024-23647 (cherry-pick #8345) (#8347)
security: fix CVE-2024-23647 (#8345)

* security: fix CVE-2024-23647



* add tests



* add website



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2024-01-29 17:42:04 +01:00
46fdb45273 root: fix redis config not being updated to match previous change
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-01-29 14:02:18 +01:00
6d4125cb90 root: fix listen trusted_proxy_cidrs config loading from environment (#8075)
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>

# Conflicts:
#	go.mod
#	go.sum
#	internal/config/struct.go
2024-01-25 14:51:25 +01:00
bc83176962 stages/authenticator_validate: use friendly_name for stage selector when enrolling (cherry-pick #8255) (#8256)
stages/authenticator_validate: use friendly_name for stage selector when enrolling (#8255)

* stages/authenticator_validate: use friendly_name for stage selector when enrolling



* fix tests



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2024-01-22 16:27:46 +01:00
0fa8432b72 rbac: fix invitations listing with restricted permissions (cherry-pick #8227) (#8229)
rbac: fix invitations listing with restricted permissions (#8227)

* rbac: fix missing permission definition for list



* core: fix users's system_permissions not including role permissions



* core: don't require permissions for users/me/



* web/admin: catch error when listing stages on invitation page fails



* Revert "rbac: fix missing permission definition for list"

This reverts commit fd7572e699.

* Revert "core: don't require permissions for users/me/"

This reverts commit 9df0dbda8a.

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2024-01-18 23:24:58 +01:00
bb9a524b53 sources/oauth: fix URLs being overwritten by OIDC urls (cherry-pick #8147) (#8156)
sources/oauth: fix URLs being overwritten by OIDC urls (#8147)

* sources/oauth: fix URLs being overwritten by OIDC urls



* fix tests



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2024-01-13 16:37:47 +01:00
d31c05625b sources/oauth: fix azure_ad user_id and add test and fallback (cherry-pick #8146) (#8152) 2024-01-12 21:01:24 +01:00
399223b770 web/flows: fix icon for generic oauth source with dark theme (cherry-pick #8148) (#8151) 2024-01-12 21:01:11 +01:00
19197d3f9b sources/oauth: revert azure_ad profile URL change (cherry-pick #8139) (#8141)
sources/oauth: revert azure_ad profile URL change (#8139)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2024-01-12 16:21:59 +01:00
30 changed files with 238 additions and 105 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2023.10.6
current_version = 2023.10.7
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)

View File

@ -2,7 +2,7 @@
from os import environ
from typing import Optional
__version__ = "2023.10.6"
__version__ = "2023.10.7"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -233,9 +233,9 @@ class UserSelfSerializer(ModelSerializer):
def get_system_permissions(self, user: User) -> list[str]:
"""Get all system permissions assigned to the user"""
return list(
user.user_permissions.filter(
content_type__app_label="authentik_rbac", content_type__model="systempermission"
).values_list("codename", flat=True)
x.split(".", maxsplit=1)[1]
for x in user.get_all_permissions()
if x.startswith("authentik_rbac")
)
class Meta:

View File

@ -22,8 +22,9 @@ class TestTokenPKCE(OAuthTestCase):
self.factory = RequestFactory()
self.app = Application.objects.create(name=generate_id(), slug="test")
def test_pkce_missing_in_token(self):
"""Test full with pkce"""
def test_pkce_missing_in_authorize(self):
"""Test PKCE with code_challenge in authorize request
and missing verifier in token request"""
flow = create_test_flow()
provider = OAuth2Provider.objects.create(
name=generate_id(),
@ -74,7 +75,77 @@ class TestTokenPKCE(OAuthTestCase):
)
self.assertJSONEqual(
response.content,
{"error": "invalid_request", "error_description": "The request is otherwise malformed"},
{
"error": "invalid_grant",
"error_description": (
"The provided authorization grant or refresh token is invalid, expired, "
"revoked, does not match the redirection URI used in the authorization "
"request, or was issued to another client"
),
},
)
self.assertEqual(response.status_code, 400)
def test_pkce_missing_in_token(self):
"""Test PKCE with missing code_challenge in authorization request but verifier
set in token request"""
flow = create_test_flow()
provider = OAuth2Provider.objects.create(
name=generate_id(),
client_id="test",
authorization_flow=flow,
redirect_uris="foo://localhost",
access_code_validity="seconds=100",
)
Application.objects.create(name="app", slug="app", provider=provider)
state = generate_id()
user = create_test_admin_user()
self.client.force_login(user)
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
# Step 1, initiate params and get redirect to flow
self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "code",
"client_id": "test",
"state": state,
"redirect_uri": "foo://localhost",
# "code_challenge": challenge,
# "code_challenge_method": "S256",
},
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first()
self.assertJSONEqual(
response.content.decode(),
{
"component": "xak-flow-redirect",
"type": ChallengeTypes.REDIRECT.value,
"to": f"foo://localhost?code={code.code}&state={state}",
},
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
data={
"grant_type": GRANT_TYPE_AUTHORIZATION_CODE,
"code": code.code,
"code_verifier": generate_id(),
"redirect_uri": "foo://localhost",
},
HTTP_AUTHORIZATION=f"Basic {header}",
)
self.assertJSONEqual(
response.content,
{
"error": "invalid_grant",
"error_description": (
"The provided authorization grant or refresh token is invalid, expired, "
"revoked, does not match the redirection URI used in the authorization "
"request, or was issued to another client"
),
},
)
self.assertEqual(response.status_code, 400)

View File

@ -231,7 +231,7 @@ class TokenParams:
if self.authorization_code.code_challenge:
# Authorization code had PKCE but we didn't get one
if not self.code_verifier:
raise TokenError("invalid_request")
raise TokenError("invalid_grant")
if self.authorization_code.code_challenge_method == PKCE_METHOD_S256:
new_code_challenge = (
urlsafe_b64encode(sha256(self.code_verifier.encode("ascii")).digest())
@ -244,6 +244,10 @@ class TokenParams:
if new_code_challenge != self.authorization_code.code_challenge:
LOGGER.warning("Code challenge not matching")
raise TokenError("invalid_grant")
# Token request had a code_verifier but code did not have a code challenge
# Prevent downgrade
if not self.authorization_code.code_challenge and self.code_verifier:
raise TokenError("invalid_grant")
def __post_init_refresh(self, raw_token: str, request: HttpRequest):
if not raw_token:

View File

@ -56,6 +56,7 @@ class OAuthSourceSerializer(SourceSerializer):
"""Get source's type configuration"""
return SourceTypeSerializer(instance.source_type).data
# pylint: disable=too-many-locals
def validate(self, attrs: dict) -> dict:
session = get_http_session()
source_type = registry.find_type(attrs["provider_type"])
@ -73,9 +74,17 @@ class OAuthSourceSerializer(SourceSerializer):
config = well_known_config.json()
if "issuer" not in config:
raise ValidationError({"oidc_well_known_url": "Invalid well-known configuration"})
attrs["authorization_url"] = config.get("authorization_endpoint", "")
attrs["access_token_url"] = config.get("token_endpoint", "")
attrs["profile_url"] = config.get("userinfo_endpoint", "")
field_map = {
# authentik field to oidc field
"authorization_url": "authorization_endpoint",
"access_token_url": "token_endpoint",
"profile_url": "userinfo_endpoint",
}
for ak_key, oidc_key in field_map.items():
# Don't overwrite user-set values
if ak_key in attrs and attrs[ak_key]:
continue
attrs[ak_key] = config.get(oidc_key, "")
inferred_oidc_jwks_url = config.get("jwks_uri", "")
# Prefer user-entered URL to inferred URL to default URL

View File

@ -44,3 +44,7 @@ class TestTypeAzureAD(TestCase):
self.assertEqual(ak_context["username"], AAD_USER["userPrincipalName"])
self.assertEqual(ak_context["email"], AAD_USER["mail"])
self.assertEqual(ak_context["name"], AAD_USER["displayName"])
def test_user_id(self):
"""Test azure AD user ID"""
self.assertEqual(AzureADOAuthCallback().get_user_id(AAD_USER), AAD_USER["id"])

View File

@ -69,9 +69,6 @@ class TestOAuthSource(TestCase):
"provider_type": "openidconnect",
"consumer_key": "foo",
"consumer_secret": "foo",
"authorization_url": "http://foo",
"access_token_url": "http://foo",
"profile_url": "http://foo",
"oidc_well_known_url": url,
"oidc_jwks_url": "",
},

View File

@ -25,6 +25,11 @@ class AzureADOAuthCallback(OpenIDConnectOAuth2Callback):
client_class = UserprofileHeaderAuthClient
def get_user_id(self, info: dict[str, str]) -> str:
# Default try to get `id` for the Graph API endpoint
# fallback to OpenID logic in case the profile URL was changed
return info.get("id", super().get_user_id(info))
def get_user_enroll_context(
self,
info: dict[str, Any],
@ -50,7 +55,7 @@ class AzureADType(SourceType):
authorization_url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
access_token_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token" # nosec
profile_url = "https://login.microsoftonline.com/common/openid/userinfo"
profile_url = "https://graph.microsoft.com/v1.0/me"
oidc_well_known_url = (
"https://login.microsoftonline.com/common/.well-known/openid-configuration"
)

View File

@ -38,4 +38,11 @@ class Migration(migrations.Migration):
name="statictoken",
options={"verbose_name": "Static Token", "verbose_name_plural": "Static Tokens"},
),
migrations.AlterModelOptions(
name="authenticatorstaticstage",
options={
"verbose_name": "Static Authenticator Setup Stage",
"verbose_name_plural": "Static Authenticator Setup Stages",
},
),
]

View File

@ -46,11 +46,11 @@ class AuthenticatorStaticStage(ConfigurableStage, FriendlyNamedStage, Stage):
)
def __str__(self) -> str:
return f"Static Authenticator Stage {self.name}"
return f"Static Authenticator Setup Stage {self.name}"
class Meta:
verbose_name = _("Static Authenticator Stage")
verbose_name_plural = _("Static Authenticator Stages")
verbose_name = _("Static Authenticator Setup Stage")
verbose_name_plural = _("Static Authenticator Setup Stages")
class StaticDevice(SerializerModel, ThrottlingMixin, Device):

View File

@ -300,8 +300,10 @@ class AuthenticatorValidateStageView(ChallengeStageView):
serializer = SelectableStageSerializer(
data={
"pk": stage.pk,
"name": stage.name,
"verbose_name": str(stage._meta.verbose_name),
"name": stage.friendly_name or stage.name,
"verbose_name": str(stage._meta.verbose_name)
.replace("Setup Stage", "")
.strip(),
"meta_model_name": f"{stage._meta.app_label}.{stage._meta.model_name}",
}
)

View File

@ -11,6 +11,7 @@ from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.generators import generate_id, generate_key
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
from authentik.stages.authenticator_static.models import AuthenticatorStaticStage
from authentik.stages.authenticator_validate.api import AuthenticatorValidateStageSerializer
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses
from authentik.stages.authenticator_validate.stage import PLAN_CONTEXT_DEVICE_CHALLENGES
@ -26,19 +27,22 @@ class AuthenticatorValidateStageTests(FlowTestCase):
def test_not_configured_action(self):
"""Test not_configured_action"""
conf_stage = IdentificationStage.objects.create(
ident_stage = IdentificationStage.objects.create(
name=generate_id(),
user_fields=[
UserFields.USERNAME,
],
)
conf_stage = AuthenticatorStaticStage.objects.create(
name=generate_id(),
)
stage = AuthenticatorValidateStage.objects.create(
name=generate_id(),
not_configured_action=NotConfiguredAction.CONFIGURE,
)
stage.configuration_stages.set([conf_stage])
flow = create_test_flow()
FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0)
FlowStageBinding.objects.create(target=flow, stage=ident_stage, order=0)
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
response = self.client.get(
@ -57,27 +61,22 @@ class AuthenticatorValidateStageTests(FlowTestCase):
self.assertStageResponse(
response,
flow,
component="ak-stage-identification",
password_fields=False,
primary_action="Continue",
user_fields=["username"],
sources=[],
show_source_labels=False,
component="ak-stage-authenticator-static",
)
def test_not_configured_action_multiple(self):
"""Test not_configured_action"""
conf_stage = IdentificationStage.objects.create(
ident_stage = IdentificationStage.objects.create(
name=generate_id(),
user_fields=[
UserFields.USERNAME,
],
)
conf_stage2 = IdentificationStage.objects.create(
conf_stage = AuthenticatorStaticStage.objects.create(
name=generate_id(),
)
conf_stage2 = AuthenticatorStaticStage.objects.create(
name=generate_id(),
user_fields=[
UserFields.USERNAME,
],
)
stage = AuthenticatorValidateStage.objects.create(
name=generate_id(),
@ -85,7 +84,7 @@ class AuthenticatorValidateStageTests(FlowTestCase):
)
stage.configuration_stages.set([conf_stage, conf_stage2])
flow = create_test_flow()
FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0)
FlowStageBinding.objects.create(target=flow, stage=ident_stage, order=0)
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
# Get initial identification stage
@ -118,12 +117,7 @@ class AuthenticatorValidateStageTests(FlowTestCase):
self.assertStageResponse(
response,
flow,
component="ak-stage-identification",
password_fields=False,
primary_action="Continue",
user_fields=["username"],
sources=[],
show_source_labels=False,
component="ak-stage-authenticator-static",
)
def test_stage_validation(self):

View File

@ -14,6 +14,7 @@ entries:
- attrs:
configure_flow: !KeyOf flow
token_count: 6
friendly_name: Static tokens
identifiers:
name: default-authenticator-static-setup
id: default-authenticator-static-setup

View File

@ -14,6 +14,7 @@ entries:
- attrs:
configure_flow: !KeyOf flow
digits: 6
friendly_name: TOTP Device
identifiers:
name: default-authenticator-totp-setup
id: default-authenticator-totp-setup

View File

@ -13,6 +13,7 @@ entries:
id: flow
- attrs:
configure_flow: !KeyOf flow
friendly_name: WebAuthn device
identifiers:
name: default-authenticator-webauthn-setup
id: default-authenticator-webauthn-setup

View File

@ -32,7 +32,7 @@ services:
volumes:
- redis:/data
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.6}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.7}
restart: unless-stopped
command: server
environment:
@ -53,7 +53,7 @@ services:
- postgresql
- redis
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.6}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.7}
restart: unless-stopped
command: worker
environment:

2
go.mod
View File

@ -4,7 +4,6 @@ go 1.21
require (
beryju.io/ldap v0.1.0
github.com/Netflix/go-env v0.0.0-20210215222557-e437a7e7f9fb
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/getsentry/sentry-go v0.25.0
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
@ -24,6 +23,7 @@ require (
github.com/pires/go-proxyproto v0.7.0
github.com/prometheus/client_golang v1.17.0
github.com/redis/go-redis/v9 v9.2.1
github.com/sethvargo/go-envconfig v1.0.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.4

8
go.sum
View File

@ -37,8 +37,6 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Netflix/go-env v0.0.0-20210215222557-e437a7e7f9fb h1:w9IDEB7P1VzNcBpOG7kMpFkZp2DkyJIUt0gDx5MBhRU=
github.com/Netflix/go-env v0.0.0-20210215222557-e437a7e7f9fb/go.mod h1:9XMFaCeRyW7fC9XJOWQ+NdAv8VLG7ys7l3x4ozEGLUQ=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
@ -198,8 +196,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -303,6 +301,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sethvargo/go-envconfig v1.0.0 h1:1C66wzy4QrROf5ew4KdVw942CQDa55qmlYmw9FZxZdU=
github.com/sethvargo/go-envconfig v1.0.0/go.mod h1:Lzc75ghUn5ucmcRGIdGQ33DKJrcjk4kihFYgSTBmjIc=
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=

View File

@ -1,6 +1,7 @@
package config
import (
"context"
_ "embed"
"errors"
"fmt"
@ -10,10 +11,11 @@ import (
"reflect"
"strings"
env "github.com/Netflix/go-env"
env "github.com/sethvargo/go-envconfig"
log "github.com/sirupsen/logrus"
"goauthentik.io/authentik/lib"
"gopkg.in/yaml.v2"
"goauthentik.io/authentik/lib"
)
var cfg *Config
@ -113,7 +115,8 @@ func (c *Config) LoadConfigFromFile(path string) error {
}
func (c *Config) fromEnv() error {
_, err := env.UnmarshalFromEnviron(c)
ctx := context.Background()
err := env.Process(ctx, c)
if err != nil {
return fmt.Errorf("failed to load environment variables: %w", err)
}

View File

@ -3,17 +3,17 @@ package config
type Config struct {
// Core specific config
Paths PathsConfig `yaml:"paths"`
LogLevel string `yaml:"log_level" env:"AUTHENTIK_LOG_LEVEL"`
ErrorReporting ErrorReportingConfig `yaml:"error_reporting"`
Redis RedisConfig `yaml:"redis"`
Outposts OutpostConfig `yaml:"outposts"`
LogLevel string `yaml:"log_level" env:"AUTHENTIK_LOG_LEVEL, overwrite"`
ErrorReporting ErrorReportingConfig `yaml:"error_reporting" env:", prefix=AUTHENTIK_ERROR_REPORTING__"`
Redis RedisConfig `yaml:"redis" env:", prefix=AUTHENTIK_REDIS__"`
Outposts OutpostConfig `yaml:"outposts" env:", prefix=AUTHENTIK_OUTPOSTS__"`
// Config for core and embedded outpost
SecretKey string `yaml:"secret_key" env:"AUTHENTIK_SECRET_KEY"`
SecretKey string `yaml:"secret_key" env:"AUTHENTIK_SECRET_KEY, overwrite"`
// Config for both core and outposts
Debug bool `yaml:"debug" env:"AUTHENTIK_DEBUG"`
Listen ListenConfig `yaml:"listen"`
Debug bool `yaml:"debug" env:"AUTHENTIK_DEBUG, overwrite"`
Listen ListenConfig `yaml:"listen" env:", prefix=AUTHENTIK_LISTEN__"`
// Outpost specific config
// These are only relevant for proxy/ldap outposts, and cannot be set via YAML
@ -25,27 +25,28 @@ type Config struct {
}
type RedisConfig struct {
Host string `yaml:"host" env:"AUTHENTIK_REDIS__HOST"`
Port int `yaml:"port" env:"AUTHENTIK_REDIS__PORT"`
Password string `yaml:"password" env:"AUTHENTIK_REDIS__PASSWORD"`
TLS bool `yaml:"tls" env:"AUTHENTIK_REDIS__TLS"`
TLSReqs string `yaml:"tls_reqs" env:"AUTHENTIK_REDIS__TLS_REQS"`
DB int `yaml:"cache_db" env:"AUTHENTIK_REDIS__DB"`
CacheTimeout int `yaml:"cache_timeout" env:"AUTHENTIK_REDIS__CACHE_TIMEOUT"`
CacheTimeoutFlows int `yaml:"cache_timeout_flows" env:"AUTHENTIK_REDIS__CACHE_TIMEOUT_FLOWS"`
CacheTimeoutPolicies int `yaml:"cache_timeout_policies" env:"AUTHENTIK_REDIS__CACHE_TIMEOUT_POLICIES"`
CacheTimeoutReputation int `yaml:"cache_timeout_reputation" env:"AUTHENTIK_REDIS__CACHE_TIMEOUT_REPUTATION"`
Host string `yaml:"host" env:"HOST, overwrite"`
Port int `yaml:"port" env:"PORT, overwrite"`
DB int `yaml:"db" env:"DB, overwrite"`
Username string `yaml:"username" env:"USERNAME, overwrite"`
Password string `yaml:"password" env:"PASSWORD, overwrite"`
TLS bool `yaml:"tls" env:"TLS, overwrite"`
TLSReqs string `yaml:"tls_reqs" env:"TLS_REQS, overwrite"`
CacheTimeout int `yaml:"cache_timeout" env:"CACHE_TIMEOUT, overwrite"`
CacheTimeoutFlows int `yaml:"cache_timeout_flows" env:"CACHE_TIMEOUT_FLOWS, overwrite"`
CacheTimeoutPolicies int `yaml:"cache_timeout_policies" env:"CACHE_TIMEOUT_POLICIES, overwrite"`
CacheTimeoutReputation int `yaml:"cache_timeout_reputation" env:"CACHE_TIMEOUT_REPUTATION, overwrite"`
}
type ListenConfig struct {
HTTP string `yaml:"listen_http" env:"AUTHENTIK_LISTEN__HTTP"`
HTTPS string `yaml:"listen_https" env:"AUTHENTIK_LISTEN__HTTPS"`
LDAP string `yaml:"listen_ldap" env:"AUTHENTIK_LISTEN__LDAP"`
LDAPS string `yaml:"listen_ldaps" env:"AUTHENTIK_LISTEN__LDAPS"`
Radius string `yaml:"listen_radius" env:"AUTHENTIK_LISTEN__RADIUS"`
Metrics string `yaml:"listen_metrics" env:"AUTHENTIK_LISTEN__METRICS"`
Debug string `yaml:"listen_debug" env:"AUTHENTIK_LISTEN__DEBUG"`
TrustedProxyCIDRs []string `yaml:"trusted_proxy_cidrs" env:"AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS"`
HTTP string `yaml:"listen_http" env:"HTTP, overwrite"`
HTTPS string `yaml:"listen_https" env:"HTTPS, overwrite"`
LDAP string `yaml:"listen_ldap" env:"LDAP, overwrite"`
LDAPS string `yaml:"listen_ldaps" env:"LDAPS, overwrite"`
Radius string `yaml:"listen_radius" env:"RADIUS, overwrite"`
Metrics string `yaml:"listen_metrics" env:"METRICS, overwrite"`
Debug string `yaml:"listen_debug" env:"DEBUG, overwrite"`
TrustedProxyCIDRs []string `yaml:"trusted_proxy_cidrs" env:"TRUSTED_PROXY_CIDRS, overwrite"`
}
type PathsConfig struct {
@ -53,15 +54,15 @@ type PathsConfig struct {
}
type ErrorReportingConfig struct {
Enabled bool `yaml:"enabled" env:"AUTHENTIK_ERROR_REPORTING__ENABLED"`
SentryDSN string `yaml:"sentry_dsn" env:"AUTHENTIK_ERROR_REPORTING__SENTRY_DSN"`
Environment string `yaml:"environment" env:"AUTHENTIK_ERROR_REPORTING__ENVIRONMENT"`
SendPII bool `yaml:"send_pii" env:"AUTHENTIK_ERROR_REPORTING__SEND_PII"`
SampleRate float64 `yaml:"sample_rate" env:"AUTHENTIK_ERROR_REPORTING__SAMPLE_RATE"`
Enabled bool `yaml:"enabled" env:"ENABLED, overwrite"`
SentryDSN string `yaml:"sentry_dsn" env:"SENTRY_DSN, overwrite"`
Environment string `yaml:"environment" env:"ENVIRONMENT, overwrite"`
SendPII bool `yaml:"send_pii" env:"SEND_PII, overwrite"`
SampleRate float64 `yaml:"sample_rate" env:"SAMPLE_RATE, overwrite"`
}
type OutpostConfig struct {
ContainerImageBase string `yaml:"container_image_base" env:"AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE"`
Discover bool `yaml:"discover" env:"AUTHENTIK_OUTPOSTS__DISCOVER"`
DisableEmbeddedOutpost bool `yaml:"disable_embedded_outpost" env:"AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST"`
ContainerImageBase string `yaml:"container_image_base" env:"CONTAINER_IMAGE_BASE, overwrite"`
Discover bool `yaml:"discover" env:"DISCOVER, overwrite"`
DisableEmbeddedOutpost bool `yaml:"disable_embedded_outpost" env:"DISABLE_EMBEDDED_OUTPOST, overwrite"`
}

View File

@ -29,4 +29,4 @@ func UserAgent() string {
return fmt.Sprintf("authentik@%s", FullVersion())
}
const VERSION = "2023.10.6"
const VERSION = "2023.10.7"

View File

@ -113,7 +113,7 @@ filterwarnings = [
[tool.poetry]
name = "authentik"
version = "2023.10.6"
version = "2023.10.7"
description = ""
authors = ["authentik Team <hello@goauthentik.io>"]

View File

@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: authentik
version: 2023.10.6
version: 2023.10.7
description: Making authentication simple.
contact:
email: hello@goauthentik.io

View File

@ -62,20 +62,24 @@ export class InvitationListPage extends TablePage<Invitation> {
multipleEnrollmentFlows = false;
async apiEndpoint(page: number): Promise<PaginatedResponse<Invitation>> {
// Check if any invitation stages exist
const stages = await new StagesApi(DEFAULT_CONFIG).stagesInvitationStagesList({
noFlows: false,
});
this.invitationStageExists = stages.pagination.count > 0;
this.expandable = this.invitationStageExists;
stages.results.forEach((stage) => {
const enrollmentFlows = (stage.flowSet || []).filter(
(flow) => flow.designation === FlowDesignationEnum.Enrollment,
);
if (enrollmentFlows.length > 1) {
this.multipleEnrollmentFlows = true;
}
});
try {
// Check if any invitation stages exist
const stages = await new StagesApi(DEFAULT_CONFIG).stagesInvitationStagesList({
noFlows: false,
});
this.invitationStageExists = stages.pagination.count > 0;
this.expandable = this.invitationStageExists;
stages.results.forEach((stage) => {
const enrollmentFlows = (stage.flowSet || []).filter(
(flow) => flow.designation === FlowDesignationEnum.Enrollment,
);
if (enrollmentFlows.length > 1) {
this.multipleEnrollmentFlows = true;
}
});
} catch {
// assuming we can't fetch stages, ignore the error
}
return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsList({
ordering: this.order,
page: page,

View File

@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
export const ERROR_CLASS = "pf-m-danger";
export const PROGRESS_CLASS = "pf-m-in-progress";
export const CURRENT_CLASS = "pf-m-current";
export const VERSION = "2023.10.6";
export const VERSION = "2023.10.7";
export const TITLE_DEFAULT = "authentik";
export const ROUTE_SEPARATOR = ";";

View File

@ -257,7 +257,8 @@ select[multiple] option:checked {
.pf-c-login__main-header-desc {
color: var(--ak-dark-foreground);
}
.pf-c-login__main-footer-links-item img {
.pf-c-login__main-footer-links-item img,
.pf-c-login__main-footer-links-item .fas {
filter: invert(1);
}
.pf-c-login__main-footer-band {

View File

@ -86,7 +86,7 @@ To check if your config has been applied correctly, you can run the following co
`AUTHENTIK_REDIS__CACHE_TIMEOUT_REPUTATION` only applies to the cache expiry, see [`AUTHENTIK_REPUTATION__EXPIRY`](#authentik_reputation__expiry) to control how long reputation is persisted for.
:::
## Listen Setting
## Listen Settings
- `AUTHENTIK_LISTEN__HTTP`: Listening address:port (e.g. `0.0.0.0:9000`) for HTTP (Applies to Server and Proxy outpost)
- `AUTHENTIK_LISTEN__HTTPS`: Listening address:port (e.g. `0.0.0.0:9443`) for HTTPS (Applies to Server and Proxy outpost)
@ -94,7 +94,7 @@ To check if your config has been applied correctly, you can run the following co
- `AUTHENTIK_LISTEN__LDAPS`: Listening address:port (e.g. `0.0.0.0:6636`) for LDAPS (Applies to LDAP outpost)
- `AUTHENTIK_LISTEN__METRICS`: Listening address:port (e.g. `0.0.0.0:9300`) for Prometheus metrics (Applies to All)
- `AUTHENTIK_LISTEN__DEBUG`: Listening address:port (e.g. `0.0.0.0:9900`) for Go Debugging metrics (Applies to All)
- `AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS`: List of CIDRs that proxy headers should be accepted from (Applies to Server)
- `AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS`: List of comma-separated CIDRs that proxy headers should be accepted from (Applies to Server)
Defaults to `127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `fe80::/10`, `::1/128`.

View File

@ -0,0 +1,27 @@
# CVE-2024-23647
_Reported by [@pieterphilippaerts](https://github.com/pieterphilippaerts)_
## PKCE downgrade attack in authentik
## Summary
PKCE is a very important countermeasure in OAuth2 , both for public and confidential clients. It protects against CSRF attacks and code injection attacks. Because of this bug, an attacker can circumvent the protection PKCE offers.
## Patches
authentik 2023.8.7 and 2023.10.7 fix this issue.
## Details
There is a bug in our implementation of PKCE that allows an attacker to circumvent the protection that PKCE offers. PKCE adds the `code_challenge parameter to the authorization request and adds the `code_verifier parameter to the token request. We recently fixed a downgrade attack (in v2023.8.5 and 2023.10.4) where if the attacker removed the `code_verifier parameter in the token request, authentik would allow the request to pass, thus circumventing PKCEs protection. However, in the latest version of the software, another downgrade scenario is still possible: if the attacker removes the `code_challenge parameter from the authorization request, authentik will also not do the PKCE check.
Note that this type of downgrade enables an attacker to perform a code injection attack, even if the OAuth client is using PKCE (which is supposed to protect against code injection attacks). To start the attack, the attacker must initiate the authorization process without that `code_challenge parameter in the authorization request. But this is easy to do (just use a phishing site or email to trick the user into clicking on a link that the attacker controls the authorization link without that `code_challenge parameter).
The OAuth BCP (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics) explicitly mentions this particular attack in section 2.1.1: “Authorization servers MUST mitigate PKCE Downgrade Attacks by ensuring that a token request containing a code_verifier parameter is accepted only if a code_challenge parameter was present in the authorization request, see Section 4.8.2 for details.”
## For more information
If you have any questions or comments about this advisory:
- Email us at [security@goauthentik.io](mailto:security@goauthentik.io)

View File

@ -407,6 +407,7 @@ const docsSidebar = {
},
items: [
"security/policy",
"security/CVE-2024-23647",
"security/CVE-2024-21637",
"security/CVE-2023-48228",
"security/GHSA-rjvp-29xq-f62w",