3 import binascii
, base64
4 from twisted
.python
import log
5 from twisted
.application
import service
, strports
6 from twisted
.cred
import checkers
, portal
7 from twisted
.conch
import manhole
, telnet
, manhole_ssh
, checkers
as conchc
8 from twisted
.conch
.insults
import insults
9 from twisted
.internet
import protocol
11 from buildbot
.util
import ComparableMixin
12 from zope
.interface
import implements
# requires Twisted-2.0 or later
14 # makeTelnetProtocol and _TelnetRealm are for the TelnetManhole
16 class makeTelnetProtocol
:
17 # this curries the 'portal' argument into a later call to
19 def __init__(self
, portal
):
23 auth
= telnet
.AuthenticatingTelnetProtocol
24 return telnet
.TelnetTransport(auth
, self
.portal
)
27 implements(portal
.IRealm
)
29 def __init__(self
, namespace_maker
):
30 self
.namespace_maker
= namespace_maker
32 def requestAvatar(self
, avatarId
, *interfaces
):
33 if telnet
.ITelnetProtocol
in interfaces
:
34 namespace
= self
.namespace_maker()
35 p
= telnet
.TelnetBootstrapProtocol(insults
.ServerProtocol
,
36 manhole
.ColoredManhole
,
38 return (telnet
.ITelnetProtocol
, p
, lambda: None)
39 raise NotImplementedError()
42 class chainedProtocolFactory
:
43 # this curries the 'namespace' argument into a later call to
44 # chainedProtocolFactory()
45 def __init__(self
, namespace
):
46 self
.namespace
= namespace
49 return insults
.ServerProtocol(manhole
.ColoredManhole
, self
.namespace
)
51 class AuthorizedKeysChecker(conchc
.SSHPublicKeyDatabase
):
52 """Accept connections using SSH keys from a given file.
54 SSHPublicKeyDatabase takes the username that the prospective client has
55 requested and attempts to get a ~/.ssh/authorized_keys file for that
56 username. This requires root access, so it isn't as useful as you'd
59 Instead, this subclass looks for keys in a single file, given as an
60 argument. This file is typically kept in the buildmaster's basedir. The
61 file should have 'ssh-dss ....' lines in it, just like authorized_keys.
64 def __init__(self
, authorized_keys_file
):
65 self
.authorized_keys_file
= os
.path
.expanduser(authorized_keys_file
)
67 def checkKey(self
, credentials
):
68 f
= open(self
.authorized_keys_file
)
69 for l
in f
.readlines():
74 if base64
.decodestring(l2
[1]) == credentials
.blob
:
76 except binascii
.Error
:
81 class _BaseManhole(service
.MultiService
):
82 """This provides remote access to a python interpreter (a read/exec/print
83 loop) embedded in the buildmaster via an internal SSH server. This allows
84 detailed inspection of the buildmaster state. It is of most use to
85 buildbot developers. Connect to this by running an ssh client.
88 def __init__(self
, port
, checker
, using_ssh
=True):
90 @type port: string or int
91 @param port: what port should the Manhole listen on? This is a
92 strports specification string, like 'tcp:12345' or
93 'tcp:12345:interface=127.0.0.1'. Bare integers are treated as a
96 @type checker: an object providing the
97 L{twisted.cred.checkers.ICredentialsChecker} interface
98 @param checker: if provided, this checker is used to authenticate the
99 client instead of using the username/password scheme. You must either
100 provide a username/password or a Checker. Some useful values are::
101 import twisted.cred.checkers as credc
102 import twisted.conch.checkers as conchc
103 c = credc.AllowAnonymousAccess # completely open
104 c = credc.FilePasswordDB(passwd_filename) # file of name:passwd
105 c = conchc.UNIXPasswordDatabase # getpwnam() (probably /etc/passwd)
107 @type using_ssh: bool
108 @param using_ssh: If True, accept SSH connections. If False, accept
109 regular unencrypted telnet connections.
112 # unfortunately, these don't work unless we're running as root
113 #c = credc.PluggableAuthenticationModulesChecker: PAM
114 #c = conchc.SSHPublicKeyDatabase() # ~/.ssh/authorized_keys
115 # and I can't get UNIXPasswordDatabase to work
117 service
.MultiService
.__init
__(self
)
118 if type(port
) is int:
119 port
= "tcp:%d" % port
120 self
.port
= port
# for comparison later
121 self
.checker
= checker
# to maybe compare later
124 # close over 'self' so we can get access to .parent later
128 'status': master
.getStatus(),
133 namespace
= makeNamespace()
134 p
= insults
.ServerProtocol(manhole
.ColoredManhole
, namespace
)
137 self
.using_ssh
= using_ssh
139 r
= manhole_ssh
.TerminalRealm()
140 r
.chainedProtocolFactory
= makeProtocol
141 p
= portal
.Portal(r
, [self
.checker
])
142 f
= manhole_ssh
.ConchFactory(p
)
144 r
= _TelnetRealm(makeNamespace
)
145 p
= portal
.Portal(r
, [self
.checker
])
146 f
= protocol
.ServerFactory()
147 f
.protocol
= makeTelnetProtocol(p
)
148 s
= strports
.service(self
.port
, f
)
149 s
.setServiceParent(self
)
152 def startService(self
):
153 service
.MultiService
.startService(self
)
158 log
.msg("Manhole listening %s on port %s" % (via
, self
.port
))
161 class TelnetManhole(_BaseManhole
, ComparableMixin
):
162 """This Manhole accepts unencrypted (telnet) connections, and requires a
163 username and password authorize access. You are encouraged to use the
164 encrypted ssh-based manhole classes instead."""
166 compare_attrs
= ["port", "username", "password"]
168 def __init__(self
, port
, username
, password
):
170 @type port: string or int
171 @param port: what port should the Manhole listen on? This is a
172 strports specification string, like 'tcp:12345' or
173 'tcp:12345:interface=127.0.0.1'. Bare integers are treated as a
177 @param password: username= and password= form a pair of strings to
178 use when authenticating the remote user.
181 self
.username
= username
182 self
.password
= password
184 c
= checkers
.InMemoryUsernamePasswordDatabaseDontUse()
185 c
.addUser(username
, password
)
187 _BaseManhole
.__init
__(self
, port
, c
, using_ssh
=False)
189 class PasswordManhole(_BaseManhole
, ComparableMixin
):
190 """This Manhole accepts encrypted (ssh) connections, and requires a
191 username and password to authorize access.
194 compare_attrs
= ["port", "username", "password"]
196 def __init__(self
, port
, username
, password
):
198 @type port: string or int
199 @param port: what port should the Manhole listen on? This is a
200 strports specification string, like 'tcp:12345' or
201 'tcp:12345:interface=127.0.0.1'. Bare integers are treated as a
205 @param password: username= and password= form a pair of strings to
206 use when authenticating the remote user.
209 self
.username
= username
210 self
.password
= password
212 c
= checkers
.InMemoryUsernamePasswordDatabaseDontUse()
213 c
.addUser(username
, password
)
215 _BaseManhole
.__init
__(self
, port
, c
)
217 class AuthorizedKeysManhole(_BaseManhole
, ComparableMixin
):
218 """This Manhole accepts ssh connections, and requires that the
219 prospective client have an ssh private key that matches one of the public
220 keys in our authorized_keys file. It is created with the name of a file
221 that contains the public keys that we will accept."""
223 compare_attrs
= ["port", "keyfile"]
225 def __init__(self
, port
, keyfile
):
227 @type port: string or int
228 @param port: what port should the Manhole listen on? This is a
229 strports specification string, like 'tcp:12345' or
230 'tcp:12345:interface=127.0.0.1'. Bare integers are treated as a
233 @param keyfile: the name of a file (relative to the buildmaster's
234 basedir) that contains SSH public keys of authorized
235 users, one per line. This is the exact same format
236 as used by sshd in ~/.ssh/authorized_keys .
239 # TODO: expanduser this, and make it relative to the buildmaster's
241 self
.keyfile
= keyfile
242 c
= AuthorizedKeysChecker(keyfile
)
243 _BaseManhole
.__init
__(self
, port
, c
)
245 class ArbitraryCheckerManhole(_BaseManhole
, ComparableMixin
):
246 """This Manhole accepts ssh connections, but uses an arbitrary
247 user-supplied 'checker' object to perform authentication."""
249 compare_attrs
= ["port", "checker"]
251 def __init__(self
, port
, checker
):
253 @type port: string or int
254 @param port: what port should the Manhole listen on? This is a
255 strports specification string, like 'tcp:12345' or
256 'tcp:12345:interface=127.0.0.1'. Bare integers are treated as a
259 @param checker: an instance of a twisted.cred 'checker' which will
260 perform authentication
263 _BaseManhole
.__init
__(self
, port
, checker
)