Merge lp:~jml/launchpad/extract-ssh-server into lp:launchpad
- extract-ssh-server
- Merge into devel
Proposed by
Jonathan Lange
Status: | Merged |
---|---|
Approved by: | Michael Hudson-Doyle |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~jml/launchpad/extract-ssh-server |
Merge into: | lp:launchpad |
Diff against target: |
351 lines (+108/-79) 5 files modified
daemons/sftp.tac (+15/-6) lib/lp/codehosting/sshserver/accesslog.py (+1/-4) lib/lp/codehosting/sshserver/auth.py (+4/-4) lib/lp/codehosting/sshserver/service.py (+66/-33) lib/lp/codehosting/sshserver/tests/test_auth.py (+22/-32) |
To merge this branch: | bzr merge lp:~jml/launchpad/extract-ssh-server |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Hudson-Doyle | Approve | ||
Canonical Launchpad Engineering | Pending | ||
Review via email:
|
Commit message
Refactor some codehosting SSH server stuff to not depend on codehosting configuration
Description of the change
This branch extracts some of the changes from my mega-branch lp:~jml/launchpad/ssh-key-auth.
In particular, it parametrizes three of the key classes so that they don't look up configuration variables themselves, but rather get passed the values that they need. This pushes most of the configuration up to the sftp.tac file, which is good, since tac files are all about configuration.
The three classes are SSHUserAuthServer, Factory and SSHService. The changes are pretty boring -- just moving config variables out into constructor parameters.
Other than that, there's a tweak to LoggingManager to remove an unused parameter.
I look forward to your review.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'daemons/sftp.tac' | |||
2 | --- daemons/sftp.tac 2009-06-24 20:55:31 +0000 | |||
3 | +++ daemons/sftp.tac 2010-04-15 13:43:32 +0000 | |||
4 | @@ -1,4 +1,4 @@ | |||
6 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
7 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
8 | 3 | 3 | ||
9 | 4 | # This is a Twisted application config file. To run, use: | 4 | # This is a Twisted application config file. To run, use: |
10 | @@ -7,13 +7,22 @@ | |||
11 | 7 | 7 | ||
12 | 8 | from twisted.application import service | 8 | from twisted.application import service |
13 | 9 | 9 | ||
14 | 10 | from canonical.config import config | ||
15 | 10 | from canonical.launchpad.daemons import tachandler | 11 | from canonical.launchpad.daemons import tachandler |
20 | 11 | from lp.codehosting.sshserver.service import SSHService | 12 | |
21 | 12 | 13 | from lp.codehosting.sshserver.service import ( | |
22 | 13 | 14 | get_key_path, make_portal, PRIVATE_KEY_FILE, PUBLIC_KEY_FILE, SSHService) | |
23 | 14 | # Construct an Application that includes a supermirror SFTP service. | 15 | |
24 | 16 | |||
25 | 17 | # Construct an Application that has the codehosting SSH server. | ||
26 | 15 | application = service.Application('sftponly') | 18 | application = service.Application('sftponly') |
28 | 16 | svc = SSHService() | 19 | svc = SSHService( |
29 | 20 | portal=make_portal(), | ||
30 | 21 | private_key_path=get_key_path(PRIVATE_KEY_FILE), | ||
31 | 22 | public_key_path=get_key_path(PUBLIC_KEY_FILE), | ||
32 | 23 | strport=config.codehosting.port, | ||
33 | 24 | idle_timeout=config.codehosting.idle_timeout, | ||
34 | 25 | banner=config.codehosting.banner) | ||
35 | 17 | svc.setServiceParent(application) | 26 | svc.setServiceParent(application) |
36 | 18 | 27 | ||
37 | 19 | # Service that announces when the daemon is ready | 28 | # Service that announces when the daemon is ready |
38 | 20 | 29 | ||
39 | === modified file 'lib/lp/codehosting/sshserver/accesslog.py' | |||
40 | --- lib/lp/codehosting/sshserver/accesslog.py 2010-04-09 12:45:48 +0000 | |||
41 | +++ lib/lp/codehosting/sshserver/accesslog.py 2010-04-15 13:43:32 +0000 | |||
42 | @@ -36,7 +36,7 @@ | |||
43 | 36 | class LoggingManager: | 36 | class LoggingManager: |
44 | 37 | """Class for managing codehosting logging.""" | 37 | """Class for managing codehosting logging.""" |
45 | 38 | 38 | ||
47 | 39 | def setUp(self, configure_oops_reporting=False, mangle_stdout=False): | 39 | def setUp(self, configure_oops_reporting=False): |
48 | 40 | """Set up logging for the smart server. | 40 | """Set up logging for the smart server. |
49 | 41 | 41 | ||
50 | 42 | This sets up a debugging handler on the 'codehosting' logger, makes | 42 | This sets up a debugging handler on the 'codehosting' logger, makes |
51 | @@ -45,9 +45,6 @@ | |||
52 | 45 | 45 | ||
53 | 46 | :param configure_oops_reporting: If True, install a Twisted log | 46 | :param configure_oops_reporting: If True, install a Twisted log |
54 | 47 | observer that ensures unhandled exceptions get reported as OOPSes. | 47 | observer that ensures unhandled exceptions get reported as OOPSes. |
55 | 48 | :param mangle_stdout: If True and if configure_oops_reporting is True, | ||
56 | 49 | redirect standard error and standard output to the OOPS logging | ||
57 | 50 | observer. | ||
58 | 51 | """ | 48 | """ |
59 | 52 | log = get_codehosting_logger() | 49 | log = get_codehosting_logger() |
60 | 53 | self._orig_level = log.level | 50 | self._orig_level = log.level |
61 | 54 | 51 | ||
62 | === modified file 'lib/lp/codehosting/sshserver/auth.py' | |||
63 | --- lib/lp/codehosting/sshserver/auth.py 2010-04-10 13:55:56 +0000 | |||
64 | +++ lib/lp/codehosting/sshserver/auth.py 2010-04-15 13:43:32 +0000 | |||
65 | @@ -168,8 +168,9 @@ | |||
66 | 168 | paste and change the implementations of these methods. | 168 | paste and change the implementations of these methods. |
67 | 169 | """ | 169 | """ |
68 | 170 | 170 | ||
70 | 171 | def __init__(self, transport=None): | 171 | def __init__(self, transport=None, banner=None): |
71 | 172 | self.transport = transport | 172 | self.transport = transport |
72 | 173 | self._banner = banner | ||
73 | 173 | self._configured_banner_sent = False | 174 | self._configured_banner_sent = False |
74 | 174 | self._mind = UserDetailsMind() | 175 | self._mind = UserDetailsMind() |
75 | 175 | self.interfaceToMethod = userauth.SSHUserAuthServer.interfaceToMethod | 176 | self.interfaceToMethod = userauth.SSHUserAuthServer.interfaceToMethod |
76 | @@ -181,10 +182,9 @@ | |||
77 | 181 | NS(bytes) + NS(language)) | 182 | NS(bytes) + NS(language)) |
78 | 182 | 183 | ||
79 | 183 | def _sendConfiguredBanner(self, passed_through): | 184 | def _sendConfiguredBanner(self, passed_through): |
82 | 184 | if (not self._configured_banner_sent | 185 | if not self._configured_banner_sent and self._banner: |
81 | 185 | and config.codehosting.banner is not None): | ||
83 | 186 | self._configured_banner_sent = True | 186 | self._configured_banner_sent = True |
85 | 187 | self.sendBanner(config.codehosting.banner) | 187 | self.sendBanner(self._banner) |
86 | 188 | return passed_through | 188 | return passed_through |
87 | 189 | 189 | ||
88 | 190 | def ssh_USERAUTH_REQUEST(self, packet): | 190 | def ssh_USERAUTH_REQUEST(self, packet): |
89 | 191 | 191 | ||
90 | === modified file 'lib/lp/codehosting/sshserver/service.py' | |||
91 | --- lib/lp/codehosting/sshserver/service.py 2010-03-19 10:43:51 +0000 | |||
92 | +++ lib/lp/codehosting/sshserver/service.py 2010-04-15 13:43:32 +0000 | |||
93 | @@ -30,6 +30,12 @@ | |||
94 | 30 | from lp.services.twistedsupport import gatherResults | 30 | from lp.services.twistedsupport import gatherResults |
95 | 31 | 31 | ||
96 | 32 | 32 | ||
97 | 33 | # The names of the key files of the server itself. The directory itself is | ||
98 | 34 | # given in config.codehosting.host_key_pair_path. | ||
99 | 35 | PRIVATE_KEY_FILE = 'ssh_host_key_rsa' | ||
100 | 36 | PUBLIC_KEY_FILE = 'ssh_host_key_rsa.pub' | ||
101 | 37 | |||
102 | 38 | |||
103 | 33 | class KeepAliveSettingSSHServerTransport(SSHServerTransport): | 39 | class KeepAliveSettingSSHServerTransport(SSHServerTransport): |
104 | 34 | 40 | ||
105 | 35 | def connectionMade(self): | 41 | def connectionMade(self): |
106 | @@ -37,6 +43,24 @@ | |||
107 | 37 | self.transport.setTcpKeepAlive(True) | 43 | self.transport.setTcpKeepAlive(True) |
108 | 38 | 44 | ||
109 | 39 | 45 | ||
110 | 46 | def get_key_path(key_filename): | ||
111 | 47 | key_directory = config.codehosting.host_key_pair_path | ||
112 | 48 | return os.path.join(config.root, key_directory, key_filename) | ||
113 | 49 | |||
114 | 50 | |||
115 | 51 | def make_portal(): | ||
116 | 52 | """Create and return a `Portal` for the SSH service. | ||
117 | 53 | |||
118 | 54 | This portal accepts SSH credentials and returns our customized SSH | ||
119 | 55 | avatars (see `lp.codehosting.sshserver.auth.LaunchpadAvatar`). | ||
120 | 56 | """ | ||
121 | 57 | authentication_proxy = Proxy( | ||
122 | 58 | config.codehosting.authentication_endpoint) | ||
123 | 59 | branchfs_proxy = Proxy(config.codehosting.branchfs_endpoint) | ||
124 | 60 | return get_portal(authentication_proxy, branchfs_proxy) | ||
125 | 61 | |||
126 | 62 | |||
127 | 63 | |||
128 | 40 | class Factory(SSHFactory): | 64 | class Factory(SSHFactory): |
129 | 41 | """SSH factory that uses the codehosting custom authentication. | 65 | """SSH factory that uses the codehosting custom authentication. |
130 | 42 | 66 | ||
131 | @@ -47,13 +71,29 @@ | |||
132 | 47 | 71 | ||
133 | 48 | protocol = KeepAliveSettingSSHServerTransport | 72 | protocol = KeepAliveSettingSSHServerTransport |
134 | 49 | 73 | ||
136 | 50 | def __init__(self, portal): | 74 | def __init__(self, portal, private_key, public_key, banner=None): |
137 | 75 | """Construct an SSH factory. | ||
138 | 76 | |||
139 | 77 | :param portal: The portal used to turn credentials into users. | ||
140 | 78 | :param private_key: The private key of the server, must be an RSA | ||
141 | 79 | key, given as a `twisted.conch.ssh.keys.Key` object. | ||
142 | 80 | :param public_key: The public key of the server, must be an RSA | ||
143 | 81 | key, given as a `twisted.conch.ssh.keys.Key` object. | ||
144 | 82 | :param banner: The text to display when users successfully log in. | ||
145 | 83 | """ | ||
146 | 51 | # Although 'portal' isn't part of the defined interface for | 84 | # Although 'portal' isn't part of the defined interface for |
147 | 52 | # `SSHFactory`, defining it here is how the `SSHUserAuthServer` gets | 85 | # `SSHFactory`, defining it here is how the `SSHUserAuthServer` gets |
148 | 53 | # at it. (Look for the beautiful line "self.portal = | 86 | # at it. (Look for the beautiful line "self.portal = |
149 | 54 | # self.transport.factory.portal"). | 87 | # self.transport.factory.portal"). |
150 | 55 | self.portal = portal | 88 | self.portal = portal |
152 | 56 | self.services['ssh-userauth'] = SSHUserAuthServer | 89 | self.services['ssh-userauth'] = self._makeAuthServer |
153 | 90 | self._private_key = private_key | ||
154 | 91 | self._public_key = public_key | ||
155 | 92 | self._banner = banner | ||
156 | 93 | |||
157 | 94 | def _makeAuthServer(self, *args, **kwargs): | ||
158 | 95 | kwargs['banner'] = self._banner | ||
159 | 96 | return SSHUserAuthServer(*args, **kwargs) | ||
160 | 57 | 97 | ||
161 | 58 | def buildProtocol(self, address): | 98 | def buildProtocol(self, address): |
162 | 59 | """Build an SSH protocol instance, logging the event. | 99 | """Build an SSH protocol instance, logging the event. |
163 | @@ -87,58 +127,51 @@ | |||
164 | 87 | notify(accesslog.AuthenticationFailed(transport)) | 127 | notify(accesslog.AuthenticationFailed(transport)) |
165 | 88 | notify(accesslog.UserDisconnected(transport)) | 128 | notify(accesslog.UserDisconnected(transport)) |
166 | 89 | 129 | ||
167 | 90 | def _loadKey(self, key_filename): | ||
168 | 91 | key_directory = config.codehosting.host_key_pair_path | ||
169 | 92 | key_path = os.path.join(config.root, key_directory, key_filename) | ||
170 | 93 | return Key.fromFile(key_path) | ||
171 | 94 | |||
172 | 95 | def getPublicKeys(self): | 130 | def getPublicKeys(self): |
173 | 96 | """Return the server's configured public key. | 131 | """Return the server's configured public key. |
174 | 97 | 132 | ||
175 | 98 | See `SSHFactory.getPublicKeys`. | 133 | See `SSHFactory.getPublicKeys`. |
176 | 99 | """ | 134 | """ |
179 | 100 | public_key = self._loadKey('ssh_host_key_rsa.pub') | 135 | return {'ssh-rsa': self._public_key} |
178 | 101 | return {'ssh-rsa': public_key} | ||
180 | 102 | 136 | ||
181 | 103 | def getPrivateKeys(self): | 137 | def getPrivateKeys(self): |
182 | 104 | """Return the server's configured private key. | 138 | """Return the server's configured private key. |
183 | 105 | 139 | ||
184 | 106 | See `SSHFactory.getPrivateKeys`. | 140 | See `SSHFactory.getPrivateKeys`. |
185 | 107 | """ | 141 | """ |
188 | 108 | private_key = self._loadKey('ssh_host_key_rsa') | 142 | return {'ssh-rsa': self._private_key} |
187 | 109 | return {'ssh-rsa': private_key} | ||
189 | 110 | 143 | ||
190 | 111 | 144 | ||
191 | 112 | class SSHService(service.Service): | 145 | class SSHService(service.Service): |
192 | 113 | """A Twisted service for the codehosting SSH server.""" | 146 | """A Twisted service for the codehosting SSH server.""" |
193 | 114 | 147 | ||
211 | 115 | def __init__(self): | 148 | def __init__(self, portal, private_key_path, public_key_path, |
212 | 116 | self.service = self.makeService() | 149 | strport='tcp:22', idle_timeout=3600, banner=None): |
213 | 117 | 150 | """Construct an SSH service. | |
214 | 118 | def makePortal(self): | 151 | |
215 | 119 | """Create and return a `Portal` for the SSH service. | 152 | :param portal: The `Portal` that turns authentication requests into |
216 | 120 | 153 | views on the system. | |
217 | 121 | This portal accepts SSH credentials and returns our customized SSH | 154 | :param private_key_path: The path to the SSH server's private key. |
218 | 122 | avatars (see `lp.codehosting.sshserver.auth.LaunchpadAvatar`). | 155 | :param public_key_path: The path to the SSH server's public key. |
219 | 123 | """ | 156 | :param strport: The port to run the server on, expressed in Twisted's |
220 | 124 | authentication_proxy = Proxy( | 157 | "strports" mini-language. Defaults to 'tcp:22'. |
221 | 125 | config.codehosting.authentication_endpoint) | 158 | :param idle_timeout: The number of seconds to wait before killing a |
222 | 126 | branchfs_proxy = Proxy(config.codehosting.branchfs_endpoint) | 159 | connection that isn't doing anything. Defaults to 3600. |
223 | 127 | return get_portal(authentication_proxy, branchfs_proxy) | 160 | :param banner: An announcement printed to users when they connect. |
224 | 128 | 161 | By default, announce nothing. | |
208 | 129 | def makeService(self): | ||
209 | 130 | """Return a service that provides an SFTP server. This is called in | ||
210 | 131 | the constructor. | ||
225 | 132 | """ | 162 | """ |
226 | 133 | ssh_factory = TimeoutFactory( | 163 | ssh_factory = TimeoutFactory( |
230 | 134 | Factory(self.makePortal()), | 164 | Factory( |
231 | 135 | timeoutPeriod=config.codehosting.idle_timeout) | 165 | portal, |
232 | 136 | return strports.service(config.codehosting.port, ssh_factory) | 166 | private_key=Key.fromFile(private_key_path), |
233 | 167 | public_key=Key.fromFile(public_key_path), | ||
234 | 168 | banner=banner), | ||
235 | 169 | timeoutPeriod=idle_timeout) | ||
236 | 170 | self.service = strports.service(strport, ssh_factory) | ||
237 | 137 | 171 | ||
238 | 138 | def startService(self): | 172 | def startService(self): |
239 | 139 | """Start the SSH service.""" | 173 | """Start the SSH service.""" |
242 | 140 | accesslog.LoggingManager().setUp( | 174 | accesslog.LoggingManager().setUp(configure_oops_reporting=True) |
241 | 141 | configure_oops_reporting=True, mangle_stdout=True) | ||
243 | 142 | notify(accesslog.ServerStarting()) | 175 | notify(accesslog.ServerStarting()) |
244 | 143 | # By default, only the owner of files should be able to write to them. | 176 | # By default, only the owner of files should be able to write to them. |
245 | 144 | # Perhaps in the future this line will be deleted and the umask | 177 | # Perhaps in the future this line will be deleted and the umask |
246 | 145 | 178 | ||
247 | === modified file 'lib/lp/codehosting/sshserver/tests/test_auth.py' | |||
248 | --- lib/lp/codehosting/sshserver/tests/test_auth.py 2010-03-19 10:43:51 +0000 | |||
249 | +++ lib/lp/codehosting/sshserver/tests/test_auth.py 2010-04-15 13:43:32 +0000 | |||
250 | @@ -1,4 +1,4 @@ | |||
252 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
253 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
254 | 3 | 3 | ||
255 | 4 | import os | 4 | import os |
256 | @@ -193,11 +193,6 @@ | |||
257 | 193 | 193 | ||
258 | 194 | layer = TwistedLayer | 194 | layer = TwistedLayer |
259 | 195 | 195 | ||
260 | 196 | banner_conf = """ | ||
261 | 197 | [codehosting] | ||
262 | 198 | banner: banner | ||
263 | 199 | """ | ||
264 | 200 | |||
265 | 201 | def setUp(self): | 196 | def setUp(self): |
266 | 202 | UserAuthServerMixin.setUp(self) | 197 | UserAuthServerMixin.setUp(self) |
267 | 203 | self.portal.registerChecker(MockChecker()) | 198 | self.portal.registerChecker(MockChecker()) |
268 | @@ -240,25 +235,22 @@ | |||
269 | 240 | self.assertMessageOrder([userauth.MSG_USERAUTH_SUCCESS]) | 235 | self.assertMessageOrder([userauth.MSG_USERAUTH_SUCCESS]) |
270 | 241 | return d.addCallback(check) | 236 | return d.addCallback(check) |
271 | 242 | 237 | ||
274 | 243 | def test_configuredBannerSentOnSuccess(self): | 238 | def test_defaultBannerSentOnSuccess(self): |
275 | 244 | # If a banner is set in the codehosting config then we send it to the | 239 | # If a banner was passed to the user auth agent then we send it to the |
276 | 245 | # user when they log in. | 240 | # user when they log in. |
278 | 246 | config.push('codehosting_overlay', self.banner_conf) | 241 | self.user_auth._banner = "Boogedy boo" |
279 | 247 | d = self.requestSuccessfulAuthentication() | 242 | d = self.requestSuccessfulAuthentication() |
280 | 248 | def check(ignored): | 243 | def check(ignored): |
281 | 249 | self.assertMessageOrder( | 244 | self.assertMessageOrder( |
282 | 250 | [userauth.MSG_USERAUTH_BANNER, userauth.MSG_USERAUTH_SUCCESS]) | 245 | [userauth.MSG_USERAUTH_BANNER, userauth.MSG_USERAUTH_SUCCESS]) |
288 | 251 | self.assertBannerSent(config.codehosting.banner + '\r\n') | 246 | self.assertBannerSent(self.user_auth._banner + '\r\n') |
289 | 252 | def cleanup(ignored): | 247 | return d.addCallback(check) |
285 | 253 | config.pop('codehosting_overlay') | ||
286 | 254 | return ignored | ||
287 | 255 | return d.addCallback(check).addBoth(cleanup) | ||
290 | 256 | 248 | ||
292 | 257 | def test_configuredBannerSentOnlyOnce(self): | 249 | def test_defaultBannerSentOnlyOnce(self): |
293 | 258 | # We don't send the banner on each authentication attempt, just on the | 250 | # We don't send the banner on each authentication attempt, just on the |
294 | 259 | # first one. It is usual for there to be many authentication attempts | 251 | # first one. It is usual for there to be many authentication attempts |
295 | 260 | # per SSH session. | 252 | # per SSH session. |
297 | 261 | config.push('codehosting_overlay', self.banner_conf) | 253 | self.user_auth._banner = "Boogedy boo" |
298 | 262 | 254 | ||
299 | 263 | d = self.requestUnsupportedAuthentication() | 255 | d = self.requestUnsupportedAuthentication() |
300 | 264 | d.addCallback(lambda ignored: self.requestSuccessfulAuthentication()) | 256 | d.addCallback(lambda ignored: self.requestSuccessfulAuthentication()) |
301 | @@ -268,17 +260,14 @@ | |||
302 | 268 | self.assertMessageOrder( | 260 | self.assertMessageOrder( |
303 | 269 | [userauth.MSG_USERAUTH_FAILURE, userauth.MSG_USERAUTH_BANNER, | 261 | [userauth.MSG_USERAUTH_FAILURE, userauth.MSG_USERAUTH_BANNER, |
304 | 270 | userauth.MSG_USERAUTH_SUCCESS]) | 262 | userauth.MSG_USERAUTH_SUCCESS]) |
314 | 271 | self.assertBannerSent(config.codehosting.banner + '\r\n') | 263 | self.assertBannerSent(self.user_auth._banner + '\r\n') |
315 | 272 | 264 | ||
316 | 273 | def cleanup(ignored): | 265 | return d.addCallback(check) |
317 | 274 | config.pop('codehosting_overlay') | 266 | |
318 | 275 | return ignored | 267 | def test_defaultBannerNotSentOnFailure(self): |
319 | 276 | return d.addCallback(check).addBoth(cleanup) | 268 | # Failed authentication attempts do not get the default banner |
311 | 277 | |||
312 | 278 | def test_configuredBannerNotSentOnFailure(self): | ||
313 | 279 | # Failed authentication attempts do not get the configured banner | ||
320 | 280 | # sent. | 269 | # sent. |
322 | 281 | config.push('codehosting_overlay', self.banner_conf) | 270 | self.user_auth._banner = "You come away two hundred quid down" |
323 | 282 | 271 | ||
324 | 283 | d = self.requestFailedAuthentication() | 272 | d = self.requestFailedAuthentication() |
325 | 284 | 273 | ||
326 | @@ -287,11 +276,7 @@ | |||
327 | 287 | [userauth.MSG_USERAUTH_BANNER, userauth.MSG_USERAUTH_FAILURE]) | 276 | [userauth.MSG_USERAUTH_BANNER, userauth.MSG_USERAUTH_FAILURE]) |
328 | 288 | self.assertBannerSent(MockChecker.error_message + '\r\n') | 277 | self.assertBannerSent(MockChecker.error_message + '\r\n') |
329 | 289 | 278 | ||
335 | 290 | def cleanup(ignored): | 279 | return d.addCallback(check) |
331 | 291 | config.pop('codehosting_overlay') | ||
332 | 292 | return ignored | ||
333 | 293 | |||
334 | 294 | return d.addCallback(check).addBoth(cleanup) | ||
336 | 295 | 280 | ||
337 | 296 | def test_loggedToBanner(self): | 281 | def test_loggedToBanner(self): |
338 | 297 | # When there's an authentication failure, we display an informative | 282 | # When there's an authentication failure, we display an informative |
339 | @@ -532,7 +517,12 @@ | |||
340 | 532 | 517 | ||
341 | 533 | def makeFactory(self): | 518 | def makeFactory(self): |
342 | 534 | """Create and start the factory that our SSH server uses.""" | 519 | """Create and start the factory that our SSH server uses.""" |
344 | 535 | factory = service.Factory(auth.get_portal(None, None)) | 520 | factory = service.Factory( |
345 | 521 | auth.get_portal(None, None), | ||
346 | 522 | private_key=Key.fromFile( | ||
347 | 523 | service.get_key_path(service.PRIVATE_KEY_FILE)), | ||
348 | 524 | public_key=Key.fromFile( | ||
349 | 525 | service.get_key_path(service.PUBLIC_KEY_FILE))) | ||
350 | 536 | factory.startFactory() | 526 | factory.startFactory() |
351 | 537 | return factory | 527 | return factory |
352 | 538 | 528 |
Well, I certainly like the idea of this branch.
I think SSHService. __init_ _'s port argument should perhaps be called strport. I'd expect 'port' to be an integer (I guess the config item is poorly named too, but that's harder to fix).
The parameterization around the key path names smells a little funny to me. Would it be safe for SSHService to just take the directory and assume the file names? Maybe not...
I think service. Factory. __doc__ could be clearer about the expected types of the key arguments.
Um, I think that's it.
Thanks for working on this!