Commit 8195f50d authored by Helga Velroyen's avatar Helga Velroyen

node-daemon-setup: generate client certificate

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: default avatarHelga Velroyen <helgav@google.com>
Reviewed-by: default avatarKlaus Aehlig <aehlig@google.com>
parent daeece8b
......@@ -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
......
......@@ -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,
......
......@@ -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)
......
......@@ -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
......
......@@ -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:
......
......@@ -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)
......@@ -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):
......
......@@ -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
......
......@@ -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,
......
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