Check database dump privileges before stopping applications

This commit is contained in:
admin committed 2026-09-10 14:52:04 +02:00
1 parent 898df2e926
commit 5c8a7a30be
6 files changed
+241 -7

No files matched your search

+52
View File
@@ -170,6 +170,56 @@ def crypt(name):
print("Crypt configuration accepted (contents, filenames, directories)")
def has_reload(grants):
"""Inspect privilege names only; SHOW GRANTS can contain authentication hashes."""
for line in grants.splitlines():
match = re.match(r'^GRANT ([A-Z_ ,]+) ON \*\.\* TO ', line)
if match and {'RELOAD', 'ALL PRIVILEGES'} & set(match[1].split(', ')):
return True
return False
def db_privileges(client, credentials, database):
"""Read privileges without executing FLUSH or acquiring a production DB lock.
Check lock-all-tables and MariaDB's routine metadata prerequisite, not every
object-specific permission. Grants (including auth hashes) stay in memory.
"""
def query(sql, purpose='privilege metadata'):
result = subprocess.run([client, '--defaults-extra-file=' + credentials,
'--database=' + database, '--batch', '--skip-column-names',
'--execute=' + sql], capture_output=True, text=True)
if result.returncode:
# Neither query results nor server errors may disclose auth material.
raise ValueError(f'DB {database}: {purpose} query failed; inspect account permissions')
return result.stdout
grants = query('SHOW GRANTS FOR CURRENT_USER')
allowed = has_reload(grants)
via_role = False
mariadb = 'MariaDB' in query('SELECT VERSION()')
if not allowed and mariadb:
# Only session-enabled roles count; a merely granted but inactive role
# does not help mariadb-dump. ENABLED_ROLES also includes nested roles.
roles = query('SELECT ROLE_NAME FROM information_schema.ENABLED_ROLES WHERE ROLE_NAME IS NOT NULL').splitlines()
for role in roles:
if not re.fullmatch(r'[A-Za-z0-9_.:-]+', role):
raise ValueError(f'DB {database}: unsupported role name; configure direct RELOAD grant for backup account')
if has_reload(query('SHOW GRANTS FOR `' + role + '`')):
allowed = via_role = True
break
if not allowed:
raise ValueError(f'DB {database}: global RELOAD privilege missing or not verifiable; '
'required for --lock-all-tables. Grant RELOAD to the actual backup account; '
'see UPDATE_ANLEITUNG.md. No FLUSH or database lock performed by this check')
print(f'DB {database}: global RELOAD privilege confirmed' + (' through an enabled role' if via_role else ''))
if mariadb:
# --routines needs mysql.proc access. LIMIT 0 checks permissions without
# returning routine bodies or reading the stored routine contents.
query('SELECT * FROM mysql.proc LIMIT 0', 'routine metadata (SELECT on mysql.proc required for --routines)')
print(f'DB {database}: MariaDB routine metadata access confirmed')
def candidates(prefix, days, keep):
# Only v2 names supplied AFTER completion-marker validation are eligible.
names = set(sys.stdin.read().splitlines())
@@ -233,6 +283,8 @@ def main():
op, *args = sys.argv[1:]
if op == "wait-nextcloud":
wait_nextcloud(*args)
elif op == "db-privileges":
db_privileges(*args)
elif op == "status":
write_status(*args)
elif op == "crypt":