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