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