pythonpython-3.xrpcrpyc

How to implement username password authenticator for RPyC server


I am trying to secure my RpyC server connections through username and password. The documentation indeed shows an example, but it is too brief. No details were given on how exactly the password is passed from the client-side. Anyone figured out how to do that? Thanks in advance.


Solution

  • Answering my own question:

    I had to override some internal methods of RPyC on the client side to achieve the desired behaviour. I don't know if much cleaner solution exists, but this seems to be a plausible one.

    Server:

    import rpyc
    from rpyc.utils.authenticators import AuthenticationError
    
    def magic_word_authenticator(sock):
        if sock.recv(5).decode() != "Ma6ik":
            raise AuthenticationError("wrong magic word")
        return sock, None
    
    class SecuredService(rpyc.Service):
    
        def exposed_secured_op(self):
            return 'Secret String'
    
    rpyc.ThreadedServer(
        service=SecuredService, hostname='localhost',
        port=18812, authenticator=magic_word_authenticator
    ).start()
    

    Client:

    import rpyc
    import traceback
    
    
    class AuthSocketStream(rpyc.SocketStream):
    
        @classmethod
        def connect(cls, *args, authorizer=None, **kwargs):
            stream_obj =  super().connect(*args, **kwargs)
    
            if callable(authorizer):
                authorizer(stream_obj.sock)
    
            return stream_obj
    
    
    def rpyc_connect(host, port, service=rpyc.VoidService, config={}, ipv6=False, keepalive=False, authorizer=None):
        s = AuthSocketStream.connect(
                host, port, ipv6=ipv6, keepalive=keepalive,
                authorizer=authorizer
        )
    
        return rpyc.connect_stream(s, service, config)
    
    print('With correct authorizer')
    
    conn1 = rpyc_connect(
            'localhost', 18812, authorizer=lambda sock: sock.send('Ma6ik'.encode())
    )
    
    print(conn1.root.secured_op())
    
    print('With wrong authorizer')
    
    conn2 = rpyc_connect(
            'localhost', 18812, authorizer=lambda sock: sock.send('Invalid'.encode())
    )
    
    try:
        conn2.root
    except Exception:
        print(traceback.format_exc())
    
    
    print('With no authorizer')
    
    conn3 = rpyc_connect(
            'localhost', 18812
    )
    
    try:
        conn3.root
    except Exception:
        print(traceback.format_exc())
    

    Client Console Log:

    With correct authorizer
    Secret String
    With wrong authorizer
    Traceback (most recent call last):
      File "/home/client.py", line 40, in <module>
        conn2.root
      File "/usr/lib/python3.10/site-packages/rpyc/core/protocol.py", line 507, in root
        self._remote_root = self.sync_request(consts.HANDLE_GETROOT)
      File "/usr/lib/python3.10/site-packages/rpyc/core/protocol.py", line 474, in sync_request
        return self.async_request(handler, *args, timeout=timeout).value
      File "/usr/lib/python3.10/site-packages/rpyc/core/async_.py", line 101, in value
        self.wait()
      File "/usr/lib/python3.10/site-packages/rpyc/core/async_.py", line 48, in wait
        self._conn.serve(self._ttl)
      File "/usr/lib/python3.10/site-packages/rpyc/core/protocol.py", line 387, in serve
        data = self._channel.poll(timeout) and self._channel.recv()
      File "/usr/lib/python3.10/site-packages/rpyc/core/channel.py", line 55, in recv
        header = self.stream.read(self.FRAME_HEADER.size)
      File "/usr/lib/python3.10/site-packages/rpyc/core/stream.py", line 260, in read
        raise EOFError("connection closed by peer")
    EOFError: connection closed by peer
    
    With no authorizer
    Traceback (most recent call last):
      File "/home/client.py", line 52, in <module>
        conn3.root
      File "/usr/lib/python3.10/site-packages/rpyc/core/protocol.py", line 507, in root
        self._remote_root = self.sync_request(consts.HANDLE_GETROOT)
      File "/usr/lib/python3.10/site-packages/rpyc/core/protocol.py", line 474, in sync_request
        return self.async_request(handler, *args, timeout=timeout).value
      File "/usr/lib/python3.10/site-packages/rpyc/core/async_.py", line 101, in value
        self.wait()
      File "/usr/lib/python3.10/site-packages/rpyc/core/async_.py", line 48, in wait
        self._conn.serve(self._ttl)
      File "/usr/lib/python3.10/site-packages/rpyc/core/protocol.py", line 387, in serve
        data = self._channel.poll(timeout) and self._channel.recv()
      File "/usr/lib/python3.10/site-packages/rpyc/core/channel.py", line 55, in recv
        header = self.stream.read(self.FRAME_HEADER.size)
      File "/usr/lib/python3.10/site-packages/rpyc/core/stream.py", line 260, in read
        raise EOFError("connection closed by peer")
    EOFError: connection closed by peer