d3c4f671907b27f0198259617f42c518faef01ba
[ganeti-github.git] / lib / tools / common.py
1 #
2 #
3
4 # Copyright (C) 2014 Google Inc.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are
9 # met:
10 #
11 # 1. Redistributions of source code must retain the above copyright notice,
12 # this list of conditions and the following disclaimer.
13 #
14 # 2. Redistributions in binary form must reproduce the above copyright
15 # notice, this list of conditions and the following disclaimer in the
16 # documentation and/or other materials provided with the distribution.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19 # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 """Common functions for tool scripts.
31
32 """
33
34 import logging
35 import OpenSSL
36 import os
37 import time
38 from cStringIO import StringIO
39
40 from ganeti import constants
41 from ganeti import errors
42 from ganeti import pathutils
43 from ganeti import utils
44 from ganeti import serializer
45 from ganeti import ssconf
46 from ganeti import ssh
47
48
49 def VerifyOptions(parser, opts, args):
50 """Verifies options and arguments for correctness.
51
52 """
53 if args:
54 parser.error("No arguments are expected")
55
56 return opts
57
58
59 def _VerifyCertificateStrong(cert_pem, error_fn,
60 _check_fn=utils.CheckNodeCertificate):
61 """Verifies a certificate against the local node daemon certificate.
62
63 @type cert_pem: string
64 @param cert_pem: Certificate and key in PEM format
65 @type error_fn: callable
66 @param error_fn: function to call in case of an error
67 @rtype: string
68 @return: Formatted key and certificate
69
70 """
71 try:
72 cert = \
73 OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
74 except Exception, err:
75 raise error_fn("(stdin) Unable to load certificate: %s" % err)
76
77 try:
78 key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
79 except OpenSSL.crypto.Error, err:
80 raise error_fn("(stdin) Unable to load private key: %s" % err)
81
82 # Check certificate with given key; this detects cases where the key given on
83 # stdin doesn't match the certificate also given on stdin
84 try:
85 utils.X509CertKeyCheck(cert, key)
86 except OpenSSL.SSL.Error:
87 raise error_fn("(stdin) Certificate is not signed with given key")
88
89 # Standard checks, including check against an existing local certificate
90 # (no-op if that doesn't exist)
91 _check_fn(cert)
92
93 key_encoded = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)
94 cert_encoded = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
95 cert)
96 complete_cert_encoded = key_encoded + cert_encoded
97 if not cert_pem == complete_cert_encoded:
98 logging.error("The certificate differs after being reencoded. Please"
99 " renew the certificates cluster-wide to prevent future"
100 " inconsistencies.")
101
102 # Format for storing on disk
103 buf = StringIO()
104 buf.write(cert_pem)
105 return buf.getvalue()
106
107
108 def _VerifyCertificateSoft(cert_pem, error_fn,
109 _check_fn=utils.CheckNodeCertificate):
110 """Verifies a certificate against the local node daemon certificate.
111
112 @type cert_pem: string
113 @param cert_pem: Certificate in PEM format (no key)
114
115 """
116 try:
117 OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
118 except OpenSSL.crypto.Error, err:
119 pass
120 else:
121 raise error_fn("No private key may be given")
122
123 try:
124 cert = \
125 OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
126 except Exception, err:
127 raise errors.X509CertError("(stdin)",
128 "Unable to load certificate: %s" % err)
129
130 _check_fn(cert)
131
132
133 def VerifyCertificateSoft(data, error_fn, _verify_fn=_VerifyCertificateSoft):
134 """Verifies cluster certificate if existing.
135
136 @type data: dict
137 @type error_fn: callable
138 @param error_fn: function to call in case of an error
139 @rtype: string
140 @return: Formatted key and certificate
141
142 """
143 cert = data.get(constants.SSHS_NODE_DAEMON_CERTIFICATE)
144 if cert:
145 _verify_fn(cert, error_fn)
146
147
148 def VerifyCertificateStrong(data, error_fn,
149 _verify_fn=_VerifyCertificateStrong):
150 """Verifies cluster certificate. Throws error when not existing.
151
152 @type data: dict
153 @type error_fn: callable
154 @param error_fn: function to call in case of an error
155 @rtype: string
156 @return: Formatted key and certificate
157
158 """
159 cert = data.get(constants.NDS_NODE_DAEMON_CERTIFICATE)
160 if not cert:
161 raise error_fn("Node daemon certificate must be specified")
162
163 return _verify_fn(cert, error_fn)
164
165
166 def VerifyClusterName(data, error_fn,
167 _verify_fn=ssconf.VerifyClusterName):
168 """Verifies cluster name.
169
170 @type data: dict
171
172 """
173 name = data.get(constants.SSHS_CLUSTER_NAME)
174 if name:
175 _verify_fn(name)
176 else:
177 raise error_fn("Cluster name must be specified")
178
179
180 def LoadData(raw, data_check):
181 """Parses and verifies input data.
182
183 @rtype: dict
184
185 """
186 return serializer.LoadAndVerifyJson(raw, data_check)
187
188
189 def GenerateRootSshKeys(error_fn, _suffix="", _homedir_fn=None):
190 """Generates root's SSH keys for this node.
191
192 """
193 ssh.InitSSHSetup(error_fn=error_fn, _homedir_fn=_homedir_fn, _suffix=_suffix)
194
195
196 def GenerateClientCertificate(
197 data, error_fn, client_cert=pathutils.NODED_CLIENT_CERT_FILE,
198 signing_cert=pathutils.NODED_CERT_FILE):
199 """Regenerates the client certificate of the node.
200
201 @type data: string
202 @param data: the JSON-formated input data
203
204 """
205 if not os.path.exists(signing_cert):
206 raise error_fn("The signing certificate '%s' cannot be found."
207 % signing_cert)
208
209 # TODO: This sets the serial number to the number of seconds
210 # since epoch. This is technically not a correct serial number
211 # (in the way SSL is supposed to be used), but it serves us well
212 # enough for now, as we don't have any infrastructure for keeping
213 # track of the number of signed certificates yet.
214 serial_no = int(time.time())
215
216 # The hostname of the node is provided with the input data.
217 hostname = data.get(constants.NDS_NODE_NAME)
218 if not hostname:
219 raise error_fn("No hostname found.")
220
221 utils.GenerateSignedSslCert(client_cert, serial_no, signing_cert,
222 common_name=hostname)