authelia / authelia

The Single Sign-On Multi-Factor portal for web apps
https://www.authelia.com
Apache License 2.0
21.33k stars 1.1k forks source link

Support for multiple configuration files #646

Closed jamescurtin closed 3 years ago

jamescurtin commented 4 years ago

Thanks for your work on this great project!

I'm believe that it would be valuable to split configuration across multiple files: this would be in addition to the support for storing passwords in environment variables.

The use case is to keep deployment specific parameters (e.g. access control rules) separate from parameters that would be re-used across deployments (e.g. port number, regulation options, etc.)


Ideally, I could structure my project like:

my_authelia_project
├── authelia.env
├── config
│   ├── base.yml
│   ├── deployment1.yml
│   └── deployment2.yml
├── ...
└── docker-compose.yml

And have the docker-compose file (contrived example, but you get the idea):

version: "3.7"

services:
  authelia1: &auth
    command: ./authelia --config /etc/authelia/config
    env_file:
      - authelia.env
    image: authelia/authelia:4
    volumes:
      - ./config/base.yml:/etc/authelia/config/base.yml
      - ./config/deployment1.yml:/etc/authelia/config/deployment1.yml

  authelia2:
    <<: *auth
    volumes:
      - ./config/base.yml:/etc/authelia/config/base.yml
      - ./config/deployment2.yml:/etc/authelia/config/deployment2.yml

What do you think? Because the project users viper, this should be a fairly straightforward addition; would just need to check if the --config path was a directory, and if so iterate through the directory contents, using an approach like the one suggested here.

clems4ever commented 4 years ago

Hello @jamescurtin , I agree that splitting the config file could be handy in some circumstances. Clearly, access control rules could be in a dedicated file. It'll make even more sense when Authelia have its own set of custom resources for Kubernetes which will definitely come at some point

Splitting the config file is definitely a nice to have for me at the moment and therefore I'm assigning a low priority to that issue. So unless someone provide specs and implement it, it'll wait the stream on Kubernetes custom resources. Stay tuned.

james-d-elliott commented 4 years ago

I think the simplest implementation of this that would likely be the most effective is to have --config specify a file, and --config-dir specify a dir. One and only one of these options must be defined.

For config dir it would consider all yml's in a dir, and expect all of them to have root config values. One caveat to this is overlapping config could be an issue.

For example if you wanted session in mysession.yml it would have to contain etc:

session:
    domain: example.com

myrules.yml could contain:

access_control:
    default_policy: deny

    rules:
    - domain: public.example.com
      policy: bypass

    - domain: secure.example.com
      policy: one_factor
      networks:
      - 192.168.1.0/24

    - domain: secure.example.com
      policy: two_factor

    - domain: singlefactor.example.com
      policy: one_factor

    - domain: "mx2.mail.example.com"
      subject: "group:admins"
      policy: deny

    - domain: "*.example.com"
      subject: "group:admins"
      policy: two_factor

    - domain: dev.example.com
      resources:
      - "^/groups/dev/.*$"
      subject: "group:dev"
      policy: two_factor

    - domain: dev.example.com
      resources:
      - "^/users/john/.*$"
      subject: "user:john"
      policy: two_factor
james-d-elliott commented 3 years ago

@jamescurtin this is now implemented! It works by just adding more --config options. They are processed in order, each of them can override settings from all previous ones if they have the same keys.

orens commented 3 months ago

A bit late to the party, but I had a similar problem. My aim, however, was to split the rules section between domain-specific (site-specific) config folders. The documentation states

If duplicate keys are specified the last one to be specified is the one that takes precedence

and

if you have two files that specify these sections and expect them to merge properly you are asking for trouble

I found the following solution. I hope it may help others with the same problem.

The solution

I built on the Go templates mechanism. I included the following snippet in my configuration.yml:

access_control:
  default_policy: deny
  rules:
{{- range mustEnv "HOMELAB_AUTHELIA_FRAGMENTS" | split ":" }} 
{{-  if hasPrefix  "rules." (base .) }}
{{-   fileContent . | expandenv | nindent 4 }} 
{{-  end}}
{{- end}} 

I also added an environment variable that includes a colon-separated list of all the names of files containing authelia "fragment files": files named *.authelia.fragment. Of which I only use the ones called rules.authelia.fragment. I do the same for the networks part of the configuration with files named networks.authelia.fragment and can do the same for any other part of the configuration I wish to make by collecting many site-specific files.

The env. variable HOMELAB_AUTHELIA_FRAGMENTS is a bit of pain to populate. That's why I also added an override to the entrypoint script for docker compose:

services:
  authelia:
    image: authelia/authelia:latest
    volumes:
      - ./config:/config
      - ./extend_env_entrypoint.sh:/extend-env-entrypoint.sh
      - ./services:/services
    entrypoint: ["/extend-env-entrypoint.sh"]
...

This script collects all these names:

#!/usr/bin/env sh
set -eu
set -o pipefail

export HOMELAB_AUTHELIA_FRAGMENTS="$(find /services -name "*.authelia.fragment" | head -c -1 | tr -s "\n" ':' )"
exec /app/entrypoint.sh "$@"

(the call to head -c -1 there is only to remove the trailing newline)

Of course, I also have to map my base path for search to /services container folder, which is done in the service's volumes section. That's it. And so far it works great. All I have to do when adding a new service to my reverse proxy is add a file named rules.authelia.fragment with the relevant rules configuration for it.

One other thing, thanks so much for all your work. It is a pleasure to work with authelia!

ZzenlD commented 2 months ago

Thank you for the instructions on how to combine multiple configuration files. I would like to save the access_control rules sorted by hosts for more clarity.

The docker-compose.yml looks like this:

version: "3.9"
services:
  authelia:
    image: authelia/authelia:4.38.9
    hostname: authelia
    volumes:
      - ./etc:/config
      - ./var:/var
    networks:
      - proxy
    environment:
      - TZ=Europe/Berlin
      - AUTHELIA_RULES_FILES=/config/rules.host1.yml:/config/rules.host2.yml
    restart: unless-stopped

networks:
  proxy:
    external:
      name: proxy

The access_control section in the configuration.yml looks like this:

access_control:
  default_policy: deny
  rules:
{{- range mustEnv "AUTHELIA_RULES_FILES" | split ":" }} 
{{-  if hasPrefix  "rules." (base .) }}
{{-   fileContent . | expandenv | nindent 4 }} 
{{-  end}}
{{- end}} 

For example, the rules.host1.yml looks like this:

- domain: traefik.local
  policy: one_factor
  subject:
    - group:admin

If I start the container with docker-compose up, I get the following error message:

[authelia] | time="2024-07-22T14:10:56+02:00" level=error msg="Configuration: failed to load configuration from file path(/config/configuration.yml) source: yaml: line 37: could not find expected ':'"

If I write the content of the rules.host1.yml directly into the configuration.yml, Authelia starts without an error message. Maybe someone sees my mistake and can help me, or is there a better solution for the problem in the meantime?

james-d-elliott commented 2 months ago

You can use the authelia config template command within the context of the container shell to debug it.

ZzenlD commented 2 months ago

Thanks for the tip, now it works. I had an authorization problem with SELinux.

Wouldn't it make sense to implement a possibility that Authelia automatically monitors a config folder and automatically loads the configuration from it?

The current workaround is not very nice, but quite “easy” to implement, so I think that a permanent implementation in Authelia should not be a huge effort or am I wrong?

Regardless, thanks for this great piece of software :)