Statistics
| Branch: | Tag: | Revision:

root / lib / ssh.py @ 8f07f831

History | View | Annotate | Download (6.3 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Module encapsulating ssh functionality.
23

24
"""
25

    
26

    
27
import os
28

    
29
from ganeti import logger
30
from ganeti import utils
31
from ganeti import errors
32
from ganeti import constants
33

    
34

    
35
KNOWN_HOSTS_OPTS = [
36
  "-oGlobalKnownHostsFile=%s" % constants.SSH_KNOWN_HOSTS_FILE,
37
  "-oUserKnownHostsFile=/dev/null",
38
  ]
39

    
40
# Note: BATCH_MODE conflicts with ASK_KEY
41
BATCH_MODE_OPTS = [
42
  "-oBatchMode=yes",
43
  "-oEscapeChar=none",
44
  "-oStrictHostKeyChecking=yes",
45
  ]
46

    
47
ASK_KEY_OPTS = [
48
  "-oEscapeChar=none",
49
  "-oHashKnownHosts=no",
50
  "-oStrictHostKeyChecking=ask",
51
  ]
52

    
53

    
54
def GetUserFiles(user, mkdir=False):
55
  """Return the paths of a user's ssh files.
56

57
  The function will return a triplet (priv_key_path, pub_key_path,
58
  auth_key_path) that are used for ssh authentication. Currently, the
59
  keys used are DSA keys, so this function will return:
60
  (~user/.ssh/id_dsa, ~user/.ssh/id_dsa.pub,
61
  ~user/.ssh/authorized_keys).
62

63
  If the optional parameter mkdir is True, the ssh directory will be
64
  created if it doesn't exist.
65

66
  Regardless of the mkdir parameters, the script will raise an error
67
  if ~user/.ssh is not a directory.
68

69
  """
70
  user_dir = utils.GetHomeDir(user)
71
  if not user_dir:
72
    raise errors.OpExecError("Cannot resolve home of user %s" % user)
73

    
74
  ssh_dir = os.path.join(user_dir, ".ssh")
75
  if not os.path.lexists(ssh_dir):
76
    if mkdir:
77
      try:
78
        os.mkdir(ssh_dir, 0700)
79
      except EnvironmentError, err:
80
        raise errors.OpExecError("Can't create .ssh dir for user %s: %s" %
81
                                 (user, str(err)))
82
  elif not os.path.isdir(ssh_dir):
83
    raise errors.OpExecError("path ~%s/.ssh is not a directory" % user)
84

    
85
  return [os.path.join(ssh_dir, base)
86
          for base in ["id_dsa", "id_dsa.pub", "authorized_keys"]]
87

    
88

    
89
class SshRunner:
90
  """Wrapper for SSH commands.
91

92
  """
93
  def BuildCmd(self, hostname, user, command, batch=True, ask_key=False,
94
               tty=False):
95
    """Build an ssh command to execute a command on a remote node.
96

97
    Args:
98
      hostname: the target host, string
99
      user: user to auth as
100
      command: the command
101
      batch: if true, ssh will run in batch mode with no prompting
102
      ask_key: if true, ssh will run with StrictHostKeyChecking=ask, so that
103
               we can connect to an unknown host (not valid in batch mode)
104

105
    Returns:
106
      The ssh call to run 'command' on the remote host.
107

108
    """
109
    argv = ["ssh", "-q"]
110
    argv.extend(KNOWN_HOSTS_OPTS)
111
    if batch:
112
      # if we are in batch mode, we can't ask the key
113
      if ask_key:
114
        raise errors.ProgrammerError("SSH call requested conflicting options")
115
      argv.extend(BATCH_MODE_OPTS)
116
    elif ask_key:
117
      argv.extend(ASK_KEY_OPTS)
118
    if tty:
119
      argv.append("-t")
120
    argv.extend(["%s@%s" % (user, hostname), command])
121
    return argv
122

    
123
  def Run(self, hostname, user, command, batch=True, ask_key=False):
124
    """Runs a command on a remote node.
125

126
    This method has the same return value as `utils.RunCmd()`, which it
127
    uses to launch ssh.
128

129
    Args:
130
      hostname: the target host, string
131
      user: user to auth as
132
      command: the command
133
      batch: if true, ssh will run in batch mode with no prompting
134
      ask_key: if true, ssh will run with StrictHostKeyChecking=ask, so that
135
               we can connect to an unknown host (not valid in batch mode)
136

137
    Returns:
138
      `utils.RunResult` like `utils.RunCmd()`
139

140
    """
141
    return utils.RunCmd(self.BuildCmd(hostname, user, command, batch=batch,
142
                                      ask_key=ask_key))
143

    
144
  def CopyFileToNode(self, node, filename):
145
    """Copy a file to another node with scp.
146

147
    Args:
148
      node: node in the cluster
149
      filename: absolute pathname of a local file
150

151
    Returns:
152
      success: True/False
153

154
    """
155
    if not os.path.isfile(filename):
156
      logger.Error("file %s does not exist" % (filename))
157
      return False
158

    
159
    if not os.path.isabs(filename):
160
      logger.Error("file %s must be an absolute path" % (filename))
161
      return False
162

    
163
    command = ["scp", "-q", "-p"]
164
    command.extend(KNOWN_HOSTS_OPTS)
165
    command.extend(BATCH_MODE_OPTS)
166
    command.append(filename)
167
    command.append("%s:%s" % (node, filename))
168

    
169
    result = utils.RunCmd(command)
170

    
171
    if result.failed:
172
      logger.Error("copy to node %s failed (%s) error %s,"
173
                   " command was %s" %
174
                   (node, result.fail_reason, result.output, result.cmd))
175

    
176
    return not result.failed
177

    
178
  def VerifyNodeHostname(self, node):
179
    """Verify hostname consistency via SSH.
180

181
    This functions connects via ssh to a node and compares the hostname
182
    reported by the node to the name with have (the one that we
183
    connected to).
184

185
    This is used to detect problems in ssh known_hosts files
186
    (conflicting known hosts) and incosistencies between dns/hosts
187
    entries and local machine names
188

189
    Args:
190
      node: nodename of a host to check. can be short or full qualified hostname
191

192
    Returns:
193
      (success, detail)
194
      where
195
        success: True/False
196
        detail: String with details
197

198
    """
199
    retval = self.Run(node, 'root', 'hostname')
200

    
201
    if retval.failed:
202
      msg = "ssh problem"
203
      output = retval.output
204
      if output:
205
        msg += ": %s" % output
206
      return False, msg
207

    
208
    remotehostname = retval.stdout.strip()
209

    
210
    if not remotehostname or remotehostname != node:
211
      return False, "hostname mismatch, got %s" % remotehostname
212

    
213
    return True, "host matches"
214

    
215

    
216
def WriteKnownHostsFile(cfg, sstore, file_name):
217
  """Writes the cluster-wide equally known_hosts file.
218

219
  """
220
  utils.WriteFile(file_name, mode=0700,
221
                  data="%s ssh-rsa %s\n" % (sstore.GetClusterName(),
222
                                            cfg.GetHostKey()))