mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-20 11:21:49 +00:00
fixes
This commit is contained in:
@@ -252,14 +252,29 @@ require_data_volume() {
|
||||
log "warning: ${DATA_MOUNT} is not a mount point; data will not survive a VM replace (RDS_ALLOW_LOCAL_DATADIR=1)"
|
||||
}
|
||||
|
||||
# True when the datadir holds anything at all. A failed enumeration is neither
|
||||
# "empty" nor "has content": it is a refusal, because both callers below decide
|
||||
# whether something may be created or deleted from the answer.
|
||||
datadir_has_content() {
|
||||
[ -d "${DATADIR}" ] || return 1
|
||||
_contents=$(ls -A -- "${DATADIR}" 2>/dev/null) ||
|
||||
fail "could not inspect datadir ${DATADIR}; refusing to decide whether it is ours"
|
||||
[ -n "${_contents}" ]
|
||||
# True when the data volume holds anything that is not ours. The mount is what
|
||||
# is inspected rather than ${DATADIR}: another engine lays its data out under its
|
||||
# own name, so a PostgreSQL volume has content here while ${DATADIR} does not
|
||||
# exist at all.
|
||||
data_volume_has_content() {
|
||||
[ -d "${DATA_MOUNT}" ] || return 1
|
||||
|
||||
# A mount that cannot be read is neither empty nor not: it is a refusal,
|
||||
# because the caller decides from the answer whether to initialise.
|
||||
ls -A -- "${DATA_MOUNT}" >/dev/null 2>&1 ||
|
||||
fail "could not inspect ${DATA_MOUNT}; refusing to decide whether the data volume is ours"
|
||||
|
||||
# Globbed rather than split out of `ls`, so an entry carrying whitespace
|
||||
# counts once. lost+found is the filesystem's, and the metadata and include
|
||||
# directories are ours — none of the three says whose data the volume holds.
|
||||
for _entry in "${DATA_MOUNT}"/* "${DATA_MOUNT}"/.[!.]* "${DATA_MOUNT}"/..?*; do
|
||||
[ -e "${_entry}" ] || [ -L "${_entry}" ] || continue
|
||||
case "${_entry##*/}" in
|
||||
lost+found | .spinifex-rds | "${CONF_DIR##*/}") continue ;;
|
||||
esac
|
||||
return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Written before the datadir is inspected, so a volume this image goes on to
|
||||
@@ -304,11 +319,11 @@ require_engine_stamp() {
|
||||
fi
|
||||
|
||||
# No MariaDB volume in the field predates the stamp, so unlike PostgreSQL
|
||||
# there is nothing to backfill onto: an unstamped datadir with content in it
|
||||
# there is nothing to backfill onto: an unstamped volume with content on it
|
||||
# is by definition not ours, and stamping it would turn the check into a
|
||||
# rubber stamp on exactly the volume it exists to refuse.
|
||||
if datadir_has_content; then
|
||||
fail "${DATADIR} holds data but the volume carries no ${ENGINE} engine stamp; refusing to touch it. Recover the volume out of band."
|
||||
if data_volume_has_content; then
|
||||
fail "${DATA_MOUNT} holds data but the volume carries no ${ENGINE} engine stamp; refusing to touch it. Recover the volume out of band."
|
||||
fi
|
||||
|
||||
log "data volume carries no engine stamp — recording ${ENGINE}"
|
||||
@@ -639,13 +654,24 @@ FLUSH PRIVILEGES;
|
||||
SQL
|
||||
}
|
||||
|
||||
# The master user is administrative but deliberately holds none of the
|
||||
# privileges that reach outside the database: FILE is SELECT INTO OUTFILE and
|
||||
# LOAD_FILE against any path as the mysql OS user, INSERT on mysql.* is native
|
||||
# code execution through CREATE FUNCTION ... SONAME, SUPER and its 11.8 split-out
|
||||
# successors reach SET GLOBAL and plugin loading, and SHUTDOWN stops the server
|
||||
# underneath the control plane's lifecycle. Master credentials must be a database
|
||||
# privilege, not a shell in the DB VM.
|
||||
# The master user is administrative but is not granted the privileges that reach
|
||||
# outside the database: FILE is SELECT INTO OUTFILE and LOAD_FILE against any
|
||||
# path as the mysql OS user, SUPER and its 11.8 split-out successors reach SET
|
||||
# GLOBAL and plugin loading, and SHUTDOWN stops the server underneath the control
|
||||
# plane's lifecycle. Master credentials must be a database privilege, not a shell
|
||||
# in the DB VM.
|
||||
#
|
||||
# What the grant list cannot do is withhold the mysql schema. MariaDB privileges
|
||||
# are additive and it has no DENY, so the DML below reaches mysql.global_priv,
|
||||
# and REVOKE ... ON mysql.* is refused outright (ER_NONEXISTING_GRANT) because no
|
||||
# database-level row exists to revoke from. A master that writes that table and
|
||||
# runs FLUSH PRIVILEGES therefore holds ALL PRIVILEGES, including the three above.
|
||||
#
|
||||
# What still holds after that is the boundary this preset actually depends on:
|
||||
# secure_file_priv and plugin_dir are read-only variables, so 90-rds-init.cnf
|
||||
# pins them past any SET GLOBAL and FILE reaches only an empty root-owned
|
||||
# directory. The residual exposure is SHUTDOWN and unrecorded SET GLOBAL, not a
|
||||
# shell in the DB VM.
|
||||
apply_master_user() {
|
||||
log "creating the master user ${RDS_MASTER_USERNAME}"
|
||||
# Enumerated rather than GRANT ALL followed by revocations: a MariaDB release
|
||||
|
||||
@@ -387,15 +387,35 @@ if run_ok "initialize"; then
|
||||
|| fail "initialize: the database was created under the compiled default"
|
||||
|
||||
# Withheld deliberately: FILE is arbitrary host file access as the mysql user,
|
||||
# SUPER reaches SET GLOBAL and plugin loading, and SHUTDOWN stops the server
|
||||
# underneath the control plane's lifecycle.
|
||||
for privilege in SUPER FILE SHUTDOWN 'ALL PRIVILEGES'; do
|
||||
# SUPER and its 11.8 split-out successors reach SET GLOBAL and plugin loading,
|
||||
# and SHUTDOWN stops the server underneath the control plane's lifecycle.
|
||||
for privilege in SUPER FILE SHUTDOWN 'ALL PRIVILEGES' 'SET USER' \
|
||||
'BINLOG ADMIN' 'BINLOG REPLAY' 'CONNECTION ADMIN' 'FEDERATED ADMIN' \
|
||||
'READ_ONLY ADMIN' 'REPLICATION MASTER ADMIN' 'REPLICATION SLAVE ADMIN' \
|
||||
'SLAVE MONITOR'; do
|
||||
grep -q "${privilege}" "${CLIENT_CALLS}" \
|
||||
&& fail "initialize: the master grant includes ${privilege}" \
|
||||
|| pass "initialize: master holds no ${privilege}"
|
||||
done
|
||||
grep -q 'GRANT SELECT, INSERT, UPDATE, DELETE' "${CLIENT_CALLS}" \
|
||||
&& pass "initialize: the grant is enumerated" || fail "initialize: no enumerated grant"
|
||||
|
||||
# Asserted whole rather than by prefix. A privilege appended to the list is a
|
||||
# privilege the master silently gains, and the enumeration exists precisely so
|
||||
# that widening it has to be deliberate.
|
||||
_grant=$(sed -n '/^GRANT /,/WITH GRANT OPTION;/p' "${CLIENT_CALLS}" | tr '\n' ' ' | tr -s ' ')
|
||||
_expected_grant="GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, PROCESS, \
|
||||
REFERENCES, INDEX, ALTER, SHOW DATABASES, CREATE TEMPORARY TABLES, \
|
||||
LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, \
|
||||
CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, \
|
||||
CREATE USER, EVENT, TRIGGER ON *.* TO 'mulgamaster'@'%' WITH GRANT OPTION; "
|
||||
[ "${_grant}" = "${_expected_grant}" ] \
|
||||
&& pass "initialize: the grant is exactly the enumerated list" \
|
||||
|| fail "initialize: the master grant has changed: ${_grant}"
|
||||
|
||||
# MariaDB privileges are additive and it has no DENY, so scope is the only
|
||||
# control left: a second ON clause would widen the master beyond this list.
|
||||
[ "$(grep -c '^ ON ' "${CLIENT_CALLS}")" = "1" ] \
|
||||
&& pass "initialize: the grant is scoped ON *.* and nothing else" \
|
||||
|| fail "initialize: the master carries more than one grant scope"
|
||||
grep -q "WITH GRANT OPTION" "${CLIENT_CALLS}" \
|
||||
&& pass "initialize: master can delegate its own privileges" \
|
||||
|| fail "initialize: master cannot grant"
|
||||
@@ -699,7 +719,7 @@ unset LS_FAIL
|
||||
[ -s "${INSTALLDB_CALLS}" ] \
|
||||
&& fail "datadir-probe-fail: initialised after the probe failed" \
|
||||
|| pass "datadir-probe-fail: nothing created"
|
||||
grep -q 'could not inspect datadir' "${WORK}/out" \
|
||||
grep -q 'refusing to decide whether the data volume is ours' "${WORK}/out" \
|
||||
&& pass "datadir-probe-fail: refusal explains why" || fail "datadir-probe-fail: no refusal message"
|
||||
|
||||
# --- Case 6c: the bootstrap server never starts ---
|
||||
@@ -1012,6 +1032,47 @@ if run_ok "stamp-absent-empty"; then
|
||||
grep -q 'no mariadb engine stamp' "${WORK}/out" \
|
||||
&& pass "stamp-absent-nonempty: refusal names the missing stamp" \
|
||||
|| fail "stamp-absent-nonempty: no refusal message"
|
||||
|
||||
# --- Case 12f: an unstamped PostgreSQL volume is refused ---
|
||||
# The one unstamped volume that actually exists in the field, and the reason
|
||||
# emptiness is decided on the mount: PostgreSQL keeps its cluster under
|
||||
# ${DATA_MOUNT}/18/data, so ${DATADIR} is absent and the volume reads as new.
|
||||
rm -rf "${DATADIR}" "${STAMP}"
|
||||
mkdir -p "${DATA_MOUNT}/18/data"
|
||||
echo 'customer cluster data' > "${DATA_MOUNT}/18/data/PG_VERSION"
|
||||
: > "${INSTALLDB_CALLS}"
|
||||
run_fails "stamp-absent-pgvolume"
|
||||
[ -s "${INSTALLDB_CALLS}" ] \
|
||||
&& fail "stamp-absent-pgvolume: initialised over a PostgreSQL volume" \
|
||||
|| pass "stamp-absent-pgvolume: nothing created"
|
||||
[ -f "${DATA_MOUNT}/18/data/PG_VERSION" ] \
|
||||
&& pass "stamp-absent-pgvolume: the PostgreSQL cluster is preserved" \
|
||||
|| fail "stamp-absent-pgvolume: the PostgreSQL cluster was touched"
|
||||
# A stamp here is the worst outcome of all: the PostgreSQL image refuses a
|
||||
# 'mariadb' stamp too, so the volume becomes unreachable from both engines.
|
||||
[ -e "${STAMP}" ] \
|
||||
&& fail "stamp-absent-pgvolume: another engine's volume was stamped ours" \
|
||||
|| pass "stamp-absent-pgvolume: no stamp written"
|
||||
grep -q 'no mariadb engine stamp' "${WORK}/out" \
|
||||
&& pass "stamp-absent-pgvolume: refusal names the missing stamp" \
|
||||
|| fail "stamp-absent-pgvolume: no refusal message"
|
||||
rm -rf "${DATA_MOUNT}/18"
|
||||
|
||||
# --- Case 12g: lost+found alone does not make a fresh volume look used ---
|
||||
# ext4 creates it at mkfs, so counting it as content would refuse every
|
||||
# genuinely new data volume.
|
||||
rm -rf "${DATADIR}" "${STAMP}"
|
||||
mkdir -p "${DATA_MOUNT}/lost+found"
|
||||
: > "${INSTALLDB_CALLS}"
|
||||
write_handoff initialize 's3cr3t' appdb
|
||||
if run_ok "stamp-absent-lostfound"; then
|
||||
[ -s "${INSTALLDB_CALLS}" ] \
|
||||
&& pass "stamp-absent-lostfound: a fresh volume still initialises" \
|
||||
|| fail "stamp-absent-lostfound: nothing was initialised"
|
||||
[ "$(cat "${STAMP}")" = "mariadb" ] \
|
||||
&& pass "stamp-absent-lostfound: the fresh volume was stamped" \
|
||||
|| fail "stamp-absent-lostfound: no stamp written"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Case 12e: a stamp that cannot be written is fatal, before initialisation ---
|
||||
|
||||
Reference in New Issue
Block a user