node-daemon-setup: generate client certificate
authorHelga Velroyen <helgav@google.com>
Mon, 15 Jun 2015 14:43:24 +0000 (16:43 +0200)
committerHelga Velroyen <helgav@google.com>
Mon, 6 Jul 2015 10:46:04 +0000 (12:46 +0200)
So far, the client certificate of a node that is added
to the cluster was created in LUNodeAdd using an RPC
call. This is now simplified by creating the certificate
already in tools/node_daemon_setup.py and only retrieving
its fingerprint by RPC to add it to the configuration.

This simplifies the backend function from only reading
the fingerprint instead of creating the certificate.

Signed-off-by: Helga Velroyen <helgav@google.com>
Reviewed-by: Klaus Aehlig <aehlig@google.com>

lib/backend.py
lib/bootstrap.py
lib/cmdlib/cluster.py
lib/cmdlib/common.py
lib/cmdlib/node.py
lib/tools/common.py
lib/tools/node_daemon_setup.py
src/Ganeti/Constants.hs
test/py/ganeti.backend_unittest.py

index 2122bad..0542a62 100644 (file)
@@ -1211,13 +1211,8 @@ def GetCryptoTokens(token_requests):
   @return: list of tuples of the token type and the public crypto token
 
   """
-  getents = runtime.GetEnts()
-  _VALID_CERT_FILES = [pathutils.NODED_CERT_FILE,
-                       pathutils.NODED_CLIENT_CERT_FILE,
-                       pathutils.NODED_CLIENT_CERT_FILE_TMP]
-  _DEFAULT_CERT_FILE = pathutils.NODED_CLIENT_CERT_FILE
   tokens = []
-  for (token_type, action, options) in token_requests:
+  for (token_type, action, _) in token_requests:
     if token_type not in constants.CRYPTO_TYPES:
       raise errors.ProgrammerError("Token type '%s' not supported." %
                                    token_type)
@@ -1225,46 +1220,8 @@ def GetCryptoTokens(token_requests):
       raise errors.ProgrammerError("Action '%s' is not supported." %
                                    action)
     if token_type == constants.CRYPTO_TYPE_SSL_DIGEST:
-      if action == constants.CRYPTO_ACTION_CREATE:
-
-        # extract file name from options
-        cert_filename = None
-        if options:
-          cert_filename = options.get(constants.CRYPTO_OPTION_CERT_FILE)
-        if not cert_filename:
-          cert_filename = _DEFAULT_CERT_FILE
-        # For security reason, we don't allow arbitrary filenames
-        if not cert_filename in _VALID_CERT_FILES:
-          raise errors.ProgrammerError(
-            "The certificate file name path '%s' is not allowed." %
-            cert_filename)
-
-        # extract serial number from options
-        serial_no = None
-        if options:
-          try:
-            serial_no = int(options[constants.CRYPTO_OPTION_SERIAL_NO])
-          except ValueError:
-            raise errors.ProgrammerError(
-              "The given serial number is not an intenger: %s." %
-              options.get(constants.CRYPTO_OPTION_SERIAL_NO))
-          except KeyError:
-            raise errors.ProgrammerError("No serial number was provided.")
-
-        if not serial_no:
-          raise errors.ProgrammerError(
-            "Cannot create an SSL certificate without a serial no.")
-
-        utils.GenerateNewSslCert(
-          True, cert_filename, serial_no,
-          "Create new client SSL certificate in %s." % cert_filename,
-          uid=getents.masterd_uid, gid=getents.masterd_gid)
-        tokens.append((token_type,
-                       utils.GetCertificateDigest(
-                         cert_filename=cert_filename)))
-      elif action == constants.CRYPTO_ACTION_GET:
-        tokens.append((token_type,
-                       utils.GetCertificateDigest()))
+      tokens.append((token_type,
+                     utils.GetCertificateDigest()))
   return tokens
 
 
index a9b1f68..fa6ae19 100644 (file)
@@ -1010,6 +1010,7 @@ def SetupNodeDaemon(opts, cluster_name, node, ssh_port):
       utils.ReadFile(pathutils.NODED_CERT_FILE),
     constants.NDS_SSCONF: ssconf.SimpleStore().ReadAll(),
     constants.NDS_START_NODE_DAEMON: True,
+    constants.NDS_NODE_NAME: node,
     }
 
   RunNodeSetupCmd(cluster_name, node, pathutils.NODE_DAEMON_SETUP,
index 0251525..5d09202 100644 (file)
@@ -65,10 +65,9 @@ from ganeti.cmdlib.common import ShareAll, RunPostHook, \
   CheckOSParams, CheckHVParams, AdjustCandidatePool, CheckNodePVs, \
   ComputeIPolicyInstanceViolation, AnnotateDiskParams, SupportsOob, \
   CheckIpolicyVsDiskTemplates, CheckDiskAccessModeValidity, \
-  CheckDiskAccessModeConsistency, CreateNewClientCert, \
+  CheckDiskAccessModeConsistency, GetClientCertDigest, \
   AddInstanceCommunicationNetworkOp, ConnectInstanceCommunicationNetworkOp, \
-  CheckImageValidity, \
-  CheckDiskAccessModeConsistency, CreateNewClientCert, EnsureKvmdOnNodes
+  CheckImageValidity, CheckDiskAccessModeConsistency, EnsureKvmdOnNodes
 
 import ganeti.masterd.instance
 
@@ -93,7 +92,7 @@ def _UpdateMasterClientCert(
   @return: the digest of the newly created client certificate
 
   """
-  client_digest = CreateNewClientCert(lu, master_uuid, filename=client_cert_tmp)
+  client_digest = GetClientCertDigest(lu, master_uuid, filename=client_cert_tmp)
   cfg.AddNodeToCandidateCerts(master_uuid, client_digest)
   # This triggers an update of the config and distribution of it with the old
   # SSL certificate
@@ -188,7 +187,7 @@ class LUClusterRenewCrypto(NoHooksLU):
         last_exception = None
         for i in range(self._MAX_NUM_RETRIES):
           try:
-            new_digest = CreateNewClientCert(self, node_uuid)
+            new_digest = GetClientCertDigest(self, node_uuid)
             if node_info.master_candidate:
               self.cfg.AddNodeToCandidateCerts(node_uuid,
                                                new_digest)
index e2eb909..4298e6c 100644 (file)
@@ -1366,8 +1366,8 @@ def RemoveNodeCertFromCandidateCerts(cfg, node_uuid):
   cfg.RemoveNodeFromCandidateCerts(node_uuid)
 
 
-def CreateNewClientCert(lu, node_uuid, filename=None):
-  """Creates a new client SSL certificate for the node.
+def GetClientCertDigest(lu, node_uuid, filename=None):
+  """Get the client SSL certificate digest for the node.
 
   @type node_uuid: string
   @param node_uuid: the node's UUID
@@ -1380,13 +1380,12 @@ def CreateNewClientCert(lu, node_uuid, filename=None):
   options = {}
   if filename:
     options[constants.CRYPTO_OPTION_CERT_FILE] = filename
-  options[constants.CRYPTO_OPTION_SERIAL_NO] = utils.UuidToInt(node_uuid)
   result = lu.rpc.call_node_crypto_tokens(
              node_uuid,
              [(constants.CRYPTO_TYPE_SSL_DIGEST,
-               constants.CRYPTO_ACTION_CREATE,
+               constants.CRYPTO_ACTION_GET,
                options)])
-  result.Raise("Could not create the node's (uuid %s) SSL client"
+  result.Raise("Could not fetch the node's (uuid %s) SSL client"
                " certificate." % node_uuid)
   ((crypto_type, new_digest), ) = result.payload
   assert crypto_type == constants.CRYPTO_TYPE_SSL_DIGEST
index 1b13aa9..2f83c73 100644 (file)
@@ -51,7 +51,7 @@ from ganeti.cmdlib.common import CheckParamsNotGlobal, \
   CheckInstanceState, INSTANCE_DOWN, GetUpdatedParams, \
   AdjustCandidatePool, CheckIAllocatorOrNode, LoadNodeEvacResult, \
   GetWantedNodes, MapInstanceLvsToNodes, RunPostHook, \
-  FindFaultyInstanceDisks, CheckStorageTypeEnabled, CreateNewClientCert, \
+  FindFaultyInstanceDisks, CheckStorageTypeEnabled, GetClientCertDigest, \
   AddNodeCertToCandidateCerts, RemoveNodeCertFromCandidateCerts, \
   EnsureKvmdOnNodes
 
@@ -431,7 +431,7 @@ class LUNodeAdd(LogicalUnit):
       RedistributeAncillaryFiles(self)
 
     # We create a new certificate even if the node is readded
-    digest = CreateNewClientCert(self, self.new_node.uuid)
+    digest = GetClientCertDigest(self, self.new_node.uuid)
     if self.new_node.master_candidate:
       self.cfg.AddNodeToCandidateCerts(self.new_node.uuid, digest)
     else:
index 547c072..b48b1ee 100644 (file)
@@ -165,6 +165,8 @@ def GenerateClientCertificate(
 
   # The hostname of the node is provided with the input data.
   hostname = data.get(constants.NDS_NODE_NAME)
+  if not hostname:
+    raise error_fn("No hostname found.")
 
   utils.GenerateSignedSslCert(client_cert, serial_no, signing_cert,
                               common_name=hostname)
index 3eec177..89e8a18 100644 (file)
@@ -48,6 +48,7 @@ from ganeti import serializer
 from ganeti import runtime
 from ganeti import ht
 from ganeti import ssconf
+from ganeti.tools import common
 
 
 _DATA_CHECK = ht.TStrictDict(False, True, {
@@ -55,6 +56,7 @@ _DATA_CHECK = ht.TStrictDict(False, True, {
   constants.NDS_NODE_DAEMON_CERTIFICATE: ht.TNonEmptyString,
   constants.NDS_SSCONF: ht.TDictOf(ht.TNonEmptyString, ht.TString),
   constants.NDS_START_NODE_DAEMON: ht.TBool,
+  constants.NDS_NODE_NAME: ht.TString,
   })
 
 
@@ -227,6 +229,7 @@ def Main():
                     mode=pathutils.NODED_CERT_MODE,
                     uid=getent.masterd_uid, gid=getent.masterd_gid,
                     dry_run=opts.dry_run)
+    common.GenerateClientCertificate(data, SetupError)
 
     if (data.get(constants.NDS_START_NODE_DAEMON) and # pylint: disable=E1103
         not opts.dry_run):
index 3e8dd54..0c8f4a8 100644 (file)
@@ -4382,12 +4382,8 @@ cryptoTypes = ConstantUtils.mkSet [cryptoTypeSslDigest]
 cryptoActionGet :: String
 cryptoActionGet = "get"
 
--- This is 'create and get'
-cryptoActionCreate :: String
-cryptoActionCreate = "create"
-
 cryptoActions :: FrozenSet String
-cryptoActions = ConstantUtils.mkSet [cryptoActionGet, cryptoActionCreate]
+cryptoActions = ConstantUtils.mkSet [cryptoActionGet]
 
 -- * Options for CryptoActions
 
index 2e33993..6a3346d 100755 (executable)
@@ -105,32 +105,6 @@ class TestGetCryptoTokens(testutils.GanetiTestCase):
     self.assertTrue((constants.CRYPTO_TYPE_SSL_DIGEST, self._ssl_digest)
                     in result)
 
-  def testCreateSslToken(self):
-    result = backend.GetCryptoTokens(
-      [(constants.CRYPTO_TYPE_SSL_DIGEST, constants.CRYPTO_ACTION_CREATE,
-        {constants.CRYPTO_OPTION_SERIAL_NO: 42})])
-    self.assertTrue((constants.CRYPTO_TYPE_SSL_DIGEST, self._ssl_digest)
-                    in result)
-    self.assertTrue(utils.GenerateNewSslCert.assert_calls().once())
-
-  def testCreateSslTokenDifferentFilename(self):
-    result = backend.GetCryptoTokens(
-      [(constants.CRYPTO_TYPE_SSL_DIGEST, constants.CRYPTO_ACTION_CREATE,
-        {constants.CRYPTO_OPTION_CERT_FILE:
-          pathutils.NODED_CLIENT_CERT_FILE_TMP,
-         constants.CRYPTO_OPTION_SERIAL_NO: 42})])
-    self.assertTrue((constants.CRYPTO_TYPE_SSL_DIGEST, self._ssl_digest)
-                    in result)
-    self.assertTrue(utils.GenerateNewSslCert.assert_calls().once())
-
-  def testCreateSslTokenSerialNo(self):
-    result = backend.GetCryptoTokens(
-      [(constants.CRYPTO_TYPE_SSL_DIGEST, constants.CRYPTO_ACTION_CREATE,
-        {constants.CRYPTO_OPTION_SERIAL_NO: 42})])
-    self.assertTrue((constants.CRYPTO_TYPE_SSL_DIGEST, self._ssl_digest)
-                    in result)
-    self.assertTrue(utils.GenerateNewSslCert.assert_calls().once())
-
   def testUnknownTokenType(self):
     self.assertRaises(errors.ProgrammerError,
                       backend.GetCryptoTokens,