diff options
author | github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> | 2023-08-24 00:01:48 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-24 00:01:48 +0000 |
commit | 18b5b2d4487a54016299ec7bbf8c6352a3b0ecec (patch) | |
tree | dcfc162c0eede8a6931c30a26224a345b23b1869 /nixos | |
parent | 1e23ff55be53d9328f705ff4ed8feb51b0b7ac9b (diff) | |
parent | dfe898c8014622334a2ad541f1837b5319905b34 (diff) | |
download | nixlib-18b5b2d4487a54016299ec7bbf8c6352a3b0ecec.tar nixlib-18b5b2d4487a54016299ec7bbf8c6352a3b0ecec.tar.gz nixlib-18b5b2d4487a54016299ec7bbf8c6352a3b0ecec.tar.bz2 nixlib-18b5b2d4487a54016299ec7bbf8c6352a3b0ecec.tar.lz nixlib-18b5b2d4487a54016299ec7bbf8c6352a3b0ecec.tar.xz nixlib-18b5b2d4487a54016299ec7bbf8c6352a3b0ecec.tar.zst nixlib-18b5b2d4487a54016299ec7bbf8c6352a3b0ecec.zip |
Merge master into staging-next
Diffstat (limited to 'nixos')
-rw-r--r-- | nixos/modules/security/wrappers/default.nix | 10 | ||||
-rw-r--r-- | nixos/modules/security/wrappers/wrapper.c | 109 | ||||
-rw-r--r-- | nixos/modules/security/wrappers/wrapper.nix | 4 | ||||
-rw-r--r-- | nixos/modules/services/networking/tailscale.nix | 2 | ||||
-rw-r--r-- | nixos/modules/services/networking/twingate.nix | 2 | ||||
-rw-r--r-- | nixos/modules/services/web-servers/caddy/default.nix | 54 | ||||
-rw-r--r-- | nixos/tests/caddy.nix | 22 | ||||
-rw-r--r-- | nixos/tests/virtualbox.nix | 2 | ||||
-rw-r--r-- | nixos/tests/wrappers.nix | 11 |
9 files changed, 89 insertions, 127 deletions
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix index 12255d8392fe..24f368b3e967 100644 --- a/nixos/modules/security/wrappers/default.nix +++ b/nixos/modules/security/wrappers/default.nix @@ -5,8 +5,8 @@ let parentWrapperDir = dirOf wrapperDir; - securityWrapper = pkgs.callPackage ./wrapper.nix { - inherit parentWrapperDir; + securityWrapper = sourceProg : pkgs.callPackage ./wrapper.nix { + inherit sourceProg; }; fileModeType = @@ -91,8 +91,7 @@ let , ... }: '' - cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}" - echo -n "${source}" > "$wrapperDir/${program}.real" + cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}" # Prevent races chmod 0000 "$wrapperDir/${program}" @@ -119,8 +118,7 @@ let , ... }: '' - cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}" - echo -n "${source}" > "$wrapperDir/${program}.real" + cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}" # Prevent races chmod 0000 "$wrapperDir/${program}" diff --git a/nixos/modules/security/wrappers/wrapper.c b/nixos/modules/security/wrappers/wrapper.c index 17776a97af81..2cf1727a31c8 100644 --- a/nixos/modules/security/wrappers/wrapper.c +++ b/nixos/modules/security/wrappers/wrapper.c @@ -17,6 +17,10 @@ #include <syscall.h> #include <byteswap.h> +#ifndef SOURCE_PROG +#error SOURCE_PROG should be defined via preprocessor commandline +#endif + // aborts when false, printing the failed expression #define ASSERT(expr) ((expr) ? (void) 0 : assert_failure(#expr)) // aborts when returns non-zero, printing the failed expression and errno @@ -24,10 +28,6 @@ extern char **environ; -// The WRAPPER_DIR macro is supplied at compile time so that it cannot -// be changed at runtime -static char *wrapper_dir = WRAPPER_DIR; - // Wrapper debug variable name static char *wrapper_debug = "WRAPPER_DEBUG"; @@ -151,115 +151,20 @@ static int make_caps_ambient(const char *self_path) { return 0; } -int readlink_malloc(const char *p, char **ret) { - size_t l = FILENAME_MAX+1; - int r; - - for (;;) { - char *c = calloc(l, sizeof(char)); - if (!c) { - return -ENOMEM; - } - - ssize_t n = readlink(p, c, l-1); - if (n < 0) { - r = -errno; - free(c); - return r; - } - - if ((size_t) n < l-1) { - c[n] = 0; - *ret = c; - return 0; - } - - free(c); - l *= 2; - } -} - int main(int argc, char **argv) { ASSERT(argc >= 1); - char *self_path = NULL; - int self_path_size = readlink_malloc("/proc/self/exe", &self_path); - if (self_path_size < 0) { - fprintf(stderr, "cannot readlink /proc/self/exe: %s", strerror(-self_path_size)); - } - - unsigned int ruid, euid, suid, rgid, egid, sgid; - MUSTSUCCEED(getresuid(&ruid, &euid, &suid)); - MUSTSUCCEED(getresgid(&rgid, &egid, &sgid)); - - // If true, then we did not benefit from setuid privilege escalation, - // where the original uid is still in ruid and different from euid == suid. - int didnt_suid = (ruid == euid) && (euid == suid); - // If true, then we did not benefit from setgid privilege escalation - int didnt_sgid = (rgid == egid) && (egid == sgid); - - - // Make sure that we are being executed from the right location, - // i.e., `safe_wrapper_dir'. This is to prevent someone from creating - // hard link `X' from some other location, along with a false - // `X.real' file, to allow arbitrary programs from being executed - // with elevated capabilities. - int len = strlen(wrapper_dir); - if (len > 0 && '/' == wrapper_dir[len - 1]) - --len; - ASSERT(!strncmp(self_path, wrapper_dir, len)); - ASSERT('/' == wrapper_dir[0]); - ASSERT('/' == self_path[len]); - - // If we got privileges with the fs set[ug]id bit, check that the privilege we - // got matches the one one we expected, ie that our effective uid/gid - // matches the uid/gid of `self_path`. This ensures that we were executed as - // `self_path', and not, say, as some other setuid program. - // We don't check that if we did not benefit from the set[ug]id bit, as - // can be the case in nosuid mounts or user namespaces. - struct stat st; - ASSERT(lstat(self_path, &st) != -1); - - // if the wrapper gained privilege with suid, check that we got the uid of the file owner - ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == euid)); - // if the wrapper gained privilege with sgid, check that we got the gid of the file group - ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == egid)); - // same, but with suid instead of euid - ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == suid)); - ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == sgid)); - - // And, of course, we shouldn't be writable. - ASSERT(!(st.st_mode & (S_IWGRP | S_IWOTH))); - - // Read the path of the real (wrapped) program from <self>.real. - char real_fn[PATH_MAX + 10]; - int real_fn_size = snprintf(real_fn, sizeof(real_fn), "%s.real", self_path); - ASSERT(real_fn_size < sizeof(real_fn)); - - int fd_self = open(real_fn, O_RDONLY); - ASSERT(fd_self != -1); - - char source_prog[PATH_MAX]; - len = read(fd_self, source_prog, PATH_MAX); - ASSERT(len != -1); - ASSERT(len < sizeof(source_prog)); - ASSERT(len > 0); - source_prog[len] = 0; - - close(fd_self); // Read the capabilities set on the wrapper and raise them in to // the ambient set so the program we're wrapping receives the // capabilities too! - if (make_caps_ambient(self_path) != 0) { - free(self_path); + if (make_caps_ambient("/proc/self/exe") != 0) { return 1; } - free(self_path); - execve(source_prog, argv, environ); + execve(SOURCE_PROG, argv, environ); fprintf(stderr, "%s: cannot run `%s': %s\n", - argv[0], source_prog, strerror(errno)); + argv[0], SOURCE_PROG, strerror(errno)); return 1; } diff --git a/nixos/modules/security/wrappers/wrapper.nix b/nixos/modules/security/wrappers/wrapper.nix index e3620fb222d2..aec43412404e 100644 --- a/nixos/modules/security/wrappers/wrapper.nix +++ b/nixos/modules/security/wrappers/wrapper.nix @@ -1,4 +1,4 @@ -{ stdenv, linuxHeaders, parentWrapperDir, debug ? false }: +{ stdenv, linuxHeaders, sourceProg, debug ? false }: # For testing: # $ nix-build -E 'with import <nixpkgs> {}; pkgs.callPackage ./wrapper.nix { parentWrapperDir = "/run/wrappers"; debug = true; }' stdenv.mkDerivation { @@ -7,7 +7,7 @@ stdenv.mkDerivation { dontUnpack = true; hardeningEnable = [ "pie" ]; CFLAGS = [ - ''-DWRAPPER_DIR="${parentWrapperDir}"'' + ''-DSOURCE_PROG="${sourceProg}"'' ] ++ (if debug then [ "-Werror" "-Og" "-g" ] else [ diff --git a/nixos/modules/services/networking/tailscale.nix b/nixos/modules/services/networking/tailscale.nix index f308b7e33114..8b35cc8d6669 100644 --- a/nixos/modules/services/networking/tailscale.nix +++ b/nixos/modules/services/networking/tailscale.nix @@ -6,7 +6,7 @@ let cfg = config.services.tailscale; isNetworkd = config.networking.useNetworkd; in { - meta.maintainers = with maintainers; [ danderson mbaillie twitchyliquid64 ]; + meta.maintainers = with maintainers; [ danderson mbaillie twitchyliquid64 mfrw ]; options.services.tailscale = { enable = mkEnableOption (lib.mdDoc "Tailscale client daemon"); diff --git a/nixos/modules/services/networking/twingate.nix b/nixos/modules/services/networking/twingate.nix index 170d392bf213..03c68fc874f0 100644 --- a/nixos/modules/services/networking/twingate.nix +++ b/nixos/modules/services/networking/twingate.nix @@ -17,7 +17,7 @@ in }; networking.firewall.checkReversePath = lib.mkDefault "loose"; - services.resolved.enable = !(config.networking.networkmanager.enable); + services.resolved.enable = lib.mkIf (!config.networking.networkmanager.enable) true; environment.systemPackages = [ cfg.package ]; # For the CLI. }; diff --git a/nixos/modules/services/web-servers/caddy/default.nix b/nixos/modules/services/web-servers/caddy/default.nix index 5cc9ef6dd6d9..cec0b379f67a 100644 --- a/nixos/modules/services/web-servers/caddy/default.nix +++ b/nixos/modules/services/web-servers/caddy/default.nix @@ -24,21 +24,26 @@ let } ''; - configFile = - let - Caddyfile = pkgs.writeTextDir "Caddyfile" '' - { - ${cfg.globalConfig} - } - ${cfg.extraConfig} - ''; + settingsFormat = pkgs.formats.json { }; - Caddyfile-formatted = pkgs.runCommand "Caddyfile-formatted" { nativeBuildInputs = [ cfg.package ]; } '' - mkdir -p $out - cp --no-preserve=mode ${Caddyfile}/Caddyfile $out/Caddyfile - caddy fmt --overwrite $out/Caddyfile - ''; - in + configFile = + if cfg.settings != { } then + settingsFormat.generate "caddy.json" cfg.settings + else + let + Caddyfile = pkgs.writeTextDir "Caddyfile" '' + { + ${cfg.globalConfig} + } + ${cfg.extraConfig} + ''; + + Caddyfile-formatted = pkgs.runCommand "Caddyfile-formatted" { nativeBuildInputs = [ cfg.package ]; } '' + mkdir -p $out + cp --no-preserve=mode ${Caddyfile}/Caddyfile $out/Caddyfile + caddy fmt --overwrite $out/Caddyfile + ''; + in "${if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform then Caddyfile-formatted else Caddyfile}/Caddyfile"; etcConfigFile = "caddy/caddy_config"; @@ -299,6 +304,27 @@ in which could delay the reload essentially indefinitely. ''; }; + + settings = mkOption { + type = settingsFormat.type; + default = {}; + description = lib.mdDoc '' + Structured configuration for Caddy to generate a Caddy JSON configuration file. + See <https://caddyserver.com/docs/json/> for available options. + + ::: {.warning} + Using a [Caddyfile](https://caddyserver.com/docs/caddyfile) instead of a JSON config is highly recommended by upstream. + There are only very few exception to this. + + Please use a Caddyfile via {option}`services.caddy.configFile`, {option}`services.caddy.virtualHosts` or + {option}`services.caddy.extraConfig` with {option}`services.caddy.globalConfig` instead. + ::: + + ::: {.note} + Takes presence over most `services.caddy.*` options, such as {option}`services.caddy.configFile` and {option}`services.caddy.virtualHosts`, if specified. + ::: + ''; + }; }; # implementation diff --git a/nixos/tests/caddy.nix b/nixos/tests/caddy.nix index 238091ec606f..5a0d3539394b 100644 --- a/nixos/tests/caddy.nix +++ b/nixos/tests/caddy.nix @@ -34,6 +34,20 @@ import ./make-test-python.nix ({ pkgs, ... }: { "http://localhost:8081" = { }; }; }; + specialisation.rfc42.configuration = { + services.caddy.settings = { + apps.http.servers.default = { + listen = [ ":80" ]; + routes = [{ + handle = [{ + body = "hello world"; + handler = "static_response"; + status_code = 200; + }]; + }]; + }; + }; + }; }; }; @@ -41,6 +55,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { let justReloadSystem = "${nodes.webserver.system.build.toplevel}/specialisation/config-reload"; multipleConfigs = "${nodes.webserver.system.build.toplevel}/specialisation/multiple-configs"; + rfc42Config = "${nodes.webserver.system.build.toplevel}/specialisation/rfc42"; in '' url = "http://localhost/example.html" @@ -62,5 +77,12 @@ import ./make-test-python.nix ({ pkgs, ... }: { ) webserver.wait_for_open_port(8080) webserver.wait_for_open_port(8081) + + with subtest("rfc42 settings config"): + webserver.succeed( + "${rfc42Config}/bin/switch-to-configuration test >&2" + ) + webserver.wait_for_open_port(80) + webserver.succeed("curl http://localhost | grep hello") ''; }) diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix index 062b125eb611..e522d0679e15 100644 --- a/nixos/tests/virtualbox.nix +++ b/nixos/tests/virtualbox.nix @@ -519,4 +519,4 @@ in mapAttrs (mkVBoxTest false vboxVMs) { destroy_vm_test1() destroy_vm_test2() ''; -} // (lib.optionalAttrs enableUnfree unfreeTests) +} // (optionalAttrs enableUnfree unfreeTests) diff --git a/nixos/tests/wrappers.nix b/nixos/tests/wrappers.nix index 391e9b42b45b..1f5f43286384 100644 --- a/nixos/tests/wrappers.nix +++ b/nixos/tests/wrappers.nix @@ -84,6 +84,17 @@ in test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -g', '0') test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -rg', '0') + # Test that in nonewprivs environment the wrappers simply exec their target. + test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -u', '${toString userUid}') + test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -ru', '${toString userUid}') + test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -g', '${toString usersGid}') + test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -rg', '${toString usersGid}') + + test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -u', '${toString userUid}') + test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -ru', '${toString userUid}') + test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -g', '${toString usersGid}') + test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -rg', '${toString usersGid}') + # We are only testing the permitted set, because it's easiest to look at with capsh. machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_CHOWN')) machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_SYS_ADMIN')) |