From aea5eb3419ccec57700528807c17288b47c0884c Mon Sep 17 00:00:00 2001
From: RMI78 <26175239+RMI78@users.noreply.github.com>
Date: Sun, 16 Feb 2025 11:59:50 -0500
Subject: [PATCH] feat: support `_FILE` suffix for docker secrets (again)
(#4958)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
---
docker/entry.sh | 38 ++++
.../installation/backend-config.md | 195 +++++++++++-------
2 files changed, 161 insertions(+), 72 deletions(-)
diff --git a/docker/entry.sh b/docker/entry.sh
index 38e4b5e20..cccc2ba9d 100644
--- a/docker/entry.sh
+++ b/docker/entry.sh
@@ -35,8 +35,46 @@ init() {
. /opt/mealie/bin/activate
}
+load_secrets() {
+ # Each of these environment variables will support a `_FILE` suffix that allows
+ # for setting the environment variable through the Docker Compose secret
+ # pattern.
+ local -a secret_supported_vars=(
+ "POSTGRES_USER"
+ "POSTGRES_PASSWORD"
+ "POSTGRES_SERVER"
+ "POSTGRES_PORT"
+ "POSTGRES_DB"
+ "POSTGRES_URL_OVERRIDE"
+
+ "SMTP_HOST"
+ "SMTP_PORT"
+ "SMTP_USER"
+ "SMTP_PASSWORD"
+
+ "LDAP_SERVER_URL"
+ "LDAP_QUERY_PASSWORD"
+
+ "OIDC_CONFIGURATION_URL"
+ "OIDC_CLIENT_ID"
+ "OIDC_CLIENT_SECRET"
+
+ "OPENAI_BASE_URL"
+ "OPENAI_API_KEY"
+ )
+
+ # If any secrets are set, prefer them over base environment variables.
+ for var in "${secret_supported_vars[@]}"; do
+ file_var="${var}_FILE"
+ if [ -n "${!file_var}" ]; then
+ export "$var=$(<"${!file_var}")"
+ fi
+ done
+}
+
change_user
init
+load_secrets
# Start API
HOST_IP=`/sbin/ip route|awk '/default/ { print $3 }'`
diff --git a/docs/docs/documentation/getting-started/installation/backend-config.md b/docs/docs/documentation/getting-started/installation/backend-config.md
index 6884bd6f7..819629979 100644
--- a/docs/docs/documentation/getting-started/installation/backend-config.md
+++ b/docs/docs/documentation/getting-started/installation/backend-config.md
@@ -31,27 +31,27 @@
### Database
-| Variables | Default | Description |
-| --------------------- | :------: | ----------------------------------------------------------------------- |
-| DB_ENGINE | sqlite | Optional: 'sqlite', 'postgres' |
-| POSTGRES_USER | mealie | Postgres database user |
-| POSTGRES_PASSWORD | mealie | Postgres database password |
-| POSTGRES_SERVER | postgres | Postgres database server address |
-| POSTGRES_PORT | 5432 | Postgres database port |
-| POSTGRES_DB | mealie | Postgres database name |
-| POSTGRES_URL_OVERRIDE | None | Optional Postgres URL override to use instead of POSTGRES\_\* variables |
+ | Variables | Default | Description |
+ | ------------------------------------------------------- | :------: | ----------------------------------------------------------------------- |
+ | DB_ENGINE | sqlite | Optional: 'sqlite', 'postgres' |
+ | POSTGRES_USER[†][secrets] | mealie | Postgres database user |
+ | POSTGRES_PASSWORD[†][secrets] | mealie | Postgres database password |
+ | POSTGRES_SERVER[†][secrets] | postgres | Postgres database server address |
+ | POSTGRES_PORT[†][secrets] | 5432 | Postgres database port |
+ | POSTGRES_DB[†][secrets] | mealie | Postgres database name |
+ | POSTGRES_URL_OVERRIDE[†][secrets] | None | Optional Postgres URL override to use instead of POSTGRES\_\* variables |
### Email
-| Variables | Default | Description |
-| ------------------ | :-----: | ------------------------------------------------- |
-| SMTP_HOST | None | Required For email |
-| SMTP_PORT | 587 | Required For email |
-| SMTP_FROM_NAME | Mealie | Required For email |
-| SMTP_AUTH_STRATEGY | TLS | Required For email, Options: 'TLS', 'SSL', 'NONE' |
-| SMTP_FROM_EMAIL | None | Required For email |
-| SMTP_USER | None | Required if SMTP_AUTH_STRATEGY is 'TLS' or 'SSL' |
-| SMTP_PASSWORD | None | Required if SMTP_AUTH_STRATEGY is 'TLS' or 'SSL' |
+| Variables | Default | Description |
+| ----------------------------------------------- | :-----: | ------------------------------------------------- |
+| SMTP_HOST[†][secrets] | None | Required For email |
+| SMTP_PORT[†][secrets] | 587 | Required For email |
+| SMTP_FROM_NAME | Mealie | Required For email |
+| SMTP_AUTH_STRATEGY | TLS | Required For email, Options: 'TLS', 'SSL', 'NONE' |
+| SMTP_FROM_EMAIL | None | Required For email |
+| SMTP_USER[†][secrets] | None | Required if SMTP_AUTH_STRATEGY is 'TLS' or 'SSL' |
+| SMTP_PASSWORD[†][secrets] | None | Required if SMTP_AUTH_STRATEGY is 'TLS' or 'SSL' |
### Webworker
@@ -72,21 +72,21 @@ Use this only when mealie is run without a webserver or reverse proxy.
### LDAP
-| Variables | Default | Description |
-| -------------------- | :-----: | ----------------------------------------------------------------------------------------------------------------------------------- |
-| LDAP_AUTH_ENABLED | False | Authenticate via an external LDAP server in addidion to built-in Mealie auth |
-| LDAP_SERVER_URL | None | LDAP server URL (e.g. ldap://ldap.example.com) |
-| LDAP_TLS_INSECURE | False | Do not verify server certificate when using secure LDAP |
-| LDAP_TLS_CACERTFILE | None | File path to Certificate Authority used to verify server certificate (e.g. `/path/to/ca.crt`) |
-| LDAP_ENABLE_STARTTLS | False | Optional. Use STARTTLS to connect to the server |
-| LDAP_BASE_DN | None | Starting point when searching for users authentication (e.g. `CN=Users,DC=xx,DC=yy,DC=de`) |
-| LDAP_QUERY_BIND | None | Optional bind user for LDAP search queries (e.g. `cn=admin,cn=users,dc=example,dc=com`). If `None` then anonymous bind will be used |
-| LDAP_QUERY_PASSWORD | None | Optional password for the bind user used in LDAP_QUERY_BIND |
-| LDAP_USER_FILTER | None | Optional LDAP filter to narrow down eligible users (e.g. `(memberOf=cn=mealie_user,dc=example,dc=com)`) |
-| LDAP_ADMIN_FILTER | None | Optional LDAP filter, which tells Mealie the LDAP user is an admin (e.g. `(memberOf=cn=admins,dc=example,dc=com)`) |
-| LDAP_ID_ATTRIBUTE | uid | The LDAP attribute that maps to the user's id |
-| LDAP_NAME_ATTRIBUTE | name | The LDAP attribute that maps to the user's name |
-| LDAP_MAIL_ATTRIBUTE | mail | The LDAP attribute that maps to the user's email |
+| Variables | Default | Description |
+| ----------------------------------------------------- | :-----: | ----------------------------------------------------------------------------------------------------------------------------------- |
+| LDAP_AUTH_ENABLED | False | Authenticate via an external LDAP server in addidion to built-in Mealie auth |
+| LDAP_SERVER_URL[†][secrets] | None | LDAP server URL (e.g. ldap://ldap.example.com) |
+| LDAP_TLS_INSECURE | False | Do not verify server certificate when using secure LDAP |
+| LDAP_TLS_CACERTFILE | None | File path to Certificate Authority used to verify server certificate (e.g. `/path/to/ca.crt`) |
+| LDAP_ENABLE_STARTTLS | False | Optional. Use STARTTLS to connect to the server |
+| LDAP_BASE_DN | None | Starting point when searching for users authentication (e.g. `CN=Users,DC=xx,DC=yy,DC=de`) |
+| LDAP_QUERY_BIND | None | Optional bind user for LDAP search queries (e.g. `cn=admin,cn=users,dc=example,dc=com`). If `None` then anonymous bind will be used |
+| LDAP_QUERY_PASSWORD[†][secrets] | None | Optional password for the bind user used in LDAP_QUERY_BIND |
+| LDAP_USER_FILTER | None | Optional LDAP filter to narrow down eligible users (e.g. `(memberOf=cn=mealie_user,dc=example,dc=com)`) |
+| LDAP_ADMIN_FILTER | None | Optional LDAP filter, which tells Mealie the LDAP user is an admin (e.g. `(memberOf=cn=admins,dc=example,dc=com)`) |
+| LDAP_ID_ATTRIBUTE | uid | The LDAP attribute that maps to the user's id |
+| LDAP_NAME_ATTRIBUTE | name | The LDAP attribute that maps to the user's name |
+| LDAP_MAIL_ATTRIBUTE | mail | The LDAP attribute that maps to the user's email |
### OpenID Connect (OIDC)
@@ -94,23 +94,22 @@ Use this only when mealie is run without a webserver or reverse proxy.
For usage, see [Usage - OpenID Connect](../authentication/oidc-v2.md)
-| Variables | Default | Description |
-|---------------------------------------------------|:-------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| OIDC_AUTH_ENABLED | False | Enables authentication via OpenID Connect |
-| OIDC_SIGNUP_ENABLED | True | Enables new users to be created when signing in for the first time with OIDC |
-| OIDC_CONFIGURATION_URL | None | The URL to the OIDC configuration of your provider. This is usually something like https://auth.example.com/.well-known/openid-configuration |
-| OIDC_CLIENT_ID | None | The client id of your configured client in your provider |
-| OIDC_CLIENT_SECRET
:octicons-tag-24: v2.0.0 | None | The client secret of your configured client in your provider |
-| OIDC_USER_GROUP | None | If specified, only users belonging to this group will be able to successfully authenticate. For more information see [this page](../authentication/oidc-v2.md#groups) |
-| OIDC_ADMIN_GROUP | None | If specified, users belonging to this group will be able to successfully authenticate *and* be made an admin. For more information see [this page](../authentication/oidc-v2.md#groups) |
-| OIDC_AUTO_REDIRECT | False | If `True`, then the login page will be bypassed and you will be sent directly to your Identity Provider. You can still get to the login page by adding `?direct=1` to the login URL |
-| OIDC_PROVIDER_NAME | OAuth | The provider name is shown in SSO login button. "Login with " |
-| OIDC_REMEMBER_ME | False | Because redirects bypass the login screen, you cant extend your session by clicking the "Remember Me" checkbox. By setting this value to true, a session will be extended as if "Remember Me" was checked |
-| OIDC_USER_CLAIM | email | This is the claim which Mealie will use to look up an existing user by (e.g. "email", "preferred_username") |
-| OIDC_NAME_CLAIM | name | This is the claim which Mealie will use for the users Full Name |
-| OIDC_GROUPS_CLAIM | groups | Optional if not using `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP`. This is the claim Mealie will request from your IdP and will use to compare to `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP` to allow the user to log in to Mealie or is set as an admin. **Your IdP must be configured to grant this claim** |
-| OIDC_SCOPES_OVERRIDE | None | Advanced configuration used to override the scopes requested from the IdP. **Most users won't need to change this**. At a minimum, 'openid profile email' are required. |
-| OIDC_TLS_CACERTFILE | None | File path to Certificate Authority used to verify server certificate (e.g. `/path/to/ca.crt`) |
+| Variables | Default | Description |
+| ----------------------------------------------------------------------------------- | :-----: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| OIDC_AUTH_ENABLED | False | Enables authentication via OpenID Connect |
+| OIDC_SIGNUP_ENABLED | True | Enables new users to be created when signing in for the first time with OIDC |
+| OIDC_CONFIGURATION_URL[†][secrets] | None | The URL to the OIDC configuration of your provider. This is usually something like https://auth.example.com/.well-known/openid-configuration |
+| OIDC_CLIENT_ID[†][secrets] | None | The client id of your configured client in your provider |
+| OIDC_CLIENT_SECRET[†][secrets]
:octicons-tag-24: v2.0.0 | None | The client secret of your configured client in your provider |
+| OIDC_USER_GROUP | None | If specified, only users belonging to this group will be able to successfully authenticate, regardless of the `OIDC_ADMIN_GROUP`. For more information see [this page](../authentication/oidc.md#groups) |
+| OIDC_ADMIN_GROUP | None | If specified, users belonging to this group will be made an admin. For more information see [this page](../authentication/oidc.md#groups) |
+| OIDC_AUTO_REDIRECT | False | If `True`, then the login page will be bypassed an you will be sent directly to your Identity Provider. You can still get to the login page by adding `?direct=1` to the login URL |
+| OIDC_PROVIDER_NAME | OAuth | The provider name is shown in SSO login button. "Login with " |
+| OIDC_REMEMBER_ME | False | Because redirects bypass the login screen, you cant extend your session by clicking the "Remember Me" checkbox. By setting this value to true, a session will be extended as if "Remember Me" was checked |
+| OIDC_SIGNING_ALGORITHM | RS256 | The algorithm used to sign the id token (examples: RS256, HS256) |
+| OIDC_USER_CLAIM | email | This is the claim which Mealie will use to look up an existing user by (e.g. "email", "preferred_username") |
+| OIDC_GROUPS_CLAIM | groups | Optional if not using `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP`. This is the claim Mealie will request from your IdP and will use to compare to `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP` to allow the user to log in to Mealie or is set as an admin. **Your IdP must be configured to grant this claim** |
+| OIDC_TLS_CACERTFILE | None | File path to Certificate Authority used to verify server certificate (e.g. `/path/to/ca.crt`) |
### OpenAI
@@ -119,17 +118,13 @@ For usage, see [Usage - OpenID Connect](../authentication/oidc-v2.md)
Mealie supports various integrations using OpenAI. For more information, check out our [OpenAI documentation](./open-ai.md).
For custom mapping variables (e.g. OPENAI_CUSTOM_HEADERS) you should pass values as JSON encoded strings (e.g. `OPENAI_CUSTOM_PARAMS='{"k1": "v1", "k2": "v2"}'`)
-| Variables | Default | Description |
-| ---------------------------- | :-----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| OPENAI_BASE_URL | None | The base URL for the OpenAI API. If you're not sure, leave this empty to use the standard OpenAI platform |
-| OPENAI_API_KEY | None | Your OpenAI API Key. Enables OpenAI-related features |
-| OPENAI_MODEL | gpt-4o | Which OpenAI model to use. If you're not sure, leave this empty |
-| OPENAI_CUSTOM_HEADERS | None | Custom HTTP headers to add to all OpenAI requests. This should generally be left empty unless your custom service requires them |
-| OPENAI_CUSTOM_PARAMS | None | Custom HTTP query params to add to all OpenAI requests. This should generally be left empty unless your custom service requires them |
-| OPENAI_ENABLE_IMAGE_SERVICES | True | Whether to enable OpenAI image services, such as creating recipes via image. Leave this enabled unless your custom model doesn't support it, or you want to reduce costs |
-| OPENAI_WORKERS | 2 | Number of OpenAI workers per request. Higher values may increase processing speed, but will incur additional API costs |
-| OPENAI_SEND_DATABASE_DATA | True | Whether to send Mealie data to OpenAI to improve request accuracy. This will incur additional API costs |
-| OPENAI_REQUEST_TIMEOUT | 60 | The number of seconds to wait for an OpenAI request to complete before cancelling the request. Leave this empty unless you're running into timeout issues on slower hardware |
+| Variables | Default | Description |
+| ------------------------------------------------- | :-----: | ---------------------------------------------------------------------------------------------------------------------- |
+| OPENAI_BASE_URL[†][secrets] | None | The base URL for the OpenAI API. If you're not sure, leave this empty to use the standard OpenAI platform |
+| OPENAI_API_KEY[†][secrets] | None | Your OpenAI API Key. Enables OpenAI-related features |
+| OPENAI_MODEL | gpt-4o | Which OpenAI model to use. If you're not sure, leave this empty |
+| OPENAI_WORKERS | 2 | Number of OpenAI workers per request. Higher values may increase processing speed, but will incur additional API costs |
+| OPENAI_SEND_DATABASE_DATA | True | Whether to send Mealie data to OpenAI to improve request accuracy. This will incur additional API costs |
### Theming
@@ -154,24 +149,80 @@ Setting the following environmental variables will change the theme of the front
### Docker Secrets
-Setting a credential can be done using secrets when running in a Docker container.
-This can be used to avoid leaking passwords through compose files, environment variables, or command-line history.
-For example, to configure the Postgres database password in Docker compose, create a file on the host that contains only the password, and expose that file to the Mealie service as a secret with the correct name.
-Note that environment variables take priority over secrets, so any previously defined environment variables should be removed when migrating to secrets.
+### Docker Secrets
+
+> † Starting in version `2.4.2`, any environment variable in the preceding lists with a dagger
+> symbol next to them support the Docker Compose secrets pattern, below.
+[Docker Compose secrets][docker-secrets] can be used to secure sensitive information regarding the Mealie implementation
+by managing control of each secret independently from the single `.env` file. This is helpful for users that may need
+different levels of access for various, sensitive environment variables, such as differentiating between hardening
+operations (e.g., server endpoints and ports) and user access control (e.g., usernames, passwords, and API keys).
+
+To convert any of these environment variables to a Docker Compose secret, append `_FILE` to the environment variable and
+connect it with a Docker Compose secret, per the [Docker documentation][docker-secrets].
+
+If both the base environment variable and the secret pattern of the environment variable are set, the secret will always
+take precedence.
+
+For example, a user that wishes to harden their operations by only giving some access to their database URL, but who
+wish to place additional security around their user access control, may have a Docker Compose configuration similar to:
+
```yaml
services:
mealie:
- ...
- environment:
- ...
- POSTGRES_USER: postgres
secrets:
- - POSTGRES_PASSWORD
+ # These secrets will be loaded by Docker into the `/run/secrets` folder within the container.
+ - postgres-host
+ - postgres-port
+ - postgres-db-name
+ - postgres-user
+ - postgres-password
+ environment:
+ DB_ENGINE: postgres
+ POSTGRES_SERVER: duplicate.entry.tld # This will be ignored, due to the secret defined, below.
+ POSTGRES_SERVER_FILE: /run/secrets/postgres-host
+ POSTGRES_PORT_FILE: /run/secrets/postgres-port
+ POSTGRES_DB_FILE: /run/secrets/postgres-db-name
+ POSTGRES_USER_FILE: /run/secrets/postgres-user
+ POSTGRES_PASSWORD_FILE: /run/secrets/postgres-password
+
+# Each of these secrets are loaded via these local files. Different patterns are available. See the Docker Compose
+# documentation for more information.
secrets:
- POSTGRES_PASSWORD:
- file: postgrespassword.txt
+ postgres-host:
+ file: ./secrets/postgres-host.txt
+ postgres-port:
+ file: ./secrets/postgres-port.txt
+ postgres-db-name:
+ file: ./secrets/sensitive/postgres-db-name.txt
+ postgres-user:
+ file: ./secrets/sensitive/postgres-user.txt
+ postgres-password:
+ file: ./secrets/sensitive/postgres-password.txt
+```
+In the example above, a directory organization and access pattern may look like the following:
+```text
+.
+├── docker-compose.yml
+└── secrets # Access restricted to anyone that can manage secrets
+ ├── postgres-host.txt
+ ├── postgres-port.txt
+ └── sensitive # Access further-restricted to anyone managing service accounts
+ ├── postgres-db-name.txt
+ ├── postgres-password.txt
+ └── postgres-user.txt
```
+How you organize your secrets is ultimately up to you. At minimum, it's highly recommended to use secret patterns for
+at least these sensitive environment variables when working within shared environments:
+
+- `POSTGRES_PASSWORD`
+- `SMTP_PASSWORD`
+- `LDAP_QUERY_PASSWORD`
+- `OPENAI_API_KEY`
+
+[docker-secrets]: https://docs.docker.com/compose/use-secrets/
+[secrets]: #docker-secrets
[unicorn_workers]: https://www.uvicorn.org/deployment/#built-in