Revision d5104ca4

b/UPGRADE
29 29
way, specifiying the smaller version on the ``--to`` argument.
30 30

  
31 31

  
32
2.11
33
----
34

  
35
When upgrading to 2.11, first apply the instructions of ``2.11 and
36
above``. 2.11 comes with the new feature of enhanced RPC security
37
through client certificates. This features needs to be enabled after the
38
upgrade by::
39

  
40
   $ gnt-cluster renew-crypto --new-node-certificates
41

  
42
Note that new node certificates are generated automatically without
43
warning when upgrading with ``gnt-cluster upgrade``.
44

  
45

  
32 46
2.1 and above
33 47
-------------
34 48

  
b/lib/cmdlib/cluster.py
3189 3189

  
3190 3190
    feedback_fn("* Verifying configuration file consistency")
3191 3191

  
3192
    self._VerifyClientCertificates(self.my_node_info.values(), all_nvinfo)
3192 3193
    # If not all nodes are being checked, we need to make sure the master node
3193 3194
    # and a non-checked vm_capable node are in the list.
3194 3195
    absent_node_uuids = set(self.all_node_info).difference(self.my_node_info)
b/lib/http/__init__.py
610 610
    if ssl_verify_peer:
611 611
      ctx.set_verify(OpenSSL.SSL.VERIFY_PEER |
612 612
                     OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
613
                     self._SSLVerifyCallback)
613
                     ssl_verify_callback)
614 614

  
615 615
      # Also add our certificate as a trusted CA to be sent to the client.
616 616
      # This is required at least for GnuTLS clients to work.
b/lib/rpc/node.py
36 36
import pycurl
37 37
import threading
38 38
import copy
39
import os
39 40

  
40 41
from ganeti import utils
41 42
from ganeti import objects
......
97 98

  
98 99
def _ConfigRpcCurl(curl):
99 100
  noded_cert = str(pathutils.NODED_CERT_FILE)
101
  noded_client_cert = str(pathutils.NODED_CLIENT_CERT_FILE)
102

  
103
  # FIXME: The next two lines are necessary to ensure upgradability from
104
  # 2.10 to 2.11. Remove in 2.12, because this slows down RPC calls.
105
  if not os.path.exists(noded_client_cert):
106
    logging.info("Using server certificate as client certificate for RPC"
107
                 "call.")
108
    noded_client_cert = noded_cert
100 109

  
101 110
  curl.setopt(pycurl.FOLLOWLOCATION, False)
102 111
  curl.setopt(pycurl.CAINFO, noded_cert)
103 112
  curl.setopt(pycurl.SSL_VERIFYHOST, 0)
104 113
  curl.setopt(pycurl.SSL_VERIFYPEER, True)
105 114
  curl.setopt(pycurl.SSLCERTTYPE, "PEM")
106
  curl.setopt(pycurl.SSLCERT, noded_cert)
115
  curl.setopt(pycurl.SSLCERT, noded_client_cert)
107 116
  curl.setopt(pycurl.SSLKEYTYPE, "PEM")
108
  curl.setopt(pycurl.SSLKEY, noded_cert)
117
  curl.setopt(pycurl.SSLKEY, noded_client_cert)
109 118
  curl.setopt(pycurl.CONNECTTIMEOUT, constants.RPC_CONNECT_TIMEOUT)
110 119

  
111 120

  
b/man/gnt-cluster.rst
766 766
~~~~~~~~~~~~
767 767

  
768 768
| **renew-crypto** [-f]
769
| [\--new-cluster-certificate]
769
| [\--new-cluster-certificate] | [\--new-node-certificates]
770 770
| [\--new-confd-hmac-key]
771 771
| [\--new-rapi-certificate] [\--rapi-certificate *rapi-cert*]
772 772
| [\--new-spice-certificate | \--spice-certificate *spice-cert*
......
779 779
can be used to regenerate respectively the cluster-internal SSL
780 780
certificate and the HMAC key used by **ganeti-confd**\(8).
781 781

  
782
The option ``--new-node-certificates`` will generate new node SSL
783
certificates for all nodes. Note that the regeneration of the node
784
certificates takes place after the other certificates are created
785
and distributed and the ganeti daemons are restarted again.
786

  
782 787
To generate a new self-signed RAPI certificate (used by
783 788
**ganeti-rapi**\(8)) specify ``--new-rapi-certificate``. If you want to
784 789
use your own certificate, e.g. one signed by a certificate
b/src/Ganeti/Rpc.hs
90 90
import qualified Text.JSON as J
91 91
import Text.JSON.Pretty (pp_value)
92 92
import qualified Data.ByteString.Base64.Lazy as Base64
93
import System.Directory
93 94

  
94 95
import Network.Curl hiding (content)
95 96
import qualified Ganeti.Path as P
......
228 229
executeRpcCalls :: (Rpc a b) => [(Node, a)] -> IO [(Node, ERpcError b)]
229 230
executeRpcCalls nodeCalls = do
230 231
  cert_file <- P.nodedCertFile
231
  let (nodes, calls) = unzip nodeCalls
232
      opts = map (getOptionsForCall cert_file cert_file) calls
232
  client_cert_file_name <- P.nodedClientCertFile
233
  client_file_exists <- doesFileExist client_cert_file_name
234
  -- FIXME: This is needed to ensure upgradability to 2.11
235
  -- Remove in 2.12.
236
  let client_cert_file = if client_file_exists
237
                         then client_cert_file_name
238
                         else cert_file
239
      (nodes, calls) = unzip nodeCalls
240
      opts = map (getOptionsForCall cert_file client_cert_file) calls
233 241
      opts_urls = zipWith3 (\n c o ->
234 242
                         case prepareHttpRequest o n c of
235 243
                           Left v -> Left v
b/test/py/cmdlib/cluster_unittest.py
1085 1085
    self.ExecOpCode(op)
1086 1086

  
1087 1087

  
1088
class TestLUClusterVerifyClientCerts(CmdlibTestCase):
1089

  
1090
  def _AddNormalNode(self):
1091
    self.normalnode = copy.deepcopy(self.master)
1092
    self.normalnode.master_candidate = False
1093
    self.normalnode.uuid = "normal-node-uuid"
1094
    self.cfg.AddNode(self.normalnode, None)
1095

  
1096
  def testVerifyMasterCandidate(self):
1097
    client_cert = "client-cert-digest"
1098
    self.cluster.candidate_certs = {self.master.uuid: client_cert}
1099
    self.rpc.call_node_verify.return_value = \
1100
      RpcResultsBuilder() \
1101
        .AddSuccessfulNode(self.master,
1102
          {constants.NV_CLIENT_CERT: (None, client_cert)}) \
1103
        .Build()
1104
    op = opcodes.OpClusterVerifyGroup(group_name="default", verbose=True)
1105
    self.ExecOpCode(op)
1106

  
1107
  def testVerifyMasterCandidateInvalid(self):
1108
    client_cert = "client-cert-digest"
1109
    self.cluster.candidate_certs = {self.master.uuid: client_cert}
1110
    self.rpc.call_node_verify.return_value = \
1111
      RpcResultsBuilder() \
1112
        .AddSuccessfulNode(self.master,
1113
          {constants.NV_CLIENT_CERT: (666, "Invalid Certificate")}) \
1114
        .Build()
1115
    op = opcodes.OpClusterVerifyGroup(group_name="default", verbose=True)
1116
    self.ExecOpCode(op)
1117
    self.mcpu.assertLogContainsRegex("Client certificate")
1118
    self.mcpu.assertLogContainsRegex("failed validation")
1119

  
1120
  def testVerifyNoMasterCandidateMap(self):
1121
    client_cert = "client-cert-digest"
1122
    self.cluster.candidate_certs = {}
1123
    self.rpc.call_node_verify.return_value = \
1124
      RpcResultsBuilder() \
1125
        .AddSuccessfulNode(self.master,
1126
          {constants.NV_CLIENT_CERT: (None, client_cert)}) \
1127
        .Build()
1128
    op = opcodes.OpClusterVerifyGroup(group_name="default", verbose=True)
1129
    self.ExecOpCode(op)
1130
    self.mcpu.assertLogContainsRegex(
1131
      "list of master candidate certificates is empty")
1132

  
1133
  def testVerifyNoSharingMasterCandidates(self):
1134
    client_cert = "client-cert-digest"
1135
    self.cluster.candidate_certs = {
1136
      self.master.uuid: client_cert,
1137
      "some-other-master-candidate-uuid": client_cert}
1138
    self.rpc.call_node_verify.return_value = \
1139
      RpcResultsBuilder() \
1140
        .AddSuccessfulNode(self.master,
1141
          {constants.NV_CLIENT_CERT: (None, client_cert)}) \
1142
        .Build()
1143
    op = opcodes.OpClusterVerifyGroup(group_name="default", verbose=True)
1144
    self.ExecOpCode(op)
1145
    self.mcpu.assertLogContainsRegex(
1146
      "two master candidates configured to use the same")
1147

  
1148
  def testVerifyMasterCandidateCertMismatch(self):
1149
    client_cert = "client-cert-digest"
1150
    self.cluster.candidate_certs = {self.master.uuid: "different-cert-digest"}
1151
    self.rpc.call_node_verify.return_value = \
1152
      RpcResultsBuilder() \
1153
        .AddSuccessfulNode(self.master,
1154
          {constants.NV_CLIENT_CERT: (None, client_cert)}) \
1155
        .Build()
1156
    op = opcodes.OpClusterVerifyGroup(group_name="default", verbose=True)
1157
    self.ExecOpCode(op)
1158
    self.mcpu.assertLogContainsRegex("does not match its entry")
1159

  
1160
  def testVerifyMasterCandidateUnregistered(self):
1161
    client_cert = "client-cert-digest"
1162
    self.cluster.candidate_certs = {"other-node-uuid": "different-cert-digest"}
1163
    self.rpc.call_node_verify.return_value = \
1164
      RpcResultsBuilder() \
1165
        .AddSuccessfulNode(self.master,
1166
          {constants.NV_CLIENT_CERT: (None, client_cert)}) \
1167
        .Build()
1168
    op = opcodes.OpClusterVerifyGroup(group_name="default", verbose=True)
1169
    self.ExecOpCode(op)
1170
    self.mcpu.assertLogContainsRegex("does not have an entry")
1171

  
1172
  def testVerifyMasterCandidateOtherNodesCert(self):
1173
    client_cert = "client-cert-digest"
1174
    self.cluster.candidate_certs = {"other-node-uuid": client_cert}
1175
    self.rpc.call_node_verify.return_value = \
1176
      RpcResultsBuilder() \
1177
        .AddSuccessfulNode(self.master,
1178
          {constants.NV_CLIENT_CERT: (None, client_cert)}) \
1179
        .Build()
1180
    op = opcodes.OpClusterVerifyGroup(group_name="default", verbose=True)
1181
    self.ExecOpCode(op)
1182
    self.mcpu.assertLogContainsRegex("using a certificate of another node")
1183

  
1184
  def testNormalNodeStillInList(self):
1185
    self._AddNormalNode()
1186
    client_cert_master = "client-cert-digest-master"
1187
    client_cert_normal = "client-cert-digest-normal"
1188
    self.cluster.candidate_certs = {
1189
      self.normalnode.uuid: client_cert_normal,
1190
      self.master.uuid: client_cert_master}
1191
    self.rpc.call_node_verify.return_value = \
1192
      RpcResultsBuilder() \
1193
        .AddSuccessfulNode(self.normalnode,
1194
          {constants.NV_CLIENT_CERT: (None, client_cert_normal)}) \
1195
        .AddSuccessfulNode(self.master,
1196
          {constants.NV_CLIENT_CERT: (None, client_cert_master)}) \
1197
        .Build()
1198
    op = opcodes.OpClusterVerifyGroup(group_name="default", verbose=True)
1199
    self.ExecOpCode(op)
1200
    self.mcpu.assertLogContainsRegex("not a master candidate")
1201
    self.mcpu.assertLogContainsRegex("still listed")
1202

  
1203
  def testNormalNodeStealingMasterCandidateCert(self):
1204
    self._AddNormalNode()
1205
    client_cert_master = "client-cert-digest-master"
1206
    self.cluster.candidate_certs = {
1207
      self.master.uuid: client_cert_master}
1208
    self.rpc.call_node_verify.return_value = \
1209
      RpcResultsBuilder() \
1210
        .AddSuccessfulNode(self.normalnode,
1211
          {constants.NV_CLIENT_CERT: (None, client_cert_master)}) \
1212
        .AddSuccessfulNode(self.master,
1213
          {constants.NV_CLIENT_CERT: (None, client_cert_master)}) \
1214
        .Build()
1215
    op = opcodes.OpClusterVerifyGroup(group_name="default", verbose=True)
1216
    self.ExecOpCode(op)
1217
    self.mcpu.assertLogContainsRegex("not a master candidate")
1218
    self.mcpu.assertLogContainsRegex(
1219
      "certificate of another node which is master candidate")
1220

  
1221

  
1088 1222
class TestLUClusterVerifyGroupMethods(CmdlibTestCase):
1089 1223
  """Base class for testing individual methods in LUClusterVerifyGroup.
1090 1224

  
b/tools/post-upgrade
43 43
  version = utils.version.ParseVersion(versionstring)
44 44

  
45 45
  if utils.version.IsBefore(version, 2, 11, 0):
46
    # FIXME: Add client certificate handling here when resolving issue 692.
47
    pass
48

  
46
    result = utils.RunCmd(["gnt-cluster", "renew-crypto",
47
                           "--new-node-certificates", "-f"])
48
    if result.failed:
49
      cli.ToStderr("Failed to create node certificates: %s; Output %s" %
50
                   (result.fail_reason, result.output))
49 51
  return 0
50 52

  
51 53
if __name__ == "__main__":

Also available in: Unified diff