Add tools/common.py from 2.13
[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 from cStringIO import StringIO
37
38 from ganeti import constants
39 from ganeti import utils
40 from ganeti import serializer
41 from ganeti import ssconf
42
43
44 def VerifyOptions(parser, opts, args):
45 """Verifies options and arguments for correctness.
46
47 """
48 if args:
49 parser.error("No arguments are expected")
50
51 return opts
52
53
54 def _VerifyCertificate(cert_pem, error_fn,
55 _check_fn=utils.CheckNodeCertificate):
56 """Verifies a certificate against the local node daemon certificate.
57
58 @type cert_pem: string
59 @param cert_pem: Certificate and key in PEM format
60 @type error_fn: callable
61 @param error_fn: function to call in case of an error
62 @rtype: string
63 @return: Formatted key and certificate
64
65 """
66 try:
67 cert = \
68 OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
69 except Exception, err:
70 raise error_fn("(stdin) Unable to load certificate: %s" % err)
71
72 try:
73 key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
74 except OpenSSL.crypto.Error, err:
75 raise error_fn("(stdin) Unable to load private key: %s" % err)
76
77 # Check certificate with given key; this detects cases where the key given on
78 # stdin doesn't match the certificate also given on stdin
79 try:
80 utils.X509CertKeyCheck(cert, key)
81 except OpenSSL.SSL.Error:
82 raise error_fn("(stdin) Certificate is not signed with given key")
83
84 # Standard checks, including check against an existing local certificate
85 # (no-op if that doesn't exist)
86 _check_fn(cert)
87
88 key_encoded = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)
89 cert_encoded = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
90 cert)
91 complete_cert_encoded = key_encoded + cert_encoded
92 if not cert_pem == complete_cert_encoded:
93 logging.error("The certificate differs after being reencoded. Please"
94 " renew the certificates cluster-wide to prevent future"
95 " inconsistencies.")
96
97 # Format for storing on disk
98 buf = StringIO()
99 buf.write(cert_pem)
100 return buf.getvalue()
101
102
103 def VerifyCertificate(data, error_fn, _verify_fn=_VerifyCertificate):
104 """Verifies cluster certificate.
105
106 @type data: dict
107 @type error_fn: callable
108 @param error_fn: function to call in case of an error
109 @rtype: string
110 @return: Formatted key and certificate
111
112 """
113 cert = data.get(constants.NDS_NODE_DAEMON_CERTIFICATE)
114 if not cert:
115 raise error_fn("Node daemon certificate must be specified")
116
117 return _verify_fn(cert, error_fn)
118
119
120 def VerifyClusterName(data, error_fn,
121 _verify_fn=ssconf.VerifyClusterName):
122 """Verifies cluster name.
123
124 @type data: dict
125
126 """
127 name = data.get(constants.SSHS_CLUSTER_NAME)
128 if name:
129 _verify_fn(name)
130 else:
131 raise error_fn("Cluster name must be specified")
132
133
134 def LoadData(raw, data_check):
135 """Parses and verifies input data.
136
137 @rtype: dict
138
139 """
140 return serializer.LoadAndVerifyJson(raw, data_check)