1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.virtualisation.virtualbox.host;
7
8 virtualbox = pkgs.virtualbox.override {
9 inherit (cfg) enableHardening headless;
10 };
11
12 kernelModules = config.boot.kernelPackages.virtualbox.override {
13 inherit virtualbox;
14 };
15
16in
17
18{
19 options.virtualisation.virtualbox.host = {
20 enable = mkOption {
21 type = types.bool;
22 default = false;
23 description = ''
24 Whether to enable VirtualBox.
25
26 <note><para>
27 In order to pass USB devices from the host to the guests, the user
28 needs to be in the <literal>vboxusers</literal> group.
29 </para></note>
30 '';
31 };
32
33 addNetworkInterface = mkOption {
34 type = types.bool;
35 default = true;
36 description = ''
37 Automatically set up a vboxnet0 host-only network interface.
38 '';
39 };
40
41 enableHardening = mkOption {
42 type = types.bool;
43 default = true;
44 description = ''
45 Enable hardened VirtualBox, which ensures that only the binaries in the
46 system path get access to the devices exposed by the kernel modules
47 instead of all users in the vboxusers group.
48
49 <important><para>
50 Disabling this can put your system's security at risk, as local users
51 in the vboxusers group can tamper with the VirtualBox device files.
52 </para></important>
53 '';
54 };
55
56 headless = mkOption {
57 type = types.bool;
58 default = false;
59 description = ''
60 Use VirtualBox installation without GUI and Qt dependency. Useful to enable on servers
61 and when virtual machines are controlled only via SSH.
62 '';
63 };
64 };
65
66 config = mkIf cfg.enable (mkMerge [{
67 boot.kernelModules = [ "vboxdrv" "vboxnetadp" "vboxnetflt" ];
68 boot.extraModulePackages = [ kernelModules ];
69 environment.systemPackages = [ virtualbox ];
70
71 security.wrappers = let
72 mkSuid = program: {
73 source = "${virtualbox}/libexec/virtualbox/${program}";
74 owner = "root";
75 group = "vboxusers";
76 setuid = true;
77 };
78 in mkIf cfg.enableHardening
79 (builtins.listToAttrs (map (x: { name = x; value = mkSuid x; }) [
80 "VBoxHeadless"
81 "VBoxNetAdpCtl"
82 "VBoxNetDHCP"
83 "VBoxNetNAT"
84 "VBoxSDL"
85 "VBoxVolInfo"
86 "VirtualBox"
87 ]));
88
89 users.extraGroups.vboxusers.gid = config.ids.gids.vboxusers;
90
91 services.udev.extraRules =
92 ''
93 KERNEL=="vboxdrv", OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd"
94 KERNEL=="vboxdrvu", OWNER="root", GROUP="root", MODE="0666", TAG+="systemd"
95 KERNEL=="vboxnetctl", OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd"
96 SUBSYSTEM=="usb_device", ACTION=="add", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh $major $minor $attr{bDeviceClass}"
97 SUBSYSTEM=="usb", ACTION=="add", ENV{DEVTYPE}=="usb_device", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh $major $minor $attr{bDeviceClass}"
98 SUBSYSTEM=="usb_device", ACTION=="remove", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh --remove $major $minor"
99 SUBSYSTEM=="usb", ACTION=="remove", ENV{DEVTYPE}=="usb_device", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh --remove $major $minor"
100 '';
101
102 # Since we lack the right setuid/setcap binaries, set up a host-only network by default.
103 } (mkIf cfg.addNetworkInterface {
104 systemd.services."vboxnet0" =
105 { description = "VirtualBox vboxnet0 Interface";
106 requires = [ "dev-vboxnetctl.device" ];
107 after = [ "dev-vboxnetctl.device" ];
108 wantedBy = [ "network.target" "sys-subsystem-net-devices-vboxnet0.device" ];
109 path = [ virtualbox ];
110 serviceConfig.RemainAfterExit = true;
111 serviceConfig.Type = "oneshot";
112 serviceConfig.PrivateTmp = true;
113 environment.VBOX_USER_HOME = "/tmp";
114 script =
115 ''
116 if ! [ -e /sys/class/net/vboxnet0 ]; then
117 VBoxManage hostonlyif create
118 cat /tmp/VBoxSVC.log >&2
119 fi
120 '';
121 postStop =
122 ''
123 VBoxManage hostonlyif remove vboxnet0
124 '';
125 };
126
127 networking.interfaces.vboxnet0.ip4 = [ { address = "192.168.56.1"; prefixLength = 24; } ];
128 # Make sure NetworkManager won't assume this interface being up
129 # means we have internet access.
130 networking.networkmanager.unmanaged = ["vboxnet0"];
131 })]);
132}