OpenBSD read-only ports tree with restrictive sudo

The OpenBSD folks strongly encourage users to use packages for software management. Most of the time, their packages just work. But sometimes, you must use a port.

OpenBSD includes an updated Apache 1.3 server, and recommends that everyone use it if at all possible. (There’s also nginx, which is the future platform, but it’s not quite integrated yet.) I have a Web application that only runs on Apache 2.2, so the included Web server is not an option. OpenBSD provides an Apache 2.2 package for people like me, which is very kind of them. But I need an Apache 2.2 with LDAP authentication support. That means I must build Apache 2.2 from a port.

If I have to use ports, then I want to do so as easily as possible. When I need to upgrade my ports, I want to be able to remove /usr/ports and extract the tarball that goes with whatever snapshot I’m running. I need the ports tree to do all its work, and store all its packages, outside the ports tree itself. This means a read-only ports tree.

I’m running the August 22 i386 snapshot everywhere. I build packages from ports on one machine and share out the package repo via NFS.

I dislike running as root for routine tasks, like building ports on the port-building machine. The ports tree supports using sudo for privileged operations. I don’t want to be continually interrupted to enter my password, though. And I don’t want to give unlimited root access via sudo without a password. This means that I need to lock down my account on this machine to only those activities needed to build packages from ports. I readily concede that building packages requires high-level privileges, but there’s a world of difference between rm -f /usr/ports/* and rm -rf /*. Could an intruder exploit this? Absolutely. You must run make(1) as root to build a port, and you can run fdisk(8) via make. But it will protect me from operator error. And my operators make errors.

I also want my minions to be able to build packages without giving out root. Because, you know, logging into a system and typing a command because someone else needs a package is extra work for me.

So, how to do this?

First, create a system group for people who may build packages. This group contains two users, myself and lasnyder. From /etc/group:

portbuild:*:10001:mwlucas,lasnyder

My /home partition has lots of space, so I’ll build everything there. First, we need four directories:

  • one for building stuff: /home/ports/wrkobjdir
  • one for completed packages: /home/ports/pkgrepo
  • distfiles: /home/ports/distdir
  • package plist database: /home/ports/plist

    Create these directories, and set their ownership (as well as /home/ports) for group writing.

    # chgrp portbuild /home/ports/*
    # chmod 775 /home/ports/*

    Any user in the portbuild group can write to these directories.

    Now tell the ports system about these directories. Make the following entries in /etc/mk.conf:

    WRKOBJDIR=/home/ports/wrkobjdir
    DISTDIR=/home/ports/distdir
    PACKAGE_REPOSITORY=/home/ports/pkgrepo
    PLIST_DB=/home/ports/plist
    SUDO=/usr/bin/sudo

    (The sudo isn’t necessary for the directories, but I’m not going to send you back later to add it. That would be lame.)

    Now for sudo. Give everyone in the portbuild group permission to run any command in the PORTBUILDCMDS alias.

    %portbuild ALL= NOPASSWD: PORTBUILDCMDS

    Now create the PORTBUILDCMDS alias. I built this alias iteratively: build a port, wait for the build to fail, add the missing command with the tightest restrictions that seem sensible, clean the port, and remake it. The following alias was sufficient for everything I tried:

    Cmnd_Alias PORTBUILDCMDS = /usr/bin/install, /usr/sbin/chown, /bin/chgrp, /bin/sh -c umask, /usr/sbin/mtree, /usr/bin/touch, /usr/bin/env, /usr/sbin/pkg_create, /bin/rm -f /home/ports/pkgrepo/*, /usr/bin/make, /usr/bin/perl /usr/ports/infrastructure/bin/*, /bin/chmod 555 /home/ports/*, /bin/mkdir -p /home/ports/*, /bin/rm -rf /home/ports/*

    Now choose a port and build it.

    # cd /usr/ports/editors/vim
    # make clean && make

    (When testing, I always clean a port before building it.)

    You might find that the build stops and you’re asked for a password. This means that sudo is trying to run a command that’s not in your command alias. Go ahead and enter your password. The build will fail, because you don’t have privileges, but you’ll get an error message in /var/log/secure. Between the error in the terminal window and the error in the log file, you should be able to figure out exactly which command failed.

    It’s impossible to know ahead of time every command that will ever be used by any port that ever exists. This iterative process is a pain at first, but once you’ve built a few ports you’ll find most of the necessary commands. The sudoers command alias I include here was sufficient to build editors/vim, which calls in python, dbus, glib, three different autoconfs, tcl/tk, CUPS, and a whole bunch of other crap. (I don’t use vim myself, mind you, but if you want a port that hauls in whole bunches of stuff, it’s a good choice. I could have built Emacs, but I wanted the build to finish today.)

    In building the first port, the ports system creates a temp directory, /tmp/portlocks. The ports system doesn’t use sudo to access this directory, and the directory is owned by the user who built the first port on this system. Change the group and assign group privileges to this directory.

    # chgrp portbuild /tmp/portslocks/
    # chmod 775 /tmp/portslocks/

    (Is this a bug, or a feature. I dunno. But I’m sure that some reader will tell me.)

    It seems that not all ports can be built without running as root. This isn’t a usual configuration, so I’m not shocked that not all code paths are tested — especially when building random software from random authors. When I tried to build devel/autoconf/2.59, I got:

    ===> Building package for autoconf-2.59p3
    Create /home/ports/pkgrepo/i386/all/autoconf-2.59p3.tgz
    Warning: @option no-default-conflict without @conflict
    mv: rename /home/ports/pkgrepo/i386/tmp/autoconf-2.59p3.tgz to /home/ports/pkgre po/i386/all/autoconf-2.59p3.tgz: Permission denied
    *** Error code 1

    I reported the error to ports@ like a good little user. It’s a holiday weekend, so I’m also not surprised I haven’t heard back.

    I only hit this error after building fifty-odd ports, though. It appears that limited sudo permissions are doable.

  • 6 Replies to “OpenBSD read-only ports tree with restrictive sudo”

    1. # chgrp portbuild /home/ports/*
      # chmod 755 /home/ports/*

      Any user in the portbuild group can write to these directories.

      permission should be 775

    2. You may want to consider chmod 1775 if you are concerned about the group write permissions. This would ensure only the person that created the file (or root) can remove the file.

    3. Hah, no doubt. Sorry, I should have clarified. I would like to do something similar, but all local, no NFS or anything like that, mostly as a learning exercise. More like what you described in chapter 13 of your latest book. I just thought it might be a neat idea to compile in a directory in /tmp, just more like a “what if” than anything else.

      Great work, by the way! I really feel like I have been missing by not checking out OpenBSD earlier and your book has been amazing at getting me familiar with OpenBSD as well as filling in a lot of holes in my general *nix knowledge.

      And also thank you so much for the reply.

    Comments are closed.