Statistics
| Branch: | Tag: | Revision:

root / tools / setup-ssh @ 76917d97

History | View | Annotate | Download (13.8 kB)

1 05cd934d René Nussbaumer
#!/usr/bin/python
2 05cd934d René Nussbaumer
#
3 05cd934d René Nussbaumer
4 05cd934d René Nussbaumer
# Copyright (C) 2010 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 c9a4a662 Manuel Franceschini
# pylint: disable-msg=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 paramiko
33 05cd934d René Nussbaumer
import os.path
34 05cd934d René Nussbaumer
import optparse
35 05cd934d René Nussbaumer
import sys
36 05cd934d René Nussbaumer
37 05cd934d René Nussbaumer
from ganeti import cli
38 05cd934d René Nussbaumer
from ganeti import constants
39 05cd934d René Nussbaumer
from ganeti import errors
40 05cd934d René Nussbaumer
from ganeti import netutils
41 d3b18b8e René Nussbaumer
from ganeti import ssconf
42 05cd934d René Nussbaumer
from ganeti import ssh
43 05cd934d René Nussbaumer
from ganeti import utils
44 05cd934d René Nussbaumer
45 05cd934d René Nussbaumer
46 05cd934d René Nussbaumer
class RemoteCommandError(errors.GenericError):
47 05cd934d René Nussbaumer
  """Exception if remote command was not successful.
48 05cd934d René Nussbaumer
49 05cd934d René Nussbaumer
  """
50 05cd934d René Nussbaumer
51 05cd934d René Nussbaumer
52 d3b18b8e René Nussbaumer
class JoinCheckError(errors.GenericError):
53 d3b18b8e René Nussbaumer
  """Exception raised if join check fails.
54 d3b18b8e René Nussbaumer
55 d3b18b8e René Nussbaumer
  """
56 d3b18b8e René Nussbaumer
57 d3b18b8e René Nussbaumer
58 d3b18b8e René Nussbaumer
def _CheckJoin(transport):
59 d3b18b8e René Nussbaumer
  """Checks if a join is safe or dangerous.
60 d3b18b8e René Nussbaumer
61 d3b18b8e René Nussbaumer
  Note: This function relies on the fact, that all
62 d3b18b8e René Nussbaumer
  hosts have the same configuration at compile time of
63 d3b18b8e René Nussbaumer
  Ganeti. So that the constants do not mismatch.
64 d3b18b8e René Nussbaumer
65 d3b18b8e René Nussbaumer
  @param transport: The paramiko transport instance
66 d3b18b8e René Nussbaumer
  @return: True if the join is safe; False otherwise
67 d3b18b8e René Nussbaumer
68 d3b18b8e René Nussbaumer
  """
69 d3b18b8e René Nussbaumer
  sftp = transport.open_sftp_client()
70 d3b18b8e René Nussbaumer
  ss = ssconf.SimpleStore()
71 d3b18b8e René Nussbaumer
  ss_cluster_name_path = ss.KeyToFilename(constants.SS_CLUSTER_NAME)
72 d3b18b8e René Nussbaumer
73 d3b18b8e René Nussbaumer
  cluster_files = {
74 d3b18b8e René Nussbaumer
    ss_cluster_name_path: utils.ReadFile(ss_cluster_name_path),
75 d3b18b8e René Nussbaumer
    constants.NODED_CERT_FILE: utils.ReadFile(constants.NODED_CERT_FILE),
76 d3b18b8e René Nussbaumer
    }
77 d3b18b8e René Nussbaumer
78 d3b18b8e René Nussbaumer
  try:
79 d3b18b8e René Nussbaumer
    remote_noded_file = _ReadSftpFile(sftp, constants.NODED_CERT_FILE)
80 d3b18b8e René Nussbaumer
  except IOError:
81 d3b18b8e René Nussbaumer
    # We can just assume that the file doesn't exist as such error reporting
82 d3b18b8e René Nussbaumer
    # is lacking from paramiko
83 d3b18b8e René Nussbaumer
    #
84 d3b18b8e René Nussbaumer
    # We don't have the noded certificate. As without the cert, the
85 d3b18b8e René Nussbaumer
    # noded is not running, we are on the safe bet to say that this
86 d3b18b8e René Nussbaumer
    # node doesn't belong to a cluster
87 d3b18b8e René Nussbaumer
    return True
88 d3b18b8e René Nussbaumer
89 d3b18b8e René Nussbaumer
  try:
90 d3b18b8e René Nussbaumer
    remote_cluster_name = _ReadSftpFile(sftp, ss_cluster_name_path)
91 d3b18b8e René Nussbaumer
  except IOError:
92 d3b18b8e René Nussbaumer
    # This can indicate that a previous join was not successful
93 d3b18b8e René Nussbaumer
    # So if the noded cert was found and matches we are fine
94 d3b18b8e René Nussbaumer
    return cluster_files[constants.NODED_CERT_FILE] == remote_noded_file
95 d3b18b8e René Nussbaumer
96 d3b18b8e René Nussbaumer
  return (cluster_files[constants.NODED_CERT_FILE] == remote_noded_file and
97 d3b18b8e René Nussbaumer
          cluster_files[ss_cluster_name_path] == remote_cluster_name)
98 d3b18b8e René Nussbaumer
99 d3b18b8e René Nussbaumer
100 05cd934d René Nussbaumer
def _RunRemoteCommand(transport, command):
101 05cd934d René Nussbaumer
  """Invokes and wait for the command over SSH.
102 05cd934d René Nussbaumer
103 05cd934d René Nussbaumer
  @param transport: The paramiko transport instance
104 05cd934d René Nussbaumer
  @param command: The command to be executed
105 05cd934d René Nussbaumer
106 05cd934d René Nussbaumer
  """
107 05cd934d René Nussbaumer
  chan = transport.open_session()
108 05cd934d René Nussbaumer
  chan.set_combine_stderr(True)
109 05cd934d René Nussbaumer
  output_handler = chan.makefile("r")
110 05cd934d René Nussbaumer
  chan.exec_command(command)
111 05cd934d René Nussbaumer
112 05cd934d René Nussbaumer
  result = chan.recv_exit_status()
113 05cd934d René Nussbaumer
  msg = output_handler.read()
114 05cd934d René Nussbaumer
115 05cd934d René Nussbaumer
  out_msg = "'%s' exited with status code %s, output %r" % (command, result,
116 05cd934d René Nussbaumer
                                                            msg)
117 05cd934d René Nussbaumer
118 05cd934d René Nussbaumer
  # If result is -1 (no exit status provided) we assume it was not successful
119 05cd934d René Nussbaumer
  if result:
120 05cd934d René Nussbaumer
    raise RemoteCommandError(out_msg)
121 05cd934d René Nussbaumer
122 05cd934d René Nussbaumer
  if msg:
123 05cd934d René Nussbaumer
    logging.info(out_msg)
124 05cd934d René Nussbaumer
125 05cd934d René Nussbaumer
126 05cd934d René Nussbaumer
def _InvokeDaemonUtil(transport, command):
127 05cd934d René Nussbaumer
  """Invokes daemon-util on the remote side.
128 05cd934d René Nussbaumer
129 05cd934d René Nussbaumer
  @param transport: The paramiko transport instance
130 05cd934d René Nussbaumer
  @param command: The daemon-util command to be run
131 05cd934d René Nussbaumer
132 05cd934d René Nussbaumer
  """
133 05cd934d René Nussbaumer
  _RunRemoteCommand(transport, "%s %s" % (constants.DAEMON_UTIL, command))
134 05cd934d René Nussbaumer
135 05cd934d René Nussbaumer
136 d3b18b8e René Nussbaumer
def _ReadSftpFile(sftp, filename):
137 d3b18b8e René Nussbaumer
  """Reads a file over sftp.
138 d3b18b8e René Nussbaumer
139 d3b18b8e René Nussbaumer
  @param sftp: An open paramiko SFTP client
140 d3b18b8e René Nussbaumer
  @param filename: The filename of the file to read
141 d3b18b8e René Nussbaumer
  @return: The content of the file
142 d3b18b8e René Nussbaumer
143 d3b18b8e René Nussbaumer
  """
144 d3b18b8e René Nussbaumer
  remote_file = sftp.open(filename, "r")
145 d3b18b8e René Nussbaumer
  try:
146 d3b18b8e René Nussbaumer
    return remote_file.read()
147 d3b18b8e René Nussbaumer
  finally:
148 d3b18b8e René Nussbaumer
    remote_file.close()
149 d3b18b8e René Nussbaumer
150 d3b18b8e René Nussbaumer
151 05cd934d René Nussbaumer
def _WriteSftpFile(sftp, name, perm, data):
152 05cd934d René Nussbaumer
  """SFTPs data to a remote file.
153 05cd934d René Nussbaumer
154 05cd934d René Nussbaumer
  @param sftp: A open paramiko SFTP client
155 05cd934d René Nussbaumer
  @param name: The remote file name
156 05cd934d René Nussbaumer
  @param perm: The remote file permission
157 05cd934d René Nussbaumer
  @param data: The data to write
158 05cd934d René Nussbaumer
159 05cd934d René Nussbaumer
  """
160 05cd934d René Nussbaumer
  remote_file = sftp.open(name, "w")
161 05cd934d René Nussbaumer
  try:
162 05cd934d René Nussbaumer
    sftp.chmod(name, perm)
163 05cd934d René Nussbaumer
    remote_file.write(data)
164 05cd934d René Nussbaumer
  finally:
165 05cd934d René Nussbaumer
    remote_file.close()
166 05cd934d René Nussbaumer
167 05cd934d René Nussbaumer
168 05cd934d René Nussbaumer
def SetupSSH(transport):
169 05cd934d René Nussbaumer
  """Sets the SSH up on the other side.
170 05cd934d René Nussbaumer
171 05cd934d René Nussbaumer
  @param transport: The paramiko transport instance
172 05cd934d René Nussbaumer
173 05cd934d René Nussbaumer
  """
174 05cd934d René Nussbaumer
  priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
175 05cd934d René Nussbaumer
  keyfiles = [
176 05cd934d René Nussbaumer
    (constants.SSH_HOST_DSA_PRIV, 0600),
177 05cd934d René Nussbaumer
    (constants.SSH_HOST_DSA_PUB, 0644),
178 05cd934d René Nussbaumer
    (constants.SSH_HOST_RSA_PRIV, 0600),
179 05cd934d René Nussbaumer
    (constants.SSH_HOST_RSA_PUB, 0644),
180 05cd934d René Nussbaumer
    (priv_key, 0600),
181 05cd934d René Nussbaumer
    (pub_key, 0644),
182 05cd934d René Nussbaumer
    ]
183 05cd934d René Nussbaumer
184 05cd934d René Nussbaumer
  sftp = transport.open_sftp_client()
185 05cd934d René Nussbaumer
186 05cd934d René Nussbaumer
  filemap = dict((name, (utils.ReadFile(name), perm))
187 05cd934d René Nussbaumer
                 for (name, perm) in keyfiles)
188 05cd934d René Nussbaumer
189 05cd934d René Nussbaumer
  auth_path = os.path.dirname(auth_keys)
190 05cd934d René Nussbaumer
191 05cd934d René Nussbaumer
  try:
192 05cd934d René Nussbaumer
    sftp.mkdir(auth_path, 0700)
193 05cd934d René Nussbaumer
  except IOError:
194 05cd934d René Nussbaumer
    # Sadly paramiko doesn't provide errno or similiar
195 05cd934d René Nussbaumer
    # so we can just assume that the path already exists
196 5c654e95 Iustin Pop
    logging.info("Path %s seems already to exist on remote node. Ignoring.",
197 05cd934d René Nussbaumer
                 auth_path)
198 05cd934d René Nussbaumer
199 05cd934d René Nussbaumer
  for name, (data, perm) in filemap.iteritems():
200 05cd934d René Nussbaumer
    _WriteSftpFile(sftp, name, perm, data)
201 05cd934d René Nussbaumer
202 05cd934d René Nussbaumer
  authorized_keys = sftp.open(auth_keys, "a+")
203 05cd934d René Nussbaumer
  try:
204 634a9a35 Iustin Pop
    # Due to the way SFTPFile and BufferedFile are implemented,
205 634a9a35 Iustin Pop
    # opening in a+ mode and then issuing a read(), readline() or
206 634a9a35 Iustin Pop
    # iterating over the file (which uses read() internally) will see
207 634a9a35 Iustin Pop
    # an empty file, since the paramiko internal file position and the
208 634a9a35 Iustin Pop
    # OS-level file-position are desynchronized; therefore, we issue
209 634a9a35 Iustin Pop
    # an explicit seek to resynchronize these; writes should (note
210 634a9a35 Iustin Pop
    # should) still go to the right place
211 634a9a35 Iustin Pop
    authorized_keys.seek(0, 0)
212 05cd934d René Nussbaumer
    # We don't have to close, as the close happened already in AddAuthorizedKey
213 05cd934d René Nussbaumer
    utils.AddAuthorizedKey(authorized_keys, filemap[pub_key][0])
214 05cd934d René Nussbaumer
  finally:
215 05cd934d René Nussbaumer
    authorized_keys.close()
216 05cd934d René Nussbaumer
217 05cd934d René Nussbaumer
  _InvokeDaemonUtil(transport, "reload-ssh-keys")
218 05cd934d René Nussbaumer
219 05cd934d René Nussbaumer
220 05cd934d René Nussbaumer
def ParseOptions():
221 05cd934d René Nussbaumer
  """Parses options passed to program.
222 05cd934d René Nussbaumer
223 05cd934d René Nussbaumer
  """
224 05cd934d René Nussbaumer
  program = os.path.basename(sys.argv[0])
225 05cd934d René Nussbaumer
226 d3b18b8e René Nussbaumer
  parser = optparse.OptionParser(usage=("%prog [--debug|--verbose] [--force]"
227 d3b18b8e René Nussbaumer
                                        " <node> <node...>"), prog=program)
228 05cd934d René Nussbaumer
  parser.add_option(cli.DEBUG_OPT)
229 05cd934d René Nussbaumer
  parser.add_option(cli.VERBOSE_OPT)
230 310a8944 René Nussbaumer
  parser.add_option(cli.NOSSH_KEYCHECK_OPT)
231 7a6a27af Iustin Pop
  default_key = ssh.GetUserFiles(constants.GANETI_RUNAS)[0]
232 7a6a27af Iustin Pop
  parser.add_option(optparse.Option("-f", dest="private_key",
233 7a6a27af Iustin Pop
                                    default=default_key,
234 7a6a27af Iustin Pop
                                    help="The private key to (try to) use for"
235 7a6a27af Iustin Pop
                                    "authentication "))
236 7a6a27af Iustin Pop
  parser.add_option(optparse.Option("--key-type", dest="key_type",
237 7a6a27af Iustin Pop
                                    choices=("rsa", "dsa"), default="dsa",
238 7a6a27af Iustin Pop
                                    help="The private key type (rsa or dsa)"))
239 d3b18b8e René Nussbaumer
  parser.add_option(optparse.Option("-j", "--force-join", dest="force_join",
240 d3b18b8e René Nussbaumer
                                    action="store_true", default=False,
241 d3b18b8e René Nussbaumer
                                    help="Force the join of the host"))
242 05cd934d René Nussbaumer
243 05cd934d René Nussbaumer
  (options, args) = parser.parse_args()
244 05cd934d René Nussbaumer
245 05cd934d René Nussbaumer
  return (options, args)
246 05cd934d René Nussbaumer
247 05cd934d René Nussbaumer
248 05cd934d René Nussbaumer
def SetupLogging(options):
249 05cd934d René Nussbaumer
  """Sets up the logging.
250 05cd934d René Nussbaumer
251 05cd934d René Nussbaumer
  @param options: Parsed options
252 05cd934d René Nussbaumer
253 05cd934d René Nussbaumer
  """
254 05cd934d René Nussbaumer
  fmt = "%(asctime)s: %(threadName)s "
255 05cd934d René Nussbaumer
  if options.debug or options.verbose:
256 05cd934d René Nussbaumer
    fmt += "%(levelname)s "
257 05cd934d René Nussbaumer
  fmt += "%(message)s"
258 05cd934d René Nussbaumer
259 05cd934d René Nussbaumer
  formatter = logging.Formatter(fmt)
260 05cd934d René Nussbaumer
261 05cd934d René Nussbaumer
  file_handler = logging.FileHandler(constants.LOG_SETUP_SSH)
262 05cd934d René Nussbaumer
  stderr_handler = logging.StreamHandler()
263 05cd934d René Nussbaumer
  stderr_handler.setFormatter(formatter)
264 05cd934d René Nussbaumer
  file_handler.setFormatter(formatter)
265 5c654e95 Iustin Pop
  file_handler.setLevel(logging.INFO)
266 05cd934d René Nussbaumer
267 05cd934d René Nussbaumer
  if options.debug:
268 5c654e95 Iustin Pop
    stderr_handler.setLevel(logging.DEBUG)
269 05cd934d René Nussbaumer
  elif options.verbose:
270 05cd934d René Nussbaumer
    stderr_handler.setLevel(logging.INFO)
271 05cd934d René Nussbaumer
  else:
272 5c654e95 Iustin Pop
    stderr_handler.setLevel(logging.WARNING)
273 05cd934d René Nussbaumer
274 05cd934d René Nussbaumer
  root_logger = logging.getLogger("")
275 3dc66ebc Iustin Pop
  root_logger.setLevel(logging.NOTSET)
276 05cd934d René Nussbaumer
  root_logger.addHandler(stderr_handler)
277 05cd934d René Nussbaumer
  root_logger.addHandler(file_handler)
278 8647a52c Iustin Pop
279 8647a52c Iustin Pop
  # This is the paramiko logger instance
280 8647a52c Iustin Pop
  paramiko_logger = logging.getLogger("paramiko")
281 05cd934d René Nussbaumer
  paramiko_logger.addHandler(file_handler)
282 5c654e95 Iustin Pop
  # We don't want to debug Paramiko, so filter anything below warning
283 5c654e95 Iustin Pop
  paramiko_logger.setLevel(logging.WARNING)
284 05cd934d René Nussbaumer
285 05cd934d René Nussbaumer
286 3dc66ebc Iustin Pop
def LoadPrivateKeys(options):
287 697a3d61 Manuel Franceschini
  """Load the list of available private keys.
288 05cd934d René Nussbaumer
289 3dc66ebc Iustin Pop
  It loads the standard ssh key from disk and then tries to connect to
290 3dc66ebc Iustin Pop
  the ssh agent too.
291 05cd934d René Nussbaumer
292 3dc66ebc Iustin Pop
  @rtype: list
293 3dc66ebc Iustin Pop
  @return: a list of C{paramiko.PKey}
294 05cd934d René Nussbaumer
295 3dc66ebc Iustin Pop
  """
296 7a6a27af Iustin Pop
  if options.key_type == "rsa":
297 7a6a27af Iustin Pop
    pkclass = paramiko.RSAKey
298 7a6a27af Iustin Pop
  elif options.key_type == "dsa":
299 7a6a27af Iustin Pop
    pkclass = paramiko.DSSKey
300 7a6a27af Iustin Pop
  else:
301 7a6a27af Iustin Pop
    logging.critical("Unknown key type %s selected (choose either rsa or dsa)",
302 7a6a27af Iustin Pop
                     options.key_type)
303 7a6a27af Iustin Pop
    sys.exit(1)
304 7a6a27af Iustin Pop
305 7a6a27af Iustin Pop
  try:
306 7a6a27af Iustin Pop
    private_key = pkclass.from_private_key_file(options.private_key)
307 7a6a27af Iustin Pop
  except (paramiko.SSHException, EnvironmentError), err:
308 7a6a27af Iustin Pop
    logging.critical("Can't load private key %s: %s", options.private_key, err)
309 7a6a27af Iustin Pop
    sys.exit(1)
310 7a6a27af Iustin Pop
311 3dc66ebc Iustin Pop
  try:
312 3dc66ebc Iustin Pop
    agent = paramiko.Agent()
313 3dc66ebc Iustin Pop
    agent_keys = agent.get_keys()
314 3dc66ebc Iustin Pop
  except paramiko.SSHException, err:
315 3dc66ebc Iustin Pop
    # this will only be seen when the agent is broken/uses invalid
316 3dc66ebc Iustin Pop
    # protocol; for non-existing agent, get_keys() will just return an
317 3dc66ebc Iustin Pop
    # empty tuple
318 3dc66ebc Iustin Pop
    logging.warning("Can't connect to the ssh agent: %s; skipping its use",
319 3dc66ebc Iustin Pop
                    err)
320 3dc66ebc Iustin Pop
    agent_keys = []
321 3dc66ebc Iustin Pop
322 3dc66ebc Iustin Pop
  return [private_key] + list(agent_keys)
323 3dc66ebc Iustin Pop
324 3dc66ebc Iustin Pop
325 3dc66ebc Iustin Pop
def LoginViaKeys(transport, username, keys):
326 3dc66ebc Iustin Pop
  """Try to login on the given transport via a list of keys.
327 3dc66ebc Iustin Pop
328 3dc66ebc Iustin Pop
  @param transport: the transport to use
329 3dc66ebc Iustin Pop
  @param username: the username to login as
330 3dc66ebc Iustin Pop
  @type keys: list
331 3dc66ebc Iustin Pop
  @param keys: list of C{paramiko.PKey} to use for authentication
332 3dc66ebc Iustin Pop
  @rtype: boolean
333 3dc66ebc Iustin Pop
  @return: True or False depending on whether the login was
334 3dc66ebc Iustin Pop
      successfull or not
335 3dc66ebc Iustin Pop
336 3dc66ebc Iustin Pop
  """
337 3dc66ebc Iustin Pop
  for private_key in keys:
338 3dc66ebc Iustin Pop
    try:
339 3dc66ebc Iustin Pop
      transport.auth_publickey(username, private_key)
340 3dc66ebc Iustin Pop
      fpr = ":".join("%02x" % ord(i) for i in private_key.get_fingerprint())
341 3dc66ebc Iustin Pop
      if isinstance(private_key, paramiko.AgentKey):
342 3dc66ebc Iustin Pop
        logging.debug("Authentication via the ssh-agent key %s", fpr)
343 3dc66ebc Iustin Pop
      else:
344 3dc66ebc Iustin Pop
        logging.debug("Authenticated via public key %s", fpr)
345 3dc66ebc Iustin Pop
      return True
346 3dc66ebc Iustin Pop
    except paramiko.SSHException:
347 3dc66ebc Iustin Pop
      continue
348 3dc66ebc Iustin Pop
  else:
349 3dc66ebc Iustin Pop
    # all keys exhausted
350 3dc66ebc Iustin Pop
    return False
351 3dc66ebc Iustin Pop
352 3dc66ebc Iustin Pop
353 310a8944 René Nussbaumer
def LoadKnownHosts():
354 697a3d61 Manuel Franceschini
  """Load the known hosts.
355 310a8944 René Nussbaumer
356 697a3d61 Manuel Franceschini
  @return: paramiko.util.load_host_keys dict
357 310a8944 René Nussbaumer
358 310a8944 René Nussbaumer
  """
359 310a8944 René Nussbaumer
  homedir = utils.GetHomeDir(constants.GANETI_RUNAS)
360 310a8944 René Nussbaumer
  known_hosts = os.path.join(homedir, ".ssh", "known_hosts")
361 310a8944 René Nussbaumer
362 310a8944 René Nussbaumer
  try:
363 310a8944 René Nussbaumer
    return paramiko.util.load_host_keys(known_hosts)
364 310a8944 René Nussbaumer
  except EnvironmentError:
365 310a8944 René Nussbaumer
    # We didn't found the path, silently ignore and return an empty dict
366 310a8944 René Nussbaumer
    return {}
367 310a8944 René Nussbaumer
368 310a8944 René Nussbaumer
369 3dc66ebc Iustin Pop
def main():
370 3dc66ebc Iustin Pop
  """Main routine.
371 3dc66ebc Iustin Pop
372 3dc66ebc Iustin Pop
  """
373 3dc66ebc Iustin Pop
  (options, args) = ParseOptions()
374 3dc66ebc Iustin Pop
375 3dc66ebc Iustin Pop
  SetupLogging(options)
376 3dc66ebc Iustin Pop
377 5b27346a René Nussbaumer
  errs = 0
378 5b27346a René Nussbaumer
379 3dc66ebc Iustin Pop
  all_keys = LoadPrivateKeys(options)
380 3dc66ebc Iustin Pop
381 7a6a27af Iustin Pop
  passwd = None
382 7a6a27af Iustin Pop
  username = constants.GANETI_RUNAS
383 7bff16bd Iustin Pop
  ssh_port = netutils.GetDaemonPort("ssh")
384 310a8944 René Nussbaumer
  host_keys = LoadKnownHosts()
385 05cd934d René Nussbaumer
386 8647a52c Iustin Pop
  # Below, we need to join() the transport objects, as otherwise the
387 8647a52c Iustin Pop
  # following happens:
388 8647a52c Iustin Pop
  # - the main thread finishes
389 8647a52c Iustin Pop
  # - the atexit functions run (in the main thread), and cause the
390 8647a52c Iustin Pop
  #   logging file to be closed
391 8647a52c Iustin Pop
  # - a tiny bit later, the transport thread is finally ending, and
392 8647a52c Iustin Pop
  #   wants to log one more message, which fails as the file is closed
393 8647a52c Iustin Pop
  #   now
394 8647a52c Iustin Pop
395 05cd934d René Nussbaumer
  for host in args:
396 7bff16bd Iustin Pop
    transport = paramiko.Transport((host, ssh_port))
397 7a6a27af Iustin Pop
    transport.start_client()
398 310a8944 René Nussbaumer
    server_key = transport.get_remote_server_key()
399 310a8944 René Nussbaumer
    keytype = server_key.get_name()
400 310a8944 René Nussbaumer
401 310a8944 René Nussbaumer
    our_server_key = host_keys.get(host, {}).get(keytype, None)
402 310a8944 René Nussbaumer
    if options.ssh_key_check:
403 310a8944 René Nussbaumer
      if not our_server_key:
404 310a8944 René Nussbaumer
        hexified_key = ssh.FormatParamikoFingerprint(
405 f9b87434 René Nussbaumer
            paramiko.util.hexify(server_key.get_fingerprint()))
406 310a8944 René Nussbaumer
        msg = ("Unable to verify hostkey of host %s: %s. Do you want to accept"
407 310a8944 René Nussbaumer
               " it?" % (host, hexified_key))
408 310a8944 René Nussbaumer
409 310a8944 René Nussbaumer
        if cli.AskUser(msg):
410 310a8944 René Nussbaumer
          our_server_key = server_key
411 310a8944 René Nussbaumer
412 310a8944 René Nussbaumer
      if our_server_key != server_key:
413 310a8944 René Nussbaumer
        logging.error("Unable to verify identity of host. Aborting")
414 310a8944 René Nussbaumer
        transport.close()
415 310a8944 René Nussbaumer
        transport.join()
416 310a8944 René Nussbaumer
        # TODO: Run over all hosts, fetch the keys and let them verify from the
417 310a8944 René Nussbaumer
        #       user beforehand then proceed with actual work later on
418 310a8944 René Nussbaumer
        raise paramiko.SSHException("Unable to verify identity of host")
419 310a8944 René Nussbaumer
420 8647a52c Iustin Pop
    try:
421 3dc66ebc Iustin Pop
      if LoginViaKeys(transport, username, all_keys):
422 7a6a27af Iustin Pop
        logging.info("Authenticated to %s via public key", host)
423 3dc66ebc Iustin Pop
      else:
424 7a6a27af Iustin Pop
        logging.warning("Authentication to %s via public key failed, trying"
425 7a6a27af Iustin Pop
                        " password", host)
426 7a6a27af Iustin Pop
        if passwd is None:
427 7a6a27af Iustin Pop
          passwd = getpass.getpass(prompt="%s password:" % username)
428 7a6a27af Iustin Pop
        transport.auth_password(username=username, password=passwd)
429 7a6a27af Iustin Pop
        logging.info("Authenticated to %s via password", host)
430 7a6a27af Iustin Pop
    except paramiko.SSHException, err:
431 8647a52c Iustin Pop
      logging.error("Connection or authentication failed to host %s: %s",
432 8647a52c Iustin Pop
                    host, err)
433 8647a52c Iustin Pop
      transport.close()
434 8647a52c Iustin Pop
      # this is needed for compatibility with older Paramiko or Python
435 8647a52c Iustin Pop
      # versions
436 8647a52c Iustin Pop
      transport.join()
437 8647a52c Iustin Pop
      continue
438 05cd934d René Nussbaumer
    try:
439 05cd934d René Nussbaumer
      try:
440 d3b18b8e René Nussbaumer
        if not _CheckJoin(transport):
441 d3b18b8e René Nussbaumer
          if options.force_join:
442 d3b18b8e René Nussbaumer
            logging.warning("Host %s failed join check, forced to continue",
443 d3b18b8e René Nussbaumer
                            host)
444 d3b18b8e René Nussbaumer
          else:
445 d3b18b8e René Nussbaumer
            raise JoinCheckError("Host %s failed join check" % host)
446 05cd934d René Nussbaumer
        SetupSSH(transport)
447 05cd934d René Nussbaumer
      except errors.GenericError, err:
448 5b27346a René Nussbaumer
        logging.error("While doing setup on host %s an error occurred: %s",
449 8647a52c Iustin Pop
                      host, err)
450 5b27346a René Nussbaumer
        errs += 1
451 05cd934d René Nussbaumer
    finally:
452 05cd934d René Nussbaumer
      transport.close()
453 8647a52c Iustin Pop
      # this is needed for compatibility with older Paramiko or Python
454 8647a52c Iustin Pop
      # versions
455 8647a52c Iustin Pop
      transport.join()
456 05cd934d René Nussbaumer
457 5b27346a René Nussbaumer
    if errs > 0:
458 5b27346a René Nussbaumer
      sys.exit(1)
459 5b27346a René Nussbaumer
460 05cd934d René Nussbaumer
461 05cd934d René Nussbaumer
if __name__ == "__main__":
462 05cd934d René Nussbaumer
  main()