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