summaryrefslogtreecommitdiffstats
path: root/nixos/modules
diff options
context:
space:
mode:
authorGuillaume Girol <symphorien@users.noreply.github.com>2021-01-10 21:51:37 +0000
committerGitHub <noreply@github.com>2021-01-10 21:51:37 +0000
commit0fbc0976db5b5f36d60d3fdc5c641987cc85096f (patch)
treec9c54f24fec3ff1ebd7fe217e0d9c378b86e7a61 /nixos/modules
parentd085417683cedabb1eaf420ca0eb128ecfb3a175 (diff)
parent3a17a9b05eec0189d82ebb84f327f386727474cd (diff)
Merge pull request #106082 from rnhmjoj/uwsgi
nixos/uwsgi: run with capabilities instead of root
Diffstat (limited to 'nixos/modules')
-rw-r--r--nixos/modules/services/web-apps/ihatemoney/default.nix9
-rw-r--r--nixos/modules/services/web-servers/uwsgi.nix71
2 files changed, 62 insertions, 18 deletions
diff --git a/nixos/modules/services/web-apps/ihatemoney/default.nix b/nixos/modules/services/web-apps/ihatemoney/default.nix
index 68769ac8c031..b4987fa4702c 100644
--- a/nixos/modules/services/web-apps/ihatemoney/default.nix
+++ b/nixos/modules/services/web-apps/ihatemoney/default.nix
@@ -44,7 +44,7 @@ let
in
{
options.services.ihatemoney = {
- enable = mkEnableOption "ihatemoney webapp. Note that this will set uwsgi to emperor mode running as root";
+ enable = mkEnableOption "ihatemoney webapp. Note that this will set uwsgi to emperor mode";
backend = mkOption {
type = types.enum [ "sqlite" "postgresql" ];
default = "sqlite";
@@ -116,16 +116,13 @@ in
services.uwsgi = {
enable = true;
plugins = [ "python3" ];
- # the vassal needs to be able to setuid
- user = "root";
- group = "root";
instance = {
type = "emperor";
vassals.ihatemoney = {
type = "normal";
strict = true;
- uid = user;
- gid = group;
+ immediate-uid = user;
+ immediate-gid = group;
# apparently flask uses threads: https://github.com/spiral-project/ihatemoney/commit/c7815e48781b6d3a457eaff1808d179402558f8c
enable-threads = true;
module = "wsgi:application";
diff --git a/nixos/modules/services/web-servers/uwsgi.nix b/nixos/modules/services/web-servers/uwsgi.nix
index 936e211ec713..506cd364a65a 100644
--- a/nixos/modules/services/web-servers/uwsgi.nix
+++ b/nixos/modules/services/web-servers/uwsgi.nix
@@ -5,11 +5,24 @@ with lib;
let
cfg = config.services.uwsgi;
+ isEmperor = cfg.instance.type == "emperor";
+
+ imperialPowers =
+ [
+ # spawn other user processes
+ "CAP_SETUID" "CAP_SETGID"
+ "CAP_SYS_CHROOT"
+ # transfer capabilities
+ "CAP_SETPCAP"
+ # create other user sockets
+ "CAP_CHOWN"
+ ];
+
buildCfg = name: c:
let
plugins =
if any (n: !any (m: m == n) cfg.plugins) (c.plugins or [])
- then throw "`plugins` attribute in UWSGI configuration contains plugins not in config.services.uwsgi.plugins"
+ then throw "`plugins` attribute in uWSGI configuration contains plugins not in config.services.uwsgi.plugins"
else c.plugins or cfg.plugins;
hasPython = v: filter (n: n == "python${v}") plugins != [];
@@ -18,7 +31,7 @@ let
python =
if hasPython2 && hasPython3 then
- throw "`plugins` attribute in UWSGI configuration shouldn't contain both python2 and python3"
+ throw "`plugins` attribute in uWSGI configuration shouldn't contain both python2 and python3"
else if hasPython2 then cfg.package.python2
else if hasPython3 then cfg.package.python3
else null;
@@ -43,7 +56,7 @@ let
oldPaths = filter (x: x != null) (map getPath env');
in env' ++ [ "PATH=${optionalString (oldPaths != []) "${last oldPaths}:"}${pythonEnv}/bin" ];
}
- else if c.type == "emperor"
+ else if isEmperor
then {
emperor = if builtins.typeOf c.vassals != "set" then c.vassals
else pkgs.buildEnv {
@@ -51,7 +64,7 @@ let
paths = mapAttrsToList buildCfg c.vassals;
};
} // removeAttrs c [ "type" "vassals" ]
- else throw "`type` attribute in UWSGI configuration should be either 'normal' or 'emperor'";
+ else throw "`type` attribute in uWSGI configuration should be either 'normal' or 'emperor'";
};
in pkgs.writeTextDir "${name}.json" (builtins.toJSON uwsgiCfg);
@@ -79,7 +92,7 @@ in {
};
instance = mkOption {
- type = with lib.types; let
+ type = with types; let
valueType = nullOr (oneOf [
bool
int
@@ -137,31 +150,65 @@ in {
user = mkOption {
type = types.str;
default = "uwsgi";
- description = "User account under which uwsgi runs.";
+ description = "User account under which uWSGI runs.";
};
group = mkOption {
type = types.str;
default = "uwsgi";
- description = "Group account under which uwsgi runs.";
+ description = "Group account under which uWSGI runs.";
+ };
+
+ capabilities = mkOption {
+ type = types.listOf types.str;
+ apply = caps: caps ++ optionals isEmperor imperialPowers;
+ default = [ ];
+ example = literalExample ''
+ [
+ "CAP_NET_BIND_SERVICE" # bind on ports <1024
+ "CAP_NET_RAW" # open raw sockets
+ ]
+ '';
+ description = ''
+ Grant capabilities to the uWSGI instance. See the
+ <literal>capabilities(7)</literal> for available values.
+ <note>
+ <para>
+ uWSGI runs as an unprivileged user (even as Emperor) with the minimal
+ capabilities required. This option can be used to add fine-grained
+ permissions without running the service as root.
+ </para>
+ <para>
+ When in Emperor mode, any capability to be inherited by a vassal must
+ be specified again in the vassal configuration using <literal>cap</literal>.
+ See the uWSGI <link
+ xlink:href="https://uwsgi-docs.readthedocs.io/en/latest/Capabilities.html">docs</link>
+ for more information.
+ </para>
+ </note>
+ '';
};
};
};
config = mkIf cfg.enable {
+ systemd.tmpfiles.rules = optional (cfg.runDir != "/run/uwsgi") ''
+ d ${cfg.runDir} 775 ${cfg.user} ${cfg.group}
+ '';
+
systemd.services.uwsgi = {
wantedBy = [ "multi-user.target" ];
- preStart = ''
- mkdir -p ${cfg.runDir}
- chown ${cfg.user}:${cfg.group} ${cfg.runDir}
- '';
serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
Type = "notify";
- ExecStart = "${cfg.package}/bin/uwsgi --uid ${cfg.user} --gid ${cfg.group} --json ${buildCfg "server" cfg.instance}/server.json";
+ ExecStart = "${cfg.package}/bin/uwsgi --json ${buildCfg "server" cfg.instance}/server.json";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
NotifyAccess = "main";
KillSignal = "SIGQUIT";
+ AmbientCapabilities = cfg.capabilities;
+ CapabilityBoundingSet = cfg.capabilities;
};
};