Setuid Script Wrapper Example
29 Apr 2017The problem? I have a Node.js script that needs to run as root and Linux doesn’t like me to shoot myself in the foot.
I have a bunch of sysadmin utilities that I like to use to do various clean up tasks on my projects. I call them “agents” and they are just short Node.js scripts that run forever using PM2 (PM2 is one of my favorite utilities ever because it lets you wrap up all your microservices into one frontend, no matter what language you wrote them in – also you’re allowed to say microservices and not sound like a complete tool).
One of these agents was ping-agent.js
, a Node script that uses net-ping
and node-schedule
to periodically send ping packets to a list
of hosts and write the results back to a Redis cache. The problem is that doing a ping requires privileged access to the OS and using sudo put
it in an entirely different PM2 daemon so it wasn’t appearing with my normal pm2 list
results. Sad!
Most of the time this would be a job for setuid, but setuid doesn’t work for interpreted scripts for security reasons. One solution is to code up a quick-and-dirty wrapper in C. This is usually a Bad Idea™ since it’s a perfect way to do privilege escalation on a compromised system. My version of this is to make it as narrowly defined as possible and useable for only a specific case, while still being kind of portable when I need to re-deploy code in a different situation.
Here’s my solution:
The C preprocessor here requires that you explicitly give a path to the script to run but the Makefile
figures that out for you
so you don’t need to hardcode something.
make
pm2 start wrapper --name ping-agent
[PM2] Spawning PM2 daemon with pm2_home=/home/austin/.pm2
[PM2] PM2 Successfully daemonized
[PM2] Starting /home/austin/projects/ping-agent/wrapper in fork_mode (1 instance)
[PM2] Done.
┌────────────┬────┬──────┬───────┬────────┬─────────┬────────┬─────┬───────────┬──────────┐
│ App name │ id │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ watching │
├────────────┼────┼──────┼───────┼────────┼─────────┼────────┼─────┼───────────┼──────────┤
│ ping-agent │ 0 │ fork │ 14568 │ online │ 0 │ 0s │ 3% │ 24.0 MB │ disabled │
└────────────┴────┴──────┴───────┴────────┴─────────┴────────┴─────┴───────────┴──────────┘
Use `pm2 show <id|name>` to get more details about an app
Woo! Now I can run my Node.js stuff in PM2 without needing a spearate PM2 daemon for root-ish activity. One big caveat is that you need to
have a shebang line on your Node.js script (e.g. #!/usr/bin/env node
).
Final warning: setuid is still pretty nasty. It’s probably not a good idea to use it for anything that gets input from the outside world.