Using copyToRoot to add sudo to images created by dockerTools

I have been successfully creating docker images containing sudo with dockerTools while I was using the contents attribute. Since contents is now obsolete, I tried switching to the copyToRoot but that gives me errors when certain (but not all) other packages are added. It looks like in certain cases /sbin/sudo and/or /etc/sudoers are not in fact copied to root but kept as symlinks to the nix store (so the commands to make sudo suid or add lines to sudoers fail). For instance this code works when contents is used but fails when copyToRoot is used.

{ dockerTools
, buildEnv
, runtimeShell
, bashInteractive
, gawk
, sudo
}:
dockerTools.buildImage {
  name = "test-image";
  tag = "latest";
  created = "now";

  copyToRoot = buildEnv {
    name = "test image";
    paths = [
      sudo
      bashInteractive
      gawk
    ];
    pathsToLink = [ "/" ];
  };

  runAsRoot = ''
         #!${runtimeShell}

         ${dockerTools.shadowSetup}

         groupadd -g 100 users
         useradd -m -g users -u 1000 testuser

         chmod +s /sbin/sudo
         echo "testuser    ALL=(ALL)    NOPASSWD:    ALL" >> /etc/sudoers
'';
  config = {
    WorkingDir = "/home/testuser";
    User = "testuser";
  };
}

The error is

error: builder for '/nix/store/p6gfsrc7z00vv7nyccmi703c49p2zild-docker-layer-test-image.drv' failed with exit code 1;
       last 10 log lines:
       > Allocating group tables: done
       > Writing inode tables: done
       > Writing superblocks and filesystem accounting information: done
       >
       > Executing pre-mount steps...
       > Adding contents...
       > Adding /nix/store/jmrjy103yjpidgh6rj2xl7cz3i6s0k8l-test-image...
       > Executing post-mount steps...
       > /nix/store/i2r0qgfplqzyyd93bhigpid421za86kv-run-as-root.sh: line 34: /etc/sudoers: Permission denied
       > [    3.741406] reboot: Power down
       For full logs, run 'nix log /nix/store/p6gfsrc7z00vv7nyccmi703c49p2zild-docker-layer-test-image.drv'.

Adding ls -l /etc/sudoers to the script confirms that it is a symlink (to /nix/store/wrvfcflkxiwxqg9wqqfmqn5g3qiymfn9-sudo-1.9.13/etc/sudoers). But if I remove gawk from the list, the sudoers file is actually copied to /etc and everything works. As I mentioned, it only happens with certain packages. If instead of gawk I start adding some other packages, it continues to work for a while, then stumbles again on something. Sometimes /sbin/sudo becomes a symlink so I can’t suid it. Is this actually a buildEnv problem? Removing pathsToLink doesn’t help.

2 Likes

The only workaround I was able to find is move sudo to a separate layer:

{ dockerTools
, buildEnv
, runCommand
, runtimeShell
, bashInteractive
, gawk
, sudo
}:

let
  sudo-image = dockerTools.buildImage {
    name = "sudo image";
    tag = "latest";
    created = "now";

    copyToRoot = sudo;

    runAsRoot = ''
      #!${runtimeShell}

      ${dockerTools.shadowSetup}

      groupadd -g 100 users
      useradd -m -g users -u 1000 testuser

      chmod +s /sbin/sudo
      echo "testuser    ALL=(ALL)    NOPASSWD:    ALL" >> /etc/sudoers
    '';
  };
in
dockerTools.buildImage {
  name = "test-image";
  tag = "latest";
  created = "now";

  fromImage = sudo-image;

  copyToRoot = buildEnv {
    name = "test image";
    paths = [
      bashInteractive
      gawk
    ];
    pathsToLink = [ "/" ];
  };

  config = {
    WorkingDir = "/home/testuser";
    User = "testuser";
    Cmd = "/bin/bash";
  };
}

Is that the right approach?