feat(copyparty): trust reverse proxy headers
This commit was merged in pull request #54.
This commit is contained in:
73
flake.nix
73
flake.nix
@@ -118,6 +118,79 @@
|
|||||||
touch $out
|
touch $out
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
copyparty-reverse-proxy-trust-config =
|
||||||
|
let
|
||||||
|
pikachuConfig = self.nixosConfigurations.pikachu.config;
|
||||||
|
serviceConfig = pikachuConfig.containers.copyparty.config.systemd.services.copyparty.serviceConfig;
|
||||||
|
configScript = builtins.readFile serviceConfig.ExecStartPre;
|
||||||
|
plainConfig =
|
||||||
|
(lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
specialArgs.my = self.lib;
|
||||||
|
modules = [
|
||||||
|
./modules/services/copyparty.nix
|
||||||
|
{
|
||||||
|
modules.services.copyparty = {
|
||||||
|
enable = true;
|
||||||
|
passwordFile = "/run/password";
|
||||||
|
volumes."/" = {
|
||||||
|
path = "/srv/files";
|
||||||
|
access.A = [ "alexion" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}).config;
|
||||||
|
plainScript = builtins.readFile plainConfig.systemd.services.copyparty.serviceConfig.ExecStartPre;
|
||||||
|
in
|
||||||
|
pkgs.runCommand "copyparty-reverse-proxy-trust-config-check" { } ''
|
||||||
|
${
|
||||||
|
lib.optionalString (
|
||||||
|
!(
|
||||||
|
lib.hasInfix " rproxy: 1" configScript
|
||||||
|
&& lib.hasInfix " xff-hdr: x-forwarded-for" configScript
|
||||||
|
&& lib.hasInfix " xff-src: 10.23.20.109/32" configScript
|
||||||
|
&& !(lib.hasInfix "rproxy" plainScript)
|
||||||
|
&& !(lib.hasInfix "xff-hdr" plainScript)
|
||||||
|
&& !(lib.hasInfix "xff-src" plainScript)
|
||||||
|
)
|
||||||
|
) "exit 1"
|
||||||
|
}
|
||||||
|
touch $out
|
||||||
|
'';
|
||||||
|
|
||||||
|
copyparty-reverse-proxy-trust-requires-source =
|
||||||
|
let
|
||||||
|
testConfig =
|
||||||
|
(lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
specialArgs.my = self.lib;
|
||||||
|
modules = [
|
||||||
|
./modules/services/copyparty.nix
|
||||||
|
{
|
||||||
|
modules.services.copyparty = {
|
||||||
|
enable = true;
|
||||||
|
passwordFile = "/run/password";
|
||||||
|
reverseProxy.enable = true;
|
||||||
|
volumes."/" = {
|
||||||
|
path = "/srv/files";
|
||||||
|
access.A = [ "alexion" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}).config;
|
||||||
|
hasFailedTrustedSourcesAssertion = lib.any (
|
||||||
|
assertion:
|
||||||
|
assertion.assertion == false
|
||||||
|
&& assertion.message == "modules.services.copyparty.reverseProxy.trustedSources must name at least one trusted proxy source when reverse-proxy support is enabled."
|
||||||
|
) testConfig.assertions;
|
||||||
|
in
|
||||||
|
pkgs.runCommand "copyparty-reverse-proxy-trust-requires-source-check" { } ''
|
||||||
|
${lib.optionalString (!hasFailedTrustedSourcesAssertion) "exit 1"}
|
||||||
|
touch $out
|
||||||
|
'';
|
||||||
|
|
||||||
reverse-proxy-basic-route =
|
reverse-proxy-basic-route =
|
||||||
let
|
let
|
||||||
testConfig =
|
testConfig =
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ in
|
|||||||
enable = true;
|
enable = true;
|
||||||
inherit (cfg) accountName stateDir;
|
inherit (cfg) accountName stateDir;
|
||||||
passwordFile = "/run/secrets/${cfg.passwordSecret}";
|
passwordFile = "/run/secrets/${cfg.passwordSecret}";
|
||||||
|
reverseProxy = {
|
||||||
|
enable = cfg.reverseProxy.enable;
|
||||||
|
inherit (cfg.reverseProxy) trustedSources;
|
||||||
|
};
|
||||||
volumes = lib.mapAttrs (_urlPath: volume: {
|
volumes = lib.mapAttrs (_urlPath: volume: {
|
||||||
inherit (volume) path access flags;
|
inherit (volume) path access flags;
|
||||||
}) cfg.volumes;
|
}) cfg.volumes;
|
||||||
@@ -77,6 +81,12 @@ in
|
|||||||
description = "Host path bind-mounted as Copyparty's state directory.";
|
description = "Host path bind-mounted as Copyparty's state directory.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
reverseProxy.trustedSources = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "IP ranges that may supply Copyparty's forwarded client IP header.";
|
||||||
|
};
|
||||||
|
|
||||||
volumes = lib.mkOption {
|
volumes = lib.mkOption {
|
||||||
type = lib.types.attrsOf volumeType;
|
type = lib.types.attrsOf volumeType;
|
||||||
default = { };
|
default = { };
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{ pkgs, ... }:
|
{ config, pkgs, ... }:
|
||||||
# pikachu — AZW ME Pro server.
|
# pikachu — AZW ME Pro server.
|
||||||
# Disk layout is in ./disk.nix.
|
# Disk layout is in ./disk.nix.
|
||||||
# `fileSystems` for the root disk are derived from it.
|
# `fileSystems` for the root disk are derived from it.
|
||||||
@@ -91,6 +91,7 @@
|
|||||||
enable = true;
|
enable = true;
|
||||||
vlan = 20;
|
vlan = 20;
|
||||||
mac = "02:98:64:5a:07:4f";
|
mac = "02:98:64:5a:07:4f";
|
||||||
|
endpoint.address = "10.23.20.109";
|
||||||
};
|
};
|
||||||
|
|
||||||
guests.copyparty = {
|
guests.copyparty = {
|
||||||
@@ -103,6 +104,7 @@
|
|||||||
enable = true;
|
enable = true;
|
||||||
host = "files.alexion.dev";
|
host = "files.alexion.dev";
|
||||||
port = 3923;
|
port = 3923;
|
||||||
|
trustedSources = [ "${config.modules.network.endpoints."reverse-proxy".address}/32" ];
|
||||||
};
|
};
|
||||||
volumes = {
|
volumes = {
|
||||||
"/inbox" = {
|
"/inbox" = {
|
||||||
|
|||||||
@@ -48,6 +48,14 @@ let
|
|||||||
''}
|
''}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
renderTrustedSource = source: " xff-src: ${source}";
|
||||||
|
|
||||||
|
renderedReverseProxy = lib.optionalString cfg.reverseProxy.enable ''
|
||||||
|
rproxy: 1
|
||||||
|
xff-hdr: x-forwarded-for
|
||||||
|
${lib.concatStringsSep "\n" (map renderTrustedSource cfg.reverseProxy.trustedSources)}
|
||||||
|
'';
|
||||||
|
|
||||||
renderedVolumes = lib.concatStringsSep "\n" (lib.mapAttrsToList renderVolume cfg.volumes);
|
renderedVolumes = lib.concatStringsSep "\n" (lib.mapAttrsToList renderVolume cfg.volumes);
|
||||||
|
|
||||||
configScript = pkgs.writeShellScript "copyparty-config" ''
|
configScript = pkgs.writeShellScript "copyparty-config" ''
|
||||||
@@ -68,7 +76,7 @@ let
|
|||||||
ses-db: ${cfg.stateDir}/sessions.db
|
ses-db: ${cfg.stateDir}/sessions.db
|
||||||
chpw-db: ${cfg.stateDir}/chpw.json
|
chpw-db: ${cfg.stateDir}/chpw.json
|
||||||
shr-db: ${cfg.stateDir}/shares.db
|
shr-db: ${cfg.stateDir}/shares.db
|
||||||
|
${renderedReverseProxy}
|
||||||
[accounts]
|
[accounts]
|
||||||
${cfg.accountName}: $password
|
${cfg.accountName}: $password
|
||||||
|
|
||||||
@@ -128,6 +136,16 @@ in
|
|||||||
default = { };
|
default = { };
|
||||||
description = "Copyparty volumes keyed by their URL path.";
|
description = "Copyparty volumes keyed by their URL path.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
reverseProxy = {
|
||||||
|
enable = lib.mkEnableOption "reverse-proxy trust settings for Copyparty";
|
||||||
|
|
||||||
|
trustedSources = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "IP ranges that may supply Copyparty's forwarded client IP header.";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
@@ -136,6 +154,10 @@ in
|
|||||||
assertion = cfg.volumes != { };
|
assertion = cfg.volumes != { };
|
||||||
message = "modules.services.copyparty.volumes must declare at least one served volume.";
|
message = "modules.services.copyparty.volumes must declare at least one served volume.";
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
assertion = !cfg.reverseProxy.enable || cfg.reverseProxy.trustedSources != [ ];
|
||||||
|
message = "modules.services.copyparty.reverseProxy.trustedSources must name at least one trusted proxy source when reverse-proxy support is enabled.";
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
users.users.${cfg.user} = {
|
users.users.${cfg.user} = {
|
||||||
|
|||||||
Reference in New Issue
Block a user