]> git.madduck.net Git - code/gsc.git/commitdiff

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Initial checkin
authormartin f. krafft <madduck@madduck.net>
Fri, 19 Dec 2014 12:17:05 +0000 (13:17 +0100)
committermartin f. krafft <madduck@madduck.net>
Fri, 19 Dec 2014 12:17:05 +0000 (13:17 +0100)
README [new file with mode: 0644]
examples/nsd/Makefile [new file with mode: 0644]
examples/nsd/nsd.conf-generator [new file with mode: 0755]
examples/nsd/params_grimple.example.org [new file with mode: 0644]
examples/nsd/params_grumble.example.org [new file with mode: 0644]
examples/nsd/zones.conf-generator [new file with mode: 0755]
gsc-post-receive [new file with mode: 0755]

diff --git a/README b/README
new file mode 100644 (file)
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 (file)
index 0000000..10cfec7
--- /dev/null
@@ -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 <madduck@madduck.net>
+# 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 (executable)
index 0000000..5efc395
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# nsd.conf-generator — generate nsd.conf through interpolation
+#
+# Copyright © 2014 martin f. krafft <madduck@madduck.net>
+# 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 (file)
index 0000000..c076503
--- /dev/null
@@ -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 (file)
index 0000000..e4c42f8
--- /dev/null
@@ -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 (executable)
index 0000000..3829073
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/sh
+#
+# zones.conf-generator — generate zones.conf through interpolation
+#
+# Copyright © 2014 martin f. krafft <madduck@madduck.net>
+# 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 (executable)
index 0000000..ff07fde
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# gsc-post-receive — Git post-receive hook to do gsc poking
+#
+# Copyright © 2014 martin f. krafft <madduck@madduck.net>
+# 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 </dev/null | sed 's,^,    ,' >&2
+
+  echo >&2 "*** Done poking $1."
+}
+
+while read target ssh_opts; do
+  poke_host $target $ssh_opts
+done < $TEMPDIR/pokehosts