Commit 7de77712 authored by Helga Velroyen's avatar Helga Velroyen

Introduce ssl_update tool

In order to renew client certificates via SSH (rather than
on the fly via SSL as it was before), we need a new tool
which can be called on remote nodes via SSH.
Signed-off-by: default avatarHelga Velroyen <helgav@google.com>
Reviewed-by: default avatarKlaus Aehlig <aehlig@google.com>
parent c204e38d
......@@ -551,7 +551,8 @@ pytools_PYTHON = \
lib/tools/ensure_dirs.py \
lib/tools/node_cleanup.py \
lib/tools/node_daemon_setup.py \
lib/tools/prepare_node_join.py
lib/tools/prepare_node_join.py \
lib/tools/ssl_update.py
utils_PYTHON = \
lib/utils/__init__.py \
......@@ -2307,6 +2308,7 @@ tools/ensure-dirs: MODULE = ganeti.tools.ensure_dirs
tools/node-daemon-setup: MODULE = ganeti.tools.node_daemon_setup
tools/prepare-node-join: MODULE = ganeti.tools.prepare_node_join
tools/node-cleanup: MODULE = ganeti.tools.node_cleanup
tools/ssl-update: MODULE = ganeti.tools.ssl_update
$(HS_BUILT_TEST_HELPERS): TESTROLE = $(patsubst test/hs/%,%,$@)
$(PYTHON_BOOTSTRAP) $(gnt_scripts) $(gnt_python_sbin_SCRIPTS): Makefile | stamp-directories
......
#
#
# Copyright (C) 2015 Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Script to recreate and sign the client SSL certificates.
"""
import os
import os.path
import optparse
import sys
import logging
import time
from ganeti import cli
from ganeti import constants
from ganeti import errors
from ganeti import utils
from ganeti import ht
from ganeti import pathutils
from ganeti.tools import common
_DATA_CHECK = ht.TStrictDict(False, True, {
constants.NDS_CLUSTER_NAME: ht.TNonEmptyString,
constants.NDS_NODE_DAEMON_CERTIFICATE: ht.TNonEmptyString,
constants.NDS_NODE_NAME: ht.TNonEmptyString,
})
class SslSetupError(errors.GenericError):
"""Local class for reporting errors.
"""
def ParseOptions():
"""Parses the options passed to the program.
@return: Options and arguments
"""
parser = optparse.OptionParser(usage="%prog [--dry-run]",
prog=os.path.basename(sys.argv[0]))
parser.add_option(cli.DEBUG_OPT)
parser.add_option(cli.VERBOSE_OPT)
parser.add_option(cli.DRY_RUN_OPT)
(opts, args) = parser.parse_args()
return common.VerifyOptions(parser, opts, args)
def RegenerateClientCertificate(
data, client_cert=pathutils.NODED_CLIENT_CERT_FILE,
signing_cert=pathutils.NODED_CERT_FILE):
"""Regenerates the client certificate of the node.
@type data: string
@param data: the JSON-formated input data
"""
if not os.path.exists(signing_cert):
raise SslSetupError("The signing certificate '%s' cannot be found."
% signing_cert)
# TODO: This sets the serial number to the number of seconds
# since epoch. This is technically not a correct serial number
# (in the way SSL is supposed to be used), but it serves us well
# enough for now, as we don't have any infrastructure for keeping
# track of the number of signed certificates yet.
serial_no = int(time.time())
# The hostname of the node is provided with the input data.
hostname = data.get(constants.NDS_NODE_NAME)
# TODO: make backup of the file before regenerating.
utils.GenerateSignedSslCert(client_cert, serial_no, signing_cert,
common_name=hostname)
def Main():
"""Main routine.
"""
opts = ParseOptions()
utils.SetupToolLogging(opts.debug, opts.verbose)
try:
data = common.LoadData(sys.stdin.read(), _DATA_CHECK)
common.VerifyClusterName(data, SslSetupError)
# Verifies whether the server certificate of the caller
# is the same as on this node.
common.VerifyCertificate(data, SslSetupError)
RegenerateClientCertificate(data)
except Exception, err: # pylint: disable=W0703
logging.debug("Caught unhandled exception", exc_info=True)
(retcode, message) = cli.FormatError(err)
logging.error(message)
return retcode
else:
return constants.EXIT_SUCCESS
......@@ -4474,6 +4474,9 @@ ndsSsconf = "ssconf"
ndsStartNodeDaemon :: String
ndsStartNodeDaemon = "start_node_daemon"
ndsNodeName :: String
ndsNodeName = "node_name"
-- * VCluster related constants
vClusterEtcHosts :: String
......
#!/usr/bin/python
#
# Copyright (C) 2015 Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Script for testing ganeti.tools.ssl_update"""
import unittest
import shutil
import tempfile
import os.path
import OpenSSL
import time
from ganeti import errors
from ganeti import constants
from ganeti import serializer
from ganeti import pathutils
from ganeti import compat
from ganeti import utils
from ganeti.tools import ssl_update
import testutils
class TestGenerateClientCert(unittest.TestCase):
def setUp(self):
self.tmpdir = tempfile.mkdtemp()
self.client_cert = os.path.join(self.tmpdir, "client.pem")
self.server_cert = os.path.join(self.tmpdir, "server.pem")
some_serial_no = int(time.time())
utils.GenerateSelfSignedSslCert(self.server_cert, some_serial_no)
def tearDown(self):
shutil.rmtree(self.tmpdir)
def testRegnerateClientCertificate(self):
my_node_name = "mynode.example.com"
data = {constants.NDS_CLUSTER_NAME: "winnie_poohs_cluster",
constants.NDS_NODE_DAEMON_CERTIFICATE: "some_cert",
constants.NDS_NODE_NAME: my_node_name}
ssl_update.RegenerateClientCertificate(data, client_cert=self.client_cert,
signing_cert=self.server_cert)
client_cert_pem = utils.ReadFile(self.client_cert)
server_cert_pem = utils.ReadFile(self.server_cert)
client_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
client_cert_pem)
signing_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
server_cert_pem)
self.assertEqual(client_cert.get_issuer().CN, signing_cert.get_subject().CN)
self.assertEqual(client_cert.get_subject().CN, my_node_name)
if __name__ == "__main__":
testutils.GanetiTestProgram()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment