Simplistic Auto Provisioning for BSDs, UNIX and Linux, using just DHCP

For a few weeks now i've been thinking about better tools to provision our bare-metal servers and VMs. All tools out there are IMHO bloatware. Over-complicated stuff where nobody knows when the next library upstream will break feature X which will prevent shit from working. Typical wobbly constructs we have these days. I'm not a fan of them, you shouldn't be either.

But yesterday noon i read one more of these guides to setup something which wants you to curl their installer and pipe it through bash, YIKES.

Then in my typical haze, i decided to play a little mind-game, WHEN would be a moment where this curl | bash scenario would be valid, or at least a bearable solution? Of course! A solution to my previous provisioning dilemma presented itself...

What you need

  • a HTTP Server (nginx, apache, anything that can serve a file)
  • a DHCP Server where you can define custom fields (dnsmasq, kea, isc-dhcpd, ..)
    • a DHCP Client which lets you parse custom fields (dhcpcd, isc-dhcpc. NOT Henning Brauer's dhclient)

The quick gist

  • DHCP server sends out custom field with URL inside
  • DHCP client picks up that field, processes it in hook with curl | sh

WARNING: THIS IS POTENTIALLY DANGEROUS! THIS IS KEPT SIMPLE FOR THE SAKE OF THIS HOWTO

BETTER APPROACH: gpg sign the script (even when auto-generated) on the server side, and have the client verify the signature against the pubkey.

How to do it

First configure your DHCP Server to deliver a custom field in the reserved range (upwards of 200 i think, but check before you decide). In the Payload we just stick in an URL that can be reached from a DHCP client.

dnsmasq.conf

dhcp-option-force=254,http://192.168.0.1/bootstrap.sh

dhcpd.conf

option server-bootstrap code 254 = string;
subnet 192.168.0.0 netmask 255.255.255.0 {
    [...]
    option server-bootstrap "http://192.168.0.1/bootstrap.sh";
}

Client configuration

Next you need to slightly modify your client's setup, i've only used dhcpcd for this, as FreeBSD's and OpenBSD's default dhclient, can't do custom fields anymore, they all get filtered and there is no configuration for it anymore.

dhcpcd

On FreeBSD, i've placed a dhcpcd.enter-hook script at /usr/local/etc/dhcpcd.enter-hook

#!/bin/sh

# for security reasons, you should really check here if bootstrapping is required
# you don't want anyone pushing bad scripts that get executed by a rogue dhcp server
if [ "${new_bootstrap}" != "" ]; then
    TMP=$(mktemp)
    fetch -o ${TMP} ${new_bootstrap}
    # for more security, you might also want to gpg sign your script and have gpg verify it here
    sh ${TMP} || exit 1
fi

Last we need to modify dhcpcd.conf to request the extra field, so it gets delievered by the DHCP Server. I just added those two lines to the default:

define 254 string bootstrap
option bootstrap

bootstrap.sh hosted on the HTTP Server

This is our bootstrapping shell script. This could be anything, there could be many of these for each profile, there could also be a rendering process on the server side, whatever floats your boat. Mine is just a basic sample to get the idea across:

#!/bin/sh

echo
echo
echo "first: do some meaningful diagnosis/inventory here"
echo "  like posting dmidecode and other stuff to your remote"
echo
echo "second: if this is used to bootstrap bare metal machines booting pxe"
echo "  IMPORTANT: check for existing installations on your disk"
echo "             like is there a partitioning scheme already here?"
echo "  then you could go ahead and install whatever you want"
echo
echo "third: enroll this system into configuration management like CFengine"
echo "  like: cf-agent -B your.cf.host && cf-agent -KIC"
echo
echo "sleeping 10 seconds... then just running some wall command"
sleep 10
echo "dhcp-bootstrapping sez HELLO KITTENS!"|wall

The result

Output from running dhcpcd em0 shows that it works :)

DUID 00:01:00:01:21:6f:7f:9e:08:00:27:d7:7f:f9
em0: IAID 27:d7:7f:f9
em0: rebinding lease of 192.168.168.80
em0: leased 192.168.168.80 for 7200 seconds
em0: changing route to 192.168.168.0/24
em0: changing default route via 192.168.168.1
/tmp/tmp.BYYgx9dr                             100% of  691  B 1670 kBps 00m00s


first: do some meaningful diagnosis/inventory here
  like posting dmidecode and other stuff to your remote

second: if this is used to bootstrap bare metal machines booting pxe
  IMPORTANT: check for existing installations on your disk
             like is there a partitioning scheme already here?
  then you could go ahead and install whatever you want

third: enroll this system into configuration management like CFengine
  like: cf-agent -B your.cf.host && cf-agent -KIC

sleeping 10 seconds... then just running some wall command

Broadcast Message from root@test
        (/dev/pts/0) at 15:45 CEST...

dhcp-bootstrapping sez HELLO KITTENS!

forked to background, child pid 34507
root@test:~ #

Final thoughts

These very simple elements thrown together in the right way, make up for a very reliable and especially maintainable setup! No wiggly parts, no extra software you don't have running anyways. Just plain old Ops-Tech put together the right way. Easy to investigate with tools you already know, easy to customize the heck out of it.

I hope this helps some of you to build better, more reliable and easier to maintain systems.

Flattr me!

Tell your friends!