--- /dev/null
+{ lib, pkgs, config, ... }:
+let
+ cfg = config.myServices.databases.mariadb;
+in {
+ options.myServices.databases = {
+ mariadb = {
+ enable = lib.mkOption {
+ default = false;
+ example = true;
+ description = "Whether to enable mariadb database";
+ type = lib.types.bool;
+ };
+ package = lib.mkOption {
+ type = lib.types.package;
+ default = pkgs.mariadb;
+ description = ''
+ Mariadb package to use.
+ '';
+ };
+ credentials = lib.mkOption {
+ default = {};
+ description = "Credentials";
+ type = lib.types.attrsOf lib.types.str;
+ };
+ ldapConfig = lib.mkOption {
+ description = "LDAP configuration to allow PAM identification via LDAP";
+ type = lib.types.submodule {
+ options = {
+ host = lib.mkOption { type = lib.types.str; };
+ base = lib.mkOption { type = lib.types.str; };
+ dn = lib.mkOption { type = lib.types.str; };
+ password = lib.mkOption { type = lib.types.str; };
+ filter = lib.mkOption { type = lib.types.str; };
+ };
+ };
+ };
+ replicationLdapConfig = lib.mkOption {
+ description = "LDAP configuration to allow replication";
+ type = lib.types.submodule {
+ options = {
+ host = lib.mkOption { type = lib.types.str; };
+ base = lib.mkOption { type = lib.types.str; };
+ dn = lib.mkOption { type = lib.types.str; };
+ password = lib.mkOption { type = lib.types.str; };
+ };
+ };
+ };
+ dataDir = lib.mkOption {
+ type = lib.types.path;
+ default = "/var/lib/mysql";
+ description = ''
+ The directory where Mariadb stores its data.
+ '';
+ };
+ # Output variables
+ socketsDir = lib.mkOption {
+ type = lib.types.path;
+ default = "/run/mysqld";
+ description = ''
+ The directory where Mariadb puts sockets.
+ '';
+ };
+ sockets = lib.mkOption {
+ type = lib.types.attrsOf lib.types.path;
+ default = {
+ mysqld = "${cfg.socketsDir}/mysqld.sock";
+ };
+ readOnly = true;
+ description = ''
+ Mariadb sockets
+ '';
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ networking.firewall.allowedTCPPorts = [ config.myEnv.databases.mysql.port ];
+
+ # for adminer, ssl is implemented with mysqli only, which is
+ # currently disabled because it’s not compatible with pam.
+ # Thus we need to generate two users for each 'remote': one remote
+ # with SSL, and one localhost without SSL.
+ # User identified by LDAP:
+ # CREATE USER foo@% IDENTIFIED VIA pam USING 'mysql' REQUIRE SSL;
+ # CREATE USER foo@localhost IDENTIFIED VIA pam USING 'mysql';
+
+ # To create a user (host) for replication:
+ # CREATE USER 'host'@'%' IDENTIFIED VIA pam USING 'mysql_replication' REQUIRE SSL;
+ # GRANT REPLICATION SLAVE, REPLICATION CLIENT, RELOAD, LOCK TABLES, SELECT, SHOW VIEW ON *.* TO 'host'@'%';
+ # (the lock/select grant permits to let the replication host handle
+ # the initial fetch of the database)
+ # % should be valid for both localhost (for cron dumps) and the origin host.
+ services.mysql = {
+ enable = true;
+ package = cfg.package;
+ dataDir = cfg.dataDir;
+ settings = {
+ mysqld = {
+ port = config.myEnv.databases.mysql.port;
+ ssl_ca = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
+ ssl_key = "${config.security.acme.certs.mysql.directory}/key.pem";
+ ssl_cert = "${config.security.acme.certs.mysql.directory}/fullchain.pem";
+
+ # for replication
+ log-bin = "mariadb-bin";
+ server-id = "1";
+
+ # this introduces a small delay before storing on disk, but
+ # makes it order of magnitudes quicker
+ innodb_flush_log_at_trx_commit = "0";
+
+ # This is necessary since the default ("dialog") is not
+ # supported by php's mysqlnd plugin (in mysqli). But with that
+ # change only regular login+password schemes can work (no
+ # "fancy" authentication methods like fprintd or keys)
+ pam_use_cleartext_plugin = true;
+ };
+ };
+ };
+
+ users.users.mysql.extraGroups = [ "keys" ];
+ security.acme.certs."mysql" = {
+ group = "mysql";
+ domain = "db-1.immae.eu";
+ postRun = ''
+ systemctl restart mysql.service
+ '';
+ };
+
+ secrets.keys = {
+ "mysql/mysqldump" = {
+ permissions = "0400";
+ user = "root";
+ group = "root";
+ text = ''
+ [mysqldump]
+ user = root
+ password = ${cfg.credentials.root}
+ '';
+ };
+ "mysql/pam" = {
+ permissions = "0400";
+ user = "mysql";
+ group = "mysql";
+ text = with cfg.ldapConfig; ''
+ host ${host}
+ base ${base}
+ binddn ${dn}
+ bindpw ${password}
+ pam_filter ${filter}
+ ssl start_tls
+ '';
+ };
+ "mysql/pam_replication" = {
+ permissions = "0400";
+ user = "mysql";
+ group = "mysql";
+ text = with cfg.replicationLdapConfig; ''
+ host ${host}
+ base ${base}
+ binddn ${dn}
+ bindpw ${password}
+ pam_login_attribute cn
+ ssl start_tls
+ '';
+ };
+ };
+
+ security.pam.services = let
+ pam_ldap = "${pkgs.pam_ldap}/lib/security/pam_ldap.so";
+ in {
+ mysql = {
+ text = ''
+ # https://mariadb.com/kb/en/mariadb/pam-authentication-plugin/
+ auth required ${pam_ldap} config=${config.secrets.fullPaths."mysql/pam"}
+ account required ${pam_ldap} config=${config.secrets.fullPaths."mysql/pam"}
+ '';
+ };
+ mysql_replication = {
+ text = ''
+ auth required ${pam_ldap} config=${config.secrets.fullPaths."mysql/pam_replication"}
+ account required ${pam_ldap} config=${config.secrets.fullPaths."mysql/pam_replication"}
+ '';
+ };
+ };
+
+ };
+}