Commit f942a838 authored by Michael Hanselmann's avatar Michael Hanselmann
Browse files

Add RPC calls to create and remove X509 certificates



Certificates and keys generated using these functions will be used for
inter-cluster instance moves. As per design, the private key should never
leave the node.
Signed-off-by: default avatarMichael Hanselmann <hansmi@google.com>
Reviewed-by: default avatarIustin Pop <iustin@google.com>
parent bc85bc75
......@@ -322,6 +322,7 @@ TEST_FILES = \
test/data/proc_drbd83.txt
python_tests = \
test/ganeti.backend_unittest.py \
test/ganeti.bdev_unittest.py \
test/ganeti.cli_unittest.py \
test/ganeti.cmdlib_unittest.py \
......
......@@ -820,6 +820,24 @@ class NodeHttpServer(http.server.HttpServer):
(hvname, hvparams) = params
return backend.ValidateHVParams(hvname, hvparams)
# Crypto
@staticmethod
def perspective_create_x509_certificate(params):
"""Creates a new X509 certificate for SSL/TLS.
"""
(validity, ) = params
return backend.CreateX509Certificate(validity)
@staticmethod
def perspective_remove_x509_certificate(params):
"""Removes a X509 certificate.
"""
(name, ) = params
return backend.RemoveX509Certificate(name)
def CheckNoded(_, args):
"""Initial checks whether to run or exit with a failure.
......@@ -870,6 +888,7 @@ def main():
dirs = [(val, constants.RUN_DIRS_MODE) for val in constants.SUB_RUN_DIRS]
dirs.append((constants.LOG_OS_DIR, 0750))
dirs.append((constants.LOCK_DIR, 1777))
dirs.append((constants.CRYPTO_KEYS_DIR, constants.CRYPTO_KEYS_DIR_MODE))
daemon.GenericMain(constants.NODED, parser, dirs, CheckNoded, ExecNoded,
default_ssl_cert=constants.NODED_CERT_FILE,
default_ssl_key=constants.NODED_CERT_FILE)
......
......@@ -63,7 +63,11 @@ _ALLOWED_CLEAN_DIRS = frozenset([
constants.DATA_DIR,
constants.JOB_QUEUE_ARCHIVE_DIR,
constants.QUEUE_DIR,
constants.CRYPTO_KEYS_DIR,
])
_MAX_SSL_CERT_VALIDITY = 7 * 24 * 60 * 60
_X509_KEY_FILE = "key"
_X509_CERT_FILE = "cert"
class RPCFail(Exception):
......@@ -385,6 +389,7 @@ def LeaveCluster(modify_ssh_setup):
"""
_CleanDirectory(constants.DATA_DIR)
_CleanDirectory(constants.CRYPTO_KEYS_DIR)
JobQueuePurge()
if modify_ssh_setup:
......@@ -2510,6 +2515,65 @@ def DemoteFromMC():
utils.RemoveFile(constants.CLUSTER_CONF_FILE)
def _GetX509Filenames(cryptodir, name):
"""Returns the full paths for the private key and certificate.
"""
return (utils.PathJoin(cryptodir, name),
utils.PathJoin(cryptodir, name, _X509_KEY_FILE),
utils.PathJoin(cryptodir, name, _X509_CERT_FILE))
def CreateX509Certificate(validity, cryptodir=constants.CRYPTO_KEYS_DIR):
"""Creates a new X509 certificate for SSL/TLS.
@type validity: int
@param validity: Validity in seconds
@rtype: tuple; (string, string)
@return: Certificate name and public part
"""
(key_pem, cert_pem) = \
utils.GenerateSelfSignedX509Cert(utils.HostInfo.SysName(),
min(validity, _MAX_SSL_CERT_VALIDITY))
cert_dir = tempfile.mkdtemp(dir=cryptodir,
prefix="x509-%s-" % utils.TimestampForFilename())
try:
name = os.path.basename(cert_dir)
assert len(name) > 5
(_, key_file, cert_file) = _GetX509Filenames(cryptodir, name)
utils.WriteFile(key_file, mode=0400, data=key_pem)
utils.WriteFile(cert_file, mode=0400, data=cert_pem)
# Never return private key as it shouldn't leave the node
return (name, cert_pem)
except Exception:
shutil.rmtree(cert_dir, ignore_errors=True)
raise
def RemoveX509Certificate(name, cryptodir=constants.CRYPTO_KEYS_DIR):
"""Removes a X509 certificate.
@type name: string
@param name: Certificate name
"""
(cert_dir, key_file, cert_file) = _GetX509Filenames(cryptodir, name)
utils.RemoveFile(key_file)
utils.RemoveFile(cert_file)
try:
os.rmdir(cert_dir)
except EnvironmentError, err:
_Fail("Cannot remove certificate directory '%s': %s",
cert_dir, err)
def _FindDisks(nodes_ip, disks):
"""Sets the physical ID on disks and returns the block devices.
......
......@@ -91,6 +91,8 @@ DISK_LINKS_DIR = RUN_GANETI_DIR + "/instance-disks"
RUN_DIRS_MODE = 0755
SOCKET_DIR = RUN_GANETI_DIR + "/socket"
SOCKET_DIR_MODE = 0700
CRYPTO_KEYS_DIR = RUN_GANETI_DIR + "/crypto"
CRYPTO_KEYS_DIR_MODE = 0700
# keep RUN_GANETI_DIR first here, to make sure all get created when the node
# daemon is started (this takes care of RUN_DIR being tmpfs)
SUB_RUN_DIRS = [ RUN_GANETI_DIR, BDEV_CACHE_DIR, DISK_LINKS_DIR ]
......
......@@ -1081,7 +1081,6 @@ class RpcRunner(object):
"""
return self._SingleNodeCall(node, "node_demote_from_mc", [])
def call_node_powercycle(self, node, hypervisor):
"""Tries to powercycle a node.
......@@ -1090,7 +1089,6 @@ class RpcRunner(object):
"""
return self._SingleNodeCall(node, "node_powercycle", [hypervisor])
def call_test_delay(self, node_list, duration):
"""Sleep for a fixed time on given node(s).
......@@ -1189,3 +1187,25 @@ class RpcRunner(object):
hv_full = objects.FillDict(cluster.hvparams.get(hvname, {}), hvparams)
return self._MultiNodeCall(node_list, "hypervisor_validate_params",
[hvname, hv_full])
def call_create_x509_certificate(self, node, validity):
"""Creates a new X509 certificate for SSL/TLS.
This is a single-node call.
@type validity: int
@param validity: Validity in seconds
"""
return self._SingleNodeCall(node, "create_x509_certificate", [validity])
def call_remove_x509_certificate(self, node, name):
"""Removes a X509 certificate.
This is a single-node call.
@type name: string
@param name: Certificate name
"""
return self._SingleNodeCall(node, "remove_x509_certificate", [name])
#!/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.
"""Script for testing ganeti.backend"""
import os
import sys
import shutil
import tempfile
import unittest
from ganeti import utils
from ganeti import backend
import testutils
class TestX509Certificates(unittest.TestCase):
def setUp(self):
self.tmpdir = tempfile.mkdtemp()
def tearDown(self):
shutil.rmtree(self.tmpdir)
def test(self):
(name, cert_pem) = backend.CreateX509Certificate(300, cryptodir=self.tmpdir)
self.assertEqual(utils.ReadFile(os.path.join(self.tmpdir, name,
backend._X509_CERT_FILE)),
cert_pem)
self.assert_(0 < os.path.getsize(os.path.join(self.tmpdir, name,
backend._X509_KEY_FILE)))
(name2, cert_pem2) = \
backend.CreateX509Certificate(300, cryptodir=self.tmpdir)
backend.RemoveX509Certificate(name, cryptodir=self.tmpdir)
backend.RemoveX509Certificate(name2, cryptodir=self.tmpdir)
self.assertEqual(utils.ListVisibleFiles(self.tmpdir), [])
def testNonEmpty(self):
(name, _) = backend.CreateX509Certificate(300, cryptodir=self.tmpdir)
utils.WriteFile(utils.PathJoin(self.tmpdir, name, "hello-world"),
data="Hello World")
self.assertRaises(backend.RPCFail, backend.RemoveX509Certificate,
name, cryptodir=self.tmpdir)
self.assertEqual(utils.ListVisibleFiles(self.tmpdir), [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