diff --git a/Makefile.am b/Makefile.am index 04101d4ac08cb759127392e2b323310024a85e61..3f225e80171c015bf97315513ae2f4c1f96e7b01 100644 --- a/Makefile.am +++ b/Makefile.am @@ -245,7 +245,8 @@ dist_tools_SCRIPTS = \ tools/sanitize-config pkglib_python_scripts = \ - daemons/import-export + daemons/import-export \ + tools/check-cert-expired pkglib_SCRIPTS = \ daemons/daemon-util \ @@ -366,6 +367,7 @@ python_tests = \ dist_TESTS = \ test/daemon-util_unittest.bash \ test/import-export_unittest.bash \ + test/check-cert-expired_unittest.bash \ $(python_tests) nodist_TESTS = diff --git a/daemons/ganeti-cleaner.in b/daemons/ganeti-cleaner.in index 1874b165eda1d68748a41bdae2bab7b216a7d153..59fd08ed613bf4b8433fefd811427d79c9972f23 100755 --- a/daemons/ganeti-cleaner.in +++ b/daemons/ganeti-cleaner.in @@ -32,9 +32,24 @@ cleanup_master() { xargs -r0 rm -vf } +cleanup_node() { + # Return if directory for crypto keys doesn't exist + [[ -d $CRYPTO_DIR ]] || return 0 + + find $CRYPTO_DIR -mindepth 1 -maxdepth 1 -type d | \ + while read dir; do + if $CHECK_CERT_EXPIRED $dir/cert; then + rm -vf $dir/{cert,key} + rmdir -v --ignore-fail-on-non-empty $dir + fi + done +} + DATA_DIR=@LOCALSTATEDIR@/lib/ganeti CLEANER_LOG_DIR=@LOCALSTATEDIR@/log/ganeti/cleaner QUEUE_ARCHIVE_DIR=$DATA_DIR/queue/archive +CRYPTO_DIR=@LOCALSTATEDIR@/run/ganeti/crypto +CHECK_CERT_EXPIRED=@PKGLIBDIR@/check-cert-expired # Define how many days archived jobs should be left alone REMOVE_AFTER=21 @@ -58,5 +73,6 @@ find $CLEANER_LOG_DIR -maxdepth 1 -type f | sort | head -n -$KEEP_LOGS | \ xargs -r rm -vf cleanup_master +cleanup_node exit 0 diff --git a/man/ganeti-cleaner.sgml b/man/ganeti-cleaner.sgml index ac60bdaf620125f3ed79fb89970f240c12e4f2dc..9cd10aaf5ac0001efddf228b9eca0179edb4e800 100644 --- a/man/ganeti-cleaner.sgml +++ b/man/ganeti-cleaner.sgml @@ -2,7 +2,7 @@ <!-- Fill in your name for FIRSTNAME and SURNAME. --> <!-- Please adjust the date whenever revising the manpage. --> - <!ENTITY dhdate "<date>February 11, 2009</date>"> + <!ENTITY dhdate "<date>May 17, 2010</date>"> <!-- SECTION should be 1-8, maybe w/ subsection other parameters are allowed: see man(7), man(1). --> <!ENTITY dhsection "<manvolnum>8</manvolnum>"> @@ -19,6 +19,7 @@ <refentryinfo> <copyright> <year>2009</year> + <year>2010</year> <holder>Google Inc.</holder> </copyright> &dhdate; @@ -45,13 +46,16 @@ <para> The <command>&dhpackage;</command> is a periodically run script to clean - old job files from the job queue archive. + old job files from the job queue archive and to remove expired X509 + certificates and keys. </para> <para> <command>&dhpackage;</command> automatically removes all files older than 21 days from - <filename>@LOCALSTATEDIR@/lib/ganeti/queue/archive</filename>. + <filename>@LOCALSTATEDIR@/lib/ganeti/queue/archive</filename> and all + expired certificates and keys from + <filename>@LOCALSTATEDIR@/run/ganeti/crypto</filename> </para> </refsect1> diff --git a/test/check-cert-expired_unittest.bash b/test/check-cert-expired_unittest.bash new file mode 100755 index 0000000000000000000000000000000000000000..ab4f4536f1ce9af421cc160b2f462bd7a3696099 --- /dev/null +++ b/test/check-cert-expired_unittest.bash @@ -0,0 +1,64 @@ +#!/bin/bash +# + +# Copyright (C) 2010 Google Inc. +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + +set -e +set -o pipefail + +export PYTHON=${PYTHON:=python} + +CCE=tools/check-cert-expired + +err() { + echo "$@" + echo 'Aborting' + exit 1 +} + +impexpd_helper() { + $PYTHON "${TOP_SRCDIR:-.}/test/import-export_unittest-helper" "$@" +} + +$CCE 2>/dev/null && err 'Accepted empty argument list' +$CCE foo bar 2>/dev/null && err 'Accepted more than one argument' +$CCE foo bar baz 2>/dev/null && err 'Accepted more than one argument' + +tmpdir=$(mktemp -d) +trap "rm -rf $tmpdir" EXIT + +[[ -f "$tmpdir/cert-not" ]] && err 'File existed when it should not' +$CCE $tmpdir/cert-not 2>/dev/null && err 'Accepted non-existent file' + +VALIDITY=1 impexpd_helper $tmpdir/cert-valid gencert +$CCE $tmpdir/cert-valid 2>/dev/null && \ + err 'Reported valid certificate as expired' + +VALIDITY=-50 impexpd_helper $tmpdir/cert-expired gencert +$CCE $tmpdir/cert-expired 2>/dev/null || \ + err 'Reported expired certificate as valid' + +echo > $tmpdir/cert-invalid +$CCE $tmpdir/cert-invalid 2>/dev/null && \ + err 'Reported invalid certificate as expired' + +echo 'Hello World' > $tmpdir/cert-invalid2 +$CCE $tmpdir/cert-invalid2 2>/dev/null && \ + err 'Reported invalid certificate as expired' + +exit 0 diff --git a/test/import-export_unittest-helper b/test/import-export_unittest-helper index 3968e1ff20c3f110cc24429a063107d1cc20c153..58cc65e725005001af35bd8d2fc731788d9c1c67 100755 --- a/test/import-export_unittest-helper +++ b/test/import-export_unittest-helper @@ -33,6 +33,7 @@ from ganeti import serializer RETRY_INTERVAL = 0.1 TIMEOUT = int(os.getenv("TIMEOUT", 10)) +VALIDITY = int(os.getenv("VALIDITY", 1)) def _GetImportExportData(filename): @@ -68,7 +69,7 @@ def main(): elif what == "connected": WaitForConnected(filename) elif what == "gencert": - utils.GenerateSelfSignedSslCert(filename, validity=1) + utils.GenerateSelfSignedSslCert(filename, validity=VALIDITY) else: raise Exception("Unknown command '%s'" % what) diff --git a/tools/check-cert-expired b/tools/check-cert-expired new file mode 100755 index 0000000000000000000000000000000000000000..f47ca984905d37766da6dee9c4dd2de39b000545 --- /dev/null +++ b/tools/check-cert-expired @@ -0,0 +1,76 @@ +#!/usr/bin/python +# + +# Copyright (C) 2010 Google Inc. +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + +"""Tool to detect expired X509 certificates. + +""" + +# pylint: disable-msg=C0103 +# C0103: Invalid name check-cert-expired + +import os.path +import sys +import OpenSSL + +from ganeti import constants +from ganeti import cli +from ganeti import utils + + +def main(): + """Main routine. + + """ + program = os.path.basename(sys.argv[0]) + + if len(sys.argv) != 2: + cli.ToStderr("Usage: %s <certificate-path>", program) + sys.exit(constants.EXIT_FAILURE) + + filename = sys.argv[1] + + # Read certificate + try: + cert_pem = utils.ReadFile(filename) + except EnvironmentError, err: + cli.ToStderr("Unable to read %s: %s", filename, err) + sys.exit(constants.EXIT_FAILURE) + + # Check validity + try: + cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, + cert_pem) + + (errcode, msg) = utils.VerifyX509Certificate(cert, None, None) + if msg: + cli.ToStderr("%s: %s", filename, msg) + if errcode == utils.CERT_ERROR: + sys.exit(constants.EXIT_SUCCESS) + + except (KeyboardInterrupt, SystemExit): + raise + except Exception, err: # pylint: disable-msg=W0703 + cli.ToStderr("Unable to check %s: %s", filename, err) + + sys.exit(constants.EXIT_FAILURE) + + +if __name__ == "__main__": + main()