From 809ea5c6bd44535e02e93ae4277c01e1c0d1b46d Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Sat, 20 Jul 2024 19:42:43 +0200 Subject: [PATCH 1/5] nixos/ollama: replace flawed sandboxing option The ollama module in its default configuration relies on systemd's `DynamicUser=` feature for user allocation. In #305076 that allocation was made conditional and tied to the `sandboxing` option, that was intended to fix access to model directories outside the allocated state directory. However, by disabling sandboxing ollama would inadvertently run as root, given that `User=` and `Group=` are not required to be set. The correct way to grant access to other paths is to allocate static user and group, and grant permissions to the destination path to that allocation. We therefore replace the sandboxing option user and group options, that default to `null`, which means they default to `DynamicUser=`, but can be replaced with a statically allocated user/group, and thereby a stable uid/gid. Fixes: 552eb759 ("nixos/ollama: add options to bypass sandboxing") --- nixos/modules/services/misc/ollama.nix | 67 ++++++++++++++++++-------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/nixos/modules/services/misc/ollama.nix b/nixos/modules/services/misc/ollama.nix index a0a32f1702bf..bfa59fb506c7 100644 --- a/nixos/modules/services/misc/ollama.nix +++ b/nixos/modules/services/misc/ollama.nix @@ -1,25 +1,54 @@ { config, lib, pkgs, ... }: let - inherit (lib) types mkBefore; + inherit (lib) literalExpression types mkBefore; cfg = config.services.ollama; ollamaPackage = cfg.package.override { inherit (cfg) acceleration; }; + + staticUser = cfg.user != null && cfg.group != null; in { imports = [ (lib.mkRemovedOptionModule [ "services" "ollama" "listenAddress" ] "Use `services.ollama.host` and `services.ollama.port` instead.") + (lib.mkRemovedOptionModule [ "services" "ollama" "sandbox" ] + "Set `services.ollama.user` and `services.ollama.group` instead.") ]; options = { services.ollama = { enable = lib.mkEnableOption "ollama server for local large language models"; package = lib.mkPackageOption pkgs "ollama" { }; + + user = lib.mkOption { + type = with types; nullOr str; + default = null; + example = "ollama"; + description = '' + User account under which to run ollama. Defaults to [`DynamicUser`](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#DynamicUser=) + when set to `null`. + + The user will automatically be created, if this option is set to a non-null value. + ''; + }; + + group = lib.mkOption { + type = with types; nullOr str; + default = cfg.user; + defaultText = literalExpression "config.services.ollama.user"; + example = "ollama"; + description = '' + Group under which to run ollama. Only used when `services.ollama.user` is set. + + The group will automatically be created, if this option is set to a non-null value. + ''; + }; + home = lib.mkOption { type = types.str; - default = "%S/ollama"; + default = "/var/lib/ollama"; example = "/home/foo"; description = '' The home directory that the ollama service is started in. @@ -27,9 +56,11 @@ in See also `services.ollama.writablePaths` and `services.ollama.sandbox`. ''; }; + models = lib.mkOption { type = types.str; - default = "%S/ollama/models"; + default = "${cfg.home}/models"; + defaultText = "\${config.services.ollama.home}/models"; example = "/path/to/ollama/models"; description = '' The directory that the ollama service will read models from and download new models to. @@ -38,20 +69,6 @@ in if downloading models or other mutation of the filesystem is required. ''; }; - sandbox = lib.mkOption { - type = types.bool; - default = true; - example = false; - description = '' - Whether to enable systemd's sandboxing capabilities. - - This sets [`DynamicUser`]( - https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#DynamicUser= - ), which runs the server as a unique user with read-only access to most of the filesystem. - - See also `services.ollama.writablePaths`. - ''; - }; writablePaths = lib.mkOption { type = types.listOf types.str; default = [ ]; @@ -149,6 +166,15 @@ in }; config = lib.mkIf cfg.enable { + users = lib.mkIf staticUser { + users.${cfg.user} = { + inherit (cfg) home; + isSystemUser = true; + group = cfg.group; + }; + groups.${cfg.group} = {}; + }; + systemd.services.ollama = { description = "Server for local large language models"; wantedBy = [ "multi-user.target" ]; @@ -159,11 +185,14 @@ in OLLAMA_HOST = "${cfg.host}:${toString cfg.port}"; HSA_OVERRIDE_GFX_VERSION = lib.mkIf (cfg.rocmOverrideGfx != null) cfg.rocmOverrideGfx; }; - serviceConfig = { + serviceConfig = lib.optionalAttrs staticUser { + User = cfg.user; + Group = cfg.group; + } // { + DynamicUser = true; ExecStart = "${lib.getExe ollamaPackage} serve"; WorkingDirectory = cfg.home; StateDirectory = [ "ollama" ]; - DynamicUser = cfg.sandbox; ReadWritePaths = cfg.writablePaths; }; postStart = mkBefore '' From be7bce879faf4aaeb666c48f17c0d9812ac1210a Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Sun, 21 Jul 2024 01:59:22 +0200 Subject: [PATCH 2/5] nixos/ollama: remove writablePaths option Making the models directory always writable is much simpler, than having to watch out for an option to facilitate that. --- nixos/modules/services/misc/ollama.nix | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/nixos/modules/services/misc/ollama.nix b/nixos/modules/services/misc/ollama.nix index bfa59fb506c7..be86ecb70e10 100644 --- a/nixos/modules/services/misc/ollama.nix +++ b/nixos/modules/services/misc/ollama.nix @@ -15,6 +15,8 @@ in "Use `services.ollama.host` and `services.ollama.port` instead.") (lib.mkRemovedOptionModule [ "services" "ollama" "sandbox" ] "Set `services.ollama.user` and `services.ollama.group` instead.") + (lib.mkRemovedOptionModule [ "services" "ollama" "writablePaths" ] + "The `models` directory is now always writable. To make other directories writable, use `systemd.services.ollama.serviceConfig.ReadWritePaths`." ) ]; options = { @@ -52,8 +54,6 @@ in example = "/home/foo"; description = '' The home directory that the ollama service is started in. - - See also `services.ollama.writablePaths` and `services.ollama.sandbox`. ''; }; @@ -64,25 +64,9 @@ in example = "/path/to/ollama/models"; description = '' The directory that the ollama service will read models from and download new models to. - - See also `services.ollama.writablePaths` and `services.ollama.sandbox` - if downloading models or other mutation of the filesystem is required. ''; }; - writablePaths = lib.mkOption { - type = types.listOf types.str; - default = [ ]; - example = [ "/home/foo" "/mnt/foo" ]; - description = '' - Paths that the server should have write access to. - This sets [`ReadWritePaths`]( - https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#ReadWritePaths= - ), which allows specified paths to be written to through the default sandboxing. - - See also `services.ollama.sandbox`. - ''; - }; host = lib.mkOption { type = types.str; default = "127.0.0.1"; @@ -193,7 +177,7 @@ in ExecStart = "${lib.getExe ollamaPackage} serve"; WorkingDirectory = cfg.home; StateDirectory = [ "ollama" ]; - ReadWritePaths = cfg.writablePaths; + ReadWritePaths = [ cfg.models ]; }; postStart = mkBefore '' set -x From 12897b37a8a0e5cb186f809773afdbc403947353 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Sun, 21 Jul 2024 02:46:50 +0200 Subject: [PATCH 3/5] nixos/ollama: harden systemd unit Tested with CPU and CUDA acceleration. Researched for ROCm, but I have no compatible card to test it with. --- nixos/modules/services/misc/ollama.nix | 50 +++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/nixos/modules/services/misc/ollama.nix b/nixos/modules/services/misc/ollama.nix index be86ecb70e10..7d0b66c4f8a2 100644 --- a/nixos/modules/services/misc/ollama.nix +++ b/nixos/modules/services/misc/ollama.nix @@ -177,7 +177,55 @@ in ExecStart = "${lib.getExe ollamaPackage} serve"; WorkingDirectory = cfg.home; StateDirectory = [ "ollama" ]; - ReadWritePaths = [ cfg.models ]; + ReadWritePaths = [ + cfg.home + cfg.models + ]; + + CapabilityBoundingSet = [ "" ]; + DeviceAllow = [ + # CUDA + # https://docs.nvidia.com/dgx/pdf/dgx-os-5-user-guide.pdf + "char-nvidiactl" + "char-nvidia-caps" + "char-nvidia-uvm" + # ROCm + "char-drm" + "char-kfd" + ]; + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = false; # hides acceleration devices + PrivateTmp = true; + PrivateUsers = true; + ProcSubset = "all"; # /proc/meminfo + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + SupplementaryGroups = [ "render" ]; # for rocm to access /dev/dri/renderD* devices + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service @resources" + "~@privileged" + ]; + UMask = "0077"; }; postStart = mkBefore '' set -x From bd473ceae3f53d8f10bb3656d7a764158e856f66 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Sun, 21 Jul 2024 23:58:41 +0200 Subject: [PATCH 4/5] nixos/doc/rl-2411: add ollama changes --- nixos/doc/manual/release-notes/rl-2411.section.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md index 38168b312e41..348e6be5498c 100644 --- a/nixos/doc/manual/release-notes/rl-2411.section.md +++ b/nixos/doc/manual/release-notes/rl-2411.section.md @@ -133,6 +133,10 @@ - The Invoiceplane module now only accepts the structured `settings` option. `extraConfig` is now removed. +- The `ollama` services replaces its `sandbox` toggle with options to configure + a static `user` and `group`. The `writablePaths` option has been removed and + the models directory is now always exempt from sandboxing. + - Legacy package `stalwart-mail_0_6` was dropped, please note the [manual upgrade process](https://github.com/stalwartlabs/mail-server/blob/main/UPGRADING.md) before changing the package to `pkgs.stalwart-mail` in From fe58e8856f206406a9b988e70b1f383cbb3ea331 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Mon, 22 Jul 2024 00:06:31 +0200 Subject: [PATCH 5/5] nixos/ollama: make host example dualstack wildcard Binding to IPv6 wildcard generally binds dual-stack, which is also a better example because the way to specify it is not always very clear. --- nixos/modules/services/misc/ollama.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/modules/services/misc/ollama.nix b/nixos/modules/services/misc/ollama.nix index 7d0b66c4f8a2..07031a082344 100644 --- a/nixos/modules/services/misc/ollama.nix +++ b/nixos/modules/services/misc/ollama.nix @@ -70,7 +70,7 @@ in host = lib.mkOption { type = types.str; default = "127.0.0.1"; - example = "0.0.0.0"; + example = "[::]"; description = '' The host address which the ollama server HTTP interface listens to. '';