Compare commits

...

4 Commits

Author SHA1 Message Date
72d67f65e5 release: 2023.8.4 2023-10-28 21:44:15 +02:00
ea75741ec2 security: fix oobe-flow reuse when akadmin is deleted (#7361)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
# Conflicts:
#	website/docs/releases/2023/v2023.10.md
2023-10-28 21:26:53 +02:00
aaa9b398f4 sources/ldap: fix inverted interpretation of FreeIPA nsaccountlock (cherry-pick #6877) (#6879)
sources/ldap: fix inverted interpretation of FreeIPA nsaccountlock (#6877)

sources/ldap: fix inverted interpretation of nsaccountlock

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L <jens@goauthentik.io>
2023-09-13 19:50:48 +02:00
d54d01b118 providers/saml: set WantAuthnRequestsSigned in metadata (cherry-pick #6851) (#6880)
providers/saml: set WantAuthnRequestsSigned in metadata (#6851)

Co-authored-by: Jens L <jens@goauthentik.io>
2023-09-13 19:50:41 +02:00
18 changed files with 144 additions and 17 deletions

View File

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

View File

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

View File

@ -0,0 +1,25 @@
# Generated by Django 4.2.6 on 2023-10-10 17:18
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0025_alter_flowstagebinding_evaluate_on_plan_and_more"),
]
operations = [
migrations.AlterModelOptions(
name="flow",
options={
"permissions": [
("export_flow", "Can export a Flow"),
("inspect_flow", "Can inspect a Flow's execution"),
("view_flow_cache", "View Flow's cache metrics"),
("clear_flow_cache", "Clear Flow's cache metrics"),
],
"verbose_name": "Flow",
"verbose_name_plural": "Flows",
},
),
]

View File

@ -0,0 +1,34 @@
# Generated by Django 4.2.6 on 2023-10-28 14:24
from django.apps.registry import Apps
from django.db import migrations
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def set_oobe_flow_authentication(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
from guardian.shortcuts import get_anonymous_user
Flow = apps.get_model("authentik_flows", "Flow")
User = apps.get_model("authentik_core", "User")
db_alias = schema_editor.connection.alias
users = User.objects.using(db_alias).exclude(username="akadmin")
try:
users = users.exclude(pk=get_anonymous_user().pk)
# pylint: disable=broad-except
except Exception: # nosec
pass
if users.exists():
Flow.objects.filter(slug="initial-setup").update(authentication="require_superuser")
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0026_alter_flow_options"),
]
operations = [
migrations.RunPython(set_oobe_flow_authentication),
]

View File

@ -171,6 +171,8 @@ class MetadataProcessor:
entity_descriptor, f"{{{NS_SAML_METADATA}}}IDPSSODescriptor" entity_descriptor, f"{{{NS_SAML_METADATA}}}IDPSSODescriptor"
) )
idp_sso_descriptor.attrib["protocolSupportEnumeration"] = NS_SAML_PROTOCOL idp_sso_descriptor.attrib["protocolSupportEnumeration"] = NS_SAML_PROTOCOL
if self.provider.verification_kp:
idp_sso_descriptor.attrib["WantAuthnRequestsSigned"] = "true"
signing_descriptor = self.get_signing_key_descriptor() signing_descriptor = self.get_signing_key_descriptor()
if signing_descriptor is not None: if signing_descriptor is not None:

View File

@ -12,7 +12,7 @@ from authentik.lib.xml import lxml_from_string
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.metadata import MetadataProcessor from authentik.providers.saml.processors.metadata import MetadataProcessor
from authentik.providers.saml.processors.metadata_parser import ServiceProviderMetadataParser from authentik.providers.saml.processors.metadata_parser import ServiceProviderMetadataParser
from authentik.sources.saml.processors.constants import NS_MAP from authentik.sources.saml.processors.constants import NS_MAP, NS_SAML_METADATA
class TestServiceProviderMetadataParser(TestCase): class TestServiceProviderMetadataParser(TestCase):
@ -55,6 +55,24 @@ class TestServiceProviderMetadataParser(TestCase):
schema = etree.XMLSchema(etree.parse("schemas/saml-schema-metadata-2.0.xsd")) # nosec schema = etree.XMLSchema(etree.parse("schemas/saml-schema-metadata-2.0.xsd")) # nosec
self.assertTrue(schema.validate(metadata)) self.assertTrue(schema.validate(metadata))
def test_schema_want_authn_requests_signed(self):
"""Test metadata generation with WantAuthnRequestsSigned"""
cert = create_test_cert()
provider = SAMLProvider.objects.create(
name=generate_id(),
authorization_flow=self.flow,
verification_kp=cert,
)
Application.objects.create(
name=generate_id(),
slug=generate_id(),
provider=provider,
)
request = self.factory.get("/")
metadata = lxml_from_string(MetadataProcessor(provider, request).build_entity_descriptor())
idp_sso_descriptor = metadata.findall(f"{{{NS_SAML_METADATA}}}IDPSSODescriptor")[0]
self.assertEqual(idp_sso_descriptor.attrib["WantAuthnRequestsSigned"], "true")
def test_simple(self): def test_simple(self):
"""Test simple metadata without Signing""" """Test simple metadata without Signing"""
metadata = ServiceProviderMetadataParser().parse(load_fixture("fixtures/simple.xml")) metadata = ServiceProviderMetadataParser().parse(load_fixture("fixtures/simple.xml"))

View File

@ -47,9 +47,11 @@ class FreeIPA(BaseLDAPSynchronizer):
return return
# For some reason, nsaccountlock is not defined properly in the schema as bool # For some reason, nsaccountlock is not defined properly in the schema as bool
# hence we get it as a list of strings # hence we get it as a list of strings
_is_active = str(self._flatten(attributes.get("nsaccountlock", ["FALSE"]))) _is_locked = str(self._flatten(attributes.get("nsaccountlock", ["FALSE"])))
# So we have to attempt to convert it to a bool # So we have to attempt to convert it to a bool
is_active = _is_active.lower() == "true" is_locked = _is_locked.lower() == "true"
# And then invert it since freeipa saves locked and we save active
is_active = not is_locked
if is_active != user.is_active: if is_active != user.is_active:
user.is_active = is_active user.is_active = is_active
user.save() user.save()

View File

@ -137,6 +137,7 @@ class LDAPSyncTests(TestCase):
user_sync.sync_full() user_sync.sync_full()
self.assertTrue(User.objects.filter(username="user0_sn").exists()) self.assertTrue(User.objects.filter(username="user0_sn").exists())
self.assertFalse(User.objects.filter(username="user1_sn").exists()) self.assertFalse(User.objects.filter(username="user1_sn").exists())
self.assertFalse(User.objects.get(username="user-nsaccountlock").is_active)
def test_sync_groups_ad(self): def test_sync_groups_ad(self):
"""Test group sync""" """Test group sync"""

View File

@ -85,6 +85,19 @@ entries:
identifiers: identifiers:
name: default-oobe-password-usable name: default-oobe-password-usable
model: authentik_policies_expression.expressionpolicy model: authentik_policies_expression.expressionpolicy
- attrs:
expression: |
# This policy ensures that the setup flow can only be
# used one time
from authentik.flows.models import Flow, FlowAuthenticationRequirement
Flow.objects.filter(slug="initial-setup").update(
authentication=FlowAuthenticationRequirement.REQUIRE_SUPERUSER,
)
return True
id: policy-default-oobe-flow-set-authentication
identifiers:
name: default-oobe-flow-set-authentication
model: authentik_policies_expression.expressionpolicy
- attrs: - attrs:
fields: fields:
- !KeyOf prompt-field-header - !KeyOf prompt-field-header
@ -129,6 +142,7 @@ entries:
evaluate_on_plan: true evaluate_on_plan: true
invalid_response_action: retry invalid_response_action: retry
re_evaluate_policies: false re_evaluate_policies: false
id: binding-login
identifiers: identifiers:
order: 100 order: 100
stage: !KeyOf stage-default-authentication-login stage: !KeyOf stage-default-authentication-login
@ -144,3 +158,8 @@ entries:
policy: !KeyOf policy-default-oobe-prefill-user policy: !KeyOf policy-default-oobe-prefill-user
target: !KeyOf binding-password-write target: !KeyOf binding-password-write
model: authentik_policies.policybinding model: authentik_policies.policybinding
- identifiers:
order: 0
policy: !KeyOf policy-default-oobe-flow-set-authentication
target: !KeyOf binding-login
model: authentik_policies.policybinding

View File

@ -42,9 +42,3 @@ entries:
user: !KeyOf admin-user user: !KeyOf admin-user
attrs: attrs:
key: !Context token key: !Context token
- model: authentik_blueprints.blueprintinstance
identifiers:
metadata:
labels:
blueprints.goauthentik.io/system-bootstrap: "true"
state: absent

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -155,6 +155,10 @@ image:
- web: don't import entire SourceViewPage in flow and user interface (#6761) - web: don't import entire SourceViewPage in flow and user interface (#6761)
- web: replace ampersand (#6737) - web: replace ampersand (#6737)
## Fixed in 2023.8.4
- \*: fix [GHSA-rjvp-29xq-f62w](../security/GHSA-rjvp-29xq-f62w), Reported by [@devSparkle](https://github.com/devSparkle)
## API Changes ## API Changes
#### What's New #### What's New

View File

@ -0,0 +1,27 @@
# GHSA-rjvp-29xq-f62w
_Reported by [@devSparkle](https://github.com/devSparkle)_
## Potential Installation takeover when default admin user is deleted
### Summary
In the affected versions, when the default admin user has been deleted, it is potentially possible for an attacker to set the password of the default admin user without any authentication.
### Patches
authentik 2023.8.4 and 2023.10.2 fix this issue, for other versions the workaround can be used.
### Impact
authentik uses a blueprint to create the default admin user, which can also optionally set the default admin users' password from an environment variable. When the user is deleted, the `initial-setup` flow used to configure authentik after the first installation becomes available again.
### Workarounds
Ensure the default admin user (Username `akadmin`) exists and has a password set. It is recommended to use a very strong password for this user, and store it in a secure location like a password manager. It is also possible to deactivate the user to prevent any logins as akadmin.
### 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

@ -362,6 +362,7 @@ const docsSidebar = {
}, },
items: [ items: [
"security/policy", "security/policy",
"security/GHSA-rjvp-29xq-f62w",
"security/CVE-2023-39522", "security/CVE-2023-39522",
"security/CVE-2023-36456", "security/CVE-2023-36456",
"security/2023-06-cure53", "security/2023-06-cure53",