Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Octoprint: hardening and RFC 42 #335827

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions nixos/doc/manual/release-notes/rl-2411.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,10 @@

- the `ankisyncd` package and its `services.ankisyncd` have been removed, use [`services.anki-sync-server`](#opt-services.anki-sync-server.enable) instead.

- The `octoprint` service has gained an `enableRaspberryPi` option, which will
be disabled for state versions following 25.05. Users running on Raspberry Pi
should enable the option to restore full functionality.

- `nodePackages.vscode-css-languageserver-bin`, `nodePackages.vscode-html-languageserver-bin`,
and `nodePackages.vscode-json-languageserver-bin` were dropped due to an unmaintained upstream.
The `vscode-langservers-extracted` package is a maintained drop-in replacement.
Expand Down
123 changes: 96 additions & 27 deletions nixos/modules/services/misc/octoprint.nix
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let

cfg = config.services.octoprint;
inherit (lib)
literalExpression
mkDefault
mkEnableOption
mkOption
mkRenamedOptionModule
optional
types
versionOlder
;

baseConfig = {
plugins.curalegacy.cura_engine = "${pkgs.curaengine_stable}/bin/CuraEngine";
server.port = cfg.port;
webcam.ffmpeg = "${pkgs.ffmpeg.bin}/bin/ffmpeg";
} // lib.optionalAttrs (cfg.host != null) {server.host = cfg.host;};
cfg = config.services.octoprint;

fullConfig = lib.recursiveUpdate cfg.extraConfig baseConfig;
formatType = pkgs.formats.json { };

cfgUpdate = pkgs.writeText "octoprint-config.yaml" (builtins.toJSON fullConfig);
configFile = formatType.generate "octoprint-config.yaml" cfg.settings;

pluginsEnv = package.python.withPackages (ps: [ ps.octoprint ] ++ (cfg.plugins ps));

Expand Down Expand Up @@ -67,25 +77,53 @@ in
description = "State directory of the daemon.";
};

plugins = lib.mkOption {
type = lib.types.functionTo (lib.types.listOf lib.types.package);
default = plugins: [ ];
defaultText = lib.literalExpression "plugins: []";
example = lib.literalExpression "plugins: with plugins; [ themeify stlviewer ]";
plugins = mkOption {
type = types.functionTo (types.listOf types.package);
default = _plugins: [ ];
defaultText = literalExpression "plugins: []";
example = literalExpression "plugins: with plugins; [ themeify stlviewer ]";
description = "Additional plugins to be used. Available plugins are passed through the plugins input.";
};

extraConfig = lib.mkOption {
type = lib.types.attrs;
settings = mkOption {
default = { };
description = "Extra options which are added to OctoPrint's YAML configuration file.";
description = ''
The octoprint settings, for definitions see the upstream [documentation](https://docs.octoprint.org).
Will override any existing settings.
'';
type = types.submodule {
freeformType = formatType.type;
config = {
plugins.curalegacy.cura_engine = mkDefault "${pkgs.curaengine_stable}/bin/CuraEngine";
server.host = cfg.host;
server.port = cfg.port;
webcam.ffmpeg = mkDefault "${pkgs.ffmpeg.bin}/bin/ffmpeg";
};
};
};
enableRaspberryPi = mkEnableOption "RaspberryPi specific hardware access rules" // {
default = versionOlder config.system.stateVersion "25.05";
};

};

};

##### implementation
imports = [
(mkRenamedOptionModule
[
"services"
"octoprint"
"extraConfig"
]
[
"services"
"octoprint"
"settings"
]
)
];

config = lib.mkIf cfg.enable {

Expand All @@ -100,12 +138,13 @@ in
octoprint.gid = config.ids.gids.octoprint;
};

systemd.tmpfiles.rules = [
"d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
# this will allow octoprint access to raspberry specific hardware to check for throttling
# read-only will not work: "VCHI initialization failed" error
"a /dev/vchiq - - - - u:octoprint:rw"
];
systemd.tmpfiles.rules =
[ "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -" ]
++ optional cfg.enableRaspberryPi
# this will allow octoprint access to raspberry specific hardware to check for throttling
# read-only will not work: "VCHI initialization failed" error
# FIXME: this should probably be a udev rule
"a /dev/vchiq - - - - u:octoprint:rw";

systemd.services.octoprint = {
description = "OctoPrint, web interface for 3D printers";
Expand All @@ -115,10 +154,10 @@ in

preStart = ''
if [ -e "${cfg.stateDir}/config.yaml" ]; then
${pkgs.yaml-merge}/bin/yaml-merge "${cfg.stateDir}/config.yaml" "${cfgUpdate}" > "${cfg.stateDir}/config.yaml.tmp"
${pkgs.yaml-merge}/bin/yaml-merge "${cfg.stateDir}/config.yaml" "${configFile}" > "${cfg.stateDir}/config.yaml.tmp"
mv "${cfg.stateDir}/config.yaml.tmp" "${cfg.stateDir}/config.yaml"
else
cp "${cfgUpdate}" "${cfg.stateDir}/config.yaml"
cp "${configFile}" "${cfg.stateDir}/config.yaml"
chmod 600 "${cfg.stateDir}/config.yaml"
fi
'';
Expand All @@ -127,12 +166,42 @@ in
ExecStart = "${pluginsEnv}/bin/octoprint serve -b ${cfg.stateDir}";
User = cfg.user;
Group = cfg.group;
SupplementaryGroups = [
"dialout"
SupplementaryGroups = [ "dialout" ];

# Hardening
CapabilityBoundingSet = "";
LockPersonality = true;
MemoryDenyWriteExecute = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProcSubset = "pid";
ProtectSystem = "strict";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_NETLINK"
];
RestrictNamespaces = true;
RestrictRealtime = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"@pkey"
];
ReadWritePaths = [ cfg.stateDir ];
UMask = "0077";

};
};

networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ];
};
meta.maintainers = with lib.maintainers; [ patrickdag ];
}
17 changes: 12 additions & 5 deletions nixos/tests/octoprint.nix
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ in
environment.systemPackages = with pkgs; [ jq ];
services.octoprint = {
enable = true;
extraConfig = {
settings = {
server = {
firstRun = false;
};
Expand Down Expand Up @@ -50,11 +50,18 @@ in
# used to fail early, in case octoprint first starts and then crashes
with octoprint_running: # type: ignore[union-attr]
with subtest("Check for web interface"):
machine.wait_until_succeeds("curl -s localhost:5000")
machine.wait_until_succeeds("curl -s -4 localhost:5000")
machine.wait_until_succeeds("curl -s -6 localhost:5000")

with subtest("Check API"):
version = json.loads(machine.succeed(curl_cmd + "localhost:5000/api/version"))
server = json.loads(machine.succeed(curl_cmd + "localhost:5000/api/server"))
with subtest("Check API IPv4"):
version = json.loads(machine.succeed(curl_cmd + "-4 localhost:5000/api/version"))
server = json.loads(machine.succeed(curl_cmd + "-4 localhost:5000/api/server"))
assert version["server"] == str("${pkgs.octoprint.version}")
assert server["safemode"] == None

with subtest("Check API IPv6"):
version = json.loads(machine.succeed(curl_cmd + "-6 localhost:5000/api/version"))
server = json.loads(machine.succeed(curl_cmd + "-6 localhost:5000/api/server"))
assert version["server"] == str("${pkgs.octoprint.version}")
assert server["safemode"] == None
'';
Expand Down
8 changes: 1 addition & 7 deletions pkgs/applications/misc/octoprint/default.nix
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
pkgs,
stdenv,
callPackage,
lib,
Expand Down Expand Up @@ -197,12 +196,6 @@ let
src = ./pip-path.patch;
pip = "${self.pip}/bin/pip";
})

# hardcore path to ffmpeg and hide related settings
(substituteAll {
src = ./ffmpeg-path.patch;
ffmpeg = "${pkgs.ffmpeg}/bin/ffmpeg";
})
];

postPatch =
Expand Down Expand Up @@ -266,6 +259,7 @@ let
gebner
WhittlesJr
gador
patrickdag
];
};
};
Expand Down
22 changes: 0 additions & 22 deletions pkgs/applications/misc/octoprint/ffmpeg-path.patch

This file was deleted.