9 from select import select
12 from novnc.websocket import WebSocketServer
14 from invirt.config import structs as config
15 from optparse import OptionParser
17 def drop_privileges(uid_name='nobody', gid_name='nogroup'):
21 # Get the uid/gid from the name
22 uid = pwd.getpwnam(uid_name).pw_uid
23 gid = grp.getgrnam(gid_name).gr_gid
25 # Try setting the new uid/gid
29 # Ensure a very convervative umask
34 _RESTRICTED_SERVER_CIPHERS = (
35 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
36 'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:!aNULL:'
37 '!eNULL:!MD5:!DSS:!RC4'
40 @contextlib.contextmanager
44 class WebSocketProxy(WebSocketServer):
46 Proxy traffic from a WebSockets client to an Invirt VNC server,
47 doing the auth handshake for the client.
50 def __init__(self, user=None, group=None, *args, **kwargs):
51 super(WebSocketProxy, self).__init__(*args, **kwargs)
54 self.server_cas = None
57 super(WebSocketProxy, self).started()
59 cert = open(self.cert).read()
60 key = open(self.key).read()
62 for h in config.hosts:
63 cas += invirt.remctl.remctl(config.remote.hostname, "web", "vnccert", h.hostname)
64 drop_privileges(self.user, self.group)
65 self.cert_tf = tempfile.NamedTemporaryFile()
66 self.cert_tf.write(cert)
68 self.cert = self.cert_tf.name
69 self.key_tf = tempfile.NamedTemporaryFile()
70 self.key_tf.write(key)
72 self.key = self.key_tf.name
73 self.server_cafile = tempfile.NamedTemporaryFile()
74 self.server_cafile.write(cas)
75 self.server_cafile.flush()
76 self.server_cas = self.server_cafile.name
79 url = urlparse.urlparse(self.path)
80 query = urlparse.parse_qs(url.query)
82 host = query.get('host', [None])[-1]
83 vmname = query.get('vmname', [None])[-1]
84 token = query.get('token', [None])[-1]
87 target_port = config.vnc.base_port
89 for h in config.hosts:
90 if h.hostname == host:
94 raise Exception("host not found")
96 raise Exception("vmname not provided")
98 raise Exception("token not provided")
100 tsock = self.socket(target_host, target_port, connect=True)
102 server_cas = self.server_cas
105 ctx = tempfile.NamedTemporaryFile()
109 cadata = invirt.remctl.remctl(config.remote.hostname, "web", "vnccert", host)
112 server_cas = cafile.name
114 # TODO: Use ssl.create_default_context when we move to Python >=2.7.9
115 tsock = ssl.wrap_socket(tsock, ca_certs=server_cas, cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_SSLv23, ciphers=_RESTRICTED_SERVER_CIPHERS)
119 extra_data = self.do_auth_handshake(tsock, vmname, token)
120 self.do_proxy(tsock, extra_data)
123 tsock.shutdown(socket.SHUT_RDWR)
125 self.vmsg("%s:%s: Target closed" % (
126 target_host, target_port))
129 def do_auth_handshake(self, target, vmname, token):
130 target.send("CONNECTVNC %s VNCProxy/1.0\r\nAuth-token: %s\r\n\r\n" % (vmname, token))
131 data = target.recv(128)
132 if data.startswith("VNCProxy/1.0 200 "):
134 return data[data.find("\n")+3:]
137 raise Exception(data)
139 def do_proxy(self, target, extra_data):
141 Proxy client WebSocket to normal target socket.
146 rlist = [self.client, target]
149 tqueue.append(extra_data)
154 if tqueue: wlist.append(target)
155 if cqueue or c_pend: wlist.append(self.client)
156 ins, outs, excepts = select(rlist, wlist, [], 1)
157 if excepts: raise Exception("Socket exception")
160 # Send queued client data to the target
162 sent = target.send(dat)
164 # requeue the remaining data
165 tqueue.insert(0, dat[sent:])
169 # Receive target data, encode it and queue for client
170 buf = target.recv(self.buffer_size)
171 if len(buf) == 0: raise self.EClose("Target closed")
176 if self.client in outs:
177 # Send queued target data to the client
178 c_pend = self.send_frames(cqueue)
183 if self.client in ins:
184 # Receive client data, decode it, and queue for target
185 bufs, closed = self.recv_frames()
189 # TODO: What about blocking on client socket?
191 raise self.EClose(closed)
193 if __name__ == '__main__':
194 parser = OptionParser()
195 parser.add_option("-u", "--user", dest="user", default="nobody",
196 help="user to drop privileges to", metavar="USER")
197 parser.add_option("-g", "--group", dest="group", default="nogroup",
198 help="group to drop privileges to", metavar="GROUP")
200 (options, args) = parser.parse_args()
202 server = WebSocketProxy(cert="/etc/apache2/ssl/server.crt",
203 key="/etc/apache2/ssl/server.key",
204 listen_port=config.vnc.novnc_port,
209 server.start_server()