From 0130edb870137b86ceb686505616796bd20c9b1a Mon Sep 17 00:00:00 2001 From: James Elliott Date: Fri, 23 Dec 2022 11:03:50 +1100 Subject: [PATCH] feat(configuration): env config file discovery (#4618) This allows Authelia to discover config files and config options via environment variables. Co-authored-by: Amir Zarrinkafsh --- Dockerfile | 8 ++-- Dockerfile.coverage | 25 +++++------ Dockerfile.dev | 26 ++++++------ .../content/en/configuration/methods/files.md | 15 ++++--- internal/commands/const.go | 42 ++++++++++++++++++- internal/commands/context.go | 2 +- internal/commands/root.go | 7 ++-- internal/commands/util.go | 4 +- .../compose/authelia/Dockerfile.backend | 3 +- .../compose/authelia/Dockerfile.frontend | 1 - .../authelia/docker-compose.frontend.dev.yml | 2 +- .../authelia/resources/run-backend-dev.sh | 2 +- internal/suites/suite_cli_test.go | 4 +- 13 files changed, 94 insertions(+), 47 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7bb7a5497..31728093a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,15 +11,16 @@ WORKDIR /app # Set environment variables ENV PATH="/app:${PATH}" \ PUID=0 \ - PGID=0 + PGID=0 \ + X_AUTHELIA_CONFIG="/config/configuration.yml" RUN \ -apk --no-cache add ca-certificates su-exec tzdata + apk --no-cache add ca-certificates su-exec tzdata COPY LICENSE .healthcheck.env entrypoint.sh healthcheck.sh ./ RUN \ -chmod 0666 /app/.healthcheck.env + chmod 0666 /app/.healthcheck.env COPY authelia-${TARGETOS}-${TARGETARCH}-musl ./authelia @@ -28,5 +29,4 @@ EXPOSE 9091 VOLUME /config ENTRYPOINT ["/app/entrypoint.sh"] -CMD ["--config", "/config/configuration.yml"] HEALTHCHECK --interval=30s --timeout=3s --start-period=1m CMD /app/healthcheck.sh diff --git a/Dockerfile.coverage b/Dockerfile.coverage index de77fcef9..e37244997 100644 --- a/Dockerfile.coverage +++ b/Dockerfile.coverage @@ -20,14 +20,14 @@ FROM golang:1.19.4-alpine AS builder-backend WORKDIR /go/src/app RUN \ -echo ">> Downloading required apk's..." && \ -apk --no-cache add gcc musl-dev + echo ">> Downloading required apk's..." && \ + apk --no-cache add gcc musl-dev COPY go.mod go.sum ./ RUN \ -echo ">> Downloading go modules..." && \ -go mod download + echo ">> Downloading go modules..." && \ + go mod download COPY / ./ @@ -36,12 +36,12 @@ COPY --from=builder-frontend /node/src/internal/server/public_html internal/serv ARG LDFLAGS_EXTRA RUN \ -mv api internal/server/public_html/api && \ -cd cmd/authelia && \ -chmod 0666 /go/src/app/.healthcheck.env && \ -echo ">> Starting go build (coverage via go test)..." && \ -CGO_ENABLED=1 CGO_CPPFLAGS="-D_FORTIFY_SOURCE=2 -fstack-protector-strong" CGO_LDFLAGS="-Wl,-z,relro,-z,now" go test -c --tags coverage -covermode=atomic \ --ldflags "${LDFLAGS_EXTRA}" -o authelia -coverpkg github.com/authelia/authelia/... + mv api internal/server/public_html/api && \ + cd cmd/authelia && \ + chmod 0666 /go/src/app/.healthcheck.env && \ + echo ">> Starting go build (coverage via go test)..." && \ + CGO_ENABLED=1 CGO_CPPFLAGS="-D_FORTIFY_SOURCE=2 -fstack-protector-strong" CGO_LDFLAGS="-Wl,-z,relro,-z,now" go test -c --tags coverage -covermode=atomic \ + -ldflags "${LDFLAGS_EXTRA}" -o authelia -coverpkg github.com/authelia/authelia/... # =================================== # ===== Authelia official image ===== @@ -58,7 +58,8 @@ EXPOSE 9091 VOLUME /config -ENV PATH="/app:${PATH}" +ENV PATH="/app:${PATH}" \ + X_AUTHELIA_CONFIG="/config/configuration.yml" -CMD ["authelia", "-test.coverprofile=/authelia/coverage.txt", "COVERAGE", "--config", "/config/configuration.yml"] +CMD ["authelia", "-test.coverprofile=/authelia/coverage.txt", "COVERAGE"] HEALTHCHECK --interval=30s --timeout=3s CMD /app/healthcheck.sh diff --git a/Dockerfile.dev b/Dockerfile.dev index 5dab79868..bf8408f3f 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -18,14 +18,14 @@ FROM golang:1.19.4-alpine AS builder-backend WORKDIR /go/src/app RUN \ -echo ">> Downloading required apk's..." && \ -apk --no-cache add gcc musl-dev + echo ">> Downloading required apk's..." && \ + apk --no-cache add gcc musl-dev COPY go.mod go.sum ./ RUN \ -echo ">> Downloading go modules..." && \ -go mod download + echo ">> Downloading go modules..." && \ + go mod download COPY / ./ @@ -34,11 +34,11 @@ COPY --from=builder-frontend /node/src/internal/server/public_html internal/serv ARG LDFLAGS_EXTRA RUN \ -mv api internal/server/public_html/api && \ -chmod 0666 /go/src/app/.healthcheck.env && \ -echo ">> Starting go build..." && \ -CGO_ENABLED=1 CGO_CPPFLAGS="-D_FORTIFY_SOURCE=2 -fstack-protector-strong" CGO_LDFLAGS="-Wl,-z,relro,-z,now" go build \ --ldflags "-linkmode=external -s -w ${LDFLAGS_EXTRA}" -trimpath -buildmode=pie -o authelia ./cmd/authelia + mv api internal/server/public_html/api && \ + chmod 0666 /go/src/app/.healthcheck.env && \ + echo ">> Starting go build..." && \ + CGO_ENABLED=1 CGO_CPPFLAGS="-D_FORTIFY_SOURCE=2 -fstack-protector-strong" CGO_LDFLAGS="-Wl,-z,relro,-z,now" go build \ + -ldflags "-linkmode=external -s -w ${LDFLAGS_EXTRA}" -trimpath -buildmode=pie -o authelia ./cmd/authelia # =================================== # ===== Authelia official image ===== @@ -50,20 +50,20 @@ WORKDIR /app # Set environment variables ENV PATH="/app:${PATH}" \ PUID=0 \ - PGID=0 + PGID=0 \ + X_AUTHELIA_CONFIG="/config/configuration.yml" RUN \ -apk --no-cache add ca-certificates su-exec tzdata + apk --no-cache add ca-certificates su-exec tzdata COPY --from=builder-backend /go/src/app/authelia /go/src/app/LICENSE /go/src/app/entrypoint.sh /go/src/app/healthcheck.sh /go/src/app/.healthcheck.env ./ RUN \ -chmod 0666 /app/.healthcheck.env + chmod 0666 /app/.healthcheck.env EXPOSE 9091 VOLUME /config ENTRYPOINT ["/app/entrypoint.sh"] -CMD ["--config", "/config/configuration.yml"] HEALTHCHECK --interval=30s --timeout=3s --start-period=1m CMD /app/healthcheck.sh diff --git a/docs/content/en/configuration/methods/files.md b/docs/content/en/configuration/methods/files.md index fc310195f..ea195a15b 100644 --- a/docs/content/en/configuration/methods/files.md +++ b/docs/content/en/configuration/methods/files.md @@ -12,18 +12,23 @@ weight: 101200 toc: true --- -## Loading Behaviour +## Loading Behaviour and Discovery There are several options which affect the loading of files: -| Name | Argument | Description | -|:-----------------:|:-------------------------------:|:----------------------------------------------------------------------------------:| -| Files/Directories | `--config`, `-c` | A list of file or directory (non-recursive) paths to load configuration files from | -| Filters | `--config.experimental.filters` | A list of filters applied to every file from the Files or Directories options | +| Name | Argument | Environment Variable | Description | +|:-----------------:|:-------------------------------:|:---------------------------:|:----------------------------------------------------------------------------------:| +| Files/Directories | `--config`, `-c` | `X_AUTHELIA_CONFIG` | A list of file or directory (non-recursive) paths to load configuration files from | +| Filters | `--config.experimental.filters` | `X_AUTHELIA_CONFIG_FILTERS` | A list of filters applied to every file from the Files or Directories options | __*Note:* when specifying directories and files, the individual files specified must not be within any of the directories specified.__ +Configuration options can be discovered via either the Argument or Environment Variable, but not both at the same time. +If both are specified the Argument takes precedence and the Environment Variable is ignored. It is generally recommended +that if you're using a container that you use the Environment Variable as this will allow you to execute other commands +from the context of the container more easily. + ## Formats The only supported configuration file format is [YAML](#yaml). diff --git a/internal/commands/const.go b/internal/commands/const.go index b7cd882e2..6a1512d48 100644 --- a/internal/commands/const.go +++ b/internal/commands/const.go @@ -537,9 +537,11 @@ const ( cmdConfigDefaultContainer = "/config/configuration.yml" cmdConfigDefaultDaemon = "/etc/authelia/configuration.yml" - cmdFlagNameConfig = "config" + cmdFlagNameConfig = "config" + cmdFlagEnvNameConfig = "X_AUTHELIA_CONFIG" cmdFlagNameConfigExpFilters = "config.experimental.filters" + cmdFlagEnvNameConfigFilters = "X_AUTHELIA_CONFIG_FILTERS" cmdFlagNameCharSet = "charset" cmdFlagValueCharSet = "alphanumeric" @@ -655,4 +657,42 @@ The following filters are available: 'expand-env' filter for example. For a full list of functions see: https://www.authelia.com/configuration/methods/files/#functions` + + helpTopicConfig = `Configuration can be specified in multiple layers where each layer is a different source from +the last. The layers are loaded in the order below where each layer potentially overrides the individual settings from +previous layers with the individual settings it provides (i.e. if the same setting is specified twice). + +Layers: + - File/Directory Paths + - Environment Variables + - Secrets + +File/Directory Paths: + + File/Directory Paths can be specified either via the '--config' CLI argument or the 'X_AUTHELIA_CONFIG' environment + variable. If both the environment variable AND the CLI argument are specified the environment variable is completely + ignored. These values both take lists separated by commas. + + Directories that are loaded via this method load all files with relevant extensions from the directory, this is not + recursive. This means all files with these extensions must be Authelia configuration files with valid syntax. + + The paths specified are loaded in order, where individual settings specified by later files potentially overrides + individual settings by later files (i.e. if the same setting is specified twice). Files specified Files in + directories are loaded in lexicographic order. + + The files loaded via this method can be interpolated or templated via the configuration filters. Read more about + this topic by running: authelia -h authelia filters + +Environment Variables: + + Most configuration options in Authelia can be specified via an environment variable. The available options and the + specific environment variable mapping can be found here: https://www.authelia.com/configuration/methods/environment/ + +Secrets: + + Some configuration options in Authelia can be specified via an environment variable which refers to the location of + a file; also known as a secret. Every configuration key that ends with the following strings can be loaded in this + way: 'key', 'secret', 'password', 'token'. + + The available options and the specific secret mapping can be found here: https://www.authelia.com/configuration/methods/secrets/` ) diff --git a/internal/commands/context.go b/internal/commands/context.go index 3487f8f5d..edb79bc1e 100644 --- a/internal/commands/context.go +++ b/internal/commands/context.go @@ -277,7 +277,7 @@ func (ctx *CmdCtx) ConfigEnsureExistsRunE(cmd *cobra.Command, _ []string) (err e result XEnvCLIResult ) - if configs, result, err = loadXEnvCLIStringSliceValue(cmd, "", cmdFlagNameConfig); err != nil { + if configs, result, err = loadXEnvCLIStringSliceValue(cmd, cmdFlagEnvNameConfig, cmdFlagNameConfig); err != nil { return err } diff --git a/internal/commands/root.go b/internal/commands/root.go index 5201b32f3..29a1bdc0d 100644 --- a/internal/commands/root.go +++ b/internal/commands/root.go @@ -45,9 +45,9 @@ func NewRootCmd() (cmd *cobra.Command) { DisableAutoGenTag: true, } - cmd.PersistentFlags().StringSliceP(cmdFlagNameConfig, "c", []string{"configuration.yml"}, "configuration files or directories to load") + cmd.PersistentFlags().StringSliceP(cmdFlagNameConfig, "c", []string{"configuration.yml"}, "configuration files or directories to load, for more information run 'authelia -h authelia config'") - cmd.PersistentFlags().StringSlice(cmdFlagNameConfigExpFilters, nil, "list of filters to apply to all configuration files, for more information: authelia --help authelia filters") + cmd.PersistentFlags().StringSlice(cmdFlagNameConfigExpFilters, nil, "list of filters to apply to all configuration files, for more information run 'authelia -h authelia filters'") cmd.AddCommand( newAccessControlCommand(ctx), @@ -56,7 +56,8 @@ func NewRootCmd() (cmd *cobra.Command) { newStorageCmd(ctx), newValidateConfigCmd(ctx), - newHelpTopic("filters", "Help for the config filters", helpTopicConfigFilters), + newHelpTopic("config", "Help for the config file/directory paths", helpTopicConfig), + newHelpTopic("filters", "help topic for the config filters", helpTopicConfigFilters), ) return cmd diff --git a/internal/commands/util.go b/internal/commands/util.go index 94c333b89..8d1eb95d4 100644 --- a/internal/commands/util.go +++ b/internal/commands/util.go @@ -246,7 +246,7 @@ func loadXEnvCLIConfigValues(cmd *cobra.Command) (configs []string, filters []co filterNames []string ) - if configs, _, err = loadXEnvCLIStringSliceValue(cmd, "", cmdFlagNameConfig); err != nil { + if configs, _, err = loadXEnvCLIStringSliceValue(cmd, cmdFlagEnvNameConfig, cmdFlagNameConfig); err != nil { return nil, nil, err } @@ -254,7 +254,7 @@ func loadXEnvCLIConfigValues(cmd *cobra.Command) (configs []string, filters []co return nil, nil, err } - if filterNames, _, err = loadXEnvCLIStringSliceValue(cmd, "", cmdFlagNameConfigExpFilters); err != nil { + if filterNames, _, err = loadXEnvCLIStringSliceValue(cmd, cmdFlagEnvNameConfigFilters, cmdFlagNameConfigExpFilters); err != nil { return nil, nil, err } diff --git a/internal/suites/example/compose/authelia/Dockerfile.backend b/internal/suites/example/compose/authelia/Dockerfile.backend index d7670ffb7..a5fe1c342 100644 --- a/internal/suites/example/compose/authelia/Dockerfile.backend +++ b/internal/suites/example/compose/authelia/Dockerfile.backend @@ -11,7 +11,8 @@ RUN mkdir -p /config && chown dev:dev /config USER dev -ENV PATH="/app:${PATH}" +ENV PATH="/app:${PATH}" \ + X_AUTHELIA_CONFIG="/config/configuration.yml" VOLUME /config diff --git a/internal/suites/example/compose/authelia/Dockerfile.frontend b/internal/suites/example/compose/authelia/Dockerfile.frontend index ef85b9b38..184f4a398 100644 --- a/internal/suites/example/compose/authelia/Dockerfile.frontend +++ b/internal/suites/example/compose/authelia/Dockerfile.frontend @@ -4,7 +4,6 @@ ARG USER_ID ARG GROUP_ID RUN yarn global add pnpm && \ - pnpm config set --global store-dir /tmp/.pnpm-store && \ deluser node && \ addgroup --gid ${GROUP_ID} dev && \ adduser --uid ${USER_ID} -G dev -D dev diff --git a/internal/suites/example/compose/authelia/docker-compose.frontend.dev.yml b/internal/suites/example/compose/authelia/docker-compose.frontend.dev.yml index cc7056037..98a7d3829 100644 --- a/internal/suites/example/compose/authelia/docker-compose.frontend.dev.yml +++ b/internal/suites/example/compose/authelia/docker-compose.frontend.dev.yml @@ -14,7 +14,7 @@ services: volumes: - './example/compose/authelia/resources/:/resources' - '../../web:/app' - - '~/.local/share/pnpm/store:/tmp/.pnpm-store' + - '~/.local/share/pnpm/store:/app/.pnpm-store' labels: # Traefik 1.x - 'traefik.frontend.rule=Host:login.example.com' diff --git a/internal/suites/example/compose/authelia/resources/run-backend-dev.sh b/internal/suites/example/compose/authelia/resources/run-backend-dev.sh index d58cce0f6..3134a7a35 100755 --- a/internal/suites/example/compose/authelia/resources/run-backend-dev.sh +++ b/internal/suites/example/compose/authelia/resources/run-backend-dev.sh @@ -4,6 +4,6 @@ set -e while true; do - AUTHELIA_SERVER_DISABLE_HEALTHCHECK=true CGO_ENABLED=1 dlv --listen 0.0.0.0:2345 --headless=true --output=./authelia --continue --accept-multiclient debug cmd/authelia/*.go -- --config /config/configuration.yml + AUTHELIA_SERVER_DISABLE_HEALTHCHECK=true CGO_ENABLED=1 dlv --listen 0.0.0.0:2345 --headless=true --output=./authelia --continue --accept-multiclient debug cmd/authelia/*.go sleep 10 done diff --git a/internal/suites/suite_cli_test.go b/internal/suites/suite_cli_test.go index a340e301b..715d1fb1a 100644 --- a/internal/suites/suite_cli_test.go +++ b/internal/suites/suite_cli_test.go @@ -10,7 +10,7 @@ import ( "testing" "github.com/stretchr/testify/suite" - yaml "gopkg.in/yaml.v3" + "gopkg.in/yaml.v3" "github.com/authelia/authelia/v4/internal/model" "github.com/authelia/authelia/v4/internal/storage" @@ -73,7 +73,7 @@ func (s *CLISuite) TestShouldPrintVersion() { } func (s *CLISuite) TestShouldValidateConfig() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "validate-config", "--config=/config/configuration.yml"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "validate-config"}) s.Assert().NoError(err) s.Assert().Contains(output, "Configuration parsed and loaded successfully without errors.") }