at 25.11-pre 9.9 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 cfg = config.services.postgrest; 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 db-uri = lib.pipe (cfg.settings.db-uri or { }) [ 19 (lib.filterAttrs (_: v: v != null)) 20 (lib.mapAttrsToList (k: v: "${k}=${v}")) 21 (lib.concatStringsSep " ") 22 ]; 23 24 # Writes a postgrest config file according to: 25 # https://hackage.haskell.org/package/configurator-0.3.0.0/docs/Data-Configurator.html 26 # Only a subset of the functionality is used by PostgREST. 27 configFile = lib.pipe (cfg.settings // { inherit db-uri; }) [ 28 (lib.filterAttrs (_: v: v != null)) 29 30 (lib.mapAttrs ( 31 _: v: 32 if true == v then 33 "true" 34 else if false == v then 35 "false" 36 else if lib.isInt v then 37 toString v 38 else 39 "\"${lib.escape [ "\"" ] v}\"" 40 )) 41 42 (lib.mapAttrsToList (k: v: "${k} = ${v}")) 43 (lib.concatStringsSep "\n") 44 (pkgs.writeText "postgrest.conf") 45 ]; 46in 47 48{ 49 meta = { 50 maintainers = with lib.maintainers; [ wolfgangwalther ]; 51 }; 52 53 options.services.postgrest = { 54 enable = lib.mkEnableOption "PostgREST"; 55 56 pgpassFile = lib.mkOption { 57 type = 58 with lib.types; 59 nullOr (pathWith { 60 inStore = false; 61 absolute = true; 62 }); 63 default = null; 64 example = "/run/keys/db_password"; 65 description = '' 66 The password to authenticate to PostgreSQL with. 67 Not needed for peer or trust based authentication. 68 69 The file must be a valid `.pgpass` file as described in: 70 <https://www.postgresql.org/docs/current/libpq-pgpass.html> 71 72 In most cases, the following will be enough: 73 ``` 74 *:*:*:*:<password> 75 ``` 76 ''; 77 }; 78 79 jwtSecretFile = lib.mkOption { 80 type = 81 with lib.types; 82 nullOr (pathWith { 83 inStore = false; 84 absolute = true; 85 }); 86 default = null; 87 example = "/run/keys/jwt_secret"; 88 description = '' 89 The secret or JSON Web Key (JWK) (or set) used to decode JWT tokens clients provide for authentication. 90 For security the key must be at least 32 characters long. 91 If this parameter is not specified then PostgREST refuses authentication requests. 92 93 <https://docs.postgrest.org/en/stable/references/configuration.html#jwt-secret> 94 ''; 95 }; 96 97 settings = lib.mkOption { 98 type = lib.types.submodule { 99 freeformType = 100 with lib.types; 101 attrsOf (oneOf [ 102 bool 103 ints.unsigned 104 str 105 ]); 106 107 options = { 108 admin-server-port = lib.mkOption { 109 type = with lib.types; nullOr port; 110 default = null; 111 description = '' 112 Specifies the port for the admin server, which can be used for healthchecks. 113 114 <https://docs.postgrest.org/en/stable/references/admin_server.html#admin-server> 115 ''; 116 }; 117 118 db-config = lib.mkOption { 119 type = lib.types.bool; 120 default = false; 121 example = true; 122 description = '' 123 Enables the in-database configuration. 124 125 <https://docs.postgrest.org/en/stable/references/configuration.html#in-database-configuration> 126 127 ::: {.note} 128 This is enabled by default upstream, but disabled by default in this module. 129 ::: 130 ''; 131 }; 132 133 db-uri = lib.mkOption { 134 type = lib.types.submodule { 135 freeformType = with lib.types; attrsOf str; 136 137 # This should not be used; use pgpassFile instead. 138 options.password = lib.mkOption { 139 default = null; 140 readOnly = true; 141 internal = true; 142 }; 143 # This should not be used; use pgpassFile instead. 144 options.passfile = lib.mkOption { 145 default = null; 146 readOnly = true; 147 internal = true; 148 }; 149 }; 150 default = { }; 151 description = '' 152 libpq connection parameters as documented in: 153 154 <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS> 155 156 ::: {.note} 157 The `settings.db-uri.password` and `settings.db-uri.passfile` options are blocked. 158 Use [`pgpassFile`](#opt-services.postgrest.pgpassFile) instead. 159 ::: 160 ''; 161 example = lib.literalExpression '' 162 { 163 host = "localhost"; 164 dbname = "postgres"; 165 } 166 ''; 167 }; 168 169 # This should not be used; use jwtSecretFile instead. 170 jwt-secret = lib.mkOption { 171 default = null; 172 readOnly = true; 173 internal = true; 174 }; 175 176 server-host = lib.mkOption { 177 type = with lib.types; nullOr str; 178 default = "127.0.0.1"; 179 description = '' 180 Where to bind the PostgREST web server. 181 182 ::: {.note} 183 The admin server will also bind here, but potentially exposes sensitive information. 184 Make sure you turn off the admin server, when opening this to the public. 185 186 <https://github.com/PostgREST/postgrest/issues/3956> 187 ::: 188 ''; 189 }; 190 191 server-port = lib.mkOption { 192 type = with lib.types; nullOr port; 193 default = null; 194 example = 3000; 195 description = '' 196 The TCP port to bind the web server. 197 ''; 198 }; 199 200 server-unix-socket = lib.mkOption { 201 type = with lib.types; nullOr path; 202 default = "/run/postgrest/postgrest.sock"; 203 description = '' 204 Unix domain socket where to bind the PostgREST web server. 205 ''; 206 }; 207 }; 208 }; 209 default = { }; 210 description = '' 211 PostgREST configuration as documented in: 212 <https://docs.postgrest.org/en/stable/references/configuration.html#list-of-parameters> 213 214 `db-uri` is represented as an attribute set, see [`settings.db-uri`](#opt-services.postgrest.settings.db-uri) 215 216 ::: {.note} 217 The `settings.jwt-secret` option is blocked. 218 Use [`jwtSecretFile`](#opt-services.postgrest.jwtSecretFile) instead. 219 ::: 220 ''; 221 example = lib.literalExpression '' 222 { 223 db-anon-role = "anon"; 224 db-uri.dbname = "postgres"; 225 "app.settings.custom" = "value"; 226 } 227 ''; 228 }; 229 }; 230 231 config = lib.mkIf cfg.enable { 232 assertions = [ 233 { 234 assertion = (cfg.settings.server-port == null) != (cfg.settings.server-unix-socket == null); 235 message = '' 236 PostgREST can listen either on a TCP port or on a unix socket, but not both. 237 Please set one of `settings.server-port`](#opt-services.postgrest.jwtSecretFile) or `settings.server-unix-socket` to `null`. 238 239 <https://docs.postgrest.org/en/stable/references/configuration.html#server-unix-socket> 240 ''; 241 } 242 ]; 243 244 warnings = 245 lib.optional (cfg.settings.admin-server-port != null && cfg.settings.server-host != "127.0.0.1") 246 "The PostgREST admin server is potentially listening on a public host. This may expose sensitive information via the `/config` endpoint."; 247 248 # Since we're using DynamicUser, we can't add the e.g. nginx user to 249 # a postgrest group, so the unix socket must be world-readable to make it useful. 250 services.postgrest.settings.service-unix-socket-mode = "666"; 251 252 systemd.services.postgrest = { 253 description = "PostgREST"; 254 255 wantedBy = [ "multi-user.target" ]; 256 wants = [ "network-online.target" ]; 257 after = [ 258 "network-online.target" 259 "postgresql.service" 260 ]; 261 262 serviceConfig = { 263 CacheDirectory = "postgrest"; 264 CacheDirectoryMode = "0700"; 265 Environment = 266 lib.optional (cfg.pgpassFile != null) "PGPASSFILE=%C/postgrest/pgpass" 267 ++ lib.optional (cfg.jwtSecretFile != null) "PGRST_JWT_SECRET=@%d/jwt_secret"; 268 LoadCredential = 269 lib.optional (cfg.pgpassFile != null) "pgpass:${cfg.pgpassFile}" 270 ++ lib.optional (cfg.jwtSecretFile != null) "jwt_secret:${cfg.jwtSecretFile}"; 271 Restart = "always"; 272 RuntimeDirectory = "postgrest"; 273 User = "postgrest"; 274 275 # Hardening 276 CapabilityBoundingSet = [ "" ]; 277 DevicePolicy = "closed"; 278 DynamicUser = true; 279 LockPersonality = true; 280 MemoryDenyWriteExecute = true; 281 NoNewPrivileges = true; 282 PrivateDevices = true; 283 PrivateIPC = true; 284 PrivateMounts = true; 285 ProcSubset = "pid"; 286 ProtectClock = true; 287 ProtectControlGroups = true; 288 ProtectHostname = true; 289 ProtectKernelLogs = true; 290 ProtectKernelModules = true; 291 ProtectKernelTunables = true; 292 ProtectProc = "invisible"; 293 RestrictAddressFamilies = [ 294 "AF_INET" 295 "AF_INET6" 296 "AF_UNIX" 297 ]; 298 RestrictNamespaces = true; 299 RestrictRealtime = true; 300 SystemCallArchitectures = "native"; 301 SystemCallFilter = [ "" ]; 302 UMask = "0077"; 303 }; 304 305 # Copy the pgpass file to different location, to have it report mode 0400. 306 # Fixes: https://github.com/systemd/systemd/issues/29435 307 script = '' 308 if [ -f "$CREDENTIALS_DIRECTORY/pgpass" ]; then 309 cp -f "$CREDENTIALS_DIRECTORY/pgpass" "$CACHE_DIRECTORY/pgpass" 310 fi 311 exec ${lib.getExe pkgs.postgrest} ${configFile} 312 ''; 313 }; 314 }; 315}