From d8eafff6266e40e386421641344494c89cff4d4b Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Tue, 31 May 2011 21:47:12 +0100 Subject: [PATCH] prologue, Makefile, local.mk: Overhaul installation. The prologue mollyguard is abstracted and generalized a bit so that we can install stuff remotely without too much worry. Installation is moved into the main Makefile (with slightly spruced-up documentation), leaving only a few very minor tweaks in the local configuration. --- Makefile | 56 +++++++++++++++++++++++++ dummy-payload.m4 | 53 ++++++++++++++++++++++++ local.mk | 29 +------------ prologue.m4 | 121 ++++++++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 212 insertions(+), 47 deletions(-) create mode 100644 dummy-payload.m4 diff --git a/Makefile b/Makefile index 242bbfd..7596df7 100644 --- a/Makefile +++ b/Makefile @@ -29,8 +29,18 @@ MAIN_M4_SOURCES = HOSTS = +## Where to install the scripts. +FIREWALL = /etc/init.d/firewall + +## How to achieve root privileges. +ROOT = sudo + +## Throw additional scripts in here to have them installed. SCRIPTS = +sbindir = /usr/local/sbin +## Establish the default target early, so that targets in `local.mk' don't +## override it. default: all .PHONY: default @@ -88,6 +98,18 @@ M4_SOURCES += $(MAIN_M4_SOURCES) TARGETS = $(addsuffix .sh,$(HOSTS)) ###-------------------------------------------------------------------------- +### Prologue testing. + +TARGETS += dummy.sh +dummy.sh: base.m4 prologue.m4 dummy-payload.m4 + $(V_M4) $^ >$@.new && chmod +x $@.new && mv $@.new $@ + +TARGETS += dummy-inst.sh +dummy-inst.sh: dummy.sh + $(V_GEN)sed '/dummy_action=/s/lose/win/' $< >$@.new + $(V_AT)chmod +x $@.new && mv $@.new $@ + +###-------------------------------------------------------------------------- ### Building. all: $(TARGETS) @@ -100,4 +122,38 @@ all: $(TARGETS) clean:; rm -f $(TARGETS) *.new .PHONY: clean +###-------------------------------------------------------------------------- +### Installation. + +## The local machine doesn't want the complicated SSH stuff. +THISHOST = $(shell hostname) + +## Testing. +check: $(THISHOST).sh + $(ROOT) ./$(THISHOST).sh test + +## Installation on a local host, +install/$(THISHOST): $(THISHOST).sh + [ "x$(SCRIPTS)" = x ] || $(ROOT) install -m755 $(SCRIPTS) $(sbindir) + $(ROOT) ./$(THISHOST).sh replace + +## Installation on a remote host. +install/%: %.sh + if [ "x$(SCRIPTS)" != x ]; then \ + for i in $(SCRIPTS); do \ + $(ROOT) scp $$i root@$*:$(sbindir)/$$i.new && \ + $(ROOT) ssh root@$* \ + 'cd $(sbindir) && chmod 755 $$i.new && mv $$i.new $i' || \ + exit 1; \ + done; \ + fi + $(ROOT) scp $*.sh root@$*:$(FIREWALL).new + $(ROOT) ssh root@$* $(FIREWALL) remote-prepare + $(ROOT) ssh root@$* $(FIREWALL) remote-commit + $(ROOT) ssh root@$* rm -f $(FIREWALL).new + +## General installation target. +install: all install/$(THISHOST) $(addprefix install/,$(HOSTS)) +.PHONY: install install/$(THISHOST) + ###----- That's all, folks -------------------------------------------------- diff --git a/dummy-payload.m4 b/dummy-payload.m4 new file mode 100644 index 0000000..117dc4c --- /dev/null +++ b/dummy-payload.m4 @@ -0,0 +1,53 @@ +### -*-sh-*- +### +### Dummy payload for testing the prologue +### +### (c) 2011 Mark Wooding +### + +###----- Licensing notice --------------------------------------------------- +### +### This program is free software; you can redistribute it and/or modify +### it under the terms of the GNU General Public License as published by +### the Free Software Foundation; either version 2 of the License, or +### (at your option) any later version. +### +### This program is distributed in the hope that it will be useful, +### but WITHOUT ANY WARRANTY; without even the implied warranty of +### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +### GNU General Public License for more details. +### +### You should have received a copy of the GNU General Public License +### along with this program; if not, write to the Free Software Foundation, +### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +m4_divert(5)m4_dnl +###-------------------------------------------------------------------------- +### Configuration. + +defconf(firewall_script, ./dummy-inst.sh) +defconf(firewall_failsafe, ./dummy-failsafe.sh) +defconf(dummy_action, lose) + +m4_divert(20)m4_dnl +###-------------------------------------------------------------------------- +### Produce some output, and fail or succeed as instructed. + +case "$dummy_action" in + lose) + echo "dummy-payload (stdout): losing" + echo >&2 "dummy-payload (stderr): still losing" + false + ;; + win) + echo "dummy-payload (stdout): winning" + echo >&2 "dummy-payload (stderr): still winning" + true + ;; + *) + eval "$dummy_action" + ;; +esac + +m4_divert(-1) +###----- That's all, folks -------------------------------------------------- diff --git a/local.mk b/local.mk index df5e26d..9a5ade5 100644 --- a/local.mk +++ b/local.mk @@ -1,33 +1,8 @@ ### Local configuration makefile. +## Common configuration for all hosts. MAIN_M4_SOURCES += local.m4 +## The avaiable hosts. HOSTS += metalzone HOSTS += vampire -THISHOST = $(shell hostname) - -ROOT = sudo - -## Testing. -check: $(THISHOST).sh - firewall_script=./$(THISHOST).sh && \ - firewall_failsafe=/etc/init.d/firewall && \ - export firewall_script firewall_failsafe && \ - [ -x $$firewall_failsafe ] && \ - $(ROOT) ./$$firewall_script - -## Installation. -install: all check - for i in $(HOSTS); do \ - $(ROOT) scp $$i.sh $$i:/etc/init.d/firewall; \ - if [ "$(SCRIPTS)" ]; then \ - for j in $(SCRIPTS); do \ - $(ROOT) ssh $$i <$$j " \ - cd /usr/local/sbin && \ - rm -f $$j.new && \ - cat >$$j.new && \ - chmod 755 $$j.new && \ - mv $$j.new $$j"; \ - done; \ - fi; \ - done diff --git a/prologue.m4 b/prologue.m4 index 4b14578..36f8b86 100644 --- a/prologue.m4 +++ b/prologue.m4 @@ -26,8 +26,11 @@ m4_divert(10)m4_dnl ### Failsafe prologue. revert () { - echo "$1! Retreating to safe version..." - if ! "$firewall_failsafe" revert; then + escape=$1 badness=$2 + ## Report a firewall script failure and retreat to a safe place. + + echo "$2! Retreating to safe version..." + if ! "$1" revert; then echo >&2 "Safe firewall failed. You're screwed. Good luck." exit 1 fi @@ -40,40 +43,118 @@ finished () { exit 0 } +try () { + old=$1 new=$2 + ## Install the NEW firewall rules. If it fails, revert to the OLD ones. + ## Updating firewall rules can fail spectacularly, so be careful. Leave a + ## timebomb in the form of NEW.errors: if this isn't removed in 10 seconds + ## after the NEW rules complete successfully, then revert. Write errors to + ## NEW.errors. + + ## Make sure we have an escape route. + if [ ! -x "$old" ]; then + echo >&2 "$0: no escape plan: \`$old' is missing" + exit 1 + fi + + ## Clear the air and make the errors file. + rm -f "$new.errors" "$new.timebomb" "$new.grabbed" + exec >"$new.errors" 2>&1 + + ## Now try to install the new firewall. + "$new" install || revert "$old" "Failed" + + ## Set up the time bomb. Leave the errors file there if we failed. + (sleep 10 + if [ -f "$new.errors" ]; then + mv "$new.errors" "$new.timebomb" + revert "$old" "Time bomb" + fi)& +} + +catch () { + new=$1 + ## Report successful installation of the script. + + if mv "$new.errors" "$new.grabbed" 2>/dev/null; then + rc=0 + echo "Installed OK." + else + mv "$new.timebomb" "$new.grabbed" + echo "Timebomb went off." + rc=1 + fi + cat "$new.grabbed" >&2 + rm -f "$new.grabbed" + return $rc +} + exit_after_clearing=: export FWCOOKIE=magical -case "${1-update}" in - start | restart | reload | force-reload) +case "$#,${1-update}" in + 1,start | 1,restart | 1,reload | 1,force-reload) echo -n "Starting up firewall... " - "$firewall_script" install || revert "Failed" + "$firewall_script" install || revert "$firewall_failsafe" "Failed" finished ;; - stop) + 1,stop) echo -n "Shutting down firewall... " exit_after_clearing=finished ;; - update) - echo -n "Installing new firewall... " - "$firewall_script" install || revert "Failed" - echo "Done." + 1,replace | 1,test) + echo -n "Running new firewall... " + if ! (try "$firewall_script" "$0"); then + echo "FAILED." + cat "$0.errors" >&2 + exit + fi + echo "done." echo "Can you hear me?" - parent=$$ - (sleep 5; kill $parent; revert "Timeout")& - child=$! + (trap 'exit 127' TERM + while :; do + if [ -f "$0.timebomb" ]; then + kill $$ + echo "Timebomb went off!" + cat "$0.timebomb" + exit 1 + fi + sleep 1 + done)& read answer - kill $child - case "$answer" in - y* | Y*) - echo "Cool. We're done here." + kill $! + catch "$0" + case "$1,$answer" in + replace,y* | replace,Y*) + install -m755 "$0" "$firewall_script" + echo "Cool. Firewall script replaced." exit 0 ;; + test,y* | test,Y*) + echo "Cool. Everything seems good." + exit 0 + ;; + *) + revert "$firewall_script" "Bogus" + ;; esac - revert "Bogus" ;; - install | revert) + 1,remote-prepare) + try "$firewall_script" "$0" + exit 0 + ;; + 1,remote-commit) + catch "$0" + install -m755 "$0" "$firewall_script" + exit 0 + ;; + 1,install | 1,revert) ;; *) - echo >&2 "Usage: firewall start|stop|reload|restart|force-reload|update|install|revert" + cat >&2 <