Drop privileges in VNC proxy if requested
authorQuentin Smith <quentin@mit.edu>
Sun, 17 Jun 2018 03:27:35 +0000 (23:27 -0400)
committerQuentin Smith <quentin@mit.edu>
Sun, 17 Jun 2018 03:28:54 +0000 (23:28 -0400)
files/usr/bin/invirt-novnc-wsproxy

index f66aaba..708a9b1 100755 (executable)
@@ -1,5 +1,9 @@
 #!/usr/bin/env python
 
+import os
+import pwd
+import grp
+import contextlib
 import socket
 import ssl
 from select import select
@@ -8,6 +12,23 @@ import urlparse
 from novnc.websocket import WebSocketServer
 import invirt.remctl
 from invirt.config import structs as config
+from optparse import OptionParser
+
+def drop_privileges(uid_name='nobody', gid_name='nogroup'):
+    if os.getuid() != 0:
+        return
+
+    # Get the uid/gid from the name
+    uid = pwd.getpwnam(uid_name).pw_uid
+    gid = grp.getgrnam(gid_name).gr_gid
+
+    # Try setting the new uid/gid
+    os.setgid(gid)
+    os.setuid(uid)
+
+    # Ensure a very convervative umask
+    new_umask = 077
+    os.umask(new_umask)
 
 # From Python >=2.7.9
 _RESTRICTED_SERVER_CIPHERS = (
@@ -16,12 +37,44 @@ _RESTRICTED_SERVER_CIPHERS = (
     '!eNULL:!MD5:!DSS:!RC4'
 )
 
+@contextlib.contextmanager
+def noop():
+    yield
+
 class WebSocketProxy(WebSocketServer):
     """
     Proxy traffic from a WebSockets client to an Invirt VNC server,
     doing the auth handshake for the client.
     """
 
+    def __init__(self, user=None, group=None, *args, **kwargs):
+        super(WebSocketProxy, self).__init__(*args, **kwargs)
+        self.user = user
+        self.group = group
+        self.server_cas = None
+
+    def started(self):
+        super(WebSocketProxy, self).started()
+        if self.user:
+            cert = open(self.cert).read()
+            key = open(self.key).read()
+            cas = ""
+            for h in config.hosts:
+                cas += invirt.remctl.remctl(config.remote.hostname, "web", "vnccert", h.hostname)
+            drop_privileges(self.user, self.group)
+            self.cert_tf = tempfile.NamedTemporaryFile()
+            self.cert_tf.write(cert)
+            self.cert_tf.flush()
+            self.cert = self.cert_tf.name
+            self.key_tf = tempfile.NamedTemporaryFile()
+            self.key_tf.write(key)
+            self.key_tf.flush()
+            self.key = self.key_tf.name
+            self.server_cafile = tempfile.NamedTemporaryFile()
+            self.server_cafile.write(cas)
+            self.server_cafile.flush()
+            self.server_cas = self.server_cafile.name
+
     def new_client(self):
         url = urlparse.urlparse(self.path)
         query = urlparse.parse_qs(url.query)
@@ -46,13 +99,20 @@ class WebSocketProxy(WebSocketServer):
 
         tsock = self.socket(target_host, target_port, connect=True)
 
-        with tempfile.NamedTemporaryFile() as cafile:
-            cadata = invirt.remctl.remctl(config.remote.hostname, "web", "vnccert", host)
-            cafile.write(cadata)
-            cafile.flush()
+        server_cas = self.server_cas
+        ctx = noop()
+        if not server_cas:
+            ctx = tempfile.NamedTemporaryFile()
+
+        with ctx as cafile:
+            if not server_cas:
+                cadata = invirt.remctl.remctl(config.remote.hostname, "web", "vnccert", host)
+                cafile.write(cadata)
+                cafile.flush()
+                server_cas = cafile.name
 
             # TODO: Use ssl.create_default_context when we move to Python >=2.7.9
-            tsock = ssl.wrap_socket(tsock, ca_certs=cafile.name, cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_SSLv23, ciphers=_RESTRICTED_SERVER_CIPHERS)
+            tsock = ssl.wrap_socket(tsock, ca_certs=server_cas, cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_SSLv23, ciphers=_RESTRICTED_SERVER_CIPHERS)
 
             # Start proxying
             try:
@@ -131,8 +191,19 @@ class WebSocketProxy(WebSocketServer):
                     raise self.EClose(closed)
 
 if __name__ == '__main__':
+    parser = OptionParser()
+    parser.add_option("-u", "--user", dest="user", default="nobody",
+                      help="user to drop privileges to", metavar="USER")
+    parser.add_option("-g", "--group", dest="group", default="nogroup",
+                      help="group to drop privileges to", metavar="GROUP")
+
+    (options, args) = parser.parse_args()
+
     server = WebSocketProxy(cert="/etc/apache2/ssl/server.crt",
                             key="/etc/apache2/ssl/server.key",
                             listen_port=config.vnc.novnc_port,
-                            ssl_only=True)
+                            ssl_only=True,
+                            user=options.user,
+                            group=options.group,
+    )
     server.start_server()