Statistics
| Branch: | Tag: | Revision:

root / tools / setup-ssh @ 052783ff

History | View | Annotate | Download (14.6 kB)

1 05cd934d René Nussbaumer
#!/usr/bin/python
2 05cd934d René Nussbaumer
#
3 05cd934d René Nussbaumer
4 f1bebf4c Iustin Pop
# Copyright (C) 2010, 2012 Google Inc.
5 05cd934d René Nussbaumer
#
6 05cd934d René Nussbaumer
# This program is free software; you can redistribute it and/or modify
7 05cd934d René Nussbaumer
# it under the terms of the GNU General Public License as published by
8 05cd934d René Nussbaumer
# the Free Software Foundation; either version 2 of the License, or
9 05cd934d René Nussbaumer
# (at your option) any later version.
10 05cd934d René Nussbaumer
#
11 05cd934d René Nussbaumer
# This program is distributed in the hope that it will be useful, but
12 05cd934d René Nussbaumer
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 05cd934d René Nussbaumer
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 05cd934d René Nussbaumer
# General Public License for more details.
15 05cd934d René Nussbaumer
#
16 05cd934d René Nussbaumer
# You should have received a copy of the GNU General Public License
17 05cd934d René Nussbaumer
# along with this program; if not, write to the Free Software
18 05cd934d René Nussbaumer
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 05cd934d René Nussbaumer
# 02110-1301, USA.
20 05cd934d René Nussbaumer
21 05cd934d René Nussbaumer
"""Tool to setup the SSH configuration on a remote node.
22 05cd934d René Nussbaumer
23 05cd934d René Nussbaumer
This is needed before we can join the node into the cluster.
24 05cd934d René Nussbaumer
25 05cd934d René Nussbaumer
"""
26 05cd934d René Nussbaumer
27 b459a848 Andrea Spadaccini
# pylint: disable=C0103
28 c9a4a662 Manuel Franceschini
# C0103: Invalid name setup-ssh
29 c9a4a662 Manuel Franceschini
30 05cd934d René Nussbaumer
import getpass
31 05cd934d René Nussbaumer
import logging
32 05cd934d René Nussbaumer
import os.path
33 05cd934d René Nussbaumer
import optparse
34 05cd934d René Nussbaumer
import sys
35 05cd934d René Nussbaumer
36 3ec10f0d Iustin Pop
# workaround paramiko warnings
37 3ec10f0d Iustin Pop
# FIXME: use 'with warnings.catch_warnings' once we drop Python 2.4
38 3ec10f0d Iustin Pop
import warnings
39 3ec10f0d Iustin Pop
warnings.simplefilter("ignore")
40 3ec10f0d Iustin Pop
import paramiko
41 3ec10f0d Iustin Pop
warnings.resetwarnings()
42 3ec10f0d Iustin Pop
43 05cd934d René Nussbaumer
from ganeti import cli
44 05cd934d René Nussbaumer
from ganeti import constants
45 05cd934d René Nussbaumer
from ganeti import errors
46 05cd934d René Nussbaumer
from ganeti import netutils
47 d3b18b8e René Nussbaumer
from ganeti import ssconf
48 05cd934d René Nussbaumer
from ganeti import ssh
49 05cd934d René Nussbaumer
from ganeti import utils
50 09bf5d24 Michael Hanselmann
from ganeti import pathutils
51 05cd934d René Nussbaumer
52 05cd934d René Nussbaumer
53 05cd934d René Nussbaumer
class RemoteCommandError(errors.GenericError):
54 05cd934d René Nussbaumer
  """Exception if remote command was not successful.
55 05cd934d René Nussbaumer
56 05cd934d René Nussbaumer
  """
57 05cd934d René Nussbaumer
58 05cd934d René Nussbaumer
59 d3b18b8e René Nussbaumer
class JoinCheckError(errors.GenericError):
60 d3b18b8e René Nussbaumer
  """Exception raised if join check fails.
61 d3b18b8e René Nussbaumer
62 d3b18b8e René Nussbaumer
  """
63 d3b18b8e René Nussbaumer
64 d3b18b8e René Nussbaumer
65 c4da9eaf René Nussbaumer
class HostKeyVerificationError(errors.GenericError):
66 c4da9eaf René Nussbaumer
  """Exception if host key do not match.
67 c4da9eaf René Nussbaumer
68 c4da9eaf René Nussbaumer
  """
69 c4da9eaf René Nussbaumer
70 c4da9eaf René Nussbaumer
71 c4da9eaf René Nussbaumer
class AuthError(errors.GenericError):
72 c4da9eaf René Nussbaumer
  """Exception for authentication errors to hosts.
73 c4da9eaf René Nussbaumer
74 c4da9eaf René Nussbaumer
  """
75 c4da9eaf René Nussbaumer
76 c4da9eaf René Nussbaumer
77 d3b18b8e René Nussbaumer
def _CheckJoin(transport):
78 d3b18b8e René Nussbaumer
  """Checks if a join is safe or dangerous.
79 d3b18b8e René Nussbaumer
80 d3b18b8e René Nussbaumer
  Note: This function relies on the fact, that all
81 d3b18b8e René Nussbaumer
  hosts have the same configuration at compile time of
82 d3b18b8e René Nussbaumer
  Ganeti. So that the constants do not mismatch.
83 d3b18b8e René Nussbaumer
84 d3b18b8e René Nussbaumer
  @param transport: The paramiko transport instance
85 d3b18b8e René Nussbaumer
  @return: True if the join is safe; False otherwise
86 d3b18b8e René Nussbaumer
87 d3b18b8e René Nussbaumer
  """
88 d3b18b8e René Nussbaumer
  sftp = transport.open_sftp_client()
89 d3b18b8e René Nussbaumer
  ss = ssconf.SimpleStore()
90 d3b18b8e René Nussbaumer
  ss_cluster_name_path = ss.KeyToFilename(constants.SS_CLUSTER_NAME)
91 d3b18b8e René Nussbaumer
92 c4da9eaf René Nussbaumer
  cluster_files = [
93 09bf5d24 Michael Hanselmann
    (pathutils.NODED_CERT_FILE, utils.ReadFile(pathutils.NODED_CERT_FILE)),
94 c4da9eaf René Nussbaumer
    (ss_cluster_name_path, utils.ReadFile(ss_cluster_name_path)),
95 c4da9eaf René Nussbaumer
    ]
96 d3b18b8e René Nussbaumer
97 c4da9eaf René Nussbaumer
  for (filename, local_content) in cluster_files:
98 c4da9eaf René Nussbaumer
    try:
99 c4da9eaf René Nussbaumer
      remote_content = _ReadSftpFile(sftp, filename)
100 c4da9eaf René Nussbaumer
    except IOError, err:
101 c4da9eaf René Nussbaumer
      # Assume file does not exist. Paramiko's error reporting is lacking.
102 c4da9eaf René Nussbaumer
      logging.debug("Failed to read %s: %s", filename, err)
103 c4da9eaf René Nussbaumer
      continue
104 d3b18b8e René Nussbaumer
105 c4da9eaf René Nussbaumer
    if remote_content != local_content:
106 c4da9eaf René Nussbaumer
      logging.error("File %s doesn't match local version", filename)
107 c4da9eaf René Nussbaumer
      return False
108 d3b18b8e René Nussbaumer
109 c4da9eaf René Nussbaumer
  return True
110 d3b18b8e René Nussbaumer
111 d3b18b8e René Nussbaumer
112 05cd934d René Nussbaumer
def _RunRemoteCommand(transport, command):
113 05cd934d René Nussbaumer
  """Invokes and wait for the command over SSH.
114 05cd934d René Nussbaumer
115 05cd934d René Nussbaumer
  @param transport: The paramiko transport instance
116 05cd934d René Nussbaumer
  @param command: The command to be executed
117 05cd934d René Nussbaumer
118 05cd934d René Nussbaumer
  """
119 05cd934d René Nussbaumer
  chan = transport.open_session()
120 05cd934d René Nussbaumer
  chan.set_combine_stderr(True)
121 05cd934d René Nussbaumer
  output_handler = chan.makefile("r")
122 05cd934d René Nussbaumer
  chan.exec_command(command)
123 05cd934d René Nussbaumer
124 05cd934d René Nussbaumer
  result = chan.recv_exit_status()
125 05cd934d René Nussbaumer
  msg = output_handler.read()
126 05cd934d René Nussbaumer
127 05cd934d René Nussbaumer
  out_msg = "'%s' exited with status code %s, output %r" % (command, result,
128 05cd934d René Nussbaumer
                                                            msg)
129 05cd934d René Nussbaumer
130 05cd934d René Nussbaumer
  # If result is -1 (no exit status provided) we assume it was not successful
131 05cd934d René Nussbaumer
  if result:
132 05cd934d René Nussbaumer
    raise RemoteCommandError(out_msg)
133 05cd934d René Nussbaumer
134 05cd934d René Nussbaumer
  if msg:
135 05cd934d René Nussbaumer
    logging.info(out_msg)
136 05cd934d René Nussbaumer
137 05cd934d René Nussbaumer
138 05cd934d René Nussbaumer
def _InvokeDaemonUtil(transport, command):
139 05cd934d René Nussbaumer
  """Invokes daemon-util on the remote side.
140 05cd934d René Nussbaumer
141 05cd934d René Nussbaumer
  @param transport: The paramiko transport instance
142 05cd934d René Nussbaumer
  @param command: The daemon-util command to be run
143 05cd934d René Nussbaumer
144 05cd934d René Nussbaumer
  """
145 09bf5d24 Michael Hanselmann
  _RunRemoteCommand(transport, "%s %s" % (pathutils.DAEMON_UTIL, command))
146 05cd934d René Nussbaumer
147 05cd934d René Nussbaumer
148 d3b18b8e René Nussbaumer
def _ReadSftpFile(sftp, filename):
149 d3b18b8e René Nussbaumer
  """Reads a file over sftp.
150 d3b18b8e René Nussbaumer
151 d3b18b8e René Nussbaumer
  @param sftp: An open paramiko SFTP client
152 d3b18b8e René Nussbaumer
  @param filename: The filename of the file to read
153 d3b18b8e René Nussbaumer
  @return: The content of the file
154 d3b18b8e René Nussbaumer
155 d3b18b8e René Nussbaumer
  """
156 d3b18b8e René Nussbaumer
  remote_file = sftp.open(filename, "r")
157 d3b18b8e René Nussbaumer
  try:
158 d3b18b8e René Nussbaumer
    return remote_file.read()
159 d3b18b8e René Nussbaumer
  finally:
160 d3b18b8e René Nussbaumer
    remote_file.close()
161 d3b18b8e René Nussbaumer
162 d3b18b8e René Nussbaumer
163 05cd934d René Nussbaumer
def _WriteSftpFile(sftp, name, perm, data):
164 05cd934d René Nussbaumer
  """SFTPs data to a remote file.
165 05cd934d René Nussbaumer
166 05cd934d René Nussbaumer
  @param sftp: A open paramiko SFTP client
167 05cd934d René Nussbaumer
  @param name: The remote file name
168 05cd934d René Nussbaumer
  @param perm: The remote file permission
169 05cd934d René Nussbaumer
  @param data: The data to write
170 05cd934d René Nussbaumer
171 05cd934d René Nussbaumer
  """
172 05cd934d René Nussbaumer
  remote_file = sftp.open(name, "w")
173 05cd934d René Nussbaumer
  try:
174 05cd934d René Nussbaumer
    sftp.chmod(name, perm)
175 05cd934d René Nussbaumer
    remote_file.write(data)
176 05cd934d René Nussbaumer
  finally:
177 05cd934d René Nussbaumer
    remote_file.close()
178 05cd934d René Nussbaumer
179 05cd934d René Nussbaumer
180 05cd934d René Nussbaumer
def SetupSSH(transport):
181 05cd934d René Nussbaumer
  """Sets the SSH up on the other side.
182 05cd934d René Nussbaumer
183 05cd934d René Nussbaumer
  @param transport: The paramiko transport instance
184 05cd934d René Nussbaumer
185 05cd934d René Nussbaumer
  """
186 052783ff Michael Hanselmann
  priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.SSH_LOGIN_USER)
187 05cd934d René Nussbaumer
  keyfiles = [
188 09bf5d24 Michael Hanselmann
    (pathutils.SSH_HOST_DSA_PRIV, 0600),
189 09bf5d24 Michael Hanselmann
    (pathutils.SSH_HOST_DSA_PUB, 0644),
190 09bf5d24 Michael Hanselmann
    (pathutils.SSH_HOST_RSA_PRIV, 0600),
191 09bf5d24 Michael Hanselmann
    (pathutils.SSH_HOST_RSA_PUB, 0644),
192 05cd934d René Nussbaumer
    (priv_key, 0600),
193 05cd934d René Nussbaumer
    (pub_key, 0644),
194 05cd934d René Nussbaumer
    ]
195 05cd934d René Nussbaumer
196 05cd934d René Nussbaumer
  sftp = transport.open_sftp_client()
197 05cd934d René Nussbaumer
198 05cd934d René Nussbaumer
  filemap = dict((name, (utils.ReadFile(name), perm))
199 05cd934d René Nussbaumer
                 for (name, perm) in keyfiles)
200 05cd934d René Nussbaumer
201 05cd934d René Nussbaumer
  auth_path = os.path.dirname(auth_keys)
202 05cd934d René Nussbaumer
203 05cd934d René Nussbaumer
  try:
204 05cd934d René Nussbaumer
    sftp.mkdir(auth_path, 0700)
205 c4da9eaf René Nussbaumer
  except IOError, err:
206 05cd934d René Nussbaumer
    # Sadly paramiko doesn't provide errno or similiar
207 05cd934d René Nussbaumer
    # so we can just assume that the path already exists
208 c4da9eaf René Nussbaumer
    logging.info("Assuming directory %s on remote node exists: %s",
209 c4da9eaf René Nussbaumer
                 auth_path, err)
210 05cd934d René Nussbaumer
211 05cd934d René Nussbaumer
  for name, (data, perm) in filemap.iteritems():
212 05cd934d René Nussbaumer
    _WriteSftpFile(sftp, name, perm, data)
213 05cd934d René Nussbaumer
214 05cd934d René Nussbaumer
  authorized_keys = sftp.open(auth_keys, "a+")
215 05cd934d René Nussbaumer
  try:
216 634a9a35 Iustin Pop
    # Due to the way SFTPFile and BufferedFile are implemented,
217 634a9a35 Iustin Pop
    # opening in a+ mode and then issuing a read(), readline() or
218 634a9a35 Iustin Pop
    # iterating over the file (which uses read() internally) will see
219 634a9a35 Iustin Pop
    # an empty file, since the paramiko internal file position and the
220 634a9a35 Iustin Pop
    # OS-level file-position are desynchronized; therefore, we issue
221 634a9a35 Iustin Pop
    # an explicit seek to resynchronize these; writes should (note
222 634a9a35 Iustin Pop
    # should) still go to the right place
223 634a9a35 Iustin Pop
    authorized_keys.seek(0, 0)
224 05cd934d René Nussbaumer
    # We don't have to close, as the close happened already in AddAuthorizedKey
225 05cd934d René Nussbaumer
    utils.AddAuthorizedKey(authorized_keys, filemap[pub_key][0])
226 05cd934d René Nussbaumer
  finally:
227 05cd934d René Nussbaumer
    authorized_keys.close()
228 05cd934d René Nussbaumer
229 05cd934d René Nussbaumer
  _InvokeDaemonUtil(transport, "reload-ssh-keys")
230 05cd934d René Nussbaumer
231 05cd934d René Nussbaumer
232 05cd934d René Nussbaumer
def ParseOptions():
233 05cd934d René Nussbaumer
  """Parses options passed to program.
234 05cd934d René Nussbaumer
235 05cd934d René Nussbaumer
  """
236 05cd934d René Nussbaumer
  program = os.path.basename(sys.argv[0])
237 052783ff Michael Hanselmann
  (default_key, _, _) = ssh.GetUserFiles(constants.SSH_LOGIN_USER)
238 05cd934d René Nussbaumer
239 d3b18b8e René Nussbaumer
  parser = optparse.OptionParser(usage=("%prog [--debug|--verbose] [--force]"
240 d3b18b8e René Nussbaumer
                                        " <node> <node...>"), prog=program)
241 05cd934d René Nussbaumer
  parser.add_option(cli.DEBUG_OPT)
242 05cd934d René Nussbaumer
  parser.add_option(cli.VERBOSE_OPT)
243 310a8944 René Nussbaumer
  parser.add_option(cli.NOSSH_KEYCHECK_OPT)
244 7a6a27af Iustin Pop
  parser.add_option(optparse.Option("-f", dest="private_key",
245 7a6a27af Iustin Pop
                                    default=default_key,
246 7a6a27af Iustin Pop
                                    help="The private key to (try to) use for"
247 7a6a27af Iustin Pop
                                    "authentication "))
248 7a6a27af Iustin Pop
  parser.add_option(optparse.Option("--key-type", dest="key_type",
249 7a6a27af Iustin Pop
                                    choices=("rsa", "dsa"), default="dsa",
250 7a6a27af Iustin Pop
                                    help="The private key type (rsa or dsa)"))
251 d3b18b8e René Nussbaumer
  parser.add_option(optparse.Option("-j", "--force-join", dest="force_join",
252 d3b18b8e René Nussbaumer
                                    action="store_true", default=False,
253 d3b18b8e René Nussbaumer
                                    help="Force the join of the host"))
254 05cd934d René Nussbaumer
255 05cd934d René Nussbaumer
  (options, args) = parser.parse_args()
256 05cd934d René Nussbaumer
257 f1bebf4c Iustin Pop
  if not args:
258 f1bebf4c Iustin Pop
    parser.print_help()
259 f1bebf4c Iustin Pop
    sys.exit(constants.EXIT_FAILURE)
260 f1bebf4c Iustin Pop
261 05cd934d René Nussbaumer
  return (options, args)
262 05cd934d René Nussbaumer
263 05cd934d René Nussbaumer
264 05cd934d René Nussbaumer
def SetupLogging(options):
265 05cd934d René Nussbaumer
  """Sets up the logging.
266 05cd934d René Nussbaumer
267 05cd934d René Nussbaumer
  @param options: Parsed options
268 05cd934d René Nussbaumer
269 05cd934d René Nussbaumer
  """
270 05cd934d René Nussbaumer
  fmt = "%(asctime)s: %(threadName)s "
271 05cd934d René Nussbaumer
  if options.debug or options.verbose:
272 05cd934d René Nussbaumer
    fmt += "%(levelname)s "
273 05cd934d René Nussbaumer
  fmt += "%(message)s"
274 05cd934d René Nussbaumer
275 05cd934d René Nussbaumer
  formatter = logging.Formatter(fmt)
276 05cd934d René Nussbaumer
277 09bf5d24 Michael Hanselmann
  file_handler = logging.FileHandler(pathutils.LOG_SETUP_SSH)
278 05cd934d René Nussbaumer
  stderr_handler = logging.StreamHandler()
279 05cd934d René Nussbaumer
  stderr_handler.setFormatter(formatter)
280 05cd934d René Nussbaumer
  file_handler.setFormatter(formatter)
281 5c654e95 Iustin Pop
  file_handler.setLevel(logging.INFO)
282 05cd934d René Nussbaumer
283 05cd934d René Nussbaumer
  if options.debug:
284 5c654e95 Iustin Pop
    stderr_handler.setLevel(logging.DEBUG)
285 05cd934d René Nussbaumer
  elif options.verbose:
286 05cd934d René Nussbaumer
    stderr_handler.setLevel(logging.INFO)
287 05cd934d René Nussbaumer
  else:
288 5c654e95 Iustin Pop
    stderr_handler.setLevel(logging.WARNING)
289 05cd934d René Nussbaumer
290 05cd934d René Nussbaumer
  root_logger = logging.getLogger("")
291 3dc66ebc Iustin Pop
  root_logger.setLevel(logging.NOTSET)
292 05cd934d René Nussbaumer
  root_logger.addHandler(stderr_handler)
293 05cd934d René Nussbaumer
  root_logger.addHandler(file_handler)
294 8647a52c Iustin Pop
295 8647a52c Iustin Pop
  # This is the paramiko logger instance
296 8647a52c Iustin Pop
  paramiko_logger = logging.getLogger("paramiko")
297 05cd934d René Nussbaumer
  paramiko_logger.addHandler(file_handler)
298 5c654e95 Iustin Pop
  # We don't want to debug Paramiko, so filter anything below warning
299 5c654e95 Iustin Pop
  paramiko_logger.setLevel(logging.WARNING)
300 05cd934d René Nussbaumer
301 05cd934d René Nussbaumer
302 3dc66ebc Iustin Pop
def LoadPrivateKeys(options):
303 697a3d61 Manuel Franceschini
  """Load the list of available private keys.
304 05cd934d René Nussbaumer
305 3dc66ebc Iustin Pop
  It loads the standard ssh key from disk and then tries to connect to
306 3dc66ebc Iustin Pop
  the ssh agent too.
307 05cd934d René Nussbaumer
308 3dc66ebc Iustin Pop
  @rtype: list
309 3dc66ebc Iustin Pop
  @return: a list of C{paramiko.PKey}
310 05cd934d René Nussbaumer
311 3dc66ebc Iustin Pop
  """
312 7a6a27af Iustin Pop
  if options.key_type == "rsa":
313 7a6a27af Iustin Pop
    pkclass = paramiko.RSAKey
314 7a6a27af Iustin Pop
  elif options.key_type == "dsa":
315 7a6a27af Iustin Pop
    pkclass = paramiko.DSSKey
316 7a6a27af Iustin Pop
  else:
317 7a6a27af Iustin Pop
    logging.critical("Unknown key type %s selected (choose either rsa or dsa)",
318 7a6a27af Iustin Pop
                     options.key_type)
319 7a6a27af Iustin Pop
    sys.exit(1)
320 7a6a27af Iustin Pop
321 7a6a27af Iustin Pop
  try:
322 7a6a27af Iustin Pop
    private_key = pkclass.from_private_key_file(options.private_key)
323 7a6a27af Iustin Pop
  except (paramiko.SSHException, EnvironmentError), err:
324 7a6a27af Iustin Pop
    logging.critical("Can't load private key %s: %s", options.private_key, err)
325 7a6a27af Iustin Pop
    sys.exit(1)
326 7a6a27af Iustin Pop
327 3dc66ebc Iustin Pop
  try:
328 3dc66ebc Iustin Pop
    agent = paramiko.Agent()
329 3dc66ebc Iustin Pop
    agent_keys = agent.get_keys()
330 3dc66ebc Iustin Pop
  except paramiko.SSHException, err:
331 3dc66ebc Iustin Pop
    # this will only be seen when the agent is broken/uses invalid
332 3dc66ebc Iustin Pop
    # protocol; for non-existing agent, get_keys() will just return an
333 3dc66ebc Iustin Pop
    # empty tuple
334 3dc66ebc Iustin Pop
    logging.warning("Can't connect to the ssh agent: %s; skipping its use",
335 3dc66ebc Iustin Pop
                    err)
336 3dc66ebc Iustin Pop
    agent_keys = []
337 3dc66ebc Iustin Pop
338 3dc66ebc Iustin Pop
  return [private_key] + list(agent_keys)
339 3dc66ebc Iustin Pop
340 3dc66ebc Iustin Pop
341 c4da9eaf René Nussbaumer
def _FormatFingerprint(fpr):
342 c4da9eaf René Nussbaumer
  """Formats a paramiko.PKey.get_fingerprint() human readable.
343 c4da9eaf René Nussbaumer
344 c4da9eaf René Nussbaumer
  @param fpr: The fingerprint to be formatted
345 c4da9eaf René Nussbaumer
  @return: A human readable fingerprint
346 c4da9eaf René Nussbaumer
347 c4da9eaf René Nussbaumer
  """
348 c4da9eaf René Nussbaumer
  return ssh.FormatParamikoFingerprint(paramiko.util.hexify(fpr))
349 c4da9eaf René Nussbaumer
350 c4da9eaf René Nussbaumer
351 3dc66ebc Iustin Pop
def LoginViaKeys(transport, username, keys):
352 3dc66ebc Iustin Pop
  """Try to login on the given transport via a list of keys.
353 3dc66ebc Iustin Pop
354 3dc66ebc Iustin Pop
  @param transport: the transport to use
355 3dc66ebc Iustin Pop
  @param username: the username to login as
356 3dc66ebc Iustin Pop
  @type keys: list
357 3dc66ebc Iustin Pop
  @param keys: list of C{paramiko.PKey} to use for authentication
358 3dc66ebc Iustin Pop
  @rtype: boolean
359 3dc66ebc Iustin Pop
  @return: True or False depending on whether the login was
360 3dc66ebc Iustin Pop
      successfull or not
361 3dc66ebc Iustin Pop
362 3dc66ebc Iustin Pop
  """
363 3dc66ebc Iustin Pop
  for private_key in keys:
364 3dc66ebc Iustin Pop
    try:
365 3dc66ebc Iustin Pop
      transport.auth_publickey(username, private_key)
366 c4da9eaf René Nussbaumer
      fpr = _FormatFingerprint(private_key.get_fingerprint())
367 3dc66ebc Iustin Pop
      if isinstance(private_key, paramiko.AgentKey):
368 3dc66ebc Iustin Pop
        logging.debug("Authentication via the ssh-agent key %s", fpr)
369 3dc66ebc Iustin Pop
      else:
370 3dc66ebc Iustin Pop
        logging.debug("Authenticated via public key %s", fpr)
371 3dc66ebc Iustin Pop
      return True
372 3dc66ebc Iustin Pop
    except paramiko.SSHException:
373 3dc66ebc Iustin Pop
      continue
374 3dc66ebc Iustin Pop
  else:
375 3dc66ebc Iustin Pop
    # all keys exhausted
376 3dc66ebc Iustin Pop
    return False
377 3dc66ebc Iustin Pop
378 3dc66ebc Iustin Pop
379 310a8944 René Nussbaumer
def LoadKnownHosts():
380 697a3d61 Manuel Franceschini
  """Load the known hosts.
381 310a8944 René Nussbaumer
382 697a3d61 Manuel Franceschini
  @return: paramiko.util.load_host_keys dict
383 310a8944 René Nussbaumer
384 310a8944 René Nussbaumer
  """
385 052783ff Michael Hanselmann
  homedir = utils.GetHomeDir(constants.SSH_LOGIN_USER)
386 310a8944 René Nussbaumer
  known_hosts = os.path.join(homedir, ".ssh", "known_hosts")
387 310a8944 René Nussbaumer
388 310a8944 René Nussbaumer
  try:
389 310a8944 René Nussbaumer
    return paramiko.util.load_host_keys(known_hosts)
390 310a8944 René Nussbaumer
  except EnvironmentError:
391 c4da9eaf René Nussbaumer
    # We didn't find the path, silently ignore and return an empty dict
392 310a8944 René Nussbaumer
    return {}
393 310a8944 René Nussbaumer
394 310a8944 René Nussbaumer
395 c4da9eaf René Nussbaumer
def _VerifyServerKey(transport, host, host_keys):
396 c4da9eaf René Nussbaumer
  """Verify the server keys.
397 c4da9eaf René Nussbaumer
398 c4da9eaf René Nussbaumer
  @param transport: A paramiko.transport instance
399 c4da9eaf René Nussbaumer
  @param host: Name of the host we verify
400 c4da9eaf René Nussbaumer
  @param host_keys: Loaded host keys
401 c4da9eaf René Nussbaumer
  @raises HostkeyVerificationError: When the host identify couldn't be verified
402 c4da9eaf René Nussbaumer
403 c4da9eaf René Nussbaumer
  """
404 c4da9eaf René Nussbaumer
405 c4da9eaf René Nussbaumer
  server_key = transport.get_remote_server_key()
406 c4da9eaf René Nussbaumer
  keytype = server_key.get_name()
407 c4da9eaf René Nussbaumer
408 c4da9eaf René Nussbaumer
  our_server_key = host_keys.get(host, {}).get(keytype, None)
409 c4da9eaf René Nussbaumer
  if not our_server_key:
410 c4da9eaf René Nussbaumer
    hexified_key = _FormatFingerprint(server_key.get_fingerprint())
411 c4da9eaf René Nussbaumer
    msg = ("Unable to verify hostkey of host %s: %s. Do you want to accept"
412 c4da9eaf René Nussbaumer
           " it?" % (host, hexified_key))
413 c4da9eaf René Nussbaumer
414 c4da9eaf René Nussbaumer
    if cli.AskUser(msg):
415 c4da9eaf René Nussbaumer
      our_server_key = server_key
416 c4da9eaf René Nussbaumer
417 c4da9eaf René Nussbaumer
  if our_server_key != server_key:
418 c4da9eaf René Nussbaumer
    raise HostKeyVerificationError("Unable to verify host identity")
419 c4da9eaf René Nussbaumer
420 c4da9eaf René Nussbaumer
421 3dc66ebc Iustin Pop
def main():
422 3dc66ebc Iustin Pop
  """Main routine.
423 3dc66ebc Iustin Pop
424 3dc66ebc Iustin Pop
  """
425 3dc66ebc Iustin Pop
  (options, args) = ParseOptions()
426 3dc66ebc Iustin Pop
427 3dc66ebc Iustin Pop
  SetupLogging(options)
428 3dc66ebc Iustin Pop
429 3dc66ebc Iustin Pop
  all_keys = LoadPrivateKeys(options)
430 3dc66ebc Iustin Pop
431 7a6a27af Iustin Pop
  passwd = None
432 052783ff Michael Hanselmann
  username = constants.SSH_LOGIN_USER
433 7bff16bd Iustin Pop
  ssh_port = netutils.GetDaemonPort("ssh")
434 310a8944 René Nussbaumer
  host_keys = LoadKnownHosts()
435 05cd934d René Nussbaumer
436 8647a52c Iustin Pop
  # Below, we need to join() the transport objects, as otherwise the
437 8647a52c Iustin Pop
  # following happens:
438 8647a52c Iustin Pop
  # - the main thread finishes
439 8647a52c Iustin Pop
  # - the atexit functions run (in the main thread), and cause the
440 8647a52c Iustin Pop
  #   logging file to be closed
441 8647a52c Iustin Pop
  # - a tiny bit later, the transport thread is finally ending, and
442 8647a52c Iustin Pop
  #   wants to log one more message, which fails as the file is closed
443 8647a52c Iustin Pop
  #   now
444 8647a52c Iustin Pop
445 c4da9eaf René Nussbaumer
  success = True
446 c4da9eaf René Nussbaumer
447 05cd934d René Nussbaumer
  for host in args:
448 c4da9eaf René Nussbaumer
    logging.info("Configuring %s", host)
449 310a8944 René Nussbaumer
450 c4da9eaf René Nussbaumer
    transport = paramiko.Transport((host, ssh_port))
451 05cd934d René Nussbaumer
    try:
452 05cd934d René Nussbaumer
      try:
453 c4da9eaf René Nussbaumer
        transport.start_client()
454 c4da9eaf René Nussbaumer
455 c4da9eaf René Nussbaumer
        if options.ssh_key_check:
456 c4da9eaf René Nussbaumer
          _VerifyServerKey(transport, host, host_keys)
457 c4da9eaf René Nussbaumer
458 c4da9eaf René Nussbaumer
        try:
459 c4da9eaf René Nussbaumer
          if LoginViaKeys(transport, username, all_keys):
460 c4da9eaf René Nussbaumer
            logging.info("Authenticated to %s via public key", host)
461 d3b18b8e René Nussbaumer
          else:
462 c4da9eaf René Nussbaumer
            if all_keys:
463 c4da9eaf René Nussbaumer
              logging.warning("Authentication to %s via public key failed,"
464 c4da9eaf René Nussbaumer
                              " trying password", host)
465 c4da9eaf René Nussbaumer
            if passwd is None:
466 c4da9eaf René Nussbaumer
              passwd = getpass.getpass(prompt="%s password:" % username)
467 c4da9eaf René Nussbaumer
            transport.auth_password(username=username, password=passwd)
468 c4da9eaf René Nussbaumer
            logging.info("Authenticated to %s via password", host)
469 c4da9eaf René Nussbaumer
        except paramiko.SSHException, err:
470 c4da9eaf René Nussbaumer
          raise AuthError("Auth error TODO" % err)
471 c4da9eaf René Nussbaumer
472 c4da9eaf René Nussbaumer
        if not _CheckJoin(transport):
473 c4da9eaf René Nussbaumer
          if not options.force_join:
474 e389d95b René Nussbaumer
            raise JoinCheckError(("Host %s failed join check; Please verify"
475 e389d95b René Nussbaumer
                                  " that the host was not previously joined"
476 e389d95b René Nussbaumer
                                  " to another cluster and use --force-join"
477 e389d95b René Nussbaumer
                                  " to continue") % host)
478 c4da9eaf René Nussbaumer
479 c4da9eaf René Nussbaumer
          logging.warning("Host %s failed join check, forced to continue",
480 c4da9eaf René Nussbaumer
                          host)
481 c4da9eaf René Nussbaumer
482 05cd934d René Nussbaumer
        SetupSSH(transport)
483 c4da9eaf René Nussbaumer
        logging.info("%s successfully configured", host)
484 c4da9eaf René Nussbaumer
      finally:
485 c4da9eaf René Nussbaumer
        transport.close()
486 c4da9eaf René Nussbaumer
        # this is needed for compatibility with older Paramiko or Python
487 c4da9eaf René Nussbaumer
        # versions
488 c4da9eaf René Nussbaumer
        transport.join()
489 c4da9eaf René Nussbaumer
    except AuthError, err:
490 c4da9eaf René Nussbaumer
      logging.error("Authentication error: %s", err)
491 c4da9eaf René Nussbaumer
      success = False
492 c4da9eaf René Nussbaumer
      break
493 c4da9eaf René Nussbaumer
    except HostKeyVerificationError, err:
494 c4da9eaf René Nussbaumer
      logging.error("Host key verification error: %s", err)
495 c4da9eaf René Nussbaumer
      success = False
496 c4da9eaf René Nussbaumer
    except Exception, err:
497 c4da9eaf René Nussbaumer
      logging.exception("During setup of %s: %s", host, err)
498 c4da9eaf René Nussbaumer
      success = False
499 c4da9eaf René Nussbaumer
500 c4da9eaf René Nussbaumer
  if success:
501 c4da9eaf René Nussbaumer
    sys.exit(constants.EXIT_SUCCESS)
502 c4da9eaf René Nussbaumer
503 c4da9eaf René Nussbaumer
  sys.exit(constants.EXIT_FAILURE)
504 5b27346a René Nussbaumer
505 05cd934d René Nussbaumer
506 05cd934d René Nussbaumer
if __name__ == "__main__":
507 05cd934d René Nussbaumer
  main()