ISC DHCP Config Git Hooks - Pre-deploy Sanity Check
Below are some git hooks I have written to ease deployment of ISC DHCP configs to live environments. These will ensure that dhcpd breaking config will never be deployed to your live environment and give you the basic history and versioning that comes with git. Branching is not supported.
The following works on CentOS 7. Minor adjustments are probably needed for Debian based distros.
Background
I have written these git hooks for an environment where the configuration of new dhcp scopes are added manually. I needed a way to ensure the configuration was validated by dhcpd before it was deployed. The following setup will ensure this is the case.
Preparation
These packages are needed:
- dhcp-server 4.x.x
- sudo
- git
Misc:
- Assumes include based dhcp config, where the includes are stored in a separate location from dhcpd.conf.
- /etc/dhcp/dhcpd.conf is not in git, but is deployed using some configuration mangement system.
- Commands in this instruction is run as root, except where specified otherwise.
- Adjust paths to your environment.
Setting up the environment
Create group used for git:
groupadd gitdhcp
Create user(s) and add user to gitdhcp group:
useradd -s /bin/bash -G gitdhcp -d /home/someuser -m -c"Someuser info" someuser
Create sudoers file for gitdhcp users:
visudo -f /etc/sudoers.d/0300gitdhcp
Add this content to sudoers file:
Host_Alias GITDHCP_HOSTS = hostname_please_change
Runas_Alias GITDHCP_RUNAS = root
Cmnd_Alias GITDHCP_CMNDS = /bin/chgrp, /bin/cp, /usr/bin/git, /bin/systemctl restart dhcpd
Defaults!GITDHCP_CMNDS env_keep += "GIT_WORK_TREE", !requiretty
%%gitdhcp GITDHCP_HOSTS = (GITDHCP_RUNAS) NOPASSWD: GITDHCP_CMNDS
Create shared git repo (place it where you see fit, here I will use /opt and consider using a more descriptive name than dhcp_config):
cd /opt && git init --bare dhcp_config.git
Fix ownership and rights:
chown -R root:gitdhcp dhcp_config.git
chmod -R g+rw dhcp_config.git
chmod g+s $(find dhcp_config.git -type d)
The Hooks
Both of the hooks below should be placed in your newly created shared git repo in hooks folder.
cd /opt/dhcp_config.git/hooks
touch {pre,post}-receive
chmod +x {pre,post}-receive
Pre-receive
The pre-receive hook check the dhcp config separately from the running instance of dhcpd and rejects the commit all-together if the sanity check fails.
Paste the following in to /opt/dhcp_config.git/hooks/pre-receive:
#!/bin/bash
# Terminate immediately on non zero exit status. Don't allow empty variables.
set -euo pipefail
# Set exit message on fail.
trap '[ "$?" -eq 0 ] || echo -en "\E[1;31mDeployment failed! See error message on line above.\E[m"' EXIT
# Environment specific variables. Please adjust or add if needed. Set your include path here. The one you defined in your dhcpd.conf or elsewhere. This is essential.
dhcpd_conf_path='/etc/dhcp'
include_path='REMEMBER TO ADD A PATH HERE'
git_group='gitdhcp'
# tmpdir var must include $HOME
tmpdir="$HOME/dhcptest/"
# Exit if dhcpd is not running
if ! systemctl status dhcpd; then
printf '\e[1;31m%-6s\e[m\n' "Rejecting commit. Dhcpd is stopped for some reason."
exit 1
fi
# Make tmpdir in users homedir
if [[ ! -d "$tmpdir" ]]; then
mkdir "$tmpdir"
fi
# Transfer commit to tmpdir. Remember to add extra files if you added them above.
while read -r oldrev newrev refname; do
if [[ $refname = "refs/heads/master" ]] ; then
git archive "$newrev" | tar -x -C "$tmpdir"
sudo cp "$dhcpd_conf_path"/dhcpd.conf "$tmpdir"/dhcpd.conf
sudo chgrp "$git_group" "$tmpdir"/dhcpd.conf
else
printf '\e[1;31m%-6s\e[m\n' "Copy to test failed."
fi
done
# Fix paths for testing
if ! find "$tmpdir" -type f -exec sed -i s="$include_path"="$tmpdir"=g {} \; ; then
printf '\e[1;31m%-6s\e[m\n' "Path fixing in $tmpdir failed. Exiting."
exit 1
fi
# Test dhcpd config in tmpdir
printf '\e[1;32m%-6s\e[m\n' "Testing dhcpd.conf outside of running config."
if ! /sbin/dhcpd -t -cf "$tmpdir"dhcpd.conf; then
printf '\e[1;31m%-6s\e[m\n' "Sanity check failed! Fix the config."
exit 1
else
printf '\e[1;32m%-6s\e[m\n' "Dhcpd sanity check passed! Applying config."
fi
# Committing changes to live environment
# post-receive script will be called now
# Cleaning up
rm -rf "$tmpdir"
If you have files (like static ip configurations) generated by provisioning, remember to add these to .gitignore and to add checks for them to the pre-receive hook.
Post-receive
Below is the post-receive hook, which checkouts the updated repo to the GIT_WORK_TREE destination. Please read the comments.
Paste the following in to /opt/dhcp_config.git/hooks/post-receive:
#!/bin/bash
# Terminate immediately on non zero exit status. Don't allow empty variables
set -euo pipefail
# Set exit message on fail.
trap '[ "$?" -eq 0 ] || echo -en "\E[1;31mDeployment failed! See error message on line above.\E[m"' EXIT
# Environment specific directory variable. Please adjust. This is where configuration will be deployed to.
export GIT_WORK_TREE='REMEMBER TO ADD A PATH HERE!'
# Store commit message. For check below.
message="$(git --git-dir "$PWD" log -1 HEAD --pretty=format:%s)"
# @OVERRIDE in the commit message overrides if someone edited files outside git and forcibly checks out to GIT_WORK_TREE
if ! git diff --quiet; then
if echo $message | grep -q '@OVERRIDE'; then
git diff
printf '\e[1;31m%-6s\e[m\n' "Overriding local changes as requested."
else
printf '\e[1;31m%-6s\e[m\n' "Someone edited files outside git - never do this. Will not continue, please merge local changes to git. Override by including @OVERRIDE in commit message - this will overwrite all non-git edits!"
exit 1
fi
fi
# Untracked files git ls-files
nongit_files=$(git ls-files --others --exclude-standard || true)
if [[ -n $nongit_files ]]; then
if echo $message | grep -q '@OVERRIDE'; then
printf '\e[1;31m%-6s\e[m\n' "Nuking local files as requested."
else
printf '\e[1;31m%-6s\e[m\n' "Someone added the files below outside git. Untracked files are not accepted. Exiting before checkout to production. Delete or move files to git"
echo $nongit_files
exit 1
fi
fi
if sudo git checkout -f; then
printf '\e[1;32m%-6s\e[m\n' "Checkout successful!"
else
printf '\e[1;31m%-6s\e[m\n' "Checkout failed."
fi
if sudo systemctl restart dhcpd; then
printf '\e[1;32m%-6s\e[m\n' "Dhcpd restart successful."
else
printf '\e[1;31m%-6s\e[m\n' "Dhcpd restart failed. Obviously."
fi
Initial commit and deployment
Now migrate any existing dhcp configs to the shared git repository and do the initial commit. Remember to use a user who is member of the gitdhcp group on the dhcp-server:
git clone dhcp-server:/opt/dhcp_config
cd dhcp_config
scp -r dhcp-server:/path/to/dhcp/configs/* .
git add ./*
git commit -am "Initial commit. @OVERRIDE"
git push
The @OVERRIDE string is used to overwrite existing configs already at the GIT_WORK_TREE destination. See post-receive hook for more info. The @OVERRIDE string is usually only needed for the initial commit and for those rare occasions where someone has edited the configurations outside of git.
The output from the remote hooks, should look like this: