Follow-up: UPDATING Caddy as a user

Sep 24, 2016   #caddy  #server  #tutorial 

This is a follow-up to my earlier blog post, Running a Python webapp with Caddy and Gunicorn as a user. It was a short tutorial how to run the Caddy webserver as a user (that is not as root, you only need root once for setup, after that you can do EVERYTHING as a user). Except…

The problem

Except you CAN’T do everything as a user. When you update Caddy, you replace the executable file and thus the flags on it that setcap set, that allow it to bind to port 80 and 443. So you’d have to sudo and do it again.
That’s quite annoying (at least to my perfectionist sensitivities).

The solution

Write a little C program that calls setcap for us and make sure it runs as root, even if we call it from our www-user.

Here’s the program:

#include <stdlib.h>
#include <unistd.h>

int main()
{
    setuid( 0 );
    system("/sbin/setcap CAP_NET_BIND_SERVICE=+eip /home/www/caddy");
    return 0;
}

This program does nothing but run the command from the earlier tutorial to give the caddy executable in www’s home folder (and really only that file!) the capabilities to run on “lower” ports.

Next we run:

gcc set_caddy_ports.c
sudo mv a.out /usr/sbin/set_caddy_ports
sudo mod u+s /usr/sbin/set_caddy_ports

This compiles the program, moves it to /usr/sbin/ and sets the setuid flag, so it is always executed with root-privileges.

Now, this could possibly open a privilege escalation bug. I’m not a security expert, so I can’t guarantee this is safe!

But: I do this only for my convenience. I have root access (well, sudo rights), it’s just that I appreciate seperation of concerns and not mucking about as root any more than strictly necessary.
I am the only user on the system. Deploy on a multi-user system at your own risk! (But do you really want to let an untrusted user have anything bind to port 80? I don’t think so!)

Now the user can, after updating Caddy, run:

set_caddy_ports

Et voilĂ !

The non-problem

Updating Caddy manually is annoying. So much to type! You even have to remember which plugins you were using. Why not write a shell script to do all of that for you?

In the .caddy directory I created two files, url.txt and plugins.txt. The former contains the Caddy download url:

https://caddyserver.com/download/build?os=linux&arch=amd64&features=

The latter whatever plugins you need, comma seperated, like this:

git,mulitpass,filemanager

Then there is the update script itself, simply called update, also in the .caddy directory:

#!/bin/bash
OLDDIR=`pwd`
TARGETDIR=/home/www/
cd "${0%/*}"
curl -# "`head -c -1 -q url.txt plugins.txt`"| tar -xzf - caddy
mv caddy $TARGETDIR
set_caddy_ports
cd $OLDDIR

Make it executable:

chmod +x ~/.caddy/update

Running .caddy/update now downloads Caddy with the configured plugins, extracts it to /home/www/ and then runs the set_caddy_ports command we created earlier.