summary refs log tree commit diff
path: root/nixos/modules/services/databases/redis.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/databases/redis.nix')
-rw-r--r--nixos/modules/services/databases/redis.nix168
1 files changed, 129 insertions, 39 deletions
diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix
index f1777854e14..9c0740f28c9 100644
--- a/nixos/modules/services/databases/redis.nix
+++ b/nixos/modules/services/databases/redis.nix
@@ -4,39 +4,27 @@ with lib;
 
 let
   cfg = config.services.redis;
-  redisBool = b: if b then "yes" else "no";
-  condOption = name: value: if value != null then "${name} ${toString value}" else "";
-
-  redisConfig = pkgs.writeText "redis.conf" ''
-    port ${toString cfg.port}
-    ${condOption "bind" cfg.bind}
-    ${condOption "unixsocket" cfg.unixSocket}
-    daemonize no
-    supervised systemd
-    loglevel ${cfg.logLevel}
-    logfile ${cfg.logfile}
-    syslog-enabled ${redisBool cfg.syslog}
-    databases ${toString cfg.databases}
-    ${concatMapStrings (d: "save ${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}\n") cfg.save}
-    dbfilename dump.rdb
-    dir /var/lib/redis
-    ${if cfg.slaveOf != null then "slaveof ${cfg.slaveOf.ip} ${toString cfg.slaveOf.port}" else ""}
-    ${condOption "masterauth" cfg.masterAuth}
-    ${condOption "requirepass" cfg.requirePass}
-    appendOnly ${redisBool cfg.appendOnly}
-    appendfsync ${cfg.appendFsync}
-    slowlog-log-slower-than ${toString cfg.slowLogLogSlowerThan}
-    slowlog-max-len ${toString cfg.slowLogMaxLen}
-    ${cfg.extraConfig}
-  '';
-in
-{
+
+  ulimitNofile = cfg.maxclients + 32;
+
+  mkValueString = value:
+    if value == true then "yes"
+    else if value == false then "no"
+    else generators.mkValueStringDefault { } value;
+
+  redisConfig = pkgs.writeText "redis.conf" (generators.toKeyValue {
+    listsAsDuplicateKeys = true;
+    mkKeyValue = generators.mkKeyValueDefault { inherit mkValueString; } " ";
+  } cfg.settings);
+
+in {
   imports = [
     (mkRemovedOptionModule [ "services" "redis" "user" ] "The redis module now is hardcoded to the redis user.")
     (mkRemovedOptionModule [ "services" "redis" "dbpath" ] "The redis module now uses /var/lib/redis as data directory.")
     (mkRemovedOptionModule [ "services" "redis" "dbFilename" ] "The redis module now uses /var/lib/redis/dump.rdb as database dump location.")
     (mkRemovedOptionModule [ "services" "redis" "appendOnlyFilename" ] "This option was never used.")
     (mkRemovedOptionModule [ "services" "redis" "pidFile" ] "This option was removed.")
+    (mkRemovedOptionModule [ "services" "redis" "extraConfig" ] "Use services.redis.settings instead.")
   ];
 
   ###### interface
@@ -64,7 +52,7 @@ in
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 6379;
         description = "The port for Redis to listen to.";
       };
@@ -87,9 +75,12 @@ in
 
       bind = mkOption {
         type = with types; nullOr str;
-        default = null; # All interfaces
-        description = "The IP interface to bind to.";
-        example = "127.0.0.1";
+        default = "127.0.0.1";
+        description = ''
+          The IP interface to bind to.
+          <literal>null</literal> means "all interfaces".
+        '';
+        example = "192.0.2.1";
       };
 
       unixSocket = mkOption {
@@ -99,6 +90,13 @@ in
         example = "/run/redis/redis.sock";
       };
 
+      unixSocketPerm = mkOption {
+        type = types.int;
+        default = 750;
+        description = "Change permissions for the socket";
+        example = 700;
+      };
+
       logLevel = mkOption {
         type = types.str;
         default = "notice"; # debug, verbose, notice, warning
@@ -125,6 +123,12 @@ in
         description = "Set the number of databases.";
       };
 
+      maxclients = mkOption {
+        type = types.int;
+        default = 10000;
+        description = "Set the max number of connected clients at the same time.";
+      };
+
       save = mkOption {
         type = with types; listOf (listOf int);
         default = [ [900 1] [300 10] [60 10000] ];
@@ -133,12 +137,29 @@ in
       };
 
       slaveOf = mkOption {
-        default = null; # { ip, port }
-        description = "An attribute set with two attributes: ip and port to which this redis instance acts as a slave.";
+        type = with types; nullOr (submodule ({ ... }: {
+          options = {
+            ip = mkOption {
+              type = str;
+              description = "IP of the Redis master";
+              example = "192.168.1.100";
+            };
+
+            port = mkOption {
+              type = port;
+              description = "port of the Redis master";
+              default = 6379;
+            };
+          };
+        }));
+
+        default = null;
+        description = "IP and port to which this redis instance acts as a slave.";
         example = { ip = "192.168.1.100"; port = 6379; };
       };
 
       masterAuth = mkOption {
+        type = with types; nullOr str;
         default = null;
         description = ''If the master is password protected (using the requirePass configuration)
         it is possible to tell the slave to authenticate before starting the replication synchronization
@@ -188,10 +209,19 @@ in
         description = "Maximum number of items to keep in slow log.";
       };
 
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        description = "Extra configuration options for redis.conf.";
+      settings = mkOption {
+        type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
+        default = {};
+        description = ''
+          Redis configuration. Refer to
+          <link xlink:href="https://redis.io/topics/config"/>
+          for details on supported values.
+        '';
+        example = literalExample ''
+          {
+            loadmodule = [ "/path/to/my_module.so" "/path/to/other_module.so" ];
+          }
+        '';
       };
     };
 
@@ -222,6 +252,31 @@ in
 
     environment.systemPackages = [ cfg.package ];
 
+    services.redis.settings = mkMerge [
+      {
+        port = cfg.port;
+        daemonize = false;
+        supervised = "systemd";
+        loglevel = cfg.logLevel;
+        logfile = cfg.logfile;
+        syslog-enabled = cfg.syslog;
+        databases = cfg.databases;
+        maxclients = cfg.maxclients;
+        save = map (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}") cfg.save;
+        dbfilename = "dump.rdb";
+        dir = "/var/lib/redis";
+        appendOnly = cfg.appendOnly;
+        appendfsync = cfg.appendFsync;
+        slowlog-log-slower-than = cfg.slowLogLogSlowerThan;
+        slowlog-max-len = cfg.slowLogMaxLen;
+      }
+      (mkIf (cfg.bind != null) { bind = cfg.bind; })
+      (mkIf (cfg.unixSocket != null) { unixsocket = cfg.unixSocket; unixsocketperm = "${toString cfg.unixSocketPerm}"; })
+      (mkIf (cfg.slaveOf != null) { slaveof = "${cfg.slaveOf.ip} ${cfg.slaveOf.port}"; })
+      (mkIf (cfg.masterAuth != null) { masterauth = cfg.masterAuth; })
+      (mkIf (cfg.requirePass != null) { requirepass = cfg.requirePass; })
+    ];
+
     systemd.services.redis = {
       description = "Redis Server";
 
@@ -237,11 +292,46 @@ in
 
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/redis-server /run/redis/redis.conf";
-        RuntimeDirectory = "redis";
-        StateDirectory = "redis";
         Type = "notify";
+        # User and group
         User = "redis";
         Group = "redis";
+        # Runtime directory and mode
+        RuntimeDirectory = "redis";
+        RuntimeDirectoryMode = "0750";
+        # State directory and mode
+        StateDirectory = "redis";
+        StateDirectoryMode = "0700";
+        # Access write directories
+        UMask = "0077";
+        # Capabilities
+        CapabilityBoundingSet = "";
+        # Security
+        NoNewPrivileges = true;
+        # Process Properties
+        LimitNOFILE = "${toString ulimitNofile}";
+        # Sandboxing
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        PrivateMounts = true;
+        # System Call Filtering
+        SystemCallArchitectures = "native";
+        SystemCallFilter = "~@cpu-emulation @debug @keyring @memlock @mount @obsolete @privileged @resources @setuid";
       };
     };
   };