nixos/mysql: don't run parts of mysqld.service as root (#61589)

nixos/mysql: don't run parts of mysqld.service as root
This commit is contained in:
Florian Klink 2019-05-31 22:29:32 +02:00 committed by GitHub
commit d2905ff559
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 78 additions and 64 deletions

View file

@ -93,6 +93,14 @@
the module for some time and so was removed as cleanup.
</para>
</listitem>
<listitem>
<para>
The <option>services.mysql.pidDir</option> option was removed, as it was only used by the wordpress
apache-httpd service to wait for mysql to have started up.
This can be accomplished by either describing a dependency on mysql.service (preferred)
or waiting for the (hardcoded) <filename>/run/mysqld/mysql.sock</filename> file to appear.
</para>
</listitem>
<listitem>
<para>
The <option>services.emby.enable</option> module has been removed, see
@ -161,6 +169,17 @@
The <literal>hunspellDicts.fr-any</literal> dictionary now ships with <literal>fr_FR.{aff,dic}</literal>
which is linked to <literal>fr-toutesvariantes.{aff,dic}</literal>.
</para>
</listitem>
<listitem>
<para>
The <literal>mysql</literal> service now runs as <literal>mysql</literal>
user. Previously, systemd did execute it as root, and mysql dropped privileges
itself.
This includes <literal>ExecStartPre=</literal> and
<literal>ExecStartPost=</literal> phases.
To accomplish that, runtime and data directory setup was delegated to
RuntimeDirectory and tmpfiles.
</para>
</listitem>
</itemizedlist>
</section>

View file

@ -212,6 +212,7 @@ with lib;
(mkRemovedOptionModule [ "services" "logstash" "enableWeb" ] "The web interface was removed from logstash")
(mkRemovedOptionModule [ "boot" "zfs" "enableLegacyCrypto" ] "The corresponding package was removed from nixpkgs.")
(mkRemovedOptionModule [ "services" "winstone" ] "The corresponding package was removed from nixpkgs.")
(mkRemovedOptionModule [ "services" "mysql" "pidDir" ] "Don't wait for pidfiles, describe dependencies through systemd")
# ZSH
(mkRenamedOptionModule [ "programs" "zsh" "enableSyntaxHighlighting" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ])

View file

@ -18,16 +18,12 @@ let
in (pName mysql == pName pkgs.mysql57)
&& ((builtins.compareVersions mysql.version "5.7") >= 0);
pidFile = "${cfg.pidDir}/mysqld.pid";
mysqldAndInstallOptions =
"--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${mysql}";
mysqldOptions =
"${mysqldAndInstallOptions} --pid-file=${pidFile}";
"--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${mysql}";
# For MySQL 5.7+, --insecure creates the root user without password
# (earlier versions and MariaDB do this by default).
installOptions =
"${mysqldAndInstallOptions} ${lib.optionalString isMysqlAtLeast57 "--insecure"}";
"${mysqldOptions} ${lib.optionalString isMysqlAtLeast57 "--insecure"}";
in
@ -80,11 +76,6 @@ in
description = "Location where MySQL stores its table files";
};
pidDir = mkOption {
default = "/run/mysqld";
description = "Location of the file which stores the PID of the MySQL server";
};
extraOptions = mkOption {
type = types.lines;
default = "";
@ -296,6 +287,10 @@ in
${cfg.extraOptions}
'';
systemd.tmpfiles.rules = [
"d '${cfg.dataDir}' 0700 ${cfg.user} mysql -"
];
systemd.services.mysql = let
hasNotify = (cfg.package == pkgs.mariadb);
in {
@ -313,70 +308,69 @@ in
pkgs.nettools
];
preStart =
''
if ! test -e ${cfg.dataDir}/mysql; then
mkdir -m 0700 -p ${cfg.dataDir}
chown -R ${cfg.user} ${cfg.dataDir}
${mysql}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${installOptions}
touch /tmp/mysql_init
fi
mkdir -m 0755 -p ${cfg.pidDir}
chown -R ${cfg.user} ${cfg.pidDir}
'';
preStart = ''
if ! test -e ${cfg.dataDir}/mysql; then
${mysql}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${installOptions}
touch /tmp/mysql_init
fi
'';
serviceConfig = {
User = cfg.user;
Group = "mysql";
Type = if hasNotify then "notify" else "simple";
RuntimeDirectory = "mysqld";
RuntimeDirectoryMode = "0755";
# The last two environment variables are used for starting Galera clusters
ExecStart = "${mysql}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION";
};
postStart = ''
${lib.optionalString (!hasNotify) ''
# Wait until the MySQL server is available for use
count=0
while [ ! -e /run/mysqld/mysqld.sock ]
do
if [ $count -eq 30 ]
then
echo "Tried 30 times, giving up..."
exit 1
fi
postStart =
let
cmdWatchForMysqlSocket = ''
# Wait until the MySQL server is available for use
count=0
while [ ! -e /run/mysqld/mysqld.sock ]
do
if [ $count -eq 30 ]
then
echo "Tried 30 times, giving up..."
exit 1
fi
echo "MySQL daemon not yet started. Waiting for 1 second..."
count=$((count++))
sleep 1
done
''}
echo "MySQL daemon not yet started. Waiting for 1 second..."
count=$((count++))
sleep 1
done
'';
cmdInitialDatabases = concatMapStrings (database: ''
# Create initial databases
if ! test -e "${cfg.dataDir}/${database.name}"; then
echo "Creating initial database: ${database.name}"
( echo 'create database `${database.name}`;'
${optionalString (database.schema != null) ''
echo 'use `${database.name}`;'
# TODO: this silently falls through if database.schema does not exist,
# we should catch this somehow and exit, but can't do it here because we're in a subshell.
if [ -f "${database.schema}" ]
then
cat ${database.schema}
elif [ -d "${database.schema}" ]
then
cat ${database.schema}/mysql-databases/*.sql
fi
''}
) | ${mysql}/bin/mysql -u root -N
fi
'') cfg.initialDatabases;
in
lib.optionalString (!hasNotify) cmdWatchForMysqlSocket + ''
if [ -f /tmp/mysql_init ]
then
${concatMapStrings (database:
''
# Create initial databases
if ! test -e "${cfg.dataDir}/${database.name}"; then
echo "Creating initial database: ${database.name}"
( echo 'create database `${database.name}`;'
${optionalString (database.schema != null) ''
echo 'use `${database.name}`;'
# TODO: this silently falls through if database.schema does not exist,
# we should catch this somehow and exit, but can't do it here because we're in a subshell.
if [ -f "${database.schema}" ]
then
cat ${database.schema}
elif [ -d "${database.schema}" ]
then
cat ${database.schema}/mysql-databases/*.sql
fi
''}
) | ${mysql}/bin/mysql -u root -N
fi
'') cfg.initialDatabases}
${cmdInitialDatabases}
${optionalString (cfg.replication.role == "master")
''
# Set up the replication master

View file

@ -273,7 +273,7 @@ in
if [ ! -d ${serverInfo.fullConfig.services.mysql.dataDir}/${config.dbName} ]; then
echo "Need to create the database '${config.dbName}' and grant permissions to user named '${config.dbUser}'."
# Wait until MySQL is up
while [ ! -e ${serverInfo.fullConfig.services.mysql.pidDir}/mysqld.pid ]; do
while [ ! -S /run/mysqld/mysqld.sock ]; do
sleep 1
done
${pkgs.mysql}/bin/mysql -e 'CREATE DATABASE ${config.dbName};'