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 return self.get_hostname().split(':')[0].lstrip('[').rstrip(']')
83
85 """\
86 Retrieve the server port of the server to be validated.
87
88 @return: port
89 @rtype: C{str}
90
91 """
92 return self.get_hostname().split(':')[1]
93
95 """\
96 Retrieve the host key of the server to be validated.
97
98 @return: host key
99 @rtype: Paramiko/SSH key instance
100
101 """
102 return self.key
103
105 """\
106 Retrieve the host key name of the server to be validated.
107
108 @return: host key name (RSA, DSA, ...)
109 @rtype: C{str}
110
111 """
112 return self.key.get_name().upper()
113
115 """\
116 Retrieve the host key fingerprint of the server to be validated.
117
118 @return: host key fingerprint
119 @rtype: C{str}
120
121 """
122 return binascii.hexlify(self.key.get_fingerprint())
123
125 """\
126 Retrieve the (colonized) host key fingerprint of the server
127 to be validated.
128
129 @return: host key fingerprint (with colons)
130 @rtype: C{str}
131
132 """
133 _fingerprint = self.get_key_fingerprint()
134 _colon_fingerprint = ''
135 idx = 0
136 for char in _fingerprint:
137 idx += 1
138 _colon_fingerprint += char
139 if idx % 2 == 0:
140 _colon_fingerprint += ':'
141 return _colon_fingerprint.rstrip(':')
142
143
145
147 self.client = client
148 self.hostname = hostname
149 self.key = key
150 if self.session_instance and self.session_instance.control_session.unique_hostkey_aliases:
151 self.client._host_keys.add(self.session_instance.get_profile_id(), self.key.get_name(), self.key)
152 else:
153 self.client._host_keys.add(self.get_hostname(), self.key.get_name(), self.key)
154 if self.client._host_keys_filename is not None:
155 self.client.save_host_keys(self.client._host_keys_filename)
156 self.client._log(paramiko.common.DEBUG, 'Adding %s host key for %s: %s' %
157 (self.key.get_name(), self.get_hostname(), binascii.hexlify(self.key.get_fingerprint())))
158
159
161 """\
162 Policy for making host key information available to Python X2Go after a
163 Paramiko/SSH connect has been attempted. This class needs information
164 about the associated L{X2GoSession} instance.
165
166 Once called, the L{missing_host_key} method of this class will try to call
167 L{X2GoSession.HOOK_check_host_dialog()}. This hook method---if not re-defined
168 in your application---will then try to call the L{X2GoClient.HOOK_check_host_dialog()},
169 which then will return C{True} by default if not customized in your application.
170
171 To accept host key checks, make sure to either customize the
172 L{X2GoClient.HOOK_check_host_dialog()} method or the L{X2GoSession.HOOK_check_host_dialog()}
173 method and hook some interactive user dialog to either of them.
174
175 """
177 """\
178 Handle a missing host key situation. This method calls
179
180 Once called, the L{missing_host_key} method will try to call
181 L{X2GoSession.HOOK_check_host_dialog()}. This hook method---if not re-defined
182 in your application---will then try to call the L{X2GoClient.HOOK_check_host_dialog()},
183 which then will return C{True} by default if not customized in your application.
184
185 To accept host key checks, make sure to either customize the
186 L{X2GoClient.HOOK_check_host_dialog()} method or the L{X2GoSession.HOOK_check_host_dialog()}
187 method and hook some interactive user dialog to either of them.
188
189 @param client: SSH client (C{X2GoControlSession*}) instance
190 @type client: C{X2GoControlSession*} instance
191 @param hostname: remote hostname
192 @type hostname: C{str}
193 @param key: host key to validate
194 @type key: Paramiko/SSH key instance
195
196 @raise X2GoHostKeyException: if the X2Go server host key is not in the C{known_hosts} file
197 @raise X2GoSSHProxyHostKeyException: if the SSH proxy host key is not in the C{known_hosts} file
198 @raise SSHException: if this instance does not know its {self.session_instance}
199
200 """
201 self.client = client
202 self.hostname = hostname
203 if (self.hostname.find(']') == -1) and (self.hostname.find(':') == -1):
204
205 self.hostname = '[%s]:22' % self.hostname
206 self.key = key
207 self.client._log(paramiko.common.DEBUG, 'Interactively Checking %s host key for %s: %s' %
208 (self.key.get_name(), self.get_hostname(), binascii.hexlify(self.key.get_fingerprint())))
209 if self.session_instance:
210
211 if self.fake_hostname is not None:
212 server_key = client.get_transport().get_remote_server_key()
213 keytype = server_key.get_name()
214 our_server_key = client._system_host_keys.get(self.fake_hostname, {}).get(keytype, None)
215 if our_server_key is None:
216 if self.session_instance.control_session.unique_hostkey_aliases:
217 our_server_key = client._host_keys.get(self.session_instance.get_profile_id(), {}).get(keytype, None)
218 if our_server_key is not None:
219 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)
220 return
221 else:
222 our_server_key = client._host_keys.get(self.fake_hostname, {}).get(keytype, None)
223 if our_server_key is not None:
224 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)
225 return
226
227 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)
228 _valid = self.session_instance.HOOK_check_host_dialog(self.get_hostname_name(),
229 port=self.get_hostname_port(),
230 fingerprint=self.get_key_fingerprint_with_colons(),
231 fingerprint_type=self.get_key_name(),
232 )
233 if _valid:
234 if self.session_instance.control_session.unique_hostkey_aliases and type(self.caller) not in (sshproxy.X2GoSSHProxy, ):
235 paramiko.AutoAddPolicy().missing_host_key(client, self.session_instance.get_profile_id(), key)
236 else:
237 paramiko.AutoAddPolicy().missing_host_key(client, self.get_hostname(), key)
238
239 else:
240 if type(self.caller) in (sshproxy.X2GoSSHProxy, ):
241 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())
242 else:
243 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())
244 else:
245 raise x2go_exceptions.SSHException('Policy has collected host key information on %s for further introspection' % self.get_hostname())
246
247
249 """\
250 Perform a Paramiko/SSH host key check by connecting to the host and
251 validating the results (i.e. by validating raised exceptions during the
252 connect process).
253
254 @param x2go_sshclient_instance: a Paramiko/SSH client instance to be used for testing host key validity.
255 @type x2go_sshclient_instance: C{X2GoControlSession*} instance
256 @param hostname: hostname of server to validate
257 @type hostname: C{str}
258 @param port: port of server to validate
259 @type port: C{int}
260
261 @return: returns a tuple with the following components (<host_ok>, <hostname>, <port>, <fingerprint>, <fingerprint_type>)
262 @rtype: C{tuple}
263
264 @raise SSHException: if an SSH exception occurred, that we did not provocate in L{X2GoInteractiveAddPolicy.missing_host_key()}
265
266 """
267 _hostname = hostname
268 _port = port
269 _fingerprint = 'NO-FINGERPRINT'
270 _fingerprint_type = 'SOME-KEY-TYPE'
271
272 _check_policy = X2GoInteractiveAddPolicy()
273 x2go_sshclient_instance.set_missing_host_key_policy(_check_policy)
274
275 host_ok = False
276 try:
277 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)]))
278 except x2go_exceptions.AuthenticationException:
279 host_ok = True
280 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)
281 except x2go_exceptions.SSHException, e:
282 msg = str(e)
283 if msg.startswith('Policy has collected host key information on '):
284 _hostname = _check_policy.get_hostname().split(':')[0].lstrip('[').rstrip(']')
285 _port = _check_policy.get_hostname().split(':')[1]
286 _fingerprint = _check_policy.get_key_fingerprint_with_colons()
287 _fingerprint_type = _check_policy.get_key_name()
288 else:
289 raise(e)
290 x2go_sshclient_instance.set_missing_host_key_policy(paramiko.RejectPolicy())
291 except:
292
293 pass
294
295 return (host_ok, _hostname, _port, _fingerprint, _fingerprint_type)
296