at 25.11-pre 6.5 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 cfg = config.services.postgres-websockets; 10 11 # Turns an attrset of libpq connection params: 12 # { 13 # dbname = "postgres"; 14 # user = "authenticator"; 15 # } 16 # into a libpq connection string: 17 # dbname=postgres user=authenticator 18 PGWS_DB_URI = lib.pipe cfg.environment.PGWS_DB_URI [ 19 (lib.filterAttrs (_: v: v != null)) 20 (lib.mapAttrsToList (k: v: "${k}='${lib.escape [ "'" "\\" ] v}'")) 21 (lib.concatStringsSep " ") 22 ]; 23in 24 25{ 26 meta = { 27 maintainers = with lib.maintainers; [ wolfgangwalther ]; 28 }; 29 30 options.services.postgres-websockets = { 31 enable = lib.mkEnableOption "postgres-websockets"; 32 33 pgpassFile = lib.mkOption { 34 type = 35 with lib.types; 36 nullOr (pathWith { 37 inStore = false; 38 absolute = true; 39 }); 40 default = null; 41 example = "/run/keys/db_password"; 42 description = '' 43 The password to authenticate to PostgreSQL with. 44 Not needed for peer or trust based authentication. 45 46 The file must be a valid `.pgpass` file as described in: 47 <https://www.postgresql.org/docs/current/libpq-pgpass.html> 48 49 In most cases, the following will be enough: 50 ``` 51 *:*:*:*:<password> 52 ``` 53 ''; 54 }; 55 56 jwtSecretFile = lib.mkOption { 57 type = 58 with lib.types; 59 nullOr (pathWith { 60 inStore = false; 61 absolute = true; 62 }); 63 example = "/run/keys/jwt_secret"; 64 description = '' 65 Secret used to sign JWT tokens used to open communications channels. 66 ''; 67 }; 68 69 environment = lib.mkOption { 70 type = lib.types.submodule { 71 freeformType = with lib.types; attrsOf str; 72 73 options = { 74 PGWS_DB_URI = lib.mkOption { 75 type = lib.types.submodule { 76 freeformType = with lib.types; attrsOf str; 77 78 # This should not be used; use pgpassFile instead. 79 options.password = lib.mkOption { 80 default = null; 81 readOnly = true; 82 internal = true; 83 }; 84 # This should not be used; use pgpassFile instead. 85 options.passfile = lib.mkOption { 86 default = null; 87 readOnly = true; 88 internal = true; 89 }; 90 }; 91 default = { }; 92 description = '' 93 libpq connection parameters as documented in: 94 95 <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS> 96 97 ::: {.note} 98 The `environment.PGWS_DB_URI.password` and `environment.PGWS_DB_URI.passfile` options are blocked. 99 Use [`pgpassFile`](#opt-services.postgres-websockets.pgpassFile) instead. 100 ::: 101 ''; 102 example = lib.literalExpression '' 103 { 104 host = "localhost"; 105 dbname = "postgres"; 106 } 107 ''; 108 }; 109 110 # This should not be used; use jwtSecretFile instead. 111 PGWS_JWT_SECRET = lib.mkOption { 112 default = null; 113 readOnly = true; 114 internal = true; 115 }; 116 117 PGWS_HOST = lib.mkOption { 118 type = with lib.types; nullOr str; 119 default = "127.0.0.1"; 120 description = '' 121 Address the server will listen for websocket connections. 122 ''; 123 }; 124 }; 125 }; 126 default = { }; 127 description = '' 128 postgres-websockets configuration as defined in: 129 <https://github.com/diogob/postgres-websockets/blob/master/src/PostgresWebsockets/Config.hs#L71-L87> 130 131 `PGWS_DB_URI` is represented as an attribute set, see [`environment.PGWS_DB_URI`](#opt-services.postgres-websockets.environment.PGWS_DB_URI) 132 133 ::: {.note} 134 The `environment.PGWS_JWT_SECRET` option is blocked. 135 Use [`jwtSecretFile`](#opt-services.postgres-websockets.jwtSecretFile) instead. 136 ::: 137 ''; 138 example = lib.literalExpression '' 139 { 140 PGWS_LISTEN_CHANNEL = "my_channel"; 141 PGWS_DB_URI.dbname = "postgres"; 142 } 143 ''; 144 }; 145 }; 146 147 config = lib.mkIf cfg.enable { 148 services.postgres-websockets.environment.PGWS_DB_URI.application_name = 149 with pkgs.postgres-websockets; 150 "${pname} ${version}"; 151 152 systemd.services.postgres-websockets = { 153 description = "postgres-websockets"; 154 155 wantedBy = [ "multi-user.target" ]; 156 wants = [ "network-online.target" ]; 157 after = [ 158 "network-online.target" 159 "postgresql.service" 160 ]; 161 162 environment = 163 cfg.environment 164 // { 165 inherit PGWS_DB_URI; 166 PGWS_JWT_SECRET = "@%d/jwt_secret"; 167 } 168 // lib.optionalAttrs (cfg.pgpassFile != null) { 169 PGPASSFILE = "%C/postgres-websockets/pgpass"; 170 }; 171 172 serviceConfig = { 173 CacheDirectory = "postgres-websockets"; 174 CacheDirectoryMode = "0700"; 175 LoadCredential = [ 176 "jwt_secret:${cfg.jwtSecretFile}" 177 ] ++ lib.optional (cfg.pgpassFile != null) "pgpass:${cfg.pgpassFile}"; 178 Restart = "always"; 179 User = "postgres-websockets"; 180 181 # Hardening 182 CapabilityBoundingSet = [ "" ]; 183 DevicePolicy = "closed"; 184 DynamicUser = true; 185 LockPersonality = true; 186 MemoryDenyWriteExecute = true; 187 NoNewPrivileges = true; 188 PrivateDevices = true; 189 PrivateIPC = true; 190 PrivateMounts = true; 191 ProcSubset = "pid"; 192 ProtectClock = true; 193 ProtectControlGroups = true; 194 ProtectHostname = true; 195 ProtectKernelLogs = true; 196 ProtectKernelModules = true; 197 ProtectKernelTunables = true; 198 ProtectProc = "invisible"; 199 RestrictAddressFamilies = [ 200 "AF_INET" 201 "AF_INET6" 202 "AF_UNIX" 203 ]; 204 RestrictNamespaces = true; 205 RestrictRealtime = true; 206 SystemCallArchitectures = "native"; 207 SystemCallFilter = [ "" ]; 208 UMask = "0077"; 209 }; 210 211 # Copy the pgpass file to different location, to have it report mode 0400. 212 # Fixes: https://github.com/systemd/systemd/issues/29435 213 script = '' 214 if [ -f "$CREDENTIALS_DIRECTORY/pgpass" ]; then 215 cp -f "$CREDENTIALS_DIRECTORY/pgpass" "$CACHE_DIRECTORY/pgpass" 216 fi 217 exec ${lib.getExe pkgs.postgres-websockets} 218 ''; 219 }; 220 }; 221}