On Thu, Dec 19, 2013 at 3:49 PM, Helga Velroyen <[email protected]> wrote:

> In various cluster operations, the master node need to
>
s/need/needs/ or s/need/may need/

> retrieve the digest of a node's SSL certificate. For this
> purpose, we add an RPC call to retrieve the digest. The
> function is designed in a general way to make it possible
> to retrieve other (public) cryptographic tokens of a node
> in the future as well (for example an SSH key).
>
> Signed-off-by: Helga Velroyen <[email protected]>
> ---
>  lib/backend.py                            | 24 ++++++++++++++++++++++++
>  lib/rpc_defs.py                           |  3 +++
>  lib/server/noded.py                       |  8 ++++++++
>  lib/utils/__init__.py                     |  1 +
>  lib/utils/security.py                     | 18 ++++++++++++++++++
>  src/Ganeti/Constants.hs                   | 15 +++++++++++++++
>  test/py/ganeti.backend_unittest.py        | 20 ++++++++++++++++++++
>  test/py/ganeti.utils.security_unittest.py | 18 ++++++++++++++++++
>  8 files changed, 107 insertions(+)
>
> diff --git a/lib/backend.py b/lib/backend.py
> index b329864..e2e3e28 100644
> --- a/lib/backend.py
> +++ b/lib/backend.py
> @@ -1162,6 +1162,30 @@ def VerifyNode(what, cluster_name, all_hvparams,
> node_groups, groups_cfg):
>    return result
>
>
> +def GetCryptoTokens(token_types):
> +  """Get the node's public cryptographic tokens.
> +
> +  This can be the public ssh key of the node or the certifciate digest of
>
s/certifciate/certificate/

> +  the node's public client SSL certificate.
> +
> +  Note: so far, only retrieval of the SSL digest is implemented
> +
> +  @type token_types: list of strings in constants.CRYPTO_TYPES
> +  @param token_types: list of types of requested cryptographic tokens
> +  @rtype: list of tuples (string, string)
> +  @return: list of tuples of the token type and the public crypto token
> +
> +  """
> +  tokens = []
> +  for token_type in token_types:
> +    if token_type not in constants.CRYPTO_TYPES:
> +      raise errors.ProgrammerError("Token type %s not supported." %
> +                                   token_type)
> +    if token_type == constants.CRYPTO_TYPE_SSL:
>

This is quite a generic name; given that this call can retrieve any sort of
crypto token,
there might be confusion.

Consider making it more descriptive, maybe by adding a _DIGEST to the name?


> +      tokens.append((token_type, utils.GetClientCertificateDigest()))
> +  return tokens
> +
> +
>  def GetBlockDevSizes(devices):
>    """Return the size of the given block devices
>
> diff --git a/lib/rpc_defs.py b/lib/rpc_defs.py
> index b9b1905..b92f720 100644
> --- a/lib/rpc_defs.py
> +++ b/lib/rpc_defs.py
> @@ -505,6 +505,9 @@ _NODE_CALLS = [
>      ("ovs_name", None, "Name of the OpenvSwitch to create"),
>      ("ovs_link", None, "Link of the OpenvSwitch to the outside"),
>      ], None, None, "This will create and setup the OpenvSwitch"),
> +  ("node_crypto_tokens", SINGLE, None, constants.RPC_TMO_NORMAL, [
> +    ("token_types", None, "List of requested crypto token types"),
> +    ], None, None, "Retrieve public crypto tokes from the node."),
>
s/tokes/tokens/

>    ]
>
>  _MISC_CALLS = [
> diff --git a/lib/server/noded.py b/lib/server/noded.py
> index 74f300d..e5bf330 100644
> --- a/lib/server/noded.py
> +++ b/lib/server/noded.py
> @@ -864,6 +864,14 @@ class
> NodeRequestHandler(http.server.HttpServerHandler):
>      (ovs_name, ovs_link) = params
>      return backend.ConfigureOVS(ovs_name, ovs_link)
>
> +  @staticmethod
> +  def perspective_node_crypto_tokens(params):
> +    """Gets the node's public crypto tokens.
> +
> +    """
> +    token_types = params[0]
> +    return backend.GetCryptoTokens(token_types)
> +
>    # cluster --------------------------
>
>    @staticmethod
> diff --git a/lib/utils/__init__.py b/lib/utils/__init__.py
> index 23650ca..50d88c0 100644
> --- a/lib/utils/__init__.py
> +++ b/lib/utils/__init__.py
> @@ -53,6 +53,7 @@ from ganeti.utils.mlock import *
>  from ganeti.utils.nodesetup import *
>  from ganeti.utils.process import *
>  from ganeti.utils.retry import *
> +from ganeti.utils.security import *
>  from ganeti.utils.storage import *
>  from ganeti.utils.text import *
>  from ganeti.utils.wrapper import *
> diff --git a/lib/utils/security.py b/lib/utils/security.py
> index 221b587..fe1f21c 100644
> --- a/lib/utils/security.py
> +++ b/lib/utils/security.py
> @@ -23,6 +23,10 @@
>  """
>
>  import logging
> +import OpenSSL
> +
> +from ganeti.utils import io
> +from ganeti import pathutils
>
>
>  def AddNodeToCandidateCerts(node_uuid, cert_digest, candidate_certs,
> @@ -74,3 +78,17 @@ def RemoveNodeFromCandidateCerts(node_uuid,
> candidate_certs,
>              "candidate map.")
>      return
>    del candidate_certs[node_uuid]
> +
> +
> +def GetClientCertificateDigest(cert_filename=pathutils.NODED_CERT_FILE):
> +  """Reads the SSL certificate and returns the sha1 digest.
> +
> +  """
> +  # FIXME: This is supposed to read the client certificate, but
> +  # in this stage of the patch series there is no client certificate
> +  # yet, so we return the digest of the server certificate to get
> +  # the rest of the key management infrastructure running.
> +  cert_plain = io.ReadFile(cert_filename)
> +  cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
> +                                         cert_plain)
> +  return cert.digest("sha1")
> diff --git a/src/Ganeti/Constants.hs b/src/Ganeti/Constants.hs
> index b27657a..09ee7b8 100644
> --- a/src/Ganeti/Constants.hs
> +++ b/src/Ganeti/Constants.hs
> @@ -4140,6 +4140,21 @@ fakeOpMasterTurndown = "OP_CLUSTER_IP_TURNDOWN"
>  fakeOpMasterTurnup :: String
>  fakeOpMasterTurnup = "OP_CLUSTER_IP_TURNUP"
>
> +
> +-- * Crypto Types
> +-- Types of cryptographic tokens used in node communication
> +
> +cryptoTypeSsl :: String
> +cryptoTypeSsl = "ssl"
> +
> +cryptoTypeSsh :: String
> +cryptoTypeSsh = "ssh"
>

A quick search of this patch series did not show the constant being used
afterwards.
Was it left behind during testing or do you want it to remain as a reminder?


> +
> +-- So far only ssl keys are used in the context of this constant
> +cryptoTypes :: FrozenSet String
> +cryptoTypes = ConstantUtils.mkSet [cryptoTypeSsl]
> +
> +
>  -- * SSH key types
>
>  sshkDsa :: String
> diff --git a/test/py/ganeti.backend_unittest.py b/test/py/
> ganeti.backend_unittest.py
> index 033419e..5c30bc2 100755
> --- a/test/py/ganeti.backend_unittest.py
> +++ b/test/py/ganeti.backend_unittest.py
> @@ -73,6 +73,26 @@ class TestX509Certificates(unittest.TestCase):
>      self.assertEqual(utils.ListVisibleFiles(self.tmpdir), [name])
>
>
> +class TestGetCryptoTokens(testutils.GanetiTestCase):
> +
> +  def setUp(self):
> +    self._digest_fn_orig = utils.GetClientCertificateDigest
> +    self._ssl_digest = "12345"
> +    utils.GetClientCertificateDigest = mock.Mock(
> +      return_value=self._ssl_digest)
> +
> +  def tearDown(self):
> +    utils.GetClientCertificateDigest = self._digest_fn_orig
> +
> +  def testSslToken(self):
> +    result = backend.GetCryptoTokens([constants.CRYPTO_TYPE_SSL])
> +    self.assertTrue((constants.CRYPTO_TYPE_SSL, self._ssl_digest) in
> result)
> +
> +  def testUnknownToken(self):
> +    self.assertRaises(errors.ProgrammerError,
> +                      backend.GetCryptoTokens, ["pink_bunny"])
> +
> +
>  class TestNodeVerify(testutils.GanetiTestCase):
>
>    def setUp(self):
> diff --git a/test/py/ganeti.utils.security_unittest.py b/test/py/
> ganeti.utils.security_unittest.py
> index 08c6b58..f42dd22 100755
> --- a/test/py/ganeti.utils.security_unittest.py
> +++ b/test/py/ganeti.utils.security_unittest.py
> @@ -70,5 +70,23 @@ class TestCandidateCerts(unittest.TestCase):
>      self.assertEqual(0, len(self._candidate_certs))
>
>
> +class TestGetCertificateDigest(testutils.GanetiTestCase):
> +
> +  def setUp(self):
> +    testutils.GanetiTestCase.setUp(self)
> +    # certificate file that contains the certificate only
> +    self._certfilename1 = testutils.TestDataFilename("cert1.pem")
> +    # (different) certificate file that contains both, certificate
> +    # and private key
> +    self._certfilename2 = testutils.TestDataFilename("cert2.pem")
> +
> +  def testGetCertificateDigest(self):
> +    digest1 = security.GetClientCertificateDigest(
> +      cert_filename=self._certfilename1)
> +    digest2 = security.GetClientCertificateDigest(
> +      cert_filename=self._certfilename2)
> +    self.assertFalse(digest1 == digest2)
> +
> +
>  if __name__ == "__main__":
>    testutils.GanetiTestProgram()
> --
> 1.8.5.1
>
>

Reply via email to