Initial commit
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
result
|
||||
result-*
|
||||
*.qcow2
|
||||
target
|
||||
26
flake.lock
generated
Normal file
26
flake.lock
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1763283776,
|
||||
"narHash": "sha256-Y7TDFPK4GlqrKrivOcsHG8xSGqQx3A6c+i7novT85Uk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "50a96edd8d0db6cc8db57dab6bb6d6ee1f3dc49a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-unstable",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
115
flake.nix
Normal file
115
flake.nix
Normal file
@@ -0,0 +1,115 @@
|
||||
{
|
||||
inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||
|
||||
outputs = { self, nixpkgs }: let
|
||||
inherit (nixpkgs) lib;
|
||||
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
];
|
||||
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system nixpkgs.legacyPackages.${system});
|
||||
in {
|
||||
apps = forAllSystems (system: pkgs: {
|
||||
default = self.apps.${system}.vm;
|
||||
vm = {
|
||||
type = "app";
|
||||
program = "${lib.getExe self.nixosConfigurations."vm-${system}".config.system.build.vm}";
|
||||
};
|
||||
});
|
||||
|
||||
nixosModules.default = ./module.nix;
|
||||
|
||||
nixosConfigurations = lib.mapAttrs' (n: v: lib.nameValuePair "vm-${n}" v) (forAllSystems (system: pkgs:
|
||||
lib.nixosSystem {
|
||||
inherit system pkgs;
|
||||
modules = [
|
||||
"${nixpkgs}/nixos/modules/virtualisation/qemu-vm.nix"
|
||||
self.nixosModules.default
|
||||
|
||||
({ config, ... }: {
|
||||
system.stateVersion = config.system.nixos.release;
|
||||
virtualisation.graphics = false;
|
||||
|
||||
services.getty.autologinUser = "root";
|
||||
|
||||
users.motd = ''
|
||||
==================================
|
||||
Welcome to the user-jails test vm!
|
||||
|
||||
Try logging in as a user:
|
||||
ssh user1@localhost
|
||||
ssh user2@localhost
|
||||
|
||||
user1: default jail
|
||||
- private networking
|
||||
- global users (private users doesn't work atm)
|
||||
- allow outside network access
|
||||
|
||||
user2: permissive jail
|
||||
- global networking
|
||||
- global users
|
||||
- allow outside network access
|
||||
|
||||
All users have password 'foobar'
|
||||
|
||||
To exit, press Ctrl+A, then X
|
||||
==================================
|
||||
'';
|
||||
|
||||
users.users.user1 = {
|
||||
uid = 1000;
|
||||
isNormalUser = true;
|
||||
createHome = true;
|
||||
password = "foobar";
|
||||
};
|
||||
|
||||
users.users'.user1.jail = {
|
||||
enable = true;
|
||||
# Private users doesn't work inside VM for now
|
||||
# See https://github.com/NixOS/nixpkgs/issues/451167
|
||||
useGlobalUsers = true;
|
||||
};
|
||||
|
||||
users.users.user2 = {
|
||||
uid = 1001;
|
||||
isNormalUser = true;
|
||||
createHome = true;
|
||||
password = "foobar";
|
||||
};
|
||||
|
||||
users.users'.user2.jail = {
|
||||
enable = true;
|
||||
# bindGlobalNixStore = true; # doesn't do anything for now
|
||||
useGlobalNetworking = true;
|
||||
useGlobalUsers = true;
|
||||
};
|
||||
|
||||
# users.users.user3 = {
|
||||
# uid = 1002;
|
||||
# isNormalUser = true;
|
||||
# createHome = true;
|
||||
# password = "foobar";
|
||||
# };
|
||||
|
||||
# users.users'.user3.jail = {
|
||||
# enable = true;
|
||||
# allowNetworking = false;
|
||||
# };
|
||||
|
||||
# MOTD description:
|
||||
# user3: strict jail
|
||||
# - private networking
|
||||
# - private users
|
||||
# - deny outside network access
|
||||
|
||||
services.openssh.enable = true;
|
||||
|
||||
programs.vim.enable = true;
|
||||
})
|
||||
];
|
||||
}
|
||||
));
|
||||
};
|
||||
}
|
||||
199
module.nix
Normal file
199
module.nix
Normal file
@@ -0,0 +1,199 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
let
|
||||
extraUserOpts = {
|
||||
options.jail = {
|
||||
enable = lib.mkEnableOption "";
|
||||
# TODO: it's not possible to configure this with the current container module
|
||||
# chrootPath = lib.mkOption {
|
||||
# type = with lib.types; nullOr path;
|
||||
# default = null;
|
||||
# example = "/run/users/<uid>/root-mnt";
|
||||
# description = ''
|
||||
# '';
|
||||
# };
|
||||
|
||||
# Bind global nix store instead of a separate isolated one.
|
||||
bindGlobalNixStore = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
Whether to bindmount the global nix store into the user jail.
|
||||
Note that this will expose any content in the nix store downloaded by
|
||||
other users as well.
|
||||
'';
|
||||
default = false;
|
||||
example = true;
|
||||
};
|
||||
|
||||
allowNetworking = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
Whether to bridge the user's private network namespace to the main namespace
|
||||
so the user can establish network connections outwards from the host machine.
|
||||
'';
|
||||
default = true;
|
||||
example = false;
|
||||
};
|
||||
|
||||
useGlobalNetworking = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
Whether to let the user be a part of the main network namespace.
|
||||
'';
|
||||
default = false;
|
||||
example = true;
|
||||
};
|
||||
|
||||
# TODO: not sure if systemd exposes this as an option? In either case, the
|
||||
# nix socket is bindmounted in, so I think the IPC namespace is global
|
||||
# by default
|
||||
# useGlobalIPC = lib.mkOption {
|
||||
# type = lib.types.bool;
|
||||
# description = ''
|
||||
# Whether to let the user be a part of the main IPC namespace.
|
||||
# '';
|
||||
# default = false;
|
||||
# example = true;
|
||||
# };
|
||||
|
||||
useGlobalUsers = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
Whether to let the user be a part of the main user namespace.
|
||||
'';
|
||||
default = false;
|
||||
example = true;
|
||||
};
|
||||
|
||||
# NOTE: I believe this is inherently disabled
|
||||
# useGlobalPIDs = lib.mkOption {
|
||||
# type = lib.types.bool;
|
||||
# description = ''
|
||||
# Whether to let the user be a part of the main pid namespace.
|
||||
# '';
|
||||
# default = false;
|
||||
# example = true;
|
||||
# };
|
||||
|
||||
# TODO: Let the user configure their own timezone
|
||||
# useGlobalUTS = lib.mkOption {
|
||||
# type = lib.types.bool;
|
||||
# description = ''
|
||||
# Whether to let the user be a part of the main UTS namespace.
|
||||
# '';
|
||||
# default = false;
|
||||
# example = true;
|
||||
# };
|
||||
|
||||
extraBindPaths = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
description = ''
|
||||
Additional paths to bindmount into the user's jail.
|
||||
'';
|
||||
default = [ ];
|
||||
example = [ ];
|
||||
};
|
||||
|
||||
extraBindReadOnlyPaths = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
description = ''
|
||||
Additional paths to bindmount as readonly into the user's jail.
|
||||
'';
|
||||
default = [ ];
|
||||
example = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
options = {
|
||||
users.users' = lib.mkOption {
|
||||
default = { };
|
||||
type = with lib.types; attrsOf (submodule extraUserOpts);
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
assertions = [{
|
||||
assertion = config.boot.enableContainers;
|
||||
message = "Machine needs to be able to run containers in order to create user jails";
|
||||
}]
|
||||
# NOTE: needed for uid mapping to work correctly?
|
||||
++ lib.mapAttrsToList (k: _: {
|
||||
assertion = config.users.users.${k}.uid != null;
|
||||
message = "All jailed users need a deterministic uid, missing for user '${k}'";
|
||||
}) (lib.filterAttrs (_: v: v.jail.enable) config.users.users');
|
||||
|
||||
users.users = lib.pipe config.users.users' [
|
||||
(lib.filterAttrs (_: v: v.jail.enable))
|
||||
(lib.mapAttrs' (k: v: {
|
||||
name = k;
|
||||
value.group = k;
|
||||
}))
|
||||
];
|
||||
|
||||
users.groups = lib.pipe config.users.users' [
|
||||
(lib.filterAttrs (_: v: v.jail.enable))
|
||||
(lib.mapAttrs' (k: v: {
|
||||
name = k;
|
||||
value = { gid = config.users.users.${k}.uid; };
|
||||
}))
|
||||
];
|
||||
|
||||
containers = lib.mapAttrs' (k: v: {
|
||||
name = "user-jail-${k}";
|
||||
value = {
|
||||
# TODO: don't linger unless users.users.linger = true;
|
||||
autoStart = true;
|
||||
|
||||
ephemeral = true;
|
||||
privateNetwork = !v.jail.useGlobalNetworking;
|
||||
privateUsers = if v.jail.useGlobalUsers then "no" else "pick";
|
||||
extraFlags = [
|
||||
# TODO: add support for bindmount arguments instead of hacking it in here
|
||||
# "--bind=${config.users.users.${k}.home}:${config.users.users.${k}.home}:rbind,idmap"
|
||||
"--bind=${config.users.users.${k}.home}:${config.users.users.${k}.home}"
|
||||
# TODO: add support for bind-user in nixos-container module
|
||||
# "--bind-user=${k}"
|
||||
];
|
||||
config = {
|
||||
system.stateVersion = config.system.stateVersion;
|
||||
networking.hostName = config.networking.hostName;
|
||||
# NOTE: seemingly not needed with --bind-user.
|
||||
users.users.${k} = config.users.users.${k};
|
||||
users.groups.${config.users.users.${k}.group} = config.users.groups.${config.users.users.${k}.group};
|
||||
};
|
||||
};
|
||||
}) (lib.filterAttrs (_: v: v.jail.enable) config.users.users');
|
||||
|
||||
systemd.services = lib.mapAttrs' (k: v: {
|
||||
name = "container@user-jail-${k}";
|
||||
value = {
|
||||
# unitConfig.RequiresMountsFor = lib.mapAttrsToList (k: v: if v.hostPath != null then v.hostPath else k) config.containers."user-jail-${k}".bindMounts;
|
||||
};
|
||||
}) (lib.filterAttrs (_: v: v.jail.enable) config.users.users');
|
||||
|
||||
services.openssh.extraConfig = lib.pipe config.users.users' [
|
||||
(lib.filterAttrs (_: v: v.jail.enable))
|
||||
(lib.mapAttrsToList (k: v: ''
|
||||
Match User ${k}
|
||||
ForceCommand 'machinectl' --quiet shell '${k}@user-jail-${k}' $SSH_ORIGINAL_COMMAND
|
||||
''))
|
||||
lib.concatStrings
|
||||
];
|
||||
|
||||
security.polkit.enable = true;
|
||||
security.polkit.extraConfig = ''
|
||||
polkit.addRule(function(action, subject) {
|
||||
if (
|
||||
action.id === "org.freedesktop.machine1.shell"
|
||||
&& action.lookup("user") === subject.user
|
||||
&& action.lookup("machine") === "user-jail-" + subject.user
|
||||
) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
||||
'';
|
||||
|
||||
# TODO: use pam module to maybe stop the container upon closing the connection
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user