root: migrate bootstrap to blueprints (#6433)

* remove old bootstrap

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* add meta model to set user password

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* ensure KeyOf works with objects in the state of created that already exist

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* migrate

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add support for shorter form !If tag

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* allow !Context to resolve other yaml tags

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* don't require serializer to be valid for deleting an object

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix check if a model is being created

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove duplicate way to set password

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* migrate token

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* only change what is required with migrations

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add description

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix admin status

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* expand tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* don't require bootstrap in events to fix ci?

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L 2023-07-31 19:34:46 +02:00 committed by GitHub
parent 5139656e95
commit 10b0c84d97
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 114 additions and 97 deletions

View file

@ -31,7 +31,8 @@
"!Format sequence",
"!Condition sequence",
"!Env sequence",
"!Env scalar"
"!Env scalar",
"!If sequence"
],
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.importModuleSpecifierEnding": "index",

View file

@ -7,7 +7,5 @@ entries:
state: absent
- identifiers:
name: "%(id)s"
expression: |
return True
model: authentik_policies_expression.expressionpolicy
state: absent

View file

@ -9,6 +9,8 @@ context:
mapping:
key1: value
key2: 2
context1: context-nested-value
context2: !Context context1
entries:
- model: !Format ["%s", authentik_sources_oauth.oauthsource]
state: !Format ["%s", present]
@ -97,6 +99,7 @@ entries:
[list, with, items, !Format ["foo-%s", !Context foo]],
]
if_true_simple: !If [!Context foo, true, text]
if_short: !If [!Context foo]
if_false_simple: !If [null, false, 2]
enumerate_mapping_to_mapping: !Enumerate [
!Context mapping,
@ -141,6 +144,7 @@ entries:
]
]
]
nested_context: !Context context2
identifiers:
name: test
conditions:

View file

@ -155,6 +155,7 @@ class TestBlueprintsV1(TransactionTestCase):
},
"if_false_complex": ["list", "with", "items", "foo-bar"],
"if_true_simple": True,
"if_short": True,
"if_false_simple": 2,
"enumerate_mapping_to_mapping": {
"prefix-key1": "other-prefix-value",
@ -211,6 +212,7 @@ class TestBlueprintsV1(TransactionTestCase):
],
},
},
"nested_context": "context-nested-value",
}
)
)

View file

@ -249,6 +249,8 @@ class Context(YAMLTag):
value = self.default
if self.key in blueprint.context:
value = blueprint.context[self.key]
if isinstance(value, YAMLTag):
return value.resolve(entry, blueprint)
return value
@ -372,6 +374,10 @@ class If(YAMLTag):
def __init__(self, loader: "BlueprintLoader", node: SequenceNode) -> None:
super().__init__()
self.condition = loader.construct_object(node.value[0])
if len(node.value) == 1:
self.when_true = True
self.when_false = False
else:
self.when_true = loader.construct_object(node.value[1])
self.when_false = loader.construct_object(node.value[2])

View file

@ -199,9 +199,6 @@ class Importer:
serializer_kwargs = {}
model_instance = existing_models.first()
if not isinstance(model(), BaseMetaModel) and model_instance:
if entry.get_state(self.__import) == BlueprintEntryDesiredState.CREATED:
self.logger.debug("instance exists, skipping")
return None
self.logger.debug(
"initialise serializer with instance",
model=model,
@ -268,21 +265,34 @@ class Importer:
try:
serializer = self._validate_single(entry)
except EntryInvalidError as exc:
# For deleting objects we don't need the serializer to be valid
if entry.get_state(self.__import) == BlueprintEntryDesiredState.ABSENT:
continue
self.logger.warning(f"entry invalid: {exc}", entry=entry, error=exc)
return False
if not serializer:
continue
state = entry.get_state(self.__import)
if state in [
BlueprintEntryDesiredState.PRESENT,
BlueprintEntryDesiredState.CREATED,
]:
model = serializer.save()
if state in [BlueprintEntryDesiredState.PRESENT, BlueprintEntryDesiredState.CREATED]:
instance = serializer.instance
if (
instance
and not instance._state.adding
and state == BlueprintEntryDesiredState.CREATED
):
self.logger.debug(
"instance exists, skipping",
model=model,
instance=instance,
pk=instance.pk,
)
else:
instance = serializer.save()
self.logger.debug("updated model", model=instance)
if "pk" in entry.identifiers:
self.__pk_map[entry.identifiers["pk"]] = model.pk
entry._state = BlueprintEntryState(model)
self.logger.debug("updated model", model=model)
self.__pk_map[entry.identifiers["pk"]] = instance.pk
entry._state = BlueprintEntryState(instance)
elif state == BlueprintEntryDesiredState.ABSENT:
instance: Optional[Model] = serializer.instance
if instance.pk:
@ -309,5 +319,6 @@ class Importer:
self.logger.debug("Blueprint validation failed")
for log in logs:
getattr(self.logger, log.get("log_level"))(**log)
self.logger.debug("Finished blueprint import validation")
self.__import = orig_import
return successful, logs

View file

@ -1,55 +1,11 @@
# Generated by Django 3.2.8 on 2021-10-10 16:16
from os import environ
import django.db.models.deletion
from django.apps.registry import Apps
from django.conf import settings
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
import authentik.core.models
def create_default_user(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
from django.contrib.auth.hashers import make_password
User = apps.get_model("authentik_core", "User")
db_alias = schema_editor.connection.alias
akadmin, _ = User.objects.using(db_alias).get_or_create(
username="akadmin",
email=environ.get("AUTHENTIK_BOOTSTRAP_EMAIL", "root@localhost"),
name="authentik Default Admin",
)
password = None
if "TF_BUILD" in environ or settings.TEST:
password = "akadmin" # noqa # nosec
if "AUTHENTIK_BOOTSTRAP_PASSWORD" in environ:
password = environ["AUTHENTIK_BOOTSTRAP_PASSWORD"]
if password:
akadmin.password = make_password(password)
else:
akadmin.password = make_password(None)
akadmin.save()
def create_default_admin_group(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Group = apps.get_model("authentik_core", "Group")
User = apps.get_model("authentik_core", "User")
# Creates a default admin group
group, _ = Group.objects.using(db_alias).get_or_create(
is_superuser=True,
defaults={
"name": "authentik Admins",
},
)
group.users.set(User.objects.filter(username="akadmin"))
group.save()
class Migration(migrations.Migration):
replaces = [
("authentik_core", "0002_auto_20200523_1133"),
@ -119,9 +75,6 @@ class Migration(migrations.Migration):
model_name="user",
name="is_staff",
),
migrations.RunPython(
code=create_default_user,
),
migrations.AddField(
model_name="user",
name="is_superuser",
@ -201,9 +154,6 @@ class Migration(migrations.Migration):
default=False, help_text="Users added to this group will be superusers."
),
),
migrations.RunPython(
code=create_default_admin_group,
),
migrations.AlterModelManagers(
name="user",
managers=[

View file

@ -1,7 +1,6 @@
# Generated by Django 3.2.8 on 2021-10-10 16:12
import uuid
from os import environ
import django.db.models.deletion
from django.apps.registry import Apps
@ -35,29 +34,6 @@ def fix_duplicates(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
Token.objects.using(db_alias).filter(identifier=ident["identifier"]).delete()
def create_default_user_token(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
from authentik.core.models import TokenIntents
User = apps.get_model("authentik_core", "User")
Token = apps.get_model("authentik_core", "Token")
db_alias = schema_editor.connection.alias
akadmin = User.objects.using(db_alias).filter(username="akadmin")
if not akadmin.exists():
return
if "AUTHENTIK_BOOTSTRAP_TOKEN" not in environ:
return
key = environ["AUTHENTIK_BOOTSTRAP_TOKEN"]
Token.objects.using(db_alias).create(
identifier="authentik-bootstrap-token",
user=akadmin.first(),
intent=TokenIntents.INTENT_API,
expiring=False,
key=key,
)
class Migration(migrations.Migration):
replaces = [
("authentik_core", "0018_auto_20210330_1345"),
@ -214,9 +190,6 @@ class Migration(migrations.Migration):
"verbose_name_plural": "Authenticated Sessions",
},
),
migrations.RunPython(
code=create_default_user_token,
),
migrations.AlterField(
model_name="token",
name="intent",

View file

@ -44,7 +44,11 @@ def config_loggers(*args, **kwargs):
def after_task_publish_hook(sender=None, headers=None, body=None, **kwargs):
"""Log task_id after it was published"""
info = headers if "task" in headers else body
LOGGER.info("Task published", task_id=info.get("id", ""), task_name=info.get("task", ""))
LOGGER.info(
"Task published",
task_id=info.get("id", "").replace("-", ""),
task_name=info.get("task", ""),
)
@task_prerun.connect
@ -59,7 +63,9 @@ def task_prerun_hook(task_id: str, task, *args, **kwargs):
def task_postrun_hook(task_id, task, *args, retval=None, state=None, **kwargs):
"""Log task_id on worker"""
CTX_TASK_ID.set(...)
LOGGER.info("Task finished", task_id=task_id, task_name=task.__name__, state=state)
LOGGER.info(
"Task finished", task_id=task_id.replace("-", ""), task_name=task.__name__, state=state
)
@task_failure.connect

View file

@ -2,6 +2,12 @@ version: 1
metadata:
name: Default - Events Transport & Rules
entries:
# Run bootstrap blueprint first to ensure we have the group created
- model: authentik_blueprints.metaapplyblueprint
attrs:
identifiers:
path: system/bootstrap.yaml
required: false
- model: authentik_events.notificationtransport
id: default-email-transport
attrs:
@ -16,6 +22,7 @@ entries:
name: default-local-transport
- model: authentik_core.group
id: group
state: created
identifiers:
name: authentik Admins

View file

@ -1,8 +1,8 @@
version: 1
metadata:
name: Migration - Remove old prompt fields
labels:
blueprints.goauthentik.io/description: Migrate to 2023.2, remove unused prompt fields
name: Migration - Remove old prompt fields
entries:
- model: authentik_stages_prompt.prompt
identifiers:

View file

@ -0,0 +1,49 @@
version: 1
metadata:
name: authentik Bootstrap
labels:
blueprints.goauthentik.io/system-bootstrap: "true"
blueprints.goauthentik.io/system: "true"
blueprints.goauthentik.io/description: |
This blueprint configures the default admin user and group, and configures them for the [Automated install](https://goauthentik.io/docs/installation/automated-install).
context:
username: akadmin
group_name: authentik Admins
email: !Env [AUTHENTIK_BOOTSTRAP_EMAIL, "root@example.com"]
password: !Env [AUTHENTIK_BOOTSTRAP_PASSWORD, null]
token: !Env [AUTHENTIK_BOOTSTRAP_TOKEN, null]
entries:
- model: authentik_core.group
state: created
identifiers:
name: !Context group_name
attrs:
is_superuser: true
id: admin-group
- model: authentik_core.user
state: created
id: admin-user
identifiers:
username: !Context username
attrs:
name: authentik Default Admin
email: !Context email
groups:
- !KeyOf admin-group
password: !Context password
- model: authentik_core.token
state: created
conditions:
- !If [!Context token]
identifiers:
identifier: authentik-bootstrap-token
intent: api
expiring: false
key: !Context token
user: !KeyOf admin-user
- model: authentik_blueprints.blueprintinstance
identifiers:
metadata:
labels:
blueprints.goauthentik.io/system-bootstrap: "true"
state: absent

View file

@ -49,7 +49,17 @@ Format a string using python's % formatting. First argument is the format string
Minimal example:
`required: !If [true, true, false] # !If [<condition>, <when true>, <when false>`
```yaml
# Short form
# !If [<condition>]
required: !If [true]
```
```yaml
# Longer form
# !If [<condition>, <when true>, <when false>]
required: !If [true, true, false]
```
Full example: