Package buildbot :: Module manhole
[frames] | no frames]

Source Code for Module buildbot.manhole

  1   
  2  import os.path 
  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 
 10   
 11  from buildbot.util import ComparableMixin 
 12  from zope.interface import implements # requires Twisted-2.0 or later 
 13   
 14  # makeTelnetProtocol and _TelnetRealm are for the TelnetManhole 
 15   
16 -class makeTelnetProtocol:
17 # this curries the 'portal' argument into a later call to 18 # TelnetTransport()
19 - def __init__(self, portal):
20 self.portal = portal
21
22 - def __call__(self):
23 auth = telnet.AuthenticatingTelnetProtocol 24 return telnet.TelnetTransport(auth, self.portal)
25
26 -class _TelnetRealm:
27 implements(portal.IRealm) 28
29 - def __init__(self, namespace_maker):
30 self.namespace_maker = namespace_maker
31
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, 37 namespace) 38 return (telnet.ITelnetProtocol, p, lambda: None) 39 raise NotImplementedError()
40 41
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
47
48 - def __call__(self):
49 return insults.ServerProtocol(manhole.ColoredManhole, self.namespace)
50
51 -class AuthorizedKeysChecker(conchc.SSHPublicKeyDatabase):
52 """Accept connections using SSH keys from a given file. 53 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 57 like. 58 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. 62 """ 63
64 - def __init__(self, authorized_keys_file):
65 self.authorized_keys_file = os.path.expanduser(authorized_keys_file)
66
67 - def checkKey(self, credentials):
68 f = open(self.authorized_keys_file) 69 for l in f.readlines(): 70 l2 = l.split() 71 if len(l2) < 2: 72 continue 73 try: 74 if base64.decodestring(l2[1]) == credentials.blob: 75 return 1 76 except binascii.Error: 77 continue 78 return 0
79 80
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. 86 """ 87
88 - def __init__(self, port, checker, using_ssh=True):
89 """ 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 94 simple tcp port. 95 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) 106 107 @type using_ssh: bool 108 @param using_ssh: If True, accept SSH connections. If False, accept 109 regular unencrypted telnet connections. 110 """ 111 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 116 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 122 123 def makeNamespace(): 124 # close over 'self' so we can get access to .parent later 125 master = self.parent 126 namespace = { 127 'master': master, 128 'status': master.getStatus(), 129 } 130 return namespace
131 132 def makeProtocol(): 133 namespace = makeNamespace() 134 p = insults.ServerProtocol(manhole.ColoredManhole, namespace) 135 return p
136 137 self.using_ssh = using_ssh 138 if using_ssh: 139 r = manhole_ssh.TerminalRealm() 140 r.chainedProtocolFactory = makeProtocol 141 p = portal.Portal(r, [self.checker]) 142 f = manhole_ssh.ConchFactory(p) 143 else: 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) 150 151
152 - def startService(self):
153 service.MultiService.startService(self) 154 if self.using_ssh: 155 via = "via SSH" 156 else: 157 via = "via telnet" 158 log.msg("Manhole listening %s on port %s" % (via, self.port))
159 160
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.""" 165 166 compare_attrs = ["port", "username", "password"] 167
168 - def __init__(self, port, username, password):
169 """ 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 174 simple tcp port. 175 176 @param username: 177 @param password: username= and password= form a pair of strings to 178 use when authenticating the remote user. 179 """ 180 181 self.username = username 182 self.password = password 183 184 c = checkers.InMemoryUsernamePasswordDatabaseDontUse() 185 c.addUser(username, password) 186 187 _BaseManhole.__init__(self, port, c, using_ssh=False)
188
189 -class PasswordManhole(_BaseManhole, ComparableMixin):
190 """This Manhole accepts encrypted (ssh) connections, and requires a 191 username and password to authorize access. 192 """ 193 194 compare_attrs = ["port", "username", "password"] 195
196 - def __init__(self, port, username, password):
197 """ 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 202 simple tcp port. 203 204 @param username: 205 @param password: username= and password= form a pair of strings to 206 use when authenticating the remote user. 207 """ 208 209 self.username = username 210 self.password = password 211 212 c = checkers.InMemoryUsernamePasswordDatabaseDontUse() 213 c.addUser(username, password) 214 215 _BaseManhole.__init__(self, port, c)
216
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.""" 222 223 compare_attrs = ["port", "keyfile"] 224
225 - def __init__(self, port, keyfile):
226 """ 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 231 simple tcp port. 232 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 . 237 """ 238 239 # TODO: expanduser this, and make it relative to the buildmaster's 240 # basedir 241 self.keyfile = keyfile 242 c = AuthorizedKeysChecker(keyfile) 243 _BaseManhole.__init__(self, port, c)
244
245 -class ArbitraryCheckerManhole(_BaseManhole, ComparableMixin):
246 """This Manhole accepts ssh connections, but uses an arbitrary 247 user-supplied 'checker' object to perform authentication.""" 248 249 compare_attrs = ["port", "checker"] 250
251 - def __init__(self, port, checker):
252 """ 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 257 simple tcp port. 258 259 @param checker: an instance of a twisted.cred 'checker' which will 260 perform authentication 261 """ 262 263 _BaseManhole.__init__(self, port, checker)
264