ganeti-cleaner: Remove expired X509 certs
authorMichael Hanselmann <hansmi@google.com>
Tue, 18 May 2010 12:40:09 +0000 (14:40 +0200)
committerMichael Hanselmann <hansmi@google.com>
Tue, 18 May 2010 12:40:57 +0000 (14:40 +0200)
Importing/exporting an instance to a remote machine creates X509
certificates which expire after some time. They need to be removed from
the nodes as they become useless.

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>

Makefile.am
daemons/ganeti-cleaner.in
man/ganeti-cleaner.sgml
test/check-cert-expired_unittest.bash [new file with mode: 0755]
test/import-export_unittest-helper
tools/check-cert-expired [new file with mode: 0755]

index 04101d4..3f225e8 100644 (file)
@@ -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 =
index 1874b16..59fd08e 100755 (executable)
@@ -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
index ac60bda..9cd10aa 100644 (file)
@@ -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;
 
     <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 (executable)
index 0000000..ab4f453
--- /dev/null
@@ -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
index 3968e1f..58cc65e 100755 (executable)
@@ -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 (executable)
index 0000000..f47ca98
--- /dev/null
@@ -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()