Revision d722af8b

b/lib/backend.py
1162 1162
  return result
1163 1163

  
1164 1164

  
1165
def GetCryptoTokens(token_types):
1166
  """Get the node's public cryptographic tokens.
1167

  
1168
  This can be the public ssh key of the node or the certificate digest of
1169
  the node's public client SSL certificate.
1170

  
1171
  Note: so far, only retrieval of the SSL digest is implemented
1172

  
1173
  @type token_types: list of strings in constants.CRYPTO_TYPES
1174
  @param token_types: list of types of requested cryptographic tokens
1165
def GetCryptoTokens(token_requests):
1166
  """Perform actions on the node's cryptographic tokens.
1167

  
1168
  Token types can be 'ssl' or 'ssh'. So far only some actions are implemented
1169
  for 'ssl'. Action 'get' returns the digest of the public client ssl
1170
  certificate. Action 'create' creates a new client certificate and private key
1171
  and also returns the digest of the certificate. The third parameter of a
1172
  token request are optional parameters for the actions, so far only the
1173
  filename is supported.
1174

  
1175
  @type token_requests: list of tuples of (string, string, dict), where the
1176
    first string is in constants.CRYPTO_TYPES, the second in
1177
    constants.CRYPTO_ACTIONS. The third parameter is a dictionary of string
1178
    to string.
1179
  @param token_requests: list of requests of cryptographic tokens and actions
1180
    to perform on them. The actions come with a dictionary of options.
1175 1181
  @rtype: list of tuples (string, string)
1176 1182
  @return: list of tuples of the token type and the public crypto token
1177 1183

  
1178 1184
  """
1185
  _VALID_CERT_FILES = [pathutils.NODED_CERT_FILE,
1186
                       pathutils.NODED_CLIENT_CERT_FILE,
1187
                       pathutils.NODED_CLIENT_CERT_FILE_TMP]
1188
  _DEFAULT_CERT_FILE = pathutils.NODED_CLIENT_CERT_FILE
1179 1189
  tokens = []
1180
  for token_type in token_types:
1190
  for (token_type, action, options) in token_requests:
1181 1191
    if token_type not in constants.CRYPTO_TYPES:
1182
      raise errors.ProgrammerError("Token type %s not supported." %
1192
      raise errors.ProgrammerError("Token type '%s' not supported." %
1183 1193
                                   token_type)
1194
    if action not in constants.CRYPTO_ACTIONS:
1195
      raise errors.ProgrammerError("Action '%s' is not supported." %
1196
                                   action)
1184 1197
    if token_type == constants.CRYPTO_TYPE_SSL_DIGEST:
1185
      tokens.append((token_type, utils.GetClientCertificateDigest()))
1198
      if action == constants.CRYPTO_ACTION_CREATE:
1199
        cert_filename = None
1200
        if options:
1201
          cert_filename = options.get(constants.CRYPTO_OPTION_CERT_FILE)
1202
        if not cert_filename:
1203
          cert_filename = _DEFAULT_CERT_FILE
1204
        # For security reason, we don't allow arbitrary filenames
1205
        if not cert_filename in _VALID_CERT_FILES:
1206
          raise errors.ProgrammerError(
1207
            "The certificate file name path '%s' is not allowed." %
1208
            cert_filename)
1209
        utils.GenerateNewSslCert(
1210
          True, cert_filename,
1211
          "Create new client SSL certificate in %s." % cert_filename)
1212
        tokens.append((token_type,
1213
                       utils.GetClientCertificateDigest(
1214
                         cert_filename=cert_filename)))
1215
      elif action == constants.CRYPTO_ACTION_GET:
1216
        tokens.append((token_type,
1217
                       utils.GetClientCertificateDigest()))
1186 1218
  return tokens
1187 1219

  
1188 1220

  
b/lib/cmdlib/common.py
1228 1228

  
1229 1229
  """
1230 1230
  result = lu.rpc.call_node_crypto_tokens(
1231
             node_uuid, [constants.CRYPTO_TYPE_SSL_DIGEST])
1231
             node_uuid,
1232
             [(constants.CRYPTO_TYPE_SSL_DIGEST, constants.CRYPTO_ACTION_GET,
1233
               None)])
1232 1234
  result.Raise("Could not retrieve the node's (uuid %s) SSL digest."
1233 1235
               % node_uuid)
1234 1236
  ((crypto_type, digest), ) = result.payload
b/lib/pathutils.py
107 107
#: Node daemon certificate path
108 108
NODED_CERT_FILE = DATA_DIR + "/server.pem"
109 109
NODED_CLIENT_CERT_FILE = DATA_DIR + "/client.pem"
110
NODED_CLIENT_CERT_FILE_TMP = DATA_DIR + "/client.pem.tmp"
110 111

  
111 112
#: Node daemon certificate file permissions
112 113
NODED_CERT_MODE = 0440
b/lib/rpc_defs.py
506 506
    ("ovs_link", None, "Link of the OpenvSwitch to the outside"),
507 507
    ], None, None, "This will create and setup the OpenvSwitch"),
508 508
  ("node_crypto_tokens", SINGLE, None, constants.RPC_TMO_NORMAL, [
509
    ("token_types", None, "List of requested crypto token types"),
510
    ], None, None, "Retrieve public crypto tokens from the node."),
509
    ("token_request", None,
510
     "List of tuples of requested crypto token types, actions"),
511
    ], None, None, "Handle crypto tokens of the node."),
511 512
  ]
512 513

  
513 514
_MISC_CALLS = [
b/lib/server/noded.py
869 869
    """Gets the node's public crypto tokens.
870 870

  
871 871
    """
872
    token_types = params[0]
873
    return backend.GetCryptoTokens(token_types)
872
    token_requests = params[0]
873
    return backend.GetCryptoTokens(token_requests)
874 874

  
875 875
  # cluster --------------------------
876 876

  
b/src/Ganeti/Constants.hs
4157 4157
cryptoTypes :: FrozenSet String
4158 4158
cryptoTypes = ConstantUtils.mkSet [cryptoTypeSslDigest]
4159 4159

  
4160
-- * Crypto Actions
4161
-- Actions that can be performed on crypto tokens
4162

  
4163
cryptoActionGet :: String
4164
cryptoActionGet = "get"
4165

  
4166
-- This is 'create and get'
4167
cryptoActionCreate :: String
4168
cryptoActionCreate = "create"
4169

  
4170
cryptoActions :: FrozenSet String
4171
cryptoActions = ConstantUtils.mkSet [cryptoActionGet, cryptoActionCreate]
4172

  
4173
-- * Options for CryptoActions
4174

  
4175
-- Filename of the certificate
4176
cryptoOptionCertFile :: String
4177
cryptoOptionCertFile = "cert_file"
4160 4178

  
4161 4179
-- * SSH key types
4162 4180

  
b/test/py/ganeti.backend_unittest.py
34 34
from ganeti import hypervisor
35 35
from ganeti import netutils
36 36
from ganeti import objects
37
from ganeti import pathutils
37 38
from ganeti import utils
38 39

  
39 40

  
......
76 77
class TestGetCryptoTokens(testutils.GanetiTestCase):
77 78

  
78 79
  def setUp(self):
79
    self._digest_fn_orig = utils.GetClientCertificateDigest
80
    self._get_digest_fn_orig = utils.GetClientCertificateDigest
81
    self._create_digest_fn_orig = utils.GenerateNewSslCert
80 82
    self._ssl_digest = "12345"
81 83
    utils.GetClientCertificateDigest = mock.Mock(
82 84
      return_value=self._ssl_digest)
85
    utils.GenerateNewSslCert = mock.Mock()
83 86

  
84 87
  def tearDown(self):
85
    utils.GetClientCertificateDigest = self._digest_fn_orig
88
    utils.GetClientCertificateDigest = self._get_digest_fn_orig
89
    utils.GenerateNewSslCert = self._create_digest_fn_orig
86 90

  
87
  def testSslToken(self):
88
    result = backend.GetCryptoTokens([constants.CRYPTO_TYPE_SSL_DIGEST])
91
  def testGetSslToken(self):
92
    result = backend.GetCryptoTokens(
93
      [(constants.CRYPTO_TYPE_SSL_DIGEST, constants.CRYPTO_ACTION_GET, None)])
89 94
    self.assertTrue((constants.CRYPTO_TYPE_SSL_DIGEST, self._ssl_digest)
90 95
                    in result)
91 96

  
92
  def testUnknownToken(self):
97
  def testCreateSslToken(self):
98
    result = backend.GetCryptoTokens(
99
      [(constants.CRYPTO_TYPE_SSL_DIGEST, constants.CRYPTO_ACTION_CREATE,
100
        None)])
101
    self.assertTrue((constants.CRYPTO_TYPE_SSL_DIGEST, self._ssl_digest)
102
                    in result)
103
    self.assertTrue(utils.GenerateNewSslCert.assert_calls().once())
104

  
105
  def testCreateSslTokenDifferentFilename(self):
106
    result = backend.GetCryptoTokens(
107
      [(constants.CRYPTO_TYPE_SSL_DIGEST, constants.CRYPTO_ACTION_CREATE,
108
        {constants.CRYPTO_OPTION_CERT_FILE:
109
          pathutils.NODED_CLIENT_CERT_FILE_TMP})])
110
    self.assertTrue((constants.CRYPTO_TYPE_SSL_DIGEST, self._ssl_digest)
111
                    in result)
112
    self.assertTrue(utils.GenerateNewSslCert.assert_calls().once())
113

  
114
  def testUnknownTokenType(self):
115
    self.assertRaises(errors.ProgrammerError,
116
                      backend.GetCryptoTokens,
117
                      [("pink_bunny", constants.CRYPTO_ACTION_GET, None)])
118

  
119
  def testUnknownAction(self):
93 120
    self.assertRaises(errors.ProgrammerError,
94
                      backend.GetCryptoTokens, ["pink_bunny"])
121
                      backend.GetCryptoTokens,
122
                      [(constants.CRYPTO_TYPE_SSL_DIGEST, "illuminate", None)])
95 123

  
96 124

  
97 125
class TestNodeVerify(testutils.GanetiTestCase):

Also available in: Unified diff