1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 """\
21 Providing mechanisms to C{X2GoControlSession*} backends for checking host validity.
22
23 """
24 __NAME__ = 'x2gocheckhosts-pylib'
25
26
27 import paramiko
28 import binascii
29
30
31 import sshproxy
32 import log
33 import x2go_exceptions
34 import random
35 import string
36
38 """\
39 Skeleton class for Python X2Go's missing host key policies.
40
41 """
42 - def __init__(self, caller=None, session_instance=None, fake_hostname=None):
43 """\
44 @param caller: calling instance
45 @type caller: C{class}
46 @param session_instance: an X2Go session instance
47 @type session_instance: L{X2GoSession} instance
48
49 """
50 self.caller = caller
51 self.session_instance = session_instance
52 self.fake_hostname = fake_hostname
53
55 """\
56 Retrieve the Paramiko SSH/Client.
57
58 @return: the associated X2Go control session instance.
59 @rtype: C{X2GoControlSession*} instance
60
61 """
62 return self.client
63
65 """\
66 Retrieve the server hostname:port expression of the server to be validated.
67
68 @return: hostname:port
69 @rtype: C{str}
70
71 """
72 return self.fake_hostname or self.hostname
73
75 """\
76 Retrieve the server hostname string of the server to be validated.
77
78 @return: hostname
79 @rtype: C{str}
80
81 """
82 if ":" in self.get_hostname():
83 return self.get_hostname().split(':')[0].lstrip('[').rstrip(']')
84 else:
85 return self.get_hostname().lstrip('[').rstrip(']')
86
88 """\
89 Retrieve the server port of the server to be validated.
90
91 @return: port
92 @rtype: C{str}
93
94 """
95 if ":" in self.get_hostname():
96 return int(self.get_hostname().split(':')[1])
97 else:
98 return 22
99
101 """\
102 Retrieve the host key of the server to be validated.
103
104 @return: host key
105 @rtype: Paramiko/SSH key instance
106
107 """
108 return self.key
109
111 """\
112 Retrieve the host key name of the server to be validated.
113
114 @return: host key name (RSA, DSA, ECDSA...)
115 @rtype: C{str}
116
117 """
118 return self.key.get_name().upper()
119
121 """\
122 Retrieve the host key fingerprint of the server to be validated.
123
124 @return: host key fingerprint
125 @rtype: C{str}
126
127 """
128 return binascii.hexlify(self.key.get_fingerprint())
129
131 """\
132 Retrieve the (colonized) host key fingerprint of the server
133 to be validated.
134
135 @return: host key fingerprint (with colons)
136 @rtype: C{str}
137
138 """
139 _fingerprint = self.get_key_fingerprint()
140 _colon_fingerprint = ''
141 idx = 0
142 for char in _fingerprint:
143 idx += 1
144 _colon_fingerprint += char
145 if idx % 2 == 0:
146 _colon_fingerprint += ':'
147 return _colon_fingerprint.rstrip(':')
148
149
151
153 self.client = client
154 self.hostname = hostname
155 self.key = key
156 if self.session_instance and self.session_instance.control_session.unique_hostkey_aliases:
157 self.client._host_keys.add(self.session_instance.get_profile_id(), self.key.get_name(), self.key)
158 else:
159 self.client._host_keys.add(self.get_hostname(), self.key.get_name(), self.key)
160 if self.client._host_keys_filename is not None:
161 self.client.save_host_keys(self.client._host_keys_filename)
162 self.client._log(paramiko.common.DEBUG, 'Adding %s host key for %s: %s' %
163 (self.key.get_name(), self.get_hostname(), binascii.hexlify(self.key.get_fingerprint())))
164
165
167 """\
168 Policy for making host key information available to Python X2Go after a
169 Paramiko/SSH connect has been attempted. This class needs information
170 about the associated L{X2GoSession} instance.
171
172 Once called, the L{missing_host_key} method of this class will try to call
173 L{X2GoSession.HOOK_check_host_dialog()}. This hook method---if not re-defined
174 in your application---will then try to call the L{X2GoClient.HOOK_check_host_dialog()},
175 which then will return C{True} by default if not customized in your application.
176
177 To accept host key checks, make sure to either customize the
178 L{X2GoClient.HOOK_check_host_dialog()} method or the L{X2GoSession.HOOK_check_host_dialog()}
179 method and hook some interactive user dialog to either of them.
180
181 """
183 """\
184 Handle a missing host key situation. This method calls
185
186 Once called, the L{missing_host_key} method will try to call
187 L{X2GoSession.HOOK_check_host_dialog()}. This hook method---if not re-defined
188 in your application---will then try to call the L{X2GoClient.HOOK_check_host_dialog()},
189 which then will return C{True} by default if not customized in your application.
190
191 To accept host key checks, make sure to either customize the
192 L{X2GoClient.HOOK_check_host_dialog()} method or the L{X2GoSession.HOOK_check_host_dialog()}
193 method and hook some interactive user dialog to either of them.
194
195 @param client: SSH client (C{X2GoControlSession*}) instance
196 @type client: C{X2GoControlSession*} instance
197 @param hostname: remote hostname
198 @type hostname: C{str}
199 @param key: host key to validate
200 @type key: Paramiko/SSH key instance
201
202 @raise X2GoHostKeyException: if the X2Go server host key is not in the C{known_hosts} file
203 @raise X2GoSSHProxyHostKeyException: if the SSH proxy host key is not in the C{known_hosts} file
204 @raise SSHException: if this instance does not know its {self.session_instance}
205
206 """
207 self.client = client
208 self.hostname = hostname
209 if (self.hostname.find(']') == -1) and (self.hostname.find(':') == -1):
210
211 self.hostname = '[%s]:22' % self.hostname
212 self.key = key
213 self.client._log(paramiko.common.DEBUG, 'Interactively Checking %s host key for %s: %s' %
214 (self.key.get_name(), self.get_hostname(), binascii.hexlify(self.key.get_fingerprint())))
215 if self.session_instance:
216
217 if self.fake_hostname is not None:
218 server_key = client.get_transport().get_remote_server_key()
219 keytype = server_key.get_name()
220 our_server_key = client._system_host_keys.get(self.fake_hostname, {}).get(keytype, None)
221 if our_server_key is None:
222 if self.session_instance.control_session.unique_hostkey_aliases:
223 our_server_key = client._host_keys.get(self.session_instance.get_profile_id(), {}).get(keytype, None)
224 if our_server_key is not None:
225 self.session_instance.logger('SSH host key verification for SSH-proxied host %s with %s fingerprint ,,%s\'\' succeeded. This host is known by the X2Go session profile ID of profile ,,%s\'\'.' % (self.fake_hostname, self.get_key_name(), self.get_key_fingerprint_with_colons(), self.session_instance.profile_name), loglevel=log.loglevel_NOTICE)
226 return
227 else:
228 our_server_key = client._host_keys.get(self.fake_hostname, {}).get(keytype, None)
229 if our_server_key is not None:
230 self.session_instance.logger('SSH host key verification for SSH-proxied host %s with %s fingerprint ,,%s\'\' succeeded. This host is known by the address it has behind the SSH proxy host.' % (self.fake_hostname, self.get_key_name(), self.get_key_fingerprint_with_colons()), loglevel=log.loglevel_NOTICE)
231 return
232
233 self.session_instance.logger('SSH host key verification for host %s with %s fingerprint ,,%s\'\' initiated. We are seeing this X2Go server for the first time.' % (self.get_hostname(), self.get_key_name(), self.get_key_fingerprint_with_colons()), loglevel=log.loglevel_NOTICE)
234 _valid = self.session_instance.HOOK_check_host_dialog(self.get_hostname_name(),
235 port=self.get_hostname_port(),
236 fingerprint=self.get_key_fingerprint_with_colons(),
237 fingerprint_type=self.get_key_name(),
238 )
239 if _valid:
240 if self.session_instance.control_session.unique_hostkey_aliases and type(self.caller) not in (sshproxy.X2GoSSHProxy, ):
241 paramiko.AutoAddPolicy().missing_host_key(client, self.session_instance.get_profile_id(), key)
242 else:
243 paramiko.AutoAddPolicy().missing_host_key(client, self.get_hostname(), key)
244
245 else:
246 if type(self.caller) in (sshproxy.X2GoSSHProxy, ):
247 raise x2go_exceptions.X2GoSSHProxyHostKeyException('Invalid host %s is not authorized for access. Add the host to Paramiko/SSH\'s known_hosts file.' % self.get_hostname())
248 else:
249 raise x2go_exceptions.X2GoHostKeyException('Invalid host %s is not authorized for access. Add the host to Paramiko/SSH\'s known_hosts file.' % self.get_hostname())
250 else:
251 raise x2go_exceptions.SSHException('Policy has collected host key information on %s for further introspection' % self.get_hostname())
252
253
255 """\
256 Perform a Paramiko/SSH host key check by connecting to the host and
257 validating the results (i.e. by validating raised exceptions during the
258 connect process).
259
260 @param x2go_sshclient_instance: a Paramiko/SSH client instance to be used for testing host key validity.
261 @type x2go_sshclient_instance: C{X2GoControlSession*} instance
262 @param hostname: hostname of server to validate
263 @type hostname: C{str}
264 @param port: port of server to validate
265 @type port: C{int}
266
267 @return: returns a tuple with the following components (<host_ok>, <hostname>, <port>, <fingerprint>, <fingerprint_type>)
268 @rtype: C{tuple}
269
270 @raise SSHException: if an SSH exception occurred, that we did not provocate in L{X2GoInteractiveAddPolicy.missing_host_key()}
271
272 """
273 _hostname = hostname
274 _port = port
275 _fingerprint = 'NO-FINGERPRINT'
276 _fingerprint_type = 'SOME-KEY-TYPE'
277
278 _check_policy = X2GoInteractiveAddPolicy()
279 x2go_sshclient_instance.set_missing_host_key_policy(_check_policy)
280
281 host_ok = False
282 try:
283 paramiko.SSHClient.connect(x2go_sshclient_instance, hostname=hostname, port=port, username='foo', password="".join([random.choice(string.letters+string.digits) for x in range(1, 20)]))
284 except x2go_exceptions.AuthenticationException:
285 host_ok = True
286 x2go_sshclient_instance.logger('SSH host key verification for host [%s]:%s succeeded. Host is already known to the client\'s Paramiko/SSH sub-system.' % (_hostname, _port), loglevel=log.loglevel_NOTICE)
287 except x2go_exceptions.SSHException, e:
288 msg = str(e)
289 if msg.startswith('Policy has collected host key information on '):
290 _hostname = _check_policy.get_hostname().split(':')[0].lstrip('[').rstrip(']')
291 _port = _check_policy.get_hostname().split(':')[1]
292 _fingerprint = _check_policy.get_key_fingerprint_with_colons()
293 _fingerprint_type = _check_policy.get_key_name()
294 else:
295 raise(e)
296 x2go_sshclient_instance.set_missing_host_key_policy(paramiko.RejectPolicy())
297 except:
298
299 pass
300
301 return (host_ok, _hostname, _port, _fingerprint, _fingerprint_type)
302