summaryrefslogtreecommitdiffstats
path: root/nixos/modules/services/databases
diff options
context:
space:
mode:
author1000101 <b1000101@pm.me>2023-07-22 12:49:23 +0200
committerGitHub <noreply@github.com>2023-07-22 12:49:23 +0200
commitf63d863fde0106cb2380de182a1e217e339abe12 (patch)
treeed7d25c486ffab2d33b3b7fae92c81de0b0c0b0b /nixos/modules/services/databases
parent7e1e290b9fa6c66e69ffe9dc8f133ec369e9db7a (diff)
nixos/pgbouncer: init (#241578)
Co-authored-by: Marek Mahut <marek.mahut@gmail.com>
Diffstat (limited to 'nixos/modules/services/databases')
-rw-r--r--nixos/modules/services/databases/pgbouncer.nix632
1 files changed, 632 insertions, 0 deletions
diff --git a/nixos/modules/services/databases/pgbouncer.nix b/nixos/modules/services/databases/pgbouncer.nix
new file mode 100644
index 000000000000..1aec03c114d1
--- /dev/null
+++ b/nixos/modules/services/databases/pgbouncer.nix
@@ -0,0 +1,632 @@
+{ lib, pkgs, config, ... } :
+
+with lib;
+
+let
+ cfg = config.services.pgbouncer;
+
+ confFile = pkgs.writeTextFile {
+ name = "pgbouncer.ini";
+ text = ''
+ [databases]
+ ${concatStringsSep "\n"
+ (mapAttrsToList (dbname : settings : "${dbname} = ${settings}") cfg.databases)}
+
+ [users]
+ ${concatStringsSep "\n"
+ (mapAttrsToList (username : settings : "${username} = ${settings}") cfg.users)}
+
+ [peers]
+ ${concatStringsSep "\n"
+ (mapAttrsToList (peerid : settings : "${peerid} = ${settings}") cfg.peers)}
+
+ [pgbouncer]
+ # general
+ ${optionalString (cfg.ignoreStartupParameters != null) "ignore_startup_parameters = ${cfg.ignoreStartupParameters}"}
+ listen_port = ${toString cfg.listenPort}
+ ${optionalString (cfg.listenAddress != null) "listen_addr = ${cfg.listenAddress}"}
+ pool_mode = ${cfg.poolMode}
+ max_client_conn = ${toString cfg.maxClientConn}
+ default_pool_size = ${toString cfg.defaultPoolSize}
+ max_user_connections = ${toString cfg.maxUserConnections}
+ max_db_connections = ${toString cfg.maxDbConnections}
+
+ #auth
+ auth_type = ${cfg.authType}
+ ${optionalString (cfg.authHbaFile != null) "auth_hba_file = ${cfg.authHbaFile}"}
+ ${optionalString (cfg.authFile != null) "auth_file = ${cfg.authFile}"}
+ ${optionalString (cfg.authUser != null) "auth_user = ${cfg.authUser}"}
+ ${optionalString (cfg.authQuery != null) "auth_query = ${cfg.authQuery}"}
+ ${optionalString (cfg.authDbname != null) "auth_dbname = ${cfg.authDbname}"}
+
+ # TLS
+ ${optionalString (cfg.tls.client != null) ''
+ client_tls_sslmode = ${cfg.tls.client.sslmode}
+ client_tls_key_file = ${cfg.tls.client.keyFile}
+ client_tls_cert_file = ${cfg.tls.client.certFile}
+ client_tls_ca_file = ${cfg.tls.client.caFile}
+ ''}
+ ${optionalString (cfg.tls.server != null) ''
+ server_tls_sslmode = ${cfg.tls.server.sslmode}
+ server_tls_key_file = ${cfg.tls.server.keyFile}
+ server_tls_cert_file = ${cfg.tls.server.certFile}
+ server_tls_ca_file = ${cfg.tls.server.caFile}
+ ''}
+
+ # log
+ ${optionalString (cfg.logFile != null) "logfile = ${cfg.homeDir}/${cfg.logFile}"}
+ ${optionalString (cfg.syslog != null) ''
+ syslog = ${if cfg.syslog.enable then "1" else "0"}
+ syslog_ident = ${cfg.syslog.syslogIdent}
+ syslog_facility = ${cfg.syslog.syslogFacility}
+ ''}
+ ${optionalString (cfg.verbose != null) "verbose = ${toString cfg.verbose}"}
+
+ # console access
+ ${optionalString (cfg.adminUsers != null) "admin_users = ${cfg.adminUsers}"}
+ ${optionalString (cfg.statsUsers != null) "stats_users = ${cfg.statsUsers}"}
+
+ # linux
+ pidfile = /run/pgbouncer/pgbouncer.pid
+
+ # extra
+ ${cfg.extraConfig}
+ '';
+ };
+
+in {
+
+ options.services.pgbouncer = {
+
+ # NixOS settings
+
+ enable = mkEnableOption (lib.mdDoc "PostgreSQL connection pooler");
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.pgbouncer;
+ defaultText = literalExpression "pkgs.pgbouncer";
+ description = lib.mdDoc ''
+ The pgbouncer package to use.
+ '';
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = lib.mdDoc ''
+ Whether to automatically open the specified TCP port in the firewall.
+ '';
+ };
+
+ # Generic settings
+
+ logFile = mkOption {
+ type = types.nullOr types.str;
+ default = "pgbouncer.log";
+ description = lib.mdDoc ''
+ Specifies the log file.
+ Either this or syslog has to be specified.
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.nullOr types.commas;
+ example = "*";
+ default = null;
+ description = lib.mdDoc ''
+ Specifies a list (comma-separated) of addresses where to listen for TCP connections.
+ You may also use * meaning “listen on all addresses”.
+ When not set, only Unix socket connections are accepted.
+
+ Addresses can be specified numerically (IPv4/IPv6) or by name.
+ '';
+ };
+
+ listenPort = mkOption {
+ type = types.port;
+ default = 6432;
+ description = lib.mdDoc ''
+ Which port to listen on. Applies to both TCP and Unix sockets.
+ '';
+ };
+
+ poolMode = mkOption {
+ type = types.enum [ "session" "transaction" "statement" ];
+ default = "session";
+ description = lib.mdDoc ''
+ Specifies when a server connection can be reused by other clients.
+
+ session
+ Server is released back to pool after client disconnects. Default.
+ transaction
+ Server is released back to pool after transaction finishes.
+ statement
+ Server is released back to pool after query finishes.
+ Transactions spanning multiple statements are disallowed in this mode.
+ '';
+ };
+
+ maxClientConn = mkOption {
+ type = types.int;
+ default = 100;
+ description = lib.mdDoc ''
+ Maximum number of client connections allowed.
+
+ When this setting is increased, then the file descriptor limits in the operating system
+ might also have to be increased. Note that the number of file descriptors potentially
+ used is more than maxClientConn. If each user connects under its own user name to the server,
+ the theoretical maximum used is:
+ maxClientConn + (max pool_size * total databases * total users)
+
+ If a database user is specified in the connection string (all users connect under the same user name),
+ the theoretical maximum is:
+ maxClientConn + (max pool_size * total databases)
+
+ The theoretical maximum should never be reached, unless somebody deliberately crafts a special load for it.
+ Still, it means you should set the number of file descriptors to a safely high number.
+ '';
+ };
+
+ defaultPoolSize = mkOption {
+ type = types.int;
+ default = 20;
+ description = lib.mdDoc ''
+ How many server connections to allow per user/database pair.
+ Can be overridden in the per-database configuration.
+ '';
+ };
+
+ maxDbConnections = mkOption {
+ type = types.int;
+ default = 0;
+ description = lib.mdDoc ''
+ Do not allow more than this many server connections per database (regardless of user).
+ This considers the PgBouncer database that the client has connected to,
+ not the PostgreSQL database of the outgoing connection.
+
+ This can also be set per database in the [databases] section.
+
+ Note that when you hit the limit, closing a client connection to one pool will
+ not immediately allow a server connection to be established for another pool,
+ because the server connection for the first pool is still open.
+ Once the server connection closes (due to idle timeout),
+ a new server connection will immediately be opened for the waiting pool.
+
+ 0 = unlimited
+ '';
+ };
+
+ maxUserConnections = mkOption {
+ type = types.int;
+ default = 0;
+ description = lib.mdDoc ''
+ Do not allow more than this many server connections per user (regardless of database).
+ This considers the PgBouncer user that is associated with a pool,
+ which is either the user specified for the server connection
+ or in absence of that the user the client has connected as.
+
+ This can also be set per user in the [users] section.
+
+ Note that when you hit the limit, closing a client connection to one pool
+ will not immediately allow a server connection to be established for another pool,
+ because the server connection for the first pool is still open.
+ Once the server connection closes (due to idle timeout), a new server connection
+ will immediately be opened for the waiting pool.
+
+ 0 = unlimited
+ '';
+ };
+
+ ignoreStartupParameters = mkOption {
+ type = types.nullOr types.commas;
+ example = "extra_float_digits";
+ default = null;
+ description = lib.mdDoc ''
+ By default, PgBouncer allows only parameters it can keep track of in startup packets:
+ client_encoding, datestyle, timezone and standard_conforming_strings.
+
+ All others parameters will raise an error.
+ To allow others parameters, they can be specified here, so that PgBouncer knows that
+ they are handled by the admin and it can ignore them.
+
+ If you need to specify multiple values, use a comma-separated list.
+
+ IMPORTANT: When using prometheus-pgbouncer-exporter, you need:
+ extra_float_digits
+ <https://github.com/prometheus-community/pgbouncer_exporter#pgbouncer-configuration>
+ '';
+ };
+
+ # Section [databases]
+ databases = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ example = {
+ exampledb = "host=/run/postgresql/ port=5432 auth_user=exampleuser dbname=exampledb sslmode=require";
+ bardb = "host=localhost dbname=bazdb";
+ foodb = "host=host1.example.com port=5432";
+ };
+ description = lib.mdDoc ''
+ Detailed information about PostgreSQL database definitions:
+ <https://www.pgbouncer.org/config.html#section-databases>
+ '';
+ };
+
+ # Section [users]
+ users = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ example = {
+ user1 = "pool_mode=session";
+ };
+ description = lib.mdDoc ''
+ Optional.
+
+ Detailed information about PostgreSQL user definitions:
+ <https://www.pgbouncer.org/config.html#section-users>
+ '';
+ };
+
+ # Section [peers]
+ peers = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ example = {
+ "1" = "host=host1.example.com";
+ "2" = "host=/tmp/pgbouncer-2 port=5555";
+ };
+ description = lib.mdDoc ''
+ Optional.
+
+ Detailed information about PostgreSQL database definitions:
+ <https://www.pgbouncer.org/config.html#section-peers>
+ '';
+ };
+
+ # Authentication settings
+ authType = mkOption {
+ type = types.enum [ "cert" "md5" "scram-sha-256" "plain" "trust" "any" "hba" "pam" ];
+ default = "md5";
+ description = lib.mdDoc ''
+ How to authenticate users.
+
+ cert
+ Client must connect over TLS connection with a valid client certificate.
+ The user name is then taken from the CommonName field from the certificate.
+ md5
+ Use MD5-based password check. This is the default authentication method.
+ authFile may contain both MD5-encrypted and plain-text passwords.
+ If md5 is configured and a user has a SCRAM secret, then SCRAM authentication is used automatically instead.
+ scram-sha-256
+ Use password check with SCRAM-SHA-256. authFile has to contain SCRAM secrets or plain-text passwords.
+ plain
+ The clear-text password is sent over the wire. Deprecated.
+ trust
+ No authentication is done. The user name must still exist in authFile.
+ any
+ Like the trust method, but the user name given is ignored.
+ Requires that all databases are configured to log in as a specific user.
+ Additionally, the console database allows any user to log in as admin.
+ hba
+ The actual authentication type is loaded from authHbaFile.
+ This allows different authentication methods for different access paths,
+ for example: connections over Unix socket use the peer auth method, connections over TCP must use TLS.
+ pam
+ PAM is used to authenticate users, authFile is ignored.
+ This method is not compatible with databases using the authUser option.
+ The service name reported to PAM is “pgbouncer”. pam is not supported in the HBA configuration file.
+ '';
+ };
+
+ authHbaFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/secrets/pgbouncer_hba";
+ description = lib.mdDoc ''
+ HBA configuration file to use when authType is hba.
+
+ See HBA file format details:
+ <https://www.pgbouncer.org/config.html#hba-file-format>
+ '';
+ };
+
+ authFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/secrets/pgbouncer_authfile";
+ description = lib.mdDoc ''
+ The name of the file to load user names and passwords from.
+
+ See section Authentication file format details:
+ <https://www.pgbouncer.org/config.html#authentication-file-format>
+
+ Most authentication types require that either authFile or authUser be set;
+ otherwise there would be no users defined.
+ '';
+ };
+
+ authUser = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "pgbouncer";
+ description = lib.mdDoc ''
+ If authUser is set, then any user not specified in authFile will be queried
+ through the authQuery query from pg_shadow in the database, using authUser.
+ The password of authUser will be taken from authFile.
+ (If the authUser does not require a password then it does not need to be defined in authFile.)
+
+ Direct access to pg_shadow requires admin rights.
+ It's preferable to use a non-superuser that calls a SECURITY DEFINER function instead.
+ '';
+ };
+
+ authQuery = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "SELECT usename, passwd FROM pg_shadow WHERE usename=$1";
+ description = lib.mdDoc ''
+ Query to load user's password from database.
+
+ Direct access to pg_shadow requires admin rights.
+ It's preferable to use a non-superuser that calls a SECURITY DEFINER function instead.
+
+ Note that the query is run inside the target database.
+ So if a function is used, it needs to be installed into each database.
+ '';
+ };
+
+ authDbname = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "authdb";
+ description = lib.mdDoc ''
+ Database name in the [database] section to be used for authentication purposes.
+ This option can be either global or overriden in the connection string if this parameter is specified.
+ '';
+ };
+
+ # TLS settings
+ tls.client = mkOption {
+ type = types.nullOr (types.submodule {
+ options = {
+ sslmode = mkOption {
+ type = types.enum [ "disable" "allow" "prefer" "require" "verify-ca" "verify-full" ];
+ default = "disable";
+ description = lib.mdDoc ''
+ TLS mode to use for connections from clients.
+ TLS connections are disabled by default.
+
+ When enabled, tls.client.keyFile and tls.client.certFile
+ must be also configured to set up the key and certificate
+ PgBouncer uses to accept client connections.
+
+ disable
+ Plain TCP. If client requests TLS, it's ignored. Default.
+ allow
+ If client requests TLS, it is used. If not, plain TCP is used.
+ If the client presents a client certificate, it is not validated.
+ prefer
+ Same as allow.
+ require
+ Client must use TLS. If not, the client connection is rejected.
+ If the client presents a client certificate, it is not validated.
+ verify-ca
+ Client must use TLS with valid client certificate.
+ verify-full
+ Same as verify-ca
+ '';
+ };
+ certFile = mkOption {
+ type = types.path;
+ example = "/secrets/pgbouncer.key";
+ description = lib.mdDoc "Path to certificate for private key. Clients can validate it";
+ };
+ keyFile = mkOption {
+ type = types.path;
+ example = "/secrets/pgbouncer.crt";
+ description = lib.mdDoc "Path to private key for PgBouncer to accept client connections";
+ };
+ caFile = mkOption {
+ type = types.path;
+ example = "/secrets/pgbouncer.crt";
+ description = lib.mdDoc "Path to root certificate file to validate client certificates";
+ };
+ };
+ });
+ default = null;
+ description = lib.mdDoc ''
+ <https://www.pgbouncer.org/config.html#tls-settings>
+ '';
+ };
+
+ tls.server = mkOption {
+ type = types.nullOr (types.submodule {
+ options = {
+ sslmode = mkOption {
+ type = types.enum [ "disable" "allow" "prefer" "require" "verify-ca" "verify-full" ];
+ default = "disable";
+ description = lib.mdDoc ''
+ TLS mode to use for connections to PostgreSQL servers.
+ TLS connections are disabled by default.
+
+ disable
+ Plain TCP. TLS is not even requested from the server. Default.
+ allow
+ FIXME: if server rejects plain, try TLS?
+ prefer
+ TLS connection is always requested first from PostgreSQL.
+ If refused, the connection will be established over plain TCP.
+ Server certificate is not validated.
+ require
+ Connection must go over TLS. If server rejects it, plain TCP is not attempted.
+ Server certificate is not validated.
+ verify-ca
+ Connection must go over TLS and server certificate must be valid according to tls.server.caFile.
+ Server host name is not checked against certificate.
+ verify-full
+ Connection must go over TLS and server certificate must be valid according to tls.server.caFile.
+ Server host name must match certificate information.
+ '';
+ };
+ certFile = mkOption {
+ type = types.path;
+ example = "/secrets/pgbouncer_server.key";
+ description = lib.mdDoc "Certificate for private key. PostgreSQL server can validate it.";
+ };
+ keyFile = mkOption {
+ type = types.path;
+ example = "/secrets/pgbouncer_server.crt";
+ description = lib.mdDoc "Private key for PgBouncer to authenticate against PostgreSQL server.";
+ };
+ caFile = mkOption {
+ type = types.path;
+ example = "/secrets/pgbouncer_server.crt";
+ description = lib.mdDoc "Root certificate file to validate PostgreSQL server certificates.";
+ };
+ };
+ });
+ default = null;
+ description = lib.mdDoc ''
+ <https://www.pgbouncer.org/config.html#tls-settings>
+ '';
+ };
+
+ # Log settings
+ syslog = mkOption {
+ type = types.nullOr (types.submodule {
+ options = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = lib.mdDoc ''
+ Toggles syslog on/off.
+ '';
+ };
+ syslogIdent = mkOption {
+ type = types.str;
+ default = "pgbouncer";
+ description = lib.mdDoc ''
+ Under what name to send logs to syslog.
+ '';
+ };
+ syslogFacility = mkOption {
+ type = types.enum [ "auth" "authpriv" "daemon" "user" "local0" "local1" "local2" "local3" "local4" "local5" "local6" "local7" ];
+ default = "daemon";
+ description = lib.mdDoc ''
+ Under what facility to send logs to syslog.
+ '';
+ };
+ };
+ });
+ default = null;
+ description = lib.mdDoc ''
+ <https://www.pgbouncer.org/config.html#log-settings>
+ '';
+ };
+
+ verbose = lib.mkOption {
+ type = lib.types.int;
+ default = 0;
+ description = lib.mdDoc ''
+ Increase verbosity. Mirrors the “-v” switch on the command line.
+ '';
+ };
+
+ # Console access control
+ adminUsers = mkOption {
+ type = types.nullOr types.commas;
+ default = null;
+ description = lib.mdDoc ''
+ Comma-separated list of database users that are allowed to connect and run all commands on the console.
+ Ignored when authType is any, in which case any user name is allowed in as admin.
+ '';
+ };
+
+ statsUsers = mkOption {
+ type = types.nullOr types.commas;
+ default = null;
+ description = lib.mdDoc ''
+ Comma-separated list of database users that are allowed to connect and run read-only queries on the console.
+ That means all SHOW commands except SHOW FDS.
+ '';
+ };
+
+ # Linux settings
+ openFilesLimit = lib.mkOption {
+ type = lib.types.int;
+ default = 65536;
+ description = lib.mdDoc ''
+ Maximum number of open files.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "pgbouncer";
+ description = lib.mdDoc ''
+ The user pgbouncer is run as.
+ '';
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "pgbouncer";
+ description = lib.mdDoc ''
+ The group pgbouncer is run as.
+ '';
+ };
+
+ homeDir = mkOption {
+ type = types.path;
+ default = "/var/lib/pgbouncer";
+ description = lib.mdDoc ''
+ Specifies the home directory.
+ '';
+ };
+
+ # Extra settings
+ extraConfig = mkOption {
+ type = types.lines;
+ description = lib.mdDoc ''
+ Any additional text to be appended to config.ini
+ <https://www.pgbouncer.org/config.html>.
+ '';
+ default = "";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ users.groups.${cfg.group} = { };
+ users.users.${cfg.user} = {
+ description = "PgBouncer service user";
+ group = cfg.group;
+ home = cfg.homeDir;
+ createHome = true;
+ isSystemUser = true;
+ };
+
+ systemd.services.pgbouncer = {
+ description = "PgBouncer - PostgreSQL connection pooler";
+ wants = [ "postgresql.service" ];
+ after = [ "postgresql.service" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ Type = "forking";
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStart = "${pkgs.pgbouncer}/bin/pgbouncer -d ${confFile}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
+ RuntimeDirectory = "pgbouncer";
+ PIDFile = "/run/pgbouncer/pgbouncer.pid";
+ LimitNOFILE = cfg.openFilesLimit;
+ };
+ };
+
+ networking.firewall.allowedTCPPorts = optional cfg.openFirewall cfg.port;
+
+ };
+
+ meta.maintainers = [ maintainers._1000101 ];
+
+}