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")
This commit is contained in:
Martin Weinelt 2024-07-20 19:42:43 +02:00
parent 3df9f56eab
commit 809ea5c6bd
No known key found for this signature in database
GPG Key ID: 87C1E9888F856759

View File

@ -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 ''