Skip to content

Commit

Permalink
Improve module
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelshmitty committed Dec 20, 2023
1 parent 47cf021 commit 7e9ae9e
Showing 1 changed file with 86 additions and 84 deletions.
170 changes: 86 additions & 84 deletions modules/cryptpad/default.nix
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
flake: { config, lib, pkgs, ... }:

let
inherit (lib) types strings mdDoc mkOption mkIf mkMerge ;
inherit (lib) types strings mdDoc mkOption mkIf mkMerge;

inherit (flake.packages.${pkgs.stdenv.hostPlatform.system}) cryptpad;

cfg = config.services.cryptpad;

# The Cryptpad configuration file isn't JSON, but a JavaScript source file that assigns a JSON value
# to a variable.
# The Cryptpad configuration file is not JSON, but a JavaScript source file that assigns a JSON configuration
# object to a variable and exports it.
configFile = builtins.toFile "cryptpad_config.js" ''
module.exports = ${builtins.toJSON cfg.config}
'';

# Derive domain names for Nginx configuration from Cryptpad configuration
mainDomain = strings.removePrefix "https://" cfg.config.httpUnsafeOrigin;
sandboxDomain = if isNull cfg.config.httpSafeOrigin then mainDomain else strings.removePrefix "https://" cfg.config.httpSafeOrigin;
sandboxDomain =
if cfg.config.httpSafeOrigin == null then mainDomain else strings.removePrefix "https://" cfg.config.httpSafeOrigin;

in
{
# Some workaround due to cryptpad being a disabled module name:
# Some workaround due to `cryptpad` being a disabled module name:
# Disabling the rename.nix module is necessary to be able to use the name 'cryptpad'
disabledModules = [ "rename.nix" ];
# The following import is necessary when disabling the rename.nix module above
Expand All @@ -45,102 +46,132 @@ in
default = false;
description = mdDoc ''
Configure Nginx as a reverse proxy for Cryptpad.
Note that this makes some assumptions on your setup, and sets settings that will
Note that this makes some assumptions on your setup, and configures settings that will
affect other virtualHosts running on your Nginx instance, if any.
Alternatively you can configure a reverse-proxy of your choice.
'';
};

confinement = mkOption {
type = types.bool;
default = false;
clientMaxBodySize = mkOption {
default = "150m";
type = types.str;
description = mdDoc ''
Value for the Nginx client_max_body_size header. Only relevant if `configureNginx` is `true`.
'';
};

hstsMaxAge = mkOption {
type = types.ints.positive;
default = 63072000;
description = mdDoc ''
FIXME: Enables 'confinement' (explain what it does?)
Enable the nixos systemd.services.cryptpad.confinement setting, including the necessary
extra bind mounts.
Value for the `max-age` directive of the HTTP `Strict-Transport-Security` header.
Only relevant if `configureNginx` is `true`.
See section 6.1.1 of IETF RFC 6797 for detailed information on this
directive and header.
'';
};

config = mkOption {
type = types.submodule {
freeformType = (pkgs.formats.json {}).type;
freeformType = (pkgs.formats.json { }).type;
options = {
httpUnsafeOrigin = mkOption {
type = types.str;
example = "https://cryptpad.example.com";
default = "";
description = mdDoc "This is the URL that users will enter to load your instance";
description = mdDoc "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 = mdDoc "Cryptpad sandbox URL";
example = "https://cryptpad-ui.example.com";
description = mdDoc ''
This is the URL that is used for the 'sandbox' described in the Cryptpad documentation.
'';
};
httpAddress = mkOption {
type = types.str;
default = "127.0.0.1";
description = mdDoc "Address on which the Node.js server should listen";
description = mdDoc "Address on which the Node.js server should listen.";
};
httpPort = mkOption {
type = types.int;
default = 3000;
description = mdDoc "Port on which the Node.js server should listen";
description = mdDoc "Port on which the Node.js server should listen.";
};
maxWorkers = mkOption {
type = types.nullOr types.int;
default = null;
description = mdDoc "Number of child processes, defaults to number of cores available";
description = mdDoc "Number of child processes, defaults to number of cores available.";
};
adminKeys = mkOption {
type = types.listOf types.str;
default = [];
description = mdDoc "List of public signing keys of users that can access the admin panel";
default = [ ];
description = mdDoc "List of public signing keys of users that can access the admin panel.";
example = [ "[[email protected]/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=]" ];
};
filePath = mkOption {
type = types.str;
default = "./datastore/";
description = mdDoc "FIXME";
description = mdDoc ''
Specifies the directory where files are stored.
'';
};
archivePath = mkOption {
type = types.str;
default = "./data/archive";
description = mdDoc "FIXME";
description = mdDoc ''
Specifies the directory where archived data is stored.
'';
};
pinPath = mkOption {
type = types.str;
default = "./data/pins";
description = mdDoc "FIXME";
description = mdDoc ''
Specifies the directory where pinned documents are stored.
'';
};
taskPath = mkOption {
type = types.str;
default = "./data/tasks";
description = mdDoc "FIXME";
description = mdDoc ''
Specifies the directory where scheduled tasks are stored.
'';
};
blockPath = mkOption {
type = types.str;
default = "./block";
description = mdDoc "FIXME";
description = mdDoc ''
Specifies the directory where users' authenticated blocks are stored.
'';
};
blobPath = mkOption {
type = types.str;
default = "./blob";
description = mdDoc "FIXME";
description = mdDoc ''
Specifies the directory where encrypted blobs are stored.
'';
};
blobStagingPath = mkOption {
type = types.str;
default = "./data/blobstage";
description = mdDoc "FIXME";
description = mdDoc ''
Specifies the directory where incomplete blobs are stored.
'';
};
decreePath = mkOption {
type = types.str;
default = "./data/decrees";
description = mdDoc "FIXME";
description = mdDoc ''
Specifies the directory where decrees are stored.
'';
};
logPath = mkOption {
type = types.str;
default = "./data/logs";
description = mdDoc "FIXME";
type = types.oneOf [ types.str types.bool];
default = false;
description = mdDoc ''
Specifies the directory where logs are stored. Set to false (or nothing) if you'd rather
not log.
'';
};
logToStdout = mkOption {
type = types.bool;
Expand All @@ -155,19 +186,21 @@ in
logFeedback = mkOption {
type = types.bool;
default = false;
description = mdDoc "FIXME";
description = mdDoc ''
Provide usage feedback to the Cryptpad admin.
'';
};
verbose = mkOption {
type = types.bool;
default = false;
description = mdDoc "Controls verbose logging";
description = mdDoc "Controls verbose logging.";
};
installMethod = mkOption {
type = types.str;
default = "nixos";
description = mdDoc ''
Install method is listed in telemetry if you agree to it through the consentToContact
setting in the admin panel.
Install method information included in server telemetry to voluntarily indicate how many
instances are using unofficial installation methods such as Nix.
'';
};
blockDailyCheck = mkOption {
Expand All @@ -192,11 +225,13 @@ in

config = mkIf cfg.enable (mkMerge [
{
users.users.cryptpad = {
isSystemUser = true;
group = "cryptpad";
users = {
users.cryptpad = {
isSystemUser = true;
group = "cryptpad";
};
groups.cryptpad = { };
};
users.groups.cryptpad = { };

systemd.services.cryptpad = {
description = "Cryptpad service";
Expand All @@ -216,71 +251,38 @@ in
};
};
}
(mkIf cfg.confinement {
systemd.services.cryptpad = {
serviceConfig.BindReadOnlyPaths = [
configFile
# apparently needs proc for workers management
"/proc"
"/dev/urandom"
] ++ (if ! cfg.config.blockDailyCheck then [
"/etc/resolv.conf"
"/etc/hosts"
] else []);
confinement = {
enable = true;
binSh = null;
mode = "chroot-only";
};
};
})

(mkIf cfg.configureNginx {
assertions = [
{ assertion = cfg.config.httpUnsafeOrigin != "";
{
assertion = cfg.config.httpUnsafeOrigin != "";
message = "services.cryptpad.config.httpUnsafeOrigin is required";
}
{ assertion = strings.hasPrefix "https://" cfg.config.httpUnsafeOrigin;
{
assertion = strings.hasPrefix "https://" cfg.config.httpUnsafeOrigin;
message = "services.cryptpad.config.httpUnsafeOrigin must start with https://";
}
{ assertion = isNull cfg.config.httpSafeOrigin || strings.hasPrefix "https://" cfg.config.httpSafeOrigin;
{
assertion = cfg.config.httpSafeOrigin == null || strings.hasPrefix "https://" cfg.config.httpSafeOrigin;
message = "services.cryptpad.config.httpSafeOrigin must start with https:// (or be unset)";
}
];

services.nginx = {
enable = true;
recommendedTlsSettings = true;

# FIXME: Check / compare this with [Nginx module configuration in nixpkgs](https://github.com/NixOS/nixpkgs/blob/nixos-23.11/nixos/modules/services/web-servers/nginx/default.nix).
# Find out why Cryptpad has this in their documetation. Does this decrease the security of a Cryptpad install
# if not used?
# diffie-hellman parameters are used to negotiate keys for your session
# generate strong parameters using the following command
# ssl_dhparam /etc/nginx/dhparam.pem; # openssl dhparam -out /etc/nginx/dhparam.pem 4096
#
# sslDhparam = null;

# FIXME: Check / compare this with [Nginx module configuration in nixpkgs](https://github.com/NixOS/nixpkgs/blob/nixos-23.11/nixos/modules/services/web-servers/nginx/default.nix).
# Find out why Cryptpad has this in their documentation. Does this decrease the security of a Cryptpad install
# if not used?
# replace with the IP address of your resolver
# resolver 8.8.8.8 8.8.4.4 1.1.1.1 1.0.0.1 9.9.9.9 149.112.112.112 208.67.222.222 208.67.220.220;
#
# resolver = {};

virtualHosts = mkMerge [
{
"${mainDomain}" = {
serverAliases = if isNull cfg.config.httpSafeOrigin then [ ] else [ sandboxDomain ];
# NOTE: I see no reason not to enable ACME and forcing SSL if you enable Nginx for
# Cryptpad, IMHO. Given the security context of Cryptpad, it should only ever be used with SSL.
serverAliases = lib.optionals (cfg.config.httpSafeOrigin != null) [ sandboxDomain ];
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://${cfg.config.httpAddress}:${builtins.toString cfg.config.httpPort}";
proxyWebsockets = true;
extraConfig = ''
client_max_body_size 150m;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
client_max_body_size ${cfg.clientMaxBodySize};
add_header Strict-Transport-Security "max-age=${toString cfg.hstsMaxAge}; includeSubDomains" always;
'';
};
};
Expand Down

0 comments on commit 7e9ae9e

Please sign in to comment.