Proteggere MariaDB e MySQL: i passi dopo l'installazione

Pubblicato il 17 min di lettura

Dopo l'installazione del pacchetto un database non è ancora sicuro. Questa guida percorre mysql_secure_installation, chiarisce la questione unix_socket e mostra come creare un utente dedicato per ogni applicazione.

Un database appena installato su un server root raramente è insicuro quanto sostengono le guide più vecchie, e raramente è sicuro quanto lascia intendere il sistema dei pacchetti. Tra questi due estremi stanno esattamente le operazioni descritte in questo articolo: che cosa fa davvero mysql_secure_installation, perché alla domanda su unix_socket oggi si risponde in modo diverso rispetto al 2015, come è fatto un utente applicativo con permessi minimi e come si tengono le password fuori dalla lista dei processi.

Tutti i comandi vengono eseguiti come root. Chi lavora con un utente normale antepone sudo. Gli output provengono da Debian 13 e Debian 12 oltre che da Ubuntu 24.04 e Ubuntu 22.04.

Punto di partenza: quale pacchetto gira su quale distribuzione

Il primo ostacolo si presenta prima ancora del primo passo di sicurezza. Da anni Debian non distribuisce più un pacchetto proprio chiamato mysql-server. Lì il nome esiste soltanto come pacchetto virtuale senza una versione propria, visibile unicamente attraverso le dipendenze di mariadb-server:

apt-cache policy mysql-server
mysql-server:
  Installed: (none)
  Candidate: (none)
  Version table:

Questo confronto di versioni restituisce quindi un risultato solo su Ubuntu, dove si tratta della 8.0.46 sia su 24.04 sia su 22.04. Su Debian si usa invece apt-cache showpkg mysql-server, che rende visibile la natura virtuale del nome.

Su Debian il database è dunque sempre MariaDB. Su Ubuntu esistono entrambi e bisogna scegliere. Le versioni presenti nelle distribuzioni attuali:

Distribuzionemariadb-servermysql-server
Debian 1311.8solo nome virtuale, nessuna versione
Debian 1210.11solo nome virtuale, nessuna versione
Ubuntu 24.0410.118.0.46
Ubuntu 22.0410.68.0.46

L'installazione avviene come di consueto:

apt update
apt install -y mariadb-server

A dire se si è davvero ottenuta la versione attesa non basta il numero di versione del pacchetto, serve il server in esecuzione:

mariadb -e "SELECT @@version, @@version_comment;"

Il secondo ostacolo è il nome del programma. A partire da MariaDB 11.0 i nomi compatibili con MySQL non si trovano più nel pacchetto principale, ma in mariadb-client-compat e mariadb-server-compat. Su Debian 13 un mysql --version può quindi rispondere con un avviso oppure non esistere affatto:

mysql: Deprecated program name. It will be removed in a future release, use '/usr/bin/mariadb' instead
bash: mysql: command not found

Chi scrive script che devono funzionare su tutti e quattro i sistemi usa sotto MariaDB sempre mariadb, mariadb-dump e mariadb-secure-installation. Questi nomi esistono da MariaDB 10.5, quindi anche su Ubuntu 22.04.

mysql_secure_installation passo per passo

Lo strumento si chiama in modo diverso a seconda del server. Sotto MariaDB il nome canonico è mariadb-secure-installation, sotto MySQL 8.0 resta mysql_secure_installation. Il vecchio nome sotto MariaDB continua a esistere come collegamento simbolico su Debian 12 e sulle versioni Ubuntu 24.04 e 22.04, mentre su Debian 13 non c'è più: lì esiste esclusivamente mariadb-secure-installation.

Vale la pena dare un'occhiata alle opzioni, perché il programma accetta tra l'altro --defaults-file, --socket e --protocol. Questo elenco però si trova solo nel manuale:

man mariadb-secure-installation

Un --help infatti non esiste. Lo script non valuta affatto gli argomenti, ingoia in silenzio l'opzione sconosciuta e avvia subito la procedura interattiva. Se in quel momento manca una console vera, per esempio in una pipeline o in un container senza terminale, ripete all'infinito la richiesta della password e non torna mai indietro da solo. Per questo lo si richiama senza argomenti e in una shell interattiva:

mariadb-secure-installation

Su Debian 13 lo script premette alla procedura un avviso esplicito:

NOTE: MariaDB is secure by default in Debian. Running this script is useless at best,
and misleading at worst. This script will be removed in a future MariaDB release in Debian.

Debian considera quindi superflua l'esecuzione e rimuoverà lo script in una versione futura, come si legge in /usr/share/doc/mariadb-server/README.Debian.gz. I passaggi seguenti restano comunque utili, perché mostrano che cosa viene verificato e perché su un sistema aggiornato non c'è quasi più nulla da cambiare.

La procedura inizia con la domanda sulla password di root. La formulazione dipende dalla versione dello script:

Enter current password for root (enter for none):

Le versioni più recenti chiedono invece:

Enter root user password or leave blank:

In entrambi i casi si intende la stessa cosa. Su un'installazione appena fatta non esiste alcuna password, qui basta premere Invio.

Seguono poi le decisioni vere e proprie. L'ordine e la formulazione differiscono tra MariaDB e MySQL, nella sostanza si tratta degli stessi cinque punti.

La domanda su unix_socket

Con MariaDB compare come passo successivo:

Switch to unix_socket authentication [Y/n]

Questa domanda confonde, perché su tutti i sistemi trattati qui la risposta è già data. Da MariaDB 10.4 l'account root@localhost è protetto di serie dal plugin unix_socket, e questo vale per la 10.6 su Ubuntu 22.04 esattamente come per la 11.8 su Debian 13. In MySQL 8.0 la controparte si chiama auth_socket, e l'assistente lo dice apertamente:

Skipping password set for root as authentication with auth_socket is used by default.

In pratica significa: chi è collegato come utente di sistema root entra nel database con mariadb senza password. Chi non lo è non entra affatto, nemmeno con la password giusta. Non è un difetto, è la variante più robusta. Non esiste alcuna password che possa uscire da un backup, da un file di configurazione o da uno screenshot. La risposta alla domanda è quindi mantenere il Sì, e una password root aggiuntiva su un singolo server applicativo è superflua.

Lo stato si può verificare solo sulla tabella reale. La query più ovvia, SELECT user, host, plugin FROM mysql.user, qui infatti porta fuori strada: per root mostra il valore mysql_native_password, mentre in realtà il metodo attivo è unix_socket. In MariaDB dalla 10.4 mysql.user è solo una vista su mysql.global_priv, e questa vista conosce un solo metodo di autenticazione per account. Chi ci si affida crede erroneamente che il server usi l'autenticazione a password. La regola completa si trova nel campo JSON Priv della tabella reale:

mariadb -e "SELECT User, Host, JSON_DETAILED(Priv) FROM mysql.global_priv;"

Per root su localhost sotto MariaDB lì compare in sostanza:

{"plugin":"mysql_native_password","authentication_string":"invalid","auth_or":[{},{"plugin":"unix_socket"}]}

La prima voce è il metodo a password confrontato con l'hash inutilizzabile della stringa invalid, che nessuno può indovinare. La seconda voce sotto auth_or è l'autenticazione via socket effettivamente in uso. In modo più compatto si interrogano entrambe così:

mariadb -e "SELECT User, Host, JSON_VALUE(Priv,'$.plugin') AS plugin, JSON_QUERY(Priv,'$.auth_or') AS auth_or FROM mysql.global_priv;"

Per root e localhost ci si aspetta un unix_socket, se non nella colonna plugin, allora sotto auth_or. Se lì compare esclusivamente un metodo a password e sotto auth_or non c'è nulla, l'account lavora soltanto con password. In MySQL 8.0 mysql.global_priv non esiste, lì mysql.user è una tabella vera e la colonna plugin deve mostrare auth_socket. Dalla vista di MariaDB si può del resto continuare a leggere, scrivere invece no.

Solo se uno strumento richiede necessariamente una password, per esempio un monitoraggio che non gira come root, si cambia impostazione. In quel caso però ha più senso creare un secondo utente amministrativo, invece di riconvertire root. In MariaDB dalla 11.6, quindi anche nella 11.8 su Debian 13, un account può essere legato in aggiunta a un determinato utente di sistema:

CREATE USER 'dbadmin'@'localhost' IDENTIFIED VIA unix_socket AS 'deploy';

In questo modo l'utente di sistema deploy può autenticarsi come utente di database dbadmin, senza che i nomi debbano coincidere. Su Debian 12 e sulle versioni Ubuntu la stringa dopo AS viene ancora ignorata, lì utente di sistema e utente di database devono avere lo stesso nome.

Le quattro domande rimanenti

Il resto è indiscusso e si risponde sempre con Sì: rimuovere gli utenti anonimi, vietare l'accesso root da remoto, eliminare il database test insieme ai relativi permessi, ricaricare le tabelle dei permessi. Su un Debian o Ubuntu attuale gli utenti anonimi e il database di prova di solito non sono nemmeno presenti, lo script segnala allora semplicemente che non c'era nulla da fare.

Con MySQL 8.0 si aggiunge una domanda che MariaDB non conosce:

Would you like to setup VALIDATE PASSWORD component?

Questo componente impone requisiti minimi a tutte le password impostate in seguito. È utile quando più persone creano utenti. È fastidioso quando uno script di provisioning genera password casuali che per caso non contengono alcun carattere speciale. In quel caso lo script si interrompe con:

ERROR 1819 (HY000): Your password does not satisfy the current policy requirements

Chi lo attiva dovrebbe configurare prima il generatore di password di conseguenza. Il livello predefinito è MEDIUM e richiede almeno otto caratteri, lettere maiuscole e minuscole, una cifra e un carattere speciale.

Quando qualcosa va storto: rientrare nel database

Il modo più frequente per chiudersi fuori è il passaggio, fatto con le migliori intenzioni, da unix_socket a una password che poi va persa. Oppure il contrario: un vecchio script ricrea /etc/mysql/debian.cnf e improvvisamente non torna più nulla. I messaggi di errore che si cercano in quei casi sono:

ERROR 1698 (28000): Access denied for user 'root'@'localhost'
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
ERROR 1524 (HY000): Plugin 'unix_socket' is not loaded

L'errore 1698 significa: l'account si aspetta unix_socket, ma tu non sei l'utente di sistema giusto. Spesso basta anteporre un sudo. L'errore 1045 significa: viene richiesta una password e la tua non è corretta.

Se non si riesce più a entrare, si avvia il server senza il controllo dei permessi. Con MariaDB questo funziona in modo pulito tramite la variabile d'ambiente che la unit systemd fornita con il pacchetto valuta, senza toccare alcun file dei pacchetti:

systemctl stop mariadb
systemctl set-environment MYSQLD_OPTS="--skip-grant-tables --skip-networking"
systemctl start mariadb

Adesso ci si collega con mariadb -u root e si ripristina lo stato. È importante il FLUSH PRIVILEGES iniziale, perché senza tabelle dei permessi caricate ALTER USER fallisce:

FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED VIA unix_socket;

Dopo occorre assolutamente fare pulizia, altrimenti il server si avvia senza protezione a ogni riavvio:

systemctl stop mariadb
systemctl unset-environment MYSQLD_OPTS
systemctl start mariadb

Con MySQL 8.0 su Ubuntu questa strada non funziona, perché la unit non valuta una variabile del genere e perché ALTER USER sotto --skip-grant-tables nasconde insidie aggiuntive. Lì si usa un file di avvio. Deve trovarsi in una directory che AppArmor consente al processo del server, altrimenti l'avvio fallisce con Can't open file. /var/lib/mysql-files è consentita, /tmp no:

systemctl stop mysql
echo "ALTER USER 'root'@'localhost' IDENTIFIED WITH auth_socket;" > /var/lib/mysql-files/reset.sql
chown mysql:mysql /var/lib/mysql-files/reset.sql
systemctl edit mysql

Nell'editor si inserisce una sovrascrittura del comando di avvio, la prima riga vuota è necessaria per cancellare il valore originale:

[Service]
ExecStart=
ExecStart=/usr/sbin/mysqld --init-file=/var/lib/mysql-files/reset.sql

Dopo un systemctl daemon-reload e un avvio l'account è ripristinato. Successivamente si rimuove la sovrascrittura con systemctl revert mysql e si elimina il file. Chi gestisce i database su sistemi separati trova indicazioni complementari sulla protezione dell'accesso nell'articolo Proteggere il server SSH.

Un utente dedicato per ogni applicazione al posto di root

Il passo più efficace non compare in nessun assistente. Le applicazioni non devono collegarsi come root, e non hanno nemmeno bisogno di un GRANT ALL. Una tipica applicazione web legge e scrive righe, non crea database e non legge file dal server.

CREATE DATABASE shopdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'shopapp'@'localhost' IDENTIFIED BY 'QuiUnaLungaPasswordCasuale';
GRANT SELECT, INSERT, UPDATE, DELETE ON shopdb.* TO 'shopapp'@'localhost';

Tre cose contano qui. Primo, il punto in shopdb.* al posto di *.*: i permessi su *.* sono permessi globali e valgono anche per mysql e information_schema. Secondo, l'indicazione @'localhost' al posto di @'%': così l'account resta utilizzabile solo in locale, anche se un giorno la porta dovesse restare aperta. Terzo, manca WITH GRANT OPTION, perché un account che può cedere permessi è di fatto un amministratore.

Le modifiche allo schema passano poi per un secondo account, usato solo durante il deployment:

CREATE USER 'shopmigrate'@'localhost' IDENTIFIED BY 'UnAltraLungaPasswordCasuale';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP, INDEX, REFERENCES ON shopdb.* TO 'shopmigrate'@'localhost';

Sembra lavoro in più ed è esattamente il punto in cui una SQL injection nell'applicazione diventa una fastidiosa fuga di dati invece di una perdita totale. Un account senza DROP non può cancellare alcuna tabella.

Il controllo non si fa sul testo del GRANT, ma sul risultato:

SHOW GRANTS FOR 'shopapp'@'localhost';

Ci si aspettano esattamente due righe: un GRANT USAGE ON *.*, che rappresenta soltanto il diritto di autenticarsi e non comporta alcun accesso ai dati, e la riga con i quattro permessi su shopdb.*. Se lì compare ALL PRIVILEGES ON *.*, il punto era nel posto sbagliato. La seconda prova, più significativa, è un accesso con il nuovo account e un SHOW DATABASES;. Devono essere visibili solo information_schema e shopdb.

Un errore frequente in fase di creazione:

ERROR 1396 (HY000): Operation CREATE USER failed for 'shopapp'@'localhost'

Quasi sempre significa che l'account esiste già, spesso come residuo di un tentativo precedente. DROP USER 'shopapp'@'localhost'; e si ricomincia da capo.

bind-address: dove il database si mette davvero in ascolto

Su tutte e quattro le distribuzioni, dopo l'installazione del pacchetto il database ascolta solo su 127.0.0.1. Con MariaDB la riga si trova in /etc/mysql/mariadb.conf.d/50-server.cnf e con MySQL 8.0 in /etc/mysql/mysql.conf.d/mysqld.cnf. Meglio verificare che supporre:

grep -R "bind-address" /etc/mysql/

Con MySQL 8.0 esiste una seconda riga che viene volentieri trascurata: mysqlx-bind-address governa il protocollo X sulla porta 33060. Chi modifica solo bind-address apre eventualmente metà accesso oppure lascia aperta la seconda porta.

La verifica più affidabile non è il file di configurazione, ma il kernel:

ss -lntp
LISTEN 0 80 127.0.0.1:3306 0.0.0.0:* users:(("mariadbd",pid=712,fd=22))

Se lì compare 0.0.0.0:3306 oppure *:3306, il servizio è raggiungibile dalla rete. In aggiunta il server fornisce la propria versione dei fatti:

mariadb -e "SELECT @@bind_address, @@port, @@skip_networking;"

Qui sono diffuse due trappole. La prima: i file in mariadb.conf.d vengono letti in ordine alfabetico, e vince l'indicazione letta per ultima. Chi scrive la propria modifica in un file separato lo chiama quindi 99-eigene.cnf e non 10-eigene.cnf. È esattamente qui che si arenano la maggior parte delle segnalazioni in cui MariaDB ignorerebbe bind-address. Il vantaggio di un file proprio: gli aggiornamenti dei pacchetti non chiedono di risolvere conflitti, perché il file fornito resta intatto.

La seconda trappola: chi non ha affatto bisogno dell'accesso di rete va un passo oltre bind-address e imposta skip-networking. Allora non esiste più alcuna porta TCP, resta solo il socket Unix. È l'impostazione giusta per il caso standard di applicazione web e database sullo stesso server, ma costa nervi quando un'applicazione ha nella configurazione 127.0.0.1 invece di localhost: con 127.0.0.1 le librerie client forzano TCP.

Accesso dall'esterno solo se è davvero necessario

Una porta di database esposta in rete viene trovata nel giro di poche ore e provata di continuo. La risposta migliore alla domanda sull'accesso esterno è quindi evitarlo. Per la manutenzione occasionale basta un tunnel SSH che sul proprio computer mappa una porta locale sul socket del database della controparte. Lo strumento di amministrazione si collega poi a 127.0.0.1, senza che il server apra nulla verso la rete.

Per connessioni permanenti tra più server la soluzione pulita è un tunnel WireGuard. Il database si lega allora esclusivamente all'indirizzo del tunnel, non all'indirizzo IP pubblico.

Se proprio deve esserci una porta aperta, quattro misure agiscono insieme. Il server si lega a esattamente un indirizzo interno. Il firewall lascia passare solo l'indirizzo sorgente noto. L'account di database è legato allo stesso indirizzo, quindi 'shopapp'@'10.0.0.5' e mai 'shopapp'@'%'. E la connessione viene imposta cifrata:

ALTER USER 'shopapp'@'10.0.0.5' REQUIRE SSL;

Provando dall'esterno si incontrano due errori che vengono spesso confusi. Il primo significa che nessuno risponde, quindi firewall oppure bind-address:

ERROR 2003 (HY000): Can't connect to MySQL server on '203.0.113.10:3306' (110)

Il secondo significa che il server risponde e rifiuta consapevolmente la connessione, manca quindi l'account adatto a questa provenienza:

ERROR 1130 (HY000): Host '203.0.113.55' is not allowed to connect to this MariaDB server

In locale, invece, il classico è che il servizio semplicemente non sia in esecuzione:

ERROR 2002 (HY000): Can't connect to local server through socket '/run/mysqld/mysqld.sock' (2)

Password mai sulla riga di comando

La chiamata mariadb -u shopapp -pGeheim123 funziona ed è comunque un errore. Lo dice il server stesso:

Warning: Using a password on the command line interface can be insecure.

Dietro ci sono due motivi. Primo, la riga finisce nella cronologia della shell. Secondo, su un sistema standard la riga di comando di un processo è visibile tramite ps a qualunque utente collegato. Su un server con più clienti o più servizi questo equivale a una raccolta di password senza il minimo sforzo.

La strada giusta per le persone è -p senza nulla dopo. In quel caso la password viene chiesta in modo interattivo e nella cronologia non resta nulla:

mariadb -u shopapp -p shopdb

La strada giusta per script e cron job è un file di opzioni con permessi ristretti. Lo si crea con il modo corretto in un unico passaggio:

install -m 600 /dev/null /root/.my.cnf

Contenuto:

[client]
user=backup
password=QuiUnaLungaPasswordCasuale

Da quel momento ogni client trova da solo le credenziali. Per compiti separati si creano più file e li si richiama in modo mirato. Vale però una regola su cui in molti inciampano: --defaults-extra-file e --defaults-file devono essere la prima opzione della chiamata, altrimenti vengono ignorati senza alcun avviso.

mariadb-dump --defaults-extra-file=/root/.my-backup.cnf --single-transaction shopdb

La variabile d'ambiente MYSQL_PWD non è una soluzione. Si trova in /proc ed è quindi visibile più o meno quanto la riga di comando. Sotto MySQL 8.0 esiste in aggiunta mysql_config_editor, che scrive un file ~/.mylogin.cnf. Il contenuto è offuscato, ma non cifrato, e MariaDB non conosce questo strumento. In ambienti misti il semplice file di opzioni con modo 600 è la scelta più affidabile.

Un ultimo punto riguarda i backup. Un dump contiene tutto ciò che l'applicazione vede, e un account di backup non ha bisogno di permessi di scrittura. Per mariadb-dump con --single-transaction di norma basta:

GRANT SELECT, SHOW VIEW, TRIGGER, LOCK TABLES ON shopdb.* TO 'backup'@'localhost';
GRANT PROCESS ON *.* TO 'backup'@'localhost';

Collaudo: da cosa si capisce che tiene

Un comando che va a buon fine non dimostra nulla. Queste sei prove dimostrano qualcosa:

  1. L'account root usa l'autenticazione via socket: mariadb -e "SELECT User, Host, JSON_VALUE(Priv,'$.plugin') AS plugin, JSON_QUERY(Priv,'$.auth_or') AS auth_or FROM mysql.global_priv;" mostra per root un unix_socket, nella colonna plugin oppure sotto auth_or. La vista mysql.user non serve allo scopo, restituisce solo il primo metodo. Sotto MySQL 8.0 fa fede al contrario la colonna plugin in mysql.user, lì deve comparire auth_socket.
  2. Non esistono account anonimi: mariadb -e "SELECT user, host FROM mysql.user WHERE user = '';" restituisce un insieme di risultati vuoto.
  3. Il database di prova non c'è più: mariadb -e "SHOW DATABASES LIKE 'test';" non restituisce nulla.
  4. La porta è chiusa: ss -lntp per la 3306 non mostra nulla oppure esclusivamente 127.0.0.1.
  5. L'account applicativo è limitato: un accesso con quell'account mostra in SHOW DATABASES; solo il proprio database, e un DROP TABLE fallisce.
  6. Nessuna password in chiaro in circolazione: grep -rs "password" /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/ non trova nulla, e lo stesso vale per crontab -l di root e per un'occhiata in /var/spool/cron/crontabs/, e i file di opzioni hanno modo 600. L'opzione -s fa parte del comando, perché altrimenti una directory mancante interrompe la chiamata con codice di uscita 2: su Debian 13 /etc/cron.d non esiste dopo una semplice installazione del database, lì non è ancora installato alcun pacchetto cron.

Chi affronta questi sei punti su un server nuovo ha già escluso la stragrande maggioranza degli attacchi contro i database, senza installare un solo software aggiuntivo. Il resto è disciplina negli aggiornamenti e un backup funzionante, il cui ripristino sia stato provato almeno una volta.

Domande frequenti

Con MariaDB serve ancora una password di root?
Su Debian 12 e 13 e su Ubuntu 22.04 e 24.04 no. L'account root@localhost usa lì di default il plugin unix_socket, quindi l'accesso passa dall'identità dell'utente di sistema. Una password aggiuntiva non aumenta la sicurezza, crea solo un segreto in più che può andare perso o trapelare. Solo se uno strumento richiede necessariamente un accesso con password conviene creare un account dedicato, invece di modificare root.
Perché il mio Debian dice che il pacchetto mysql-server non esiste?
Da diverse versioni Debian non distribuisce più un pacchetto mysql-server proprio. Né su Debian 12 né su Debian 13 esiste una versione installabile: apt-cache policy mysql-server riporta Candidate: (none) e una Version table vuota, perché il nome esiste ormai solo in forma virtuale attraverso mariadb-server. A renderlo visibile è apt-cache showpkg mysql-server. Il comando per l'installazione è apt install -y mariadb-server. Su Ubuntu 22.04 e 24.04 sono disponibili entrambi, MySQL nella versione 8.0.46 e MariaDB nella 10.6 oppure nella 10.11 a seconda della distribuzione.
Come rientro nel database se mi sono chiuso fuori?
Con MariaDB si ferma il servizio, si imposta con systemctl set-environment la variabile MYSQLD_OPTS su --skip-grant-tables --skip-networking e si riavvia. Dopo l'accesso serve prima un FLUSH PRIVILEGES, poi l'account si ripristina con ALTER USER. Alla fine si rimuove di nuovo la variabile con systemctl unset-environment. Con MySQL 8.0 questa strada non funziona, lì si usa un file di avvio tramite --init-file, che a causa di AppArmor deve trovarsi in /var/lib/mysql-files.
Che differenza c'è tra ERROR 1698 ed ERROR 1045?
ERROR 1698 (28000) significa che l'account si aspetta l'autenticazione via socket e che l'utente di sistema chiamante non corrisponde. Qui di solito basta un sudo davanti al comando. ERROR 1045 (28000) con l'aggiunta (using password: YES) significa invece che è stato tentato un accesso con password e che la password non è corretta. I due errori hanno quindi cause e soluzioni completamente diverse.
bind-address = 127.0.0.1 basta a proteggere il database?
È il passo più importante, ma non l'unico. Con MySQL 8.0 esiste in più mysqlx-bind-address per la porta 33060, che va impostato separatamente. Inoltre in /etc/mysql/mariadb.conf.d vince l'ultimo file letto, quindi le modifiche proprie vanno in un file con numero alto come 99-eigene.cnf. Il risultato si verifica sempre con ss -lntp, mai nel file di configurazione.
Perché l'applicazione non deve accedere al database come root?
Perché così ogni falla nell'applicazione porta all'accesso completo su tutti i database, tabelle dei permessi comprese. Un account con GRANT SELECT, INSERT, UPDATE, DELETE ON miodb.* non può cancellare alcuna tabella, non può leggere database altrui e non può assegnare permessi. Le modifiche allo schema passano per un account separato, usato solo durante il deployment.
Come passo in sicurezza una password di database a un cron job?
Tramite un file di opzioni con modo 600, creato per esempio con install -m 600 /dev/null /root/.my.cnf e contenente una sezione [client] con user e password. La chiamata vi rimanda poi con --defaults-extra-file, obbligatoriamente come prima opzione, altrimenti l'argomento viene ignorato. Le password scritte direttamente dopo -p sulla riga di comando sono visibili a qualsiasi utente tramite ps, e MYSQL_PWD è consultabile in modo analogo via /proc.

MariaDB MySQL Database Sicurezza server Debian Ubuntu Amministrazione Linux