lsupg Nix Support
I have been reading Ian Henry’s How to Learn Nix series of blog posts and am just about caught up. I have learned a number of things through the series and have really enjoyed it. One of the things that I have learned is directly related to my lsupg software, which lists items in a container that can be upgraded.
One of the challenges of using containers in production is managing software upgrades. When an upgrade is available, a new image needs to be built, and then containers need to be re-run using the updated image. This is particularly important for security upgrades. Note that updating official images is not sufficient, because the official images tend to lag behind OS upgrades. In practice, one should apply upgrades on top of official images or simply avoid third-party images altogether.
lsupg
is a utility that helps determine what needs to be
updated. Some companies simply upgrade everything on a fixed
schedule (every night), and lsupg
can help prune the image
hierarchy so that only images that have upgrades (or depend on images
that have upgrades) are rebuilt. lsupg
provides an
abstraction layer over different operating systems, and my initial
release includes support for Nix. When using Docker, it can be used with
the nixos/nix
image.
Support for Nix is implemented using the following two commands:
$ nix-channel --update
$ nix-env --upgrade --dry-run
The first command syncs the package channel, and the second command
lists available upgrades of installed packages. It seems
straightforward, but, thanks to Ian’s blog series, I learned that
nix-env --upgrade
is fundamentally broken and should not be
used!
Docker Demonstration
I will demonstrate the lsupg
issue using the same
package that Ian used in his How
to install Python blog post: python3
.
First, run an ephemeral container for the demonstration:
$ docker run --rm -it --hostname nix nixos/nix
The container has the nixpkgs-unstable
channel
configured by default, so we update it to synchronize the package
channel:
nix:/# nix-channel --list
nixpkgs https://nixos.org/channels/nixpkgs-unstable
nix:/# nix-channel --update
unpacking channels...
created 1 symlinks in user environment
The following command installs one of the many Python 3 packages, making it available in the current environment:
nix:/# nix-env -iA nixpkgs.python3
installing 'python3-3.8.9'
...
building '/nix/store/pa4gyj30a360yw845pwh810s9pvs4zp1-user-environment.drv'...
created 76 symlinks in user environment
This command installs the package via attribute path. This is
critically important because it installs the current “stable” version of
Python 3. If one were to install by name instead
(nix-env -i python3
), the version with the highest version
number (an alpha release) is installed instead. The consensus is that
the nix-env -i
command is fundamentally broken and should
never be used.
When lsupg
lists available upgrades, it runs the command
without specifying any names or attributes in order to determine
all available upgrades. Unfortunately, upgrades are determined
by name, even when a package was installed by attribute path. In this
case, the command checks for upgrades to the python3
name and registers the upgrade to the alpha release as
available.
nix:/# nix-env --upgrade --dry-run
(dry run; not doing anything)
upgrading 'python3-3.8.9' to 'python3-3.10.0a5'
...
This is not desired. The consensus is that the
nix-env -u
command is fundamentally broken and should
never be used. (Unfortunately both broken commands are
demonstrated in the Quick
Start of the Nix manual!)
Aside: Python Versions
The current “stable” version of Python 3 in Nixpkgs (3.8.9) is actually old… The latest Python 3.8 release is 3.8.11, and the more recent releases include security fixes! Also, Python 3.9 has been out for more than half a year. Conventional operating systems like Debian tend to use old versions of such packages because they have to work with many other packages that might be installed on the system. I do not know why Nixpkgs does not keep up to date even though it does not have such constraints, but I suspect that it has to do with compatibility with Python modules defined in the monolithic repository.
Nix security is a topic that is too large to include in this blog entry…
Declarative Package Management
The “correct” way to manage “installed” Nix packages is to do so
declaratively using attribute paths. When using NixOS, packages
are listed by attribute path in the
/etc/nix/configuration.nix
configuration file.
Unfortunately, there is no standard for declarative package management
outside of NixOS. If there were a standard configuration file, then a
general upgrade command would be possible. Without a standard
configuration file, the “correct” way to upgrade “installed” Nix
packages depends on how the system is configured.
Note that even the NixOS manual includes a broken upgrade command in the Ad-Hoc Package Management section!
lsupg
Plans
Nix container images tend to not use NixOS. The
nixos/nix
image installs Nix on top of Alpine Linux. Due to
the issue described in this blog entry, Nix support in
lsupg
is currently broken.
I am currently considering two options:
Remove support for Nix. Frankly, people who use Nix are probably not very security-conscious or use it in environments where security is not a significant concern. If there is not a good way to monitor upgrade availability in Nix, one can always just rebuild everything periodically (daily).
Only support environments that install packages declaratively using a fixed configuration path. For example,
/etc/nix/packages.nix
might be a good name for such a file.
I plan on giving it some more thought and consulting friends before making a decision and moving forward.