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

Source Code for Module buildbot.manhole

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 15   
 16   
 17  import os.path 
 18  import binascii, base64 
 19  from twisted.python import log 
 20  from twisted.application import service, strports 
 21  from twisted.cred import checkers, portal 
 22  from twisted.conch import manhole, telnet, manhole_ssh, checkers as conchc 
 23  from twisted.conch.insults import insults 
 24  from twisted.internet import protocol 
 25   
 26  from buildbot.util import ComparableMixin 
 27  from zope.interface import implements # requires Twisted-2.0 or later 
 28   
 29  # makeTelnetProtocol and _TelnetRealm are for the TelnetManhole 
 30   
31 -class makeTelnetProtocol:
32 # this curries the 'portal' argument into a later call to 33 # TelnetTransport()
34 - def __init__(self, portal):
35 self.portal = portal
36
37 - def __call__(self):
38 auth = telnet.AuthenticatingTelnetProtocol 39 return telnet.TelnetTransport(auth, self.portal)
40
41 -class _TelnetRealm:
42 implements(portal.IRealm) 43
44 - def __init__(self, namespace_maker):
45 self.namespace_maker = namespace_maker
46
47 - def requestAvatar(self, avatarId, *interfaces):
48 if telnet.ITelnetProtocol in interfaces: 49 namespace = self.namespace_maker() 50 p = telnet.TelnetBootstrapProtocol(insults.ServerProtocol, 51 manhole.ColoredManhole, 52 namespace) 53 return (telnet.ITelnetProtocol, p, lambda: None) 54 raise NotImplementedError()
55 56
57 -class chainedProtocolFactory:
58 # this curries the 'namespace' argument into a later call to 59 # chainedProtocolFactory()
60 - def __init__(self, namespace):
61 self.namespace = namespace
62
63 - def __call__(self):
64 return insults.ServerProtocol(manhole.ColoredManhole, self.namespace)
65
66 -class AuthorizedKeysChecker(conchc.SSHPublicKeyDatabase):
67 """Accept connections using SSH keys from a given file. 68 69 SSHPublicKeyDatabase takes the username that the prospective client has 70 requested and attempts to get a ~/.ssh/authorized_keys file for that 71 username. This requires root access, so it isn't as useful as you'd 72 like. 73 74 Instead, this subclass looks for keys in a single file, given as an 75 argument. This file is typically kept in the buildmaster's basedir. The 76 file should have 'ssh-dss ....' lines in it, just like authorized_keys. 77 """ 78
79 - def __init__(self, authorized_keys_file):
80 self.authorized_keys_file = os.path.expanduser(authorized_keys_file)
81
82 - def checkKey(self, credentials):
83 f = open(self.authorized_keys_file) 84 for l in f.readlines(): 85 l2 = l.split() 86 if len(l2) < 2: 87 continue 88 try: 89 if base64.decodestring(l2[1]) == credentials.blob: 90 return 1 91 except binascii.Error: 92 continue 93 return 0
94 95
96 -class _BaseManhole(service.MultiService):
97 """This provides remote access to a python interpreter (a read/exec/print 98 loop) embedded in the buildmaster via an internal SSH server. This allows 99 detailed inspection of the buildmaster state. It is of most use to 100 buildbot developers. Connect to this by running an ssh client. 101 """ 102
103 - def __init__(self, port, checker, using_ssh=True):
104 """ 105 @type port: string or int 106 @param port: what port should the Manhole listen on? This is a 107 strports specification string, like 'tcp:12345' or 108 'tcp:12345:interface=127.0.0.1'. Bare integers are treated as a 109 simple tcp port. 110 111 @type checker: an object providing the 112 L{twisted.cred.checkers.ICredentialsChecker} interface 113 @param checker: if provided, this checker is used to authenticate the 114 client instead of using the username/password scheme. You must either 115 provide a username/password or a Checker. Some useful values are:: 116 import twisted.cred.checkers as credc 117 import twisted.conch.checkers as conchc 118 c = credc.AllowAnonymousAccess # completely open 119 c = credc.FilePasswordDB(passwd_filename) # file of name:passwd 120 c = conchc.UNIXPasswordDatabase # getpwnam() (probably /etc/passwd) 121 122 @type using_ssh: bool 123 @param using_ssh: If True, accept SSH connections. If False, accept 124 regular unencrypted telnet connections. 125 """ 126 127 # unfortunately, these don't work unless we're running as root 128 #c = credc.PluggableAuthenticationModulesChecker: PAM 129 #c = conchc.SSHPublicKeyDatabase() # ~/.ssh/authorized_keys 130 # and I can't get UNIXPasswordDatabase to work 131 132 service.MultiService.__init__(self) 133 if type(port) is int: 134 port = "tcp:%d" % port 135 self.port = port # for comparison later 136 self.checker = checker # to maybe compare later 137 138 def makeNamespace(): 139 # close over 'self' so we can get access to .parent later 140 master = self.parent 141 namespace = { 142 'master': master, 143 'status': master.getStatus(), 144 } 145 return namespace
146 147 def makeProtocol(): 148 namespace = makeNamespace() 149 p = insults.ServerProtocol(manhole.ColoredManhole, namespace) 150 return p
151 152 self.using_ssh = using_ssh 153 if using_ssh: 154 r = manhole_ssh.TerminalRealm() 155 r.chainedProtocolFactory = makeProtocol 156 p = portal.Portal(r, [self.checker]) 157 f = manhole_ssh.ConchFactory(p) 158 else: 159 r = _TelnetRealm(makeNamespace) 160 p = portal.Portal(r, [self.checker]) 161 f = protocol.ServerFactory() 162 f.protocol = makeTelnetProtocol(p) 163 s = strports.service(self.port, f) 164 s.setServiceParent(self) 165 166
167 - def startService(self):
168 service.MultiService.startService(self) 169 if self.using_ssh: 170 via = "via SSH" 171 else: 172 via = "via telnet" 173 log.msg("Manhole listening %s on port %s" % (via, self.port))
174 175
176 -class TelnetManhole(_BaseManhole, ComparableMixin):
177 """This Manhole accepts unencrypted (telnet) connections, and requires a 178 username and password authorize access. You are encouraged to use the 179 encrypted ssh-based manhole classes instead.""" 180 181 compare_attrs = ["port", "username", "password"] 182
183 - def __init__(self, port, username, password):
184 """ 185 @type port: string or int 186 @param port: what port should the Manhole listen on? This is a 187 strports specification string, like 'tcp:12345' or 188 'tcp:12345:interface=127.0.0.1'. Bare integers are treated as a 189 simple tcp port. 190 191 @param username: 192 @param password: username= and password= form a pair of strings to 193 use when authenticating the remote user. 194 """ 195 196 self.username = username 197 self.password = password 198 199 c = checkers.InMemoryUsernamePasswordDatabaseDontUse() 200 c.addUser(username, password) 201 202 _BaseManhole.__init__(self, port, c, using_ssh=False)
203
204 -class PasswordManhole(_BaseManhole, ComparableMixin):
205 """This Manhole accepts encrypted (ssh) connections, and requires a 206 username and password to authorize access. 207 """ 208 209 compare_attrs = ["port", "username", "password"] 210
211 - def __init__(self, port, username, password):
212 """ 213 @type port: string or int 214 @param port: what port should the Manhole listen on? This is a 215 strports specification string, like 'tcp:12345' or 216 'tcp:12345:interface=127.0.0.1'. Bare integers are treated as a 217 simple tcp port. 218 219 @param username: 220 @param password: username= and password= form a pair of strings to 221 use when authenticating the remote user. 222 """ 223 224 self.username = username 225 self.password = password 226 227 c = checkers.InMemoryUsernamePasswordDatabaseDontUse() 228 c.addUser(username, password) 229 230 _BaseManhole.__init__(self, port, c)
231
232 -class AuthorizedKeysManhole(_BaseManhole, ComparableMixin):
233 """This Manhole accepts ssh connections, and requires that the 234 prospective client have an ssh private key that matches one of the public 235 keys in our authorized_keys file. It is created with the name of a file 236 that contains the public keys that we will accept.""" 237 238 compare_attrs = ["port", "keyfile"] 239
240 - def __init__(self, port, keyfile):
241 """ 242 @type port: string or int 243 @param port: what port should the Manhole listen on? This is a 244 strports specification string, like 'tcp:12345' or 245 'tcp:12345:interface=127.0.0.1'. Bare integers are treated as a 246 simple tcp port. 247 248 @param keyfile: the name of a file (relative to the buildmaster's 249 basedir) that contains SSH public keys of authorized 250 users, one per line. This is the exact same format 251 as used by sshd in ~/.ssh/authorized_keys . 252 """ 253 254 # TODO: expanduser this, and make it relative to the buildmaster's 255 # basedir 256 self.keyfile = keyfile 257 c = AuthorizedKeysChecker(keyfile) 258 _BaseManhole.__init__(self, port, c)
259
260 -class ArbitraryCheckerManhole(_BaseManhole, ComparableMixin):
261 """This Manhole accepts ssh connections, but uses an arbitrary 262 user-supplied 'checker' object to perform authentication.""" 263 264 compare_attrs = ["port", "checker"] 265
266 - def __init__(self, port, checker):
267 """ 268 @type port: string or int 269 @param port: what port should the Manhole listen on? This is a 270 strports specification string, like 'tcp:12345' or 271 'tcp:12345:interface=127.0.0.1'. Bare integers are treated as a 272 simple tcp port. 273 274 @param checker: an instance of a twisted.cred 'checker' which will 275 perform authentication 276 """ 277 278 _BaseManhole.__init__(self, port, checker)
279