From 1eeebc2a5903d9d137dc000ab0fc3ba0a921ca36 Mon Sep 17 00:00:00 2001 From: Josh Sokol Date: Wed, 28 Oct 2026 15:08:57 -0500 Subject: [PATCH] fix(minimal): use TLS flags the installed mysql client understands The arm64 image's mysql is MariaDB's client (Debian's default-mysql-client; MySQL's apt repo has no arm64 packages), which rejects --ssl-mode. With DB_SSL_ENABLED=true the privileged setup and delete calls failed with "unknown variable 'ssl-mode=REQUIRED'", so no customer database could be created on Graviton against a TLS-only RDS (found on the first Canada deploy). set_db_ssl_flags now asks the client: Oracle's keeps --ssl-mode=REQUIRED --enable-cleartext-plugin (unchanged for amd64); MariaDB's gets --ssl --skip-ssl-verify-server-cert, its equivalent (TLS required, certificate not verified, cleartext plugin built in). If the client cannot be asked the historical flags are kept. Verified on an arm64 node against the Canada RDS (MySQL 8.4.8, IAM auth token): the lifted set_db_ssl_flags + mysql_setup connect over TLS as the master user. test_db_ssl_flags.sh covers both clients, the opt-in-only rule and the fallback, and fails on the old code; it runs in CI. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/container-validation.yml | 3 + simplerisk-minimal/README.md | 2 +- simplerisk-minimal/common/entrypoint.sh | 22 ++++++- simplerisk-minimal/test_db_ssl_flags.sh | 72 ++++++++++++++++++++++ 4 files changed, 96 insertions(+), 3 deletions(-) create mode 100755 simplerisk-minimal/test_db_ssl_flags.sh 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