3. Privileges and Trust

Hint

Abstract

This chapter explains which features of Flake Pilot need root privileges and how they get them: through one program, the Flake Pilot helper, which is called through sudo. It describes what the helper checks, what a sudo rule for it grants and introduces the reference sudoers policy which comes with the flake-pilot package.

Note

A sudo rule for the helper is not a root shell, but it is trust.

The helper provides a fixed set of commands and checks every argument and the authority of the calling user. A user with a sudo rule for it cannot run arbitrary commands as root. The rules do let the user change the network setup of the host for the firecracker VMs, and run every podman flake registered system wide, as root, with what its registration grants on the host.

3.1. How Flake Pilot Gets Its Privileges

Flake Pilot does not install any setuid binary. Every action which needs more privileges than the calling user has is performed by the helper:

/usr/libexec/flake-pilot/flake-pilot-helper

flake-ctl, podman-pilot and bubblewrap-pilot call it through sudo. It is the only program Flake Pilot calls through sudo. A caller who is root already runs it directly. Every other program, podman included, is called with the privileges of the process that calls it.

The helper belongs to root and refuses to run as a setuid program, it has to be called through sudo or by root. It takes the calling user from sudo and clears its environment before it does anything else. Every command checks its arguments against a strict pattern before they become part of a path, a device name or a program argument, and it checks the authority of the caller against data which belongs to root. man 8 flake-pilot-helper lists the commands.

3.2. What the Helper Checks

The network setup of the firecracker VMs

The private network of the VMs has to be one of the networks Flake Pilot selects from and must not collide with a network of the host. The outgoing interface has to exist. Both are recorded in the network state, /var/lib/firecracker/network-state.yaml, which belongs to root. The address of a VM, the route to it and its firewall rules are only ever created within the recorded network. The TAP device of a VM is created for the caller, only root can create it for another user. The flake configuration which identifies the VM has to belong to the caller or to the system wide registry. See Firecracker Networking.

NFS exports

A directory can only be exported by its owner, or by root for a user other than root. It is exported to exactly the VM the network state records for the flake instance. Every access of the VM is mapped to the owner of the directory. Only that owner and root can release the export. See Firecracker Volumes.

The root filesystem of a bubblewrap sandbox

All paths are derived from the caller and the instance name. The directories of the setup have to belong to the caller and are opened without following a symbolic link. The tmpfs and the overlay are mounted nosuid and nodev onto these opened directories. The tree of the sandbox and the layers a flake stacks on top of it are the read only layers of the overlay, access to their content through the overlay is checked against their own permissions. The writable layer is a tmpfs of the caller which is kept until the caller discards it or the host is rebooted, it uses memory of the host meanwhile. Its size is limited by sandbox_tmpfs_size in /etc/flakes.yml, a caller can only ask for less. See Applications From a Sandbox.

System wide podman flakes

The application has to be a link to podman-pilot which belongs to root, and its registration has to belong to root. The helper starts podman-pilot as root for the caller with a fresh environment. The container and its runtime options are taken from the registration. A %VAR reference in the runtime options, e.g %HOME, resolves to the value of the user database for the caller, not to a value the caller set. The meta data of the instances is kept in a directory which belongs to root. See Applications From a Container.

3.3. What Each Feature Needs

Feature

Privileges

Grants

podman flake from the user registry, podman-pilot and flake-ctl podman

none, rootless podman of the calling user

nothing

podman flake from the system registry, podman-pilot

helper, podman-pilot command

what the registration grants, see below

flake-ctl podman for the system registry

must be called by root

n/a

firecracker flake, firecracker-pilot, from either registry

none

nothing

flake-ctl firecracker pull

none for the user registry, root for the system registry

nothing

flake-ctl firecracker network init, cleanup

helper, network-init and network-cleanup commands

the network setup of the host

flake-ctl firecracker network add, remove

helper, network-connect and network-disconnect commands

the VMs of the caller

flake-ctl firecracker volume export, release

helper, volume-export and volume-release commands

the directories of the caller

bubblewrap flake, bubblewrap-pilot, from either registry

helper, sandbox-mount and sandbox-umount commands

the sandboxes of the caller

flake-ctl init on a host without the meta data directory

helper, meta-dirs-init command

nothing

flake-ctl firecracker volume add and remove only change the flake configuration and need no privileges.

3.4. What This Means in Practice

Flakes of the calling user and firecracker flakes need no rule.

Podman flakes from the user registry run in the rootless podman setup of the user, firecracker flakes run the VM as the calling user. Neither of them calls the helper.

A system wide podman flake is granted to every user with the rule.

The flake runs as root in the podman setup of root, with the container and the runtime options of its registration. Whatever the flake can do on the host, e.g through the host directories its registration mounts into the container, every user with the rule can do through it. Register a podman flake system wide only if all of these users may use it this way.

The content of a system wide flake is trusted as well.

A delta container may ship a /systemfiles script which collects the files to provision from the host. podman-pilot runs it on the host in a bash process of the user who calls the flake, with the privileges of that user only, the files it lists are copied into the container as root. The script runs with the privileges of every user who calls the flake and decides which host files end up in a container of root. Register only containers and images whose authors you trust with that.

The network setup is host wide.

A user with the rule for it picks the private network of the VMs, the interface their traffic leaves the host through and the services of the host they may reach. Deleting the setup disconnects the VMs of all users. Consider creating the host setup as root and granting only the rules for the per flake commands.

The metadata directory is provided by root, not by the pilots.

All pilots keep the state of their instances in a private directory of the calling user below a metadata directory shared by all users, /tmp/flakes by default. The owner of that directory can delete and replace the private directories of all other users. It therefore has to belong to root and to have the permissions 1777, like /tmp. No pilot creates it. A pilot refuses to run if it does not exist or is set up differently, and tells how to fix it. The flake-pilot package lets systemd-tmpfiles create it at boot and when the package is installed, see /usr/lib/tmpfiles.d/flake-pilot.conf. On a host without systemd, or with a metadata directory other than /tmp/flakes configured in /etc/flakes.yml, call flake-ctl init once after every boot instead. It creates the directory through the helper.

A user can block the flakes of another user.

The private directory of a user is named after the ID of that user, e.g /tmp/flakes/1000. Another user can create it first, or a symbolic link or file in its place. The pilots of the user it is named after then refuse to run and name the user who owns the entry along with the call to remove it, which only root can do. flake-ctl init called as root reports all such entries.

A password limits who uses the rule.

sudo asks for the password of the calling user and caches it for a few minutes. NOPASSWD gives the user no further privilege, but it allows every process running under their account, e.g a malicious build script, to use the helper without any interaction. The reference policy does not use it.

sudo logs every call.

The calls of the helper show up in the system journal, e.g with journalctl _COMM=sudo. The helper logs the user it acts for with every change of the host network setup.

3.5. The Reference Policy

The flake-pilot package ships a reference policy as documentation, e.g in /usr/share/doc/packages/flake-pilot/flake-pilot.sudoers. In the source tree it is located in doc/sudoers/flake-pilot.sudoers.

The policy grants the members of the flake-pilot group the use of the helper, one rule per feature, and states what each rule grants. The first argument of the helper is the command to perform, each rule is limited to the commands of its feature.

sudo ignores files in /etc/sudoers.d whose name contains a dot. Copied there under its original name, the policy therefore stays inactive. To activate it:

  1. Edit a copy of the file and delete the rules of the features you do not want to grant.

  2. Create the group:

    groupadd --system flake-pilot
    
  3. Check the edited copy and install it without a dot in its name:

    visudo --check --file ./flake-pilot.sudoers
    install -m 0440 -o root -g root ./flake-pilot.sudoers \
        /etc/sudoers.d/flake-pilot
    
  4. Add the users to the group who may use the features listed above:

    usermod --append --groups flake-pilot USER
    

The complete policy:

#
# Reference sudoers policy for flake-pilot
# ========================================
#
# flake-pilot performs every action which needs root privileges
# through one program, the flake-pilot helper:
#
#   /usr/libexec/flake-pilot/flake-pilot-helper
#
# It is the only program flake-pilot runs through sudo. The helper
# belongs to root and provides a fixed set of commands. It checks
# every argument and the authority of the calling user, which it
# takes from sudo, against data which belongs to root. It never
# passes an argument of the user on to a program unchecked.
#
# What the rule grants
# --------------------
# Unlike a rule for general purpose programs like mount, iptables or
# podman, the rules for the helper do not let a user run arbitrary
# commands as root. A user with the rules can:
#
#   * Prepare and delete the network setup of the firecracker VMs:
#     IP forwarding, the firewall chains of the setup and the TAP
#     devices of the VMs. This is host wide: the user picks the
#     private network of the VMs out of the networks flake-pilot
#     selects from, the interface their traffic leaves the host
#     through and the services of the host they may use. Deleting
#     the setup disconnects the VMs of all users.
#
#   * Connect and disconnect the VMs of its own flakes and of the
#     system wide flakes. Only root can do this for another user.
#
#   * Export a directory it owns through NFS to such a VM, and
#     release the exports of its own directories. Every access of
#     the VM is mapped to the owner of the directory.
#
#   * Mount and unmount the root filesystem of its own bubblewrap
#     sandboxes: an overlay of directory trees, mounted nosuid and
#     nodev below its own directory in /var/tmp. The trees are only
#     read, the overlay grants no access the user does not have on
#     the trees themselves. The writable layer is a tmpfs, kept
#     until the user discards it or the host is rebooted, which uses
#     memory of the host meanwhile. Set sandbox_tmpfs_size in
#     /etc/flakes.yml to limit it, users can only ask for less.
#
#   * Run the podman flakes registered system wide, as root, in the
#     podman setup of root. The container and its runtime options
#     are taken from the registration, which only root can write.
#     Whatever such a flake can do on the host, e.g through the host
#     directories mounted into its container, every user with this
#     rule can do through it. A delta container runs its
#     /systemfiles script on the host, as the user who calls the
#     flake, and the host files the script lists are copied into the
#     container as root. Registering a flake system wide therefore
#     means granting it to every user with this rule, and trusting
#     the author of its container with the accounts of these users
#     and with the host files the script lists.
#
#   * Create the meta data directories of the pilots configured in
#     /etc/flakes.yml.
#
# Flakes registered by the calling user and run in its rootless
# podman setup, as well as firecracker flakes, need no rule at all.
#
# The rules below are grouped by feature. The first argument of the
# helper is the command to perform, a rule can therefore be limited
# to the commands of a feature. Delete the groups of the features you
# do not want to grant, e.g the host network setup if root creates it.
#
# Not active as shipped
# ---------------------
# sudo ignores files in /etc/sudoers.d whose name contains a dot.
# This file therefore stays inactive even when copied there under its
# original name. To activate it:
#
#   1. Edit a copy and delete the groups of the features you do
#      not want to grant
#   2. groupadd --system flake-pilot
#   3. visudo --check --file ./flake-pilot.sudoers
#   4. install -m 0440 -o root -g root ./flake-pilot.sudoers \
#          /etc/sudoers.d/flake-pilot
#   5. usermod --append --groups flake-pilot USER
#
# Passwords
# ---------
# The rules ask for the password of the calling user, which is the
# sudo default. sudo caches it for a few minutes, as configured by
# timestamp_timeout. A user who runs system wide podman flakes or
# bubblewrap sandboxes is asked for it when the cache expires.
# NOPASSWD does not give the user any additional privilege, but it
# allows every process running under the account of the user, e.g a
# malicious build script, to use the helper without any interaction.
# Weigh this against the system wide flakes the helper runs before
# using it.
#
# The log messages of the helper are only shown if PILOT_DEBUG is
# set for the caller, which passes --debug after the command then.
# The rules for commands without other arguments list that form.
#
# The helper is given by its absolute path, the way flake-pilot
# passes it to sudo. The helper refuses to run setuid, it has to be
# called through sudo or by root.
#

#
# podman flakes registered system wide, /usr/share/flakes
# -------------------------------------------------------------------
# Used by: podman-pilot
# GRANTS:  every system wide podman flake as root, see above
#
Cmnd_Alias FLAKE_HELPER_PODMAN = \
    /usr/libexec/flake-pilot/flake-pilot-helper podman-pilot *

%flake-pilot ALL = (root) FLAKE_HELPER_PODMAN

#
# bubblewrap flakes
# -------------------------------------------------------------------
# Used by: bubblewrap-pilot, for system wide flakes as well as for
#          flakes registered by the calling user
# GRANTS:  the root filesystem mounts of the sandboxes of the caller
#
Cmnd_Alias FLAKE_HELPER_SANDBOX = \
    /usr/libexec/flake-pilot/flake-pilot-helper sandbox-mount *, \
    /usr/libexec/flake-pilot/flake-pilot-helper sandbox-umount *

%flake-pilot ALL = (root) FLAKE_HELPER_SANDBOX

#
# flake-ctl firecracker network add, remove, volume export, release
# -------------------------------------------------------------------
# GRANTS:  the network connection of the VMs of the caller and the
#          NFS exports of the directories of the caller
#
Cmnd_Alias FLAKE_HELPER_VM = \
    /usr/libexec/flake-pilot/flake-pilot-helper network-connect *, \
    /usr/libexec/flake-pilot/flake-pilot-helper network-disconnect *, \
    /usr/libexec/flake-pilot/flake-pilot-helper volume-export *, \
    /usr/libexec/flake-pilot/flake-pilot-helper volume-release *

%flake-pilot ALL = (root) FLAKE_HELPER_VM

#
# flake-ctl firecracker network init, cleanup and flake-ctl init
# -------------------------------------------------------------------
# GRANTS:  the network setup of the host for the VMs of all users,
#          the meta data directories of the pilots
#
# The network setup changes the host, not a single flake. Consider
# creating it as root instead of granting this rule.
#
Cmnd_Alias FLAKE_HELPER_HOST = \
    /usr/libexec/flake-pilot/flake-pilot-helper network-init *, \
    /usr/libexec/flake-pilot/flake-pilot-helper network-cleanup, \
    /usr/libexec/flake-pilot/flake-pilot-helper network-cleanup --debug, \
    /usr/libexec/flake-pilot/flake-pilot-helper meta-dirs-init, \
    /usr/libexec/flake-pilot/flake-pilot-helper meta-dirs-init --debug

%flake-pilot ALL = (root) FLAKE_HELPER_HOST