Skip to content

fix: handle Attr objects in config path navigation#21557

Open
alfonsodg wants to merge 1 commit intogoauthentik:mainfrom
alfonsodg:fix/attr-path-navigation
Open

fix: handle Attr objects in config path navigation#21557
alfonsodg wants to merge 1 commit intogoauthentik:mainfrom
alfonsodg:fix/attr-path-navigation

Conversation

@alfonsodg
Copy link
Copy Markdown

Problem

When environment variables with __ separators (e.g. AUTHENTIK_POSTGRESQL__HOST) are processed, they create nested config paths. If a YAML config file has already set a value at an intermediate path, that value is an Attr object. The path navigation functions then try to use dict operations on the Attr object, causing:

TypeError: 'Attr' object does not support item assignment

This affects Docker Swarm deployments where all config is passed via environment variables.

Fix

  • dict.py: Add _unwrap_attr() that extracts .value dict from Attr objects during path traversal
  • config.py: Update update(), get(), refresh(), get_keys() to handle non-Attr results

Changes: +36 lines, -9 lines across 2 files.

Testing

Tested on Docker Swarm 3-node cluster with env vars:
AUTHENTIK_POSTGRESQL__HOST, AUTHENTIK_POSTGRESQL__USER, AUTHENTIK_POSTGRESQL__NAME, AUTHENTIK_POSTGRESQL__PASSWORD, AUTHENTIK_REDIS__HOST, AUTHENTIK_SECRET_KEY

PostgreSQL 18 + Valkey 8.

When environment variables with __ separators (e.g. AUTHENTIK_POSTGRESQL__HOST)
are processed, they create nested config paths. If a YAML config file has already
set a value at an intermediate path, that value is an Attr object. The path
navigation functions (get_path_from_dict, set_path_in_dict) then try to use
dict operations on the Attr object, causing:

  TypeError: 'Attr' object does not support item assignment

This commit adds _unwrap_attr() to dict.py that extracts the .value dict from
Attr objects during path traversal, and updates config.py methods (update, get,
refresh, get_keys) to handle cases where path navigation returns a dict instead
of an Attr.

Tested with Docker Swarm using env vars:
  AUTHENTIK_POSTGRESQL__HOST, AUTHENTIK_POSTGRESQL__USER,
  AUTHENTIK_POSTGRESQL__NAME, AUTHENTIK_POSTGRESQL__PASSWORD,
  AUTHENTIK_REDIS__HOST, AUTHENTIK_SECRET_KEY
@alfonsodg alfonsodg requested a review from a team as a code owner April 13, 2026 00:42
Copilot AI review requested due to automatic review settings April 13, 2026 00:42
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 13, 2026

Deploy Preview for authentik-docs ready!

Name Link
🔨 Latest commit cd395b2
🔍 Latest deploy log https://app.netlify.com/projects/authentik-docs/deploys/69dc3c0e960257000807e102
😎 Deploy Preview https://deploy-preview-21557--authentik-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@rissson
Copy link
Copy Markdown
Member

rissson commented Apr 13, 2026

What's the scenario where this happens?

@alfonsodg
Copy link
Copy Markdown
Author

Scenario

Docker Swarm passes all configuration via environment variables with __ as path separator. For example:

AUTHENTIK_POSTGRESQL__HOST=authentik-db
AUTHENTIK_POSTGRESQL__USER=authentik
AUTHENTIK_POSTGRESQL__NAME=authentik
AUTHENTIK_POSTGRESQL__PASSWORD=secret
AUTHENTIK_REDIS__HOST=authentik-valkey
AUTHENTIK_SECRET_KEY=my-secret-key

Unlike Docker Compose or Kubernetes, Swarm does not support mounting config files into services easily — environment variables are the standard way to configure services.

What happens

  1. authentik/lib/config.py loads the default YAML config (default.yml), which creates nested Attr objects for paths like postgresql.host
  2. Then refresh() processes environment variables and calls set_path_in_dict() to set postgresql.host = authentik-db
  3. set_path_in_dict() walks the path: at postgresql, it finds an Attr object (from the YAML load), not a plain dict
  4. It tries root["host"] = value on the Attr object → TypeError: 'Attr' object does not support item assignment

Reproduction

# Docker Swarm (no compose file, pure env vars)
docker service create --name authentik-server \
  -e AUTHENTIK_POSTGRESQL__HOST=db \
  -e AUTHENTIK_POSTGRESQL__PASSWORD=secret \
  -e AUTHENTIK_SECRET_KEY=key \
  ghcr.io/goauthentik/server:2024.8.3

The server crashes on startup with the TypeError.

Why this doesn't happen in Docker Compose

In Docker Compose, you typically mount config.yml which sets all values as plain dicts. The env vars only override leaf values, so the path traversal never encounters an Attr at an intermediate node. In Swarm, there's no config file — everything comes from env vars, and the default YAML creates Attr objects at intermediate paths.

@rissson
Copy link
Copy Markdown
Member

rissson commented Apr 13, 2026

I feel like I'm talking with an LLM so why doesn't this happen with setting AUTHENTIK_POSTGRESQL__PASSWORD=file:///secrets/postgres-password?

@alfonsodg
Copy link
Copy Markdown
Author

Ha, fair enough on the LLM comment — I was trying to be thorough but I see how it reads that
way.

To your question: using file:/// doesn't help here because the crash happens before the
value is even parsed. The TypeError is in set_path_in_dict when it walks the path
postgresql.host — at the postgresql node it finds an Attr (from default.yml) and tries to do
root["host"] = value on it. It blows up there regardless of whether the value is "db" or
"file:///some/path".

You can reproduce it with just AUTHENTIK_POSTGRESQL__HOST=db — no secrets involved. The
issue is that Attr wraps the default dict but doesn't support item assignment, so any env
var with __ that targets a path already defined in default.yml will crash.

@rissson
Copy link
Copy Markdown
Member

rissson commented Apr 14, 2026

The thing is I have seen many multiple setup where configuration is done through both the default.yml file, another .yml file, and then overrides with environment variables on top, and I've never encountered this.

So without a minimal reproduction, this won't go anywhere.

alfonsodg added a commit to ccvass/swarmex that referenced this pull request Apr 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants