mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-06-11 04:05:40 +03:00

nixosTests.cryptpad started failing recently. Investigating the issue shows that seccomp has become problematic during the init phase, (e.g. this can be reproduced by removing the customize directory in /var/lib/cryptpad): machine # [ 10.774365] systemd-coredump[864]: Process 756 (node) of user 65513 dumped core. machine # machine # Module libgcc_s.so.1 without build-id. machine # Module libstdc++.so.6 without build-id. machine # Module libicudata.so.74 without build-id. machine # Module libicuuc.so.74 without build-id. machine # Module libicui18n.so.74 without build-id. machine # Module libz.so.1 without build-id. machine # Module node without build-id. machine # Stack trace of thread 756: machine # #0 0x00007ff951974dcb fchown (libc.so.6 + 0x107dcb) machine # #1 0x00007ff95490d0c0 uv__fs_copyfile (libuv.so.1 + 0x150c0) machine # #2 0x00007ff95490d89a uv__fs_work (libuv.so.1 + 0x1589a) machine # #3 0x00007ff954910c76 uv_fs_copyfile (libuv.so.1 + 0x18c76) machine # #4 0x0000000000eb8a39 _ZN4node2fsL8CopyFileERKN2v820FunctionCallbackInfoINS1_5ValueEEE (node + 0xab8a39) machine # #5 0x0000000001cda5e2 Builtins_CallApiCallbackGeneric (node + 0x18da5e2) [...] machine # [ 10.877468] cryptpad[685]: /nix/store/h4yhhxpfm03c5rgz91q7jrvknh596ly2-cryptpad-2024.12.0/bin/cryptpad: line 3: 756 Bad system call (core dumped) "/nix/store/fkyp1bm5gll9adnfcj92snyym524mdrj-nodejs-22.11.0/bin/node" "/nix/store/h4yhhxpfm03c5rgz91q7jrvknh596ly2-cryptpad-2024.12.0/lib/node_modules/cryptpad/scripts/build.js" nodejs 20.18 rightly did not require chown when the source and destination are the same owner (heck, the script does not run as root so even if it is not blocked there is no way it'd work with a different owner...) For now just allow chown calls again, this is not worth wasting more time. Fixes https://github.com/NixOS/nixpkgs/issues/370717
293 lines
9.7 KiB
Nix
293 lines
9.7 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
let
|
|
cfg = config.services.cryptpad;
|
|
|
|
inherit (lib)
|
|
mkIf
|
|
mkMerge
|
|
mkOption
|
|
strings
|
|
types
|
|
;
|
|
|
|
# The Cryptpad configuration file isn't JSON, but a JavaScript source file that assigns a JSON value
|
|
# to a variable.
|
|
cryptpadConfigFile = builtins.toFile "cryptpad_config.js" ''
|
|
module.exports = ${builtins.toJSON cfg.settings}
|
|
'';
|
|
|
|
# Derive domain names for Nginx configuration from Cryptpad configuration
|
|
mainDomain = strings.removePrefix "https://" cfg.settings.httpUnsafeOrigin;
|
|
sandboxDomain =
|
|
if cfg.settings.httpSafeOrigin == null then
|
|
mainDomain
|
|
else
|
|
strings.removePrefix "https://" cfg.settings.httpSafeOrigin;
|
|
|
|
in
|
|
{
|
|
options.services.cryptpad = {
|
|
enable = lib.mkEnableOption "cryptpad";
|
|
|
|
package = lib.mkPackageOption pkgs "cryptpad" { };
|
|
|
|
configureNginx = mkOption {
|
|
description = ''
|
|
Configure Nginx as a reverse proxy for Cryptpad.
|
|
Note that this makes some assumptions on your setup, and sets settings that will
|
|
affect other virtualHosts running on your Nginx instance, if any.
|
|
Alternatively you can configure a reverse-proxy of your choice.
|
|
'';
|
|
type = types.bool;
|
|
default = false;
|
|
};
|
|
|
|
settings = mkOption {
|
|
description = ''
|
|
Cryptpad configuration settings.
|
|
See https://github.com/cryptpad/cryptpad/blob/main/config/config.example.js for a more extensive
|
|
reference documentation.
|
|
Test your deployed instance through `https://<domain>/checkup/`.
|
|
'';
|
|
type = types.submodule {
|
|
freeformType = (pkgs.formats.json { }).type;
|
|
options = {
|
|
httpUnsafeOrigin = mkOption {
|
|
type = types.str;
|
|
example = "https://cryptpad.example.com";
|
|
default = "";
|
|
description = "This is the URL that users will enter to load your instance";
|
|
};
|
|
httpSafeOrigin = mkOption {
|
|
type = types.nullOr types.str;
|
|
example = "https://cryptpad-ui.example.com. Apparently optional but recommended.";
|
|
description = "Cryptpad sandbox URL";
|
|
};
|
|
httpAddress = mkOption {
|
|
type = types.str;
|
|
default = "127.0.0.1";
|
|
description = "Address on which the Node.js server should listen";
|
|
};
|
|
httpPort = mkOption {
|
|
type = types.int;
|
|
default = 3000;
|
|
description = "Port on which the Node.js server should listen";
|
|
};
|
|
websocketPort = mkOption {
|
|
type = types.int;
|
|
default = 3003;
|
|
description = "Port for the websocket that needs to be separate";
|
|
};
|
|
maxWorkers = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "Number of child processes, defaults to number of cores available";
|
|
};
|
|
adminKeys = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ ];
|
|
description = "List of public signing keys of users that can access the admin panel";
|
|
example = [ "[cryptpad-user1@my.awesome.website/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=]" ];
|
|
};
|
|
logToStdout = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Controls whether log output should go to stdout of the systemd service";
|
|
};
|
|
logLevel = mkOption {
|
|
type = types.str;
|
|
default = "info";
|
|
description = "Controls log level";
|
|
};
|
|
blockDailyCheck = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = ''
|
|
Disable telemetry. This setting is only effective if the 'Disable server telemetry'
|
|
setting in the admin menu has been untouched, and will be ignored by cryptpad once
|
|
that option is set either way.
|
|
Note that due to the service confinement, just enabling the option in the admin
|
|
menu will not be able to resolve DNS and fail; this setting must be set as well.
|
|
'';
|
|
};
|
|
installMethod = mkOption {
|
|
type = types.str;
|
|
default = "nixos";
|
|
description = ''
|
|
Install method is listed in telemetry if you agree to it through the consentToContact
|
|
setting in the admin panel.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable (mkMerge [
|
|
{
|
|
systemd.services.cryptpad = {
|
|
description = "Cryptpad service";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "networking.target" ];
|
|
serviceConfig = {
|
|
BindReadOnlyPaths = [
|
|
cryptpadConfigFile
|
|
# apparently needs proc for workers management
|
|
"/proc"
|
|
"/dev/urandom"
|
|
];
|
|
DynamicUser = true;
|
|
Environment = [
|
|
"CRYPTPAD_CONFIG=${cryptpadConfigFile}"
|
|
"HOME=%S/cryptpad"
|
|
];
|
|
ExecStart = lib.getExe cfg.package;
|
|
Restart = "always";
|
|
StateDirectory = "cryptpad";
|
|
WorkingDirectory = "%S/cryptpad";
|
|
# security way too many numerous options, from the systemd-analyze security output
|
|
# at end of test: block everything except
|
|
# - SystemCallFiters=@resources is required for node
|
|
# - MemoryDenyWriteExecute for node JIT
|
|
# - RestrictAddressFamilies=~AF_(INET|INET6) / PrivateNetwork to bind to sockets
|
|
# - IPAddressDeny likewise allow localhost if binding to localhost or any otherwise
|
|
# - PrivateUsers somehow service doesn't start with that
|
|
# - DeviceAllow (char-rtc r added by ProtectClock)
|
|
AmbientCapabilities = "";
|
|
CapabilityBoundingSet = "";
|
|
DeviceAllow = "";
|
|
LockPersonality = true;
|
|
NoNewPrivileges = true;
|
|
PrivateDevices = true;
|
|
PrivateTmp = true;
|
|
ProcSubset = "pid";
|
|
ProtectClock = true;
|
|
ProtectControlGroups = true;
|
|
ProtectHome = true;
|
|
ProtectHostname = true;
|
|
ProtectKernelLogs = true;
|
|
ProtectKernelModules = true;
|
|
ProtectKernelTunables = true;
|
|
ProtectProc = "invisible";
|
|
ProtectSystem = "strict";
|
|
RemoveIPC = true;
|
|
RestrictAddressFamilies = [
|
|
"AF_INET"
|
|
"AF_INET6"
|
|
];
|
|
RestrictNamespaces = true;
|
|
RestrictRealtime = true;
|
|
RestrictSUIDSGID = true;
|
|
RuntimeDirectoryMode = "700";
|
|
SocketBindAllow = [
|
|
"tcp:${builtins.toString cfg.settings.httpPort}"
|
|
"tcp:${builtins.toString cfg.settings.websocketPort}"
|
|
];
|
|
SocketBindDeny = [ "any" ];
|
|
StateDirectoryMode = "0700";
|
|
SystemCallArchitectures = "native";
|
|
SystemCallFilter = [
|
|
"@pkey"
|
|
"@system-service"
|
|
"@chown"
|
|
"~@keyring"
|
|
"~@memlock"
|
|
"~@privileged"
|
|
"~@resources"
|
|
"~@setuid"
|
|
"~@timer"
|
|
];
|
|
UMask = "0077";
|
|
};
|
|
confinement = {
|
|
enable = true;
|
|
binSh = null;
|
|
mode = "chroot-only";
|
|
};
|
|
};
|
|
}
|
|
# block external network access if not phoning home and
|
|
# binding to localhost (default)
|
|
(mkIf
|
|
(
|
|
cfg.settings.blockDailyCheck
|
|
&& (builtins.elem cfg.settings.httpAddress [
|
|
"127.0.0.1"
|
|
"::1"
|
|
])
|
|
)
|
|
{
|
|
systemd.services.cryptpad = {
|
|
serviceConfig = {
|
|
IPAddressAllow = [ "localhost" ];
|
|
IPAddressDeny = [ "any" ];
|
|
};
|
|
};
|
|
}
|
|
)
|
|
# .. conversely allow DNS & TLS if telemetry is explicitly enabled
|
|
(mkIf (!cfg.settings.blockDailyCheck) {
|
|
systemd.services.cryptpad = {
|
|
serviceConfig = {
|
|
BindReadOnlyPaths = [
|
|
"-/etc/resolv.conf"
|
|
"-/run/systemd"
|
|
"/etc/hosts"
|
|
"/etc/ssl/certs/ca-certificates.crt"
|
|
];
|
|
};
|
|
};
|
|
})
|
|
|
|
(mkIf cfg.configureNginx {
|
|
assertions = [
|
|
{
|
|
assertion = cfg.settings.httpUnsafeOrigin != "";
|
|
message = "services.cryptpad.settings.httpUnsafeOrigin is required";
|
|
}
|
|
{
|
|
assertion = strings.hasPrefix "https://" cfg.settings.httpUnsafeOrigin;
|
|
message = "services.cryptpad.settings.httpUnsafeOrigin must start with https://";
|
|
}
|
|
{
|
|
assertion =
|
|
cfg.settings.httpSafeOrigin == null || strings.hasPrefix "https://" cfg.settings.httpSafeOrigin;
|
|
message = "services.cryptpad.settings.httpSafeOrigin must start with https:// (or be unset)";
|
|
}
|
|
];
|
|
services.nginx = {
|
|
enable = true;
|
|
recommendedTlsSettings = true;
|
|
recommendedProxySettings = true;
|
|
recommendedOptimisation = true;
|
|
recommendedGzipSettings = true;
|
|
|
|
virtualHosts = mkMerge [
|
|
{
|
|
"${mainDomain}" = {
|
|
serverAliases = lib.optionals (cfg.settings.httpSafeOrigin != null) [ sandboxDomain ];
|
|
enableACME = lib.mkDefault true;
|
|
forceSSL = true;
|
|
locations."/" = {
|
|
proxyPass = "http://${cfg.settings.httpAddress}:${builtins.toString cfg.settings.httpPort}";
|
|
extraConfig = ''
|
|
client_max_body_size 150m;
|
|
'';
|
|
};
|
|
locations."/cryptpad_websocket" = {
|
|
proxyPass = "http://${cfg.settings.httpAddress}:${builtins.toString cfg.settings.websocketPort}";
|
|
proxyWebsockets = true;
|
|
};
|
|
};
|
|
}
|
|
];
|
|
};
|
|
})
|
|
]);
|
|
}
|