~launchpad-pqm/launchpad/devel

12221.16.16 by Andrew Bennetts
Reorder some imports and update copyright years as requested by Aaron's review.
1
# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
8687.15.17 by Karl Fogel
Add the copyright header block to the rest of the files under lib/lp/.
2
# GNU Affero General Public License version 3 (see the file LICENSE).
7483.1.28 by Jonathan Lange
Significantly refactor the service module to make it documentable.
3
10693.5.5 by Jonathan Lange
Move auth logic to services
4
"""Twisted `service.Service` class for the Launchpad SSH server.
7483.1.28 by Jonathan Lange
Significantly refactor the service module to make it documentable.
5
6
An `SSHService` object can be used to launch the SSH server.
3858.1.5 by jml at canonical
Move SFTP server in-process
7
"""
8
9
__metaclass__ = type
7483.1.18 by Jonathan Lange
Tweak __all__ defns
10
__all__ = [
11
    'SSHService',
12
    ]
3858.1.5 by jml at canonical
Move SFTP server in-process
13
14
10693.3.13 by Jonathan Lange
Oops, missed the most important invocation
15
import logging
3858.1.5 by jml at canonical
Move SFTP server in-process
16
import os
17
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
18
from twisted.application import (
19
    service,
20
    strports,
21
    )
7483.1.17 by Jonathan Lange
Import classes directly. This makes the contract between modules clearer.
22
from twisted.conch.ssh.factory import SSHFactory
6789.11.2 by Michael Hudson
rewrite TestPublicKeyFromLaunchpadChecker to use a fake authentication
23
from twisted.conch.ssh.keys import Key
7033.2.6 by Michael Hudson
merge trunk, but...
24
from twisted.conch.ssh.transport import SSHServerTransport
7033.2.9 by Michael Hudson
lint
25
from twisted.internet import defer
7483.2.41 by Jonathan Lange
Use the zope.event system for handling the logging events.
26
from zope.event import notify
27
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
28
from lp.services.sshserver import (
29
    accesslog,
30
    events,
31
    )
10693.5.5 by Jonathan Lange
Move auth logic to services
32
from lp.services.sshserver.auth import SSHUserAuthServer
10548.1.1 by Jonathan Lange
Move twistedsupport to lp.services
33
from lp.services.twistedsupport import gatherResults
10693.3.4 by Jonathan Lange
No point in parametrizing oops reporting
34
from lp.services.twistedsupport.loggingsupport import set_up_oops_reporting
7483.1.17 by Jonathan Lange
Import classes directly. This makes the contract between modules clearer.
35
36
7033.2.6 by Michael Hudson
merge trunk, but...
37
class KeepAliveSettingSSHServerTransport(SSHServerTransport):
38
39
    def connectionMade(self):
7033.2.8 by Michael Hudson
this works
40
        SSHServerTransport.connectionMade(self)
7033.2.6 by Michael Hudson
merge trunk, but...
41
        self.transport.setTcpKeepAlive(True)
42
43
7483.1.17 by Jonathan Lange
Import classes directly. This makes the contract between modules clearer.
44
class Factory(SSHFactory):
10693.3.21 by Jonathan Lange
More references to codehosting that are not necessary
45
    """SSH factory that uses Launchpad's custom authentication.
7483.1.28 by Jonathan Lange
Significantly refactor the service module to make it documentable.
46
47
    This class tells the SSH service to use our custom authentication service
7483.2.38 by Jonathan Lange
Merge in changes from dependent branch, resolving conflicts.
48
    and configures the host keys for the SSH server. It also logs connection
49
    to and disconnection from the SSH server.
7483.1.28 by Jonathan Lange
Significantly refactor the service module to make it documentable.
50
    """
51
10363.1.4 by Michael Hudson
remove another workaround for a fixed twisted bug
52
    protocol = KeepAliveSettingSSHServerTransport
53
10693.2.6 by Jonathan Lange
Parametrize banner up from the factory
54
    def __init__(self, portal, private_key, public_key, banner=None):
10693.2.4 by Jonathan Lange
Parametrize the keys in the factory
55
        """Construct an SSH factory.
56
57
        :param portal: The portal used to turn credentials into users.
10693.2.17 by Jonathan Lange
Docstring improvements as per code review
58
        :param private_key: The private key of the server, must be an RSA
59
            key, given as a `twisted.conch.ssh.keys.Key` object.
60
        :param public_key: The public key of the server, must be an RSA
61
            key, given as a `twisted.conch.ssh.keys.Key` object.
10693.2.6 by Jonathan Lange
Parametrize banner up from the factory
62
        :param banner: The text to display when users successfully log in.
10693.2.4 by Jonathan Lange
Parametrize the keys in the factory
63
        """
7483.1.28 by Jonathan Lange
Significantly refactor the service module to make it documentable.
64
        # Although 'portal' isn't part of the defined interface for
65
        # `SSHFactory`, defining it here is how the `SSHUserAuthServer` gets
66
        # at it. (Look for the beautiful line "self.portal =
67
        # self.transport.factory.portal").
68
        self.portal = portal
10693.2.6 by Jonathan Lange
Parametrize banner up from the factory
69
        self.services['ssh-userauth'] = self._makeAuthServer
10693.2.4 by Jonathan Lange
Parametrize the keys in the factory
70
        self._private_key = private_key
71
        self._public_key = public_key
10693.2.6 by Jonathan Lange
Parametrize banner up from the factory
72
        self._banner = banner
10693.2.5 by Jonathan Lange
Parametrize the banner in the UserAuth agent
73
74
    def _makeAuthServer(self, *args, **kwargs):
10693.2.6 by Jonathan Lange
Parametrize banner up from the factory
75
        kwargs['banner'] = self._banner
10693.2.5 by Jonathan Lange
Parametrize the banner in the UserAuth agent
76
        return SSHUserAuthServer(*args, **kwargs)
7483.1.16 by Jonathan Lange
Move the Factory object to the service module.
77
7483.2.34 by Jonathan Lange
Log user connection, and change the 'session id' so that it can be tracked
78
    def buildProtocol(self, address):
7483.2.38 by Jonathan Lange
Merge in changes from dependent branch, resolving conflicts.
79
        """Build an SSH protocol instance, logging the event.
80
81
        The protocol object we return is slightly modified so that we can hook
82
        into the 'connectionLost' event and log the disconnection.
83
        """
10363.1.4 by Michael Hudson
remove another workaround for a fixed twisted bug
84
        transport = SSHFactory.buildProtocol(self, address)
7483.2.35 by Jonathan Lange
Log actual TCP disconnect.
85
        transport._realConnectionLost = transport.connectionLost
86
        transport.connectionLost = (
87
            lambda reason: self.connectionLost(transport, reason))
10693.3.1 by Jonathan Lange
Split the events classes into their own module.
88
        notify(events.UserConnected(transport, address))
7483.2.34 by Jonathan Lange
Log user connection, and change the 'session id' so that it can be tracked
89
        return transport
90
7483.2.35 by Jonathan Lange
Log actual TCP disconnect.
91
    def connectionLost(self, transport, reason):
7483.2.38 by Jonathan Lange
Merge in changes from dependent branch, resolving conflicts.
92
        """Call 'connectionLost' on 'transport', logging the event."""
7483.2.35 by Jonathan Lange
Log actual TCP disconnect.
93
        try:
94
            return transport._realConnectionLost(reason)
95
        finally:
7483.2.50 by Jonathan Lange
Generate an event when authentication fails.
96
            # Conch's userauth module sets 'avatar' on the transport if the
97
            # authentication succeeded. Thus, if it's not there,
98
            # authentication failed. We can't generate this event from the
99
            # authentication layer since:
100
            #
101
            # a) almost every SSH login has at least one failure to
102
            # authenticate due to multiple keys on the client-side.
103
            #
104
            # b) the server doesn't normally generate a "go away" event.
105
            # Rather, the client simply stops trying.
106
            if getattr(transport, 'avatar', None) is None:
10693.3.1 by Jonathan Lange
Split the events classes into their own module.
107
                notify(events.AuthenticationFailed(transport))
108
            notify(events.UserDisconnected(transport))
7483.2.35 by Jonathan Lange
Log actual TCP disconnect.
109
7483.1.28 by Jonathan Lange
Significantly refactor the service module to make it documentable.
110
    def getPublicKeys(self):
111
        """Return the server's configured public key.
112
113
        See `SSHFactory.getPublicKeys`.
114
        """
10693.2.4 by Jonathan Lange
Parametrize the keys in the factory
115
        return {'ssh-rsa': self._public_key}
7483.1.28 by Jonathan Lange
Significantly refactor the service module to make it documentable.
116
117
    def getPrivateKeys(self):
118
        """Return the server's configured private key.
119
120
        See `SSHFactory.getPrivateKeys`.
121
        """
10693.2.4 by Jonathan Lange
Parametrize the keys in the factory
122
        return {'ssh-rsa': self._private_key}
7483.2.32 by Jonathan Lange
Log server start and stop to access log.
123
7483.1.16 by Jonathan Lange
Move the Factory object to the service module.
124
4292.1.31 by Jonathan Lange
Rename SFTPService to SSHService
125
class SSHService(service.Service):
10693.3.21 by Jonathan Lange
More references to codehosting that are not necessary
126
    """A Twisted service for the SSH server."""
3858.1.10 by jml at canonical
Turn the 'setup' code in daemons/ into Services.
127
10693.2.11 by Jonathan Lange
Parametrize a bunch of other things
128
    def __init__(self, portal, private_key_path, public_key_path,
10693.3.17 by Jonathan Lange
Push all of the config out to the service
129
                 oops_configuration, main_log, access_log,
12221.16.1 by Andrew Bennetts
Initial hackery towards #702024.
130
                 access_log_path, strport='tcp:22', factory_decorator=None,
12221.16.7 by Andrew Bennetts
Remove obsolete idle_timeout param.
131
                 banner=None):
10693.2.9 by Jonathan Lange
Push the key path parametrization out to the SSH service
132
        """Construct an SSH service.
133
10918.2.1 by Steve Kowalik
Merge in changes for plumbling for poppy-sftp
134
        :param portal: The `twisted.cred.portal.Portal` that turns
135
            authentication requests into views on the system.
10693.2.9 by Jonathan Lange
Push the key path parametrization out to the SSH service
136
        :param private_key_path: The path to the SSH server's private key.
137
        :param public_key_path: The path to the SSH server's public key.
10693.3.17 by Jonathan Lange
Push all of the config out to the service
138
        :param oops_configuration: The section of the configuration file with
139
            the OOPS config details for this server.
10918.2.1 by Steve Kowalik
Merge in changes for plumbling for poppy-sftp
140
        :param main_log: The name of the logger to log most of the server
10693.3.17 by Jonathan Lange
Push all of the config out to the service
141
            stuff to.
10918.2.1 by Steve Kowalik
Merge in changes for plumbling for poppy-sftp
142
        :param access_log: The name of the logger object to log the server
143
            access details to.
10693.3.17 by Jonathan Lange
Push all of the config out to the service
144
        :param access_log_path: The path to the access log file.
10693.2.17 by Jonathan Lange
Docstring improvements as per code review
145
        :param strport: The port to run the server on, expressed in Twisted's
10693.2.11 by Jonathan Lange
Parametrize a bunch of other things
146
            "strports" mini-language. Defaults to 'tcp:22'.
12221.16.15 by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review.
147
        :param factory_decorator: An optional callable that can decorate the
148
            server factory (e.g. with a
149
            `twisted.protocols.policies.TimeoutFactory`).  It takes one
150
            argument, a factory, and must return a factory.
10693.2.11 by Jonathan Lange
Parametrize a bunch of other things
151
        :param banner: An announcement printed to users when they connect.
152
            By default, announce nothing.
10693.2.9 by Jonathan Lange
Push the key path parametrization out to the SSH service
153
        """
12221.16.1 by Andrew Bennetts
Initial hackery towards #702024.
154
        ssh_factory = Factory(
155
            portal,
156
            private_key=Key.fromFile(private_key_path),
157
            public_key=Key.fromFile(public_key_path),
158
            banner=banner)
159
        if factory_decorator is not None:
160
            ssh_factory = factory_decorator(ssh_factory)
10693.2.17 by Jonathan Lange
Docstring improvements as per code review
161
        self.service = strports.service(strport, ssh_factory)
10693.3.17 by Jonathan Lange
Push all of the config out to the service
162
        self._oops_configuration = oops_configuration
163
        self._main_log = main_log
164
        self._access_log = access_log
165
        self._access_log_path = access_log_path
3858.1.10 by jml at canonical
Turn the 'setup' code in daemons/ into Services.
166
167
    def startService(self):
7483.1.28 by Jonathan Lange
Significantly refactor the service module to make it documentable.
168
        """Start the SSH service."""
10693.3.13 by Jonathan Lange
Oops, missed the most important invocation
169
        manager = accesslog.LoggingManager(
10693.3.17 by Jonathan Lange
Push all of the config out to the service
170
            logging.getLogger(self._main_log),
171
            logging.getLogger(self._access_log_path),
172
            self._access_log_path)
10693.3.10 by Jonathan Lange
Parametrize the loggers access path
173
        manager.setUp()
10693.3.1 by Jonathan Lange
Split the events classes into their own module.
174
        notify(events.ServerStarting())
7483.1.28 by Jonathan Lange
Significantly refactor the service module to make it documentable.
175
        # By default, only the owner of files should be able to write to them.
176
        # Perhaps in the future this line will be deleted and the umask
177
        # managed by the startup script.
178
        os.umask(0022)
3858.1.10 by jml at canonical
Turn the 'setup' code in daemons/ into Services.
179
        service.Service.startService(self)
180
        self.service.startService()
181
182
    def stopService(self):
7483.1.28 by Jonathan Lange
Significantly refactor the service module to make it documentable.
183
        """Stop the SSH service."""
7483.2.62 by Jonathan Lange
A raft of review comments.
184
        deferred = gatherResults([
185
            defer.maybeDeferred(service.Service.stopService, self),
186
            defer.maybeDeferred(self.service.stopService)])
14198.1.1 by Jeroen Vermeulen
Lint.
187
7483.2.48 by Jonathan Lange
Handle return values of stopService correctly.
188
        def log_stopped(ignored):
10693.3.1 by Jonathan Lange
Split the events classes into their own module.
189
            notify(events.ServerStopped())
7483.2.48 by Jonathan Lange
Handle return values of stopService correctly.
190
            return ignored
14198.1.1 by Jeroen Vermeulen
Lint.
191
7483.2.48 by Jonathan Lange
Handle return values of stopService correctly.
192
        return deferred.addBoth(log_stopped)