root / lib / tools / prepare_node_join.py @ 8f096849
History | View | Annotate | Download (6.9 kB)
1 | d12b9f66 | Michael Hanselmann | #
|
---|---|---|---|
2 | d12b9f66 | Michael Hanselmann | #
|
3 | d12b9f66 | Michael Hanselmann | |
4 | d12b9f66 | Michael Hanselmann | # Copyright (C) 2012 Google Inc.
|
5 | d12b9f66 | Michael Hanselmann | #
|
6 | d12b9f66 | Michael Hanselmann | # This program is free software; you can redistribute it and/or modify
|
7 | d12b9f66 | Michael Hanselmann | # it under the terms of the GNU General Public License as published by
|
8 | d12b9f66 | Michael Hanselmann | # the Free Software Foundation; either version 2 of the License, or
|
9 | d12b9f66 | Michael Hanselmann | # (at your option) any later version.
|
10 | d12b9f66 | Michael Hanselmann | #
|
11 | d12b9f66 | Michael Hanselmann | # This program is distributed in the hope that it will be useful, but
|
12 | d12b9f66 | Michael Hanselmann | # WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 | d12b9f66 | Michael Hanselmann | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14 | d12b9f66 | Michael Hanselmann | # General Public License for more details.
|
15 | d12b9f66 | Michael Hanselmann | #
|
16 | d12b9f66 | Michael Hanselmann | # You should have received a copy of the GNU General Public License
|
17 | d12b9f66 | Michael Hanselmann | # along with this program; if not, write to the Free Software
|
18 | d12b9f66 | Michael Hanselmann | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
19 | d12b9f66 | Michael Hanselmann | # 02110-1301, USA.
|
20 | d12b9f66 | Michael Hanselmann | |
21 | d12b9f66 | Michael Hanselmann | """Script to prepare a node for joining a cluster.
|
22 | d12b9f66 | Michael Hanselmann |
|
23 | d12b9f66 | Michael Hanselmann | """
|
24 | d12b9f66 | Michael Hanselmann | |
25 | d12b9f66 | Michael Hanselmann | import os |
26 | d12b9f66 | Michael Hanselmann | import os.path |
27 | d12b9f66 | Michael Hanselmann | import optparse |
28 | d12b9f66 | Michael Hanselmann | import sys |
29 | d12b9f66 | Michael Hanselmann | import logging |
30 | d12b9f66 | Michael Hanselmann | import OpenSSL |
31 | d12b9f66 | Michael Hanselmann | |
32 | d12b9f66 | Michael Hanselmann | from ganeti import cli |
33 | d12b9f66 | Michael Hanselmann | from ganeti import constants |
34 | d12b9f66 | Michael Hanselmann | from ganeti import errors |
35 | d12b9f66 | Michael Hanselmann | from ganeti import pathutils |
36 | d12b9f66 | Michael Hanselmann | from ganeti import utils |
37 | d12b9f66 | Michael Hanselmann | from ganeti import serializer |
38 | d12b9f66 | Michael Hanselmann | from ganeti import ht |
39 | d12b9f66 | Michael Hanselmann | from ganeti import ssh |
40 | d12b9f66 | Michael Hanselmann | from ganeti import ssconf |
41 | d12b9f66 | Michael Hanselmann | |
42 | d12b9f66 | Michael Hanselmann | |
43 | 1facaf11 | Michael Hanselmann | _SSH_KEY_LIST_ITEM = \ |
44 | 1facaf11 | Michael Hanselmann | ht.TAnd(ht.TIsLength(3),
|
45 | 1facaf11 | Michael Hanselmann | ht.TItems([ |
46 | 1facaf11 | Michael Hanselmann | ht.TElemOf(constants.SSHK_ALL), |
47 | 1facaf11 | Michael Hanselmann | ht.Comment("public")(ht.TNonEmptyString),
|
48 | 1facaf11 | Michael Hanselmann | ht.Comment("private")(ht.TNonEmptyString),
|
49 | 1facaf11 | Michael Hanselmann | ])) |
50 | 1facaf11 | Michael Hanselmann | |
51 | 1facaf11 | Michael Hanselmann | _SSH_KEY_LIST = ht.TListOf(_SSH_KEY_LIST_ITEM) |
52 | d12b9f66 | Michael Hanselmann | |
53 | d12b9f66 | Michael Hanselmann | _DATA_CHECK = ht.TStrictDict(False, True, { |
54 | d12b9f66 | Michael Hanselmann | constants.SSHS_CLUSTER_NAME: ht.TNonEmptyString, |
55 | d12b9f66 | Michael Hanselmann | constants.SSHS_NODE_DAEMON_CERTIFICATE: ht.TNonEmptyString, |
56 | d12b9f66 | Michael Hanselmann | constants.SSHS_SSH_HOST_KEY: _SSH_KEY_LIST, |
57 | d12b9f66 | Michael Hanselmann | constants.SSHS_SSH_ROOT_KEY: _SSH_KEY_LIST, |
58 | d12b9f66 | Michael Hanselmann | }) |
59 | d12b9f66 | Michael Hanselmann | |
60 | d12b9f66 | Michael Hanselmann | |
61 | d12b9f66 | Michael Hanselmann | class JoinError(errors.GenericError): |
62 | d12b9f66 | Michael Hanselmann | """Local class for reporting errors.
|
63 | d12b9f66 | Michael Hanselmann |
|
64 | d12b9f66 | Michael Hanselmann | """
|
65 | d12b9f66 | Michael Hanselmann | |
66 | d12b9f66 | Michael Hanselmann | |
67 | d12b9f66 | Michael Hanselmann | def ParseOptions(): |
68 | d12b9f66 | Michael Hanselmann | """Parses the options passed to the program.
|
69 | d12b9f66 | Michael Hanselmann |
|
70 | d12b9f66 | Michael Hanselmann | @return: Options and arguments
|
71 | d12b9f66 | Michael Hanselmann |
|
72 | d12b9f66 | Michael Hanselmann | """
|
73 | d12b9f66 | Michael Hanselmann | program = os.path.basename(sys.argv[0])
|
74 | d12b9f66 | Michael Hanselmann | |
75 | d12b9f66 | Michael Hanselmann | parser = optparse.OptionParser(usage="%prog [--dry-run]",
|
76 | d12b9f66 | Michael Hanselmann | prog=program) |
77 | d12b9f66 | Michael Hanselmann | parser.add_option(cli.DEBUG_OPT) |
78 | d12b9f66 | Michael Hanselmann | parser.add_option(cli.VERBOSE_OPT) |
79 | d12b9f66 | Michael Hanselmann | parser.add_option(cli.DRY_RUN_OPT) |
80 | d12b9f66 | Michael Hanselmann | |
81 | d12b9f66 | Michael Hanselmann | (opts, args) = parser.parse_args() |
82 | d12b9f66 | Michael Hanselmann | |
83 | d12b9f66 | Michael Hanselmann | return VerifyOptions(parser, opts, args)
|
84 | d12b9f66 | Michael Hanselmann | |
85 | d12b9f66 | Michael Hanselmann | |
86 | d12b9f66 | Michael Hanselmann | def VerifyOptions(parser, opts, args): |
87 | d12b9f66 | Michael Hanselmann | """Verifies options and arguments for correctness.
|
88 | d12b9f66 | Michael Hanselmann |
|
89 | d12b9f66 | Michael Hanselmann | """
|
90 | d12b9f66 | Michael Hanselmann | if args:
|
91 | d12b9f66 | Michael Hanselmann | parser.error("No arguments are expected")
|
92 | d12b9f66 | Michael Hanselmann | |
93 | d12b9f66 | Michael Hanselmann | return opts
|
94 | d12b9f66 | Michael Hanselmann | |
95 | d12b9f66 | Michael Hanselmann | |
96 | 0602cef3 | Michael Hanselmann | def _VerifyCertificate(cert_pem, _check_fn=utils.CheckNodeCertificate): |
97 | d12b9f66 | Michael Hanselmann | """Verifies a certificate against the local node daemon certificate.
|
98 | d12b9f66 | Michael Hanselmann |
|
99 | 0602cef3 | Michael Hanselmann | @type cert_pem: string
|
100 | 0602cef3 | Michael Hanselmann | @param cert_pem: Certificate in PEM format (no key)
|
101 | d12b9f66 | Michael Hanselmann |
|
102 | d12b9f66 | Michael Hanselmann | """
|
103 | d12b9f66 | Michael Hanselmann | try:
|
104 | 0602cef3 | Michael Hanselmann | OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, cert_pem) |
105 | d12b9f66 | Michael Hanselmann | except OpenSSL.crypto.Error, err:
|
106 | d12b9f66 | Michael Hanselmann | pass
|
107 | d12b9f66 | Michael Hanselmann | else:
|
108 | d12b9f66 | Michael Hanselmann | raise JoinError("No private key may be given") |
109 | d12b9f66 | Michael Hanselmann | |
110 | d12b9f66 | Michael Hanselmann | try:
|
111 | 0602cef3 | Michael Hanselmann | cert = \ |
112 | 0602cef3 | Michael Hanselmann | OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem) |
113 | d12b9f66 | Michael Hanselmann | except Exception, err: |
114 | d12b9f66 | Michael Hanselmann | raise errors.X509CertError("(stdin)", |
115 | d12b9f66 | Michael Hanselmann | "Unable to load certificate: %s" % err)
|
116 | d12b9f66 | Michael Hanselmann | |
117 | 0602cef3 | Michael Hanselmann | _check_fn(cert) |
118 | d12b9f66 | Michael Hanselmann | |
119 | d12b9f66 | Michael Hanselmann | |
120 | d12b9f66 | Michael Hanselmann | def VerifyCertificate(data, _verify_fn=_VerifyCertificate): |
121 | d12b9f66 | Michael Hanselmann | """Verifies cluster certificate.
|
122 | d12b9f66 | Michael Hanselmann |
|
123 | d12b9f66 | Michael Hanselmann | @type data: dict
|
124 | d12b9f66 | Michael Hanselmann |
|
125 | d12b9f66 | Michael Hanselmann | """
|
126 | d12b9f66 | Michael Hanselmann | cert = data.get(constants.SSHS_NODE_DAEMON_CERTIFICATE) |
127 | d12b9f66 | Michael Hanselmann | if cert:
|
128 | d12b9f66 | Michael Hanselmann | _verify_fn(cert) |
129 | d12b9f66 | Michael Hanselmann | |
130 | d12b9f66 | Michael Hanselmann | |
131 | dffa96d6 | Michael Hanselmann | def VerifyClusterName(data, _verify_fn=ssconf.VerifyClusterName): |
132 | d12b9f66 | Michael Hanselmann | """Verifies cluster name.
|
133 | d12b9f66 | Michael Hanselmann |
|
134 | d12b9f66 | Michael Hanselmann | @type data: dict
|
135 | d12b9f66 | Michael Hanselmann |
|
136 | d12b9f66 | Michael Hanselmann | """
|
137 | d12b9f66 | Michael Hanselmann | name = data.get(constants.SSHS_CLUSTER_NAME) |
138 | d12b9f66 | Michael Hanselmann | if name:
|
139 | d12b9f66 | Michael Hanselmann | _verify_fn(name) |
140 | d12b9f66 | Michael Hanselmann | else:
|
141 | d12b9f66 | Michael Hanselmann | raise JoinError("Cluster name must be specified") |
142 | d12b9f66 | Michael Hanselmann | |
143 | d12b9f66 | Michael Hanselmann | |
144 | d12b9f66 | Michael Hanselmann | def _UpdateKeyFiles(keys, dry_run, keyfiles): |
145 | d12b9f66 | Michael Hanselmann | """Updates SSH key files.
|
146 | d12b9f66 | Michael Hanselmann |
|
147 | d12b9f66 | Michael Hanselmann | @type keys: sequence of tuple; (string, string, string)
|
148 | d12b9f66 | Michael Hanselmann | @param keys: Keys to write, tuples consist of key type
|
149 | d12b9f66 | Michael Hanselmann | (L{constants.SSHK_ALL}), public and private key
|
150 | d12b9f66 | Michael Hanselmann | @type dry_run: boolean
|
151 | d12b9f66 | Michael Hanselmann | @param dry_run: Whether to perform a dry run
|
152 | d12b9f66 | Michael Hanselmann | @type keyfiles: dict; (string as key, tuple with (string, string) as values)
|
153 | d12b9f66 | Michael Hanselmann | @param keyfiles: Mapping from key types (L{constants.SSHK_ALL}) to file
|
154 | d12b9f66 | Michael Hanselmann | names; value tuples consist of public key filename and private key filename
|
155 | d12b9f66 | Michael Hanselmann |
|
156 | d12b9f66 | Michael Hanselmann | """
|
157 | d12b9f66 | Michael Hanselmann | assert set(keyfiles) == constants.SSHK_ALL |
158 | d12b9f66 | Michael Hanselmann | |
159 | 340ae7da | Michael Hanselmann | for (kind, private_key, public_key) in keys: |
160 | 340ae7da | Michael Hanselmann | (private_file, public_file) = keyfiles[kind] |
161 | d12b9f66 | Michael Hanselmann | |
162 | d12b9f66 | Michael Hanselmann | logging.debug("Writing %s ...", private_file)
|
163 | d12b9f66 | Michael Hanselmann | utils.WriteFile(private_file, data=private_key, mode=0600,
|
164 | d12b9f66 | Michael Hanselmann | backup=True, dry_run=dry_run)
|
165 | d12b9f66 | Michael Hanselmann | |
166 | 340ae7da | Michael Hanselmann | logging.debug("Writing %s ...", public_file)
|
167 | 340ae7da | Michael Hanselmann | utils.WriteFile(public_file, data=public_key, mode=0644,
|
168 | 340ae7da | Michael Hanselmann | backup=True, dry_run=dry_run)
|
169 | 340ae7da | Michael Hanselmann | |
170 | d12b9f66 | Michael Hanselmann | |
171 | d12b9f66 | Michael Hanselmann | def UpdateSshDaemon(data, dry_run, _runcmd_fn=utils.RunCmd, |
172 | d12b9f66 | Michael Hanselmann | _keyfiles=None):
|
173 | d12b9f66 | Michael Hanselmann | """Updates SSH daemon's keys.
|
174 | d12b9f66 | Michael Hanselmann |
|
175 | d12b9f66 | Michael Hanselmann | Unless C{dry_run} is set, the daemon is restarted at the end.
|
176 | d12b9f66 | Michael Hanselmann |
|
177 | d12b9f66 | Michael Hanselmann | @type data: dict
|
178 | d12b9f66 | Michael Hanselmann | @param data: Input data
|
179 | d12b9f66 | Michael Hanselmann | @type dry_run: boolean
|
180 | d12b9f66 | Michael Hanselmann | @param dry_run: Whether to perform a dry run
|
181 | d12b9f66 | Michael Hanselmann |
|
182 | d12b9f66 | Michael Hanselmann | """
|
183 | d12b9f66 | Michael Hanselmann | keys = data.get(constants.SSHS_SSH_HOST_KEY) |
184 | d12b9f66 | Michael Hanselmann | if not keys: |
185 | d12b9f66 | Michael Hanselmann | return
|
186 | d12b9f66 | Michael Hanselmann | |
187 | d12b9f66 | Michael Hanselmann | if _keyfiles is None: |
188 | ebae9e37 | Michael Hanselmann | _keyfiles = constants.SSH_DAEMON_KEYFILES |
189 | d12b9f66 | Michael Hanselmann | |
190 | d12b9f66 | Michael Hanselmann | logging.info("Updating SSH daemon key files")
|
191 | d12b9f66 | Michael Hanselmann | _UpdateKeyFiles(keys, dry_run, _keyfiles) |
192 | d12b9f66 | Michael Hanselmann | |
193 | d12b9f66 | Michael Hanselmann | if dry_run:
|
194 | d12b9f66 | Michael Hanselmann | logging.info("This is a dry run, not restarting SSH daemon")
|
195 | d12b9f66 | Michael Hanselmann | else:
|
196 | d12b9f66 | Michael Hanselmann | result = _runcmd_fn([pathutils.DAEMON_UTIL, "reload-ssh-keys"],
|
197 | d12b9f66 | Michael Hanselmann | interactive=True)
|
198 | d12b9f66 | Michael Hanselmann | if result.failed:
|
199 | d12b9f66 | Michael Hanselmann | raise JoinError("Could not reload SSH keys, command '%s'" |
200 | d12b9f66 | Michael Hanselmann | " had exitcode %s and error %s" %
|
201 | d12b9f66 | Michael Hanselmann | (result.cmd, result.exit_code, result.output)) |
202 | d12b9f66 | Michael Hanselmann | |
203 | d12b9f66 | Michael Hanselmann | |
204 | d12b9f66 | Michael Hanselmann | def UpdateSshRoot(data, dry_run, _homedir_fn=None): |
205 | d12b9f66 | Michael Hanselmann | """Updates root's SSH keys.
|
206 | d12b9f66 | Michael Hanselmann |
|
207 | d12b9f66 | Michael Hanselmann | Root's C{authorized_keys} file is also updated with new public keys.
|
208 | d12b9f66 | Michael Hanselmann |
|
209 | d12b9f66 | Michael Hanselmann | @type data: dict
|
210 | d12b9f66 | Michael Hanselmann | @param data: Input data
|
211 | d12b9f66 | Michael Hanselmann | @type dry_run: boolean
|
212 | d12b9f66 | Michael Hanselmann | @param dry_run: Whether to perform a dry run
|
213 | d12b9f66 | Michael Hanselmann |
|
214 | d12b9f66 | Michael Hanselmann | """
|
215 | d12b9f66 | Michael Hanselmann | keys = data.get(constants.SSHS_SSH_ROOT_KEY) |
216 | d12b9f66 | Michael Hanselmann | if not keys: |
217 | d12b9f66 | Michael Hanselmann | return
|
218 | d12b9f66 | Michael Hanselmann | |
219 | f712208d | Michael Hanselmann | (auth_keys_file, keyfiles) = \ |
220 | f712208d | Michael Hanselmann | ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=True,
|
221 | f712208d | Michael Hanselmann | _homedir_fn=_homedir_fn) |
222 | f712208d | Michael Hanselmann | |
223 | f712208d | Michael Hanselmann | _UpdateKeyFiles(keys, dry_run, keyfiles) |
224 | d12b9f66 | Michael Hanselmann | |
225 | d12b9f66 | Michael Hanselmann | if dry_run:
|
226 | d12b9f66 | Michael Hanselmann | logging.info("This is a dry run, not modifying %s", auth_keys_file)
|
227 | d12b9f66 | Michael Hanselmann | else:
|
228 | 910ef222 | Michael Hanselmann | for (_, _, public_key) in keys: |
229 | 910ef222 | Michael Hanselmann | utils.AddAuthorizedKey(auth_keys_file, public_key) |
230 | d12b9f66 | Michael Hanselmann | |
231 | d12b9f66 | Michael Hanselmann | |
232 | d12b9f66 | Michael Hanselmann | def LoadData(raw): |
233 | d12b9f66 | Michael Hanselmann | """Parses and verifies input data.
|
234 | d12b9f66 | Michael Hanselmann |
|
235 | d12b9f66 | Michael Hanselmann | @rtype: dict
|
236 | d12b9f66 | Michael Hanselmann |
|
237 | d12b9f66 | Michael Hanselmann | """
|
238 | 5d630c22 | Michael Hanselmann | return serializer.LoadAndVerifyJson(raw, _DATA_CHECK)
|
239 | d12b9f66 | Michael Hanselmann | |
240 | d12b9f66 | Michael Hanselmann | |
241 | d12b9f66 | Michael Hanselmann | def Main(): |
242 | d12b9f66 | Michael Hanselmann | """Main routine.
|
243 | d12b9f66 | Michael Hanselmann |
|
244 | d12b9f66 | Michael Hanselmann | """
|
245 | d12b9f66 | Michael Hanselmann | opts = ParseOptions() |
246 | d12b9f66 | Michael Hanselmann | |
247 | 796b5152 | Michael Hanselmann | utils.SetupToolLogging(opts.debug, opts.verbose) |
248 | d12b9f66 | Michael Hanselmann | |
249 | d12b9f66 | Michael Hanselmann | try:
|
250 | d12b9f66 | Michael Hanselmann | data = LoadData(sys.stdin.read()) |
251 | d12b9f66 | Michael Hanselmann | |
252 | d12b9f66 | Michael Hanselmann | # Check if input data is correct
|
253 | d12b9f66 | Michael Hanselmann | VerifyClusterName(data) |
254 | d12b9f66 | Michael Hanselmann | VerifyCertificate(data) |
255 | d12b9f66 | Michael Hanselmann | |
256 | d12b9f66 | Michael Hanselmann | # Update SSH files
|
257 | d12b9f66 | Michael Hanselmann | UpdateSshDaemon(data, opts.dry_run) |
258 | d12b9f66 | Michael Hanselmann | UpdateSshRoot(data, opts.dry_run) |
259 | d12b9f66 | Michael Hanselmann | |
260 | d12b9f66 | Michael Hanselmann | logging.info("Setup finished successfully")
|
261 | d12b9f66 | Michael Hanselmann | except Exception, err: # pylint: disable=W0703 |
262 | d12b9f66 | Michael Hanselmann | logging.debug("Caught unhandled exception", exc_info=True) |
263 | d12b9f66 | Michael Hanselmann | |
264 | d12b9f66 | Michael Hanselmann | (retcode, message) = cli.FormatError(err) |
265 | d12b9f66 | Michael Hanselmann | logging.error(message) |
266 | d12b9f66 | Michael Hanselmann | |
267 | d12b9f66 | Michael Hanselmann | return retcode
|
268 | d12b9f66 | Michael Hanselmann | else:
|
269 | d12b9f66 | Michael Hanselmann | return constants.EXIT_SUCCESS |