Merge lp:~jml/launchpad/codehosting-to-services into lp:launchpad
- codehosting-to-services
- Merge into devel
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~jml/launchpad/codehosting-to-services |
Merge into: | lp:launchpad |
Prerequisite: | lp:~jml/launchpad/extract-ssh-server-auth |
Diff against target: |
594 lines (+191/-134) (has conflicts) 18 files modified
daemons/sftp.tac (+1/-1) lib/lp/codehosting/sftp.py (+1/-23) lib/lp/codehosting/sshserver/daemon.py (+3/-3) lib/lp/codehosting/sshserver/session.py (+19/-75) lib/lp/codehosting/sshserver/tests/test_daemon.py (+2/-2) lib/lp/services/sshserver/__init__.py (+8/-0) lib/lp/services/sshserver/accesslog.py (+1/-1) lib/lp/services/sshserver/auth.py (+6/-5) lib/lp/services/sshserver/events.py (+2/-13) lib/lp/services/sshserver/service.py (+3/-3) lib/lp/services/sshserver/session.py (+79/-0) lib/lp/services/sshserver/sftp.py (+35/-0) lib/lp/services/sshserver/tests/__init__.py (+8/-0) lib/lp/services/sshserver/tests/keys/ssh_host_key_rsa (+15/-0) lib/lp/services/sshserver/tests/keys/ssh_host_key_rsa.pub (+1/-0) lib/lp/services/sshserver/tests/test_accesslog.py (+4/-4) lib/lp/services/sshserver/tests/test_auth.py (+2/-3) lib/lp/services/sshserver/tests/test_events.py (+1/-1) Text conflict in daemons/sftp.tac Text conflict in lib/lp/services/sshserver/accesslog.py Text conflict in lib/lp/services/sshserver/service.py Text conflict in lib/lp/services/sshserver/tests/test_auth.py |
To merge this branch: | bzr merge lp:~jml/launchpad/codehosting-to-services |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Paul Hummer (community) | code | Approve | |
Review via email: mp+23491@code.launchpad.net |
Commit message
Create a new generic Launchpad SSH server in lp.services.
Description of the change
As best as I can tell, this branch completes the work done in the mega lp:~jml/launchpad/ssh-key-auth branch. It moves all of the generic code out of lp.codehosting.
Most of the changes are simple module moves. There are some exceptions though.
* PatchedSSHSession is extracted from lp.codehosting.
* Likewise, FileTransferServer is moved out of lp.codehosting.sftp and into lp.services. It is modified slightly so that the SFTPStarted event is generated from it. Behaviour should be equivalent to current behaviour.
* BazaarSSHStarted and BazaarSSHClosed events are moved to the lp.codehosting session module. Relevant docstrings are updated.
* I had to copy some keys over for testing.
* Some tests and docstrings were tweaked to refer to Launchpad SSH server, rather than codehosting.
I look forward to your review.
jml
Preview Diff
1 | === modified file 'daemons/sftp.tac' | |||
2 | --- daemons/sftp.tac 2010-04-16 19:01:12 +0000 | |||
3 | +++ daemons/sftp.tac 2010-04-16 19:01:18 +0000 | |||
4 | @@ -21,7 +21,7 @@ | |||
5 | 21 | from lp.codehosting.sshserver.daemon import ( | 21 | from lp.codehosting.sshserver.daemon import ( |
6 | 22 | ACCESS_LOG_NAME, get_key_path, LOG_NAME, make_portal, OOPS_CONFIG_SECTION, | 22 | ACCESS_LOG_NAME, get_key_path, LOG_NAME, make_portal, OOPS_CONFIG_SECTION, |
7 | 23 | PRIVATE_KEY_FILE, PUBLIC_KEY_FILE) | 23 | PRIVATE_KEY_FILE, PUBLIC_KEY_FILE) |
9 | 24 | from lp.codehosting.sshserver.service import SSHService | 24 | from lp.services.sshserver.service import SSHService |
10 | 25 | 25 | ||
11 | 26 | 26 | ||
12 | 27 | # Construct an Application that has the codehosting SSH server. | 27 | # Construct an Application that has the codehosting SSH server. |
13 | 28 | 28 | ||
14 | === modified file 'lib/lp/codehosting/sftp.py' | |||
15 | --- lib/lp/codehosting/sftp.py 2010-04-16 19:01:12 +0000 | |||
16 | +++ lib/lp/codehosting/sftp.py 2010-04-16 19:01:18 +0000 | |||
17 | @@ -15,7 +15,6 @@ | |||
18 | 15 | __metaclass__ = type | 15 | __metaclass__ = type |
19 | 16 | __all__ = [ | 16 | __all__ = [ |
20 | 17 | 'avatar_to_sftp_server', | 17 | 'avatar_to_sftp_server', |
21 | 18 | 'FileTransferServer', | ||
22 | 19 | 'TransportSFTPServer', | 18 | 'TransportSFTPServer', |
23 | 20 | ] | 19 | ] |
24 | 21 | 20 | ||
25 | @@ -34,12 +33,10 @@ | |||
26 | 34 | from twisted.internet import defer | 33 | from twisted.internet import defer |
27 | 35 | from twisted.python import util | 34 | from twisted.python import util |
28 | 36 | 35 | ||
29 | 37 | from zope.event import notify | ||
30 | 38 | from zope.interface import implements | 36 | from zope.interface import implements |
31 | 39 | 37 | ||
32 | 38 | from canonical.config import config | ||
33 | 40 | from lp.codehosting.vfs import AsyncLaunchpadTransport, LaunchpadServer | 39 | from lp.codehosting.vfs import AsyncLaunchpadTransport, LaunchpadServer |
34 | 41 | from lp.codehosting.sshserver import events | ||
35 | 42 | from canonical.config import config | ||
36 | 43 | from lp.services.twistedsupport import gatherResults | 40 | from lp.services.twistedsupport import gatherResults |
37 | 44 | 41 | ||
38 | 45 | 42 | ||
39 | @@ -253,28 +250,9 @@ | |||
40 | 253 | avatar.branchfs_proxy, user_id, hosted_transport, mirror_transport) | 250 | avatar.branchfs_proxy, user_id, hosted_transport, mirror_transport) |
41 | 254 | server.start_server() | 251 | server.start_server() |
42 | 255 | transport = AsyncLaunchpadTransport(server, server.get_url()) | 252 | transport = AsyncLaunchpadTransport(server, server.get_url()) |
43 | 256 | notify(events.SFTPStarted(avatar)) | ||
44 | 257 | return TransportSFTPServer(transport) | 253 | return TransportSFTPServer(transport) |
45 | 258 | 254 | ||
46 | 259 | 255 | ||
47 | 260 | class FileTransferServer(filetransfer.FileTransferServer): | ||
48 | 261 | |||
49 | 262 | def __init__(self, data=None, avatar=None): | ||
50 | 263 | filetransfer.FileTransferServer.__init__(self, data, avatar) | ||
51 | 264 | self.avatar = avatar | ||
52 | 265 | |||
53 | 266 | def connectionLost(self, reason): | ||
54 | 267 | # This method gets called twice: once from `SSHChannel.closeReceived` | ||
55 | 268 | # when the client closes the channel and once from `SSHSession.closed` | ||
56 | 269 | # when the server closes the session. We change the avatar attribute | ||
57 | 270 | # to avoid logging the `SFTPClosed` event twice. | ||
58 | 271 | filetransfer.FileTransferServer.connectionLost(self, reason) | ||
59 | 272 | if self.avatar is not None: | ||
60 | 273 | avatar = self.avatar | ||
61 | 274 | self.avatar = None | ||
62 | 275 | notify(events.SFTPClosed(avatar)) | ||
63 | 276 | |||
64 | 277 | |||
65 | 278 | class TransportSFTPServer: | 256 | class TransportSFTPServer: |
66 | 279 | """An implementation of `ISFTPServer` that backs onto a Bazaar transport. | 257 | """An implementation of `ISFTPServer` that backs onto a Bazaar transport. |
67 | 280 | 258 | ||
68 | 281 | 259 | ||
69 | === modified file 'lib/lp/codehosting/sshserver/daemon.py' | |||
70 | --- lib/lp/codehosting/sshserver/daemon.py 2010-04-16 19:01:12 +0000 | |||
71 | +++ lib/lp/codehosting/sshserver/daemon.py 2010-04-16 19:01:18 +0000 | |||
72 | @@ -27,9 +27,9 @@ | |||
73 | 27 | 27 | ||
74 | 28 | from canonical.config import config | 28 | from canonical.config import config |
75 | 29 | from lp.codehosting import sftp | 29 | from lp.codehosting import sftp |
77 | 30 | from lp.codehosting.sshserver.auth import ( | 30 | from lp.codehosting.sshserver.session import launch_smart_server |
78 | 31 | from lp.services.sshserver.auth import ( | ||
79 | 31 | LaunchpadAvatar, PublicKeyFromLaunchpadChecker) | 32 | LaunchpadAvatar, PublicKeyFromLaunchpadChecker) |
80 | 32 | from lp.codehosting.sshserver.session import launch_smart_server | ||
81 | 33 | 33 | ||
82 | 34 | 34 | ||
83 | 35 | # The names of the key files of the server itself. The directory itself is | 35 | # The names of the key files of the server itself. The directory itself is |
84 | @@ -97,7 +97,7 @@ | |||
85 | 97 | """Create and return a `Portal` for the SSH service. | 97 | """Create and return a `Portal` for the SSH service. |
86 | 98 | 98 | ||
87 | 99 | This portal accepts SSH credentials and returns our customized SSH | 99 | This portal accepts SSH credentials and returns our customized SSH |
89 | 100 | avatars (see `lp.codehosting.sshserver.auth.CodehostingAvatar`). | 100 | avatars (see `CodehostingAvatar`). |
90 | 101 | """ | 101 | """ |
91 | 102 | authentication_proxy = Proxy( | 102 | authentication_proxy = Proxy( |
92 | 103 | config.codehosting.authentication_endpoint) | 103 | config.codehosting.authentication_endpoint) |
93 | 104 | 104 | ||
94 | === modified file 'lib/lp/codehosting/sshserver/session.py' | |||
95 | --- lib/lp/codehosting/sshserver/session.py 2010-04-16 19:01:12 +0000 | |||
96 | +++ lib/lp/codehosting/sshserver/session.py 2010-04-16 19:01:18 +0000 | |||
97 | @@ -6,7 +6,6 @@ | |||
98 | 6 | __metaclass__ = type | 6 | __metaclass__ = type |
99 | 7 | __all__ = [ | 7 | __all__ = [ |
100 | 8 | 'launch_smart_server', | 8 | 'launch_smart_server', |
101 | 9 | 'PatchedSSHSession', | ||
102 | 10 | ] | 9 | ] |
103 | 11 | 10 | ||
104 | 12 | import os | 11 | import os |
105 | @@ -16,81 +15,23 @@ | |||
106 | 16 | from zope.interface import implements | 15 | from zope.interface import implements |
107 | 17 | 16 | ||
108 | 18 | from twisted.conch.interfaces import ISession | 17 | from twisted.conch.interfaces import ISession |
110 | 19 | from twisted.conch.ssh import channel, connection, session | 18 | from twisted.conch.ssh import connection |
111 | 20 | from twisted.internet.process import ProcessExitedAlready | 19 | from twisted.internet.process import ProcessExitedAlready |
112 | 21 | from twisted.python import log | 20 | from twisted.python import log |
113 | 22 | 21 | ||
114 | 23 | from canonical.config import config | 22 | from canonical.config import config |
115 | 24 | from lp.codehosting import get_bzr_path | 23 | from lp.codehosting import get_bzr_path |
185 | 25 | from lp.codehosting.sshserver import events | 24 | from lp.services.sshserver.events import AvatarEvent |
186 | 26 | 25 | ||
187 | 27 | 26 | ||
188 | 28 | class PatchedSSHSession(session.SSHSession, object): | 27 | class BazaarSSHStarted(AvatarEvent): |
189 | 29 | """Session adapter that corrects bugs in Conch. | 28 | |
190 | 30 | 29 | template = '[%(session_id)s] %(username)s started bzr+ssh session.' | |
191 | 31 | This object provides no custom logic for Launchpad, it just addresses some | 30 | |
192 | 32 | simple bugs in the base `session.SSHSession` class that are not yet fixed | 31 | |
193 | 33 | upstream. | 32 | class BazaarSSHClosed(AvatarEvent): |
194 | 34 | """ | 33 | |
195 | 35 | 34 | template = '[%(session_id)s] %(username)s closed bzr+ssh session.' | |
127 | 36 | def closeReceived(self): | ||
128 | 37 | # Without this, the client hangs when it's finished transferring. | ||
129 | 38 | # XXX: JonathanLange 2009-01-05: This does not appear to have a | ||
130 | 39 | # corresponding bug in Twisted. We should test that the above comment | ||
131 | 40 | # is indeed correct and then file a bug upstream. | ||
132 | 41 | self.loseConnection() | ||
133 | 42 | |||
134 | 43 | def loseConnection(self): | ||
135 | 44 | # XXX: JonathanLange 2008-03-31: This deliberately replaces the | ||
136 | 45 | # implementation of session.SSHSession.loseConnection. The default | ||
137 | 46 | # implementation will try to call loseConnection on the client | ||
138 | 47 | # transport even if it's None. I don't know *why* it is None, so this | ||
139 | 48 | # doesn't necessarily address the root cause. | ||
140 | 49 | # See http://twistedmatrix.com/trac/ticket/2754. | ||
141 | 50 | transport = getattr(self.client, 'transport', None) | ||
142 | 51 | if transport is not None: | ||
143 | 52 | transport.loseConnection() | ||
144 | 53 | # This is called by session.SSHSession.loseConnection. SSHChannel is | ||
145 | 54 | # the base class of SSHSession. | ||
146 | 55 | channel.SSHChannel.loseConnection(self) | ||
147 | 56 | |||
148 | 57 | def stopWriting(self): | ||
149 | 58 | """See `session.SSHSession.stopWriting`. | ||
150 | 59 | |||
151 | 60 | When the client can't keep up with us, we ask the child process to | ||
152 | 61 | stop giving us data. | ||
153 | 62 | """ | ||
154 | 63 | # XXX: MichaelHudson 2008-06-27: Being cagey about whether | ||
155 | 64 | # self.client.transport is entirely paranoia inspired by the comment | ||
156 | 65 | # in `loseConnection` above. It would be good to know if and why it is | ||
157 | 66 | # necessary. See http://twistedmatrix.com/trac/ticket/2754. | ||
158 | 67 | transport = getattr(self.client, 'transport', None) | ||
159 | 68 | if transport is not None: | ||
160 | 69 | # For SFTP connections, 'transport' is actually a _DummyTransport | ||
161 | 70 | # instance. Neither _DummyTransport nor the protocol it wraps | ||
162 | 71 | # (filetransfer.FileTransferServer) support pausing. | ||
163 | 72 | pauseProducing = getattr(transport, 'pauseProducing', None) | ||
164 | 73 | if pauseProducing is not None: | ||
165 | 74 | pauseProducing() | ||
166 | 75 | |||
167 | 76 | def startWriting(self): | ||
168 | 77 | """See `session.SSHSession.startWriting`. | ||
169 | 78 | |||
170 | 79 | The client is ready for data again, so ask the child to start | ||
171 | 80 | producing data again. | ||
172 | 81 | """ | ||
173 | 82 | # XXX: MichaelHudson 2008-06-27: Being cagey about whether | ||
174 | 83 | # self.client.transport is entirely paranoia inspired by the comment | ||
175 | 84 | # in `loseConnection` above. It would be good to know if and why it is | ||
176 | 85 | # necessary. See http://twistedmatrix.com/trac/ticket/2754. | ||
177 | 86 | transport = getattr(self.client, 'transport', None) | ||
178 | 87 | if transport is not None: | ||
179 | 88 | # For SFTP connections, 'transport' is actually a _DummyTransport | ||
180 | 89 | # instance. Neither _DummyTransport nor the protocol it wraps | ||
181 | 90 | # (filetransfer.FileTransferServer) support pausing. | ||
182 | 91 | resumeProducing = getattr(transport, 'resumeProducing', None) | ||
183 | 92 | if resumeProducing is not None: | ||
184 | 93 | resumeProducing() | ||
196 | 94 | 35 | ||
197 | 95 | 36 | ||
198 | 96 | class ForbiddenCommand(Exception): | 37 | class ForbiddenCommand(Exception): |
199 | @@ -116,7 +57,10 @@ | |||
200 | 116 | def closed(self): | 57 | def closed(self): |
201 | 117 | """See ISession.""" | 58 | """See ISession.""" |
202 | 118 | if self._transport is not None: | 59 | if self._transport is not None: |
204 | 119 | notify(events.BazaarSSHClosed(self.avatar)) | 60 | # XXX: JonathanLange 2010-04-15: This is something of an |
205 | 61 | # abstraction violation. Apart from this line and its twin, this | ||
206 | 62 | # class knows nothing about Bazaar. | ||
207 | 63 | notify(BazaarSSHClosed(self.avatar)) | ||
208 | 120 | try: | 64 | try: |
209 | 121 | self._transport.signalProcess('HUP') | 65 | self._transport.signalProcess('HUP') |
210 | 122 | except (OSError, ProcessExitedAlready): | 66 | except (OSError, ProcessExitedAlready): |
211 | @@ -154,9 +98,9 @@ | |||
212 | 154 | "ERROR: %r already running a command on transport %r" | 98 | "ERROR: %r already running a command on transport %r" |
213 | 155 | % (self, self._transport)) | 99 | % (self, self._transport)) |
214 | 156 | # XXX: JonathanLange 2008-12-23: This is something of an abstraction | 100 | # XXX: JonathanLange 2008-12-23: This is something of an abstraction |
218 | 157 | # violation. Apart from this line, this class knows nothing about | 101 | # violation. Apart from this line and its twin, this class knows |
219 | 158 | # Bazaar. | 102 | # nothing about Bazaar. |
220 | 159 | notify(events.BazaarSSHStarted(self.avatar)) | 103 | notify(BazaarSSHStarted(self.avatar)) |
221 | 160 | self._transport = self.reactor.spawnProcess( | 104 | self._transport = self.reactor.spawnProcess( |
222 | 161 | protocol, executable, arguments, env=self.environment) | 105 | protocol, executable, arguments, env=self.environment) |
223 | 162 | 106 | ||
224 | 163 | 107 | ||
225 | === modified file 'lib/lp/codehosting/sshserver/tests/test_daemon.py' | |||
226 | --- lib/lp/codehosting/sshserver/tests/test_daemon.py 2010-04-16 19:01:12 +0000 | |||
227 | +++ lib/lp/codehosting/sshserver/tests/test_daemon.py 2010-04-16 19:01:18 +0000 | |||
228 | @@ -14,10 +14,10 @@ | |||
229 | 14 | 14 | ||
230 | 15 | from canonical.testing.layers import TwistedLayer | 15 | from canonical.testing.layers import TwistedLayer |
231 | 16 | 16 | ||
232 | 17 | from lp.codehosting.sshserver.auth import SSHUserAuthServer | ||
233 | 18 | from lp.codehosting.sshserver.daemon import ( | 17 | from lp.codehosting.sshserver.daemon import ( |
234 | 19 | get_key_path, get_portal, PRIVATE_KEY_FILE, PUBLIC_KEY_FILE) | 18 | get_key_path, get_portal, PRIVATE_KEY_FILE, PUBLIC_KEY_FILE) |
236 | 20 | from lp.codehosting.sshserver.service import Factory | 19 | from lp.services.sshserver.auth import SSHUserAuthServer |
237 | 20 | from lp.services.sshserver.service import Factory | ||
238 | 21 | 21 | ||
239 | 22 | 22 | ||
240 | 23 | class StringTransportWith_setTcpKeepAlive(StringTransport): | 23 | class StringTransportWith_setTcpKeepAlive(StringTransport): |
241 | 24 | 24 | ||
242 | === added directory 'lib/lp/services/sshserver' | |||
243 | === added file 'lib/lp/services/sshserver/__init__.py' | |||
244 | --- lib/lp/services/sshserver/__init__.py 1970-01-01 00:00:00 +0000 | |||
245 | +++ lib/lp/services/sshserver/__init__.py 2010-04-16 19:01:18 +0000 | |||
246 | @@ -0,0 +1,8 @@ | |||
247 | 1 | # Copyright 2010 Canonical Ltd. This software is licensed under the | ||
248 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
249 | 3 | |||
250 | 4 | """The Launchpad SSH server.""" | ||
251 | 5 | |||
252 | 6 | __metaclass__ = type | ||
253 | 7 | __all__ = [] | ||
254 | 8 | |||
255 | 0 | 9 | ||
256 | === renamed file 'lib/lp/codehosting/sshserver/accesslog.py' => 'lib/lp/services/sshserver/accesslog.py' | |||
257 | --- lib/lp/codehosting/sshserver/accesslog.py 2010-04-16 19:01:12 +0000 | |||
258 | +++ lib/lp/services/sshserver/accesslog.py 2010-04-16 19:01:18 +0000 | |||
259 | @@ -17,7 +17,7 @@ | |||
260 | 17 | import zope.component.event | 17 | import zope.component.event |
261 | 18 | 18 | ||
262 | 19 | from canonical.launchpad.scripts import WatchedFileHandler | 19 | from canonical.launchpad.scripts import WatchedFileHandler |
264 | 20 | from lp.codehosting.sshserver.events import ILoggingEvent | 20 | from lp.services.sshserver.events import ILoggingEvent |
265 | 21 | from lp.services.utils import synchronize | 21 | from lp.services.utils import synchronize |
266 | 22 | 22 | ||
267 | 23 | 23 | ||
268 | 24 | 24 | ||
269 | === renamed file 'lib/lp/codehosting/sshserver/auth.py' => 'lib/lp/services/sshserver/auth.py' | |||
270 | --- lib/lp/codehosting/sshserver/auth.py 2010-04-16 19:01:12 +0000 | |||
271 | +++ lib/lp/services/sshserver/auth.py 2010-04-16 19:01:18 +0000 | |||
272 | @@ -38,11 +38,12 @@ | |||
273 | 38 | from zope.event import notify | 38 | from zope.event import notify |
274 | 39 | from zope.interface import implements | 39 | from zope.interface import implements |
275 | 40 | 40 | ||
279 | 41 | from lp.codehosting import sftp | 41 | from canonical.launchpad.xmlrpc import faults |
280 | 42 | from lp.codehosting.sshserver import events | 42 | |
281 | 43 | from lp.codehosting.sshserver.session import PatchedSSHSession | 43 | from lp.services.sshserver import events |
282 | 44 | from lp.services.sshserver.sftp import FileTransferServer | ||
283 | 45 | from lp.services.sshserver.session import PatchedSSHSession | ||
284 | 44 | from lp.services.twistedsupport.xmlrpc import trap_fault | 46 | from lp.services.twistedsupport.xmlrpc import trap_fault |
285 | 45 | from canonical.launchpad.xmlrpc import faults | ||
286 | 46 | 47 | ||
287 | 47 | 48 | ||
288 | 48 | class LaunchpadAvatar(avatar.ConchUser): | 49 | class LaunchpadAvatar(avatar.ConchUser): |
289 | @@ -68,7 +69,7 @@ | |||
290 | 68 | # fixes). | 69 | # fixes). |
291 | 69 | self.channelLookup = {'session': PatchedSSHSession} | 70 | self.channelLookup = {'session': PatchedSSHSession} |
292 | 70 | # ...and set the only subsystem to be SFTP. | 71 | # ...and set the only subsystem to be SFTP. |
294 | 71 | self.subsystemLookup = {'sftp': sftp.FileTransferServer} | 72 | self.subsystemLookup = {'sftp': FileTransferServer} |
295 | 72 | 73 | ||
296 | 73 | def logout(self): | 74 | def logout(self): |
297 | 74 | notify(events.UserLoggedOut(self)) | 75 | notify(events.UserLoggedOut(self)) |
298 | 75 | 76 | ||
299 | === renamed file 'lib/lp/codehosting/sshserver/events.py' => 'lib/lp/services/sshserver/events.py' | |||
300 | --- lib/lp/codehosting/sshserver/events.py 2010-04-16 19:01:12 +0000 | |||
301 | +++ lib/lp/services/sshserver/events.py 2010-04-16 19:01:18 +0000 | |||
302 | @@ -6,8 +6,7 @@ | |||
303 | 6 | __metaclass__ = type | 6 | __metaclass__ = type |
304 | 7 | __all__ = [ | 7 | __all__ = [ |
305 | 8 | 'AuthenticationFailed', | 8 | 'AuthenticationFailed', |
308 | 9 | 'BazaarSSHClosed', | 9 | 'AvatarEvent', |
307 | 10 | 'BazaarSSHStarted', | ||
309 | 11 | 'ILoggingEvent', | 10 | 'ILoggingEvent', |
310 | 12 | 'LoggingEvent', | 11 | 'LoggingEvent', |
311 | 13 | 'ServerStarting', | 12 | 'ServerStarting', |
312 | @@ -28,7 +27,7 @@ | |||
313 | 28 | class ILoggingEvent(Interface): | 27 | class ILoggingEvent(Interface): |
314 | 29 | """An event is a logging event if it has a message and a severity level. | 28 | """An event is a logging event if it has a message and a severity level. |
315 | 30 | 29 | ||
317 | 31 | Events that provide this interface will be logged in codehosting access | 30 | Events that provide this interface will be logged in the SSH server access |
318 | 32 | log. | 31 | log. |
319 | 33 | """ | 32 | """ |
320 | 34 | 33 | ||
321 | @@ -143,13 +142,3 @@ | |||
322 | 143 | class SFTPClosed(AvatarEvent): | 142 | class SFTPClosed(AvatarEvent): |
323 | 144 | 143 | ||
324 | 145 | template = '[%(session_id)s] %(username)s closed SFTP session.' | 144 | template = '[%(session_id)s] %(username)s closed SFTP session.' |
325 | 146 | |||
326 | 147 | |||
327 | 148 | class BazaarSSHStarted(AvatarEvent): | ||
328 | 149 | |||
329 | 150 | template = '[%(session_id)s] %(username)s started bzr+ssh session.' | ||
330 | 151 | |||
331 | 152 | |||
332 | 153 | class BazaarSSHClosed(AvatarEvent): | ||
333 | 154 | |||
334 | 155 | template = '[%(session_id)s] %(username)s closed bzr+ssh session.' | ||
335 | 156 | 145 | ||
336 | === renamed file 'lib/lp/codehosting/sshserver/service.py' => 'lib/lp/services/sshserver/service.py' | |||
337 | --- lib/lp/codehosting/sshserver/service.py 2010-04-16 19:01:12 +0000 | |||
338 | +++ lib/lp/services/sshserver/service.py 2010-04-16 19:01:18 +0000 | |||
339 | @@ -1,7 +1,7 @@ | |||
340 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the |
341 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
342 | 3 | 3 | ||
344 | 4 | """Twisted `service.Service` class for the codehosting SSH server. | 4 | """Twisted `service.Service` class for the Launchpad SSH server. |
345 | 5 | 5 | ||
346 | 6 | An `SSHService` object can be used to launch the SSH server. | 6 | An `SSHService` object can be used to launch the SSH server. |
347 | 7 | """ | 7 | """ |
348 | @@ -24,8 +24,8 @@ | |||
349 | 24 | 24 | ||
350 | 25 | from zope.event import notify | 25 | from zope.event import notify |
351 | 26 | 26 | ||
354 | 27 | from lp.codehosting.sshserver import accesslog, events | 27 | from lp.services.sshserver import accesslog, events |
355 | 28 | from lp.codehosting.sshserver.auth import SSHUserAuthServer | 28 | from lp.services.sshserver.auth import SSHUserAuthServer |
356 | 29 | from lp.services.twistedsupport import gatherResults | 29 | from lp.services.twistedsupport import gatherResults |
357 | 30 | from lp.services.twistedsupport.loggingsupport import set_up_oops_reporting | 30 | from lp.services.twistedsupport.loggingsupport import set_up_oops_reporting |
358 | 31 | 31 | ||
359 | 32 | 32 | ||
360 | === added file 'lib/lp/services/sshserver/session.py' | |||
361 | --- lib/lp/services/sshserver/session.py 1970-01-01 00:00:00 +0000 | |||
362 | +++ lib/lp/services/sshserver/session.py 2010-04-16 19:01:18 +0000 | |||
363 | @@ -0,0 +1,79 @@ | |||
364 | 1 | # Copyright 2010 Canonical Ltd. This software is licensed under the | ||
365 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
366 | 3 | |||
367 | 4 | """Patched SSH session for the Launchpad server.""" | ||
368 | 5 | |||
369 | 6 | __metaclass__ = type | ||
370 | 7 | __all__ = [ | ||
371 | 8 | 'PatchedSSHSession', | ||
372 | 9 | ] | ||
373 | 10 | |||
374 | 11 | from twisted.conch.ssh import channel, session | ||
375 | 12 | |||
376 | 13 | |||
377 | 14 | class PatchedSSHSession(session.SSHSession, object): | ||
378 | 15 | """Session adapter that corrects bugs in Conch. | ||
379 | 16 | |||
380 | 17 | This object provides no custom logic for Launchpad, it just addresses some | ||
381 | 18 | simple bugs in the base `session.SSHSession` class that are not yet fixed | ||
382 | 19 | upstream. | ||
383 | 20 | """ | ||
384 | 21 | |||
385 | 22 | def closeReceived(self): | ||
386 | 23 | # Without this, the client hangs when it's finished transferring. | ||
387 | 24 | # XXX: JonathanLange 2009-01-05: This does not appear to have a | ||
388 | 25 | # corresponding bug in Twisted. We should test that the above comment | ||
389 | 26 | # is indeed correct and then file a bug upstream. | ||
390 | 27 | self.loseConnection() | ||
391 | 28 | |||
392 | 29 | def loseConnection(self): | ||
393 | 30 | # XXX: JonathanLange 2008-03-31: This deliberately replaces the | ||
394 | 31 | # implementation of session.SSHSession.loseConnection. The default | ||
395 | 32 | # implementation will try to call loseConnection on the client | ||
396 | 33 | # transport even if it's None. I don't know *why* it is None, so this | ||
397 | 34 | # doesn't necessarily address the root cause. | ||
398 | 35 | # See http://twistedmatrix.com/trac/ticket/2754. | ||
399 | 36 | transport = getattr(self.client, 'transport', None) | ||
400 | 37 | if transport is not None: | ||
401 | 38 | transport.loseConnection() | ||
402 | 39 | # This is called by session.SSHSession.loseConnection. SSHChannel is | ||
403 | 40 | # the base class of SSHSession. | ||
404 | 41 | channel.SSHChannel.loseConnection(self) | ||
405 | 42 | |||
406 | 43 | def stopWriting(self): | ||
407 | 44 | """See `session.SSHSession.stopWriting`. | ||
408 | 45 | |||
409 | 46 | When the client can't keep up with us, we ask the child process to | ||
410 | 47 | stop giving us data. | ||
411 | 48 | """ | ||
412 | 49 | # XXX: MichaelHudson 2008-06-27: Being cagey about whether | ||
413 | 50 | # self.client.transport is entirely paranoia inspired by the comment | ||
414 | 51 | # in `loseConnection` above. It would be good to know if and why it is | ||
415 | 52 | # necessary. See http://twistedmatrix.com/trac/ticket/2754. | ||
416 | 53 | transport = getattr(self.client, 'transport', None) | ||
417 | 54 | if transport is not None: | ||
418 | 55 | # For SFTP connections, 'transport' is actually a _DummyTransport | ||
419 | 56 | # instance. Neither _DummyTransport nor the protocol it wraps | ||
420 | 57 | # (filetransfer.FileTransferServer) support pausing. | ||
421 | 58 | pauseProducing = getattr(transport, 'pauseProducing', None) | ||
422 | 59 | if pauseProducing is not None: | ||
423 | 60 | pauseProducing() | ||
424 | 61 | |||
425 | 62 | def startWriting(self): | ||
426 | 63 | """See `session.SSHSession.startWriting`. | ||
427 | 64 | |||
428 | 65 | The client is ready for data again, so ask the child to start | ||
429 | 66 | producing data again. | ||
430 | 67 | """ | ||
431 | 68 | # XXX: MichaelHudson 2008-06-27: Being cagey about whether | ||
432 | 69 | # self.client.transport is entirely paranoia inspired by the comment | ||
433 | 70 | # in `loseConnection` above. It would be good to know if and why it is | ||
434 | 71 | # necessary. See http://twistedmatrix.com/trac/ticket/2754. | ||
435 | 72 | transport = getattr(self.client, 'transport', None) | ||
436 | 73 | if transport is not None: | ||
437 | 74 | # For SFTP connections, 'transport' is actually a _DummyTransport | ||
438 | 75 | # instance. Neither _DummyTransport nor the protocol it wraps | ||
439 | 76 | # (filetransfer.FileTransferServer) support pausing. | ||
440 | 77 | resumeProducing = getattr(transport, 'resumeProducing', None) | ||
441 | 78 | if resumeProducing is not None: | ||
442 | 79 | resumeProducing() | ||
443 | 0 | 80 | ||
444 | === added file 'lib/lp/services/sshserver/sftp.py' | |||
445 | --- lib/lp/services/sshserver/sftp.py 1970-01-01 00:00:00 +0000 | |||
446 | +++ lib/lp/services/sshserver/sftp.py 2010-04-16 19:01:18 +0000 | |||
447 | @@ -0,0 +1,35 @@ | |||
448 | 1 | # Copyright 2010 Canonical Ltd. This software is licensed under the | ||
449 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
450 | 3 | |||
451 | 4 | """Generic SFTP server functionality.""" | ||
452 | 5 | |||
453 | 6 | __metaclass__ = type | ||
454 | 7 | __all__ = [ | ||
455 | 8 | 'FileTransferServer', | ||
456 | 9 | ] | ||
457 | 10 | |||
458 | 11 | from twisted.conch.ssh import filetransfer | ||
459 | 12 | |||
460 | 13 | from zope.event import notify | ||
461 | 14 | |||
462 | 15 | from lp.services.sshserver import events | ||
463 | 16 | |||
464 | 17 | |||
465 | 18 | class FileTransferServer(filetransfer.FileTransferServer): | ||
466 | 19 | """SFTP protocol implementation that logs key events.""" | ||
467 | 20 | |||
468 | 21 | def __init__(self, data=None, avatar=None): | ||
469 | 22 | filetransfer.FileTransferServer.__init__(self, data, avatar) | ||
470 | 23 | notify(events.SFTPStarted(avatar)) | ||
471 | 24 | self.avatar = avatar | ||
472 | 25 | |||
473 | 26 | def connectionLost(self, reason): | ||
474 | 27 | # This method gets called twice: once from `SSHChannel.closeReceived` | ||
475 | 28 | # when the client closes the channel and once from `SSHSession.closed` | ||
476 | 29 | # when the server closes the session. We change the avatar attribute | ||
477 | 30 | # to avoid logging the `SFTPClosed` event twice. | ||
478 | 31 | filetransfer.FileTransferServer.connectionLost(self, reason) | ||
479 | 32 | if self.avatar is not None: | ||
480 | 33 | avatar = self.avatar | ||
481 | 34 | self.avatar = None | ||
482 | 35 | notify(events.SFTPClosed(avatar)) | ||
483 | 0 | 36 | ||
484 | === added directory 'lib/lp/services/sshserver/tests' | |||
485 | === added file 'lib/lp/services/sshserver/tests/__init__.py' | |||
486 | --- lib/lp/services/sshserver/tests/__init__.py 1970-01-01 00:00:00 +0000 | |||
487 | +++ lib/lp/services/sshserver/tests/__init__.py 2010-04-16 19:01:18 +0000 | |||
488 | @@ -0,0 +1,8 @@ | |||
489 | 1 | # Copyright 2010 Canonical Ltd. This software is licensed under the | ||
490 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
491 | 3 | |||
492 | 4 | """Tests for the Launchpad SSH server.""" | ||
493 | 5 | |||
494 | 6 | __metaclass__ = type | ||
495 | 7 | __all__ = [] | ||
496 | 8 | |||
497 | 0 | 9 | ||
498 | === added directory 'lib/lp/services/sshserver/tests/keys' | |||
499 | === added file 'lib/lp/services/sshserver/tests/keys/ssh_host_key_rsa' | |||
500 | --- lib/lp/services/sshserver/tests/keys/ssh_host_key_rsa 1970-01-01 00:00:00 +0000 | |||
501 | +++ lib/lp/services/sshserver/tests/keys/ssh_host_key_rsa 2010-04-16 19:01:18 +0000 | |||
502 | @@ -0,0 +1,15 @@ | |||
503 | 1 | -----BEGIN RSA PRIVATE KEY----- | ||
504 | 2 | MIICXAIBAAKBgQDSSpVRPhCiU9PPuZN7QyJdMOgTVwPyYpZGOHutR/9kxFvOLa39 | ||
505 | 3 | nY0Eqo39OTumfZBMEEVqIPadQanO9LcdTnl9/Z4LcBGn09EFQ2y7VUkC6J2dSQtr | ||
506 | 4 | YMY0tV+C5HGZ2oYBWKBl5PZ1RI4+qrJpAMMmINdnF0uEE/x8B1iMWGB3PwIBIwKB | ||
507 | 5 | gQCcN2ebb+8ZgBmwQLazVnFMirsHDXClbc630i/9EOmbUAmvGp6B4sCHH5ytevkc | ||
508 | 6 | l8pHIget7JnxKXbUQMKKzJTCpPwwEyL3ZVDxYXg37WQU74cVf93CjOjChs+hOeS1 | ||
509 | 7 | sW5m9JFr9oomL5JWnGXr+TV/kYBCNVW++J1Bckn6kYpH+wJBAPUw0ZunXlBRuugA | ||
510 | 8 | YTSmXUUX+ALu6maDD1t7gAk37waxNQMaH5DMk5R4IQtoxeQgCLqL2yEJUqK1lxOy | ||
511 | 9 | wlp1k8UCQQDbj+Vr4poE9MpYNtPDiDqv2aXe6CJ3p1qQuNE0rSxk+0G9h3ASISRw | ||
512 | 10 | DDLgcapg2xkOvG3pidfAJG9P827XiVszAkEA4CyiYm0jB5suivkIaqa7rOK23hxE | ||
513 | 11 | BfQrTFOoQvFPkRcL5ZQ6Hfzes6EIRPIUA8WEUskC3GBLjXLTRTW5Aj+dCwJBAMi+ | ||
514 | 12 | E5XWfjBqx6EcL1OvwKDG/g2hCZH4GEnNjBLneQveZ/29qEsW/L43CfHHAiyrD5hx | ||
515 | 13 | w5OxOklF4h02VrZu9EsCQBEZcTAQOrWnkmp7uBrz1V8nFLAE6zFh/6Wj1Mn8k08j | ||
516 | 14 | pcsLJAhm+qlV7EtV/5rk+v3WcXrKiRIiiC0Ron96Dx0= | ||
517 | 15 | -----END RSA PRIVATE KEY----- | ||
518 | 0 | 16 | ||
519 | === added file 'lib/lp/services/sshserver/tests/keys/ssh_host_key_rsa.pub' | |||
520 | --- lib/lp/services/sshserver/tests/keys/ssh_host_key_rsa.pub 1970-01-01 00:00:00 +0000 | |||
521 | +++ lib/lp/services/sshserver/tests/keys/ssh_host_key_rsa.pub 2010-04-16 19:01:18 +0000 | |||
522 | @@ -0,0 +1,1 @@ | |||
523 | 1 | ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA0kqVUT4QolPTz7mTe0MiXTDoE1cD8mKWRjh7rUf/ZMRbzi2t/Z2NBKqN/Tk7pn2QTBBFaiD2nUGpzvS3HU55ff2eC3ARp9PRBUNsu1VJAuidnUkLa2DGNLVfguRxmdqGAVigZeT2dUSOPqqyaQDDJiDXZxdLhBP8fAdYjFhgdz8= andrew@frobozz | ||
524 | 0 | 2 | ||
525 | === renamed file 'lib/lp/codehosting/sshserver/tests/test_logging.py' => 'lib/lp/services/sshserver/tests/test_accesslog.py' | |||
526 | --- lib/lp/codehosting/sshserver/tests/test_logging.py 2010-04-16 19:01:12 +0000 | |||
527 | +++ lib/lp/services/sshserver/tests/test_accesslog.py 2010-04-16 19:01:18 +0000 | |||
528 | @@ -18,7 +18,7 @@ | |||
529 | 18 | import zope.component.event | 18 | import zope.component.event |
530 | 19 | 19 | ||
531 | 20 | from canonical.launchpad.scripts import WatchedFileHandler | 20 | from canonical.launchpad.scripts import WatchedFileHandler |
533 | 21 | from lp.codehosting.sshserver.accesslog import LoggingManager | 21 | from lp.services.sshserver.accesslog import LoggingManager |
534 | 22 | from lp.testing import TestCase | 22 | from lp.testing import TestCase |
535 | 23 | 23 | ||
536 | 24 | 24 | ||
537 | @@ -71,9 +71,9 @@ | |||
538 | 71 | self.assertEqual(root_handlers, logging.getLogger('').handlers) | 71 | self.assertEqual(root_handlers, logging.getLogger('').handlers) |
539 | 72 | self.assertEqual(bzr_handlers, logging.getLogger('bzr').handlers) | 72 | self.assertEqual(bzr_handlers, logging.getLogger('bzr').handlers) |
540 | 73 | 73 | ||
542 | 74 | def test_codehosting_log_doesnt_go_to_stderr(self): | 74 | def test_log_doesnt_go_to_stderr(self): |
543 | 75 | # Once logging setup is called, any messages logged to the | 75 | # Once logging setup is called, any messages logged to the |
545 | 76 | # codehosting logger should *not* be logged to stderr. If they are, | 76 | # SSH server logger should *not* be logged to stderr. If they are, |
546 | 77 | # they will appear on the user's terminal. | 77 | # they will appear on the user's terminal. |
547 | 78 | log = self.makeLogger() | 78 | log = self.makeLogger() |
548 | 79 | self.installLoggingManager(log) | 79 | self.installLoggingManager(log) |
549 | @@ -136,7 +136,7 @@ | |||
550 | 136 | 136 | ||
551 | 137 | def test_access_handlers(self): | 137 | def test_access_handlers(self): |
552 | 138 | # The logging setup installs a rotatable log handler that logs output | 138 | # The logging setup installs a rotatable log handler that logs output |
554 | 139 | # to config.codehosting.access_log. | 139 | # to the SSH server access log. |
555 | 140 | directory = self.makeTemporaryDirectory() | 140 | directory = self.makeTemporaryDirectory() |
556 | 141 | access_log = self.makeLogger() | 141 | access_log = self.makeLogger() |
557 | 142 | access_log_path = os.path.join(directory, 'access.log') | 142 | access_log_path = os.path.join(directory, 'access.log') |
558 | 143 | 143 | ||
559 | === renamed file 'lib/lp/codehosting/sshserver/tests/test_auth.py' => 'lib/lp/services/sshserver/tests/test_auth.py' | |||
560 | --- lib/lp/codehosting/sshserver/tests/test_auth.py 2010-04-16 19:01:12 +0000 | |||
561 | +++ lib/lp/services/sshserver/tests/test_auth.py 2010-04-16 19:01:18 +0000 | |||
562 | @@ -21,10 +21,9 @@ | |||
563 | 21 | 21 | ||
564 | 22 | from twisted.trial.unittest import TestCase as TrialTestCase | 22 | from twisted.trial.unittest import TestCase as TrialTestCase |
565 | 23 | 23 | ||
566 | 24 | from canonical.config import config | ||
567 | 25 | from canonical.launchpad.xmlrpc import faults | 24 | from canonical.launchpad.xmlrpc import faults |
568 | 26 | from canonical.testing.layers import TwistedLayer | 25 | from canonical.testing.layers import TwistedLayer |
570 | 27 | from lp.codehosting.sshserver import auth | 26 | from lp.services.sshserver import auth |
571 | 28 | from lp.services.twistedsupport import suppress_stderr | 27 | from lp.services.twistedsupport import suppress_stderr |
572 | 29 | 28 | ||
573 | 30 | 29 | ||
574 | @@ -224,7 +223,7 @@ | |||
575 | 224 | 223 | ||
576 | 225 | def test_bannerNotSentOnSuccess(self): | 224 | def test_bannerNotSentOnSuccess(self): |
577 | 226 | # No banner is printed when the user authenticates successfully. | 225 | # No banner is printed when the user authenticates successfully. |
579 | 227 | self.assertEqual(None, config.codehosting.banner) | 226 | self.user_auth._banner = None |
580 | 228 | 227 | ||
581 | 229 | d = self.requestSuccessfulAuthentication() | 228 | d = self.requestSuccessfulAuthentication() |
582 | 230 | def check(ignored): | 229 | def check(ignored): |
583 | 231 | 230 | ||
584 | === renamed file 'lib/lp/codehosting/sshserver/tests/test_events.py' => 'lib/lp/services/sshserver/tests/test_events.py' | |||
585 | --- lib/lp/codehosting/sshserver/tests/test_events.py 2010-04-16 19:01:12 +0000 | |||
586 | +++ lib/lp/services/sshserver/tests/test_events.py 2010-04-16 19:01:18 +0000 | |||
587 | @@ -13,7 +13,7 @@ | |||
588 | 13 | import zope.component.event | 13 | import zope.component.event |
589 | 14 | from zope.event import notify | 14 | from zope.event import notify |
590 | 15 | 15 | ||
592 | 16 | from lp.codehosting.sshserver.events import ILoggingEvent, LoggingEvent | 16 | from lp.services.sshserver.events import ILoggingEvent, LoggingEvent |
593 | 17 | 17 | ||
594 | 18 | from lp.testing import TestCase | 18 | from lp.testing import TestCase |
595 | 19 | 19 |
<rockstar> jml, all of the codehosting- to-services branch is moves right? No real changes? sshkeygen. com/
<jml> rockstar, pretty much. except for what I mention in my comment.
<rockstar> jml, okay, so basically just docstrings and test comments got changed (outside of imports)
<jml> rockstar, that sounds right.
<rockstar> jml, okay. Looking now.
<rockstar> jml, your comment says you had to copy keys over. Why couldn't you move them?
<jml> rockstar, they are also used by the codehosting acceptance tests.
<rockstar> jml, hm. Do you really think it's wise to duplicate those keys?
<jml> rockstar, why would it be unwise?
<rockstar> jml, duplication. It's not code, this I know. But duplication in general can become unfun.
<jml> rockstar, you want me to generate a new RSA SSH key pair?
<jml> rockstar, it's pretty easy. there's even a cool website that'll do it for you!
<rockstar> jml, no, we can use the same one, but I'm wondering if we could symlink or something.
<rockstar> jml, that sounds like a very silly website. :)
<jml> rockstar, it is – http://
* rockstar groans
<jml> rockstar, anyway, I can make them symlinks if you want. it doesn't matter much either way to me
<rockstar> jml, actually, it doesn't matter much to me either. The question was more or less one of Kiko's "exploratory" questions. If you're okay with it, I am.
<jml> rockstar, cool.