summary refs log tree commit diff
path: root/nixos/modules/virtualisation/containers.nix
diff options
context:
space:
mode:
authorWilhelm Schuster <ws@wilhelm.re>2016-09-25 17:33:01 +0200
committerWilhelm Schuster <ws@wilhelm.re>2016-09-25 19:25:17 +0200
commit0d1e1b1810081f8fb6496e2879bfa8326d79e580 (patch)
treebdcbbdd67bb4e379c64a3b6187065e52fcbec89f /nixos/modules/virtualisation/containers.nix
parentf081a1aaf4ec30f953c8c68ce1be9f687179d4e5 (diff)
downloadnixlib-0d1e1b1810081f8fb6496e2879bfa8326d79e580.tar
nixlib-0d1e1b1810081f8fb6496e2879bfa8326d79e580.tar.gz
nixlib-0d1e1b1810081f8fb6496e2879bfa8326d79e580.tar.bz2
nixlib-0d1e1b1810081f8fb6496e2879bfa8326d79e580.tar.lz
nixlib-0d1e1b1810081f8fb6496e2879bfa8326d79e580.tar.xz
nixlib-0d1e1b1810081f8fb6496e2879bfa8326d79e580.tar.zst
nixlib-0d1e1b1810081f8fb6496e2879bfa8326d79e580.zip
containers module: Add option to enable tunnel access
This adds the containers.<name>.enableTun option allowing containers to
access /dev/net/tun. This is required by openvpn, tinc, etc. in order to
work properly inside containers.

The new option builds on top of two generic options
containers.<name>.additionalCapabilities and
containers.<name>.allowedDevices which also can be used for example when
adding support for FUSE later down the road.
Diffstat (limited to 'nixos/modules/virtualisation/containers.nix')
-rw-r--r--nixos/modules/virtualisation/containers.nix165
1 files changed, 118 insertions, 47 deletions
diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix
index eaa4d828afb6..155a56fba8dc 100644
--- a/nixos/modules/virtualisation/containers.nix
+++ b/nixos/modules/virtualisation/containers.nix
@@ -129,6 +129,9 @@ let
         --setenv HOST_ADDRESS6="$HOST_ADDRESS6" \
         --setenv LOCAL_ADDRESS6="$LOCAL_ADDRESS6" \
         --setenv PATH="$PATH" \
+        ${if cfg.additionalCapabilities != null then
+          ''--capability="${concatStringsSep " " cfg.additionalCapabilities}"'' else ""
+        } \
         ${containerInit cfg} "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/init"
     '';
 
@@ -205,6 +208,41 @@ let
       ''
   );
 
+  serviceDirectives = cfg: {
+    ExecReload = pkgs.writeScript "reload-container"
+      ''
+        #! ${pkgs.stdenv.shell} -e
+        ${pkgs.nixos-container}/bin/nixos-container run "$INSTANCE" -- \
+          bash --login -c "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/bin/switch-to-configuration test"
+      '';
+
+    SyslogIdentifier = "container %i";
+
+    EnvironmentFile = "-/etc/containers/%i.conf";
+
+    Type = "notify";
+
+    # Note that on reboot, systemd-nspawn returns 133, so this
+    # unit will be restarted. On poweroff, it returns 0, so the
+    # unit won't be restarted.
+    RestartForceExitStatus = "133";
+    SuccessExitStatus = "133";
+
+    Restart = "on-failure";
+
+    # Hack: we don't want to kill systemd-nspawn, since we call
+    # "machinectl poweroff" in preStop to shut down the
+    # container cleanly. But systemd requires sending a signal
+    # (at least if we want remaining processes to be killed
+    # after the timeout). So send an ignored signal.
+    KillMode = "mixed";
+    KillSignal = "WINCH";
+
+    DevicePolicy = "closed";
+    DeviceAllow = map (d: "${d.node} ${d.modifier}") cfg.allowedDevices;
+  };
+
+
   system = config.nixpkgs.system;
 
   bindMountOpts = { name, config, ... }: {
@@ -235,6 +273,27 @@ let
 
   };
 
+  allowedDeviceOpts = { name, config, ... }: {
+    options = {
+      node = mkOption {
+        example = "/dev/net/tun";
+        type = types.str;
+        description = "Path to device node";
+      };
+      modifier = mkOption {
+        example = "rw";
+        type = types.str;
+        description = ''
+          Device node access modifier. Takes a combination
+          <literal>r</literal> (read), <literal>w</literal> (write), and
+          <literal>m</literal> (mknod). See the
+          <literal>systemd.resource-control(5)</literal> man page for more
+          information.'';
+      };
+    };
+  };
+
+
   mkBindFlag = d:
                let flagPrefix = if d.isReadOnly then " --bind-ro=" else " --bind=";
                    mountstr = if d.hostPath != null then "${d.hostPath}:${d.mountPoint}" else "${d.mountPoint}";
@@ -302,6 +361,8 @@ let
   dummyConfig =
     {
       extraVeths = {};
+      additionalCapabilities = [];
+      allowedDevices = [];
       hostAddress = null;
       hostAddress6 = null;
       localAddress = null;
@@ -368,6 +429,26 @@ in
               '';
             };
 
+            additionalCapabilities = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = [ "CAP_NET_ADMIN" "CAP_MKNOD" ];
+              description = ''
+                Grant additional capabilities to the container.  See the
+                capabilities(7) and systemd-nspawn(1) man pages for more
+                information.
+              '';
+            };
+            enableTun = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Allows the container to create and setup tunnel interfaces
+                by granting the <literal>NET_ADMIN</literal> capability and
+                enabling access to <literal>/dev/net/tun</literal>.
+              '';
+            };
+
             privateNetwork = mkOption {
               type = types.bool;
               default = false;
@@ -422,6 +503,16 @@ in
                 '';
             };
 
+            allowedDevices = mkOption {
+              type = types.listOf types.optionSet;
+              options = [ allowedDeviceOpts ];
+              default = [];
+              example = [ { node = "/dev/net/tun"; modifier = "rw"; } ];
+              description = ''
+                A list of device nodes to which the containers has access to.
+              '';
+            };
+
           } // networkOptions;
 
           config = mkMerge
@@ -488,59 +579,39 @@ in
 
       restartIfChanged = false;
 
-      serviceConfig = {
-        ExecReload = pkgs.writeScript "reload-container"
-          ''
-            #! ${pkgs.stdenv.shell} -e
-            ${pkgs.nixos-container}/bin/nixos-container run "$INSTANCE" -- \
-              bash --login -c "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/bin/switch-to-configuration test"
-          '';
-
-        SyslogIdentifier = "container %i";
-
-        EnvironmentFile = "-/etc/containers/%i.conf";
-
-        Type = "notify";
-
-        # Note that on reboot, systemd-nspawn returns 133, so this
-        # unit will be restarted. On poweroff, it returns 0, so the
-        # unit won't be restarted.
-        RestartForceExitStatus = "133";
-        SuccessExitStatus = "133";
-
-        Restart = "on-failure";
-
-        # Hack: we don't want to kill systemd-nspawn, since we call
-        # "machinectl poweroff" in preStop to shut down the
-        # container cleanly. But systemd requires sending a signal
-        # (at least if we want remaining processes to be killed
-        # after the timeout). So send an ignored signal.
-        KillMode = "mixed";
-        KillSignal = "WINCH";
-
-        DevicePolicy = "closed";
-      };
+      serviceConfig = serviceDirectives dummyConfig;
     };
   in {
     systemd.services = listToAttrs (filter (x: x.value != null) (
       # The generic container template used by imperative containers
       [{ name = "container@"; value = unit; }]
       # declarative containers
-      ++ (mapAttrsToList (name: cfg: nameValuePair "container@${name}" (
-        unit // {
-          preStart = preStartScript cfg;
-          script = startScript cfg;
-          postStart = postStartScript cfg;
-        } // (
-        if cfg.autoStart then
-          {
-            wantedBy = [ "multi-user.target" ];
-            wants = [ "network.target" ];
-            after = [ "network.target" ];
-            restartTriggers = [ cfg.path ];
-            reloadIfChanged = true;
-          }
-        else {})
+      ++ (mapAttrsToList (name: cfg: nameValuePair "container@${name}" (let
+          config = cfg // (
+          if cfg.enableTun then
+            {
+              allowedDevices = cfg.allowedDevices
+                ++ [ { node = "/dev/net/tun"; modifier = "rw"; } ];
+              additionalCapabilities = cfg.additionalCapabilities
+                ++ [ "CAP_NET_ADMIN" ];
+            }
+          else {});
+        in
+          unit // {
+            preStart = preStartScript config;
+            script = startScript config;
+            postStart = postStartScript config;
+            serviceConfig = serviceDirectives config;
+          } // (
+          if config.autoStart then
+            {
+              wantedBy = [ "multi-user.target" ];
+              wants = [ "network.target" ];
+              after = [ "network.target" ];
+              restartTriggers = [ config.path ];
+              reloadIfChanged = true;
+            }
+          else {})
       )) config.containers)
     ));