Remove ai-worker from docker group and enforce sudo whitelist. SECURITY: Being in the docker group gives unrestricted access to the Docker daemon socket (/var/run/docker.sock), allowing any docker command: docker exec, docker cp, docker run -v /:/host, docker commit, etc. Changes: - Remove extraGroups = ["docker"] from ai-worker user definition - Add comprehensive sudo NOPASSWD whitelist for safe docker subcommands ALLOWED: ps, inspect, logs, images, info, version, stats, start, stop, restart, rm, rmi, wait, pull, build, run, compose, system, network ls, volume ls BLOCKED (implicitly): exec, cp, commit, diff, export, import, load, save, attach, push, tag, create, plugin, network create, volume create - Update ai-worker-restricted.nix module to reflect new approach - Update README-ai-worker.md with new security model and examples All docker commands must now be prefixed with sudo. The Hermes agent's host_run tool needs to be updated to prepend sudo.
206 lines
6.9 KiB
Nix
206 lines
6.9 KiB
Nix
{ pkgs, inputs, config, keys, ... }: {
|
|
users.users.ai-worker = {
|
|
isSystemUser = true;
|
|
group = "ai-worker";
|
|
home = "/home/ai-worker";
|
|
createHome = true;
|
|
# SECURITY: ai-worker is NOT in the docker group.
|
|
# Docker access is restricted via sudo whitelist — only specific subcommands allowed.
|
|
# extraGroups = [ "docker" ]; — REMOVED: docker group gives unrestricted docker daemon access
|
|
shell = pkgs.bashInteractive;
|
|
openssh.authorizedKeys.keys = [
|
|
keys.users.ai-worker.main
|
|
];
|
|
# No password login - SSH key only
|
|
hashedPassword = "!";
|
|
};
|
|
users.groups.ai-worker = {};
|
|
|
|
# Enable restricted AI worker SSH access for ollama benchmarking
|
|
# SECURITY: ai-worker can only:
|
|
# - SSH into host from Hermes container
|
|
# - Run docker commands via sudo (whitelist below — no exec/cp/commit)
|
|
# - Run specific security audit commands
|
|
# - NO access to infra repo (no bind mount)
|
|
# - NO nix/nixos-rebuild/nh commands
|
|
# WORKFLOW: SSH from Hermes container, run docker commands via sudo, return and save results
|
|
services.aiWorkerAccess = true;
|
|
|
|
# Restricted sudo for ai-worker
|
|
# IMPORTANT: ai-worker is NOT in docker group. All docker access goes through sudo.
|
|
# Only the subcommands listed below are allowed — everything else is denied.
|
|
# This prevents: docker exec, docker cp, docker commit, and other file-modifying operations.
|
|
security.sudo.extraRules = [
|
|
{
|
|
users = [ "ai-worker" ];
|
|
commands = [
|
|
# === Docker commands: lifecycle management (NO file modification) ===
|
|
# ps/inspect/logs — read-only status checks
|
|
{
|
|
command = "/run/current-system/sw/bin/docker ps";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/docker inspect *";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/docker logs *";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/docker images";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/docker info";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/docker version";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/docker stats *";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
|
|
# start/stop/restart — container lifecycle
|
|
{
|
|
command = "/run/current-system/sw/bin/docker start *";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/docker stop *";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/docker restart *";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/docker rm *";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/docker rmi *";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/docker wait *";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
|
|
# pull/build/run — image management and container creation
|
|
{
|
|
command = "/run/current-system/sw/bin/docker pull *";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/docker build *";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/docker run *";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
|
|
# compose — orchestration
|
|
{
|
|
command = "/run/current-system/sw/bin/docker compose *";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
|
|
# system — disk cleanup
|
|
{
|
|
command = "/run/current-system/sw/bin/docker system *";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
|
|
# network — list only (create/modify not needed)
|
|
{
|
|
command = "/run/current-system/sw/bin/docker network ls";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
|
|
# volume — list only (create/modify not needed)
|
|
{
|
|
command = "/run/current-system/sw/bin/docker volume ls";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
|
|
# === EXPLICITLY DENIED docker commands (not in whitelist — sudo rejects them) ===
|
|
# docker exec — executes arbitrary commands inside running containers (FILE MODIFICATION)
|
|
# docker cp — copies files between containers and host (FILE ACCESS)
|
|
# docker commit — creates images from running containers (DATA EXFIL)
|
|
# docker diff — inspects filesystem changes (INFO LEAK)
|
|
# docker export — exports container filesystem (DATA EXFIL)
|
|
# docker import — imports filesystem archives
|
|
# docker load — loads docker images
|
|
# docker save — saves docker images to tar (DATA EXFIL)
|
|
# docker attach — attaches to running containers (INTERACTIVE ACCESS)
|
|
# docker push — pushes images to registries (DATA EXFIL)
|
|
# docker tag — renames images
|
|
# docker create — creates containers (use 'docker run' instead)
|
|
# docker plugin — manages plugins
|
|
# docker network create/rm — network management
|
|
# docker volume create/rm — volume management
|
|
|
|
# === Firewall checks ===
|
|
{
|
|
command = "/run/wrappers/bin/sudo iptables -L -n -v";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/wrappers/bin/sudo iptables -S";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
|
|
# === Fail2ban status ===
|
|
{
|
|
command = "/run/current-system/sw/bin/fail2ban-client status";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/fail2ban-client status *";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/fail2ban-client get * banned";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
|
|
# === Log inspection ===
|
|
{
|
|
command = "/run/current-system/sw/bin/journalctl -t kernel -n 100";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/journalctl -u fail2ban -n 50";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/journalctl -u firewall -n 50";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
|
|
# === SSH config verification ===
|
|
{
|
|
command = "/run/current-system/sw/bin/sshd -T";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
|
|
# === Network diagnostics ===
|
|
{
|
|
command = "/run/current-system/sw/bin/ss -tlnp";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
{
|
|
command = "/run/current-system/sw/bin/cat /proc/net/tcp";
|
|
options = [ "NOPASSWD" ];
|
|
}
|
|
];
|
|
}
|
|
];
|
|
}
|