diff --git a/.github/workflows/container-validation.yml b/.github/workflows/container-validation.yml index 96e8eae..5205276 100644 --- a/.github/workflows/container-validation.yml +++ b/.github/workflows/container-validation.yml @@ -110,6 +110,9 @@ jobs: - name: simplerisk-minimal credential escaping run: ./simplerisk-minimal/test_escaping.sh + - name: simplerisk-minimal DB TLS flags per mysql client + run: ./simplerisk-minimal/test_db_ssl_flags.sh + - name: simplerisk-minimal Apache worker setting run: ./simplerisk-minimal/test_set_apache_workers.sh diff --git a/simplerisk-minimal/README.md b/simplerisk-minimal/README.md index acf584f..237c303 100644 --- a/simplerisk-minimal/README.md +++ b/simplerisk-minimal/README.md @@ -65,7 +65,7 @@ docker run -d --name simplerisk -e SIMPLERISK_DB_PASSWORD=pass -e SIMPLERISK_DB_ | `DB_SETUP_PASS` | `root` (the bundled `stack.yml` ships `simplerisk_setup`) | Used when `DB_SETUP=automatic\|automatic-only\|delete`. Password of the privileged MySQL user used **only** to install the SimpleRisk schema and create the app DB user. In `stack.yml` it is also the bundled MySQL root password; since that MySQL is not exposed outside the stack network, a documented default is used for the zero-config trial. Override it (and `MYSQL_ROOT_PASSWORD` in `stack.yml`) for any non-trial deployment. | | `DB_SETUP_WAIT` | 20 | Used when `DB_SETUP=automatic\|automatic-only`. Time, in seconds, the application is going to wait to set up the database. Useful if you are deploying the database and SimpleRisk at the same time | | `DB_UPGRADE` | `null` (Accepts any value) | Run SimpleRisk's release-by-release database **schema upgrade** against an already-installed database (as opposed to `DB_SETUP`, which installs a fresh one), then continue running (`automatic`) or stop the container (`automatic-only`, for a one-shot upgrade job). Uses the `SIMPLERISK_DB_*` application credentials — no privileged user. The structured per-release JSON result is written to the container log, and the container exits non-zero if the upgrade fails. Idempotent — a no-op when the schema is already current | -| `DB_SSL_ENABLED` | `false` (off) | Opt-in, used when `DB_SETUP=automatic\|automatic-only\|delete`. Set to exactly `true` to require TLS on the privileged setup/delete MySQL client connection (adds `--ssl-mode=REQUIRED --enable-cleartext-plugin`). Any other value, or unset, leaves the connection unchanged (plaintext-capable) | +| `DB_SSL_ENABLED` | `false` (off) | Opt-in, used when `DB_SETUP=automatic\|automatic-only\|delete`. Set to exactly `true` to require TLS on the privileged setup/delete MySQL client connection (adds `--ssl-mode=REQUIRED --enable-cleartext-plugin`; on the arm64 image, whose `mysql` is MariaDB's client and has no `--ssl-mode`, it adds the equivalent `--ssl --skip-ssl-verify-server-cert`). Any other value, or unset, leaves the connection unchanged (plaintext-capable) | | `SIMPLERISK_DB_HOSTNAME` | `localhost` | Hostname of the database server | | `SIMPLERISK_DB_PORT` | 3306 | Port to contact the database | | `SIMPLERISK_DB_USERNAME` |`simplerisk` | User name to be used to access the SimpleRisk database | diff --git a/simplerisk-minimal/common/entrypoint.sh b/simplerisk-minimal/common/entrypoint.sh index 89e5d9e..6ef2d7c 100644 --- a/simplerisk-minimal/common/entrypoint.sh +++ b/simplerisk-minimal/common/entrypoint.sh @@ -69,7 +69,7 @@ config_define() { # password via MYSQL_PWD, so no value is subject to shell interpretation. mysql_setup() { local -a flags=() - # DB_SSL_FLAGS is an internal constant ("--ssl-mode=... --enable-cleartext-plugin") + # DB_SSL_FLAGS is an internal value from set_db_ssl_flags (client-specific TLS flags) [ -n "${DB_SSL_FLAGS:-}" ] && read -ra flags <<< "$DB_SSL_FLAGS" MYSQL_PWD="$DB_SETUP_PASS" mysql ${flags[@]+"${flags[@]}"} \ -u "$DB_SETUP_USER" -h "$SIMPLERISK_DB_HOSTNAME" -P "$SIMPLERISK_DB_PORT" @@ -473,6 +473,17 @@ set_mail_settings(){ [ -n "${MAIL_PASSWORD:-}" ] && apply_mail_setting phpmailer_password "$MAIL_PASSWORD" || true } +# True when the mysql client understands --ssl-mode. Oracle's client does (amd64 image). MariaDB's +# does not, and it is the only client packaged for arm64 (MySQL's apt repo has no arm64 builds), so +# there `mysql` rejects it with "unknown variable 'ssl-mode=REQUIRED'". If the client cannot be asked, +# assume Oracle's, which is what these flags were written for. +mysql_client_supports_ssl_mode() { + local help + help=$(mysql --help 2>/dev/null) || return 0 + [ -n "$help" ] || return 0 + grep -q -- '--ssl-mode' <<< "$help" +} + set_db_ssl_flags(){ # Some databases require the privileged setup/delete mysql client to send # its credential via the cleartext auth plugin, which the server only @@ -480,7 +491,14 @@ set_db_ssl_flags(){ # rather than a static password). Set DB_SSL_ENABLED=true to opt in. DB_SSL_FLAGS="" if [ "${DB_SSL_ENABLED:-}" = "true" ]; then - DB_SSL_FLAGS="--ssl-mode=REQUIRED --enable-cleartext-plugin" + if mysql_client_supports_ssl_mode; then + DB_SSL_FLAGS="--ssl-mode=REQUIRED --enable-cleartext-plugin" + else + # MariaDB client: --ssl makes TLS mandatory and --skip-ssl-verify-server-cert matches + # REQUIRED (encrypt, do not verify the certificate). The cleartext plugin is built in; + # --enable-cleartext-plugin is obsolete there and only prints a warning. + DB_SSL_FLAGS="--ssl --skip-ssl-verify-server-cert" + fi fi } diff --git a/simplerisk-minimal/test_db_ssl_flags.sh b/simplerisk-minimal/test_db_ssl_flags.sh new file mode 100755 index 0000000..7a45c16 --- /dev/null +++ b/simplerisk-minimal/test_db_ssl_flags.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# Verifies set_db_ssl_flags in common/entrypoint.sh picks TLS flags the installed mysql client +# understands. Oracle's client (amd64 image) takes --ssl-mode=REQUIRED --enable-cleartext-plugin. +# MariaDB's client (arm64 image: Debian's default-mysql-client, since MySQL's apt repo has no arm64 +# packages) rejects --ssl-mode ("unknown variable 'ssl-mode=REQUIRED'"), which made every DB setup +# and delete fail on Graviton against a TLS-only RDS. Its equivalent is --ssl plus +# --skip-ssl-verify-server-cert (TLS required, server certificate not verified: the same as +# REQUIRED); the cleartext plugin is built in and --enable-cleartext-plugin is obsolete there. +# +# `mysql` is faked with --help output shaped like the real clients (the MariaDB lines were captured +# from mariadb 11.8.6 in the arm64 image). Requires bash >= 4. +set -uo pipefail +cd "$(dirname "$(readlink -f "$0")")" || exit 1 + +fail=0 +ok() { echo "ok: $1"; } +bad() { echo "FAIL: $1"; echo " expected: $2"; echo " actual: $3"; fail=1; } +check() { [ "$2" = "$3" ] && ok "$1" || bad "$1" "$2" "$3"; } + +work=$(mktemp -d); trap 'rm -rf "$work"' EXIT +mkdir -p "$work/mysql-oracle" "$work/mariadb" "$work/none" + +cat > "$work/mysql-oracle/mysql" <<'FAKE' +#!/bin/sh +cat <<'HELP' +mysql Ver 8.4.8 for Linux on x86_64 (MySQL Community Server - GPL) + --ssl-mode=name SSL connection mode. + --ssl-ca=name CA file in PEM format. + --enable-cleartext-plugin + Enable/disable the clear text authentication plugin. +HELP +FAKE +cat > "$work/mariadb/mysql" <<'FAKE' +#!/bin/sh +cat <<'HELP' +mysql from 11.8.6-MariaDB, client 15.2 for debian-linux-gnu (aarch64) + --ssl Enable SSL for connection (automatically enabled with + --ssl-ca=name CA file in PEM format (check OpenSSL docs, implies + --ssl-verify-server-cert + Verify server's "Common Name" in its cert against +HELP +FAKE +chmod +x "$work/mysql-oracle/mysql" "$work/mariadb/mysql" + +# Pull in only the functions under test. +eval "$(sed -n '/^mysql_client_supports_ssl_mode() {/,/^}/p;/^set_db_ssl_flags(){/,/^}/p' common/entrypoint.sh)" + +# flags_with +flags_with() { + ( PATH="$work/$1:/usr/bin:/bin" + [ "$1" = none ] && PATH="$work/none:/nonexistent" + if [ "$2" = unset ]; then unset DB_SSL_ENABLED; else DB_SSL_ENABLED="$2"; fi + set_db_ssl_flags; printf '%s' "$DB_SSL_FLAGS" ) +} + +# Oracle client: behaviour must be exactly what it was (amd64 images depend on it). +check "oracle client + DB_SSL_ENABLED=true" "--ssl-mode=REQUIRED --enable-cleartext-plugin" "$(flags_with mysql-oracle true)" +# MariaDB client: no flag it rejects. +got=$(flags_with mariadb true) +check "mariadb client + DB_SSL_ENABLED=true" "--ssl --skip-ssl-verify-server-cert" "$got" +case "$got" in *--ssl-mode*) bad "mariadb flags must not contain --ssl-mode" "(none)" "$got";; *) ok "mariadb flags contain no --ssl-mode";; esac +case "$got" in *--enable-cleartext-plugin*) bad "mariadb flags must not contain the obsolete --enable-cleartext-plugin" "(none)" "$got";; *) ok "mariadb flags contain no --enable-cleartext-plugin";; esac +# Opt-in only: anything but exactly "true" leaves the connection unchanged, for either client. +for c in mysql-oracle mariadb; do + for v in unset false TRUE 1 ""; do + check "$c + DB_SSL_ENABLED='$v' -> no flags" "" "$(flags_with $c "$v")" + done +done +# If the client cannot be asked (not on PATH), keep the historical flags rather than guess. +check "no mysql on PATH + DB_SSL_ENABLED=true keeps the historical flags" "--ssl-mode=REQUIRED --enable-cleartext-plugin" "$(flags_with none true)" + +exit $fail