From: martin f. krafft Date: Fri, 19 Dec 2014 12:17:05 +0000 (+0100) Subject: Initial checkin X-Git-Url: https://git.madduck.net/code/gsc.git/commitdiff_plain/13f2a361642e48bebba84a1a4986e9b0d41bdcc6?ds=inline;pf=code Initial checkin --- 13f2a361642e48bebba84a1a4986e9b0d41bdcc6 diff --git a/README b/README new file mode 100644 index 0000000..5b4d286 --- /dev/null +++ b/README @@ -0,0 +1,148 @@ +gsc — Git service configuration +------------------------------- +2014-12-19-1306 madduck@madduck.net + +The following describes a method for the configuration of services, using Git +as a backend. A service in this context is e.g. a mail server like Postfix or +a DNS server like NSD, which runs on a Unix system and is configured using +files usually situated in the /etc directory. When those files are changed, +usually some steps have to be taken to fix up permissions, compile/merge +files, and reload the service. + +These steps — editing configuration files and reloading services — are also +the domain of so-called configuration management systems, such as CFengine and +Puppet. While these are indispensable tools for the general management of +machines, it is often the case that the fine-grained configuration of +a complex mailserver requires more work to express in terms of the +configuration management syntax than there are benefits. + +gsc is an alternative way of doing things. In a nutshell: + + - configuration is maintained in Git and something like gitolite can be used + to manage fine-grained access control; + + - upon push, a repo-side hook runs, reads from the repository a number of + hosts to poke and proceeds to do just that using SSH forced commands; + + - a dedicated user on each target machine receives the poke, updates the + local Git repository and executes an in-tree Makefile, which is + responsible for the integration of the changes into the running service, + possibly using in-tree host-specific parameters. + +A word for the security-conscious +''''''''''''''''''''''''''''''''' +It is true that one might question the use of in-tree configuration files and +the Makefile. After all, it allows anyone with write-access to the Git repo to +impersonate the dedicated poke user, though not without leaving a trace. + +In the end it's a matter of trust and if you're ready to assume that someone +who's supposed to be able to configure a given service on a machine can't +really do any harm by impersonating the user designed to configure the given +service, then you're on the safe side. + +The benefit of this approach is that it avoids operation as root whenever +possible, defering to sudo for just those steps that require root privileges. + +Set-up by example: nsd +---------------------- + +Let's walk through a simple example and configure the nsd DNS server to be +configurable/manageable with gsc. + + 0. We'll start with the assumption that the contents of /etc/nsd is already + tracked in a central Git repository and that trusted DNS admins can push + there. + + We'll also assume that changes to the nsd config should be pushed to two + DNS servers, grimble.example.org and grumble.example.org. + + 1. Create a new, password-less (!) SSH keyfile using ssh-keygen on your + local machine (which needs a clone of the above Git repo), store the key + into .gsc/poke-key[.pub] inside that repo, and edit the public keyfile by + prepending the following to the first (and only) line (broken here into + two lines for readability): + + command="make -sC target git_pull update",no-agent-forwarding, + no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding + + You can also add a from="" limit as per the sshd(8) manpage, although + there are benefits in not doing that, namely the ability for anyone with + access to the repo to poke the servers. + + Commit the two files .gsc/poke-key and .gsc/poke-key.pub to the + repository. + + 2. On the two "poke" hosts, make sure you have Git, make and sudo installed. + Then add a new system user, e.g. nsd_update (with group nsd_update, + disabled password, home directory /var/local/nsd_update, and shell + /bin/sh). + + 3. Copy .gsc/poke-key.pub to ~nsd_update/.ssh/authorized_keys and make sure + to run `chown -R nsd_update ~nsd_update/.ssh` or similar. + + 4. Clone the Git repo with --shared=group to /etc/nsd (or /etc/nsd3) and + similarly chown it, so that the poke user can maintain it without root + rights. Consequentially, the user "nsd" needs to be able to read + (possibly even write) the files in this repository, so you need to either + add nsd to the group nsd_update and ensure g+rwX permissions (using the + Makefile, see below), or employ ACLs. + + Make sure that the nsd_update user can pull from the repo, either by + making it publicly available, or by setting up a new password-less SSH + key for nsd_update on each host, which is then given access to the + central repo. + + 5. Create a symlink ~nsd_update/target to /etc/nsd (or whatever directory + you chose). This was done to avoid having to make /etc/nsd the + home directory of the update user and not hardcoding the location of the + configuration. + + 6. Now you should be able to poke a host from your workstation: + + ssh -Ti .gsc/poke-key -o UserKnownHostFile=.gsc/ssh_known_hosts \ + nsd_update@grimble.example.org + + which should print something like + + make: *** No rule to make target 'git_pull'. Stop. + + Do this for all hosts in pokehosts to populate .gsc/ssh_known_hosts + (verifying the fingerprints, of course), and then commit this file as + well. + + As we are using forced commands, it makes no difference what you put + after the user@host argument in the above. However, we may need this in + the future, so let's reserve it. Please refrain from using it and/or let + me know if you have any ideas. + + 7. Install the gsc-post-receive hook to hooks/post-receive in the central + git repo. When using gitolite, I suggest the use of per-repository hooks + (see further down). + + 8. Commit a file .gsc/pokehosts to the repository, listing each target + server one-per-line in user@host style, e.g. + + nsd_update@grimble.example.org + nsd_update@grumble.example.org + + The rest of each line is unused for now, but reserved for future use, so + don't put anything else there. + + 9. Now push your commits to the central repo, which should cause the hook to + poke both hosts, yielding the make error. + + 10. Now all that's left to do is write /etc/nsd/Makefile with at least the + targets git_pull and update, which should run git-pull (possibly with + --rebase) and then cause the appropriate next steps. Use sudo sparingly + for maximum security benefit. + + Please refer to the examples/ directory in the gsc repository for + a simple example that works with nsd, and which also handles per-host + difference by parametrising e.g. IP addresses or commands needed to + reload/restart the service. + +TODO: + + - per-host branches + - how to recover from faulty pushes, e.g. if the server was taken down + - gitolite notes diff --git a/examples/nsd/Makefile b/examples/nsd/Makefile new file mode 100644 index 0000000..10cfec7 --- /dev/null +++ b/examples/nsd/Makefile @@ -0,0 +1,42 @@ +#!/usr/bin/make -f +# +# Makefile — gsc example for nsd +# +# An example Makefile usable to manage nsd in the context of gsc. +# +# Copyright © 2014 martin f. krafft +# Released under the terms of the Artistic Licence 2.0 +# + +HOSTNAME = $(shell hostname --fqdn) +PARAMS_FILE = $(wildcard ./params_$(HOSTNAME)) + +ifeq ($(PARAMS_FILE),) +$(error No parameters file found for host $(HOSTNAME)) +endif + +.PHONY: update +update: .sentinel fixperms +.sentinel: .git/index Makefile nsd.conf zones.conf + @. $(PARAMS_FILE) \ + && echo "$$NSD_REBUILD_COMMAND" && $$NSD_REBUILD_COMMAND \ + && echo "$$NSD_RESTART_COMMAND" && $$NSD_RESTART_COMMAND + @touch $@ + +fixperms: ALWAYS + chmod -R o= . + setfacl -R -m group:nsd_update:rX,default:group:nsd_update:rX . + setfacl -R -m default:mask::rX,default:group::rX . + setfacl -R -m user:nsd:rX,default:user:nsd:rX . +.PHONY: fixperms + +git_pull: + git pull + +ALWAYS: +%.conf: %.conf-generator ALWAYS + @rm -f .$@.tmp && umask 0337 && ./$< $(PARAMS_FILE) > .$@.tmp + @[ -e $@ ] || touch $@ + @cmp --quiet .$@.tmp $@ || \ + { chmod 600 $@ && mv .$@.tmp $@ && echo $@ updated. ; } + @rm -f .$@.tmp diff --git a/examples/nsd/nsd.conf-generator b/examples/nsd/nsd.conf-generator new file mode 100755 index 0000000..5efc395 --- /dev/null +++ b/examples/nsd/nsd.conf-generator @@ -0,0 +1,36 @@ +#!/bin/sh +# +# nsd.conf-generator — generate nsd.conf through interpolation +# +# Copyright © 2014 martin f. krafft +# Released under the terms of the Artistic Licence 2.0 +# +set -eu + +PARAMS_FILE="$1"; +if [ ! -r "$PARAMS_FILE" ]; then + echo >&2 "E: cannot read parameters from $PARAMS_FILE" + exit 1 +fi +. $PARAMS_FILE + +NSD_DIR=${0%/*} +[ "$NSD_DIR" = "." ] && NSD_DIR=${PWD} + +exec cat <<_eof +server: + # uncomment to specify specific interfaces to bind (default all). + ip-address: 127.0.1.1 + ip-address: $BIND_IPV4 + ip-address: $BIND_IPV6 + + # don't answer VERSION.BIND and VERSION.SERVER CHAOS class queries + hide-version: yes + + # identify the server (CH TXT ID.SERVER entry). + identity: "$IDENTITY" + +# do not include subdir yet (cf. #772776) +#include: "$NSD_DIR/nsd.conf.d/*.conf" +include: "$NSD_DIR/zones.conf" +_eof diff --git a/examples/nsd/params_grimple.example.org b/examples/nsd/params_grimple.example.org new file mode 100644 index 0000000..c076503 --- /dev/null +++ b/examples/nsd/params_grimple.example.org @@ -0,0 +1,5 @@ +BIND_IPV4=192.0.2.13 +BIND_IPV6=2001:db8:1::53:0 +IDENTITY=grimble.example.org +NSD_REBUILD_COMMAND=":" +NSD_RESTART_COMMAND="sudo systemctl restart nsd.service" diff --git a/examples/nsd/params_grumble.example.org b/examples/nsd/params_grumble.example.org new file mode 100644 index 0000000..e4c42f8 --- /dev/null +++ b/examples/nsd/params_grumble.example.org @@ -0,0 +1,5 @@ +BIND_IPV4=192.0.2.105 +BIND_IPV6=2001:db8:ff::53:0 +IDENTITY=grumble.example.org +NSD_REBUILD_COMMAND="sudo nsdc rebuild" +NSD_RESTART_COMMAND="sudo invoke-rc.d nsd3 restart" diff --git a/examples/nsd/zones.conf-generator b/examples/nsd/zones.conf-generator new file mode 100755 index 0000000..3829073 --- /dev/null +++ b/examples/nsd/zones.conf-generator @@ -0,0 +1,19 @@ +#!/bin/sh +# +# zones.conf-generator — generate zones.conf through interpolation +# +# Copyright © 2014 martin f. krafft +# Released under the terms of the Artistic Licence 2.0 +# +set -eu + +for zonefile in zones/*.zone arpazones/*.arpa; do + zone=${zonefile%.zone}; zone=${zone##*/}; + printf 'zone:\n\tname: "%s"\n\tzonefile: "%s"\n' $zone $zonefile; +done + +cat <<_eof + +## checksums: +_eof +find arpazones zones -type f -exec sha256sum {} + | sed -e 's,^,#,' diff --git a/gsc-post-receive b/gsc-post-receive new file mode 100755 index 0000000..ff07fde --- /dev/null +++ b/gsc-post-receive @@ -0,0 +1,38 @@ +#!/bin/sh +# +# gsc-post-receive — Git post-receive hook to do gsc poking +# +# Copyright © 2014 martin f. krafft +# Released under the terms of the Artistic Licence 2.0 +# +set -eu + +TEMPDIR=$(mktemp -d gsc.XXXXXXXXXX) +TRAPSIGNALS="0 1 2 3 4 5 6 7 8 10 11 12 13 14 15" +cleanup() { rm -rf "$TEMPDIR"; trap - $TRAPSIGNALS; } +trap cleanup $TRAPSIGNALS + +if ! git show HEAD:.gsc/pokehosts 2>/dev/null > $TEMPDIR/pokehosts; then + exit 0 +fi +git show HEAD:.gsc/poke-key > $TEMPDIR/poke-key +git show HEAD:.gsc/ssh_known_hosts > $TEMPDIR/ssh_known_hosts + +poke_host() { + echo >&2 "*** Poking $1..." + + ssh -T -o ConnectTimeout=10 \ + -o ForwardAgent=no -o ForwardX11=no \ + -o PreferredAuthentications=publickey \ + -o IdentitiesOnly=yes \ + -o IdentityFile="$TEMPDIR/poke-key" \ + -o StrictHostKeyChecking=yes \ + -o UserKnownHostsFile=$TEMPDIR/ssh_known_hosts \ + $2 $1 &2 + + echo >&2 "*** Done poking $1." +} + +while read target ssh_opts; do + poke_host $target $ssh_opts +done < $TEMPDIR/pokehosts